# Rmarkdown stuff
library(rmarkdown, quietly = TRUE)
library(knitr, quietly = TRUE)
library(htmltools, quietly = TRUE)
library(styler, quietly = TRUE)
library(xaringanExtra, quietly = TRUE)

# Dataset
library(ISLR2, quietly = TRUE)

# Session info and package reporting
library(report, quietly = TRUE)

# Data wrangling

library(dplyr, quietly = TRUE)
library(magrittr, quietly = TRUE)

# Create interactive tables
library(reactable, quietly = TRUE)

# For plotting

library(ggplot2, quietly = TRUE)
# From github
library(parttree, quietly = TRUE)
library(ConfusionTableR, quietly = TRUE)

# For Decision Classification Trees
library(rpart, quietly = TRUE)
library(rpart.plot, quietly = TRUE)

# For Bagging and Random Forest
library(randomForest, quietly = TRUE)

# For Boosting
library(Ckmeans.1d.dp, quietly = TRUE)
library(xgboost, quietly = TRUE)
library(DiagrammeR, quietly = TRUE)
library(DiagrammeRsvg, quietly = TRUE)
library(rsvg, quietly = TRUE)

# For BART
library(dbarts, quietly = TRUE)
# From github
library(embarcadero, quietly = TRUE)

# For variable importance
library(vip, quietly = TRUE)

# For parallel processing for tuning
library(foreach, quietly = TRUE)
library(doParallel, quietly = TRUE)

# For applying tidymodels

library(rsample, quietly = TRUE)
library(recipes, quietly = TRUE)
library(parsnip, quietly = TRUE)
library(dials, quietly = TRUE)
library(tune, quietly = TRUE)
library(workflows, quietly = TRUE)
library(yardstick, quietly = TRUE)

summary(report::report(sessionInfo()))

The analysis was done using the R Statistical language (v4.2.0; R Core Team, 2022) on Windows 10 x64, using the packages randomForest (v4.7.1), vip (v0.3.2), ggplot2 (v3.3.5), rmarkdown (v2.14), report (v0.5.1), sp (v1.4.7), ROCR (v1.0.11), Ckmeans.1d.dp (v4.3.4), dbarts (v0.9.22), tidyverse (v1.3.1), knitr (v1.39), ConfusionTableR (v1.0.4), data.table (v1.14.2), DiagrammeR (v1.0.9), DiagrammeRsvg (v0.1), dials (v0.1.1), dismo (v1.3.5), doParallel (v1.0.17), dplyr (v1.0.9), embarcadero (v1.2.0.1003), forcats (v0.5.1), foreach (v1.5.2), ggpubr (v0.4.0), htmltools (v0.5.2), ISLR2 (v1.3.1), iterators (v1.0.14), magrittr (v2.0.3), matrixStats (v0.62.0), Metrics (v0.1.4), parsnip (v0.2.1), parttree (v0.0.1.9000), patchwork (v1.1.1), purrr (v0.3.4), raster (v3.5.15), reactable (v0.2.3), readr (v2.1.2), recipes (v0.2.0), rpart (v4.1.16), rpart.plot (v3.1.0), rsample (v0.1.1), rsvg (v2.3.1), scales (v1.2.0), stringr (v1.4.0), styler (v1.7.0), tibble (v3.1.6), tidyr (v1.2.0), tune (v0.2.0), workflows (v0.2.6), xaringanExtra (v0.5.5), xgboost (v1.6.0.1) and yardstick (v0.0.9).

Fitting Classification Trees

In the Carseats data set from the ISLR2 package, Sales of child car seats is a continuous variable, and so we begin by recording it as a binary variable called High, which takes on a value of Yes if the Sales variable exceeds 8, and takes on a value of No otherwise.

Carseats <- dplyr::as_tibble(ISLR2::Carseats) %>%
  dplyr::mutate(
    High = factor(dplyr::if_else(.data[["Sales"]] <= 8, "No", "Yes"))
  ) %>%
  dplyr::select(-dplyr::all_of(c("Sales")))

Carseats %>%
  reactable::reactable(
    defaultPageSize = 5,
    filterable = TRUE
  )

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

Carseats_split <- rsample::initial_split(Carseats)

Carseats_train <- rsample::training(Carseats_split)
Carseats_test <- rsample::testing(Carseats_split)

Carseats_fold <- rsample::vfold_cv(Carseats_train, v = 10)

Create the preprocessor

We create a recipe with recipes::recipe. No other preprocessing step is done.

class_tree_recipe <-
  recipes::recipe(formula = High ~ ., data = Carseats_train)

Specify the model

Use parsnip::decision_tree, parsnip::set_mode and parsnip::set_engine to create the model.

The cost complexity is tuned using tune::tune

An engine specific parameter model = TRUE is set for rpart to prevent this warning message from coming up later when rpart.plot::rpart.plot is used later.

#> Warning: Cannot retrieve the data used to build the model (so cannot determine roundint and is.binary for the variables).
#> To silence this warning:
#>     Call rpart.plot with roundint=FALSE,
#>     or rebuild the rpart model with model=TRUE.
class_tree_spec <- parsnip::decision_tree(
  tree_depth = 4,
  cost_complexity = tune::tune()
) %>%
  parsnip::set_mode("classification") %>%
  parsnip::set_engine("rpart", model = TRUE)

class_tree_spec %>%
  parsnip::translate()
> Decision Tree Model Specification (classification)
> 
> Main Arguments:
>   cost_complexity = tune::tune()
>   tree_depth = 4
> 
> Engine-Specific Arguments:
>   model = TRUE
> 
> Computational engine: rpart 
> 
> Model fit template:
> rpart::rpart(formula = missing_arg(), data = missing_arg(), weights = missing_arg(), 
>     cp = tune::tune(), maxdepth = 4, model = TRUE)

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

class_tree_workflow <- workflows::workflow() %>%
  workflows::add_recipe(class_tree_recipe) %>%
  workflows::add_model(class_tree_spec)

class_tree_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (classification)
> 
> Main Arguments:
>   cost_complexity = tune::tune()
>   tree_depth = 4
> 
> Engine-Specific Arguments:
>   model = TRUE
> 
> Computational engine: rpart

Create the cost complexity grid

A cost complexity grid of \(10\) numbers from \(0.001\) (\(10^{-3}\)) to \(10\) (\(10^1\)) is created.

Regular grid is created using dials::grid_regular, dials::cost_complexity and scales::log10_trans

# Create a range from 10
cost_complexity_grid <-
  dials::grid_regular(
    x = dials::cost_complexity(
      range = c(-3, 1),
      trans = scales::log10_trans()
    ),
    levels = 10
  )

cost_complexity_grid %>%
  reactable::reactable(defaultPageSize = 5)

Classification tree model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

We use yardstick::metric_set, to choose a set of metrics to used to evaluate the model. In this example, yardstick::accuracy, yardstick::roc_auc, yardstick::sensitivity and yardstick::specificity are used.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = class_tree_workflow,
  resamples = Carseats_fold,
  grid = cost_complexity_grid,
  metrics = yardstick::metric_set(
    yardstick::accuracy,
    yardstick::roc_auc,
    yardstick::sensitivity,
    yardstick::specificity
  )
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 × 4
>    splits           id     .metrics .notes          
>    <list>           <chr>  <list>   <list>          
>  1 <split [270/30]> Fold01 <tibble> <tibble [0 × 3]>
>  2 <split [270/30]> Fold02 <tibble> <tibble [0 × 3]>
>  3 <split [270/30]> Fold03 <tibble> <tibble [0 × 3]>
>  4 <split [270/30]> Fold04 <tibble> <tibble [0 × 3]>
>  5 <split [270/30]> Fold05 <tibble> <tibble [0 × 3]>
>  6 <split [270/30]> Fold06 <tibble> <tibble [0 × 3]>
>  7 <split [270/30]> Fold07 <tibble> <tibble [0 × 3]>
>  8 <split [270/30]> Fold08 <tibble> <tibble [0 × 3]>
>  9 <split [270/30]> Fold09 <tibble> <tibble [0 × 3]>
> 10 <split [270/30]> Fold10 <tibble> <tibble [0 × 3]>

Here we see that the amount of cost complexity affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["cost_complexity"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::scale_x_log10() +
  ggplot2::theme(legend.position = "none")

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best, this function requires you to specify a metric that it should select against. The cost complexity value is 0.001 for metric accuracy since it gives the highest value. Do note that using a different seed will give a different best cost complexity value.

top_cost_complexity <- tune::show_best(tune_res, metric = c("accuracy"), n = 5)
top_cost_complexity %>%
  reactable::reactable(defaultPageSize = 5)
best_cost_complexity <- tune::select_best(tune_res, metric = "accuracy")
best_cost_complexity
> # A tibble: 1 × 2
>   cost_complexity .config              
>             <dbl> <chr>                
> 1          0.0215 Preprocessor1_Model04

Classification tree model with optimised cost complexity value

We create the classification tree workflow with the best cost complexity score using tune::finalize_workflow.

class_tree_final <- tune::finalize_workflow(
  x = class_tree_workflow,
  parameters = best_cost_complexity
)

class_tree_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (classification)
> 
> Main Arguments:
>   cost_complexity = 0.0215443469003188
>   tree_depth = 4
> 
> Engine-Specific Arguments:
>   model = TRUE
> 
> Computational engine: rpart

We now train the classification tree model with the training data using parsnip::fit

class_tree_final_fit <- parsnip::fit(object = class_tree_final, data = Carseats_train)

We can see the tree in greater detail using tune::extract_fit_engine

class_tree_final_fit %>%
  tune::extract_fit_engine()
> n= 300 
> 
> node), split, n, loss, yval, (yprob)
>       * denotes terminal node
> 
>  1) root 300 120 No (0.6000000 0.4000000)  
>    2) ShelveLoc=Bad,Medium 242  73 No (0.6983471 0.3016529)  
>      4) Price>=92.5 213  51 No (0.7605634 0.2394366)  
>        8) Advertising< 13.5 175  30 No (0.8285714 0.1714286) *
>        9) Advertising>=13.5 38  17 Yes (0.4473684 0.5526316)  
>         18) Age>=55.5 17   4 No (0.7647059 0.2352941) *
>         19) Age< 55.5 21   4 Yes (0.1904762 0.8095238) *
>      5) Price< 92.5 29   7 Yes (0.2413793 0.7586207) *
>    3) ShelveLoc=Good 58  11 Yes (0.1896552 0.8103448)  
>      6) Price>=136.5 9   3 No (0.6666667 0.3333333) *
>      7) Price< 136.5 49   5 Yes (0.1020408 0.8979592) *

We can visualise the above better with rpart.plot::rpart.plot

class_tree_final_fit %>%
  tune::extract_fit_engine() %>%
  rpart.plot::rpart.plot()

Variable Importance

What are the most important variables in this tree for predicting Sales?

Using the vip package and workflows::extract_fit_parsnip, we have

vip_table <- class_tree_final_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vi()

vip_table %>%
  reactable::reactable(defaultPageSize = 5)
class_tree_final_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vip(
    geom = "col",
    aesthetics = list(fill = "midnightblue", alpha = 0.8)
  ) +
  ggplot2::scale_y_continuous(expand = c(0, 0))

We can use parttree to understand why these two parameters work so well on the training set.

However this package only works with two continuous predictors. As ShelveLoc is a categorical variable, there is a need to “convert” them to continuous by making “Bad” as -1, “Medium” as 0 and “Good” as 1.

Carseats_train_modfified <- Carseats_train %>%
  dplyr::mutate(
    ShelveLoc = dplyr::case_when(
      .data[["ShelveLoc"]] == "Bad" ~ "-1",
      .data[["ShelveLoc"]] == "Medium" ~ "0",
      .data[["ShelveLoc"]] == "Good" ~ "1"
    )
  ) %>%
  dplyr::mutate(
    ShelveLoc = as.numeric(.data[["ShelveLoc"]])
  )

partial_tree <- parsnip::decision_tree(
  tree_depth = 30,
  cost_complexity = best_cost_complexity$cost_complexity
) %>%
  parsnip::set_mode("classification") %>%
  parsnip::set_engine("rpart", model = TRUE) %>%
  parsnip::fit(High ~ Price + ShelveLoc,
    data = Carseats_train_modfified
  ) %>%
  tune::extract_fit_engine()

Carseats_train_modfified %>%
  ggplot2::ggplot(
    mapping = ggplot2::aes(
      x = .data[["ShelveLoc"]],
      y = .data[["Price"]]
    )
  ) +
  parttree::geom_parttree(
    data = partial_tree,
    mapping = ggplot2::aes(fill = .data[["High"]]),
    alpha = 0.2
  ) +
  ggplot2::geom_jitter(
    alpha = 0.7,
    width = 0.05,
    height = 0.2,
    mapping = ggplot2::aes(color = .data[["High"]])
  )

Classification tree model on test data

Finally, let’s turn to the testing data. For classification models, a .pred_class, column and class probability columns named .pred_{level} are added when parsnip::augment is used.

test_results <- parsnip::augment(
  x = class_tree_final_fit,
  new_data = Carseats_test
)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We can view the confusion matrix using yardstick::conf_mat

test_results %>%
  yardstick::conf_mat(
    truth = .data[["High"]],
    estimate = .data[[".pred_class"]]
  ) %>%
  ggplot2::autoplot(type = "heatmap")

test_results %>%
  yardstick::conf_mat(truth = .data[["High"]], estimate = .data[[".pred_class"]]) %>%
  summary() %>%
  reactable::reactable(defaultPageSize = 5)

or by ConfusionTableR::binary_visualiseR

ConfusionTableR::binary_visualiseR(
  train_labels = test_results[["High"]],
  truth_labels = test_results[[".pred_class"]],
  class_label1 = "Yes",
  class_label2 = "No",
  quadrant_col1 = "#28ACB4",
  quadrant_col2 = "#4397D2",
  custom_title = "High Confusion Matrix",
  text_col = "black"
)

We can view the ROC curve using yardstick::roc_curve

roc_plot_data <- test_results %>%
  yardstick::roc_curve(
    truth = test_results[["High"]],
    .data[[".pred_No"]]
  )

roc_plot_data %>%
  reactable::reactable(defaultPageSize = 5)
roc_plot_data %>%
  ggplot2::autoplot()

Here is a ggplot2 version.

roc_plot_data %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = 1 - .data[["specificity"]],
    y = .data[["sensitivity"]]
  )) +
  ggplot2::geom_line(size = 1.5, color = "midnightblue") +
  ggplot2::geom_abline(
    lty = 2, alpha = 0.5,
    color = "gray50",
    size = 1.2
  ) +
  ggplot2::coord_equal()

To view the metrics, we can also use tune::last_fit and tune::collect_metrics.

metrics_results <- tune::last_fit(
  object = class_tree_final_fit,
  split = Carseats_split
) %>%
  tune::collect_metrics()

metrics_results %>%
  reactable::reactable(defaultPageSize = 5)

Fitting Regression Trees

In the Boston data set from the ISLR2 package, we want to predict medv, the median value of owner-occupied homes in $1000’s.

Boston <- dplyr::as_tibble(ISLR2::Boston)

Boston %>%
  reactable::reactable(
    defaultPageSize = 5,
    filterable = TRUE
  )

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

Boston_split <- rsample::initial_split(Boston)

Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)

Boston_fold <- rsample::vfold_cv(Boston_train, v = 10)

Create the preprocessor

We create a recipe with recipes::recipe. No other preprocessing step is done.

reg_tree_recipe <-
  recipes::recipe(formula = medv ~ ., data = Boston_train)

Specify the model

Use parsnip::decision_tree, parsnip::set_mode and parsnip::set_engine to create the model.

The cost complexity is tuned using tune::tune

An engine specific parameter model = TRUE is set for rpart to prevent this warning message from coming up later when rpart.plot::rpart.plot is used later.

#> Warning: Cannot retrieve the data used to build the model (so cannot determine roundint and is.binary for the variables).
#> To silence this warning:
#>     Call rpart.plot with roundint=FALSE,
#>     or rebuild the rpart model with model=TRUE.
reg_tree_spec <- parsnip::decision_tree(
  # tree_depth = 4,
  cost_complexity = tune::tune()
) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("rpart", model = TRUE)

reg_tree_spec %>%
  parsnip::translate()
> Decision Tree Model Specification (regression)
> 
> Main Arguments:
>   cost_complexity = tune::tune()
> 
> Engine-Specific Arguments:
>   model = TRUE
> 
> Computational engine: rpart 
> 
> Model fit template:
> rpart::rpart(formula = missing_arg(), data = missing_arg(), weights = missing_arg(), 
>     cp = tune::tune(), model = TRUE)

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

reg_tree_workflow <- workflows::workflow() %>%
  workflows::add_recipe(reg_tree_recipe) %>%
  workflows::add_model(reg_tree_spec)

reg_tree_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (regression)
> 
> Main Arguments:
>   cost_complexity = tune::tune()
> 
> Engine-Specific Arguments:
>   model = TRUE
> 
> Computational engine: rpart

Create the cost complexity grid

A cost complexity grid of \(10\) numbers from \(0.001\) (\(10^{-4}\)) to \(10\) (\(10^1\)) is created.

Regular grid is created using dials::grid_regular, dials::cost_complexity and scales::log10_trans

# Create a range from 10
cost_complexity_grid <-
  dials::grid_regular(
    x = dials::cost_complexity(
      range = c(-4, 1),
      trans = scales::log10_trans()
    ),
    levels = 10
  )

cost_complexity_grid %>%
  reactable::reactable(defaultPageSize = 5)

Regression tree model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

We use yardstick::metric_set, to choose a set of metrics to used to evaluate the model. In this example, yardstick::rmse and yardstick::rsq are used.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = reg_tree_workflow,
  resamples = Boston_fold,
  grid = cost_complexity_grid,
  # metrics = yardstick::metric_set(yardstick::rmse,
  #                                yardstick::rsq)
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 × 4
>    splits           id     .metrics .notes          
>    <list>           <chr>  <list>   <list>          
>  1 <split [341/38]> Fold01 <tibble> <tibble [1 × 3]>
>  2 <split [341/38]> Fold02 <tibble> <tibble [1 × 3]>
>  3 <split [341/38]> Fold03 <tibble> <tibble [1 × 3]>
>  4 <split [341/38]> Fold04 <tibble> <tibble [1 × 3]>
>  5 <split [341/38]> Fold05 <tibble> <tibble [1 × 3]>
>  6 <split [341/38]> Fold06 <tibble> <tibble [1 × 3]>
>  7 <split [341/38]> Fold07 <tibble> <tibble [1 × 3]>
>  8 <split [341/38]> Fold08 <tibble> <tibble [1 × 3]>
>  9 <split [341/38]> Fold09 <tibble> <tibble [1 × 3]>
> 10 <split [342/37]> Fold10 <tibble> <tibble [1 × 3]>
> 
> There were issues with some computations:
> 
>   - Warning(s) x10: A correlation computation is required, bu...
> 
> Use `collect_notes(object)` for more information.

Here we see that the amount of cost complexity affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["cost_complexity"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::scale_x_log10() +
  ggplot2::theme(legend.position = "none")
> Warning: Removed 3 row(s) containing missing values
> (geom_path).

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best, this function requires you to specify a metric that it should select against. The cost complexity value is 0.00129 for metric rmse since it gives the lowest value. Do note that using a different seed will give a different best cost complexity value.

top_cost_complexity <- tune::show_best(tune_res, metric = c("rmse"), n = 5)
top_cost_complexity %>%
  reactable::reactable(defaultPageSize = 5)
best_cost_complexity <- tune::select_best(tune_res, metric = "rmse")
best_cost_complexity
> # A tibble: 1 × 2
>   cost_complexity .config              
>             <dbl> <chr>                
> 1         0.00129 Preprocessor1_Model03

Regression tree model with optimised cost complexity value

We create the regression tree workflow with the best cost complexity score using tune::finalize_workflow.

reg_tree_final <- tune::finalize_workflow(
  x = reg_tree_workflow,
  parameters = best_cost_complexity
)

reg_tree_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (regression)
> 
> Main Arguments:
>   cost_complexity = 0.00129154966501488
> 
> Engine-Specific Arguments:
>   model = TRUE
> 
> Computational engine: rpart

We now train the regression tree model with the training data using parsnip::fit

reg_tree_final_fit <- parsnip::fit(object = reg_tree_final, data = Boston_train)

We can see the tree in greater detail using tune::extract_fit_engine

reg_tree_final_fit %>%
  tune::extract_fit_engine()
> n= 379 
> 
> node), split, n, deviance, yval
>       * denotes terminal node
> 
>   1) root 379 32622.95000 22.548020  
>     2) rm< 6.941 320 13602.31000 19.862810  
>       4) lstat>=14.395 129  2582.10900 14.515500  
>         8) nox>=0.607 80   984.73390 12.358750  
>          16) lstat>=19.34 47   388.63320 10.359570  
>            32) tax>=551.5 40   243.94980  9.677500  
>              64) crim>=24.59775 7    26.27714  7.157143 *
>              65) crim< 24.59775 33   163.77520 10.212120 *
>            33) tax< 551.5 7    19.73714 14.257140 *
>          17) lstat< 19.34 33   140.71880 15.206060  
>            34) crim>=6.99237 13    34.98769 13.630770 *
>            35) crim< 6.99237 20    52.50200 16.230000 *
>         9) nox< 0.607 49   617.69390 18.036730  
>          18) crim>=0.381565 25   313.20000 16.200000  
>            36) ptratio>=20.6 8    16.23875 14.162500 *
>            37) ptratio< 20.6 17   248.12120 17.158820 *
>          19) crim< 0.381565 24   132.30000 19.950000  
>            38) age>=72.8 15    51.49333 18.766670 *
>            39) age< 72.8 9    24.79556 21.922220 *
>       5) lstat< 14.395 191  4840.36400 23.474350  
>        10) rm< 6.543 151  2861.39900 22.211920  
>          20) dis>=1.68515 144  1179.59700 21.820830  
>            40) rm< 6.062 56   306.22860 20.285710 *
>            41) rm>=6.062 88   657.41950 22.797730  
>              82) lstat>=9.98 35    98.32686 21.025710 *
>              83) lstat< 9.98 53   376.61550 23.967920  
>               166) crim< 0.048715 17   100.97530 22.629410 *
>               167) crim>=0.048715 36   230.80000 24.600000  
>                 334) tax>=280.5 27    43.50667 23.911110 *
>                 335) tax< 280.5 9   136.04000 26.666670 *
>          21) dis< 1.68515 7  1206.69700 30.257140 *
>        11) rm>=6.543 40   829.85600 28.240000  
>          22) lstat>=4.44 33   274.06180 27.154550  
>            44) dis>=3.9683 19   131.76530 26.015790 *
>            45) dis< 3.9683 14    84.22000 28.700000 *
>          23) lstat< 4.44 7   333.61710 33.357140 *
>     3) rm>=6.941 59  4199.10200 37.111860  
>       6) rm< 7.437 35  1012.41000 32.082860  
>        12) nox>=0.4885 14   673.46930 28.892860 *
>        13) nox< 0.4885 21   101.49810 34.209520 *
>       7) rm>=7.437 24  1010.62000 44.445830  
>        14) ptratio>=15.4 12   585.07670 40.716670 *
>        15) ptratio< 15.4 12    91.78250 48.175000 *

We can visualise the above better with rpart.plot::rpart.plot

reg_tree_final_fit %>%
  tune::extract_fit_engine() %>%
  rpart.plot::rpart.plot()

Variable Importance

What are the most important variables in this tree for predicting medv?

Using the vip package and workflows::extract_fit_parsnip, we have

vip_table <- reg_tree_final_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vi()

vip_table %>%
  reactable::reactable(defaultPageSize = 5)
reg_tree_final_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vip(
    geom = "col",
    aesthetics = list(fill = "midnightblue", alpha = 0.8)
  ) +
  ggplot2::scale_y_continuous(expand = c(0, 0))

We can use parttree to understand why these two parameters work so well on the training set.

partial_tree <- parsnip::decision_tree(
  cost_complexity = best_cost_complexity$cost_complexity
) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("rpart", model = TRUE) %>%
  parsnip::fit(medv ~ rm + lstat,
    data = Boston_train
  ) %>%
  tune::extract_fit_engine()

Boston_train %>%
  ggplot2::ggplot(
    mapping = ggplot2::aes(
      x = .data[["rm"]],
      y = .data[["lstat"]]
    )
  ) +
  parttree::geom_parttree(
    data = partial_tree,
    mapping = ggplot2::aes(fill = .data[["medv"]]),
    alpha = 0.2
  ) +
  ggplot2::geom_jitter(
    alpha = 0.7,
    width = 0.05,
    height = 0.2,
    mapping = ggplot2::aes(color = .data[["medv"]])
  ) +
  ggplot2::scale_colour_viridis_c(aesthetics = c("color", "fill"))

Regression tree model on test data

Finally, let’s turn to the testing data. For regression models, a .pred, column is added when parsnip::augment is used.

test_results <- parsnip::augment(
  x = reg_tree_final_fit,
  new_data = Boston_test
)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the medv using yardstick::rmse.

test_results %>%
  yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Alternatively, we can use tune::last_fit and tune::collect_metrics.

test_rs <- tune::last_fit(
  object = reg_tree_final_fit,
  split = Boston_split
)

test_rs %>%
  tune::collect_metrics() %>%
  reactable::reactable(defaultPageSize = 5)

Use tune::collect_predictions, to see only the actual and predicted values of the test data.

test_rs %>%
  tune::collect_predictions() %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_rs %>%
  tune::collect_predictions() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["medv"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Bagging

Here we apply bagging to the Boston data, using the randomForest package in R

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training and rsample::testing

Boston_split <- rsample::initial_split(Boston)

Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)

Create the preprocessor

We create a recipe with recipes::recipe. No other preprocessing step is done.

bagging_recipe <-
  recipes::recipe(formula = medv ~ ., data = Boston_train)

Specify the model

Use parsnip::rand_forest, parsnip::set_mode and parsnip::set_engine to create the model.

mtry is the number of predictors that will be randomly sampled at each split when creating the tree models. For bagging, that number is the number of columns in the predictor matrix denoted by parsnip::.cols

importance set to TRUE ensures the importance of predictors are assessed.

bagging_spec <- parsnip::rand_forest(mtry = .cols()) %>%
  parsnip::set_engine("randomForest", importance = TRUE) %>%
  parsnip::set_mode("regression")

bagging_spec %>%
  parsnip::translate()
> Random Forest Model Specification (regression)
> 
> Main Arguments:
>   mtry = .cols()
> 
> Engine-Specific Arguments:
>   importance = TRUE
> 
> Computational engine: randomForest 
> 
> Model fit template:
> randomForest::randomForest(x = missing_arg(), y = missing_arg(), 
>     mtry = min_cols(~.cols(), x), importance = TRUE)

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

bagging_workflow <- workflows::workflow() %>%
  workflows::add_recipe(bagging_recipe) %>%
  workflows::add_model(bagging_spec)

bagging_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: rand_forest()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Random Forest Model Specification (regression)
> 
> Main Arguments:
>   mtry = .cols()
> 
> Engine-Specific Arguments:
>   importance = TRUE
> 
> Computational engine: randomForest

Bagging model on training data

bagging_fit <- parsnip::fit(
  object = bagging_workflow,
  data = Boston_train
)

Variable Importance

What are the most important variables in this tree for predicting medv?

Using the vip package and workflows::extract_fit_parsnip, we have

vip_table <- bagging_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vi()

vip_table %>%
  reactable::reactable(defaultPageSize = 5)
bagging_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vip(
    geom = "col",
    aesthetics = list(fill = "midnightblue", alpha = 0.8)
  ) +
  ggplot2::scale_y_continuous(expand = c(0, 0))

Bagging model on test data

Finally, let’s turn to the testing data. For regression models, a .pred, column is added when parsnip::augment is used.

test_results <- parsnip::augment(
  x = bagging_fit,
  new_data = Boston_test
)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the medv using yardstick::rmse.

test_results %>%
  yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_results %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["medv"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Random Forest

Here we apply random forest to the Boston data, using the randomForest package in R

Create the rsample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training and rsample::testing

Boston_split <- rsample::initial_split(Boston)

Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)

Create the preprocessor

We create a recipe with recipes::recipe. No other preprocessing step is done.

rf_recipe <-
  recipes::recipe(formula = medv ~ ., data = Boston_train)

Specify the model

Use parsnip::rand_forest, parsnip::set_mode and parsnip::set_engine to create the model.

mtry is the number of predictors that will be randomly sampled at each split when creating the tree models. For random forest, the randomForest::randomForest use \(\frac{p}{3}\) variables when building a random forest of regression trees and \(\sqrt{p}\) variables when building a random forest of classification trees.

importance set to TRUE ensures the importance of predictors are assessed.

rf_spec <- parsnip::rand_forest() %>%
  parsnip::set_engine("randomForest", importance = TRUE) %>%
  parsnip::set_mode("regression")

rf_spec %>%
  parsnip::translate()
> Random Forest Model Specification (regression)
> 
> Engine-Specific Arguments:
>   importance = TRUE
> 
> Computational engine: randomForest 
> 
> Model fit template:
> randomForest::randomForest(x = missing_arg(), y = missing_arg(), 
>     importance = TRUE)

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

rf_workflow <- workflows::workflow() %>%
  workflows::add_recipe(rf_recipe) %>%
  workflows::add_model(rf_spec)

rf_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: rand_forest()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Random Forest Model Specification (regression)
> 
> Engine-Specific Arguments:
>   importance = TRUE
> 
> Computational engine: randomForest

Random forest on training data

rf_fit <- parsnip::fit(
  object = rf_workflow,
  data = Boston_train
)

Variable Importance

What are the most important variables in this tree for predicting medv?

Using the vip package and workflows::extract_fit_parsnip, we have

vip_table <- rf_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vi()

vip_table %>%
  reactable::reactable(defaultPageSize = 5)
rf_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vip(
    geom = "col",
    aesthetics = list(fill = "midnightblue", alpha = 0.8)
  ) +
  ggplot2::scale_y_continuous(expand = c(0, 0))

The randomForest package also has functions like randomForest::importance to view the importance of each variable as well. The key is to use workflows::extract_fit_engine

The first is based upon the mean decrease of accuracy in predictions on the out of bag samples when a given variable is permuted.

The second is a measure of the total decrease in node impurity that results from splits over that variable, averaged over all trees.

importance_table <- rf_fit %>%
  workflows::extract_fit_engine() %>%
  randomForest::importance()

importance_table %>%
  reactable::reactable(defaultPageSize = 5)

Plots of these importance measures can be produced using the randomForest::varImpPlot function.

rf_fit %>%
  workflows::extract_fit_engine() %>%
  randomForest::varImpPlot()

Random forest on test data

Finally, let’s turn to the testing data. For regression models, a .pred, column is added when parsnip::augment is used.

test_results <- parsnip::augment(
  x = rf_fit,
  new_data = Boston_test
)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the medv using yardstick::rmse.

test_results %>%
  yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_results %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["medv"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Boosting

Here we apply bagging to the Boston data. The book uses the R package gbm to do the boosting. Unfortunately, gbm is not one of the list boosted tree models in the current parsnip list.

The process of building it from scratch can be found in this github issue post

For simplicity, a different R package is used to create a bagging tree model. In this example, the default parsnip engine for boosted tree is the xgboost R package.

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

Boston_split <- rsample::initial_split(Boston)

Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)

Boston_fold <- rsample::vfold_cv(Boston_train, v = 10)

Create the preprocessor

We create a recipe with recipes::recipe. No other preprocessing step is done.

boost_recipe <-
  recipes::recipe(formula = medv ~ ., data = Boston_train)

Specify the model

Use parsnip::boost_tree, parsnip::set_mode and parsnip::set_engine to create the model.

Recall in the book that we can tune the number of trees trees, the shrinkage parameter learn_rate and the number of splits/depth tree_depth.

These can be tuned using tune::tune

boost_spec <- parsnip::boost_tree(
  trees = tune::tune(),
  tree_depth = tune::tune(),
  learn_rate = tune::tune()
) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("xgboost")

boost_spec %>%
  parsnip::translate()
> Boosted Tree Model Specification (regression)
> 
> Main Arguments:
>   trees = tune::tune()
>   tree_depth = tune::tune()
>   learn_rate = tune::tune()
> 
> Computational engine: xgboost 
> 
> Model fit template:
> parsnip::xgb_train(x = missing_arg(), y = missing_arg(), nrounds = tune::tune(), 
>     max_depth = tune::tune(), eta = tune::tune(), nthread = 1, 
>     verbose = 0)

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

boost_workflow <- workflows::workflow() %>%
  workflows::add_recipe(boost_recipe) %>%
  workflows::add_model(boost_spec)

boost_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: boost_tree()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Boosted Tree Model Specification (regression)
> 
> Main Arguments:
>   trees = tune::tune()
>   tree_depth = tune::tune()
>   learn_rate = tune::tune()
> 
> Computational engine: xgboost

Create the boosting tree grid

Let’s use a space-filling design (non-regular grid) so that we can cover the hyperparameter space as well as possible. We do this using dials::grid_latin_hypercube

We use the default values for dials::trees, dials::tree_depth and dials::learn_rate

boost_grid <-
  dials::grid_latin_hypercube(
    dials::trees(range = c(1L, 2000L)),
    dials::tree_depth(range = c(1L, 15L)),
    dials::learn_rate(
      range = c(-10, -1),
      trans = scales::log10_trans()
    ),
    size = 10
  )

boost_grid %>%
  reactable::reactable(defaultPageSize = 5)

You may refer to the Tidyverse blog or Tengku Hanis blog for more details about how the different grids work.

Boosting tree model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

We use yardstick::metric_set, to choose a set of metrics to used to evaluate the model. In this example, yardstick::rmse and yardstick::rsq are used.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = boost_workflow,
  resamples = Boston_fold,
  grid = boost_grid,
  metrics = yardstick::metric_set(
    yardstick::rmse,
    yardstick::rsq
  )
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 × 4
>    splits           id     .metrics .notes          
>    <list>           <chr>  <list>   <list>          
>  1 <split [341/38]> Fold01 <tibble> <tibble [1 × 3]>
>  2 <split [341/38]> Fold02 <tibble> <tibble [1 × 3]>
>  3 <split [341/38]> Fold03 <tibble> <tibble [1 × 3]>
>  4 <split [341/38]> Fold04 <tibble> <tibble [1 × 3]>
>  5 <split [341/38]> Fold05 <tibble> <tibble [1 × 3]>
>  6 <split [341/38]> Fold06 <tibble> <tibble [1 × 3]>
>  7 <split [341/38]> Fold07 <tibble> <tibble [1 × 3]>
>  8 <split [341/38]> Fold08 <tibble> <tibble [1 × 3]>
>  9 <split [341/38]> Fold09 <tibble> <tibble [1 × 3]>
> 10 <split [342/37]> Fold10 <tibble> <tibble [1 × 3]>
> 
> There were issues with some computations:
> 
>   - Warning(s) x10: A correlation computation is required, bu...
> 
> Use `collect_notes(object)` for more information.

Here we see that the different amount of trees, learning/shrinkage/rate and tree depth affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

for_plotting <- tune_res %>%
  tune::collect_metrics() %>%
  dplyr::select(dplyr::all_of(c(
    "mean", ".metric",
    "trees", "tree_depth", "learn_rate"
  ))) %>%
  tidyr::pivot_longer(
    cols = dplyr::all_of(c("trees", "tree_depth", "learn_rate")),
    values_to = "value",
    names_to = "parameter"
  )

for_plotting %>%
  reactable::reactable(defaultPageSize = 5)
for_plotting %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["value"]],
    y = .data[["mean"]],
    colour = .data[["parameter"]]
  )) +
  ggplot2::geom_point(alpha = 0.8, show.legend = FALSE) +
  ggplot2::facet_grid(
    cols = ggplot2::vars(.data[["parameter"]]),
    rows = ggplot2::vars(.data[[".metric"]]),
    scales = "free"
  ) +
  ggplot2::labs(x = NULL, y = NULL)
> Warning: Removed 3 rows containing missing values
> (geom_point).

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best.

Do note that using a different seed will give a different optimised value.

top_boost_grid <- tune::show_best(tune_res, metric = c("rmse"), n = 5)
top_boost_grid %>%
  reactable::reactable(defaultPageSize = 5)
best_boost_grid <- tune::select_best(tune_res, metric = "rmse")
best_boost_grid
> # A tibble: 1 × 4
>   trees tree_depth learn_rate .config              
>   <int>      <int>      <dbl> <chr>                
> 1   492          5    0.00997 Preprocessor1_Model03

Boosting tree model with optimised grid

We create the boosting tree workflow with the best grid tune::finalize_workflow.

boost_final <- tune::finalize_workflow(
  x = boost_workflow,
  parameters = best_boost_grid
)

boost_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: boost_tree()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> Boosted Tree Model Specification (regression)
> 
> Main Arguments:
>   trees = 492
>   tree_depth = 5
>   learn_rate = 0.00996976561192888
> 
> Computational engine: xgboost

We now train the boosting tree model with the training data using parsnip::fit

boost_final_fit <- parsnip::fit(object = boost_final, data = Boston_train)

We can see the tree in greater detail using tune::extract_fit_engine

xgb_model <- boost_final_fit %>%
  tune::extract_fit_engine()

xgb_model
> ##### xgb.Booster
> raw: 893.9 Kb 
> call:
>   xgboost::xgb.train(params = list(eta = 0.00996976561192888, max_depth = 5L, 
>     gamma = 0, colsample_bytree = 1, colsample_bynode = 1, min_child_weight = 1, 
>     subsample = 1, objective = "reg:squarederror"), data = x$data, 
>     nrounds = 492L, watchlist = x$watchlist, verbose = 0, nthread = 1)
> params (as set within xgb.train):
>   eta = "0.00996976561192888", max_depth = "5", gamma = "0", colsample_bytree = "1", colsample_bynode = "1", min_child_weight = "1", subsample = "1", objective = "reg:squarederror", nthread = "1", validate_parameters = "TRUE"
> xgb.attributes:
>   niter
> callbacks:
>   cb.evaluation.log()
> # of features: 12 
> niter: 492
> nfeatures : 12 
> evaluation_log:
>     iter training_rmse
>        1     24.024099
>        2     23.796185
> ---                   
>      491      1.172467
>      492      1.170336

We can visualise one of the trees with xgboost::xgb.plot.tree

# See the first tree
gr <- xgboost::xgb.plot.tree(
  model = xgb_model, trees = 0,
  render = FALSE
)

DiagrammeR::export_graph(
  graph = gr,
  file_name = "docs/xgboost_single_tree.png",
  file_type = "PNG"
)
knitr::include_graphics("docs/xgboost_single_tree.png")

We can visualise a summary of all the trees with xgboost::xgb.plot.multi.trees

gr <- xgboost::xgb.plot.multi.trees(
  model = xgb_model,
  features_keep = 3,
  render = FALSE
)

DiagrammeR::export_graph(
  graph = gr,
  file_name = "docs/xgboost_multiple_tree.png",
  file_type = "PNG"
)
knitr::include_graphics("docs/xgboost_multiple_tree.png")

Variable Importance

What are the most important variables in this tree for predicting medv?

We can extract the important features from the boosted tree model with xgboost::xgb.importance

Details on what Gain, Cover and Frequency can be found in this blog post

importance_matrix <- xgboost::xgb.importance(model = xgb_model)

importance_matrix %>%
  reactable::reactable(defaultPageSize = 5)

The xgboost::xgb.ggplot.importance uses the Gain variable importance measurement by default to calculate variable importance.

xgboost::xgb.ggplot.importance(importance_matrix,
  rel_to_first = FALSE,
  xlab = "Relative importance"
)

Boosting tree model on test data

Finally, let’s turn to the testing data. For regression models, a .pred, column is added when parsnip::augment is used.

test_results <- parsnip::augment(
  x = boost_final_fit,
  new_data = Boston_test
)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the medv using yardstick::rmse.

test_results %>%
  yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_results %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["medv"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Baysesian Additive Regression Tree (BART)

Here we apply bagging to the Boston data. The book uses the R package BART to do the boosting. Unfortunately, BART is not one of the list bart models in the current parsnip list.

For simplicity, a different R package is used to create a BART model. In this example, the default parsnip engine for bosted tree is the dbarts R package.

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

Boston_split <- rsample::initial_split(Boston)

Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)

Boston_fold <- rsample::vfold_cv(Boston_train, v = 10)

Create the preprocessor

We create a recipe with recipes::recipe. No other preprocessing step is done.

bart_recipe <-
  recipes::recipe(formula = medv ~ ., data = Boston_train)

Specify the model

Use parsnip::bart, parsnip::set_mode and parsnip::set_engine to create the model.

ndpost is the number of MCMC interations. nskip is the number of burn-in iterations.

bart_spec <- parsnip::bart(
  trees = tune::tune()
) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("dbarts",
    nskip = 100,
    ndpost = 500
  )

bart_spec %>%
  parsnip::translate()
> 
> Call:
> NULL

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

bart_workflow <- workflows::workflow() %>%
  workflows::add_recipe(bart_recipe) %>%
  workflows::add_model(bart_spec)

bart_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: bart()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> 
> Call:
> NULL

Create the BART grid

Let’s use a space-filling design (non-regular grid) so that we can cover the hyperparameter space as well as possible. We do this using dials::grid_regular

We use the default values for dials::trees, dials::tree_depth and dials::learn_rate

bart_grid <-
  dials::grid_regular(
    x = dials::trees(range = c(1L, 10L)),
    levels = 10
  )

bart_grid %>%
  reactable::reactable(defaultPageSize = 5)

BART model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

We use yardstick::metric_set, to choose a set of metrics to used to evaluate the model. In this example, yardstick::rmse and yardstick::rsq are used.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = bart_workflow,
  resamples = Boston_fold,
  grid = bart_grid,
  metrics = yardstick::metric_set(
    yardstick::rmse,
    yardstick::rsq
  )
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 × 4
>    splits           id     .metrics .notes          
>    <list>           <chr>  <list>   <list>          
>  1 <split [341/38]> Fold01 <tibble> <tibble [0 × 3]>
>  2 <split [341/38]> Fold02 <tibble> <tibble [0 × 3]>
>  3 <split [341/38]> Fold03 <tibble> <tibble [0 × 3]>
>  4 <split [341/38]> Fold04 <tibble> <tibble [0 × 3]>
>  5 <split [341/38]> Fold05 <tibble> <tibble [0 × 3]>
>  6 <split [341/38]> Fold06 <tibble> <tibble [0 × 3]>
>  7 <split [341/38]> Fold07 <tibble> <tibble [0 × 3]>
>  8 <split [341/38]> Fold08 <tibble> <tibble [0 × 3]>
>  9 <split [341/38]> Fold09 <tibble> <tibble [0 × 3]>
> 10 <split [342/37]> Fold10 <tibble> <tibble [0 × 3]>

Here we see that the different amount of trees affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["trees"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::scale_x_log10() +
  ggplot2::theme(legend.position = "none")

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best.

Do note that using a different seed will give a different optimised value.

top_number_of_trees <- tune::show_best(tune_res, metric = c("rmse"), n = 5)
top_number_of_trees %>%
  reactable::reactable(defaultPageSize = 5)
best_number_of_trees <- tune::select_best(tune_res, metric = "rmse")
best_number_of_trees
> # A tibble: 1 × 2
>   trees .config              
>   <int> <chr>                
> 1     5 Preprocessor1_Model05

BART model with optimised grid

We create the BART workflow with the best grid tune::finalize_workflow.

bart_final <- tune::finalize_workflow(
  x = bart_workflow,
  parameters = best_number_of_trees
)

bart_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: bart()
> 
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
> 
> ── Model ───────────────────────────────────────────
> 
> Call:
> NULL

We now train the boosting tree model with the training data using parsnip::fit

bart_final_fit <- parsnip::fit(object = bart_final, data = Boston_train)

We can see the tree in greater detail using tune::extract_fit_engine

For example, to see the 2nd tree on the third iteration

bart_model <- bart_final_fit %>%
  tune::extract_fit_engine()

bart_model$fit$plotTree(sampleNum = 3, treeNum = 2)

Variable Importance

What are the most important variables in this tree for predicting medv?

We can extract the important features from the bart tree model with embarcadero::varimp

embarcadero::varimp(bart_model, plots = TRUE) %>%
  reactable::reactable(defaultPageSize = 5)

BART model on test data

Finally, let’s turn to the testing data. For regression models, a .pred, column is added when parsnip::augment is used.

test_results <- parsnip::augment(
  x = bart_final_fit,
  new_data = Boston_test
)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the medv using yardstick::rmse.

test_results %>%
  yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_results %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["medv"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Rmarkdown Template

This Rmarkdown template is created by the Reality Bending Lab. The template can be download from the lab’s github repository. For more information about the motivation behind creating this template, check out Dr. Dominique Makowski’s blog post

Blog References

  • Emil Hvitfeldt’s ISLR tidymodels Labs

  • Julia Silge’s blog titled “Predict which #TidyTuesday Scooby Doo monsters are REAL with a tuned decision tree model”

  • Julia Silge’s blog titled “Tune and interpret decision trees for #TidyTuesday wind turbines”

  • Julia Silge’s blog titled “Tune XGBoost with tidymodels and #TidyTuesday beach volleyball”

  • R-bloggers’ blog titled “Explaining Predictions: Boosted Trees Post-hoc Analysis (Xgboost)”

Package References

get_citation <- function(package_name) {
  transform_name <- package_name %>%
    citation() %>%
    format(style = "text")
  return(transform_name[1])
}

packages <- c(
  "base", "randomForest", "vip", "ggplot2",
  "rmarkdown", "report", "sp", "ROCR",
  "dbarts", "tidyverse", "knitr", "ConfusionTableR",
  "data.table", "DiagrammeR", "DiagrammeRsvg", "dials",
  "dismo", "doParallel", "dplyr", "embarcadero",
  "forcats", "foreach", "ggpubr", "htmltools",
  "ISLR2", "iterators", "magrittr", "matrixStats",
  "Metrics", "parsnip", "parttree", "patchwork",
  "purrr", "raster", "reactable", "readr",
  "recipes", "rpart", "rpart.plot", "rsample",
  "rsvg", "scales", "stringr", "styler",
  "tibble", "tidyr", "tune", "workflows",
  "xaringanExtra", "xgboost", "yardstick",
  "Ckmeans.1d.dp"
)

table <- tibble::tibble(Packages = packages)

table %>%
  dplyr::mutate(
    transform_name = purrr::map_chr(
      .data[["Packages"]],
      get_citation
    )
  ) %>%
  dplyr::pull(.data[["transform_name"]]) %>%
  report::as.report_parameters()
LS0tCnRpdGxlOiAnKipDaGFwdGVyIDggTGFiKionCmF1dGhvcjogIkplcmVteSBTZWx2YSIKc3VidGl0bGU6IFRoaXMgZG9jdW1lbnQgd2FzIHByZXBhcmVkIG9uIGByIGZvcm1hdChTeXMuRGF0ZSgpKWAuCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IGNlcnVsZWFuCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgc2VsZl9jb250YWluZWQ6IGZhbHNlCiAgICBsaWJfZGlyOiAiZG9jcy9ybWFya2Rvd25fbGlicyIKICBybWFya2Rvd246Omh0bWxfdmlnbmV0dGU6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAyCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCjwhLS0gCiEhISEgSU1QT1JUQU5UOiBydW4gYHNvdXJjZSgidXRpbHMvcmVuZGVyLlIiKWAgdG8gcHVibGlzaCBpbnN0ZWFkIG9mIGNsaWNraW5nIG9uICdLbml0JwojIFNlZSBodHRwczovL3lpaHVpLm9yZy9rbml0ci9vcHRpb25zLwotLT4KCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPVRSVUUsIGluY2x1ZGU9RkFMU0V9CiMgU2V0IHVwIHRoZSBlbnZpcm9ubWVudAoKIyBPcHRpb25zIHJlbGF0aXZlIHRvIGZpZ3VyZSBzaXplCiMgMS42MTggaXMgdGhlIGdvbGRlbiByYXRpbwpmaWdoZWlnaHQgPC0gNApmaWd3aWR0aCA8LSA0ICogMS42MTggCgojIEdlbmVyYWwgb3B0aW9ucwpvcHRpb25zKGtuaXRyLmthYmxlLk5BID0gIiIsCiAgICAgICAgbnNtYWxsID0gMywKICAgICAgICB0aWR5dmVyc2UucXVpZXQgPSBUUlVFCiAgICAgICAgKQpob29rX291dHB1dCA8LSBrbml0cjo6a25pdF9ob29rcyRnZXQoJ291dHB1dCcpCgprbml0cjo6a25pdF9ob29rcyRzZXQoCiAgb3V0cHV0ID0gZnVuY3Rpb24oeCwgb3B0aW9ucykgewogICAgaWYgKCFpcy5udWxsKG9wdGlvbnMkbWF4LmhlaWdodCkpIHsKICAgICAgb3B0aW9ucyRhdHRyLm91dHB1dCA8LSBjKG9wdGlvbnMkYXR0ci5vdXRwdXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCdzdHlsZT0ibWF4LWhlaWdodDogJXM7IicsIG9wdGlvbnMkbWF4LmhlaWdodCkpCiAgICB9CiAgICBob29rX291dHB1dCh4LCBvcHRpb25zKQogICAgfQogICkKCiMgQ2h1bmsgb3B0aW9ucyAoc2VlIGh0dHBzOi8veWlodWkub3JnL2tuaXRyL29wdGlvbnMvI2NodW5rX29wdGlvbnMpCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb21tZW50ID0gIj4iLCAgIyBUaGUgcHJlZml4IHRvIGJlIGFkZGVkIGJlZm9yZSBlYWNoIGxpbmUgb2YgdGhlIHRleHQgb3V0cHV0LgogIGRwaSA9IDYwMCwKICBmaWcucGF0aCA9ICJkb2NzL2ZpZ3VyZXMvIiwKICBmaWcuaGVpZ2h0ID0gZmlnaGVpZ2h0LAogIGZpZy53aWR0aCA9IGZpZ3dpZHRoLAogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLAogICMgU2VlIGh0dHBzOi8vY29tbXVuaXR5LnJzdHVkaW8uY29tL3QvY2VudGVyaW5nLWltYWdlcy1pbi1ibG9nZG93bi1wb3N0LzIwOTYyCiAgIyB0byBsZWFybiBob3cgdG8gY2VudGVyIGltYWdlcwogICMgU2VlIGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi1jb29rYm9vay9vcHRzLXRpZHkuaHRtbAogICMgU2VlIGh0dHBzOi8vd3d3LnpvdGVyby5vcmcvc3R5bGVzIGZvciBjaXRhdGlvbiBzdHlsZSByZXNwb3NpdG9yeQogIHRpZHk9J3N0eWxlcicsCiAgdGlkeS5vcHRzPWxpc3Qoc3RyaWN0PVRSVUUpCikKCmh0bWx0b29sczo6dGFnTGlzdCgKICB4YXJpbmdhbkV4dHJhOjp1c2VfY2xpcGJvYXJkKAogICAgYnV0dG9uX3RleHQgPSAiPGkgY2xhc3M9XCJmYSBmYS1jbGlwYm9hcmRcIj48L2k+IENvcHkgQ29kZSIsCiAgICBzdWNjZXNzX3RleHQgPSAiPGkgY2xhc3M9XCJmYSBmYS1jaGVja1wiIHN0eWxlPVwiY29sb3I6ICM5MEJFNkRcIj48L2k+IENvcGllZCEiLAogICksCiAgcm1hcmtkb3duOjpodG1sX2RlcGVuZGVuY3lfZm9udF9hd2Vzb21lKCkKKQpgYGAKCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2FzaXMnLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30KIyBSbWFya2Rvd24gc3R1ZmYKbGlicmFyeShybWFya2Rvd24sIHF1aWV0bHk9VFJVRSkKbGlicmFyeShrbml0ciwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KGh0bWx0b29scywgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHN0eWxlciwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHhhcmluZ2FuRXh0cmEsIHF1aWV0bHk9VFJVRSkKCiMgRGF0YXNldApsaWJyYXJ5KElTTFIyLCBxdWlldGx5PVRSVUUpCgojIFNlc3Npb24gaW5mbyBhbmQgcGFja2FnZSByZXBvcnRpbmcKbGlicmFyeShyZXBvcnQsIHF1aWV0bHk9VFJVRSkKCiMgRGF0YSB3cmFuZ2xpbmcKCmxpYnJhcnkoZHBseXIsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShtYWdyaXR0ciwgcXVpZXRseT1UUlVFKQoKIyBDcmVhdGUgaW50ZXJhY3RpdmUgdGFibGVzCmxpYnJhcnkocmVhY3RhYmxlLCBxdWlldGx5PVRSVUUpCgojIEZvciBwbG90dGluZwoKbGlicmFyeShnZ3Bsb3QyLCBxdWlldGx5PVRSVUUpCiMgRnJvbSBnaXRodWIKbGlicmFyeShwYXJ0dHJlZSwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KENvbmZ1c2lvblRhYmxlUiwgcXVpZXRseSA9IFRSVUUpCgojIEZvciBEZWNpc2lvbiBDbGFzc2lmaWNhdGlvbiBUcmVlcwpsaWJyYXJ5KHJwYXJ0LCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocnBhcnQucGxvdCwgcXVpZXRseT1UUlVFKQoKIyBGb3IgQmFnZ2luZyBhbmQgUmFuZG9tIEZvcmVzdApsaWJyYXJ5KHJhbmRvbUZvcmVzdCwgcXVpZXRseT1UUlVFKQoKIyBGb3IgQm9vc3RpbmcKbGlicmFyeShDa21lYW5zLjFkLmRwLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkoeGdib29zdCwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KERpYWdyYW1tZVIsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShEaWFncmFtbWVSc3ZnLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocnN2ZywgcXVpZXRseT1UUlVFKQoKIyBGb3IgQkFSVApsaWJyYXJ5KGRiYXJ0cywgcXVpZXRseT1UUlVFKQojIEZyb20gZ2l0aHViCmxpYnJhcnkoZW1iYXJjYWRlcm8sIHF1aWV0bHk9VFJVRSkKCiMgRm9yIHZhcmlhYmxlIGltcG9ydGFuY2UKbGlicmFyeSh2aXAsIHF1aWV0bHk9VFJVRSkKCiMgRm9yIHBhcmFsbGVsIHByb2Nlc3NpbmcgZm9yIHR1bmluZwpsaWJyYXJ5KGZvcmVhY2gsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShkb1BhcmFsbGVsLCBxdWlldGx5PVRSVUUpCgojIEZvciBhcHBseWluZyB0aWR5bW9kZWxzCgpsaWJyYXJ5KHJzYW1wbGUsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShyZWNpcGVzLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocGFyc25pcCwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KGRpYWxzLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkodHVuZSwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHdvcmtmbG93cywgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHlhcmRzdGljaywgcXVpZXRseT1UUlVFKQoKc3VtbWFyeShyZXBvcnQ6OnJlcG9ydChzZXNzaW9uSW5mbygpKSkKYGBgCgojIEZpdHRpbmcgQ2xhc3NpZmljYXRpb24gVHJlZXMKCmBgYHtyLCBlY2hvPUZBTFNFfQpzZXQuc2VlZCgxMjM0KQpgYGAKCkluIHRoZSBbYENhcnNlYXRzYF0oaHR0cHM6Ly9yZHJyLmlvL2NyYW4vSVNMUi9tYW4vQ2Fyc2VhdHMuaHRtbCkgZGF0YSBzZXQgZnJvbSB0aGUgYElTTFIyYCBwYWNrYWdlLCBgU2FsZXNgIG9mIGNoaWxkIGNhciBzZWF0cyBpcyBhIGNvbnRpbnVvdXMgdmFyaWFibGUsIGFuZCBzbyB3ZSBiZWdpbiBieSByZWNvcmRpbmcgaXQgYXMgYSBiaW5hcnkgdmFyaWFibGUgY2FsbGVkIGBIaWdoYCwgd2hpY2ggdGFrZXMgb24gYSB2YWx1ZSBvZiBZZXMgaWYgdGhlIFNhbGVzIHZhcmlhYmxlIGV4Y2VlZHMgOCwgYW5kIHRha2VzIG9uIGEgdmFsdWUgb2YgTm8gb3RoZXJ3aXNlLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpDYXJzZWF0cyA8LSBkcGx5cjo6YXNfdGliYmxlKElTTFIyOjpDYXJzZWF0cykgJT4lCiAgZHBseXI6Om11dGF0ZSgKICAgIEhpZ2ggPSBmYWN0b3IoZHBseXI6OmlmX2Vsc2UoLmRhdGFbWyJTYWxlcyJdXSA8PSA4LCAiTm8iLCAiWWVzIikpCiAgICApICU+JQogIGRwbHlyOjpzZWxlY3QoLWRwbHlyOjphbGxfb2YoYygiU2FsZXMiKSkpCgpDYXJzZWF0cyAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1LAogICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcmFibGUgPSBUUlVFKQpgYGAKCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0CgpGaXJzdCwgd2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4gRnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBjcmVhdGUgYSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LgoKVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRyYWluaW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkKYW5kCltgcnNhbXBsZTo6dmZvbGRfY3ZgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpDYXJzZWF0c19zcGxpdCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KENhcnNlYXRzKQoKQ2Fyc2VhdHNfdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQ2Fyc2VhdHNfc3BsaXQpCkNhcnNlYXRzX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhDYXJzZWF0c19zcGxpdCkKCkNhcnNlYXRzX2ZvbGQgPC0gcnNhbXBsZTo6dmZvbGRfY3YoQ2Fyc2VhdHNfdHJhaW4sIHYgPSAxMCkKCgpgYGAKCiMjIENyZWF0ZSB0aGUgcHJlcHJvY2Vzc29yCgpXZSBjcmVhdGUgYSByZWNpcGUgd2l0aCBbYHJlY2lwZXM6OnJlY2lwZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcmVjaXBlLmh0bWwpLgpObyBvdGhlciBwcmVwcm9jZXNzaW5nIHN0ZXAgaXMgZG9uZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKY2xhc3NfdHJlZV9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBIaWdoIH4gLiwgZGF0YSA9IENhcnNlYXRzX3RyYWluKQoKYGBgCgojIyBTcGVjaWZ5IHRoZSBtb2RlbAoKVXNlCltgcGFyc25pcDo6ZGVjaXNpb25fdHJlZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZGVjaXNpb25fdHJlZS5odG1sKSwKW2BwYXJzbmlwOjpzZXRfbW9kZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2FyZ3MuaHRtbCkKYW5kCltgcGFyc25pcDo6c2V0X2VuZ2luZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2VuZ2luZS5odG1sKQp0byBjcmVhdGUgdGhlIG1vZGVsLgoKVGhlIGNvc3QgY29tcGxleGl0eSBpcyB0dW5lZCB1c2luZwpbYHR1bmU6OnR1bmVgXShodHRwczovL2hhcmRoYXQudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmUuaHRtbCkKCkFuIGVuZ2luZSBzcGVjaWZpYyBwYXJhbWV0ZXIgYG1vZGVsID0gVFJVRWAgaXMgc2V0IGZvciBgcnBhcnRgIHRvIHByZXZlbnQgdGhpcyB3YXJuaW5nIG1lc3NhZ2UgZnJvbSBjb21pbmcgdXAgbGF0ZXIgd2hlbiBgcnBhcnQucGxvdDo6cnBhcnQucGxvdGAgaXMgdXNlZCBsYXRlci4KCmBgYHtyfQojPiBXYXJuaW5nOiBDYW5ub3QgcmV0cmlldmUgdGhlIGRhdGEgdXNlZCB0byBidWlsZCB0aGUgbW9kZWwgKHNvIGNhbm5vdCBkZXRlcm1pbmUgcm91bmRpbnQgYW5kIGlzLmJpbmFyeSBmb3IgdGhlIHZhcmlhYmxlcykuCiM+IFRvIHNpbGVuY2UgdGhpcyB3YXJuaW5nOgojPiAgICAgQ2FsbCBycGFydC5wbG90IHdpdGggcm91bmRpbnQ9RkFMU0UsCiM+ICAgICBvciByZWJ1aWxkIHRoZSBycGFydCBtb2RlbCB3aXRoIG1vZGVsPVRSVUUuCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpjbGFzc190cmVlX3NwZWMgPC0gcGFyc25pcDo6ZGVjaXNpb25fdHJlZSgKICB0cmVlX2RlcHRoID0gNCwKICBjb3N0X2NvbXBsZXhpdHkgPSB0dW5lOjp0dW5lKCkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpICU+JSAKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJycGFydCIsIG1vZGVsID0gVFJVRSkKCmNsYXNzX3RyZWVfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKY2xhc3NfdHJlZV93b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoY2xhc3NfdHJlZV9yZWNpcGUpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9tb2RlbChjbGFzc190cmVlX3NwZWMpCgpjbGFzc190cmVlX3dvcmtmbG93IApgYGAKCiMjIENyZWF0ZSB0aGUgY29zdCBjb21wbGV4aXR5IGdyaWQKCkEgY29zdCBjb21wbGV4aXR5IGdyaWQgb2YgJDEwJCBudW1iZXJzIGZyb20gJDAuMDAxJCAoJDEwXnstM30kKSB0bwokMTAkICgkMTBeMSQpIGlzIGNyZWF0ZWQuCgpSZWd1bGFyIGdyaWQgaXMgY3JlYXRlZCB1c2luZwpbYGRpYWxzOjpncmlkX3JlZ3VsYXJgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX3JlZ3VsYXIuaHRtbCksCltgZGlhbHM6OmNvc3RfY29tcGxleGl0eWBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3RyZWVzLmh0bWwpCmFuZApbYHNjYWxlczo6bG9nMTBfdHJhbnNgXShodHRwczovL3NjYWxlcy5yLWxpYi5vcmcvcmVmZXJlbmNlL2xvZ190cmFucy5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgojIENyZWF0ZSBhIHJhbmdlIGZyb20gMTAKY29zdF9jb21wbGV4aXR5X2dyaWQgPC0gCiAgZGlhbHM6OmdyaWRfcmVndWxhcigKICAgIHggPSBkaWFsczo6Y29zdF9jb21wbGV4aXR5KHJhbmdlID0gYygtMywgMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFucyA9IHNjYWxlczo6bG9nMTBfdHJhbnMoKSksCiAgICBsZXZlbHMgPSAxMAogICkKCmNvc3RfY29tcGxleGl0eV9ncmlkICAlPiUgCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgojIyBDbGFzc2lmaWNhdGlvbiB0cmVlIG1vZGVsIGZpdHRpbmcgb24gY3Jvc3MgdmFsaWRhdGVkIGRhdGEKCk5vdyB3ZSBoYXZlIGV2ZXJ5dGhpbmcgd2UgbmVlZCBhbmQgd2UgY2FuIGZpdCBhbGwgdGhlIG1vZGVscyBvbiB0aGUKY3Jvc3MgdmFsaWRhdGVkIGRhdGEgd2l0aApbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpLgpOb3RlIHRoYXQgdGhpcyBwcm9jZXNzIG1heSB0YWtlIHNvbWUgdGltZS4KCldlIHVzZSBbYHlhcmRzdGljazo6bWV0cmljX3NldGBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9tZXRyaWNfc2V0Lmh0bWwpLCB0byBjaG9vc2UgYSBzZXQgb2YgbWV0cmljcyB0byB1c2VkIHRvIGV2YWx1YXRlIHRoZSBtb2RlbC4gSW4gdGhpcyBleGFtcGxlLCBbYHlhcmRzdGljazo6YWNjdXJhY3lgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWNjdXJhY3kuaHRtbCksCltgeWFyZHN0aWNrOjpyb2NfYXVjYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JvY19jdXJ2ZS5odG1sKSwKW2B5YXJkc3RpY2s6OnNlbnNpdGl2aXR5YF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NlbnMuaHRtbCkgYW5kIFtgeWFyZHN0aWNrOjpzcGVjaWZpY2l0eWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zcGVjLmh0bWwpIGFyZSB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbCgpCmZvcmVhY2g6OmdldERvUGFyV29ya2VycygpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KdHVuZV9yZXMgPC0gdHVuZTo6dHVuZV9ncmlkKAogIG9iamVjdCA9IGNsYXNzX3RyZWVfd29ya2Zsb3csCiAgcmVzYW1wbGVzID0gQ2Fyc2VhdHNfZm9sZCwgCiAgZ3JpZCA9IGNvc3RfY29tcGxleGl0eV9ncmlkLAogIG1ldHJpY3MgPSB5YXJkc3RpY2s6Om1ldHJpY19zZXQoeWFyZHN0aWNrOjphY2N1cmFjeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlhcmRzdGljazo6cm9jX2F1YywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlhcmRzdGljazo6c2Vuc2l0aXZpdHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWFyZHN0aWNrOjpzcGVjaWZpY2l0eSkKKQoKdHVuZV9yZXMKYGBgCgpIZXJlIHdlIHNlZSB0aGF0IHRoZSBhbW91bnQgb2YgY29zdCBjb21wbGV4aXR5IGFmZmVjdHMgdGhlIHBlcmZvcm1hbmNlCm1ldHJpY3MgZGlmZmVyZW50bHkgdXNpbmcKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLgpEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYSBkaWZmZXJlbnQgcGxvdAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CiMgTm90ZSB0aGF0IGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGRpZmZlcmVudCBwbG90cwp0dW5lOjphdXRvcGxvdCh0dW5lX3JlcykKYGBgCgpXZSBjYW4gYWxzbyBzZWUgdGhlIHJhdyBtZXRyaWNzIHRoYXQgY3JlYXRlZCB0aGlzIGNoYXJ0IGJ5IGNhbGxpbmcKW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0dW5lOjpjb2xsZWN0X21ldHJpY3ModHVuZV9yZXMpICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCkhlcmUgaXMgdGhlIGBnZ3Bsb3RgIHdheSBzaG91bGQKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpCmZhaWxzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdHVuZV9yZXMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1siY29zdF9jb21wbGV4aXR5Il1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyIubWV0cmljIl1dKSkgKwogIGdncGxvdDI6Omdlb21fZXJyb3JiYXIobWFwcGluZyA9IGdncGxvdDI6OmFlcyh5bWluID0gLmRhdGFbWyJtZWFuIl1dIC0gLmRhdGFbWyJzdGRfZXJyIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gLmRhdGFbWyJtZWFuIl1dICsgLmRhdGFbWyJzdGRfZXJyIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArCiAgZ2dwbG90Mjo6Z2VvbV9saW5lKHNpemUgPSAxLjUpICsKICBnZ3Bsb3QyOjpmYWNldF93cmFwKGZhY2V0cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwgCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIAogICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IDIpICsKICBnZ3Bsb3QyOjpzY2FsZV94X2xvZzEwKCkgKwogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpVc2UKW2B0dW5lOjpzaG93X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKQp0byBzZWUgdGhlIHRvcCBmZXcgdmFsdWVzIGZvciBhIGdpdmVuIG1ldHJpYy4KClRoZSAiYmVzdCIgdmFsdWVzIGNhbiBiZSBzZWxlY3RlZCB1c2luZwpbYHR1bmU6OnNlbGVjdF9iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCksCnRoaXMgZnVuY3Rpb24gcmVxdWlyZXMgeW91IHRvIHNwZWNpZnkgYSBtZXRyaWMgdGhhdCBpdCBzaG91bGQgc2VsZWN0CmFnYWluc3QuIFRoZSBjb3N0IGNvbXBsZXhpdHkgdmFsdWUgaXMgMC4wMDEgZm9yIG1ldHJpYyBgYWNjdXJhY3lgIHNpbmNlIGl0CmdpdmVzIHRoZSBoaWdoZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQpkaWZmZXJlbnQgYmVzdCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdG9wX2Nvc3RfY29tcGxleGl0eSA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9IGMoImFjY3VyYWN5IiksIG4gPSA1KQp0b3BfY29zdF9jb21wbGV4aXR5ICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpiZXN0X2Nvc3RfY29tcGxleGl0eSA8LSB0dW5lOjpzZWxlY3RfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gImFjY3VyYWN5IikKYmVzdF9jb3N0X2NvbXBsZXhpdHkKCmBgYAoKIyMgQ2xhc3NpZmljYXRpb24gdHJlZSBtb2RlbCB3aXRoIG9wdGltaXNlZCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUKCldlIGNyZWF0ZSB0aGUgY2xhc3NpZmljYXRpb24gdHJlZSB3b3JrZmxvdyB3aXRoIHRoZSBiZXN0IGNvc3QgY29tcGxleGl0eSBzY29yZQp1c2luZwpbYHR1bmU6OmZpbmFsaXplX3dvcmtmbG93YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9maW5hbGl6ZV9tb2RlbC5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CgpjbGFzc190cmVlX2ZpbmFsIDwtIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KAogIHggPSBjbGFzc190cmVlX3dvcmtmbG93LCAKICBwYXJhbWV0ZXJzID0gYmVzdF9jb3N0X2NvbXBsZXhpdHkpCgpjbGFzc190cmVlX2ZpbmFsCmBgYAoKV2Ugbm93IHRyYWluIHRoZSBjbGFzc2lmaWNhdGlvbiB0cmVlIG1vZGVsIHdpdGggdGhlIHRyYWluaW5nIGRhdGEgdXNpbmcgW2BwYXJzbmlwOjpmaXRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2ZpdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpjbGFzc190cmVlX2ZpbmFsX2ZpdCA8LSBwYXJzbmlwOjpmaXQob2JqZWN0ID0gY2xhc3NfdHJlZV9maW5hbCwgZGF0YSA9IENhcnNlYXRzX3RyYWluKQpgYGAKCldlIGNhbiBzZWUgdGhlIHRyZWUgaW4gZ3JlYXRlciBkZXRhaWwgdXNpbmcKW2B0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmVgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2V4dHJhY3QtdHVuZS5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KY2xhc3NfdHJlZV9maW5hbF9maXQgJT4lCiAgdHVuZTo6ZXh0cmFjdF9maXRfZW5naW5lKCkKYGBgCgpXZSBjYW4gdmlzdWFsaXNlIHRoZSBhYm92ZSBiZXR0ZXIgd2l0aCBgcnBhcnQucGxvdDo6cnBhcnQucGxvdGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CmNsYXNzX3RyZWVfZmluYWxfZml0ICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpICU+JQogIHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3QoKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBTYWxlc2A/CgpVc2luZyB0aGUgYHZpcGAgcGFja2FnZSBhbmQKW2B3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXBgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC13b3JrZmxvdy5odG1sKSwgd2UgaGF2ZQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp2aXBfdGFibGUgPC0gY2xhc3NfdHJlZV9maW5hbF9maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aSgpIAoKdmlwX3RhYmxlICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmNsYXNzX3RyZWVfZmluYWxfZml0ICU+JQogIHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcCgpICU+JQogIHZpcDo6dmlwKGdlb20gPSAiY29sIiwgCiAgICAgICAgICAgYWVzdGhldGljcyA9IGxpc3QoZmlsbCA9ICJtaWRuaWdodGJsdWUiLCBhbHBoYSA9IDAuOCkpICsKICBnZ3Bsb3QyOjpzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkKYGBgCgpXZSBjYW4gdXNlIFtgcGFydHRyZWVgXShodHRwczovL2dpdGh1Yi5jb20vZ3JhbnRtY2Rlcm1vdHQvcGFydHRyZWUpIHRvIHVuZGVyc3RhbmQgd2h5IHRoZXNlIHR3byBwYXJhbWV0ZXJzIHdvcmsgc28gd2VsbCBvbiB0aGUgdHJhaW5pbmcgc2V0LgoKSG93ZXZlciB0aGlzIHBhY2thZ2Ugb25seSB3b3JrcyB3aXRoIHR3byBjb250aW51b3VzIHByZWRpY3RvcnMuIEFzIGBTaGVsdmVMb2NgIGlzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUsIHRoZXJlIGlzIGEgbmVlZCB0byAiY29udmVydCIgdGhlbSB0byBjb250aW51b3VzIGJ5IG1ha2luZyAiQmFkIiBhcyAtMSwgIk1lZGl1bSIgYXMgMCBhbmQgIkdvb2QiIGFzIDEuIAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpDYXJzZWF0c190cmFpbl9tb2RmaWZpZWQgPC0gQ2Fyc2VhdHNfdHJhaW4gJT4lIAogIGRwbHlyOjptdXRhdGUoCiAgICBTaGVsdmVMb2MgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICAuZGF0YVtbIlNoZWx2ZUxvYyJdXSA9PSAiQmFkIiB+ICItMSIsCiAgICAgIC5kYXRhW1siU2hlbHZlTG9jIl1dID09ICJNZWRpdW0iIH4gIjAiLAogICAgICAuZGF0YVtbIlNoZWx2ZUxvYyJdXSA9PSJHb29kIiB+ICIxIgogICAgKSkgJT4lIAogIGRwbHlyOjptdXRhdGUoCiAgICBTaGVsdmVMb2MgPSBhcy5udW1lcmljKC5kYXRhW1siU2hlbHZlTG9jIl1dKQogICkKCnBhcnRpYWxfdHJlZSA8LSBwYXJzbmlwOjpkZWNpc2lvbl90cmVlKAogIHRyZWVfZGVwdGggPSAzMCwKICBjb3N0X2NvbXBsZXhpdHkgPSBiZXN0X2Nvc3RfY29tcGxleGl0eSRjb3N0X2NvbXBsZXhpdHkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpICU+JSAKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJycGFydCIsIG1vZGVsID0gVFJVRSkgJT4lCiAgcGFyc25pcDo6Zml0KEhpZ2ggfiBQcmljZSArIFNoZWx2ZUxvYywgCiAgICAgICAgICAgICAgIGRhdGEgPSBDYXJzZWF0c190cmFpbl9tb2RmaWZpZWQpICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpCgpDYXJzZWF0c190cmFpbl9tb2RmaWZpZWQgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KAogICAgbWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJTaGVsdmVMb2MiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIlByaWNlIl1dKSkgKwogIHBhcnR0cmVlOjpnZW9tX3BhcnR0cmVlKAogICAgZGF0YSA9IHBhcnRpYWxfdHJlZSwgCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKGZpbGwgPSAuZGF0YVtbIkhpZ2giXV0pLCAKICAgIGFscGhhID0gMC4yKSArCiAgZ2dwbG90Mjo6Z2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjcsIAogICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMC4wNSwgCiAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0ID0gMC4yLCAKICAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKGNvbG9yID0gLmRhdGFbWyJIaWdoIl1dKSkKYGBgCgojIyBDbGFzc2lmaWNhdGlvbiB0cmVlIG1vZGVsIG9uIHRlc3QgZGF0YQoKRmluYWxseSwgbGV04oCZcyB0dXJuIHRvIHRoZSB0ZXN0aW5nIGRhdGEuCkZvciBjbGFzc2lmaWNhdGlvbiBtb2RlbHMsIGEKYC5wcmVkX2NsYXNzYCwgY29sdW1uIGFuZCBjbGFzcyBwcm9iYWJpbGl0eSBjb2x1bW5zIG5hbWVkIC5wcmVkX3tsZXZlbH0gYXJlIGFkZGVkIHdoZW4KW2BwYXJzbmlwOjphdWdtZW50YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdWdtZW50Lmh0bWwpCmlzIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnRlc3RfcmVzdWx0cyA8LSBwYXJzbmlwOjphdWdtZW50KHggPSBjbGFzc190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19kYXRhID0gQ2Fyc2VhdHNfdGVzdCkKICAKdGVzdF9yZXN1bHRzICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpgYGAKCldlIGNhbiB2aWV3IHRoZSBjb25mdXNpb24gbWF0cml4IHVzaW5nIFtgeWFyZHN0aWNrOjpjb25mX21hdGBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb25mX21hdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgJT4lCiAgeWFyZHN0aWNrOjpjb25mX21hdCh0cnV0aCA9IC5kYXRhW1siSGlnaCJdXSwgCiAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZSA9IC5kYXRhW1siLnByZWRfY2xhc3MiXV0pICU+JQogIGdncGxvdDI6OmF1dG9wbG90KHR5cGUgPSAiaGVhdG1hcCIpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgJT4lCiAgeWFyZHN0aWNrOjpjb25mX21hdCh0cnV0aCA9IC5kYXRhW1siSGlnaCJdXSwgZXN0aW1hdGUgPSAuZGF0YVtbIi5wcmVkX2NsYXNzIl1dKSAlPiUKICBzdW1tYXJ5KCkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpvciBieSBgQ29uZnVzaW9uVGFibGVSOjpiaW5hcnlfdmlzdWFsaXNlUmAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0ID0gOH0KQ29uZnVzaW9uVGFibGVSOjpiaW5hcnlfdmlzdWFsaXNlUih0cmFpbl9sYWJlbHMgPSB0ZXN0X3Jlc3VsdHNbWyJIaWdoIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydXRoX2xhYmVscyA9IHRlc3RfcmVzdWx0c1tbIi5wcmVkX2NsYXNzIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzX2xhYmVsMSA9ICJZZXMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc19sYWJlbDIgPSAiTm8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YWRyYW50X2NvbDEgPSAiIzI4QUNCNCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YWRyYW50X2NvbDIgPSAiIzQzOTdEMiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbV90aXRsZSA9ICJIaWdoIENvbmZ1c2lvbiBNYXRyaXgiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0X2NvbD0gImJsYWNrIikKCgpgYGAKCldlIGNhbiB2aWV3IHRoZSBST0MgY3VydmUgdXNpbmcgW2B5YXJkc3RpY2s6OnJvY19jdXJ2ZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yb2NfY3VydmUuaHRtbCkgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnJvY19wbG90X2RhdGEgPC0gdGVzdF9yZXN1bHRzICU+JQogIHlhcmRzdGljazo6cm9jX2N1cnZlKHRydXRoID0gdGVzdF9yZXN1bHRzW1siSGlnaCJdXSwKICAgICAgICAgICAgICAgICAgICAgICAuZGF0YVtbIi5wcmVkX05vIl1dKQoKcm9jX3Bsb3RfZGF0YSAlPiUgCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnJvY19wbG90X2RhdGEgJT4lIAogIGdncGxvdDI6OmF1dG9wbG90KCkKCmBgYAoKSGVyZSBpcyBhIGBnZ3Bsb3QyYCB2ZXJzaW9uLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgpyb2NfcGxvdF9kYXRhICU+JSAKICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gMSAtIC5kYXRhW1sic3BlY2lmaWNpdHkiXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbInNlbnNpdGl2aXR5Il1dKSkgKwogIGdncGxvdDI6Omdlb21fbGluZShzaXplID0gMS41LCBjb2xvciA9ICJtaWRuaWdodGJsdWUiKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoCiAgICBsdHkgPSAyLCBhbHBoYSA9IDAuNSwKICAgIGNvbG9yID0gImdyYXk1MCIsCiAgICBzaXplID0gMS4yCiAgKSArCiAgZ2dwbG90Mjo6Y29vcmRfZXF1YWwoKQoKYGBgCgoKVG8gdmlldyB0aGUgbWV0cmljcywgd2UgY2FuIGFsc28gdXNlCltgdHVuZTo6bGFzdF9maXRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2xhc3RfZml0Lmh0bWwpCmFuZApbYHR1bmU6OmNvbGxlY3RfbWV0cmljc2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4KCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCm1ldHJpY3NfcmVzdWx0cyA8LSB0dW5lOjpsYXN0X2ZpdChvYmplY3QgPSBjbGFzc190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9IENhcnNlYXRzX3NwbGl0KSAlPiUKICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKQogIAptZXRyaWNzX3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKICAKYGBgCgojIEZpdHRpbmcgUmVncmVzc2lvbiBUcmVlcwoKYGBge3IsIGVjaG89RkFMU0V9CnNldC5zZWVkKDEyMzQpCmBgYAoKSW4gdGhlIFtgQm9zdG9uYF0oaHR0cHM6Ly9yZHJyLmlvL2NyYW4vSVNMUjIvbWFuL0Jvc3Rvbi5odG1sKSBkYXRhIHNldCBmcm9tIHRoZSBgSVNMUjJgIHBhY2thZ2UsIHdlIHdhbnQgdG8gcHJlZGljdCBgbWVkdmAsIHRoZSBtZWRpYW4gdmFsdWUgb2Ygb3duZXItb2NjdXBpZWQgaG9tZXMgaW4gJDEwMDAncy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKQm9zdG9uIDwtIGRwbHlyOjphc190aWJibGUoSVNMUjI6OkJvc3RvbikKCkJvc3RvbiAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1LAogICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcmFibGUgPSBUUlVFKQpgYGAKCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0CgpGaXJzdCwgd2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4gRnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBjcmVhdGUgYSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LgoKVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRyYWluaW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkKYW5kCltgcnNhbXBsZTo6dmZvbGRfY3ZgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCkJvc3Rvbl9mb2xkIDwtIHJzYW1wbGU6OnZmb2xkX2N2KEJvc3Rvbl90cmFpbiwgdiA9IDEwKQoKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpyZWdfdHJlZV9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OmRlY2lzaW9uX3RyZWVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2RlY2lzaW9uX3RyZWUuaHRtbCksCltgcGFyc25pcDo6c2V0X21vZGVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9hcmdzLmh0bWwpCmFuZApbYHBhcnNuaXA6OnNldF9lbmdpbmVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9lbmdpbmUuaHRtbCkKdG8gY3JlYXRlIHRoZSBtb2RlbC4KClRoZSBjb3N0IGNvbXBsZXhpdHkgaXMgdHVuZWQgdXNpbmcKW2B0dW5lOjp0dW5lYF0oaHR0cHM6Ly9oYXJkaGF0LnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lLmh0bWwpCgpBbiBlbmdpbmUgc3BlY2lmaWMgcGFyYW1ldGVyIGBtb2RlbCA9IFRSVUVgIGlzIHNldCBmb3IgYHJwYXJ0YCB0byBwcmV2ZW50IHRoaXMgd2FybmluZyBtZXNzYWdlIGZyb20gY29taW5nIHVwIGxhdGVyIHdoZW4gYHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3RgIGlzIHVzZWQgbGF0ZXIuCgpgYGB7cn0KIz4gV2FybmluZzogQ2Fubm90IHJldHJpZXZlIHRoZSBkYXRhIHVzZWQgdG8gYnVpbGQgdGhlIG1vZGVsIChzbyBjYW5ub3QgZGV0ZXJtaW5lIHJvdW5kaW50IGFuZCBpcy5iaW5hcnkgZm9yIHRoZSB2YXJpYWJsZXMpLgojPiBUbyBzaWxlbmNlIHRoaXMgd2FybmluZzoKIz4gICAgIENhbGwgcnBhcnQucGxvdCB3aXRoIHJvdW5kaW50PUZBTFNFLAojPiAgICAgb3IgcmVidWlsZCB0aGUgcnBhcnQgbW9kZWwgd2l0aCBtb2RlbD1UUlVFLgpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmVnX3RyZWVfc3BlYyA8LSBwYXJzbmlwOjpkZWNpc2lvbl90cmVlKAogICN0cmVlX2RlcHRoID0gNCwKICBjb3N0X2NvbXBsZXhpdHkgPSB0dW5lOjp0dW5lKCkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIAogIHBhcnNuaXA6OnNldF9lbmdpbmUoInJwYXJ0IiwgbW9kZWwgPSBUUlVFKQoKcmVnX3RyZWVfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKcmVnX3RyZWVfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKHJlZ190cmVlX3JlY2lwZSkgJT4lIAogIHdvcmtmbG93czo6YWRkX21vZGVsKHJlZ190cmVlX3NwZWMpCgpyZWdfdHJlZV93b3JrZmxvdyAKYGBgCgojIyBDcmVhdGUgdGhlIGNvc3QgY29tcGxleGl0eSBncmlkCgpBIGNvc3QgY29tcGxleGl0eSBncmlkIG9mICQxMCQgbnVtYmVycyBmcm9tICQwLjAwMSQgKCQxMF57LTR9JCkgdG8KJDEwJCAoJDEwXjEkKSBpcyBjcmVhdGVkLgoKUmVndWxhciBncmlkIGlzIGNyZWF0ZWQgdXNpbmcKW2BkaWFsczo6Z3JpZF9yZWd1bGFyYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZ3JpZF9yZWd1bGFyLmh0bWwpLApbYGRpYWxzOjpjb3N0X2NvbXBsZXhpdHlgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90cmVlcy5odG1sKQphbmQKW2BzY2FsZXM6OmxvZzEwX3RyYW5zYF0oaHR0cHM6Ly9zY2FsZXMuci1saWIub3JnL3JlZmVyZW5jZS9sb2dfdHJhbnMuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKIyBDcmVhdGUgYSByYW5nZSBmcm9tIDEwCmNvc3RfY29tcGxleGl0eV9ncmlkIDwtIAogIGRpYWxzOjpncmlkX3JlZ3VsYXIoCiAgICB4ID0gZGlhbHM6OmNvc3RfY29tcGxleGl0eShyYW5nZSA9IGMoLTQsIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnMgPSBzY2FsZXM6OmxvZzEwX3RyYW5zKCkpLAogICAgbGV2ZWxzID0gMTAKICApCgpjb3N0X2NvbXBsZXhpdHlfZ3JpZCAgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKIyMgUmVncmVzc2lvbiB0cmVlIG1vZGVsIGZpdHRpbmcgb24gY3Jvc3MgdmFsaWRhdGVkIGRhdGEKCk5vdyB3ZSBoYXZlIGV2ZXJ5dGhpbmcgd2UgbmVlZCBhbmQgd2UgY2FuIGZpdCBhbGwgdGhlIG1vZGVscyBvbiB0aGUKY3Jvc3MgdmFsaWRhdGVkIGRhdGEgd2l0aApbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpLgpOb3RlIHRoYXQgdGhpcyBwcm9jZXNzIG1heSB0YWtlIHNvbWUgdGltZS4KCldlIHVzZSBbYHlhcmRzdGljazo6bWV0cmljX3NldGBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9tZXRyaWNfc2V0Lmh0bWwpLCB0byBjaG9vc2UgYSBzZXQgb2YgbWV0cmljcyB0byB1c2VkIHRvIGV2YWx1YXRlIHRoZSBtb2RlbC4gSW4gdGhpcyBleGFtcGxlLCBbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpIGFuZApbYHlhcmRzdGljazo6cnNxYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JzcS5odG1sKSBhcmUgdXNlZC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoKQpmb3JlYWNoOjpnZXREb1BhcldvcmtlcnMoKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CnR1bmVfcmVzIDwtIHR1bmU6OnR1bmVfZ3JpZCgKICBvYmplY3QgPSByZWdfdHJlZV93b3JrZmxvdywKICByZXNhbXBsZXMgPSBCb3N0b25fZm9sZCwgCiAgZ3JpZCA9IGNvc3RfY29tcGxleGl0eV9ncmlkLAogICNtZXRyaWNzID0geWFyZHN0aWNrOjptZXRyaWNfc2V0KHlhcmRzdGljazo6cm1zZSwKICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5YXJkc3RpY2s6OnJzcSkKKQoKdHVuZV9yZXMKYGBgCgpIZXJlIHdlIHNlZSB0aGF0IHRoZSBhbW91bnQgb2YgY29zdCBjb21wbGV4aXR5IGFmZmVjdHMgdGhlIHBlcmZvcm1hbmNlCm1ldHJpY3MgZGlmZmVyZW50bHkgdXNpbmcKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLgpEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYSBkaWZmZXJlbnQgcGxvdAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CiMgTm90ZSB0aGF0IGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGRpZmZlcmVudCBwbG90cwp0dW5lOjphdXRvcGxvdCh0dW5lX3JlcykKYGBgCgpXZSBjYW4gYWxzbyBzZWUgdGhlIHJhdyBtZXRyaWNzIHRoYXQgY3JlYXRlZCB0aGlzIGNoYXJ0IGJ5IGNhbGxpbmcKW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0dW5lOjpjb2xsZWN0X21ldHJpY3ModHVuZV9yZXMpICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCkhlcmUgaXMgdGhlIGBnZ3Bsb3RgIHdheSBzaG91bGQKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpCmZhaWxzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdHVuZV9yZXMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1siY29zdF9jb21wbGV4aXR5Il1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyIubWV0cmljIl1dKSkgKwogIGdncGxvdDI6Omdlb21fZXJyb3JiYXIobWFwcGluZyA9IGdncGxvdDI6OmFlcyh5bWluID0gLmRhdGFbWyJtZWFuIl1dIC0gLmRhdGFbWyJzdGRfZXJyIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gLmRhdGFbWyJtZWFuIl1dICsgLmRhdGFbWyJzdGRfZXJyIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArCiAgZ2dwbG90Mjo6Z2VvbV9saW5lKHNpemUgPSAxLjUpICsKICBnZ3Bsb3QyOjpmYWNldF93cmFwKGZhY2V0cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwgCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIAogICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IDIpICsKICBnZ3Bsb3QyOjpzY2FsZV94X2xvZzEwKCkgKwogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpVc2UKW2B0dW5lOjpzaG93X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKQp0byBzZWUgdGhlIHRvcCBmZXcgdmFsdWVzIGZvciBhIGdpdmVuIG1ldHJpYy4KClRoZSAiYmVzdCIgdmFsdWVzIGNhbiBiZSBzZWxlY3RlZCB1c2luZwpbYHR1bmU6OnNlbGVjdF9iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCksCnRoaXMgZnVuY3Rpb24gcmVxdWlyZXMgeW91IHRvIHNwZWNpZnkgYSBtZXRyaWMgdGhhdCBpdCBzaG91bGQgc2VsZWN0CmFnYWluc3QuIFRoZSBjb3N0IGNvbXBsZXhpdHkgdmFsdWUgaXMgMC4wMDEyOSBmb3IgbWV0cmljIGBybXNlYCBzaW5jZSBpdApnaXZlcyB0aGUgbG93ZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQpkaWZmZXJlbnQgYmVzdCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdG9wX2Nvc3RfY29tcGxleGl0eSA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9IGMoInJtc2UiKSwgbiA9IDUpCnRvcF9jb3N0X2NvbXBsZXhpdHkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmJlc3RfY29zdF9jb21wbGV4aXR5IDwtIHR1bmU6OnNlbGVjdF9iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSAicm1zZSIpCmJlc3RfY29zdF9jb21wbGV4aXR5CgpgYGAKCiMjIFJlZ3Jlc3Npb24gdHJlZSBtb2RlbCB3aXRoIG9wdGltaXNlZCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUKCldlIGNyZWF0ZSB0aGUgcmVncmVzc2lvbiB0cmVlIHdvcmtmbG93IHdpdGggdGhlIGJlc3QgY29zdCBjb21wbGV4aXR5IHNjb3JlCnVzaW5nCltgdHVuZTo6ZmluYWxpemVfd29ya2Zsb3dgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2ZpbmFsaXplX21vZGVsLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCnJlZ190cmVlX2ZpbmFsIDwtIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KAogIHggPSByZWdfdHJlZV93b3JrZmxvdywgCiAgcGFyYW1ldGVycyA9IGJlc3RfY29zdF9jb21wbGV4aXR5KQoKcmVnX3RyZWVfZmluYWwKYGBgCgpXZSBub3cgdHJhaW4gdGhlIHJlZ3Jlc3Npb24gdHJlZSBtb2RlbCB3aXRoIHRoZSB0cmFpbmluZyBkYXRhIHVzaW5nIFtgcGFyc25pcDo6Zml0YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9maXQuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmVnX3RyZWVfZmluYWxfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSByZWdfdHJlZV9maW5hbCwgZGF0YSA9IEJvc3Rvbl90cmFpbikKYGBgCgpXZSBjYW4gc2VlIHRoZSB0cmVlIGluIGdyZWF0ZXIgZGV0YWlsIHVzaW5nCltgdHVuZTo6ZXh0cmFjdF9maXRfZW5naW5lYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXR1bmUuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CnJlZ190cmVlX2ZpbmFsX2ZpdCAlPiUKICB0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmUoKQpgYGAKCldlIGNhbiB2aXN1YWxpc2UgdGhlIGFib3ZlIGJldHRlciB3aXRoIGBycGFydC5wbG90OjpycGFydC5wbG90YAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KcmVnX3RyZWVfZmluYWxfZml0ICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpICU+JQogIHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3QoKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBtZWR2YD8KClVzaW5nIHRoZSBgdmlwYCBwYWNrYWdlIGFuZApbYHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXdvcmtmbG93Lmh0bWwpLCB3ZSBoYXZlCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnZpcF90YWJsZSA8LSByZWdfdHJlZV9maW5hbF9maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aSgpIAoKdmlwX3RhYmxlICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnJlZ190cmVlX2ZpbmFsX2ZpdCAlPiUKICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXA6OnZpcChnZW9tID0gImNvbCIsIAogICAgICAgICAgIGFlc3RoZXRpY3MgPSBsaXN0KGZpbGwgPSAibWlkbmlnaHRibHVlIiwgYWxwaGEgPSAwLjgpKSArCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpCmBgYAoKV2UgY2FuIHVzZSBbYHBhcnR0cmVlYF0oaHR0cHM6Ly9naXRodWIuY29tL2dyYW50bWNkZXJtb3R0L3BhcnR0cmVlKSB0byB1bmRlcnN0YW5kIHdoeSB0aGVzZSB0d28gcGFyYW1ldGVycyB3b3JrIHNvIHdlbGwgb24gdGhlIHRyYWluaW5nIHNldC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcGFydGlhbF90cmVlIDwtIHBhcnNuaXA6OmRlY2lzaW9uX3RyZWUoCiAgY29zdF9jb21wbGV4aXR5ID0gYmVzdF9jb3N0X2NvbXBsZXhpdHkkY29zdF9jb21wbGV4aXR5KSAlPiUKICBwYXJzbmlwOjpzZXRfbW9kZSgicmVncmVzc2lvbiIpICU+JSAKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJycGFydCIsIG1vZGVsID0gVFJVRSkgJT4lCiAgcGFyc25pcDo6Zml0KG1lZHYgfiBybSArIGxzdGF0LCAKICAgICAgICAgICAgICAgZGF0YSA9IEJvc3Rvbl90cmFpbikgJT4lCiAgdHVuZTo6ZXh0cmFjdF9maXRfZW5naW5lKCkKCkJvc3Rvbl90cmFpbiAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbInJtIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJsc3RhdCJdXSkpICsKICBwYXJ0dHJlZTo6Z2VvbV9wYXJ0dHJlZSgKICAgIGRhdGEgPSBwYXJ0aWFsX3RyZWUsIAogICAgbWFwcGluZyA9IGdncGxvdDI6OmFlcyhmaWxsID0gLmRhdGFbWyJtZWR2Il1dKSwgCiAgICBhbHBoYSA9IDAuMikgKwogIGdncGxvdDI6Omdlb21faml0dGVyKGFscGhhID0gMC43LAogICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMC4wNSwKICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGdncGxvdDI6OmFlcyhjb2xvciA9IC5kYXRhW1sibWVkdiJdXSkpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvdXJfdmlyaWRpc19jKGFlc3RoZXRpY3MgPSBjKCJjb2xvciIsICJmaWxsIikpCmBgYAoKIyMgUmVncmVzc2lvbiB0cmVlIG1vZGVsIG9uIHRlc3QgZGF0YQoKRmluYWxseSwgbGV04oCZcyB0dXJuIHRvIHRoZSB0ZXN0aW5nIGRhdGEuCkZvciByZWdyZXNzaW9uIG1vZGVscywgYQpgLnByZWRgLCBjb2x1bW4gaXMgYWRkZWQgd2hlbgpbYHBhcnNuaXA6OmF1Z21lbnRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2F1Z21lbnQuaHRtbCkKaXMgdXNlZC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKdGVzdF9yZXN1bHRzIDwtIHBhcnNuaXA6OmF1Z21lbnQoeCA9IHJlZ190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19kYXRhID0gQm9zdG9uX3Rlc3QpCiAgCnRlc3RfcmVzdWx0cyAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQoKYGBgCgpXZSBjaGVjayBob3cgd2VsbCB0aGUgYC5wcmVkYCBjb2x1bW4gbWF0Y2hlcyB0aGUgYG1lZHZgIHVzaW5nCltgeWFyZHN0aWNrOjpybXNlYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Jtc2UuaHRtbCkuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdGVzdF9yZXN1bHRzICU+JQogIHlhcmRzdGljazo6cm1zZSh0cnV0aCA9IC5kYXRhW1sibWVkdiJdXSwgZXN0aW1hdGUgPSAuZGF0YVtbIi5wcmVkIl1dKSAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCkFsdGVybmF0aXZlbHksIHdlIGNhbiB1c2UKW2B0dW5lOjpsYXN0X2ZpdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbGFzdF9maXQuaHRtbCkKYW5kCltgdHVuZTo6Y29sbGVjdF9tZXRyaWNzYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3JzIDwtIHR1bmU6Omxhc3RfZml0KG9iamVjdCA9IHJlZ190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSBCb3N0b25fc3BsaXQpCiAgCnRlc3RfcnMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKICAKYGBgCgpVc2UgW2B0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLCB0byBzZWUgb25seSB0aGUgYWN0dWFsIGFuZCBwcmVkaWN0ZWQgdmFsdWVzIG9mIHRoZSB0ZXN0IGRhdGEuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdGVzdF9ycyAlPiUKICB0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3JzICU+JQogIHR1bmU6OmNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJtZWR2Il1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIi5wcmVkIl1dCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICApICsKICBnZ3Bsb3QyOjpnZW9tX2FibGluZShzbG9wZSA9IDEsIGx0eSA9IDIsIGNvbG9yID0gImdyYXk1MCIsIGFscGhhID0gMC41KSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuNiwgY29sb3IgPSAibWlkbmlnaHRibHVlIikgKwogIGdncGxvdDI6OmNvb3JkX2ZpeGVkKCkKYGBgCgojIEJhZ2dpbmcKCkhlcmUgd2UgYXBwbHkgYmFnZ2luZyB0byB0aGUgYEJvc3RvbmAgZGF0YSwgdXNpbmcgdGhlIGByYW5kb21Gb3Jlc3RgIHBhY2thZ2UgaW4gUgoKYGBge3IsIGVjaG89RkFMU0V9CnNldC5zZWVkKDEpCmBgYAoKIyMgQ3JlYXRlIHRoZSByZXNhbXBsZSBvYmplY3QKCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuCgpUaGlzIGlzIGRvbmUgd2l0aCBbYHJzYW1wbGU6OmluaXRpYWxfc3BsaXRgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkgYW5kCltgcnNhbXBsZTo6dGVzdGluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpiYWdnaW5nX3JlY2lwZSA8LSAKICByZWNpcGVzOjpyZWNpcGUoZm9ybXVsYSA9IG1lZHYgfiAuLCBkYXRhID0gQm9zdG9uX3RyYWluKQoKYGBgCgojIyBTcGVjaWZ5IHRoZSBtb2RlbAoKVXNlCltgcGFyc25pcDo6cmFuZF9mb3Jlc3RgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JhbmRfZm9yZXN0Lmh0bWwpLApbYHBhcnNuaXA6OnNldF9tb2RlYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zZXRfYXJncy5odG1sKQphbmQKW2BwYXJzbmlwOjpzZXRfZW5naW5lYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zZXRfZW5naW5lLmh0bWwpCnRvIGNyZWF0ZSB0aGUgbW9kZWwuCgpgbXRyeWAgaXMgdGhlIG51bWJlciBvZiBwcmVkaWN0b3JzIHRoYXQgd2lsbCBiZSByYW5kb21seSBzYW1wbGVkIGF0IGVhY2ggc3BsaXQgd2hlbiBjcmVhdGluZyB0aGUgdHJlZSBtb2RlbHMuIEZvciBiYWdnaW5nLCB0aGF0IG51bWJlciBpcyB0aGUgbnVtYmVyIG9mIGNvbHVtbnMgaW4gdGhlIHByZWRpY3RvciBtYXRyaXggZGVub3RlZCBieSBbYHBhcnNuaXA6Oi5jb2xzYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9kZXNjcmlwdG9ycy5odG1sKQoKYGltcG9ydGFuY2VgIHNldCB0byBUUlVFIGVuc3VyZXMgdGhlIGltcG9ydGFuY2Ugb2YgcHJlZGljdG9ycyBhcmUgYXNzZXNzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJhZ2dpbmdfc3BlYyA8LSBwYXJzbmlwOjpyYW5kX2ZvcmVzdChtdHJ5ID0gLmNvbHMoKSkgJT4lCiAgcGFyc25pcDo6c2V0X2VuZ2luZSgicmFuZG9tRm9yZXN0IiwgaW1wb3J0YW5jZSA9IFRSVUUpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikKCmJhZ2dpbmdfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYmFnZ2luZ193b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoYmFnZ2luZ19yZWNpcGUpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9tb2RlbChiYWdnaW5nX3NwZWMpCgpiYWdnaW5nX3dvcmtmbG93IApgYGAKCiMjIEJhZ2dpbmcgbW9kZWwgb24gdHJhaW5pbmcgZGF0YQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpiYWdnaW5nX2ZpdCA8LSBwYXJzbmlwOjpmaXQob2JqZWN0ID0gYmFnZ2luZ193b3JrZmxvdywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gQm9zdG9uX3RyYWluKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBtZWR2YD8KClVzaW5nIHRoZSBgdmlwYCBwYWNrYWdlIGFuZApbYHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXdvcmtmbG93Lmh0bWwpLCB3ZSBoYXZlCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnZpcF90YWJsZSA8LSBiYWdnaW5nX2ZpdCAlPiUKICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXA6OnZpKCkgCgp2aXBfdGFibGUgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KYmFnZ2luZ19maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aXAoZ2VvbSA9ICJjb2wiLCAKICAgICAgICAgICBhZXN0aGV0aWNzID0gbGlzdChmaWxsID0gIm1pZG5pZ2h0Ymx1ZSIsIGFscGhhID0gMC44KSkgKwogIGdncGxvdDI6OnNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKQpgYGAKCiMjIEJhZ2dpbmcgbW9kZWwgb24gdGVzdCBkYXRhCgpGaW5hbGx5LCBsZXTigJlzIHR1cm4gdG8gdGhlIHRlc3RpbmcgZGF0YS4KRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhCmAucHJlZGAsIGNvbHVtbiBpcyBhZGRlZCB3aGVuCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQppcyB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gYmFnZ2luZ19maXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZGF0YSA9IEJvc3Rvbl90ZXN0KQogIAp0ZXN0X3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmBgYAoKV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIGBtZWR2YCB1c2luZwpbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnRlc3RfcmVzdWx0cyAlPiUKICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSAuZGF0YVtbIm1lZHYiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sibWVkdiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpCmBgYAoKIyBSYW5kb20gRm9yZXN0CgpIZXJlIHdlIGFwcGx5IHJhbmRvbSBmb3Jlc3QgdG8gdGhlIGBCb3N0b25gIGRhdGEsIHVzaW5nIHRoZSBgcmFuZG9tRm9yZXN0YCBwYWNrYWdlIGluIFIKCmBgYHtyLCBlY2hvPUZBTFNFfQpzZXQuc2VlZCgxKQpgYGAKCiMjIENyZWF0ZSB0aGUgcnNhbXBsZSBvYmplY3QKCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuCgpUaGlzIGlzIGRvbmUgd2l0aCBbYHJzYW1wbGU6OmluaXRpYWxfc3BsaXRgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkgYW5kCltgcnNhbXBsZTo6dGVzdGluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpyZl9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OnJhbmRfZm9yZXN0YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yYW5kX2ZvcmVzdC5odG1sKSwKW2BwYXJzbmlwOjpzZXRfbW9kZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2FyZ3MuaHRtbCkKYW5kCltgcGFyc25pcDo6c2V0X2VuZ2luZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2VuZ2luZS5odG1sKQp0byBjcmVhdGUgdGhlIG1vZGVsLgoKYG10cnlgIGlzIHRoZSBudW1iZXIgb2YgcHJlZGljdG9ycyB0aGF0IHdpbGwgYmUgcmFuZG9tbHkgc2FtcGxlZCBhdCBlYWNoIHNwbGl0IHdoZW4gY3JlYXRpbmcgdGhlIHRyZWUgbW9kZWxzLiBGb3IgcmFuZG9tIGZvcmVzdCwgdGhlIGByYW5kb21Gb3Jlc3Q6OnJhbmRvbUZvcmVzdGAgdXNlICRcZnJhY3twfXszfSQgdmFyaWFibGVzIHdoZW4gYnVpbGRpbmcgYSByYW5kb20gZm9yZXN0IG9mIHJlZ3Jlc3Npb24gdHJlZXMgYW5kICRcc3FydHtwfSQgdmFyaWFibGVzIHdoZW4gYnVpbGRpbmcgYSByYW5kb20gZm9yZXN0IG9mIGNsYXNzaWZpY2F0aW9uIHRyZWVzLgoKYGltcG9ydGFuY2VgIHNldCB0byBUUlVFIGVuc3VyZXMgdGhlIGltcG9ydGFuY2Ugb2YgcHJlZGljdG9ycyBhcmUgYXNzZXNzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnJmX3NwZWMgPC0gcGFyc25pcDo6cmFuZF9mb3Jlc3QoKSAlPiUKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJyYW5kb21Gb3Jlc3QiLCBpbXBvcnRhbmNlID0gVFJVRSkgJT4lCiAgcGFyc25pcDo6c2V0X21vZGUoInJlZ3Jlc3Npb24iKQoKcmZfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKcmZfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKHJmX3JlY2lwZSkgJT4lIAogIHdvcmtmbG93czo6YWRkX21vZGVsKHJmX3NwZWMpCgpyZl93b3JrZmxvdyAKYGBgCgojIyBSYW5kb20gZm9yZXN0IG9uIHRyYWluaW5nIGRhdGEKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmZfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSByZl93b3JrZmxvdywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gQm9zdG9uX3RyYWluKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBtZWR2YD8KClVzaW5nIHRoZSBgdmlwYCBwYWNrYWdlIGFuZApbYHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXdvcmtmbG93Lmh0bWwpLCB3ZSBoYXZlCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnZpcF90YWJsZSA8LSByZl9maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aSgpIAoKdmlwX3RhYmxlICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnJmX2ZpdCAlPiUKICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXA6OnZpcChnZW9tID0gImNvbCIsIAogICAgICAgICAgIGFlc3RoZXRpY3MgPSBsaXN0KGZpbGwgPSAibWlkbmlnaHRibHVlIiwgYWxwaGEgPSAwLjgpKSArCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpCmBgYAoKVGhlIGByYW5kb21Gb3Jlc3RgIHBhY2thZ2UgYWxzbyBoYXMgZnVuY3Rpb25zIGxpa2UgYHJhbmRvbUZvcmVzdDo6aW1wb3J0YW5jZWAgdG8gdmlldyB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIHZhcmlhYmxlIGFzIHdlbGwuIFRoZSBrZXkgaXMgdG8gdXNlIFtgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9lbmdpbmVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC13b3JrZmxvdy5odG1sKQoKVGhlIGZpcnN0IGlzIGJhc2VkIHVwb24gdGhlIG1lYW4gZGVjcmVhc2Ugb2YgYWNjdXJhY3kgaW4gcHJlZGljdGlvbnMgb24gdGhlIG91dCBvZiBiYWcgc2FtcGxlcyB3aGVuIGEgZ2l2ZW4gdmFyaWFibGUgaXMgcGVybXV0ZWQuIAoKVGhlIHNlY29uZCBpcyBhIG1lYXN1cmUgb2YgdGhlIHRvdGFsIGRlY3JlYXNlIGluIG5vZGUgaW1wdXJpdHkgdGhhdCByZXN1bHRzIGZyb20gc3BsaXRzIG92ZXIgdGhhdCB2YXJpYWJsZSwgYXZlcmFnZWQgb3ZlciBhbGwgdHJlZXMuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmltcG9ydGFuY2VfdGFibGUgPC0gcmZfZml0ICU+JQogIHdvcmtmbG93czo6ZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lCiAgcmFuZG9tRm9yZXN0OjppbXBvcnRhbmNlKCkKICAKaW1wb3J0YW5jZV90YWJsZSAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKClBsb3RzIG9mIHRoZXNlIGltcG9ydGFuY2UgbWVhc3VyZXMgY2FuIGJlIHByb2R1Y2VkIHVzaW5nIHRoZSBgcmFuZG9tRm9yZXN0Ojp2YXJJbXBQbG90YCBmdW5jdGlvbi4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmZfZml0ICU+JQogIHdvcmtmbG93czo6ZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lIAogIHJhbmRvbUZvcmVzdDo6dmFySW1wUGxvdCgpCgpgYGAKCiMjIFJhbmRvbSBmb3Jlc3Qgb24gdGVzdCBkYXRhCgpGaW5hbGx5LCBsZXTigJlzIHR1cm4gdG8gdGhlIHRlc3RpbmcgZGF0YS4KRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhCmAucHJlZGAsIGNvbHVtbiBpcyBhZGRlZCB3aGVuCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQppcyB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gcmZfZml0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSBCb3N0b25fdGVzdCkKICAKdGVzdF9yZXN1bHRzICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpgYGAKCldlIGNoZWNrIGhvdyB3ZWxsIHRoZSBgLnByZWRgIGNvbHVtbiBtYXRjaGVzIHRoZSBgbWVkdmAgdXNpbmcKW2B5YXJkc3RpY2s6OnJtc2VgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm1zZS5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgeWFyZHN0aWNrOjpybXNlKHRydXRoID0gLmRhdGFbWyJtZWR2Il1dLCBlc3RpbWF0ZSA9IC5kYXRhW1siLnByZWQiXV0pICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKTGV0IHVzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgcHJlZGljdGVkIGFuZCBhY3R1YWwgcmVzcG9uc2UgYXMgYSBzY2F0dGVyIHBsb3QuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdGVzdF9yZXN1bHRzICU+JQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbIm1lZHYiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1siLnByZWQiXV0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICkgKwogIGdncGxvdDI6Omdlb21fYWJsaW5lKHNsb3BlID0gMSwgbHR5ID0gMiwgY29sb3IgPSAiZ3JheTUwIiwgYWxwaGEgPSAwLjUpICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJtaWRuaWdodGJsdWUiKSArCiAgZ2dwbG90Mjo6Y29vcmRfZml4ZWQoKQpgYGAKCiMgQm9vc3RpbmcKCkhlcmUgd2UgYXBwbHkgYmFnZ2luZyB0byB0aGUgYEJvc3RvbmAgZGF0YS4gVGhlIGJvb2sgdXNlcyB0aGUgUiBwYWNrYWdlIGBnYm1gIHRvIGRvIHRoZSBib29zdGluZy4gVW5mb3J0dW5hdGVseSwgYGdibWAgaXMgbm90IG9uZSBvZiB0aGUgbGlzdCBib29zdGVkIHRyZWUgbW9kZWxzIGluIHRoZSBjdXJyZW50IFtwYXJzbmlwIGxpc3RdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYm9vc3RfdHJlZS5odG1sKS4KClRoZSBwcm9jZXNzIG9mIGJ1aWxkaW5nIGl0IGZyb20gc2NyYXRjaCBjYW4gYmUgZm91bmQgaW4gdGhpcyBbZ2l0aHViIGlzc3VlIHBvc3RdKGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5bW9kZWxzL3BhcnNuaXAvaXNzdWVzLzIyMCkKCkZvciBzaW1wbGljaXR5LCBhIGRpZmZlcmVudCBSIHBhY2thZ2UgaXMgdXNlZCB0byBjcmVhdGUgYSBiYWdnaW5nIHRyZWUgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgdGhlIGRlZmF1bHQgcGFyc25pcCBlbmdpbmUgZm9yIGJvb3N0ZWQgdHJlZSBpcyB0aGUgYHhnYm9vc3RgIFIgcGFja2FnZS4KCmBgYHtyLCBlY2hvPUZBTFNFfQpzZXQuc2VlZCgxKQpgYGAKCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0CgpGaXJzdCwgd2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4gRnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBjcmVhdGUgYSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LgoKVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRyYWluaW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkKYW5kCltgcnNhbXBsZTo6dmZvbGRfY3ZgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCkJvc3Rvbl9mb2xkIDwtIHJzYW1wbGU6OnZmb2xkX2N2KEJvc3Rvbl90cmFpbiwgdiA9IDEwKQoKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgpib29zdF9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OmJvb3N0X3RyZWVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2Jvb3N0X3RyZWUuaHRtbCksCltgcGFyc25pcDo6c2V0X21vZGVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9hcmdzLmh0bWwpCmFuZApbYHBhcnNuaXA6OnNldF9lbmdpbmVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9lbmdpbmUuaHRtbCkKdG8gY3JlYXRlIHRoZSBtb2RlbC4KClJlY2FsbCBpbiB0aGUgYm9vayB0aGF0IHdlIGNhbiB0dW5lIHRoZSBudW1iZXIgb2YgdHJlZXMgYHRyZWVzYCwgdGhlIHNocmlua2FnZSBwYXJhbWV0ZXIgYGxlYXJuX3JhdGVgIGFuZCB0aGUgbnVtYmVyIG9mIHNwbGl0cy9kZXB0aCBgdHJlZV9kZXB0aGAuCgpUaGVzZSBjYW4gYmUgdHVuZWQgdXNpbmcgW2B0dW5lOjp0dW5lYF0oaHR0cHM6Ly9oYXJkaGF0LnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lLmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJvb3N0X3NwZWMgPC0gcGFyc25pcDo6Ym9vc3RfdHJlZSgKICB0cmVlcyA9IHR1bmU6OnR1bmUoKSwKICB0cmVlX2RlcHRoID0gdHVuZTo6dHVuZSgpLAogIGxlYXJuX3JhdGUgPSB0dW5lOjp0dW5lKCkKICApICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIAogIHBhcnNuaXA6OnNldF9lbmdpbmUoInhnYm9vc3QiKQoKYm9vc3Rfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYm9vc3Rfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKGJvb3N0X3JlY2lwZSkgJT4lIAogIHdvcmtmbG93czo6YWRkX21vZGVsKGJvb3N0X3NwZWMpCgpib29zdF93b3JrZmxvdyAKYGBgCgojIyBDcmVhdGUgdGhlIGJvb3N0aW5nIHRyZWUgZ3JpZAoKTGV04oCZcyB1c2UgYSBzcGFjZS1maWxsaW5nIGRlc2lnbiAobm9uLXJlZ3VsYXIgZ3JpZCkgc28gdGhhdCB3ZSBjYW4gY292ZXIgdGhlIGh5cGVycGFyYW1ldGVyIHNwYWNlIGFzIHdlbGwgYXMgcG9zc2libGUuIFdlIGRvIHRoaXMgdXNpbmcKW2BkaWFsczo6Z3JpZF9sYXRpbl9oeXBlcmN1YmVgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX21heF9lbnRyb3B5Lmh0bWwpCgpXZSB1c2UgdGhlIGRlZmF1bHQgdmFsdWVzIGZvciBbYGRpYWxzOjp0cmVlc2BdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3RyZWVzLmh0bWwpLCBbYGRpYWxzOjp0cmVlX2RlcHRoYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHJlZXMuaHRtbCkgYW5kCltgZGlhbHM6OmxlYXJuX3JhdGVgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9sZWFybl9yYXRlLmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJvb3N0X2dyaWQgPC0gCiAgZGlhbHM6OmdyaWRfbGF0aW5faHlwZXJjdWJlKAogICAgZGlhbHM6OnRyZWVzKHJhbmdlID0gYygxTCwgMjAwMEwpKSwKICAgIGRpYWxzOjp0cmVlX2RlcHRoKHJhbmdlID0gYygxTCwgMTVMKSksCiAgICBkaWFsczo6bGVhcm5fcmF0ZShyYW5nZSA9IGMoLTEwLCAtMSksIAogICAgICAgICAgICAgICAgICAgICAgdHJhbnMgPSBzY2FsZXM6OmxvZzEwX3RyYW5zKCkpLAogICAgc2l6ZSA9IDEwCiAgKQoKYm9vc3RfZ3JpZCAgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKWW91IG1heSByZWZlciB0byB0aGUgW1RpZHl2ZXJzZSBibG9nXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL2Jsb2cvMjAxOS8xMC9kaWFscy0wLTAtMy8pIG9yIFtUZW5na3UgSGFuaXMgYmxvZ10oaHR0cHM6Ly90ZW5na3VoYW5pcy5uZXRsaWZ5LmFwcC9wb3N0L2h5cGVycGFyYW1ldGVyLXR1bmluZy1pbi10aWR5bW9kZWxzLykgZm9yIG1vcmUgZGV0YWlscyBhYm91dCBob3cgdGhlIGRpZmZlcmVudCBncmlkcyB3b3JrLgoKIyMgQm9vc3RpbmcgdHJlZSBtb2RlbCBmaXR0aW5nIG9uIGNyb3NzIHZhbGlkYXRlZCBkYXRhCgpOb3cgd2UgaGF2ZSBldmVyeXRoaW5nIHdlIG5lZWQgYW5kIHdlIGNhbiBmaXQgYWxsIHRoZSBtb2RlbHMgb24gdGhlCmNyb3NzIHZhbGlkYXRlZCBkYXRhIHdpdGgKW2B0dW5lOjp0dW5lX2dyaWRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmVfZ3JpZC5odG1sKS4KTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyBtYXkgdGFrZSBzb21lIHRpbWUuCgpXZSB1c2UgW2B5YXJkc3RpY2s6Om1ldHJpY19zZXRgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbWV0cmljX3NldC5odG1sKSwgdG8gY2hvb3NlIGEgc2V0IG9mIG1ldHJpY3MgdG8gdXNlZCB0byBldmFsdWF0ZSB0aGUgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgW2B5YXJkc3RpY2s6OnJtc2VgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm1zZS5odG1sKSBhbmQKW2B5YXJkc3RpY2s6OnJzcWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yc3EuaHRtbCkgYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkKZm9yZWFjaDo6Z2V0RG9QYXJXb3JrZXJzKCkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQp0dW5lX3JlcyA8LSB0dW5lOjp0dW5lX2dyaWQoCiAgb2JqZWN0ID0gYm9vc3Rfd29ya2Zsb3csCiAgcmVzYW1wbGVzID0gQm9zdG9uX2ZvbGQsIAogIGdyaWQgPSBib29zdF9ncmlkLAogIG1ldHJpY3MgPSB5YXJkc3RpY2s6Om1ldHJpY19zZXQoeWFyZHN0aWNrOjpybXNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWFyZHN0aWNrOjpyc3EpCikKCnR1bmVfcmVzCmBgYAoKSGVyZSB3ZSBzZWUgdGhhdCB0aGUgZGlmZmVyZW50IGFtb3VudCBvZiB0cmVlcywgbGVhcm5pbmcvc2hyaW5rYWdlL3JhdGUgYW5kIHRyZWUgZGVwdGggYWZmZWN0cyB0aGUgcGVyZm9ybWFuY2UKbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZwpbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkuCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBwbG90CgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBOb3RlIHRoYXQgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgZGlmZmVyZW50IHBsb3RzCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQpgYGAKCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZwpbYHR1bmU6OmNvbGxlY3RfbWV0cmljcygpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCnR1bmU6OmNvbGxlY3RfbWV0cmljcyh0dW5lX3JlcykgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZApbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkKZmFpbHMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpmb3JfcGxvdHRpbmcgPC0gdHVuZV9yZXMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZHBseXI6OnNlbGVjdChkcGx5cjo6YWxsX29mKGMoIm1lYW4iLCAiLm1ldHJpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRyZWVzIiwidHJlZV9kZXB0aCIsImxlYXJuX3JhdGUiKSkpICU+JSAKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgY29scyA9IGRwbHlyOjphbGxfb2YoYygidHJlZXMiLCJ0cmVlX2RlcHRoIiwibGVhcm5fcmF0ZSIpKSwKICAgIHZhbHVlc190byA9ICJ2YWx1ZSIsCiAgICBuYW1lc190byA9ICJwYXJhbWV0ZXIiKQoKZm9yX3Bsb3R0aW5nICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKZm9yX3Bsb3R0aW5nICU+JQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbInZhbHVlIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyJwYXJhbWV0ZXIiXV0pKSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuOCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdncGxvdDI6OmZhY2V0X2dyaWQoY29scyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyJwYXJhbWV0ZXIiXV0pLAogICAgICAgICAgICAgICAgICAgICAgcm93cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwKICAgICAgICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIikgKwogIGdncGxvdDI6OmxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMKQpgYGAKClVzZQpbYHR1bmU6OnNob3dfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpCnRvIHNlZSB0aGUgdG9wIGZldyB2YWx1ZXMgZm9yIGEgZ2l2ZW4gbWV0cmljLgoKVGhlICJiZXN0IiB2YWx1ZXMgY2FuIGJlIHNlbGVjdGVkIHVzaW5nCltgdHVuZTo6c2VsZWN0X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKS4KCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBvcHRpbWlzZWQgdmFsdWUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdG9wX2Jvb3N0X2dyaWQgPC0gdHVuZTo6c2hvd19iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSBjKCJybXNlIiksIG4gPSA1KQp0b3BfYm9vc3RfZ3JpZCAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQoKYmVzdF9ib29zdF9ncmlkIDwtIHR1bmU6OnNlbGVjdF9iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSAicm1zZSIpCmJlc3RfYm9vc3RfZ3JpZAoKYGBgCgojIyBCb29zdGluZyB0cmVlIG1vZGVsIHdpdGggb3B0aW1pc2VkIGdyaWQKCldlIGNyZWF0ZSB0aGUgYm9vc3RpbmcgdHJlZSB3b3JrZmxvdyB3aXRoIHRoZSBiZXN0IGdyaWQKW2B0dW5lOjpmaW5hbGl6ZV93b3JrZmxvd2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZmluYWxpemVfbW9kZWwuaHRtbCkuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYm9vc3RfZmluYWwgPC0gdHVuZTo6ZmluYWxpemVfd29ya2Zsb3coCiAgeCA9IGJvb3N0X3dvcmtmbG93LCAKICBwYXJhbWV0ZXJzID0gYmVzdF9ib29zdF9ncmlkKQoKYm9vc3RfZmluYWwKYGBgCgpXZSBub3cgdHJhaW4gdGhlIGJvb3N0aW5nIHRyZWUgbW9kZWwgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YSB1c2luZyBbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJvb3N0X2ZpbmFsX2ZpdCA8LSBwYXJzbmlwOjpmaXQob2JqZWN0ID0gYm9vc3RfZmluYWwsIGRhdGEgPSBCb3N0b25fdHJhaW4pCmBgYAoKV2UgY2FuIHNlZSB0aGUgdHJlZSBpbiBncmVhdGVyIGRldGFpbCB1c2luZwpbYHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC10dW5lLmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQp4Z2JfbW9kZWwgPC0gYm9vc3RfZmluYWxfZml0ICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpCgp4Z2JfbW9kZWwKYGBgCgpXZSBjYW4gdmlzdWFsaXNlIG9uZSBvZiB0aGUgdHJlZXMgd2l0aCBgeGdib29zdDo6eGdiLnBsb3QudHJlZWAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CiMgU2VlIHRoZSBmaXJzdCB0cmVlCmdyIDwtIHhnYm9vc3Q6OnhnYi5wbG90LnRyZWUobW9kZWwgPSB4Z2JfbW9kZWwsIHRyZWVzID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW5kZXIgPSBGQUxTRSkKCkRpYWdyYW1tZVI6OmV4cG9ydF9ncmFwaChncmFwaCA9IGdyLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZV9uYW1lID0gImRvY3MveGdib29zdF9zaW5nbGVfdHJlZS5wbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZV90eXBlID0gIlBORyIpCgpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZG9jcy94Z2Jvb3N0X3NpbmdsZV90cmVlLnBuZyIpCmBgYAoKV2UgY2FuIHZpc3VhbGlzZSBhIHN1bW1hcnkgb2YgYWxsIHRoZSB0cmVlcyB3aXRoIGB4Z2Jvb3N0Ojp4Z2IucGxvdC5tdWx0aS50cmVlc2AKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CmdyIDwtIHhnYm9vc3Q6OnhnYi5wbG90Lm11bHRpLnRyZWVzKG1vZGVsID0geGdiX21vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlc19rZWVwID0gMywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVuZGVyID0gRkFMU0UpCgpEaWFncmFtbWVSOjpleHBvcnRfZ3JhcGgoZ3JhcGggPSBnciwKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVfbmFtZSA9ICJkb2NzL3hnYm9vc3RfbXVsdGlwbGVfdHJlZS5wbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZV90eXBlID0gIlBORyIpCgpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZG9jcy94Z2Jvb3N0X211bHRpcGxlX3RyZWUucG5nIikKYGBgCgojIyBWYXJpYWJsZSBJbXBvcnRhbmNlCgpXaGF0IGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluIHRoaXMgdHJlZSBmb3IgcHJlZGljdGluZyBgbWVkdmA/CgpXZSBjYW4gZXh0cmFjdCB0aGUgaW1wb3J0YW50IGZlYXR1cmVzIGZyb20gdGhlIGJvb3N0ZWQgdHJlZSBtb2RlbCB3aXRoIGB4Z2Jvb3N0Ojp4Z2IuaW1wb3J0YW5jZWAKCkRldGFpbHMgb24gd2hhdCBgR2FpbmAsIGBDb3ZlcmAgYW5kIGBGcmVxdWVuY3lgIGNhbiBiZSBmb3VuZCBpbiB0aGlzIFtibG9nIHBvc3RdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLzIwMTkvMTAvZXhwbGFpbmluZy1wcmVkaWN0aW9ucy1ib29zdGVkLXRyZWVzLXBvc3QtaG9jLWFuYWx5c2lzLXhnYm9vc3QvKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgppbXBvcnRhbmNlX21hdHJpeCA8LSB4Z2Jvb3N0Ojp4Z2IuaW1wb3J0YW5jZShtb2RlbCA9IHhnYl9tb2RlbCkKCmltcG9ydGFuY2VfbWF0cml4ICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKClRoZSBgeGdib29zdDo6eGdiLmdncGxvdC5pbXBvcnRhbmNlYCB1c2VzIHRoZSBgR2FpbmAgdmFyaWFibGUgaW1wb3J0YW5jZSBtZWFzdXJlbWVudCBieSBkZWZhdWx0IHRvIGNhbGN1bGF0ZSB2YXJpYWJsZSBpbXBvcnRhbmNlLiAKCmBgYHtyfQoKeGdib29zdDo6eGdiLmdncGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbF90b19maXJzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGxhYiA9ICJSZWxhdGl2ZSBpbXBvcnRhbmNlIikKCmBgYAoKIyMgQm9vc3RpbmcgdHJlZSBtb2RlbCBvbiB0ZXN0IGRhdGEKCkZpbmFsbHksIGxldOKAmXMgdHVybiB0byB0aGUgdGVzdGluZyBkYXRhLgpGb3IgcmVncmVzc2lvbiBtb2RlbHMsIGEKYC5wcmVkYCwgY29sdW1uIGlzIGFkZGVkIHdoZW4KW2BwYXJzbmlwOjphdWdtZW50YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdWdtZW50Lmh0bWwpCmlzIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnRlc3RfcmVzdWx0cyA8LSBwYXJzbmlwOjphdWdtZW50KHggPSBib29zdF9maW5hbF9maXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZGF0YSA9IEJvc3Rvbl90ZXN0KQogIAp0ZXN0X3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmBgYAoKV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIGBtZWR2YCB1c2luZwpbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnRlc3RfcmVzdWx0cyAlPiUKICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSAuZGF0YVtbIm1lZHYiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sibWVkdiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpCmBgYAoKIyBCYXlzZXNpYW4gQWRkaXRpdmUgUmVncmVzc2lvbiBUcmVlIChCQVJUKQoKSGVyZSB3ZSBhcHBseSBiYWdnaW5nIHRvIHRoZSBgQm9zdG9uYCBkYXRhLiBUaGUgYm9vayB1c2VzIHRoZSBSIHBhY2thZ2UgYEJBUlRgIHRvIGRvIHRoZSBib29zdGluZy4gVW5mb3J0dW5hdGVseSwgYEJBUlRgIGlzIG5vdCBvbmUgb2YgdGhlIGxpc3QgYmFydCBtb2RlbHMgaW4gdGhlIGN1cnJlbnQgW3BhcnNuaXAgbGlzdF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9iYXJ0Lmh0bWwpLgoKRm9yIHNpbXBsaWNpdHksIGEgZGlmZmVyZW50IFIgcGFja2FnZSBpcyB1c2VkIHRvIGNyZWF0ZSBhIEJBUlQgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgdGhlIGRlZmF1bHQgcGFyc25pcCBlbmdpbmUgZm9yIGJvc3RlZCB0cmVlIGlzIHRoZSBgZGJhcnRzYCBSIHBhY2thZ2UuCgpgYGB7ciwgZWNobz1GQUxTRX0Kc2V0LnNlZWQoMSkKYGBgCgojIyBDcmVhdGUgdGhlIHJlc2FtcGxlIG9iamVjdAoKRmlyc3QsIHdlIHNwbGl0IHRoZSBzYW1wbGVzIGludG8gYSB0cmFpbmluZyBzZXQgYW5kIGEgdGVzdCBzZXQuIEZyb20gdGhlIHRyYWluaW5nIHNldCwgd2UgY3JlYXRlIGEgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGRhdGEgc2V0IGZyb20gdGhlIHRyYWluaW5nIHNldC4KClRoaXMgaXMgZG9uZSB3aXRoIFtgcnNhbXBsZTo6aW5pdGlhbF9zcGxpdGBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwKW2Byc2FtcGxlOjp0cmFpbmluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwKW2Byc2FtcGxlOjp0ZXN0aW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpCmFuZApbYHJzYW1wbGU6OnZmb2xkX2N2YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS92Zm9sZF9jdi5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKQm9zdG9uX3NwbGl0IDwtIHJzYW1wbGU6OmluaXRpYWxfc3BsaXQoQm9zdG9uKQoKQm9zdG9uX3RyYWluIDwtIHJzYW1wbGU6OnRyYWluaW5nKEJvc3Rvbl9zcGxpdCkKQm9zdG9uX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhCb3N0b25fc3BsaXQpCgpCb3N0b25fZm9sZCA8LSByc2FtcGxlOjp2Zm9sZF9jdihCb3N0b25fdHJhaW4sIHYgPSAxMCkKCgpgYGAKCiMjIENyZWF0ZSB0aGUgcHJlcHJvY2Vzc29yCgpXZSBjcmVhdGUgYSByZWNpcGUgd2l0aCBbYHJlY2lwZXM6OnJlY2lwZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcmVjaXBlLmh0bWwpLgpObyBvdGhlciBwcmVwcm9jZXNzaW5nIHN0ZXAgaXMgZG9uZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKYmFydF9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OmJhcnRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2JhcnQuaHRtbCksCltgcGFyc25pcDo6c2V0X21vZGVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9hcmdzLmh0bWwpCmFuZApbYHBhcnNuaXA6OnNldF9lbmdpbmVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9lbmdpbmUuaHRtbCkKdG8gY3JlYXRlIHRoZSBtb2RlbC4KCmBuZHBvc3RgIGlzIHRoZSBudW1iZXIgb2YgTUNNQyBpbnRlcmF0aW9ucy4KYG5za2lwYCBpcyB0aGUgbnVtYmVyIG9mIGJ1cm4taW4gaXRlcmF0aW9ucy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKYmFydF9zcGVjIDwtIHBhcnNuaXA6OmJhcnQoCiAgdHJlZXMgPSB0dW5lOjp0dW5lKCkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIAogIHBhcnNuaXA6OnNldF9lbmdpbmUoImRiYXJ0cyIsCiAgICAgICAgICAgICAgICAgICAgICBuc2tpcCA9IDEwMCwKICAgICAgICAgICAgICAgICAgICAgIG5kcG9zdCA9IDUwMCkKCmJhcnRfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYmFydF93b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoYmFydF9yZWNpcGUpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9tb2RlbChiYXJ0X3NwZWMpCgpiYXJ0X3dvcmtmbG93IApgYGAKCiMjIENyZWF0ZSB0aGUgQkFSVCBncmlkCgpMZXTigJlzIHVzZSBhIHNwYWNlLWZpbGxpbmcgZGVzaWduIChub24tcmVndWxhciBncmlkKSBzbyB0aGF0IHdlIGNhbiBjb3ZlciB0aGUgaHlwZXJwYXJhbWV0ZXIgc3BhY2UgYXMgd2VsbCBhcyBwb3NzaWJsZS4gV2UgZG8gdGhpcyB1c2luZwpbYGRpYWxzOjpncmlkX3JlZ3VsYXJgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX3JlZ3VsYXIuaHRtbCkKCldlIHVzZSB0aGUgZGVmYXVsdCB2YWx1ZXMgZm9yIFtgZGlhbHM6OnRyZWVzYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHJlZXMuaHRtbCksIFtgZGlhbHM6OnRyZWVfZGVwdGhgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90cmVlcy5odG1sKSBhbmQKW2BkaWFsczo6bGVhcm5fcmF0ZWBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2xlYXJuX3JhdGUuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKYmFydF9ncmlkIDwtIAogIGRpYWxzOjpncmlkX3JlZ3VsYXIoCiAgICB4ID0gZGlhbHM6OnRyZWVzKHJhbmdlID0gYygxTCwgMTBMKSksCiAgICBsZXZlbHMgPSAxMAogICkKCmJhcnRfZ3JpZCAgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKIyMgQkFSVCBtb2RlbCBmaXR0aW5nIG9uIGNyb3NzIHZhbGlkYXRlZCBkYXRhCgpOb3cgd2UgaGF2ZSBldmVyeXRoaW5nIHdlIG5lZWQgYW5kIHdlIGNhbiBmaXQgYWxsIHRoZSBtb2RlbHMgb24gdGhlCmNyb3NzIHZhbGlkYXRlZCBkYXRhIHdpdGgKW2B0dW5lOjp0dW5lX2dyaWRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmVfZ3JpZC5odG1sKS4KTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyBtYXkgdGFrZSBzb21lIHRpbWUuCgpXZSB1c2UgW2B5YXJkc3RpY2s6Om1ldHJpY19zZXRgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbWV0cmljX3NldC5odG1sKSwgdG8gY2hvb3NlIGEgc2V0IG9mIG1ldHJpY3MgdG8gdXNlZCB0byBldmFsdWF0ZSB0aGUgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgW2B5YXJkc3RpY2s6OnJtc2VgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm1zZS5odG1sKSBhbmQKW2B5YXJkc3RpY2s6OnJzcWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yc3EuaHRtbCkgYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkKZm9yZWFjaDo6Z2V0RG9QYXJXb3JrZXJzKCkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQp0dW5lX3JlcyA8LSB0dW5lOjp0dW5lX2dyaWQoCiAgb2JqZWN0ID0gYmFydF93b3JrZmxvdywKICByZXNhbXBsZXMgPSBCb3N0b25fZm9sZCwgCiAgZ3JpZCA9IGJhcnRfZ3JpZCwKICBtZXRyaWNzID0geWFyZHN0aWNrOjptZXRyaWNfc2V0KHlhcmRzdGljazo6cm1zZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlhcmRzdGljazo6cnNxKQopCgp0dW5lX3JlcwpgYGAKCkhlcmUgd2Ugc2VlIHRoYXQgdGhlIGRpZmZlcmVudCBhbW91bnQgb2YgdHJlZXMgYWZmZWN0cyB0aGUgcGVyZm9ybWFuY2UKbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZwpbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkuCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBwbG90CgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBOb3RlIHRoYXQgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgZGlmZmVyZW50IHBsb3RzCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQpgYGAKCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZwpbYHR1bmU6OmNvbGxlY3RfbWV0cmljcygpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCnR1bmU6OmNvbGxlY3RfbWV0cmljcyh0dW5lX3JlcykgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZApbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkKZmFpbHMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0dW5lX3JlcyAlPiUKICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKSAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJ0cmVlcyJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJtZWFuIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG91ciA9IC5kYXRhW1siLm1ldHJpYyJdXSkpICsKICBnZ3Bsb3QyOjpnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeW1pbiA9IC5kYXRhW1sibWVhbiJdXSAtIC5kYXRhW1sic3RkX2VyciJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1heCA9IC5kYXRhW1sibWVhbiJdXSArIC5kYXRhW1sic3RkX2VyciJdXSksCiAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fbGluZShzaXplID0gMS41KSArCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcChmYWNldHMgPSBnZ3Bsb3QyOjp2YXJzKC5kYXRhW1siLm1ldHJpYyJdXSksIAogICAgICAgICAgICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLCAKICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSAyKSArCiAgZ2dwbG90Mjo6c2NhbGVfeF9sb2cxMCgpICsKICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKVXNlCltgdHVuZTo6c2hvd19iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCkKdG8gc2VlIHRoZSB0b3AgZmV3IHZhbHVlcyBmb3IgYSBnaXZlbiBtZXRyaWMuCgpUaGUgImJlc3QiIHZhbHVlcyBjYW4gYmUgc2VsZWN0ZWQgdXNpbmcKW2B0dW5lOjpzZWxlY3RfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpLgoKRG8gbm90ZSB0aGF0IHVzaW5nIGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGEgZGlmZmVyZW50IG9wdGltaXNlZCB2YWx1ZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0b3BfbnVtYmVyX29mX3RyZWVzIDwtIHR1bmU6OnNob3dfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gYygicm1zZSIpLCBuID0gNSkKdG9wX251bWJlcl9vZl90cmVlcyAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQoKYmVzdF9udW1iZXJfb2ZfdHJlZXMgPC0gdHVuZTo6c2VsZWN0X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJybXNlIikKYmVzdF9udW1iZXJfb2ZfdHJlZXMKCmBgYAoKIyMgQkFSVCBtb2RlbCB3aXRoIG9wdGltaXNlZCBncmlkCgpXZSBjcmVhdGUgdGhlIEJBUlQgd29ya2Zsb3cgd2l0aCB0aGUgYmVzdCBncmlkCltgdHVuZTo6ZmluYWxpemVfd29ya2Zsb3dgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2ZpbmFsaXplX21vZGVsLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCmJhcnRfZmluYWwgPC0gdHVuZTo6ZmluYWxpemVfd29ya2Zsb3coCiAgeCA9IGJhcnRfd29ya2Zsb3csIAogIHBhcmFtZXRlcnMgPSBiZXN0X251bWJlcl9vZl90cmVlcykKCmJhcnRfZmluYWwKYGBgCgpXZSBub3cgdHJhaW4gdGhlIGJvb3N0aW5nIHRyZWUgbW9kZWwgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YSB1c2luZyBbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJhcnRfZmluYWxfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSBiYXJ0X2ZpbmFsLCBkYXRhID0gQm9zdG9uX3RyYWluKQpgYGAKCldlIGNhbiBzZWUgdGhlIHRyZWUgaW4gZ3JlYXRlciBkZXRhaWwgdXNpbmcKW2B0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmVgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2V4dHJhY3QtdHVuZS5odG1sKQoKRm9yIGV4YW1wbGUsIHRvIHNlZSB0aGUgMm5kIHRyZWUgb24gdGhlIHRoaXJkIGl0ZXJhdGlvbgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KYmFydF9tb2RlbCA8LSBiYXJ0X2ZpbmFsX2ZpdCAlPiUKICB0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmUoKQoKYmFydF9tb2RlbCRmaXQkcGxvdFRyZWUoc2FtcGxlTnVtID0gMywgdHJlZU51bSA9IDIpCmBgYAoKIyMgVmFyaWFibGUgSW1wb3J0YW5jZQoKV2hhdCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyBpbiB0aGlzIHRyZWUgZm9yIHByZWRpY3RpbmcgYG1lZHZgPwoKV2UgY2FuIGV4dHJhY3QgdGhlIGltcG9ydGFudCBmZWF0dXJlcyBmcm9tIHRoZSBiYXJ0IHRyZWUgbW9kZWwgd2l0aCBgZW1iYXJjYWRlcm86OnZhcmltcGAKCmBgYHtyfQplbWJhcmNhZGVybzo6dmFyaW1wKGJhcnRfbW9kZWwsIHBsb3RzPVRSVUUpICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpgYGAKCiMjIEJBUlQgbW9kZWwgb24gdGVzdCBkYXRhCgpGaW5hbGx5LCBsZXTigJlzIHR1cm4gdG8gdGhlIHRlc3RpbmcgZGF0YS4KRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhCmAucHJlZGAsIGNvbHVtbiBpcyBhZGRlZCB3aGVuCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQppcyB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gYmFydF9maW5hbF9maXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZGF0YSA9IEJvc3Rvbl90ZXN0KQogIAp0ZXN0X3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmBgYAoKV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIGBtZWR2YCB1c2luZwpbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnRlc3RfcmVzdWx0cyAlPiUKICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSAuZGF0YVtbIm1lZHYiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sibWVkdiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpCmBgYAoKIyBSbWFya2Rvd24gVGVtcGxhdGUKClRoaXMgUm1hcmtkb3duIHRlbXBsYXRlIGlzIGNyZWF0ZWQgYnkgdGhlIFtSZWFsaXR5IEJlbmRpbmcKTGFiXShodHRwczovL3JlYWxpdHliZW5kaW5nLmdpdGh1Yi5pby8pLiBUaGUgdGVtcGxhdGUgY2FuIGJlIGRvd25sb2FkCmZyb20gdGhlIGxhYidzCltnaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9SZWFsaXR5QmVuZGluZy9UZW1wbGF0ZVJlc3VsdHMpIHJlcG9zaXRvcnkuCkZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBtb3RpdmF0aW9uIGJlaGluZCBjcmVhdGluZyB0aGlzIHRlbXBsYXRlLApjaGVjayBvdXQgW0RyLiBEb21pbmlxdWUgTWFrb3dza2kncyBibG9nCnBvc3RdKGh0dHBzOi8vZG9taW5pcXVlbWFrb3dza2kuZ2l0aHViLmlvL3Bvc3QvMjAyMS0wMi0xMC10ZW1wbGF0ZV9yZXN1bHRzLykKCgojIEJsb2cgUmVmZXJlbmNlcwoKLSAgIEVtaWwgSHZpdGZlbGR0J3MgW0lTTFIgdGlkeW1vZGVscwogICAgTGFic10oaHR0cHM6Ly9lbWlsaHZpdGZlbGR0LmdpdGh1Yi5pby9JU0xSLXRpZHltb2RlbHMtbGFicy90cmVlLWJhc2VkLW1ldGhvZHMuaHRtbCkKCi0gICBKdWxpYSBTaWxnZSdzCiAgICBbYmxvZ10oaHR0cHM6Ly9qdWxpYXNpbGdlLmNvbS9ibG9nL3Njb29ieS1kb28uaHRtbCkgdGl0bGVkCiAgICAiUHJlZGljdCB3aGljaCAjVGlkeVR1ZXNkYXkgU2Nvb2J5IERvbyBtb25zdGVycyBhcmUgUkVBTCB3aXRoIGEgdHVuZWQgZGVjaXNpb24gdHJlZSBtb2RlbCIKCi0gICBKdWxpYSBTaWxnZSdzIFtibG9nXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvd2luZC10dXJiaW5lLmh0bWwpIHRpdGxlZAogICAgIlR1bmUgYW5kIGludGVycHJldCBkZWNpc2lvbiB0cmVlcyBmb3IgI1RpZHlUdWVzZGF5IHdpbmQgdHVyYmluZXMiCiAgICAKLSAgIEp1bGlhIFNpbGdlJ3MgW2Jsb2ddKGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy94Z2Jvb3N0LXR1bmUtdm9sbGV5YmFsbC5odG1sKSB0aXRsZWQKICAgICJUdW5lIFhHQm9vc3Qgd2l0aCB0aWR5bW9kZWxzIGFuZCAjVGlkeVR1ZXNkYXkgYmVhY2ggdm9sbGV5YmFsbCIKICAgIAotICAgUi1ibG9nZ2VycycgW2Jsb2ddKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLzIwMTkvMTAvZXhwbGFpbmluZy1wcmVkaWN0aW9ucy1ib29zdGVkLXRyZWVzLXBvc3QtaG9jLWFuYWx5c2lzLXhnYm9vc3QvKSB0aXRsZWQgIkV4cGxhaW5pbmcgUHJlZGljdGlvbnM6IEJvb3N0ZWQgVHJlZXMgUG9zdC1ob2MgQW5hbHlzaXMgKFhnYm9vc3QpIgoKIyBQYWNrYWdlIFJlZmVyZW5jZXMKCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9VFJVRSwgcmVzdWx0cz0nYXNpcyd9CmdldF9jaXRhdGlvbiA8LSBmdW5jdGlvbihwYWNrYWdlX25hbWUpIHsKICB0cmFuc2Zvcm1fbmFtZSA8LSBwYWNrYWdlX25hbWUgJT4lIAogICAgY2l0YXRpb24oKSAlPiUgCiAgICBmb3JtYXQoc3R5bGU9InRleHQiKQogIHJldHVybih0cmFuc2Zvcm1fbmFtZVsxXSkKfSAKCnBhY2thZ2VzIDwtIGMoImJhc2UiLCJyYW5kb21Gb3Jlc3QiLCAidmlwIiwgImdncGxvdDIiLAogICAgICAgICAgICAgICJybWFya2Rvd24iLCAicmVwb3J0IiwgInNwIiwgIlJPQ1IiLAogICAgICAgICAgICAgICJkYmFydHMiLCAidGlkeXZlcnNlIiwgImtuaXRyIiwgIkNvbmZ1c2lvblRhYmxlUiIsCiAgICAgICAgICAgICAgImRhdGEudGFibGUiLCAiRGlhZ3JhbW1lUiIsICJEaWFncmFtbWVSc3ZnIiwgImRpYWxzIiwKICAgICAgICAgICAgICAiZGlzbW8iLCAiZG9QYXJhbGxlbCIsICJkcGx5ciIsICJlbWJhcmNhZGVybyIsCiAgICAgICAgICAgICAgImZvcmNhdHMiLCAiZm9yZWFjaCIsICJnZ3B1YnIiLCAiaHRtbHRvb2xzIiwKICAgICAgICAgICAgICAiSVNMUjIiLCAiaXRlcmF0b3JzIiwgIm1hZ3JpdHRyIiwgIm1hdHJpeFN0YXRzIiwKICAgICAgICAgICAgICAiTWV0cmljcyIsICJwYXJzbmlwIiwgInBhcnR0cmVlIiwgInBhdGNod29yayIsCiAgICAgICAgICAgICAgInB1cnJyIiwgInJhc3RlciIsICJyZWFjdGFibGUiLCAicmVhZHIiLAogICAgICAgICAgICAgICJyZWNpcGVzIiwgInJwYXJ0IiwgInJwYXJ0LnBsb3QiLCAicnNhbXBsZSIsCiAgICAgICAgICAgICAgInJzdmciLCAic2NhbGVzIiwgInN0cmluZ3IiLCAic3R5bGVyIiwKICAgICAgICAgICAgICAidGliYmxlIiwgInRpZHlyIiwgInR1bmUiLCAid29ya2Zsb3dzIiwKICAgICAgICAgICAgICAieGFyaW5nYW5FeHRyYSIsICJ4Z2Jvb3N0IiwgInlhcmRzdGljayIsCiAgICAgICAgICAgICAgIkNrbWVhbnMuMWQuZHAiKQoKdGFibGUgPC0gdGliYmxlOjp0aWJibGUoUGFja2FnZXMgPSBwYWNrYWdlcykKCnRhYmxlICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICB0cmFuc2Zvcm1fbmFtZSA9IHB1cnJyOjptYXBfY2hyKC5kYXRhW1siUGFja2FnZXMiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdldF9jaXRhdGlvbikKICApICU+JSAKICBkcGx5cjo6cHVsbCguZGF0YVtbInRyYW5zZm9ybV9uYW1lIl1dKSAlPiUgCiAgcmVwb3J0Ojphcy5yZXBvcnRfcGFyYW1ldGVycygpCgpgYGAK