In this lecture, we apply Best Subset Selection, Lasso Regression, and Ridge Regression to build predictive models. Following modern R development practices, we implement these using the standard tidyverse-compliant frameworks: tidymodels for the shrinkage methods and modelr for the subset selection resamples. We use ggplot2 for visualizing tuning curves and provide code for DT to generate interactive tables.

knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)

Introduction & Objectives

In this lecture, we apply Best Subset Selection, Lasso Regression, and Ridge Regression to build predictive models. This lecture focuses on the regression task of predicting the per capita crime rate (crim) using the classic Boston housing dataset.

# Install missing packages if necessary
# install.packages(c("tidymodels", "modelr", "ISLR2", "DT", "leaps", "glmnet","plotly"))

# Load libraries
library(tidymodels)
library(modelr)
library(ISLR2)
library(DT)
library(ggplot2)
library(leaps)
library(dplyr)
library(purrr)
library(tidyr)
library(stringr)
library(plotly)

# Load the classic Boston dataset
data(Boston)

# Set seed for reproducibility and establish 10-fold CV splits
set.seed(1)
boston_folds <- vfold_cv(Boston, v = 10)

# Create a preprocessing recipe (normalizing features is mandatory for Lasso/Ridge)
boston_recipe <- recipe(crim ~ ., data = Boston) %>%
  step_normalize(all_predictors())

We will cover three foundational methodologies for feature selection and regularization:

  1. Best Subset Selection
  2. Lasso Regression (\(L_1\) Regularization)
  3. Ridge Regression (\(L_2\) Regularization)

Following modern R development practices, we implement these using tidyverse-compliant frameworks: tidymodels for the shrinkage methods and modelr for the subset selection resamples. Visualizations are built using ggplot2, and interactive data representations are generated via DT.

Best Subset vs. Lasso vs. Ridge

Mathematical Formulations

Best Subset Selection:

Seeks to minimize the Residual Sum of Squares (RSS) subject to a constraint on the total number of predictors \(p\):

\[\min_{\beta} \sum_{i=1}^n \left(y_i - \beta_0 - \sum_{j=1}^m \beta_j x_{ij}\right)^2 \quad \text{subject to} \quad \sum_{j=1}^m I(\beta_j \neq 0) \le p\]

Where \(I(\cdot)\) is an indicator function. This is a discrete optimization problem.

# Robust custom prediction helper for regsubsets
predict.regsubsets <- function(object, newdata, id, ...) {
  form <- as.formula(object$call[[2]])
  mat <- model.matrix(form, newdata)
  
  # Explicitly force evaluation of id to bypass environment masking issues
  subset_id <- as.integer(id)
  coefi <- coef(object, id = subset_id)
  
  xvars <- names(coefi)
  mat[, xvars, drop = FALSE] %*% coefi
}

# Define 10 cross-validation folds using modelr
set.seed(1)
cv_folds <- Boston %>% crossv_kfold(k = 10)

# Evaluate all valid subset sizes across all 10 folds
cv_subset_errors <- map_df(1:10, function(fold_idx) {
  train_df <- as.data.frame(cv_folds$train[[fold_idx]])
  test_df  <- as.data.frame(cv_folds$test[[fold_idx]])
  
  # Fit best subsets on training fold
  fit_subsets <- regsubsets(crim ~ ., data = train_df, nvmax = 13)
  
  # Determine how many models were ACTUALLY generated for this fold
  # (Prevents subscript out of bounds if it stops early)
  num_models_built <- length(fit_subsets$rss) - 1 # Subtract 1 because index 1 is the intercept-only model
  if(num_models_built < 1) return(NULL) # Defensive escape if no models built
  
  # Build the model matrix for the test data once per fold
  test_mat <- model.matrix(crim ~ ., data = test_df)
  actual   <- test_df$crim
  
  # Map safely only over the valid indices that were built
  map_df(1:num_models_built, function(i) {
    # Extract coefficients safely for subset size 'i'
    coefi <- coef(fit_subsets, id = i)
    xvars <- names(coefi)
    
    # Compute predictions manually via matrix multiplication
    preds <- test_mat[, xvars, drop = FALSE] %*% coefi
    
    rss <- sum((actual - preds)^2)
    tss <- sum((actual - mean(actual))^2)
    rsq_val <- 1 - (rss / tss)
    
    tibble(
      fold = fold_idx,
      size = i,
      rmse = sqrt(mean((actual - preds)^2)),
      mae  = mean(abs(actual - preds)),
      rsq  = rsq_val
    )
  })
})

# Summarize metrics across folds for each subset size
subset_cv_summary <- cv_subset_errors %>%
  group_by(size) %>%
  summarize(
    mean_rmse = mean(rmse),
    mean_mae  = mean(mae),
    mean_rsq  = mean(rsq)
  )

# Extract metrics for the optimal subset size (minimizing RMSE)
best_subset_size <- subset_cv_summary %>%
  filter(mean_rmse == min(mean_rmse)) %>%
  pull(size)

best_subset_metrics <- subset_cv_summary %>%
  filter(size == best_subset_size) %>%
  pivot_longer(cols = starts_with("mean_"), names_to = "metric", values_to = "estimate") %>%
  mutate(
    metric = str_replace(metric, "mean_", ""),
    model = "Best Subset Selection"
  ) %>%
  select(metric, estimate, model)


# Highlight the optimal size
optimal_size <- subset_cv_summary %>%
  filter(mean_rmse == min(mean_rmse)) %>%
  pull(size)

subset_curve_gg <- ggplot(subset_cv_summary, aes(x = size, y = mean_rmse,
                                                 text = paste("Subset Size:", size, 
                                                              "<br>CV RMSE:", round(mean_rmse, 4)))) +
  geom_line(color = "darkgreen", size = 1) +
  geom_point(color = "darkgreen", size = 2.5) +
  geom_vline(xintercept = optimal_size, linetype = "dashed", color = "grey40") +
  scale_x_continuous(breaks = 1:13) +
  labs(
    title = "Best Subset Selection: Cross-Validation Profile",
    x = "Number of Predictors (Subset Size)",
    y = "Mean Cross-Validated RMSE"
  ) +
  theme_minimal()

ggplotly(subset_curve_gg, tooltip = "text")

This plot shows how the 10-fold cross-validated RMSE drops as variables are added, highlighting the mathematically “optimal” model size with a dashed line.

full_subsets <- regsubsets(crim ~ ., data = Boston, nvmax = 13)
subsets_summary <- summary(full_subsets)

inclusion_matrix <- as.data.frame(subsets_summary$which) %>%
  mutate(subset_size = row_number()) %>%
  pivot_longer(cols = -subset_size, names_to = "Variable", values_to = "Included") %>%
  filter(Variable != "(Intercept)") %>%
  mutate(Status = if_else(Included, "Selected", "Excluded"))

matrix_gg <- ggplot(inclusion_matrix, aes(x = factor(subset_size), y = Variable, fill = Status,
                                         text = paste("Subset Size (p):", subset_size,
                                                      "<br>Variable:", Variable,
                                                      "<br>Status:", Status))) +
  geom_tile(color = "white", size = 0.5) +
  scale_fill_manual(values = c("Selected" = "darkblue", "Excluded" = "grey90")) +
  labs(
    title = "Best Subset Selection: Variable Inclusion Matrix",
    x = "Model Subset Size (p)",
    y = "Predictor Variable",
    fill = "Status"
  ) +
  theme_minimal() +
  theme(panel.grid = element_blank())

ggplotly(matrix_gg, tooltip = "text")

Let’s compare these results to the full linear regression model

full_lm_spec <- linear_reg() %>% set_engine("lm")

full_lm_wf <- workflow() %>%
  add_recipe(boston_recipe) %>%
  add_model(full_lm_spec)

# Force the use of yardstick metrics to bypass modelr namespace masking
full_lm_results <- fit_resamples(
  full_lm_wf,
  resamples = boston_folds,
  metrics = metric_set(yardstick::rmse, yardstick::rsq, yardstick::mae)
)

full_lm_metrics <- collect_metrics(full_lm_results) %>%
  select(metric = .metric, estimate = mean) %>%
  mutate(model = "Full Linear Regression")

# 1. Properly reshape the long metrics into a single row wide-format table
full_lm_cleaned <- full_lm_metrics %>%
  select(metric, estimate) %>%
  pivot_wider(names_from = metric, values_from = estimate) %>%
  mutate(Model = "Full Linear Regression") %>%
  select(Model, RMSE = rmse, MAE = mae, `R-squared` = rsq)

# 2. View it using DT::datatable
datatable(full_lm_cleaned, options = list(dom = 't'))

Lasso Regression (\(L_1\) Penalty)

Introduces a continuous penalty based on the absolute magnitudes of the coefficients:

\[\min_{\beta} \sum_{i=1}^n \left(y_i - \beta_0 - \sum_{j=1}^m \beta_j x_{ij}\right)^2 + \lambda \sum_{j=1}^m |\beta_j|\]

# --- Lasso Specification ---
lasso_spec <- linear_reg(penalty = tune(), mixture = 1) %>%
  set_engine("glmnet")

lasso_wf <- workflow() %>%
  add_recipe(boston_recipe) %>%
  add_model(lasso_spec)

# Define a grid of penalty parameters to test (10^-4 to 10^2)
penalty_grid <- grid_regular(penalty(range = c(-4, 2)), levels = 100)

# Run hyperparameter tuning via Cross-Validation
lasso_results <- tune_grid(
  lasso_wf,
  resamples = boston_folds,
  grid = penalty_grid,
  metrics = metric_set(yardstick::rmse, yardstick::rsq, yardstick::mae)
)

# Extract best hyperparameters based on minimizing RMSE
best_lasso <- select_best(lasso_results, metric = "rmse")

# Collect cross-validated performance metrics for the best models
lasso_cv_metrics <- collect_metrics(lasso_results) %>%
  filter(penalty == best_lasso$penalty) %>%
  select(metric = .metric, estimate = mean) %>%
  mutate(model = "Lasso Regression")

Ridge Regression (\(L_2\) Penalty):

Introduces a continuous penalty based on the squared magnitudes of the coefficients:

\[\min_{\beta} \sum_{i=1}^n \left(y_i - \beta_0 - \sum_{j=1}^m \beta_j x_{ij}\right)^2 + \lambda \sum_{j=1}^m \beta_j^2\]

# --- Ridge Specification ---
ridge_spec <- linear_reg(penalty = tune(), mixture = 0) %>%
  set_engine("glmnet")

ridge_wf <- workflow() %>%
  add_recipe(boston_recipe) %>%
  add_model(ridge_spec)

# Define a grid of penalty parameters to test (10^-4 to 10^2)
penalty_grid <- grid_regular(penalty(range = c(-4, 2)), levels = 100)

# Run hyperparameter tuning via Cross-Validation
ridge_results <- tune_grid(
  ridge_wf,
  resamples = boston_folds,
  grid = penalty_grid,
  metrics = metric_set(yardstick::rmse, yardstick::rsq, yardstick::mae)
)

# Extract best hyperparameters based on minimizing RMSE
best_ridge <- select_best(ridge_results, metric = "rmse")

# Collect cross-validated performance metrics for the best models
ridge_cv_metrics <- collect_metrics(ridge_results) %>%
  filter(penalty == best_ridge$penalty) %>%
  select(metric = .metric, estimate = mean) %>%
  mutate(model = "Ridge Regression")

Visualizing Hyperparameter Tuning Paths

Cross-validation evaluates the out-of-sample RMSE across a grid of penalty hyperparameters (\(\lambda\)) for Ridge and Lasso.

  • Lasso Curve: Out-of-sample RMSE is minimized around \(\lambda \approx 0.150\). As \(\lambda\) increases further, coefficients are shrunk to exactly zero, resulting in a rapidly rising error as the model underfits.
  • Ridge Curve: Out-of-sample RMSE is minimized at a much larger penalty value (\(\lambda \approx 1189.5\)). Since Ridge shrinks coefficients asymptotically toward zero but never excludes them entirely, the model is highly robust to a wider range of penalty values.

# 1. Extract and combine the tuning datasets, adding a identifying column
lasso_plot_data <- collect_metrics(lasso_results) %>%
  filter(.metric == "rmse") %>%
  mutate(Model = "Lasso (L1)")

ridge_plot_data <- collect_metrics(ridge_results) %>%
  filter(.metric == "rmse") %>%
  mutate(Model = "Ridge (L2)")

combined_tuning_data <- bind_rows(lasso_plot_data, ridge_plot_data)

# 2. Build the combined ggplot object with custom tooltip text
combined_gg <- ggplot(combined_tuning_data, 
                      aes(x = penalty, y = mean, color = Model, group = Model,
                          text = paste("Model:", Model,
                                       "<br>Lambda:", round(penalty, 5),
                                       "<br>10-Fold CV RMSE:", round(mean, 4)))) +
  geom_line(size = 1) +
  scale_x_log10() +
  scale_color_manual(values = c("Lasso (L1)" = "darkred", "Ridge (L2)" = "navy")) +
  
  # Add individual vertical dashed lines for each optimized penalty
  geom_vline(xintercept = best_lasso$penalty, linetype = "dashed", color = "darkred", alpha = 0.7) +
  geom_vline(xintercept = best_ridge$penalty, linetype = "dashed", color = "navy", alpha = 0.7) +
  
  labs(
    title = "Regularization Parameter Tuning Paths Matrix",
    x = "Penalty Parameter (Lambda on Log10 Scale)",
    y = "10-Fold CV RMSE",
    color = "Regularization Type"
  ) +
  theme_minimal()

# 3. Convert to an interactive plotly chart, specifying our custom text tooltip
ggplotly(combined_gg, tooltip = "text")

Core Differences and Trade-offs

Feature / Dimension Best Subset Selection Lasso Regression (\(L_1\)) Ridge Regression (\(L_2\))
Optimization Type Discrete combinatorial optimization (\(2^m\) possible models). Continuous convex optimization. Continuous convex optimization.
Computational Complexity Extremely high; becomes NP-hard and computationally intractable for large \(m\). Efficiently solved via coordinate descent (e.g., glmnet). Efficiently solved via closed-form matrix solutions or coordinate descent.
Sparsity & Variable Selection Yields a truly sparse model by explicitly dropping predictors. Yields a sparse model; forces coefficients to exactly zero at high \(\lambda\). Non-sparse; shrinks coefficients asymptotically toward zero but never excludes them entirely.
Handling Multicollinearity Can pick one arbitrary predictor from a group of highly correlated variables. Selects one variable from a correlated cluster and zeroes out the others; can be unstable. Shrinks correlated coefficients together, distributing weight across them stably.

Cross-Validated Model Comparison

Using 10-fold cross-validation to estimate the out-of-sample error across the entire dataset, we obtain the following performance metrics when predicting the per capita crime rate (crim):


# 1. Clean and pivot Lasso metrics
lasso_cleaned <- lasso_cv_metrics %>%
  select(metric, estimate) %>%
  pivot_wider(names_from = metric, values_from = estimate) %>%
  mutate(
    Model = "Lasso Regression",
    Hyperparameters = paste0("Penalty (λ) ≈ ", round(best_lasso$penalty, 4), ", scaled")
  )

# 2. Clean and pivot Ridge metrics
ridge_cleaned <- ridge_cv_metrics %>%
  select(metric, estimate) %>%
  pivot_wider(names_from = metric, values_from = estimate) %>%
  mutate(
    Model = "Ridge Regression",
    Hyperparameters = paste0("Penalty (λ) ≈ ", round(best_ridge$penalty, 4), ", scaled")
  )

# 3. Clean and pivot Full Linear Regression metrics
full_lm_cleaned <- full_lm_metrics %>%
  select(metric, estimate) %>%
  pivot_wider(names_from = metric, values_from = estimate) %>%
  mutate(
    Model = "Full Linear Regression",
    Hyperparameters = "Baseline (all 13 predictors)"
  )

# 4. Clean and pivot Best Subset metrics
# (Best subset already has 'model' instead of 'Model', let's align it)
subset_cleaned <- best_subset_metrics %>%
  pivot_wider(names_from = metric, values_from = estimate) %>%
  mutate(
    Model = "Best Subset Selection",
    Hyperparameters = paste0("Optimal Subset Size p = ", best_subset_size)
  )

# 5. Bind all datasets dynamically into a single master summary data frame
cv_master_comparison <- bind_rows(
  full_lm_cleaned,
  lasso_cleaned,
  ridge_cleaned,
  subset_cleaned
) %>%
  # Standardize column arrangements and capitalize metric headers
  select(Model, RMSE = rmse, MAE = mae, `R-squared` = rsq, Hyperparameters)

# 6. Render the live interactive DT Table
datatable(
  cv_master_comparison,
  extensions = 'Responsive',
  options = list(
    pageLength = 4,
    dom = 't',
    initComplete = JS(
      "function(settings, json) {",
      "$(this.api().table().header()).css({'background-color': '#2c3e50', 'color': '#fff'});",
      "}"
    )
  ),
  caption = htmltools::tags$caption(
    style = 'caption-side: top; text-align: left; color: #2c3e50; font-weight: bold; font-size: 16px;',
    'Dynamic 10-Fold Cross-Validated Performance Matrix (Target Variable: crim)'
  )
) %>%
  # Apply professional rounding to the active columns dynamically
  formatRound(columns = c('RMSE', 'MAE', 'R-squared'), digits = 4)

Discerning the Best Model under Different Scenarios

When selecting a model for production or scientific reporting, relying on a single metric is often misleading. The choice of RMSE, MAE, or \(R^2\) as your primary decision criterion depends entirely on the specific real-world scenario and mathematical objectives.

Root Mean Squared Error (RMSE) vs. Mean Absolute Error (MAE)

  • Mathematical Difference: RMSE squares individual errors before averaging, whereas MAE averages the absolute differences linearly:

\[RMSE = \sqrt{\frac{1}{n} \sum_{i=1}^n (y_i - \hat{y}_i)^2} \quad \text{vs.} \quad MAE = \frac{1}{n} \sum_{i=1}^n |y_i - \hat{y}_i|\]

  • When to prefer RMSE: Prefer RMSE when large errors are disproportionately more costly or dangerous than small errors. Because of the squaring term, RMSE penalizes outliers heavily. For example, if predicting structural loads or financial liquidity requirements, being off by 10 units once is far worse than being off by 1 unit ten times.
  • When to prefer MAE: Prefer MAE when you want an easily interpretable metric that represents the typical or median error magnitude and is robust to outliers.
  • Case Study (Boston Dataset): In our results, Ridge Regression achieves the lowest MAE (\(2.8096\)) but has a higher RMSE (\(6.6154\)) than Lasso (\(6.6042\)). This indicates that Ridge performs better on “typical” suburbs, but makes a few larger, more egregious errors compared to Lasso. If those extreme outliers are anomalous and do not reflect everyday operations, MAE provides a more representative measure of typical performance.

\(R^2\) (R-squared) vs. MAE / RMSE

  • Standardization: RMSE and MAE are scale-dependent absolute metrics expressed in the original units of the target variable. \(R^2\) is a relative, unitless metric representing the proportion of variance explained:

\[R^2 = 1 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2}\]

  • When to prefer \(R^2\): Use \(R^2\) when you want to compare model performance across different target variables or different datasets that are on completely different scales. For example, if you want to know if your housing-value model fits better than your crime-rate model, you cannot compare their RMSEs (as dollars cannot be directly compared to per-capita rates), but you can compare their \(R^2\) values (e.g., \(80\\%\) variance explained vs. \(41\\%\)).
  • The Pitfall of \(R^2\): \(R^2\) should not be used in isolation because it does not tell you if the absolute predictions are accurate enough for practical use. If the underlying variance of your target is extremely high, a model with a high \(R^2\) can still yield predictions with unacceptably large absolute errors.
LS0tDQp0aXRsZTogIkJlc3QgU3Vic2V0IFNlbGVjdGlvbiwgTGFzc28gUmVncmVzc2lvbiwgYW5kIFJpZGdlIFJlZ3Jlc3Npb24gIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2MtZGVwdGg6IDMNCiAgICB0aGVtZTogY29zbW8NCiAgICBoaWdobGlnaHQtc3R5bGU6IHRoaXN0bGUNCi0tLQ0KDQpJbiB0aGlzIGxlY3R1cmUsIHdlIGFwcGx5IEJlc3QgU3Vic2V0IFNlbGVjdGlvbiwgTGFzc28gUmVncmVzc2lvbiwgYW5kIFJpZGdlIFJlZ3Jlc3Npb24gdG8gYnVpbGQgcHJlZGljdGl2ZSBtb2RlbHMuIEZvbGxvd2luZyBtb2Rlcm4gYFJgIGRldmVsb3BtZW50IHByYWN0aWNlcywgd2UgaW1wbGVtZW50IHRoZXNlIHVzaW5nIHRoZSBzdGFuZGFyZCBgdGlkeXZlcnNlYC1jb21wbGlhbnQgZnJhbWV3b3JrczogYHRpZHltb2RlbHNgIGZvciB0aGUgc2hyaW5rYWdlIG1ldGhvZHMgYW5kIGBtb2RlbHJgIGZvciB0aGUgc3Vic2V0IHNlbGVjdGlvbiByZXNhbXBsZXMuIFdlIHVzZSBgZ2dwbG90MmAgZm9yIHZpc3VhbGl6aW5nIHR1bmluZyBjdXJ2ZXMgYW5kIHByb3ZpZGUgY29kZSBmb3IgYERUYCB0byBnZW5lcmF0ZSBpbnRlcmFjdGl2ZSB0YWJsZXMuDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9VFJVRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24gJiBPYmplY3RpdmVzDQoNCkluIHRoaXMgbGVjdHVyZSwgd2UgYXBwbHkgQmVzdCBTdWJzZXQgU2VsZWN0aW9uLCBMYXNzbyBSZWdyZXNzaW9uLCBhbmQgUmlkZ2UgUmVncmVzc2lvbiB0byBidWlsZCBwcmVkaWN0aXZlIG1vZGVscy4gVGhpcyBsZWN0dXJlIGZvY3VzZXMgb24gdGhlIHJlZ3Jlc3Npb24gdGFzayBvZiBwcmVkaWN0aW5nIHRoZSBwZXIgY2FwaXRhIGNyaW1lIHJhdGUgKGBjcmltYCkgdXNpbmcgdGhlIGNsYXNzaWMgW0Jvc3RvbiBob3VzaW5nIGRhdGFzZXRdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vY29kZS9wcmFzYWRwZXJlcmEvdGhlLWJvc3Rvbi1ob3VzaW5nLWRhdGFzZXQpLg0KDQpgYGB7cn0NCiMgSW5zdGFsbCBtaXNzaW5nIHBhY2thZ2VzIGlmIG5lY2Vzc2FyeQ0KIyBpbnN0YWxsLnBhY2thZ2VzKGMoInRpZHltb2RlbHMiLCAibW9kZWxyIiwgIklTTFIyIiwgIkRUIiwgImxlYXBzIiwgImdsbW5ldCIsInBsb3RseSIpKQ0KDQojIExvYWQgbGlicmFyaWVzDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkoSVNMUjIpDQpsaWJyYXJ5KERUKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShsZWFwcykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHB1cnJyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkocGxvdGx5KQ0KDQojIExvYWQgdGhlIGNsYXNzaWMgQm9zdG9uIGRhdGFzZXQNCmRhdGEoQm9zdG9uKQ0KDQojIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkgYW5kIGVzdGFibGlzaCAxMC1mb2xkIENWIHNwbGl0cw0Kc2V0LnNlZWQoMSkNCmJvc3Rvbl9mb2xkcyA8LSB2Zm9sZF9jdihCb3N0b24sIHYgPSAxMCkNCg0KIyBDcmVhdGUgYSBwcmVwcm9jZXNzaW5nIHJlY2lwZSAobm9ybWFsaXppbmcgZmVhdHVyZXMgaXMgbWFuZGF0b3J5IGZvciBMYXNzby9SaWRnZSkNCmJvc3Rvbl9yZWNpcGUgPC0gcmVjaXBlKGNyaW0gfiAuLCBkYXRhID0gQm9zdG9uKSAlPiUNCiAgc3RlcF9ub3JtYWxpemUoYWxsX3ByZWRpY3RvcnMoKSkNCmBgYA0KDQoNCldlIHdpbGwgY292ZXIgdGhyZWUgZm91bmRhdGlvbmFsIG1ldGhvZG9sb2dpZXMgZm9yIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCByZWd1bGFyaXphdGlvbjoNCg0KMS4gKipCZXN0IFN1YnNldCBTZWxlY3Rpb24qKg0KMi4gKipMYXNzbyBSZWdyZXNzaW9uICgkTF8xJCBSZWd1bGFyaXphdGlvbikqKg0KMy4gKipSaWRnZSBSZWdyZXNzaW9uICgkTF8yJCBSZWd1bGFyaXphdGlvbikqKg0KDQpGb2xsb3dpbmcgbW9kZXJuIGBSYCBkZXZlbG9wbWVudCBwcmFjdGljZXMsIHdlIGltcGxlbWVudCB0aGVzZSB1c2luZyB0aWR5dmVyc2UtY29tcGxpYW50IGZyYW1ld29ya3M6IGB0aWR5bW9kZWxzYCBmb3IgdGhlIHNocmlua2FnZSBtZXRob2RzIGFuZCBgbW9kZWxyYCBmb3IgdGhlIHN1YnNldCBzZWxlY3Rpb24gcmVzYW1wbGVzLiBWaXN1YWxpemF0aW9ucyBhcmUgYnVpbHQgdXNpbmcgYGdncGxvdDJgLCBhbmQgaW50ZXJhY3RpdmUgZGF0YSByZXByZXNlbnRhdGlvbnMgYXJlIGdlbmVyYXRlZCB2aWEgYERUYC4NCg0KDQojIEJlc3QgU3Vic2V0IHZzLiBMYXNzbyB2cy4gUmlkZ2UNCg0KIyMgTWF0aGVtYXRpY2FsIEZvcm11bGF0aW9ucw0KDQojIyMgQmVzdCBTdWJzZXQgU2VsZWN0aW9uOg0KU2Vla3MgdG8gbWluaW1pemUgdGhlIFJlc2lkdWFsIFN1bSBvZiBTcXVhcmVzIChSU1MpIHN1YmplY3QgdG8gYSBjb25zdHJhaW50IG9uIHRoZSB0b3RhbCBudW1iZXIgb2YgcHJlZGljdG9ycyAkcCQ6DQoNCiQkXG1pbl97XGJldGF9IFxzdW1fe2k9MX1ebiBcbGVmdCh5X2kgLSBcYmV0YV8wIC0gXHN1bV97aj0xfV5tIFxiZXRhX2ogeF97aWp9XHJpZ2h0KV4yIFxxdWFkIFx0ZXh0e3N1YmplY3QgdG99IFxxdWFkIFxzdW1fe2o9MX1ebSBJKFxiZXRhX2ogXG5lcSAwKSBcbGUgcCQkDQoNCldoZXJlICRJKFxjZG90KSQgaXMgYW4gaW5kaWNhdG9yIGZ1bmN0aW9uLiBUaGlzIGlzIGEgZGlzY3JldGUgb3B0aW1pemF0aW9uIHByb2JsZW0uDQoNCmBgYHtyfQ0KIyBSb2J1c3QgY3VzdG9tIHByZWRpY3Rpb24gaGVscGVyIGZvciByZWdzdWJzZXRzDQpwcmVkaWN0LnJlZ3N1YnNldHMgPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhLCBpZCwgLi4uKSB7DQogIGZvcm0gPC0gYXMuZm9ybXVsYShvYmplY3QkY2FsbFtbMl1dKQ0KICBtYXQgPC0gbW9kZWwubWF0cml4KGZvcm0sIG5ld2RhdGEpDQogIA0KICAjIEV4cGxpY2l0bHkgZm9yY2UgZXZhbHVhdGlvbiBvZiBpZCB0byBieXBhc3MgZW52aXJvbm1lbnQgbWFza2luZyBpc3N1ZXMNCiAgc3Vic2V0X2lkIDwtIGFzLmludGVnZXIoaWQpDQogIGNvZWZpIDwtIGNvZWYob2JqZWN0LCBpZCA9IHN1YnNldF9pZCkNCiAgDQogIHh2YXJzIDwtIG5hbWVzKGNvZWZpKQ0KICBtYXRbLCB4dmFycywgZHJvcCA9IEZBTFNFXSAlKiUgY29lZmkNCn0NCg0KIyBEZWZpbmUgMTAgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcyB1c2luZyBtb2RlbHINCnNldC5zZWVkKDEpDQpjdl9mb2xkcyA8LSBCb3N0b24gJT4lIGNyb3Nzdl9rZm9sZChrID0gMTApDQoNCiMgRXZhbHVhdGUgYWxsIHZhbGlkIHN1YnNldCBzaXplcyBhY3Jvc3MgYWxsIDEwIGZvbGRzDQpjdl9zdWJzZXRfZXJyb3JzIDwtIG1hcF9kZigxOjEwLCBmdW5jdGlvbihmb2xkX2lkeCkgew0KICB0cmFpbl9kZiA8LSBhcy5kYXRhLmZyYW1lKGN2X2ZvbGRzJHRyYWluW1tmb2xkX2lkeF1dKQ0KICB0ZXN0X2RmICA8LSBhcy5kYXRhLmZyYW1lKGN2X2ZvbGRzJHRlc3RbW2ZvbGRfaWR4XV0pDQogIA0KICAjIEZpdCBiZXN0IHN1YnNldHMgb24gdHJhaW5pbmcgZm9sZA0KICBmaXRfc3Vic2V0cyA8LSByZWdzdWJzZXRzKGNyaW0gfiAuLCBkYXRhID0gdHJhaW5fZGYsIG52bWF4ID0gMTMpDQogIA0KICAjIERldGVybWluZSBob3cgbWFueSBtb2RlbHMgd2VyZSBBQ1RVQUxMWSBnZW5lcmF0ZWQgZm9yIHRoaXMgZm9sZA0KICAjIChQcmV2ZW50cyBzdWJzY3JpcHQgb3V0IG9mIGJvdW5kcyBpZiBpdCBzdG9wcyBlYXJseSkNCiAgbnVtX21vZGVsc19idWlsdCA8LSBsZW5ndGgoZml0X3N1YnNldHMkcnNzKSAtIDEgIyBTdWJ0cmFjdCAxIGJlY2F1c2UgaW5kZXggMSBpcyB0aGUgaW50ZXJjZXB0LW9ubHkgbW9kZWwNCiAgaWYobnVtX21vZGVsc19idWlsdCA8IDEpIHJldHVybihOVUxMKSAjIERlZmVuc2l2ZSBlc2NhcGUgaWYgbm8gbW9kZWxzIGJ1aWx0DQogIA0KICAjIEJ1aWxkIHRoZSBtb2RlbCBtYXRyaXggZm9yIHRoZSB0ZXN0IGRhdGEgb25jZSBwZXIgZm9sZA0KICB0ZXN0X21hdCA8LSBtb2RlbC5tYXRyaXgoY3JpbSB+IC4sIGRhdGEgPSB0ZXN0X2RmKQ0KICBhY3R1YWwgICA8LSB0ZXN0X2RmJGNyaW0NCiAgDQogICMgTWFwIHNhZmVseSBvbmx5IG92ZXIgdGhlIHZhbGlkIGluZGljZXMgdGhhdCB3ZXJlIGJ1aWx0DQogIG1hcF9kZigxOm51bV9tb2RlbHNfYnVpbHQsIGZ1bmN0aW9uKGkpIHsNCiAgICAjIEV4dHJhY3QgY29lZmZpY2llbnRzIHNhZmVseSBmb3Igc3Vic2V0IHNpemUgJ2knDQogICAgY29lZmkgPC0gY29lZihmaXRfc3Vic2V0cywgaWQgPSBpKQ0KICAgIHh2YXJzIDwtIG5hbWVzKGNvZWZpKQ0KICAgIA0KICAgICMgQ29tcHV0ZSBwcmVkaWN0aW9ucyBtYW51YWxseSB2aWEgbWF0cml4IG11bHRpcGxpY2F0aW9uDQogICAgcHJlZHMgPC0gdGVzdF9tYXRbLCB4dmFycywgZHJvcCA9IEZBTFNFXSAlKiUgY29lZmkNCiAgICANCiAgICByc3MgPC0gc3VtKChhY3R1YWwgLSBwcmVkcyleMikNCiAgICB0c3MgPC0gc3VtKChhY3R1YWwgLSBtZWFuKGFjdHVhbCkpXjIpDQogICAgcnNxX3ZhbCA8LSAxIC0gKHJzcyAvIHRzcykNCiAgICANCiAgICB0aWJibGUoDQogICAgICBmb2xkID0gZm9sZF9pZHgsDQogICAgICBzaXplID0gaSwNCiAgICAgIHJtc2UgPSBzcXJ0KG1lYW4oKGFjdHVhbCAtIHByZWRzKV4yKSksDQogICAgICBtYWUgID0gbWVhbihhYnMoYWN0dWFsIC0gcHJlZHMpKSwNCiAgICAgIHJzcSAgPSByc3FfdmFsDQogICAgKQ0KICB9KQ0KfSkNCg0KIyBTdW1tYXJpemUgbWV0cmljcyBhY3Jvc3MgZm9sZHMgZm9yIGVhY2ggc3Vic2V0IHNpemUNCnN1YnNldF9jdl9zdW1tYXJ5IDwtIGN2X3N1YnNldF9lcnJvcnMgJT4lDQogIGdyb3VwX2J5KHNpemUpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWVhbl9ybXNlID0gbWVhbihybXNlKSwNCiAgICBtZWFuX21hZSAgPSBtZWFuKG1hZSksDQogICAgbWVhbl9yc3EgID0gbWVhbihyc3EpDQogICkNCg0KIyBFeHRyYWN0IG1ldHJpY3MgZm9yIHRoZSBvcHRpbWFsIHN1YnNldCBzaXplIChtaW5pbWl6aW5nIFJNU0UpDQpiZXN0X3N1YnNldF9zaXplIDwtIHN1YnNldF9jdl9zdW1tYXJ5ICU+JQ0KICBmaWx0ZXIobWVhbl9ybXNlID09IG1pbihtZWFuX3Jtc2UpKSAlPiUNCiAgcHVsbChzaXplKQ0KDQpiZXN0X3N1YnNldF9tZXRyaWNzIDwtIHN1YnNldF9jdl9zdW1tYXJ5ICU+JQ0KICBmaWx0ZXIoc2l6ZSA9PSBiZXN0X3N1YnNldF9zaXplKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgibWVhbl8iKSwgbmFtZXNfdG8gPSAibWV0cmljIiwgdmFsdWVzX3RvID0gImVzdGltYXRlIikgJT4lDQogIG11dGF0ZSgNCiAgICBtZXRyaWMgPSBzdHJfcmVwbGFjZShtZXRyaWMsICJtZWFuXyIsICIiKSwNCiAgICBtb2RlbCA9ICJCZXN0IFN1YnNldCBTZWxlY3Rpb24iDQogICkgJT4lDQogIHNlbGVjdChtZXRyaWMsIGVzdGltYXRlLCBtb2RlbCkNCg0KDQojIEhpZ2hsaWdodCB0aGUgb3B0aW1hbCBzaXplDQpvcHRpbWFsX3NpemUgPC0gc3Vic2V0X2N2X3N1bW1hcnkgJT4lDQogIGZpbHRlcihtZWFuX3Jtc2UgPT0gbWluKG1lYW5fcm1zZSkpICU+JQ0KICBwdWxsKHNpemUpDQoNCnN1YnNldF9jdXJ2ZV9nZyA8LSBnZ3Bsb3Qoc3Vic2V0X2N2X3N1bW1hcnksIGFlcyh4ID0gc2l6ZSwgeSA9IG1lYW5fcm1zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gcGFzdGUoIlN1YnNldCBTaXplOiIsIHNpemUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPGJyPkNWIFJNU0U6Iiwgcm91bmQobWVhbl9ybXNlLCA0KSkpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJkYXJrZ3JlZW4iLCBzaXplID0gMSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gImRhcmtncmVlbiIsIHNpemUgPSAyLjUpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gb3B0aW1hbF9zaXplLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmV5NDAiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEzKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQmVzdCBTdWJzZXQgU2VsZWN0aW9uOiBDcm9zcy1WYWxpZGF0aW9uIFByb2ZpbGUiLA0KICAgIHggPSAiTnVtYmVyIG9mIFByZWRpY3RvcnMgKFN1YnNldCBTaXplKSIsDQogICAgeSA9ICJNZWFuIENyb3NzLVZhbGlkYXRlZCBSTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdncGxvdGx5KHN1YnNldF9jdXJ2ZV9nZywgdG9vbHRpcCA9ICJ0ZXh0IikNCmBgYA0KVGhpcyBwbG90IHNob3dzIGhvdyB0aGUgMTAtZm9sZCBjcm9zcy12YWxpZGF0ZWQgUk1TRSBkcm9wcyBhcyB2YXJpYWJsZXMgYXJlIGFkZGVkLCBoaWdobGlnaHRpbmcgdGhlIG1hdGhlbWF0aWNhbGx5ICJvcHRpbWFsIiBtb2RlbCBzaXplIHdpdGggYSBkYXNoZWQgbGluZS4NCg0KYGBge3J9DQpmdWxsX3N1YnNldHMgPC0gcmVnc3Vic2V0cyhjcmltIH4gLiwgZGF0YSA9IEJvc3RvbiwgbnZtYXggPSAxMykNCnN1YnNldHNfc3VtbWFyeSA8LSBzdW1tYXJ5KGZ1bGxfc3Vic2V0cykNCg0KaW5jbHVzaW9uX21hdHJpeCA8LSBhcy5kYXRhLmZyYW1lKHN1YnNldHNfc3VtbWFyeSR3aGljaCkgJT4lDQogIG11dGF0ZShzdWJzZXRfc2l6ZSA9IHJvd19udW1iZXIoKSkgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gLXN1YnNldF9zaXplLCBuYW1lc190byA9ICJWYXJpYWJsZSIsIHZhbHVlc190byA9ICJJbmNsdWRlZCIpICU+JQ0KICBmaWx0ZXIoVmFyaWFibGUgIT0gIihJbnRlcmNlcHQpIikgJT4lDQogIG11dGF0ZShTdGF0dXMgPSBpZl9lbHNlKEluY2x1ZGVkLCAiU2VsZWN0ZWQiLCAiRXhjbHVkZWQiKSkNCg0KbWF0cml4X2dnIDwtIGdncGxvdChpbmNsdXNpb25fbWF0cml4LCBhZXMoeCA9IGZhY3RvcihzdWJzZXRfc2l6ZSksIHkgPSBWYXJpYWJsZSwgZmlsbCA9IFN0YXR1cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IHBhc3RlKCJTdWJzZXQgU2l6ZSAocCk6Iiwgc3Vic2V0X3NpemUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPGJyPlZhcmlhYmxlOiIsIFZhcmlhYmxlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxicj5TdGF0dXM6IiwgU3RhdHVzKSkpICsNCiAgZ2VvbV90aWxlKGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDAuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJTZWxlY3RlZCIgPSAiZGFya2JsdWUiLCAiRXhjbHVkZWQiID0gImdyZXk5MCIpKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQmVzdCBTdWJzZXQgU2VsZWN0aW9uOiBWYXJpYWJsZSBJbmNsdXNpb24gTWF0cml4IiwNCiAgICB4ID0gIk1vZGVsIFN1YnNldCBTaXplIChwKSIsDQogICAgeSA9ICJQcmVkaWN0b3IgVmFyaWFibGUiLA0KICAgIGZpbGwgPSAiU3RhdHVzIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KZ2dwbG90bHkobWF0cml4X2dnLCB0b29sdGlwID0gInRleHQiKQ0KYGBgDQoNCkxldCdzIGNvbXBhcmUgdGhlc2UgcmVzdWx0cyB0byB0aGUgZnVsbCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbA0KDQpgYGB7cn0NCmZ1bGxfbG1fc3BlYyA8LSBsaW5lYXJfcmVnKCkgJT4lIHNldF9lbmdpbmUoImxtIikNCg0KZnVsbF9sbV93ZiA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfcmVjaXBlKGJvc3Rvbl9yZWNpcGUpICU+JQ0KICBhZGRfbW9kZWwoZnVsbF9sbV9zcGVjKQ0KDQojIEZvcmNlIHRoZSB1c2Ugb2YgeWFyZHN0aWNrIG1ldHJpY3MgdG8gYnlwYXNzIG1vZGVsciBuYW1lc3BhY2UgbWFza2luZw0KZnVsbF9sbV9yZXN1bHRzIDwtIGZpdF9yZXNhbXBsZXMoDQogIGZ1bGxfbG1fd2YsDQogIHJlc2FtcGxlcyA9IGJvc3Rvbl9mb2xkcywNCiAgbWV0cmljcyA9IG1ldHJpY19zZXQoeWFyZHN0aWNrOjpybXNlLCB5YXJkc3RpY2s6OnJzcSwgeWFyZHN0aWNrOjptYWUpDQopDQoNCmZ1bGxfbG1fbWV0cmljcyA8LSBjb2xsZWN0X21ldHJpY3MoZnVsbF9sbV9yZXN1bHRzKSAlPiUNCiAgc2VsZWN0KG1ldHJpYyA9IC5tZXRyaWMsIGVzdGltYXRlID0gbWVhbikgJT4lDQogIG11dGF0ZShtb2RlbCA9ICJGdWxsIExpbmVhciBSZWdyZXNzaW9uIikNCg0KIyAxLiBQcm9wZXJseSByZXNoYXBlIHRoZSBsb25nIG1ldHJpY3MgaW50byBhIHNpbmdsZSByb3cgd2lkZS1mb3JtYXQgdGFibGUNCmZ1bGxfbG1fY2xlYW5lZCA8LSBmdWxsX2xtX21ldHJpY3MgJT4lDQogIHNlbGVjdChtZXRyaWMsIGVzdGltYXRlKSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1ldHJpYywgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkgJT4lDQogIG11dGF0ZShNb2RlbCA9ICJGdWxsIExpbmVhciBSZWdyZXNzaW9uIikgJT4lDQogIHNlbGVjdChNb2RlbCwgUk1TRSA9IHJtc2UsIE1BRSA9IG1hZSwgYFItc3F1YXJlZGAgPSByc3EpDQoNCiMgMi4gVmlldyBpdCB1c2luZyBEVDo6ZGF0YXRhYmxlDQpkYXRhdGFibGUoZnVsbF9sbV9jbGVhbmVkLCBvcHRpb25zID0gbGlzdChkb20gPSAndCcpKQ0KYGBgDQoNCiMjIyBMYXNzbyBSZWdyZXNzaW9uICgkTF8xJCBQZW5hbHR5KQ0KDQpJbnRyb2R1Y2VzIGEgY29udGludW91cyBwZW5hbHR5IGJhc2VkIG9uIHRoZSBhYnNvbHV0ZSBtYWduaXR1ZGVzIG9mIHRoZSBjb2VmZmljaWVudHM6DQoNCiQkXG1pbl97XGJldGF9IFxzdW1fe2k9MX1ebiBcbGVmdCh5X2kgLSBcYmV0YV8wIC0gXHN1bV97aj0xfV5tIFxiZXRhX2ogeF97aWp9XHJpZ2h0KV4yICsgXGxhbWJkYSBcc3VtX3tqPTF9Xm0gfFxiZXRhX2p8JCQNCg0KYGBge3J9DQojIC0tLSBMYXNzbyBTcGVjaWZpY2F0aW9uIC0tLQ0KbGFzc29fc3BlYyA8LSBsaW5lYXJfcmVnKHBlbmFsdHkgPSB0dW5lKCksIG1peHR1cmUgPSAxKSAlPiUNCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikNCg0KbGFzc29fd2YgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX3JlY2lwZShib3N0b25fcmVjaXBlKSAlPiUNCiAgYWRkX21vZGVsKGxhc3NvX3NwZWMpDQoNCiMgRGVmaW5lIGEgZ3JpZCBvZiBwZW5hbHR5IHBhcmFtZXRlcnMgdG8gdGVzdCAoMTBeLTQgdG8gMTBeMikNCnBlbmFsdHlfZ3JpZCA8LSBncmlkX3JlZ3VsYXIocGVuYWx0eShyYW5nZSA9IGMoLTQsIDIpKSwgbGV2ZWxzID0gMTAwKQ0KDQojIFJ1biBoeXBlcnBhcmFtZXRlciB0dW5pbmcgdmlhIENyb3NzLVZhbGlkYXRpb24NCmxhc3NvX3Jlc3VsdHMgPC0gdHVuZV9ncmlkKA0KICBsYXNzb193ZiwNCiAgcmVzYW1wbGVzID0gYm9zdG9uX2ZvbGRzLA0KICBncmlkID0gcGVuYWx0eV9ncmlkLA0KICBtZXRyaWNzID0gbWV0cmljX3NldCh5YXJkc3RpY2s6OnJtc2UsIHlhcmRzdGljazo6cnNxLCB5YXJkc3RpY2s6Om1hZSkNCikNCg0KIyBFeHRyYWN0IGJlc3QgaHlwZXJwYXJhbWV0ZXJzIGJhc2VkIG9uIG1pbmltaXppbmcgUk1TRQ0KYmVzdF9sYXNzbyA8LSBzZWxlY3RfYmVzdChsYXNzb19yZXN1bHRzLCBtZXRyaWMgPSAicm1zZSIpDQoNCiMgQ29sbGVjdCBjcm9zcy12YWxpZGF0ZWQgcGVyZm9ybWFuY2UgbWV0cmljcyBmb3IgdGhlIGJlc3QgbW9kZWxzDQpsYXNzb19jdl9tZXRyaWNzIDwtIGNvbGxlY3RfbWV0cmljcyhsYXNzb19yZXN1bHRzKSAlPiUNCiAgZmlsdGVyKHBlbmFsdHkgPT0gYmVzdF9sYXNzbyRwZW5hbHR5KSAlPiUNCiAgc2VsZWN0KG1ldHJpYyA9IC5tZXRyaWMsIGVzdGltYXRlID0gbWVhbikgJT4lDQogIG11dGF0ZShtb2RlbCA9ICJMYXNzbyBSZWdyZXNzaW9uIikNCmBgYA0KDQojIyMgUmlkZ2UgUmVncmVzc2lvbiAoJExfMiQgUGVuYWx0eSk6DQpJbnRyb2R1Y2VzIGEgY29udGludW91cyBwZW5hbHR5IGJhc2VkIG9uIHRoZSBzcXVhcmVkIG1hZ25pdHVkZXMgb2YgdGhlIGNvZWZmaWNpZW50czoNCg0KJCRcbWluX3tcYmV0YX0gXHN1bV97aT0xfV5uIFxsZWZ0KHlfaSAtIFxiZXRhXzAgLSBcc3VtX3tqPTF9Xm0gXGJldGFfaiB4X3tpan1ccmlnaHQpXjIgKyBcbGFtYmRhIFxzdW1fe2o9MX1ebSBcYmV0YV9qXjIkJA0KDQpgYGB7cn0NCiMgLS0tIFJpZGdlIFNwZWNpZmljYXRpb24gLS0tDQpyaWRnZV9zcGVjIDwtIGxpbmVhcl9yZWcocGVuYWx0eSA9IHR1bmUoKSwgbWl4dHVyZSA9IDApICU+JQ0KICBzZXRfZW5naW5lKCJnbG1uZXQiKQ0KDQpyaWRnZV93ZiA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfcmVjaXBlKGJvc3Rvbl9yZWNpcGUpICU+JQ0KICBhZGRfbW9kZWwocmlkZ2Vfc3BlYykNCg0KIyBEZWZpbmUgYSBncmlkIG9mIHBlbmFsdHkgcGFyYW1ldGVycyB0byB0ZXN0ICgxMF4tNCB0byAxMF4yKQ0KcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KHJhbmdlID0gYygtNCwgMikpLCBsZXZlbHMgPSAxMDApDQoNCiMgUnVuIGh5cGVycGFyYW1ldGVyIHR1bmluZyB2aWEgQ3Jvc3MtVmFsaWRhdGlvbg0KcmlkZ2VfcmVzdWx0cyA8LSB0dW5lX2dyaWQoDQogIHJpZGdlX3dmLA0KICByZXNhbXBsZXMgPSBib3N0b25fZm9sZHMsDQogIGdyaWQgPSBwZW5hbHR5X2dyaWQsDQogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHlhcmRzdGljazo6cm1zZSwgeWFyZHN0aWNrOjpyc3EsIHlhcmRzdGljazo6bWFlKQ0KKQ0KDQojIEV4dHJhY3QgYmVzdCBoeXBlcnBhcmFtZXRlcnMgYmFzZWQgb24gbWluaW1pemluZyBSTVNFDQpiZXN0X3JpZGdlIDwtIHNlbGVjdF9iZXN0KHJpZGdlX3Jlc3VsdHMsIG1ldHJpYyA9ICJybXNlIikNCg0KIyBDb2xsZWN0IGNyb3NzLXZhbGlkYXRlZCBwZXJmb3JtYW5jZSBtZXRyaWNzIGZvciB0aGUgYmVzdCBtb2RlbHMNCnJpZGdlX2N2X21ldHJpY3MgPC0gY29sbGVjdF9tZXRyaWNzKHJpZGdlX3Jlc3VsdHMpICU+JQ0KICBmaWx0ZXIocGVuYWx0eSA9PSBiZXN0X3JpZGdlJHBlbmFsdHkpICU+JQ0KICBzZWxlY3QobWV0cmljID0gLm1ldHJpYywgZXN0aW1hdGUgPSBtZWFuKSAlPiUNCiAgbXV0YXRlKG1vZGVsID0gIlJpZGdlIFJlZ3Jlc3Npb24iKQ0KYGBgDQoNCiMjIyBWaXN1YWxpemluZyBIeXBlcnBhcmFtZXRlciBUdW5pbmcgUGF0aHMNCg0KQ3Jvc3MtdmFsaWRhdGlvbiBldmFsdWF0ZXMgdGhlIG91dC1vZi1zYW1wbGUgUk1TRSBhY3Jvc3MgYSBncmlkIG9mIHBlbmFsdHkgaHlwZXJwYXJhbWV0ZXJzICgkXGxhbWJkYSQpIGZvciBSaWRnZSBhbmQgTGFzc28uDQoNCiogKipMYXNzbyBDdXJ2ZToqKiBPdXQtb2Ytc2FtcGxlIFJNU0UgaXMgbWluaW1pemVkIGFyb3VuZCAkXGxhbWJkYSBcYXBwcm94IDAuMTUwJC4gQXMgJFxsYW1iZGEkIGluY3JlYXNlcyBmdXJ0aGVyLCBjb2VmZmljaWVudHMgYXJlIHNocnVuayB0byBleGFjdGx5IHplcm8sIHJlc3VsdGluZyBpbiBhIHJhcGlkbHkgcmlzaW5nIGVycm9yIGFzIHRoZSBtb2RlbCB1bmRlcmZpdHMuDQoqICoqUmlkZ2UgQ3VydmU6KiogT3V0LW9mLXNhbXBsZSBSTVNFIGlzIG1pbmltaXplZCBhdCBhIG11Y2ggbGFyZ2VyIHBlbmFsdHkgdmFsdWUgKCRcbGFtYmRhIFxhcHByb3ggMTE4OS41JCkuIFNpbmNlIFJpZGdlIHNocmlua3MgY29lZmZpY2llbnRzIGFzeW1wdG90aWNhbGx5IHRvd2FyZCB6ZXJvIGJ1dCBuZXZlciBleGNsdWRlcyB0aGVtIGVudGlyZWx5LCB0aGUgbW9kZWwgaXMgaGlnaGx5IHJvYnVzdCB0byBhIHdpZGVyIHJhbmdlIG9mIHBlbmFsdHkgdmFsdWVzLg0KDQpgYGB7ciBjb21iaW5lZC10dW5pbmctcGxvdHMsIGVjaG89VFJVRX0NCg0KIyAxLiBFeHRyYWN0IGFuZCBjb21iaW5lIHRoZSB0dW5pbmcgZGF0YXNldHMsIGFkZGluZyBhIGlkZW50aWZ5aW5nIGNvbHVtbg0KbGFzc29fcGxvdF9kYXRhIDwtIGNvbGxlY3RfbWV0cmljcyhsYXNzb19yZXN1bHRzKSAlPiUNCiAgZmlsdGVyKC5tZXRyaWMgPT0gInJtc2UiKSAlPiUNCiAgbXV0YXRlKE1vZGVsID0gIkxhc3NvIChMMSkiKQ0KDQpyaWRnZV9wbG90X2RhdGEgPC0gY29sbGVjdF9tZXRyaWNzKHJpZGdlX3Jlc3VsdHMpICU+JQ0KICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JQ0KICBtdXRhdGUoTW9kZWwgPSAiUmlkZ2UgKEwyKSIpDQoNCmNvbWJpbmVkX3R1bmluZ19kYXRhIDwtIGJpbmRfcm93cyhsYXNzb19wbG90X2RhdGEsIHJpZGdlX3Bsb3RfZGF0YSkNCg0KIyAyLiBCdWlsZCB0aGUgY29tYmluZWQgZ2dwbG90IG9iamVjdCB3aXRoIGN1c3RvbSB0b29sdGlwIHRleHQNCmNvbWJpbmVkX2dnIDwtIGdncGxvdChjb21iaW5lZF90dW5pbmdfZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBwZW5hbHR5LCB5ID0gbWVhbiwgY29sb3IgPSBNb2RlbCwgZ3JvdXAgPSBNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IHBhc3RlKCJNb2RlbDoiLCBNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8YnI+TGFtYmRhOiIsIHJvdW5kKHBlbmFsdHksIDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxicj4xMC1Gb2xkIENWIFJNU0U6Iiwgcm91bmQobWVhbiwgNCkpKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsNCiAgc2NhbGVfeF9sb2cxMCgpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIkxhc3NvIChMMSkiID0gImRhcmtyZWQiLCAiUmlkZ2UgKEwyKSIgPSAibmF2eSIpKSArDQogIA0KICAjIEFkZCBpbmRpdmlkdWFsIHZlcnRpY2FsIGRhc2hlZCBsaW5lcyBmb3IgZWFjaCBvcHRpbWl6ZWQgcGVuYWx0eQ0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBiZXN0X2xhc3NvJHBlbmFsdHksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImRhcmtyZWQiLCBhbHBoYSA9IDAuNykgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBiZXN0X3JpZGdlJHBlbmFsdHksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gIm5hdnkiLCBhbHBoYSA9IDAuNykgKw0KICANCiAgbGFicygNCiAgICB0aXRsZSA9ICJSZWd1bGFyaXphdGlvbiBQYXJhbWV0ZXIgVHVuaW5nIFBhdGhzIE1hdHJpeCIsDQogICAgeCA9ICJQZW5hbHR5IFBhcmFtZXRlciAoTGFtYmRhIG9uIExvZzEwIFNjYWxlKSIsDQogICAgeSA9ICIxMC1Gb2xkIENWIFJNU0UiLA0KICAgIGNvbG9yID0gIlJlZ3VsYXJpemF0aW9uIFR5cGUiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyAzLiBDb252ZXJ0IHRvIGFuIGludGVyYWN0aXZlIHBsb3RseSBjaGFydCwgc3BlY2lmeWluZyBvdXIgY3VzdG9tIHRleHQgdG9vbHRpcA0KZ2dwbG90bHkoY29tYmluZWRfZ2csIHRvb2x0aXAgPSAidGV4dCIpDQpgYGANCg0KDQoNCg0KIyMgQ29yZSBEaWZmZXJlbmNlcyBhbmQgVHJhZGUtb2Zmcw0KDQp8IEZlYXR1cmUgLyBEaW1lbnNpb24gfCBCZXN0IFN1YnNldCBTZWxlY3Rpb24gfCBMYXNzbyBSZWdyZXNzaW9uICgkTF8xJCkgfCBSaWRnZSBSZWdyZXNzaW9uICgkTF8yJCkgfA0KfCAtLS0gfCAtLS0gfCAtLS0gfCAtLS0gfA0KfCAqKk9wdGltaXphdGlvbiBUeXBlKiogfCBEaXNjcmV0ZSBjb21iaW5hdG9yaWFsIG9wdGltaXphdGlvbiAoJDJebSQgcG9zc2libGUgbW9kZWxzKS4gfCBDb250aW51b3VzIGNvbnZleCBvcHRpbWl6YXRpb24uIHwgQ29udGludW91cyBjb252ZXggb3B0aW1pemF0aW9uLiB8DQp8ICoqQ29tcHV0YXRpb25hbCBDb21wbGV4aXR5KiogfCBFeHRyZW1lbHkgaGlnaDsgYmVjb21lcyBOUC1oYXJkIGFuZCBjb21wdXRhdGlvbmFsbHkgaW50cmFjdGFibGUgZm9yIGxhcmdlICRtJC4gfCBFZmZpY2llbnRseSBzb2x2ZWQgdmlhIGNvb3JkaW5hdGUgZGVzY2VudCAoZS5nLiwgYGdsbW5ldGApLiB8IEVmZmljaWVudGx5IHNvbHZlZCB2aWEgY2xvc2VkLWZvcm0gbWF0cml4IHNvbHV0aW9ucyBvciBjb29yZGluYXRlIGRlc2NlbnQuIHwNCnwgKipTcGFyc2l0eSAmIFZhcmlhYmxlIFNlbGVjdGlvbioqIHwgWWllbGRzIGEgdHJ1bHkgc3BhcnNlIG1vZGVsIGJ5IGV4cGxpY2l0bHkgZHJvcHBpbmcgcHJlZGljdG9ycy4gfCBZaWVsZHMgYSBzcGFyc2UgbW9kZWw7IGZvcmNlcyBjb2VmZmljaWVudHMgdG8gZXhhY3RseSB6ZXJvIGF0IGhpZ2ggJFxsYW1iZGEkLiB8IE5vbi1zcGFyc2U7IHNocmlua3MgY29lZmZpY2llbnRzIGFzeW1wdG90aWNhbGx5IHRvd2FyZCB6ZXJvIGJ1dCBuZXZlciBleGNsdWRlcyB0aGVtIGVudGlyZWx5LiB8DQp8ICoqSGFuZGxpbmcgTXVsdGljb2xsaW5lYXJpdHkqKiB8IENhbiBwaWNrIG9uZSBhcmJpdHJhcnkgcHJlZGljdG9yIGZyb20gYSBncm91cCBvZiBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMuIHwgU2VsZWN0cyBvbmUgdmFyaWFibGUgZnJvbSBhIGNvcnJlbGF0ZWQgY2x1c3RlciBhbmQgemVyb2VzIG91dCB0aGUgb3RoZXJzOyBjYW4gYmUgdW5zdGFibGUuIHwgU2hyaW5rcyBjb3JyZWxhdGVkIGNvZWZmaWNpZW50cyB0b2dldGhlciwgZGlzdHJpYnV0aW5nIHdlaWdodCBhY3Jvc3MgdGhlbSBzdGFibHkuIHwNCg0KDQojIENyb3NzLVZhbGlkYXRlZCBNb2RlbCBDb21wYXJpc29uDQoNClVzaW5nICoqMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uKiogdG8gZXN0aW1hdGUgdGhlIG91dC1vZi1zYW1wbGUgZXJyb3IgYWNyb3NzIHRoZSBlbnRpcmUgZGF0YXNldCwgd2Ugb2J0YWluIHRoZSBmb2xsb3dpbmcgcGVyZm9ybWFuY2UgbWV0cmljcyB3aGVuIHByZWRpY3RpbmcgdGhlIHBlciBjYXBpdGEgY3JpbWUgcmF0ZSAoYGNyaW1gKToNCg0KDQpgYGB7ciBkeW5hbWljLWNvbXBhcmlzb24tdGFibGUsIGVjaG89VFJVRX0NCg0KIyAxLiBDbGVhbiBhbmQgcGl2b3QgTGFzc28gbWV0cmljcw0KbGFzc29fY2xlYW5lZCA8LSBsYXNzb19jdl9tZXRyaWNzICU+JQ0KICBzZWxlY3QobWV0cmljLCBlc3RpbWF0ZSkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtZXRyaWMsIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBtdXRhdGUoDQogICAgTW9kZWwgPSAiTGFzc28gUmVncmVzc2lvbiIsDQogICAgSHlwZXJwYXJhbWV0ZXJzID0gcGFzdGUwKCJQZW5hbHR5ICjOuykg4omIICIsIHJvdW5kKGJlc3RfbGFzc28kcGVuYWx0eSwgNCksICIsIHNjYWxlZCIpDQogICkNCg0KIyAyLiBDbGVhbiBhbmQgcGl2b3QgUmlkZ2UgbWV0cmljcw0KcmlkZ2VfY2xlYW5lZCA8LSByaWRnZV9jdl9tZXRyaWNzICU+JQ0KICBzZWxlY3QobWV0cmljLCBlc3RpbWF0ZSkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtZXRyaWMsIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBtdXRhdGUoDQogICAgTW9kZWwgPSAiUmlkZ2UgUmVncmVzc2lvbiIsDQogICAgSHlwZXJwYXJhbWV0ZXJzID0gcGFzdGUwKCJQZW5hbHR5ICjOuykg4omIICIsIHJvdW5kKGJlc3RfcmlkZ2UkcGVuYWx0eSwgNCksICIsIHNjYWxlZCIpDQogICkNCg0KIyAzLiBDbGVhbiBhbmQgcGl2b3QgRnVsbCBMaW5lYXIgUmVncmVzc2lvbiBtZXRyaWNzDQpmdWxsX2xtX2NsZWFuZWQgPC0gZnVsbF9sbV9tZXRyaWNzICU+JQ0KICBzZWxlY3QobWV0cmljLCBlc3RpbWF0ZSkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtZXRyaWMsIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBtdXRhdGUoDQogICAgTW9kZWwgPSAiRnVsbCBMaW5lYXIgUmVncmVzc2lvbiIsDQogICAgSHlwZXJwYXJhbWV0ZXJzID0gIkJhc2VsaW5lIChhbGwgMTMgcHJlZGljdG9ycykiDQogICkNCg0KIyA0LiBDbGVhbiBhbmQgcGl2b3QgQmVzdCBTdWJzZXQgbWV0cmljcw0KIyAoQmVzdCBzdWJzZXQgYWxyZWFkeSBoYXMgJ21vZGVsJyBpbnN0ZWFkIG9mICdNb2RlbCcsIGxldCdzIGFsaWduIGl0KQ0Kc3Vic2V0X2NsZWFuZWQgPC0gYmVzdF9zdWJzZXRfbWV0cmljcyAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1ldHJpYywgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkgJT4lDQogIG11dGF0ZSgNCiAgICBNb2RlbCA9ICJCZXN0IFN1YnNldCBTZWxlY3Rpb24iLA0KICAgIEh5cGVycGFyYW1ldGVycyA9IHBhc3RlMCgiT3B0aW1hbCBTdWJzZXQgU2l6ZSBwID0gIiwgYmVzdF9zdWJzZXRfc2l6ZSkNCiAgKQ0KDQojIDUuIEJpbmQgYWxsIGRhdGFzZXRzIGR5bmFtaWNhbGx5IGludG8gYSBzaW5nbGUgbWFzdGVyIHN1bW1hcnkgZGF0YSBmcmFtZQ0KY3ZfbWFzdGVyX2NvbXBhcmlzb24gPC0gYmluZF9yb3dzKA0KICBmdWxsX2xtX2NsZWFuZWQsDQogIGxhc3NvX2NsZWFuZWQsDQogIHJpZGdlX2NsZWFuZWQsDQogIHN1YnNldF9jbGVhbmVkDQopICU+JQ0KICAjIFN0YW5kYXJkaXplIGNvbHVtbiBhcnJhbmdlbWVudHMgYW5kIGNhcGl0YWxpemUgbWV0cmljIGhlYWRlcnMNCiAgc2VsZWN0KE1vZGVsLCBSTVNFID0gcm1zZSwgTUFFID0gbWFlLCBgUi1zcXVhcmVkYCA9IHJzcSwgSHlwZXJwYXJhbWV0ZXJzKQ0KDQojIDYuIFJlbmRlciB0aGUgbGl2ZSBpbnRlcmFjdGl2ZSBEVCBUYWJsZQ0KZGF0YXRhYmxlKA0KICBjdl9tYXN0ZXJfY29tcGFyaXNvbiwNCiAgZXh0ZW5zaW9ucyA9ICdSZXNwb25zaXZlJywNCiAgb3B0aW9ucyA9IGxpc3QoDQogICAgcGFnZUxlbmd0aCA9IDQsDQogICAgZG9tID0gJ3QnLA0KICAgIGluaXRDb21wbGV0ZSA9IEpTKA0KICAgICAgImZ1bmN0aW9uKHNldHRpbmdzLCBqc29uKSB7IiwNCiAgICAgICIkKHRoaXMuYXBpKCkudGFibGUoKS5oZWFkZXIoKSkuY3NzKHsnYmFja2dyb3VuZC1jb2xvcic6ICcjMmMzZTUwJywgJ2NvbG9yJzogJyNmZmYnfSk7IiwNCiAgICAgICJ9Ig0KICAgICkNCiAgKSwNCiAgY2FwdGlvbiA9IGh0bWx0b29sczo6dGFncyRjYXB0aW9uKA0KICAgIHN0eWxlID0gJ2NhcHRpb24tc2lkZTogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBjb2xvcjogIzJjM2U1MDsgZm9udC13ZWlnaHQ6IGJvbGQ7IGZvbnQtc2l6ZTogMTZweDsnLA0KICAgICdEeW5hbWljIDEwLUZvbGQgQ3Jvc3MtVmFsaWRhdGVkIFBlcmZvcm1hbmNlIE1hdHJpeCAoVGFyZ2V0IFZhcmlhYmxlOiBjcmltKScNCiAgKQ0KKSAlPiUNCiAgIyBBcHBseSBwcm9mZXNzaW9uYWwgcm91bmRpbmcgdG8gdGhlIGFjdGl2ZSBjb2x1bW5zIGR5bmFtaWNhbGx5DQogIGZvcm1hdFJvdW5kKGNvbHVtbnMgPSBjKCdSTVNFJywgJ01BRScsICdSLXNxdWFyZWQnKSwgZGlnaXRzID0gNCkNCmBgYA0KDQoNCg0KIyBEaXNjZXJuaW5nIHRoZSBCZXN0IE1vZGVsIHVuZGVyIERpZmZlcmVudCBTY2VuYXJpb3MNCg0KV2hlbiBzZWxlY3RpbmcgYSBtb2RlbCBmb3IgcHJvZHVjdGlvbiBvciBzY2llbnRpZmljIHJlcG9ydGluZywgcmVseWluZyBvbiBhIHNpbmdsZSBtZXRyaWMgaXMgb2Z0ZW4gbWlzbGVhZGluZy4gVGhlIGNob2ljZSBvZiBSTVNFLCBNQUUsIG9yICRSXjIkIGFzIHlvdXIgcHJpbWFyeSBkZWNpc2lvbiBjcml0ZXJpb24gZGVwZW5kcyBlbnRpcmVseSBvbiB0aGUgc3BlY2lmaWMgcmVhbC13b3JsZCBzY2VuYXJpbyBhbmQgbWF0aGVtYXRpY2FsIG9iamVjdGl2ZXMuDQoNCiMjIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSB2cy4gTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKQ0KDQoqICoqTWF0aGVtYXRpY2FsIERpZmZlcmVuY2U6KiogUk1TRSBzcXVhcmVzIGluZGl2aWR1YWwgZXJyb3JzIGJlZm9yZSBhdmVyYWdpbmcsIHdoZXJlYXMgTUFFIGF2ZXJhZ2VzIHRoZSBhYnNvbHV0ZSBkaWZmZXJlbmNlcyBsaW5lYXJseToNCg0KJCRSTVNFID0gXHNxcnR7XGZyYWN7MX17bn0gXHN1bV97aT0xfV5uICh5X2kgLSBcaGF0e3l9X2kpXjJ9IFxxdWFkIFx0ZXh0e3ZzLn0gXHF1YWQgTUFFID0gXGZyYWN7MX17bn0gXHN1bV97aT0xfV5uIHx5X2kgLSBcaGF0e3l9X2l8JCQNCg0KDQoqICoqV2hlbiB0byBwcmVmZXIgUk1TRToqKiBQcmVmZXIgUk1TRSB3aGVuICoqbGFyZ2UgZXJyb3JzIGFyZSBkaXNwcm9wb3J0aW9uYXRlbHkgbW9yZSBjb3N0bHkgb3IgZGFuZ2Vyb3VzIHRoYW4gc21hbGwgZXJyb3JzKiouIEJlY2F1c2Ugb2YgdGhlIHNxdWFyaW5nIHRlcm0sIFJNU0UgcGVuYWxpemVzIG91dGxpZXJzIGhlYXZpbHkuIEZvciBleGFtcGxlLCBpZiBwcmVkaWN0aW5nIHN0cnVjdHVyYWwgbG9hZHMgb3IgZmluYW5jaWFsIGxpcXVpZGl0eSByZXF1aXJlbWVudHMsIGJlaW5nIG9mZiBieSAxMCB1bml0cyBvbmNlIGlzIGZhciB3b3JzZSB0aGFuIGJlaW5nIG9mZiBieSAxIHVuaXQgdGVuIHRpbWVzLg0KKiAqKldoZW4gdG8gcHJlZmVyIE1BRToqKiBQcmVmZXIgTUFFIHdoZW4geW91IHdhbnQgYW4gZWFzaWx5IGludGVycHJldGFibGUgbWV0cmljIHRoYXQgcmVwcmVzZW50cyB0aGUgKip0eXBpY2FsIG9yIG1lZGlhbiBlcnJvciBtYWduaXR1ZGUqKiBhbmQgaXMgKipyb2J1c3QgdG8gb3V0bGllcnMqKi4NCiogKipDYXNlIFN0dWR5IChCb3N0b24gRGF0YXNldCk6KiogSW4gb3VyIHJlc3VsdHMsIFJpZGdlIFJlZ3Jlc3Npb24gYWNoaWV2ZXMgdGhlIGxvd2VzdCBNQUUgKCQyLjgwOTYkKSBidXQgaGFzIGEgaGlnaGVyIFJNU0UgKCQ2LjYxNTQkKSB0aGFuIExhc3NvICgkNi42MDQyJCkuIFRoaXMgaW5kaWNhdGVzIHRoYXQgUmlkZ2UgcGVyZm9ybXMgYmV0dGVyIG9uICJ0eXBpY2FsIiBzdWJ1cmJzLCBidXQgbWFrZXMgYSBmZXcgbGFyZ2VyLCBtb3JlIGVncmVnaW91cyBlcnJvcnMgY29tcGFyZWQgdG8gTGFzc28uIElmIHRob3NlIGV4dHJlbWUgb3V0bGllcnMgYXJlIGFub21hbG91cyBhbmQgZG8gbm90IHJlZmxlY3QgZXZlcnlkYXkgb3BlcmF0aW9ucywgTUFFIHByb3ZpZGVzIGEgbW9yZSByZXByZXNlbnRhdGl2ZSBtZWFzdXJlIG9mIHR5cGljYWwgcGVyZm9ybWFuY2UuDQoNCiMjICRSXjIkIChSLXNxdWFyZWQpIHZzLiBNQUUgLyBSTVNFDQoNCiogKipTdGFuZGFyZGl6YXRpb246KiogUk1TRSBhbmQgTUFFIGFyZSAqKnNjYWxlLWRlcGVuZGVudCoqIGFic29sdXRlIG1ldHJpY3MgZXhwcmVzc2VkIGluIHRoZSBvcmlnaW5hbCB1bml0cyBvZiB0aGUgdGFyZ2V0IHZhcmlhYmxlLiAkUl4yJCBpcyBhICoqcmVsYXRpdmUsIHVuaXRsZXNzKiogbWV0cmljIHJlcHJlc2VudGluZyB0aGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQ6DQoNCiQkUl4yID0gMSAtIFxmcmFje1xzdW0gKHlfaSAtIFxoYXR7eX1faSleMn17XHN1bSAoeV9pIC0gXGJhcnt5fSleMn0kJA0KDQoNCiogKipXaGVuIHRvIHByZWZlciAkUl4yJDoqKiBVc2UgJFJeMiQgd2hlbiB5b3Ugd2FudCB0byAqKmNvbXBhcmUgbW9kZWwgcGVyZm9ybWFuY2UgYWNyb3NzIGRpZmZlcmVudCB0YXJnZXQgdmFyaWFibGVzIG9yIGRpZmZlcmVudCBkYXRhc2V0cyoqIHRoYXQgYXJlIG9uIGNvbXBsZXRlbHkgZGlmZmVyZW50IHNjYWxlcy4gRm9yIGV4YW1wbGUsIGlmIHlvdSB3YW50IHRvIGtub3cgaWYgeW91ciBob3VzaW5nLXZhbHVlIG1vZGVsIGZpdHMgYmV0dGVyIHRoYW4geW91ciBjcmltZS1yYXRlIG1vZGVsLCB5b3UgY2Fubm90IGNvbXBhcmUgdGhlaXIgUk1TRXMgKGFzIGRvbGxhcnMgY2Fubm90IGJlIGRpcmVjdGx5IGNvbXBhcmVkIHRvIHBlci1jYXBpdGEgcmF0ZXMpLCBidXQgeW91IGNhbiBjb21wYXJlIHRoZWlyICRSXjIkIHZhbHVlcyAoZS5nLiwgJDgwXFwlJCB2YXJpYW5jZSBleHBsYWluZWQgdnMuICQ0MVxcJSQpLg0KKiAqKlRoZSBQaXRmYWxsIG9mICRSXjIkOioqICRSXjIkIHNob3VsZCBub3QgYmUgdXNlZCBpbiBpc29sYXRpb24gYmVjYXVzZSBpdCBkb2VzIG5vdCB0ZWxsIHlvdSBpZiB0aGUgYWJzb2x1dGUgcHJlZGljdGlvbnMgYXJlIGFjY3VyYXRlIGVub3VnaCBmb3IgcHJhY3RpY2FsIHVzZS4gSWYgdGhlIHVuZGVybHlpbmcgdmFyaWFuY2Ugb2YgeW91ciB0YXJnZXQgaXMgZXh0cmVtZWx5IGhpZ2gsIGEgbW9kZWwgd2l0aCBhIGhpZ2ggJFJeMiQgY2FuIHN0aWxsIHlpZWxkIHByZWRpY3Rpb25zIHdpdGggdW5hY2NlcHRhYmx5IGxhcmdlIGFic29sdXRlIGVycm9ycy4NCg==