Data loading

getwd()
[1] "/Users/joshuachang/Desktop/Stats 432 R_project/automobile"
setwd("/Users/joshuachang/Desktop/Stats 432 R_project/automobile")
column_names <- c("symboling", "normalized_losses", "make", "fuel_type", "aspiration",
                  "num_doors", "body_style", "drive_wheels", "engine_location",
                  "wheel_base", "length", "width", "height", "curb_weight", "engine_type",
                  "num_cylinders", "engine_size", "fuel_system", "bore", "stroke",
                  "compression_ratio", "horsepower", "peak_rpm", "city_mpg",
                  "highway_mpg", "price")
import_85 = read.csv("imports-85.data", header = FALSE, col.names = column_names, na.strings = "?")
import_85 = na.omit(import_85) 

# # EPA weighted average: 55% city, 45% highway
import_85$combined_mpg <- 0.55 * import_85$city_mpg + 0.45 * import_85$highway_mpg

Package Loading

library(leaps)
library(ISLR2) 
library(glmnet)
library(corrplot)
library(rpart)  
library(rpart.plot)   
library(caret)
library(randomForest)
library(ggplot2)
library(gridExtra)
library(class)
library(rmarkdown)
# Data Dimensional Analysis
head(import_85)
dim(import_85)
[1] 159  27
names(import_85)
 [1] "symboling"         "normalized_losses" "make"              "fuel_type"         "aspiration"       
 [6] "num_doors"         "body_style"        "drive_wheels"      "engine_location"   "wheel_base"       
[11] "length"            "width"             "height"            "curb_weight"       "engine_type"      
[16] "num_cylinders"     "engine_size"       "fuel_system"       "bore"              "stroke"           
[21] "compression_ratio" "horsepower"        "peak_rpm"          "city_mpg"          "highway_mpg"      
[26] "price"             "combined_mpg"     

Regression Task: Predicting Car Price

# The goal is to predict vehicle price using variable selection methods such as Ridge Regression, Lasso, 
# and Best Subset Selection. These techniques will help identify the most informative predictors with minimal redundancy. 
# By splitting the dataset into training and test sets, we can evaluate the model's predictive performance.
# The response variable will be price, and all other variables—both numerical and categorical (encoded as dummy variables)—will serve as features.
# Create numeric_data by selecting numeric columns from import_85
numeric_data <- import_85[, sapply(import_85, is.numeric)]
numeric_data <- numeric_data[, c("price", setdiff(names(numeric_data), "price"))]
head(numeric_data)
set.seed(123)  # For reproducibility
train_index <- sample(1:nrow(numeric_data), 0.7 * nrow(numeric_data))
train_data <- numeric_data[train_index, ]
test_data <- numeric_data[-train_index, ]

x_train <- as.matrix(train_data[, -1])  # predictors
y_train <- train_data[, 1]             # price

x_test <- as.matrix(test_data[, -1])
y_test <- test_data[, 1]
# Ridge regression (alpha = 0)
ridge_mod <- cv.glmnet(x_train, y_train, alpha = 0)
ridge_best_lambda <- ridge_mod$lambda.min
ridge_pred <- predict(ridge_mod, s = ridge_best_lambda, newx = x_test)
ridge_mse <- mean((ridge_pred - y_test)^2)
# Lasso regression (alpha = 1)
lasso_mod <- cv.glmnet(x_train, y_train, alpha = 1)
lasso_best_lambda <- lasso_mod$lambda.min
lasso_pred <- predict(lasso_mod, s = lasso_best_lambda, newx = x_test)
lasso_mse <- mean((lasso_pred - y_test)^2)
library(leaps)

regfit.full <- regsubsets(price ~ ., data = train_data, nvmax = 10, really.big = TRUE)
Warning: 1  linear dependencies found
predict_regsubsets <- function(object, newdata, id) {
  form <- as.formula(object$call[[2]])
  mat <- model.matrix(form, newdata)
  coefi <- coef(object, id = id)
  xvars <- names(coefi)
  mat[, xvars] %*% coefi
}

# Get the actual number of models stored
num_models <- nrow(summary(regfit.full)$which)

# Re-run validation loop with correct limit
val_errors <- sapply(1:num_models, function(i) {
  pred <- predict_regsubsets(regfit.full, test_data, i)
  mean((test_data$price - pred)^2)
})

# Select best
best_subset_mse <- min(val_errors)
best_subset_size <- which.min(val_errors)
best_subset_mse = min(val_errors)
best_subset_size = which.min(val_errors)

cat("Best Subset MSE (size =", best_subset_size, "):", round(best_subset_mse, 2), "\n")
Best Subset MSE (size = 4 ): 8823901 
cat("Ridge Regression Test MSE:", round(ridge_mse, 2), "\n")
Ridge Regression Test MSE: 9984899 
cat("Lasso Regression Test MSE:", round(lasso_mse, 2), "\n")
Lasso Regression Test MSE: 9001026 

We applied Ridge and Lasso regression methods to predict vehicle price using a dataset with numeric and dummy variables. Ridge regression shrinks all coefficients, which helps when predictors are highly correlated. Lasso regression also performs shrinkage but can set some coefficients to zero, thus doing variable selection. By comparing the test Mean Squared Errors (MSE), we selected the model with the lowest MSE. We therefore select Best Subset Selection as the optimal regression method due to its superior predictive accuracy in this case.

coef(regfit.full, id = 4)
      (Intercept) normalized_losses             width       curb_weight        horsepower 
     -47278.22053          15.64827         547.83856           7.39948          28.54066 

The best subset regression model selected 4 predictors: normalized losses, width, curb weight, and horsepower. These variables collectively yielded the lowest test Mean Squared Error (MSE), indicating strong predictive power for vehicle price. The inclusion of normalized losses likely reflects risk-based pricing adjustments, while width and curb weight capture vehicle size and mass — features typically associated with luxury or performance. Horsepower, a direct indicator of engine strength, is also positively associated with price. Together, these predictors form a parsimonious yet effective model for estimating vehicle prices using numeric data only.

# Subset the numeric data to relevant variables
vars <- c("price", "normalized_losses", "width", "curb_weight", "horsepower")
subset_data <- import_85[, vars]

# Remove rows with missing values
subset_data <- na.omit(subset_data)

# Correlation matrix
cor_matrix <- cor(subset_data)
print(round(cor_matrix, 2))
                  price normalized_losses width curb_weight horsepower
price              1.00              0.20  0.84        0.89       0.76
normalized_losses  0.20              1.00  0.11        0.13       0.29
width              0.84              0.11  1.00        0.87       0.68
curb_weight        0.89              0.13  0.87        1.00       0.79
horsepower         0.76              0.29  0.68        0.79       1.00

Regression Task: Identifying Factors Affecting Fuel Efficiency

#This task aims to explore which vehicle specifications contribute most to fuel efficiency (measured in miles per gallon). We will treat city_mpg and highway_mpg as separate response variables and also consider a combined efficiency metric. The focus will be on engineering-related features such as compression_ratio, num_cylinders, and engine_size. The outcome of this analysis can be applied to real-world vehicle purchasing decisions, especially for those seeking fuel-efficient cars.
# We only focus on engineering-related variables
engineering_vars = c("compression_ratio", "engine_size", "num_cylinders","horsepower", "curb_weight", "bore", "stroke")

# Convert num_cylinders from factor (e.g., "four", "six") to numeric (e.g., 4, 6)
cyl_map <- c("two"=2, "three"=3, "four"=4, "five"=5, "six"=6, "eight"=8, "twelve"=12)
import_85$num_cylinders <- cyl_map[as.character(import_85$num_cylinders)]
engineering_data = import_85[, c("combined_mpg", engineering_vars)]
engineering_data = na.omit(engineering_data)
set.seed(432)
# K-fold cross-validation
k = 10
folds = sample(1:k, nrow(engineering_data), replace = TRUE)
cv_errors = matrix(NA, k, 7)

for (j in 1:k) {
  regfit_cv = regsubsets(combined_mpg ~ ., data = engineering_data[folds != j, ], nvmax = 7)
  for (i in 1:7) {
    pred = predict_regsubsets(regfit_cv, engineering_data[folds == j, ], id = i)
    y_true = engineering_data$combined_mpg[folds == j]
    cv_errors[j, i] = mean((y_true - pred)^2)
  }
}
mean_cv_errors = colMeans(cv_errors)
plot(1:7, mean_cv_errors, type = "b", xlab = "Number of Variables", ylab = "CV MSE")
points(which.min(mean_cv_errors), mean_cv_errors[which.min(mean_cv_errors)], col = "blue", pch = 19)

best_cv_size = which.min(mean_cv_errors)
set.seed(432)
# Fit model for combined MPG
regfit_fwd = regsubsets(combined_mpg ~ ., data = engineering_data,
                         nvmax = 7, method = "forward")
regfit_bwd = regsubsets(combined_mpg ~ ., data = engineering_data,
                         nvmax =7, method = "backward")
coef(regfit_fwd, best_cv_size)    # Forward stepwise
      (Intercept) compression_ratio       engine_size     num_cylinders        horsepower       curb_weight 
     59.001893938       0.527786685       0.065690062      -1.061641644      -0.066140113      -0.009538737 
             bore 
     -2.684084489 
coef(regfit_bwd, best_cv_size)    # Backward stepwise
      (Intercept) compression_ratio       engine_size     num_cylinders        horsepower       curb_weight 
     59.001893938       0.527786685       0.065690062      -1.061641644      -0.066140113      -0.009538737 
             bore 
     -2.684084489 
# Set seed and split data
set.seed(432)
sample_size = floor(0.8 * nrow(engineering_data))  # 80% training
train_index = sample(seq_len(nrow(engineering_data)), size = sample_size)
train_data = engineering_data[train_index, ]
test_data =engineering_data[-train_index, ]

Linear Regression

# Fit a linear model using the top 4 variables
top_4_model = lm(combined_mpg ~ horsepower + engine_size + curb_weight + compression_ratio,
                  data = train_data)
# Predict on the test set
test_predictions = predict(top_4_model, newdata = test_data)

test_mse <- mean((test_data$combined_mpg - test_predictions)^2)
test_mse
[1] 10.84735

Linear Regression + one 2-way interaction term

main_effects = "horsepower + engine_size + curb_weight + compression_ratio"

# All possible 2-way interactions
interaction_terms = c("horsepower:engine_size", "horsepower:curb_weight", 
                       "horsepower:compression_ratio", "engine_size:curb_weight", 
                       "engine_size:compression_ratio", "curb_weight:compression_ratio")
results = data.frame(Interaction = character(), Test_MSE = numeric(), stringsAsFactors = FALSE)

# Loop over each interaction term
for (term in interaction_terms) {
  full_formula = as.formula(paste("combined_mpg ~", paste(c(main_effects, term), collapse = " + ")))
  
  # Fit model with only one interaction + 4 top variables 
  model = lm(full_formula, data = train_data)
  
  preds = predict(model, newdata = test_data)
  mse = mean((test_data$combined_mpg - preds)^2)
  results = rbind(results, data.frame(Interaction = term, Test_MSE = mse))
}

# Sort and print
results_sorted <- results[order(results$Test_MSE), ]
print(results_sorted)
results_sorted[1, ]
top_4_interaction_1 =  lm(combined_mpg ~ horsepower + engine_size + curb_weight +compression_ratio + horsepower:curb_weight, data = train_data)
summary(top_4_interaction_1)

Call:
lm(formula = combined_mpg ~ horsepower + engine_size + curb_weight + 
    compression_ratio + horsepower:curb_weight, data = train_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.3827 -1.2391 -0.4331  1.2107  8.8016 

Coefficients:
                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)             6.939e+01  4.021e+00  17.258  < 2e-16 ***
horsepower             -2.911e-01  4.266e-02  -6.825 3.72e-10 ***
engine_size            -1.037e-02  1.797e-02  -0.577    0.565    
curb_weight            -1.531e-02  1.520e-03 -10.075  < 2e-16 ***
compression_ratio       5.553e-01  6.192e-02   8.968 4.60e-15 ***
horsepower:curb_weight  8.404e-05  1.559e-05   5.390 3.54e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.185 on 121 degrees of freedom
Multiple R-squared:  0.8704,    Adjusted R-squared:  0.8651 
F-statistic: 162.6 on 5 and 121 DF,  p-value: < 2.2e-16
top_4_interaction_2 = lm(combined_mpg ~ horsepower + engine_size + curb_weight +compression_ratio + horsepower:engine_size, data = train_data)
summary(top_4_interaction_2)

Call:
lm(formula = combined_mpg ~ horsepower + engine_size + curb_weight + 
    compression_ratio + horsepower:engine_size, data = train_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-6.1769 -1.1204 -0.3605  1.0326  8.9674 

Coefficients:
                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)            62.0888035  2.8473454  21.806  < 2e-16 ***
horsepower             -0.1937826  0.0271814  -7.129 7.98e-11 ***
engine_size            -0.1287899  0.0358810  -3.589  0.00048 ***
curb_weight            -0.0074697  0.0010928  -6.835 3.55e-10 ***
compression_ratio       0.6074130  0.0624226   9.731  < 2e-16 ***
horsepower:engine_size  0.0010721  0.0002079   5.157 9.93e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.203 on 121 degrees of freedom
Multiple R-squared:  0.8683,    Adjusted R-squared:  0.8628 
F-statistic: 159.5 on 5 and 121 DF,  p-value: < 2.2e-16
top_4_interaction_3 = lm(combined_mpg ~ horsepower + engine_size + curb_weight +compression_ratio + engine_size:curb_weight, data = train_data)
summary(top_4_interaction_3)

Call:
lm(formula = combined_mpg ~ horsepower + engine_size + curb_weight + 
    compression_ratio + engine_size:curb_weight, data = train_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.4869 -1.2695 -0.3408  1.3016  9.0940 

Coefficients:
                          Estimate Std. Error t value Pr(>|t|)    
(Intercept)              6.264e+01  3.995e+00  15.682  < 2e-16 ***
horsepower              -6.203e-02  1.512e-02  -4.103 7.44e-05 ***
engine_size             -1.139e-01  4.421e-02  -2.576 0.011185 *  
curb_weight             -1.337e-02  1.551e-03  -8.618 3.08e-14 ***
compression_ratio        5.739e-01  6.520e-02   8.801 1.14e-14 ***
engine_size:curb_weight  4.210e-05  1.141e-05   3.690 0.000338 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.307 on 121 degrees of freedom
Multiple R-squared:  0.8556,    Adjusted R-squared:  0.8496 
F-statistic: 143.4 on 5 and 121 DF,  p-value: < 2.2e-16

Poly Regression

# Initialize storage
test_mse_poly = numeric(9)

# Loop through degrees 1 to 9
for (d in 1:9) {
  formula_poly = as.formula(paste(
    "combined_mpg ~", 
    paste0("I(horsepower^", d, ") + ",
           "I(engine_size^", d, ") + ",
           "I(curb_weight^", d, ") + ",
           "I(compression_ratio^", d, ")")
  ))
  
  # Fit model
  poly_model = lm(formula_poly, data = train_data)
  
  # Predict
  pred_poly = predict(poly_model, newdata = test_data)
  
  # Compute test MSE
  test_mse_poly[d] = mean((test_data$combined_mpg - pred_poly)^2)
}

# Find the degree with lowest test MSE
best_degree = which.min(test_mse_poly)
best_mse = min(test_mse_poly)

# Plot MSE vs polynomial degree
plot(1:9, test_mse_poly, type = "b", pch = 19,
     xlab = "Polynomial Degree", ylab = "Test MSE",
     main = "Test MSE vs. Polynomial Degree")
points(which.min(test_mse_poly), min(test_mse_poly), col = "red", pch = 19)


cat("Lowest MSE is", round(best_mse, 4), "at polynomial degree", best_degree, "\n")
Lowest MSE is 10.8473 at polynomial degree 1 

Classification Task: Predicting Insurance Risk Level

# Here, we aim to classify a vehicle’s insurance risk level—represented by the symbol score (ranging from -3 to +3)—based on its specifications. Using classification models, we’ll determine how features like car specs and pricing relate to insurance risk. This could provide the basis for a decision system in which cars above a certain risk threshold may be flagged for higher premiums or insurance denial. In this task, symbol will be the response variable. 

Insurance Risk Rating – A “symboling” score ranging from -3 (least risky) to +3 (most risky), indicating how risky a vehicle is relative to its price.

categorical_vars = import_85[, sapply(import_85, function(x) is.factor(x) || is.character(x))]
names(categorical_vars)
[1] "make"            "fuel_type"       "aspiration"      "num_doors"       "body_style"      "drive_wheels"   
[7] "engine_location" "engine_type"     "fuel_system"    

Car Make v.s. Risk Rating

library(ggplot2)
library(dplyr)
ggplot(import_85, aes(x = make, fill = as.factor(symboling))) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent_format()) +
  scale_fill_manual(values = c("-2"="#4575b4", "-1"="#91bfdb", "0"="#e0f3f8", 
                               "1"="#fee090", "2"="#fc8d59", "3"="#d73027")) +
  labs(title = "Proportion of Insurance Risk Ratings by Car Make",
       x = "Car Make", y = "Proportion", fill = "Risk Rating") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Fuel Type v.s. Risk Rating

# Insurance Risk by Fuel Type
ggplot(import_85, aes(x = fuel_type, fill = as.factor(symboling))) +
  geom_bar(position = "dodge") +
  labs(title = "Insurance Risk by Fuel Type", x = "Fuel Type", fill = "Symboling")

Body Style v.s. Risk Rating

# Insurance Risk by Body Style
ggplot(import_85, aes(x = body_style, fill = as.factor(symboling))) +
  geom_bar(position = "fill") +
  labs(title = "Proportion of Insurance Risk by Body Style", y = "Proportion", fill = "Symboling")

Drive Type v.s. Risk Rating

# Risk Rating Distribution by Drive Type
ggplot(import_85, aes(x = drive_wheels, fill = as.factor(symboling))) +
  geom_bar(position = "fill") +
  labs(title = "Risk Rating Distribution by Drive Type", y = "Proportion", fill = "Symboling")

Boxplot for numerical predictors


# Ensure symboling is a factor (for categorical coloring)
import_85$symboling <- as.factor(import_85$symboling)

# Boxplots for key numeric predictors
bp1 <- ggplot(import_85, aes(x = symboling, y = price)) +
  geom_boxplot(fill = "lightblue") +
  labs(title = "Price by Insurance Risk Rating", x = "Symboling", y = "Price") +
  theme_minimal()

bp2 <- ggplot(import_85, aes(x = symboling, y = combined_mpg)) +
  geom_boxplot(fill = "lightgreen") +
  labs(title = "Combined MPG by Insurance Risk Rating", x = "Symboling", y = "Combined MPG") +
  theme_minimal()

bp3 <- ggplot(import_85, aes(x = symboling, y = horsepower)) +
  geom_boxplot(fill = "lightcoral") +
  labs(title = "Horsepower by Insurance Risk Rating", x = "Symboling", y = "Horsepower") +
  theme_minimal()

bp4 <- ggplot(import_85, aes(x = symboling, y = engine_size)) +
  geom_boxplot(fill = "khaki") +
  labs(title = "Engine Size by Insurance Risk Rating", x = "Symboling", y = "Engine Size") +
  theme_minimal()

bp5 <- ggplot(import_85, aes(x = symboling, y = curb_weight)) +
  geom_boxplot(fill = "plum") +
  labs(title = "Curb Weight by Insurance Risk Rating", x = "Symboling", y = "Curb Weight") +
  theme_minimal()

# Arrange plots in a grid
grid.arrange(bp1, bp2, bp3, bp4, bp5, ncol = 2)

set.seed(432) 
# Split into 80% training, 20% testing
train_idx = sample(1:nrow(model_data), 0.8 * nrow(model_data))
train_data = model_data[train_idx, ]
test_data  = model_data[-train_idx, ]

import_85$symboling = as.numeric(as.character(import_85$symboling))  # if it's a factor
import_85$risk = ifelse(import_85$symboling > 0, "High", "Low")
import_85$risk = factor(import_85$risk)

predictors = c("price", "horsepower", "curb_weight", "engine_size", "compression_ratio", "num_cylinders")
model_data = import_85[, c("risk", predictors)]
model_data = na.omit(model_data)

logtistic regression

set.seed(432)
# Fit logistic regression model
logit_mod = glm(risk ~ ., data = model_data, family = "binomial")

# Predict probabilities on test set
prob_pred = predict(logit_mod, newdata = test_data, type = "response")

# Convert probabilities to class predictions
class_pred = ifelse(prob_pred > 0.5, "High", "Low")
class_pred = factor(class_pred, levels = levels(test_data$risk))

# Confusion matrix
conf_matrix = table(Predicted = class_pred, Actual = test_data$risk)
print(conf_matrix)
         Actual
Predicted High Low
     High    3   6
     Low    17   6
# Accuracy
accuracy = mean(class_pred == test_data$risk)
cat("Test Accuracy:", accuracy, "\n")
Test Accuracy: 0.28125 

decision tree

set.seed(432)
tree_model = rpart(risk ~ ., data = train_data, method = "class")
rpart.plot(tree_model)

KNN-classification

set.seed(432)

# Ensure only numeric predictors are used
numeric_predictors = predictors[sapply(train_data[, predictors], is.numeric)]

train_x = scale(train_data[, numeric_predictors])
test_x  = scale(test_data[, numeric_predictors])
train_y = train_data$risk
test_y  = test_data$risk

# Try Different k Values and Plot Accuracy
k_values = 1:20
accuracies = numeric(length(k_values))
for (i in k_values) {
  pred_k = knn(train_x, test_x, train_y, k = i)
  accuracies[i] = mean(pred_k == test_y)
}
plot(k_values, accuracies, type = "b", pch = 19, col = "blue",
     xlab = "k (Number of Neighbors)", ylab = "Accuracy", main = "KNN Accuracy vs k")

best_k = k_values[which.max(accuracies)]
pred_knn = knn(train_x, test_x, train_y, k = best_k)
mean(pred_knn == test_y)
[1] 0.875
confusionMatrix(pred_knn, test_y)
Confusion Matrix and Statistics

          Reference
Prediction High Low
      High   17   1
      Low     3  11
                                          
               Accuracy : 0.875           
                 95% CI : (0.7101, 0.9649)
    No Information Rate : 0.625           
    P-Value [Acc > NIR] : 0.001743        
                                          
                  Kappa : 0.7419          
                                          
 Mcnemar's Test P-Value : 0.617075        
                                          
            Sensitivity : 0.8500          
            Specificity : 0.9167          
         Pos Pred Value : 0.9444          
         Neg Pred Value : 0.7857          
             Prevalence : 0.6250          
         Detection Rate : 0.5312          
   Detection Prevalence : 0.5625          
      Balanced Accuracy : 0.8833          
                                          
       'Positive' Class : High            
                                          

Use Random Forest

set.seed(432)
accuracies = numeric(200)
for (ntree in 1:200) {
  rf_mod = randomForest(risk ~ ., data = train_data, ntree = ntree)
  rf_pred = predict(rf_mod, newdata = test_data)
  accuracies[ntree] = mean(rf_pred == test_data$risk)
}

# Plot
plot(1:200, accuracies, type = "l", col = "blue",
     xlab = "Number of Trees", ylab = "Accuracy",
     main = "Random Forest Accuracy vs Number of Trees")
abline(h = max(accuracies), col = "green", lty = 2)
abline(v = which.max(accuracies), col = "red", lty = 2)


# Find the best number of trees and its accuracy
best_ntree <- which.max(accuracies)
best_accuracy <- max(accuracies)
cat("Best number of trees:", best_ntree, "\n")
Best number of trees: 67 
cat("Highest accuracy:", round(best_accuracy, 4), "\n")
Highest accuracy: 0.9688 
LS0tCnRpdGxlOiAiVmVoZWNsZSBQcmljZSBBbmFseXNpcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQojIyMgRGF0YSBsb2FkaW5nCmBgYHtyfQpnZXR3ZCgpCnNldHdkKCIvVXNlcnMvam9zaHVhY2hhbmcvRGVza3RvcC9TdGF0cyA0MzIgUl9wcm9qZWN0L2F1dG9tb2JpbGUiKQpjb2x1bW5fbmFtZXMgPC0gYygic3ltYm9saW5nIiwgIm5vcm1hbGl6ZWRfbG9zc2VzIiwgIm1ha2UiLCAiZnVlbF90eXBlIiwgImFzcGlyYXRpb24iLAogICAgICAgICAgICAgICAgICAibnVtX2Rvb3JzIiwgImJvZHlfc3R5bGUiLCAiZHJpdmVfd2hlZWxzIiwgImVuZ2luZV9sb2NhdGlvbiIsCiAgICAgICAgICAgICAgICAgICJ3aGVlbF9iYXNlIiwgImxlbmd0aCIsICJ3aWR0aCIsICJoZWlnaHQiLCAiY3VyYl93ZWlnaHQiLCAiZW5naW5lX3R5cGUiLAogICAgICAgICAgICAgICAgICAibnVtX2N5bGluZGVycyIsICJlbmdpbmVfc2l6ZSIsICJmdWVsX3N5c3RlbSIsICJib3JlIiwgInN0cm9rZSIsCiAgICAgICAgICAgICAgICAgICJjb21wcmVzc2lvbl9yYXRpbyIsICJob3JzZXBvd2VyIiwgInBlYWtfcnBtIiwgImNpdHlfbXBnIiwKICAgICAgICAgICAgICAgICAgImhpZ2h3YXlfbXBnIiwgInByaWNlIikKaW1wb3J0Xzg1ID0gcmVhZC5jc3YoImltcG9ydHMtODUuZGF0YSIsIGhlYWRlciA9IEZBTFNFLCBjb2wubmFtZXMgPSBjb2x1bW5fbmFtZXMsIG5hLnN0cmluZ3MgPSAiPyIpCmltcG9ydF84NSA9IG5hLm9taXQoaW1wb3J0Xzg1KSAKCiMgIyBFUEEgd2VpZ2h0ZWQgYXZlcmFnZTogNTUlIGNpdHksIDQ1JSBoaWdod2F5CmltcG9ydF84NSRjb21iaW5lZF9tcGcgPC0gMC41NSAqIGltcG9ydF84NSRjaXR5X21wZyArIDAuNDUgKiBpbXBvcnRfODUkaGlnaHdheV9tcGcKYGBgCgoKIyMjIFBhY2thZ2UgTG9hZGluZwpgYGB7cn0KbGlicmFyeShsZWFwcykKbGlicmFyeShJU0xSMikgCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KHJwYXJ0KSAgCmxpYnJhcnkocnBhcnQucGxvdCkgICAKbGlicmFyeShjYXJldCkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoY2xhc3MpCmxpYnJhcnkocm1hcmtkb3duKQpgYGAKCgoKCmBgYHtyfQojIERhdGEgRGltZW5zaW9uYWwgQW5hbHlzaXMKaGVhZChpbXBvcnRfODUpCmRpbShpbXBvcnRfODUpCm5hbWVzKGltcG9ydF84NSkKYGBgCgoKCgojIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgKEVEQSk6IFVuY292ZXJpbmcgVHJlbmRzIGFuZCBSZWxhdGlvbnNoaXBzCmBgYHtyfQojVGhpcyBvYmplY3RpdmUgaW52b2x2ZXMgaWRlbnRpZnlpbmcgaGlkZGVuIHBhdHRlcm5zIGFuZCBub24tb2J2aW91cyByZWxhdGlvbnNoaXBzIHdpdGhpbiB0aGUgZGF0YXNldC4gV2hpbGUgc29tZSBjb3JyZWxhdGlvbnMgKGUuZy4sIGhpZ2hlciBob3JzZXBvd2VyIGxlYWRpbmcgdG8gaGlnaGVyIHByaWNlKSBhcmUgZXhwZWN0ZWQsIHdlIHdpbGwgdXNlIGNvcnJlbGF0aW9uIG1hdHJpY2VzIGFuZCB2aXN1YWxpemF0aW9uIHRvb2xzIHRvIGRpc2NvdmVyIGxlc3MgaW50dWl0aXZlIGFzc29jaWF0aW9ucy4gQWxsIHZhcmlhYmxlc+KAlG51bWVyaWNhbCBhbmQgY2F0ZWdvcmljYWzigJRtYXkgc2VydmUgYXMgZmVhdHVyZXMgb3IgcmVzcG9uc2VzIGRlcGVuZGluZyBvbiB0aGUgYW5hbHlzaXMuCmBgYAoKYGBge3J9CiMgU2VsZWN0IG9ubHkgbnVtZXJpYyB2YXJpYWJsZXMKbnVtZXJpY192YXJzID0gaW1wb3J0Xzg1Wywgc2FwcGx5KGltcG9ydF84NSwgaXMubnVtZXJpYyldCgojIENvcnJlbGF0aW9uIG1hdHJpeApjb3JfbWF0cml4IDwtIGNvcihudW1lcmljX3ZhcnMpCgojIEhlYXQtbWFwIChyZWQpCmNvcnJwbG90Ojpjb3JycGxvdChjb3JfbWF0cml4LCBtZXRob2QgPSAiY29sb3IiLCB0eXBlID0gInVwcGVyIiwgdGwuY2V4ID0gMC43KQpgYGAKCgojIyBCb3hwbG90cyBvZiBOdW1lcmljIFZhcmlhYmxlcyBieSBDYXRlZ29yaWVzCmBgYHtyfQpwYXIobWZyb3cgPSBjKDMsIDEpKSAKaGlzdChpbXBvcnRfODUkcHJpY2UsIG1haW4gPSAiUHJpY2UgRGlzdHJpYnV0aW9uIiwgY29sID0gInNreWJsdWUiLCB4bGFiID0gIlByaWNlIiwgYnJlYWtzID0gMjApCmhpc3QoaW1wb3J0Xzg1JGNvbWJpbmVkX21wZywgbWFpbiA9ICJDb21iaW5lZCBNUEcgRGlzdHJpYnV0aW9uIiwgY29sID0gIm9yYW5nZSIsIHhsYWIgPSAiQ29tYmluZWQgTVBHIiwgYnJlYWtzID0gMTUpCmhpc3QoaW1wb3J0Xzg1JGhvcnNlcG93ZXIsIG1haW4gPSAiSG9yc2Vwb3dlciBEaXN0cmlidXRpb24iLCBjb2wgPSAibGlnaHRncmVlbiIsIHhsYWIgPSAiSG9yc2Vwb3dlciIpCmBgYAoKIyMgQmFyIENoYXJ0cyBvZiBDYXRlZ29yaWNhbCBWYXJpYWJsZSBGcmVxdWVuY2llcwpgYGB7cn0KcGFyKG1mcm93ID0gYygzLCAxKSkgCmJhcnBsb3QodGFibGUoaW1wb3J0Xzg1JG1ha2UpLCBsYXMgPSAyLCBjZXgubmFtZXMgPSAwLjcsIG1haW4gPSAiQ2FyIE1ha2UgRnJlcXVlbmN5IiwgY29sID0gImdyYXkiKQpiYXJwbG90KHRhYmxlKGltcG9ydF84NSRmdWVsX3R5cGUpLCBtYWluID0gIkZ1ZWwgVHlwZSBGcmVxdWVuY3kiLCBjb2wgPSAibGlnaHRibHVlIikKYmFycGxvdCh0YWJsZShpbXBvcnRfODUkZHJpdmVfd2hlZWxzKSwgbWFpbiA9ICJEcml2ZSBXaGVlbCBDb25maWd1cmF0aW9uIiwgY29sID0gImxpZ2h0Z3JlZW4iKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChpbXBvcnRfODUsIGFlcyh4ID0gY29tYmluZWRfbXBnLCB5ID0gcHJpY2UsIGNvbG9yID0gZnVlbF90eXBlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICJDb21iaW5lZCBNUEcgdnMuIFByaWNlIGJ5IEZ1ZWwgVHlwZSIsIHggPSAiQ29tYmluZWQgTVBHIiwgeSA9ICJQcmljZSIpCmBgYAoKCgoKIyBSZWdyZXNzaW9uIFRhc2s6IFByZWRpY3RpbmcgQ2FyIFByaWNlCmBgYHtyfQojIFRoZSBnb2FsIGlzIHRvIHByZWRpY3QgdmVoaWNsZSBwcmljZSB1c2luZyB2YXJpYWJsZSBzZWxlY3Rpb24gbWV0aG9kcyBzdWNoIGFzIFJpZGdlIFJlZ3Jlc3Npb24sIExhc3NvLCAKIyBhbmQgQmVzdCBTdWJzZXQgU2VsZWN0aW9uLiBUaGVzZSB0ZWNobmlxdWVzIHdpbGwgaGVscCBpZGVudGlmeSB0aGUgbW9zdCBpbmZvcm1hdGl2ZSBwcmVkaWN0b3JzIHdpdGggbWluaW1hbCByZWR1bmRhbmN5LiAKIyBCeSBzcGxpdHRpbmcgdGhlIGRhdGFzZXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzLCB3ZSBjYW4gZXZhbHVhdGUgdGhlIG1vZGVsJ3MgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZS4KIyBUaGUgcmVzcG9uc2UgdmFyaWFibGUgd2lsbCBiZSBwcmljZSwgYW5kIGFsbCBvdGhlciB2YXJpYWJsZXPigJRib3RoIG51bWVyaWNhbCBhbmQgY2F0ZWdvcmljYWwgKGVuY29kZWQgYXMgZHVtbXkgdmFyaWFibGVzKeKAlHdpbGwgc2VydmUgYXMgZmVhdHVyZXMuCmBgYAoKYGBge3J9CiMgQ3JlYXRlIG51bWVyaWNfZGF0YSBieSBzZWxlY3RpbmcgbnVtZXJpYyBjb2x1bW5zIGZyb20gaW1wb3J0Xzg1Cm51bWVyaWNfZGF0YSA8LSBpbXBvcnRfODVbLCBzYXBwbHkoaW1wb3J0Xzg1LCBpcy5udW1lcmljKV0KbnVtZXJpY19kYXRhIDwtIG51bWVyaWNfZGF0YVssIGMoInByaWNlIiwgc2V0ZGlmZihuYW1lcyhudW1lcmljX2RhdGEpLCAicHJpY2UiKSldCmhlYWQobnVtZXJpY19kYXRhKQpgYGAKYGBge3J9CnNldC5zZWVkKDEyMykgICMgRm9yIHJlcHJvZHVjaWJpbGl0eQp0cmFpbl9pbmRleCA8LSBzYW1wbGUoMTpucm93KG51bWVyaWNfZGF0YSksIDAuNyAqIG5yb3cobnVtZXJpY19kYXRhKSkKdHJhaW5fZGF0YSA8LSBudW1lcmljX2RhdGFbdHJhaW5faW5kZXgsIF0KdGVzdF9kYXRhIDwtIG51bWVyaWNfZGF0YVstdHJhaW5faW5kZXgsIF0KCnhfdHJhaW4gPC0gYXMubWF0cml4KHRyYWluX2RhdGFbLCAtMV0pICAjIHByZWRpY3RvcnMKeV90cmFpbiA8LSB0cmFpbl9kYXRhWywgMV0gICAgICAgICAgICAgIyBwcmljZQoKeF90ZXN0IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCAtMV0pCnlfdGVzdCA8LSB0ZXN0X2RhdGFbLCAxXQpgYGAKCmBgYHtyfQojIFJpZGdlIHJlZ3Jlc3Npb24gKGFscGhhID0gMCkKcmlkZ2VfbW9kIDwtIGN2LmdsbW5ldCh4X3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDApCnJpZGdlX2Jlc3RfbGFtYmRhIDwtIHJpZGdlX21vZCRsYW1iZGEubWluCnJpZGdlX3ByZWQgPC0gcHJlZGljdChyaWRnZV9tb2QsIHMgPSByaWRnZV9iZXN0X2xhbWJkYSwgbmV3eCA9IHhfdGVzdCkKcmlkZ2VfbXNlIDwtIG1lYW4oKHJpZGdlX3ByZWQgLSB5X3Rlc3QpXjIpCmBgYAoKYGBge3J9CiMgTGFzc28gcmVncmVzc2lvbiAoYWxwaGEgPSAxKQpsYXNzb19tb2QgPC0gY3YuZ2xtbmV0KHhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMSkKbGFzc29fYmVzdF9sYW1iZGEgPC0gbGFzc29fbW9kJGxhbWJkYS5taW4KbGFzc29fcHJlZCA8LSBwcmVkaWN0KGxhc3NvX21vZCwgcyA9IGxhc3NvX2Jlc3RfbGFtYmRhLCBuZXd4ID0geF90ZXN0KQpsYXNzb19tc2UgPC0gbWVhbigobGFzc29fcHJlZCAtIHlfdGVzdCleMikKYGBgCgpgYGB7cn0KbGlicmFyeShsZWFwcykKCnJlZ2ZpdC5mdWxsIDwtIHJlZ3N1YnNldHMocHJpY2UgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgbnZtYXggPSAxMCwgcmVhbGx5LmJpZyA9IFRSVUUpCgpwcmVkaWN0X3JlZ3N1YnNldHMgPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhLCBpZCkgewogIGZvcm0gPC0gYXMuZm9ybXVsYShvYmplY3QkY2FsbFtbMl1dKQogIG1hdCA8LSBtb2RlbC5tYXRyaXgoZm9ybSwgbmV3ZGF0YSkKICBjb2VmaSA8LSBjb2VmKG9iamVjdCwgaWQgPSBpZCkKICB4dmFycyA8LSBuYW1lcyhjb2VmaSkKICBtYXRbLCB4dmFyc10gJSolIGNvZWZpCn0KCiMgR2V0IHRoZSBhY3R1YWwgbnVtYmVyIG9mIG1vZGVscyBzdG9yZWQKbnVtX21vZGVscyA8LSBucm93KHN1bW1hcnkocmVnZml0LmZ1bGwpJHdoaWNoKQoKIyBSZS1ydW4gdmFsaWRhdGlvbiBsb29wIHdpdGggY29ycmVjdCBsaW1pdAp2YWxfZXJyb3JzIDwtIHNhcHBseSgxOm51bV9tb2RlbHMsIGZ1bmN0aW9uKGkpIHsKICBwcmVkIDwtIHByZWRpY3RfcmVnc3Vic2V0cyhyZWdmaXQuZnVsbCwgdGVzdF9kYXRhLCBpKQogIG1lYW4oKHRlc3RfZGF0YSRwcmljZSAtIHByZWQpXjIpCn0pCgojIFNlbGVjdCBiZXN0CmJlc3Rfc3Vic2V0X21zZSA8LSBtaW4odmFsX2Vycm9ycykKYmVzdF9zdWJzZXRfc2l6ZSA8LSB3aGljaC5taW4odmFsX2Vycm9ycykKYmVzdF9zdWJzZXRfbXNlID0gbWluKHZhbF9lcnJvcnMpCmJlc3Rfc3Vic2V0X3NpemUgPSB3aGljaC5taW4odmFsX2Vycm9ycykKCmNhdCgiQmVzdCBTdWJzZXQgTVNFIChzaXplID0iLCBiZXN0X3N1YnNldF9zaXplLCAiKToiLCByb3VuZChiZXN0X3N1YnNldF9tc2UsIDIpLCAiXG4iKQpjYXQoIlJpZGdlIFJlZ3Jlc3Npb24gVGVzdCBNU0U6Iiwgcm91bmQocmlkZ2VfbXNlLCAyKSwgIlxuIikKY2F0KCJMYXNzbyBSZWdyZXNzaW9uIFRlc3QgTVNFOiIsIHJvdW5kKGxhc3NvX21zZSwgMiksICJcbiIpCgpgYGAKV2UgYXBwbGllZCBSaWRnZSBhbmQgTGFzc28gcmVncmVzc2lvbiBtZXRob2RzIHRvIHByZWRpY3QgdmVoaWNsZSBwcmljZSB1c2luZyBhIGRhdGFzZXQgd2l0aCBudW1lcmljIGFuZCBkdW1teSB2YXJpYWJsZXMuIFJpZGdlIHJlZ3Jlc3Npb24gc2hyaW5rcyBhbGwgY29lZmZpY2llbnRzLCB3aGljaCBoZWxwcyB3aGVuIHByZWRpY3RvcnMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLiBMYXNzbyByZWdyZXNzaW9uIGFsc28gcGVyZm9ybXMgc2hyaW5rYWdlIGJ1dCBjYW4gc2V0IHNvbWUgY29lZmZpY2llbnRzIHRvIHplcm8sIHRodXMgZG9pbmcgdmFyaWFibGUgc2VsZWN0aW9uLiBCeSBjb21wYXJpbmcgdGhlIHRlc3QgTWVhbiBTcXVhcmVkIEVycm9ycyAoTVNFKSwgd2Ugc2VsZWN0ZWQgdGhlIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBNU0UuIFdlIHRoZXJlZm9yZSBzZWxlY3QgQmVzdCBTdWJzZXQgU2VsZWN0aW9uIGFzIHRoZSBvcHRpbWFsIHJlZ3Jlc3Npb24gbWV0aG9kIGR1ZSB0byBpdHMgc3VwZXJpb3IgcHJlZGljdGl2ZSBhY2N1cmFjeSBpbiB0aGlzIGNhc2UuCgpgYGB7cn0KY29lZihyZWdmaXQuZnVsbCwgaWQgPSA0KQpgYGAKClRoZSBiZXN0IHN1YnNldCByZWdyZXNzaW9uIG1vZGVsIHNlbGVjdGVkIDQgcHJlZGljdG9yczogbm9ybWFsaXplZCBsb3NzZXMsIHdpZHRoLCBjdXJiIHdlaWdodCwgYW5kIGhvcnNlcG93ZXIuIFRoZXNlIHZhcmlhYmxlcyBjb2xsZWN0aXZlbHkgeWllbGRlZCB0aGUgbG93ZXN0IHRlc3QgTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpLCBpbmRpY2F0aW5nIHN0cm9uZyBwcmVkaWN0aXZlIHBvd2VyIGZvciB2ZWhpY2xlIHByaWNlLiBUaGUgaW5jbHVzaW9uIG9mIG5vcm1hbGl6ZWQgbG9zc2VzIGxpa2VseSByZWZsZWN0cyByaXNrLWJhc2VkIHByaWNpbmcgYWRqdXN0bWVudHMsIHdoaWxlIHdpZHRoIGFuZCBjdXJiIHdlaWdodCBjYXB0dXJlIHZlaGljbGUgc2l6ZSBhbmQgbWFzcyDigJQgZmVhdHVyZXMgdHlwaWNhbGx5IGFzc29jaWF0ZWQgd2l0aCBsdXh1cnkgb3IgcGVyZm9ybWFuY2UuIEhvcnNlcG93ZXIsIGEgZGlyZWN0IGluZGljYXRvciBvZiBlbmdpbmUgc3RyZW5ndGgsIGlzIGFsc28gcG9zaXRpdmVseSBhc3NvY2lhdGVkIHdpdGggcHJpY2UuIFRvZ2V0aGVyLCB0aGVzZSBwcmVkaWN0b3JzIGZvcm0gYSBwYXJzaW1vbmlvdXMgeWV0IGVmZmVjdGl2ZSBtb2RlbCBmb3IgZXN0aW1hdGluZyB2ZWhpY2xlIHByaWNlcyB1c2luZyBudW1lcmljIGRhdGEgb25seS4KCmBgYHtyfQojIFN1YnNldCB0aGUgbnVtZXJpYyBkYXRhIHRvIHJlbGV2YW50IHZhcmlhYmxlcwp2YXJzIDwtIGMoInByaWNlIiwgIm5vcm1hbGl6ZWRfbG9zc2VzIiwgIndpZHRoIiwgImN1cmJfd2VpZ2h0IiwgImhvcnNlcG93ZXIiKQpzdWJzZXRfZGF0YSA8LSBpbXBvcnRfODVbLCB2YXJzXQoKIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzCnN1YnNldF9kYXRhIDwtIG5hLm9taXQoc3Vic2V0X2RhdGEpCgojIENvcnJlbGF0aW9uIG1hdHJpeApjb3JfbWF0cml4IDwtIGNvcihzdWJzZXRfZGF0YSkKcHJpbnQocm91bmQoY29yX21hdHJpeCwgMikpCmBgYAoKIyBSZWdyZXNzaW9uIFRhc2s6IElkZW50aWZ5aW5nIEZhY3RvcnMgQWZmZWN0aW5nIEZ1ZWwgRWZmaWNpZW5jeQpgYGB7cn0KI1RoaXMgdGFzayBhaW1zIHRvIGV4cGxvcmUgd2hpY2ggdmVoaWNsZSBzcGVjaWZpY2F0aW9ucyBjb250cmlidXRlIG1vc3QgdG8gZnVlbCBlZmZpY2llbmN5IChtZWFzdXJlZCBpbiBtaWxlcyBwZXIgZ2FsbG9uKS4gV2Ugd2lsbCB0cmVhdCBjaXR5X21wZyBhbmQgaGlnaHdheV9tcGcgYXMgc2VwYXJhdGUgcmVzcG9uc2UgdmFyaWFibGVzIGFuZCBhbHNvIGNvbnNpZGVyIGEgY29tYmluZWQgZWZmaWNpZW5jeSBtZXRyaWMuIFRoZSBmb2N1cyB3aWxsIGJlIG9uIGVuZ2luZWVyaW5nLXJlbGF0ZWQgZmVhdHVyZXMgc3VjaCBhcyBjb21wcmVzc2lvbl9yYXRpbywgbnVtX2N5bGluZGVycywgYW5kIGVuZ2luZV9zaXplLiBUaGUgb3V0Y29tZSBvZiB0aGlzIGFuYWx5c2lzIGNhbiBiZSBhcHBsaWVkIHRvIHJlYWwtd29ybGQgdmVoaWNsZSBwdXJjaGFzaW5nIGRlY2lzaW9ucywgZXNwZWNpYWxseSBmb3IgdGhvc2Ugc2Vla2luZyBmdWVsLWVmZmljaWVudCBjYXJzLgpgYGAKCmBgYHtyfQojIFdlIG9ubHkgZm9jdXMgb24gZW5naW5lZXJpbmctcmVsYXRlZCB2YXJpYWJsZXMKZW5naW5lZXJpbmdfdmFycyA9IGMoImNvbXByZXNzaW9uX3JhdGlvIiwgImVuZ2luZV9zaXplIiwgIm51bV9jeWxpbmRlcnMiLCJob3JzZXBvd2VyIiwgImN1cmJfd2VpZ2h0IiwgImJvcmUiLCAic3Ryb2tlIikKCiMgQ29udmVydCBudW1fY3lsaW5kZXJzIGZyb20gZmFjdG9yIChlLmcuLCAiZm91ciIsICJzaXgiKSB0byBudW1lcmljIChlLmcuLCA0LCA2KQpjeWxfbWFwIDwtIGMoInR3byI9MiwgInRocmVlIj0zLCAiZm91ciI9NCwgImZpdmUiPTUsICJzaXgiPTYsICJlaWdodCI9OCwgInR3ZWx2ZSI9MTIpCmltcG9ydF84NSRudW1fY3lsaW5kZXJzIDwtIGN5bF9tYXBbYXMuY2hhcmFjdGVyKGltcG9ydF84NSRudW1fY3lsaW5kZXJzKV0KZW5naW5lZXJpbmdfZGF0YSA9IGltcG9ydF84NVssIGMoImNvbWJpbmVkX21wZyIsIGVuZ2luZWVyaW5nX3ZhcnMpXQplbmdpbmVlcmluZ19kYXRhID0gbmEub21pdChlbmdpbmVlcmluZ19kYXRhKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCg0MzIpCiMgSy1mb2xkIGNyb3NzLXZhbGlkYXRpb24KayA9IDEwCmZvbGRzID0gc2FtcGxlKDE6aywgbnJvdyhlbmdpbmVlcmluZ19kYXRhKSwgcmVwbGFjZSA9IFRSVUUpCmN2X2Vycm9ycyA9IG1hdHJpeChOQSwgaywgNykKCmZvciAoaiBpbiAxOmspIHsKICByZWdmaXRfY3YgPSByZWdzdWJzZXRzKGNvbWJpbmVkX21wZyB+IC4sIGRhdGEgPSBlbmdpbmVlcmluZ19kYXRhW2ZvbGRzICE9IGosIF0sIG52bWF4ID0gNykKICBmb3IgKGkgaW4gMTo3KSB7CiAgICBwcmVkID0gcHJlZGljdF9yZWdzdWJzZXRzKHJlZ2ZpdF9jdiwgZW5naW5lZXJpbmdfZGF0YVtmb2xkcyA9PSBqLCBdLCBpZCA9IGkpCiAgICB5X3RydWUgPSBlbmdpbmVlcmluZ19kYXRhJGNvbWJpbmVkX21wZ1tmb2xkcyA9PSBqXQogICAgY3ZfZXJyb3JzW2osIGldID0gbWVhbigoeV90cnVlIC0gcHJlZCleMikKICB9Cn0KbWVhbl9jdl9lcnJvcnMgPSBjb2xNZWFucyhjdl9lcnJvcnMpCnBsb3QoMTo3LCBtZWFuX2N2X2Vycm9ycywgdHlwZSA9ICJiIiwgeGxhYiA9ICJOdW1iZXIgb2YgVmFyaWFibGVzIiwgeWxhYiA9ICJDViBNU0UiKQpwb2ludHMod2hpY2gubWluKG1lYW5fY3ZfZXJyb3JzKSwgbWVhbl9jdl9lcnJvcnNbd2hpY2gubWluKG1lYW5fY3ZfZXJyb3JzKV0sIGNvbCA9ICJibHVlIiwgcGNoID0gMTkpCmJlc3RfY3Zfc2l6ZSA9IHdoaWNoLm1pbihtZWFuX2N2X2Vycm9ycykKYGBgCgoKYGBge3J9CnNldC5zZWVkKDQzMikKIyBGaXQgbW9kZWwgZm9yIGNvbWJpbmVkIE1QRwpyZWdmaXRfZndkID0gcmVnc3Vic2V0cyhjb21iaW5lZF9tcGcgfiAuLCBkYXRhID0gZW5naW5lZXJpbmdfZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgIG52bWF4ID0gNywgbWV0aG9kID0gImZvcndhcmQiKQpyZWdmaXRfYndkID0gcmVnc3Vic2V0cyhjb21iaW5lZF9tcGcgfiAuLCBkYXRhID0gZW5naW5lZXJpbmdfZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgIG52bWF4ID03LCBtZXRob2QgPSAiYmFja3dhcmQiKQpjb2VmKHJlZ2ZpdF9md2QsIGJlc3RfY3Zfc2l6ZSkgICAgIyBGb3J3YXJkIHN0ZXB3aXNlCmNvZWYocmVnZml0X2J3ZCwgYmVzdF9jdl9zaXplKSAgICAjIEJhY2t3YXJkIHN0ZXB3aXNlCmBgYAoKYGBge3J9CiMgU2V0IHNlZWQgYW5kIHNwbGl0IGRhdGEKc2V0LnNlZWQoNDMyKQpzYW1wbGVfc2l6ZSA9IGZsb29yKDAuOCAqIG5yb3coZW5naW5lZXJpbmdfZGF0YSkpICAjIDgwJSB0cmFpbmluZwp0cmFpbl9pbmRleCA9IHNhbXBsZShzZXFfbGVuKG5yb3coZW5naW5lZXJpbmdfZGF0YSkpLCBzaXplID0gc2FtcGxlX3NpemUpCnRyYWluX2RhdGEgPSBlbmdpbmVlcmluZ19kYXRhW3RyYWluX2luZGV4LCBdCnRlc3RfZGF0YSA9ZW5naW5lZXJpbmdfZGF0YVstdHJhaW5faW5kZXgsIF0KYGBgCgojIyMgTGluZWFyIFJlZ3Jlc3Npb24gIApgYGB7cn0KIyBGaXQgYSBsaW5lYXIgbW9kZWwgdXNpbmcgdGhlIHRvcCA0IHZhcmlhYmxlcwp0b3BfNF9tb2RlbCA9IGxtKGNvbWJpbmVkX21wZyB+IGhvcnNlcG93ZXIgKyBlbmdpbmVfc2l6ZSArIGN1cmJfd2VpZ2h0ICsgY29tcHJlc3Npb25fcmF0aW8sCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhKQojIFByZWRpY3Qgb24gdGhlIHRlc3Qgc2V0CnRlc3RfcHJlZGljdGlvbnMgPSBwcmVkaWN0KHRvcF80X21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQoKdGVzdF9tc2UgPC0gbWVhbigodGVzdF9kYXRhJGNvbWJpbmVkX21wZyAtIHRlc3RfcHJlZGljdGlvbnMpXjIpCnRlc3RfbXNlCmBgYAoKIyMjIExpbmVhciBSZWdyZXNzaW9uICArIG9uZSAyLXdheSBpbnRlcmFjdGlvbiB0ZXJtCmBgYHtyfQptYWluX2VmZmVjdHMgPSAiaG9yc2Vwb3dlciArIGVuZ2luZV9zaXplICsgY3VyYl93ZWlnaHQgKyBjb21wcmVzc2lvbl9yYXRpbyIKCiMgQWxsIHBvc3NpYmxlIDItd2F5IGludGVyYWN0aW9ucwppbnRlcmFjdGlvbl90ZXJtcyA9IGMoImhvcnNlcG93ZXI6ZW5naW5lX3NpemUiLCAiaG9yc2Vwb3dlcjpjdXJiX3dlaWdodCIsIAogICAgICAgICAgICAgICAgICAgICAgICJob3JzZXBvd2VyOmNvbXByZXNzaW9uX3JhdGlvIiwgImVuZ2luZV9zaXplOmN1cmJfd2VpZ2h0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgImVuZ2luZV9zaXplOmNvbXByZXNzaW9uX3JhdGlvIiwgImN1cmJfd2VpZ2h0OmNvbXByZXNzaW9uX3JhdGlvIikKcmVzdWx0cyA9IGRhdGEuZnJhbWUoSW50ZXJhY3Rpb24gPSBjaGFyYWN0ZXIoKSwgVGVzdF9NU0UgPSBudW1lcmljKCksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKCiMgTG9vcCBvdmVyIGVhY2ggaW50ZXJhY3Rpb24gdGVybQpmb3IgKHRlcm0gaW4gaW50ZXJhY3Rpb25fdGVybXMpIHsKICBmdWxsX2Zvcm11bGEgPSBhcy5mb3JtdWxhKHBhc3RlKCJjb21iaW5lZF9tcGcgfiIsIHBhc3RlKGMobWFpbl9lZmZlY3RzLCB0ZXJtKSwgY29sbGFwc2UgPSAiICsgIikpKQogIAogICMgRml0IG1vZGVsIHdpdGggb25seSBvbmUgaW50ZXJhY3Rpb24gKyA0IHRvcCB2YXJpYWJsZXMgCiAgbW9kZWwgPSBsbShmdWxsX2Zvcm11bGEsIGRhdGEgPSB0cmFpbl9kYXRhKQogIAogIHByZWRzID0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKICBtc2UgPSBtZWFuKCh0ZXN0X2RhdGEkY29tYmluZWRfbXBnIC0gcHJlZHMpXjIpCiAgcmVzdWx0cyA9IHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoSW50ZXJhY3Rpb24gPSB0ZXJtLCBUZXN0X01TRSA9IG1zZSkpCn0KCiMgU29ydCBhbmQgcHJpbnQKcmVzdWx0c19zb3J0ZWQgPC0gcmVzdWx0c1tvcmRlcihyZXN1bHRzJFRlc3RfTVNFKSwgXQpwcmludChyZXN1bHRzX3NvcnRlZCkKcmVzdWx0c19zb3J0ZWRbMSwgXQpgYGAKCgpgYGB7cn0KdG9wXzRfaW50ZXJhY3Rpb25fMSA9ICBsbShjb21iaW5lZF9tcGcgfiBob3JzZXBvd2VyICsgZW5naW5lX3NpemUgKyBjdXJiX3dlaWdodCArY29tcHJlc3Npb25fcmF0aW8gKyBob3JzZXBvd2VyOmN1cmJfd2VpZ2h0LCBkYXRhID0gdHJhaW5fZGF0YSkKc3VtbWFyeSh0b3BfNF9pbnRlcmFjdGlvbl8xKQpgYGAKCgpgYGB7cn0KdG9wXzRfaW50ZXJhY3Rpb25fMiA9IGxtKGNvbWJpbmVkX21wZyB+IGhvcnNlcG93ZXIgKyBlbmdpbmVfc2l6ZSArIGN1cmJfd2VpZ2h0ICtjb21wcmVzc2lvbl9yYXRpbyArIGhvcnNlcG93ZXI6ZW5naW5lX3NpemUsIGRhdGEgPSB0cmFpbl9kYXRhKQpzdW1tYXJ5KHRvcF80X2ludGVyYWN0aW9uXzIpCmBgYAoKCmBgYHtyfQp0b3BfNF9pbnRlcmFjdGlvbl8zID0gbG0oY29tYmluZWRfbXBnIH4gaG9yc2Vwb3dlciArIGVuZ2luZV9zaXplICsgY3VyYl93ZWlnaHQgK2NvbXByZXNzaW9uX3JhdGlvICsgZW5naW5lX3NpemU6Y3VyYl93ZWlnaHQsIGRhdGEgPSB0cmFpbl9kYXRhKQpzdW1tYXJ5KHRvcF80X2ludGVyYWN0aW9uXzMpCmBgYAoKCgojIyBQb2x5IFJlZ3Jlc3Npb24gIApgYGB7cn0KIyBJbml0aWFsaXplIHN0b3JhZ2UKdGVzdF9tc2VfcG9seSA9IG51bWVyaWMoOSkKCiMgTG9vcCB0aHJvdWdoIGRlZ3JlZXMgMSB0byA5CmZvciAoZCBpbiAxOjkpIHsKICBmb3JtdWxhX3BvbHkgPSBhcy5mb3JtdWxhKHBhc3RlKAogICAgImNvbWJpbmVkX21wZyB+IiwgCiAgICBwYXN0ZTAoIkkoaG9yc2Vwb3dlcl4iLCBkLCAiKSArICIsCiAgICAgICAgICAgIkkoZW5naW5lX3NpemVeIiwgZCwgIikgKyAiLAogICAgICAgICAgICJJKGN1cmJfd2VpZ2h0XiIsIGQsICIpICsgIiwKICAgICAgICAgICAiSShjb21wcmVzc2lvbl9yYXRpb14iLCBkLCAiKSIpCiAgKSkKICAKICAjIEZpdCBtb2RlbAogIHBvbHlfbW9kZWwgPSBsbShmb3JtdWxhX3BvbHksIGRhdGEgPSB0cmFpbl9kYXRhKQogIAogICMgUHJlZGljdAogIHByZWRfcG9seSA9IHByZWRpY3QocG9seV9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKICAKICAjIENvbXB1dGUgdGVzdCBNU0UKICB0ZXN0X21zZV9wb2x5W2RdID0gbWVhbigodGVzdF9kYXRhJGNvbWJpbmVkX21wZyAtIHByZWRfcG9seSleMikKfQoKIyBGaW5kIHRoZSBkZWdyZWUgd2l0aCBsb3dlc3QgdGVzdCBNU0UKYmVzdF9kZWdyZWUgPSB3aGljaC5taW4odGVzdF9tc2VfcG9seSkKYmVzdF9tc2UgPSBtaW4odGVzdF9tc2VfcG9seSkKCiMgUGxvdCBNU0UgdnMgcG9seW5vbWlhbCBkZWdyZWUKcGxvdCgxOjksIHRlc3RfbXNlX3BvbHksIHR5cGUgPSAiYiIsIHBjaCA9IDE5LAogICAgIHhsYWIgPSAiUG9seW5vbWlhbCBEZWdyZWUiLCB5bGFiID0gIlRlc3QgTVNFIiwKICAgICBtYWluID0gIlRlc3QgTVNFIHZzLiBQb2x5bm9taWFsIERlZ3JlZSIpCnBvaW50cyh3aGljaC5taW4odGVzdF9tc2VfcG9seSksIG1pbih0ZXN0X21zZV9wb2x5KSwgY29sID0gInJlZCIsIHBjaCA9IDE5KQoKY2F0KCJMb3dlc3QgTVNFIGlzIiwgcm91bmQoYmVzdF9tc2UsIDQpLCAiYXQgcG9seW5vbWlhbCBkZWdyZWUiLCBiZXN0X2RlZ3JlZSwgIlxuIikKYGBgCgoKCiMgQ2xhc3NpZmljYXRpb24gVGFzazogUHJlZGljdGluZyBJbnN1cmFuY2UgUmlzayBMZXZlbApgYGB7cn0KIyBIZXJlLCB3ZSBhaW0gdG8gY2xhc3NpZnkgYSB2ZWhpY2xl4oCZcyBpbnN1cmFuY2UgcmlzayBsZXZlbOKAlHJlcHJlc2VudGVkIGJ5IHRoZSBzeW1ib2wgc2NvcmUgKHJhbmdpbmcgZnJvbSAtMyB0byArMynigJRiYXNlZCBvbiBpdHMgc3BlY2lmaWNhdGlvbnMuIFVzaW5nIGNsYXNzaWZpY2F0aW9uIG1vZGVscywgd2XigJlsbCBkZXRlcm1pbmUgaG93IGZlYXR1cmVzIGxpa2UgY2FyIHNwZWNzIGFuZCBwcmljaW5nIHJlbGF0ZSB0byBpbnN1cmFuY2Ugcmlzay4gVGhpcyBjb3VsZCBwcm92aWRlIHRoZSBiYXNpcyBmb3IgYSBkZWNpc2lvbiBzeXN0ZW0gaW4gd2hpY2ggY2FycyBhYm92ZSBhIGNlcnRhaW4gcmlzayB0aHJlc2hvbGQgbWF5IGJlIGZsYWdnZWQgZm9yIGhpZ2hlciBwcmVtaXVtcyBvciBpbnN1cmFuY2UgZGVuaWFsLiBJbiB0aGlzIHRhc2ssIHN5bWJvbCB3aWxsIGJlIHRoZSByZXNwb25zZSB2YXJpYWJsZS4gCmBgYAojIEluc3VyYW5jZSBSaXNrIFJhdGluZyDigJMgQSAic3ltYm9saW5nIiBzY29yZSByYW5naW5nIGZyb20gLTMgKGxlYXN0IHJpc2t5KSB0byArMyAobW9zdCByaXNreSksIGluZGljYXRpbmcgaG93IHJpc2t5IGEgdmVoaWNsZSBpcyByZWxhdGl2ZSB0byBpdHMgcHJpY2UuCgpgYGB7cn0KY2F0ZWdvcmljYWxfdmFycyA9IGltcG9ydF84NVssIHNhcHBseShpbXBvcnRfODUsIGZ1bmN0aW9uKHgpIGlzLmZhY3Rvcih4KSB8fCBpcy5jaGFyYWN0ZXIoeCkpXQpuYW1lcyhjYXRlZ29yaWNhbF92YXJzKQpgYGAKCiMjIyBDYXIgTWFrZSB2LnMuIFJpc2sgUmF0aW5nCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmdncGxvdChpbXBvcnRfODUsIGFlcyh4ID0gbWFrZSwgZmlsbCA9IGFzLmZhY3RvcihzeW1ib2xpbmcpKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIi0yIj0iIzQ1NzViNCIsICItMSI9IiM5MWJmZGIiLCAiMCI9IiNlMGYzZjgiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIxIj0iI2ZlZTA5MCIsICIyIj0iI2ZjOGQ1OSIsICIzIj0iI2Q3MzAyNyIpKSArCiAgbGFicyh0aXRsZSA9ICJQcm9wb3J0aW9uIG9mIEluc3VyYW5jZSBSaXNrIFJhdGluZ3MgYnkgQ2FyIE1ha2UiLAogICAgICAgeCA9ICJDYXIgTWFrZSIsIHkgPSAiUHJvcG9ydGlvbiIsIGZpbGwgPSAiUmlzayBSYXRpbmciKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKCiMjIyBGdWVsIFR5cGUgdi5zLiBSaXNrIFJhdGluZwpgYGB7cn0KIyBJbnN1cmFuY2UgUmlzayBieSBGdWVsIFR5cGUKZ2dwbG90KGltcG9ydF84NSwgYWVzKHggPSBmdWVsX3R5cGUsIGZpbGwgPSBhcy5mYWN0b3Ioc3ltYm9saW5nKSkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBsYWJzKHRpdGxlID0gIkluc3VyYW5jZSBSaXNrIGJ5IEZ1ZWwgVHlwZSIsIHggPSAiRnVlbCBUeXBlIiwgZmlsbCA9ICJTeW1ib2xpbmciKQpgYGAKCiMjIEJvZHkgU3R5bGUgdi5zLiBSaXNrIFJhdGluZwpgYGB7cn0KIyBJbnN1cmFuY2UgUmlzayBieSBCb2R5IFN0eWxlCmdncGxvdChpbXBvcnRfODUsIGFlcyh4ID0gYm9keV9zdHlsZSwgZmlsbCA9IGFzLmZhY3RvcihzeW1ib2xpbmcpKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArCiAgbGFicyh0aXRsZSA9ICJQcm9wb3J0aW9uIG9mIEluc3VyYW5jZSBSaXNrIGJ5IEJvZHkgU3R5bGUiLCB5ID0gIlByb3BvcnRpb24iLCBmaWxsID0gIlN5bWJvbGluZyIpCmBgYAoKIyMgRHJpdmUgVHlwZSB2LnMuIFJpc2sgUmF0aW5nCmBgYHtyfQojIFJpc2sgUmF0aW5nIERpc3RyaWJ1dGlvbiBieSBEcml2ZSBUeXBlCmdncGxvdChpbXBvcnRfODUsIGFlcyh4ID0gZHJpdmVfd2hlZWxzLCBmaWxsID0gYXMuZmFjdG9yKHN5bWJvbGluZykpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICBsYWJzKHRpdGxlID0gIlJpc2sgUmF0aW5nIERpc3RyaWJ1dGlvbiBieSBEcml2ZSBUeXBlIiwgeSA9ICJQcm9wb3J0aW9uIiwgZmlsbCA9ICJTeW1ib2xpbmciKQpgYGAKCgoKIyMgQm94cGxvdCBmb3IgbnVtZXJpY2FsIHByZWRpY3RvcnMKYGBge3J9CgojIEVuc3VyZSBzeW1ib2xpbmcgaXMgYSBmYWN0b3IgKGZvciBjYXRlZ29yaWNhbCBjb2xvcmluZykKaW1wb3J0Xzg1JHN5bWJvbGluZyA8LSBhcy5mYWN0b3IoaW1wb3J0Xzg1JHN5bWJvbGluZykKCiMgQm94cGxvdHMgZm9yIGtleSBudW1lcmljIHByZWRpY3RvcnMKYnAxIDwtIGdncGxvdChpbXBvcnRfODUsIGFlcyh4ID0gc3ltYm9saW5nLCB5ID0gcHJpY2UpKSArCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAibGlnaHRibHVlIikgKwogIGxhYnModGl0bGUgPSAiUHJpY2UgYnkgSW5zdXJhbmNlIFJpc2sgUmF0aW5nIiwgeCA9ICJTeW1ib2xpbmciLCB5ID0gIlByaWNlIikgKwogIHRoZW1lX21pbmltYWwoKQoKYnAyIDwtIGdncGxvdChpbXBvcnRfODUsIGFlcyh4ID0gc3ltYm9saW5nLCB5ID0gY29tYmluZWRfbXBnKSkgKwogIGdlb21fYm94cGxvdChmaWxsID0gImxpZ2h0Z3JlZW4iKSArCiAgbGFicyh0aXRsZSA9ICJDb21iaW5lZCBNUEcgYnkgSW5zdXJhbmNlIFJpc2sgUmF0aW5nIiwgeCA9ICJTeW1ib2xpbmciLCB5ID0gIkNvbWJpbmVkIE1QRyIpICsKICB0aGVtZV9taW5pbWFsKCkKCmJwMyA8LSBnZ3Bsb3QoaW1wb3J0Xzg1LCBhZXMoeCA9IHN5bWJvbGluZywgeSA9IGhvcnNlcG93ZXIpKSArCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAibGlnaHRjb3JhbCIpICsKICBsYWJzKHRpdGxlID0gIkhvcnNlcG93ZXIgYnkgSW5zdXJhbmNlIFJpc2sgUmF0aW5nIiwgeCA9ICJTeW1ib2xpbmciLCB5ID0gIkhvcnNlcG93ZXIiKSArCiAgdGhlbWVfbWluaW1hbCgpCgpicDQgPC0gZ2dwbG90KGltcG9ydF84NSwgYWVzKHggPSBzeW1ib2xpbmcsIHkgPSBlbmdpbmVfc2l6ZSkpICsKICBnZW9tX2JveHBsb3QoZmlsbCA9ICJraGFraSIpICsKICBsYWJzKHRpdGxlID0gIkVuZ2luZSBTaXplIGJ5IEluc3VyYW5jZSBSaXNrIFJhdGluZyIsIHggPSAiU3ltYm9saW5nIiwgeSA9ICJFbmdpbmUgU2l6ZSIpICsKICB0aGVtZV9taW5pbWFsKCkKCmJwNSA8LSBnZ3Bsb3QoaW1wb3J0Xzg1LCBhZXMoeCA9IHN5bWJvbGluZywgeSA9IGN1cmJfd2VpZ2h0KSkgKwogIGdlb21fYm94cGxvdChmaWxsID0gInBsdW0iKSArCiAgbGFicyh0aXRsZSA9ICJDdXJiIFdlaWdodCBieSBJbnN1cmFuY2UgUmlzayBSYXRpbmciLCB4ID0gIlN5bWJvbGluZyIsIHkgPSAiQ3VyYiBXZWlnaHQiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFycmFuZ2UgcGxvdHMgaW4gYSBncmlkCmdyaWQuYXJyYW5nZShicDEsIGJwMiwgYnAzLCBicDQsIGJwNSwgbmNvbCA9IDIpCgpgYGAKCgoKYGBge3J9CnNldC5zZWVkKDQzMikgCiMgU3BsaXQgaW50byA4MCUgdHJhaW5pbmcsIDIwJSB0ZXN0aW5nCnRyYWluX2lkeCA9IHNhbXBsZSgxOm5yb3cobW9kZWxfZGF0YSksIDAuOCAqIG5yb3cobW9kZWxfZGF0YSkpCnRyYWluX2RhdGEgPSBtb2RlbF9kYXRhW3RyYWluX2lkeCwgXQp0ZXN0X2RhdGEgID0gbW9kZWxfZGF0YVstdHJhaW5faWR4LCBdCgppbXBvcnRfODUkc3ltYm9saW5nID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoaW1wb3J0Xzg1JHN5bWJvbGluZykpICAjIGlmIGl0J3MgYSBmYWN0b3IKaW1wb3J0Xzg1JHJpc2sgPSBpZmVsc2UoaW1wb3J0Xzg1JHN5bWJvbGluZyA+IDAsICJIaWdoIiwgIkxvdyIpCmltcG9ydF84NSRyaXNrID0gZmFjdG9yKGltcG9ydF84NSRyaXNrKQoKcHJlZGljdG9ycyA9IGMoInByaWNlIiwgImhvcnNlcG93ZXIiLCAiY3VyYl93ZWlnaHQiLCAiZW5naW5lX3NpemUiLCAiY29tcHJlc3Npb25fcmF0aW8iLCAibnVtX2N5bGluZGVycyIpCm1vZGVsX2RhdGEgPSBpbXBvcnRfODVbLCBjKCJyaXNrIiwgcHJlZGljdG9ycyldCm1vZGVsX2RhdGEgPSBuYS5vbWl0KG1vZGVsX2RhdGEpCmBgYAoKCiMjIGxvZ3Rpc3RpYyByZWdyZXNzaW9uCmBgYHtyfQpzZXQuc2VlZCg0MzIpCiMgRml0IGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwKbG9naXRfbW9kID0gZ2xtKHJpc2sgfiAuLCBkYXRhID0gbW9kZWxfZGF0YSwgZmFtaWx5ID0gImJpbm9taWFsIikKCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIG9uIHRlc3Qgc2V0CnByb2JfcHJlZCA9IHByZWRpY3QobG9naXRfbW9kLCBuZXdkYXRhID0gdGVzdF9kYXRhLCB0eXBlID0gInJlc3BvbnNlIikKCiMgQ29udmVydCBwcm9iYWJpbGl0aWVzIHRvIGNsYXNzIHByZWRpY3Rpb25zCmNsYXNzX3ByZWQgPSBpZmVsc2UocHJvYl9wcmVkID4gMC41LCAiSGlnaCIsICJMb3ciKQpjbGFzc19wcmVkID0gZmFjdG9yKGNsYXNzX3ByZWQsIGxldmVscyA9IGxldmVscyh0ZXN0X2RhdGEkcmlzaykpCgojIENvbmZ1c2lvbiBtYXRyaXgKY29uZl9tYXRyaXggPSB0YWJsZShQcmVkaWN0ZWQgPSBjbGFzc19wcmVkLCBBY3R1YWwgPSB0ZXN0X2RhdGEkcmlzaykKcHJpbnQoY29uZl9tYXRyaXgpCgojIEFjY3VyYWN5CmFjY3VyYWN5ID0gbWVhbihjbGFzc19wcmVkID09IHRlc3RfZGF0YSRyaXNrKQpjYXQoIlRlc3QgQWNjdXJhY3k6IiwgYWNjdXJhY3ksICJcbiIpCmBgYAoKCgojIyBkZWNpc2lvbiB0cmVlCmBgYHtyfQpzZXQuc2VlZCg0MzIpCnRyZWVfbW9kZWwgPSBycGFydChyaXNrIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIG1ldGhvZCA9ICJjbGFzcyIpCnJwYXJ0LnBsb3QodHJlZV9tb2RlbCkKYGBgCgojIyBLTk4tY2xhc3NpZmljYXRpb24KYGBge3J9CnNldC5zZWVkKDQzMikKCiMgRW5zdXJlIG9ubHkgbnVtZXJpYyBwcmVkaWN0b3JzIGFyZSB1c2VkCm51bWVyaWNfcHJlZGljdG9ycyA9IHByZWRpY3RvcnNbc2FwcGx5KHRyYWluX2RhdGFbLCBwcmVkaWN0b3JzXSwgaXMubnVtZXJpYyldCgp0cmFpbl94ID0gc2NhbGUodHJhaW5fZGF0YVssIG51bWVyaWNfcHJlZGljdG9yc10pCnRlc3RfeCAgPSBzY2FsZSh0ZXN0X2RhdGFbLCBudW1lcmljX3ByZWRpY3RvcnNdKQp0cmFpbl95ID0gdHJhaW5fZGF0YSRyaXNrCnRlc3RfeSAgPSB0ZXN0X2RhdGEkcmlzawoKIyBUcnkgRGlmZmVyZW50IGsgVmFsdWVzIGFuZCBQbG90IEFjY3VyYWN5CmtfdmFsdWVzID0gMToyMAphY2N1cmFjaWVzID0gbnVtZXJpYyhsZW5ndGgoa192YWx1ZXMpKQpmb3IgKGkgaW4ga192YWx1ZXMpIHsKICBwcmVkX2sgPSBrbm4odHJhaW5feCwgdGVzdF94LCB0cmFpbl95LCBrID0gaSkKICBhY2N1cmFjaWVzW2ldID0gbWVhbihwcmVkX2sgPT0gdGVzdF95KQp9CnBsb3Qoa192YWx1ZXMsIGFjY3VyYWNpZXMsIHR5cGUgPSAiYiIsIHBjaCA9IDE5LCBjb2wgPSAiYmx1ZSIsCiAgICAgeGxhYiA9ICJrIChOdW1iZXIgb2YgTmVpZ2hib3JzKSIsIHlsYWIgPSAiQWNjdXJhY3kiLCBtYWluID0gIktOTiBBY2N1cmFjeSB2cyBrIikKYmVzdF9rID0ga192YWx1ZXNbd2hpY2gubWF4KGFjY3VyYWNpZXMpXQpwcmVkX2tubiA9IGtubih0cmFpbl94LCB0ZXN0X3gsIHRyYWluX3ksIGsgPSBiZXN0X2spCm1lYW4ocHJlZF9rbm4gPT0gdGVzdF95KQpjb25mdXNpb25NYXRyaXgocHJlZF9rbm4sIHRlc3RfeSkKYGBgCgojIyMgVXNlIFJhbmRvbSBGb3Jlc3QKYGBge3J9CnNldC5zZWVkKDQzMikKYWNjdXJhY2llcyA9IG51bWVyaWMoMjAwKQpmb3IgKG50cmVlIGluIDE6MjAwKSB7CiAgcmZfbW9kID0gcmFuZG9tRm9yZXN0KHJpc2sgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgbnRyZWUgPSBudHJlZSkKICByZl9wcmVkID0gcHJlZGljdChyZl9tb2QsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpCiAgYWNjdXJhY2llc1tudHJlZV0gPSBtZWFuKHJmX3ByZWQgPT0gdGVzdF9kYXRhJHJpc2spCn0KCiMgUGxvdApwbG90KDE6MjAwLCBhY2N1cmFjaWVzLCB0eXBlID0gImwiLCBjb2wgPSAiYmx1ZSIsCiAgICAgeGxhYiA9ICJOdW1iZXIgb2YgVHJlZXMiLCB5bGFiID0gIkFjY3VyYWN5IiwKICAgICBtYWluID0gIlJhbmRvbSBGb3Jlc3QgQWNjdXJhY3kgdnMgTnVtYmVyIG9mIFRyZWVzIikKYWJsaW5lKGggPSBtYXgoYWNjdXJhY2llcyksIGNvbCA9ICJncmVlbiIsIGx0eSA9IDIpCmFibGluZSh2ID0gd2hpY2gubWF4KGFjY3VyYWNpZXMpLCBjb2wgPSAicmVkIiwgbHR5ID0gMikKCiMgRmluZCB0aGUgYmVzdCBudW1iZXIgb2YgdHJlZXMgYW5kIGl0cyBhY2N1cmFjeQpiZXN0X250cmVlIDwtIHdoaWNoLm1heChhY2N1cmFjaWVzKQpiZXN0X2FjY3VyYWN5IDwtIG1heChhY2N1cmFjaWVzKQpjYXQoIkJlc3QgbnVtYmVyIG9mIHRyZWVzOiIsIGJlc3RfbnRyZWUsICJcbiIpCmNhdCgiSGlnaGVzdCBhY2N1cmFjeToiLCByb3VuZChiZXN0X2FjY3VyYWN5LCA0KSwgIlxuIikKYGBgCgoKCgoKCgo=