🔎 How to download & run the codes?

All the source codes of the aggregation methods are available here . To run the codes, you can clone the repository directly or simply load the R script source file from the repository using devtools package in Rstudio as follow:

  1. Install devtools package using command:

    install.packages("devtools")

  2. Loading the source codes from GitHub repository using source_url function by:

    devtools::source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/MixCOBRAClassifier.R")


✎ Note: All codes contained in this Rmarkdown are built with recent version of (version \(>\) 4.1, available here) and Rstudio (version > 2022.02.2+485, available here). Note also that the code chucks are hidden by default.

To see the codes, you can:


1 MixCobra & important packages

1.1 MixCobra method

This Rmarkdown provides the implementation of an aggregation method using input and out trade-off by Fischer and Mougeot (2019). Let \(\mathcal{D}_n=\{(x_1,y_1),...,(x_n,y_n)\}\) be a training data of size \(n\), where the input-output couples \((x_i,y_i)\in\mathbb{R}^d\times\{1,...,N\}\) for all \(i=1,...,n\), with \(N\) be the number of total classes. \(\mathcal{D}_{n}\) is first randomly partitioned into \(\mathcal{D}_{k}\) and \(\mathcal{D}_{\ell}\) of size \(k\) and \(\ell\) respectively such that \(k+\ell=n\). We construct \(M\) classifiers (machines) \(c_1,...,c_M\) using only \(\mathcal{D}_{k}\). Let \({\bf c}(x)=(c_1(x),...,c_M(x))^T\in\{1,...,N\}^M\) be the vector of predicted classes of \(x\in\mathbb{R}^d\) (given by all classifiers \(c_1,...,c_M\)), the predicted class of the aggregation method evaluated at point \(x\) is defined by

\[\begin{equation} g_n(x)=k^*=\text{arg}\max_{1\leq k\leq N}\sum_{i=1}^{\ell}K_{\alpha,\beta}(x-x_i,d_{\cal H}({\bf c}(x)-{\bf c}(x_i)))\mathbb{1}_{\{y_i=k\}} \end{equation}\]

where \(K:\mathbb{R}^{d+M}\to\mathbb{R}_+\) is a non-increasing kernel function with \(K_{\alpha,\beta}(u,v)=K(\frac{u}{\alpha},\frac{v}{\beta})\) for some smoothing parameter \(\alpha,\beta>0\) to be tuned, with the convention \(0/0=0\).

1.2 Important packages

We prepare all the necessary tools for this Rmarkdown. pacman package allows us to load (if exists) or install (if does not exist) any available packages from The Comprehensive R Archive Network (CRAN) of .

# Check if package "pacman" is already installed 

lookup_packages <- installed.packages()[,1]
if(!("pacman" %in% lookup_packages))
  install.packages("pacman")


# To be installed or loaded
pacman::p_load(magrittr)
pacman::p_load(tidyverse)

## package for "generateMachines"
pacman::p_load(tree)
pacman::p_load(nnet)
pacman::p_load(e1071)
pacman::p_load(randomForest)
pacman::p_load(FNN)
pacman::p_load(xgboost)
pacman::p_load(adabag)
pacman::p_load(keras)
pacman::p_load(pracma)
pacman::p_load(latex2exp)
pacman::p_load(plotly)
rm(lookup_packages)

2 Basic machine generator

This section provides functions to generate basic machines (classifiers) to be aggregated.

2.1 Function : setBasicParameter_Mix

This function allows us to set the values of some key parameters of the basic machines.

  • Argument:

    • k : the parameter \(k\) of \(k\)NN (knn) classifiers and the default value is \(k=10\).
    • ntree : the number of trees in random forest (rf). By default, ntree = 300.
    • mtry : the number of random features chosen in each split of random forest procedure. By default, mtry = NULL and the default value of mtry of randomForest function from randomForest library is used.
    • ker_svm : kernel option in SVM. It should be a subset of {"linear", "polynomial", "radial", "sigmoid"}. By default, ker_svm = "radial".
    • deg_svm : degree of polynomial kernel in SVM. By default, deg_svm = 3.
    • breg_boost : Bregman divergence used in boosting of maboost package. By default, breg_boost = "entrop" and KL divergence is used, resulting Adaboost-like algorithm.
    • iter_boost : number of boosting iterations to perform. Default iter_boost = 100.
    • eta_xgb : the learning rate \(\eta>0\) in gradient step of extreme gradient boosting method (xgb) of xgboost library.
    • nrounds_xgb : the parameter nrounds indicating the max number of boosting iterations. By default, nrounds_xgb = 100.
    • early_stop_xgb : the early stopping round criterion of xgboost function. By, default, early_stop_xgb = NULL and the early stopping function is not triggered.
    • max_depth_xgb : maximum depth of trees constructed in xgboost.
    • param_xgb : list of additional parameters of xgboost classifier. By default, param_xgb = NULL. For more information, read online documentation.
  • Value:

    This function returns a list of all the parameters given in its arguments, to be fed to the basicMachineParam argument of function generateMachines defined in the next section.


🧾 ** Remark.1**: k, ntree, iter_boost and nrounds_xgb can be a single value or a vector. In other words, each type of models can be constructed several times according to the values of the hyperparameters (a single value or vector).


setBasicParameter_Mix <- function(k = 10,
                              ntree = 300,
                              mtry = NULL,
                              ker_svm = "radial",
                              deg_svm = 3,
                              mfinal_boost = 50,
                              boostrap = TRUE,
                              eta_xgb = 1, 
                              nrounds_xgb = 100, 
                              early_stop_xgb = NULL,
                              max_depth_xgb = 3,
                              param_xgb = NULL){
  return(list(
    k = k,
    ntree = ntree, 
    mtry = mtry, 
    ker_svm = ker_svm,
    deg_svm = deg_svm,
    mfinal_boost = mfinal_boost,
    boostrap = boostrap,
    eta_xgb = eta_xgb, 
    nrounds_xgb = nrounds_xgb, 
    early_stop_xgb = early_stop_xgb,
    max_depth_xgb = max_depth_xgb,
    param_xgb = param_xgb)
  )
}

2.2 Function : generateMachines_Mix

This function generates all the basic machines to be aggregated.

  • Argument:

    • train_input : a matrix or data frame of the training input data.
    • train_response : a vector of training response variable corresponding to the train_input.
    • scale_input : logical value specifying whether to scale the input data (to be between \(0\) and \(1\)) or not. By default, scale_input = FALSE.
    • machines : types of basic machines to be constructed. It is a subset of {"knn", "tree", "rf", "logit", "svm", "xgb", "adaboost"}. By default, machines = NULL and all types of the basic machines are built.
    • splits : real number between \(0\) and \(1\) specifying the proportion of training data used to train the basic machines (\(\mathcal{D}_k\)). The remaining proportion of (\(1-\) splits) is used for the aggregation (\(\mathcal{D}_{\ell}\)). By default, splits = 0.5.
    • basicMachineParam : the option used to setup the values of parameters of each machines. One should feed the function setBasicParameter_Mix() defined above to this argument.
    • silent : a logical value specifying whether or not progressing messages should be printed. Be default, silent = FALSE and the progress of the algorithm is printed.
  • Value:

    This function returns a list of the following objects.

    • fitted_remain : the predictions of the remaining part (\(\mathcal{D}_{\ell}\)) of the training data used for the aggregation.
    • models : all the constructed basic machines (it contains only the values of prapeter \(k\) for knn).
    • id2 : a logical vector of size equals to the number of lines of the training data indicating the location of the points used to build the basic machines (FALSE) and the remaining ones (TRUE).
    • train_data : a list of:
      • train_input : the trainnig input data.
      • train_response : the training response variable.
      • classes : the classes (unique) of response variable.
    • scale_max, scale_min : if the argument scale_input = TRUE, the maximun and minimun values of all columns are contained in these vectors.

✎ Note: You may need to modify the function accordingly if you want to build different types of basic machines.


generateMachines_Mix <- function(train_input, 
                             train_response,
                             scale_input = FALSE,
                             machines = NULL,
                             splits = 0.5, 
                             basicMachineParam = setBasicParameter_Mix(),
                             silent = FALSE){
  k <- basicMachineParam$k 
  ntree <- basicMachineParam$ntree 
  mtry <- basicMachineParam$mtry
  ker_svm <- basicMachineParam$ker_svm
  deg_svm <- basicMachineParam$deg_svm
  mfinal_boost = basicMachineParam$mfinal_boost
  boostrap = basicMachineParam$boostrap
  eta_xgb <- basicMachineParam$eta_xgb 
  nrounds_xgb <- basicMachineParam$nrounds_xgb
  early_stop_xgb <- basicMachineParam$early_stop_xgb
  max_depth_xgb <- basicMachineParam$max_depth_xgb
  param_xgb <- basicMachineParam$param_xgb
  class_xgb <- unique(train_response)
  numberOfClasses <- length(class_xgb)
  if(is.null(param_xgb)){
    param_xgb <- list("objective" = "multi:softmax",
                      "eval_metric" = "mlogloss",
                      "num_class" = numberOfClasses+1)
  }
  
  # Packages
  pacman::p_load(nnet)
  pacman::p_load(e1071)
  pacman::p_load(tree)
  pacman::p_load(randomForest)
  pacman::p_load(FNN)
  pacman::p_load(xgboost)
  pacman::p_load(maboost)
  
  # Preparing data
  input_names <- colnames(train_input)
  input_size <- dim(train_input)
  df_input <- train_input_scale <- train_input
  if(scale_input){
    maxs <- map_dbl(.x = df_input, .f = max)
    mins <- map_dbl(.x = df_input, .f = min)
    train_input_scale <- scale(train_input, 
                               center = mins, 
                               scale = maxs - mins)
  }
  if(is.matrix(train_input_scale)){
    df_input <- as_tibble(train_input_scale)
    matrix_input <- train_input_scale
  } else{
    df_input <- train_input_scale
    matrix_input <- as.matrix(train_input_scale)
  }
  
  # Machines
  svm_machine <- function(x, pa = NULL){
    mod <- svm(x = df_train_x1, 
               y = train_y1,
               kernel = ker_svm,
               degree = deg_svm,
               type = "C-classification")
    res <- predict(mod, 
                   newdata = x)
    return(list(pred = res,
                model = mod))
  }
  tree_machine <- function(x, pa = NULL) {
    mod <- tree(as.formula(paste("train_y1~", 
                                 paste(input_names, 
                                       sep = "", 
                                       collapse = "+"), 
                                 collapse = "", 
                                 sep = "")), 
                data = df_train_x1)
    res <- predict(mod, x, type = 'class')
    return(list(pred = res,
                model = mod))
  }
  knn_machine <- function(x, k0) {
    mod <- knn(train = matrix_train_x1, 
                   test = x, 
                   cl = train_y1, 
                   k = k0)
    return(list(pred = mod,
                model = k0))
  }
  RF_machine <- function(x, ntree0) {
    if(is.null(mtry)){
      mod <- randomForest(x = df_train_x1, 
                          y = train_y1, 
                          ntree = ntree0)
    }else{
      mod <- randomForest(x = df_train_x1, 
                          y = train_y1, 
                          ntree = ntree0, 
                          mtry = mtry)
    }
    res <- as.vector(predict(mod, x))
    return(list(pred = res,
                model = mod))
  }
  xgb_machine <- function(x, nrounds_xgb0){
    mod <- xgboost(data = matrix_train_x1,
                   label = train_y1,
                   params = param_xgb,
                   eta = eta_xgb,
                   early_stopping_rounds = early_stop_xgb,
                   max_depth = max_depth_xgb,
                   verbose = 0,
                   nrounds = nrounds_xgb0)
    res <- class_xgb[predict(mod, x)]
    return(list(pred = res,
                model = mod))
  }
  ada_machine <- function(x, mfinal0){
    data_tem <- cbind(df_train_x1, "target" = train_y1)
    mod_ <- boosting(target ~ ., 
                     data = data_tem,
                     mfinal = mfinal0,
                     boos = boostrap)
    res <- predict.boosting(mod_, 
                         newdata = as.data.frame(x))
    return(list(pred = res$class,
                model = mod_))
  }
  logit_machine <- function(x, pa = NULL){
    mod <- multinom(as.formula(paste("train_y1~", 
                                 paste(input_names, 
                                       sep = "", 
                                       collapse = "+"), 
                                 collapse = "", 
                                 sep = "")), 
                data = df_train_x1,
                trace = FALSE)
    res <- predict(mod, 
                   newdata = x)
    return(list(pred = res,
                model = mod))
  }
  # All machines
  all_machines <- list(knn = knn_machine, 
                       tree = tree_machine, 
                       rf = RF_machine,
                       logit = logit_machine,
                       svm = svm_machine,
                       adaboost = ada_machine,
                       xgb = xgb_machine)
  # All parameters
  all_parameters <- list(knn = k, 
                         tree = 1,
                         rf = ntree,
                         logit = NA,
                         svm = deg_svm,
                         adaboost = mfinal_boost,
                         xgb = nrounds_xgb)
  lookup_machines <- c("knn", "tree", "rf", "logit", "svm", "xgb", "adaboost")
  if(is.null(machines)){
    mach <- lookup_machines
  }else{
    mach <- map_chr(.x = machines,
                    .f = ~ match.arg(.x, lookup_machines))
  }
  # Extracting data
  M <- length(mach)
  size_D1 <- floor(splits*input_size[1])
  id_D1 <- logical(input_size[1])
  id_D1[sample(input_size[1], size_D1)] <- TRUE

  df_train_x1 <- df_input[id_D1,]
  matrix_train_x1 <- matrix_input[id_D1,]
  train_y1 <- train_response[id_D1]
  df_train_x2 <- df_input[!id_D1,]
  matrix_train_x2 <- matrix_input[!id_D1,]
  
  # Function to extract df and model from 'map' function
  extr_df <- function(x, nam, id){
    return(tibble("{nam}_{id}" := as.vector(pred_m[[x]]$pred)))
  }
  extr_mod <- function(x, id){
    return(pred_m[[x]]$model)
  }
  
  pred_D2 <- c()
  all_mod <- c()
  if(!silent){
    cat("\n* Building basic machines ...\n")
    cat("\t~ Progress:")
  }
  for(m in 1:M){
    if(mach[m] %in% c("knn", "xgb")){
      x0_test <-  matrix_train_x2
    } else {
      x0_test <- df_train_x2
    }
    if(is.null(all_parameters[[mach[m]]])){
      para_ <- 1
    }else{
      para_ <- all_parameters[[mach[m]]]
    }
    pred_m <-  map(para_,
                   .f = ~ all_machines[[mach[m]]](x0_test, .x))
    tem0 <- imap_dfc(.x = 1:length(para_), 
                     .f = ~ extr_df(x = .x, nam = mach[m], id = para_[.x]))
    tem1 <- map(.x = 1:length(para_), 
                 .f = extr_mod)
    names(tem0) <- names(tem1) <- paste0(mach[m], 1:length(para_))
    pred_D2 <- bind_cols(pred_D2, as_tibble(tem0))
    all_mod[[mach[m]]] <- tem1
    if(!silent){
      cat(" ... ", round(m/M, 2)*100L,"%", sep = "")
    }
  }
  if(scale_input){
    return(list(fitted_remain = pred_D2,
                models = all_mod,
                id2 = !id_D1,
                train_data = list(train_input = train_input_scale, 
                                  train_response = train_response,
                                  classes = class_xgb),
                scale_max = maxs,
                scale_min = mins))
  } else{
    return(list(fitted_remain = pred_D2,
                models = all_mod,
                id2 = !id_D1,
                train_data = list(train_input = train_input_scale, 
                                  train_response = train_response,
                                  classes = class_xgb)))
  }
}

Example.1: In this example, the method is implemented on iris dataset. All types of basic machines are built on the first part of the training data (\(\mathcal{D}_{k}\)), and the accuracy evaluated on the second part of the training data (\(\mathcal{D}_{\ell}\)) used for aggregation) are reported.


df <- iris
basic_machines <- generateMachines_Mix(train_input = df[,1:4],
                                   train_response = df$Species,
                                   scale_input = TRUE,
                                   machines = NULL, #c("knn", "tree", "rf", "logit", "svm", "xgb")
                                   basicMachineParam = setBasicParameter_Mix(ntree = 10:20 * 25,
                                                                         k = c(2:10),
                                                                         mfinal_boost = 10))

* Building basic machines ...
    ~ Progress: ... 14% ... 29% ... 43% ... 57% ... 71% ... 86% ... 100%
basic_machines$fitted_remain %>%
  sweep(1, df[basic_machines$id2, "Species"], FUN = "==") %>%
  colMeans %>%
  t %>%
  as_tibble

3 Optimizer : grid search algorithm

This part provides functions to approximate the key parameters \((\alpha,\beta)\in(\mathbb{R}_+^*)^2\) of the aggregation.

3.1 Function : setGridParameter_Mix

This function allows to set the values of grid search algorithm parameters.

  • Argument:

    • min_alpha : mininum value of \(\alpha\) in the grid. By defualt, min_alpha = 1e-5.
    • max_alpha : maxinum value of \(\alpha\) in the grid. By defualt, max_alpha = 5.
    • min_beta : mininum value of \(\beta\) in the grid. By defualt, min_beta = 0.1.
    • max_beta : maximum value of \(\beta\) in the grid. By defualt, max_alpha = 50.
    • n_alpha, n_beta = 30 : the number of \(\alpha\) and \(\beta\) respectively in the grid. By defualt, n_alpha = n_beta = 30.
    • parameters : a list of parameter \(alpha\) and \(\beta\) in case non-uniform grid is considered. It should be a list of two vectors containing the values of \(\alpha\) and \(\beta\) respectively. By default, parameters = NULL and the default uniform grid is used.
    • axes : names of \(x,y\) and \(z\)-axis respectively. By default, axes = c("alpha", "beta", "Risk").
    • title : the title of the plot. By default, title = NULL and the default title is Cross-validation risk as a function of \((\alpha, \beta)\).
    • print_result : a logical value specifying whether to print the observed result or not.
    • figure : a logical value specifying whether to plot the graphic of cross-validation error or not.
  • Value:

    This function returns a list of all the parameters given in its arguments.

setGridParameter_Mix <- function(min_alpha = 1e-5,
                                 max_alpha = 5,
                                 min_beta = 0.1,
                                 max_beta = 50,
                                 n_alpha = 30,
                                 n_beta = 30,
                                 parameters = NULL,
                                 axes = c("alpha", "beta", "Risk"),
                                 title = NULL,
                                 print_result = TRUE,
                                 figure = TRUE){
  return(list(min_alpha = min_alpha,
              max_alpha = max_alpha,
              min_beta = min_beta,
              max_beta = max_beta,
              n_alpha = n_alpha,
              n_beta = n_beta,
              axes = axes,
              title = title,
              parameters = parameters,
              print_result = print_result,
              figure = figure))
}

3.1.1 Function : gridOptimizer_Mix

  • Argument:

    • obj_fun : the objective function for which its minimizer is to be estimated. It should be a univarate function of real positive variables.
    • setParameter : the control of grid search algorithm parameters which should be the function setGridParameter_Mix() defined above.
    • silent : a logical value specifying whether or not progressing messages should be printed. Be default, silent = FALSE and the progress of the algorithm is printed.
  • Value:

    This function returns a list of the following objects:

    • opt_param : the observed value of the minimizer.
    • opt_error : the value of optimal risk.
    • all_risk : the vector of all the errors evaluated at all the values of considered parameters.
    • run.time : the running time of the algorithm.
gridOptimizer_Mix <- function(obj_func,
                              setParameter = setGridParameter_Mix(),
                              silent = FALSE){
  t0 <- Sys.time()
  if(is.null(setParameter$parameters)){
    param_list <- list(alpha =  rep(seq(setParameter$min_alpha, 
                                        setParameter$max_alpha,
                                        length.out = setParameter$n_alpha), 
                                    setParameter$n_beta),
                       beta =  rep(seq(setParameter$min_beta, 
                                       setParameter$max_beta,
                                       length.out = setParameter$n_beta),
                                   each = setParameter$n_alpha))
  } else{
    param_list <- list(alpha = rep(setParameter$parameters[[1]], 
                                   length(setParameter$parameters[[2]])),
                       beta = rep(setParameter$parameters[[2]], 
                                   each = length(setParameter$parameters[[1]])))
  }
  risk <- map2_dbl(.x = param_list$alpha,
                   .y = param_list$beta,
                   .f = ~ obj_func(c(.x, .y)))
  id_opt <- which.min(risk)
  opt_ep <- c(param_list$alpha[id_opt], param_list$beta[id_opt])
  opt_risk <- risk[id_opt]
  if(setParameter$print_result & !silent){
    cat("\n* Grid search algorithm...", "\n ~ Observed parameter: (alpha, beta) = (", opt_ep[1], 
        ", ", 
        opt_ep[2], ")", 
        sep = "")
  }
  if(setParameter$figure){
    if(is.null(setParameter$title)){
      tit <- paste("<b> Cross-validation risk as a function of</b> (",
                   setParameter$axes[1],",", 
                   setParameter$axes[2],
                   ")")
    } else{
      tit <- setParameter$title
    }
    fig <- tibble(alpha = param_list$alpha, 
                  beta = param_list$beta,
                  risk = risk) %>%
      plot_ly(x = ~alpha, y = ~beta, z = ~risk, type = "mesh3d") %>%
      add_trace(x = c(opt_ep[1], opt_ep[1]),
                y = c(0, opt_ep[2]),
                z = c(opt_risk, opt_risk),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#5E88FC", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#5E88FC", "#38DE25")),
                name = paste("Optimal",setParameter$axes[1])) %>%
      add_trace(x = c(0, opt_ep[1]),
                y = c(opt_ep[2], opt_ep[2]),
                z = c(opt_risk, opt_risk),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#F31536", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#F31536", "#38DE25")),
                name = paste("Optimal",setParameter$axes[2]))  %>%
      add_trace(x = opt_ep[1],
                y = opt_ep[2],
                z = opt_risk,
                type = "scatter3d",
                mode = 'markers',
                marker = list(
                  size = 5,
                  color = "#38DE25"),
                name = "Optimal point") %>%
      layout(title = list(text = tit,
                          x = 0.075, 
                          y = 0.925,
                          font = list(family = "Verdana",
                                      color = "#5E88FC")),
             legend = list(x = 100, y = 0.5),
             scene = list(xaxis = list(title = setParameter$axes[1]),
                          yaxis = list(title = setParameter$axes[2]),
                          zaxis = list( title = setParameter$axes[3])))
    print(fig)
  }
  t1 <- Sys.time()
  return(list(opt_param = opt_ep,
              opt_error = opt_risk,
              all_risk = risk,
              run.time = difftime(t1, t0, units = "secs")[[1]])
  )
}

3.2 \(\kappa\)-cross validation \(0\)-\(1\) lost function

Constructing aggregation method is equivalent to approximating the optimal value of parameter \((\alpha,\beta)\in(\mathbb{R}_+^*)^2\) introduced in section 1.1 by minimizing some lost function. In this study, we propose \(\kappa\)-fold cross validation misclassification error defined by

\[ \varphi^{\kappa}(\alpha, \beta)=\frac{1}{\kappa}\sum_{k=1}^{\kappa}\Big(\frac{1}{|F_k|}\sum_{(x_j,y_j)\in F_k}\mathbb{1}_{\{g_n({\bf c}(x_j))\neq y_j\}}\Big) \]

where

  • for any \(k=1,...,\kappa\), \(F_k\) denotes the \(k\)th validation fold.
  • \(g_n({\bf c}(x_j))\) is the prediction of \(x_j\) of \(F_k\), computed using the data points from the remaining part \({\cal D}_{\ell}-F_k\) by,

\[g_n({\bf c}(x_j))=k^*=\text{arg}\max_{1\leq k\leq N}\sum_{(x_i,y_i)\in \mathcal{D}\setminus F_k}K_{\alpha, \beta}(d_{\cal H}({\bf c}(x_j),{\bf c}(x_i)))\mathbb{1}_{\{y_i=k\}}.\]

3.3 Function: dist_matrix_Mix

This function computes some distances (input part) and Hamming distances (output part) between data points of each training folds (\(\mathcal{D}_{\ell}-F_k\)) and the corresponding validation fold \(F_k\) for any \(k=1,\dots,\kappa\). The distance matrices of input part are computed according to kernel types, while the distances of the output part are always the Hamming distances between predicted classes of data points. Two lists: one for input part and another for output part, each contains \(\kappa\) distance matrices \(D_k=(d[{\bf r}(x_i),{\bf r}(x_j)])_{i,j}\) for \(k=1,\dots,\kappa\), corresponding to \(\kappa\) validation folds, are computed.

  • Argument:

    • basicMachines : the basic machine object, which is an output of generateMachines_Mix function.
    • n_cv : the number \(\kappa\) of cross-validation folds. By default, n_cv = 5.
    • kernel : the kernel function used for the aggregation, which is an element of {“gaussian”, “epanechnikov”, “biweight”, “triweight”, “triangular”, “naive”}. By default, kernel = "gaussian".
    • id_shuffle : an integer vector of length equals to the size of the remaining part of the training data (\({\cal D}_{\ell}\)). Its elements are from {\(1,...,\kappa\)}, indicating the fold membership of the data points. By default, id_shuffle = NULL, and the data points will be shuffled randomly.
    • output : a logical value specifying whether the hamming distances between output classes are computed or not. By default, output = FALSE.
  • Value:

    This functions returns a list of the following objects:

    • dist_input : a list of data frame (tibble) containing sublists corresponding to kernel functions used for the aggregation. Each sublist contains n_cv numbers of input-distance matrices \(D_k=(d[{\bf r}(x_i),{\bf r}(x_j)])_{i,j}\), for \(k=1,\dots,\kappa\), containing distances between the data points in valiation fold (along the columns) and the \(1-\kappa\) remaining folds of training data (along the rows). The type of distance matrices depends on the kernel used:
      • If kernel = naive, the distance matrices contain the maximum distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_{\max})_{i,j}\text{ for }k=1,\dots,\kappa.\]
      • If kernel = triangular, the distance matrices contain the \(L_1\) distance between data points, i.e., dist_matrix_Mix \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_1)_{i,j}\text{ for }k=1,\dots,\kappa.\]
      • Otherwise, the distance matrices contain the squared \(L_2\) distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_ 2^2)_{i,j}\text{ for }k=1,\dots,\kappa.\]
    • dist_machine : a list containing \(\kappa\) tibble of Hamming distances between predicted classes of validation folds and the rest of cross validation folds.
    • id_shuffle : the shuffled indices in cross-validation.
    • n_cv : the number \(\kappa\) of cross-validation folds.
dist_matrix_Mix <- function(basicMachines,
                            n_cv = 5,
                            kernel = "gausian",
                            id_shuffle = NULL,
                            output = TRUE){
  n <- nrow(basicMachines$fitted_remain)
  n_each_fold <- floor(n/n_cv)
  # shuffled indices
  if(is.null(id_shuffle)){
    shuffle <- 1:(n_cv-1) %>%
      rep(n_each_fold) %>%
      c(., rep(n_cv, n - n_each_fold * (n_cv - 1))) %>%
      sample
  }else{
    shuffle <- id_shuffle
  }
  # the prediction matrix D_l
  df_mach <- as.matrix(basicMachines$fitted_remain)
  df_input <- as.matrix(basicMachines$train_data$train_input[basicMachines$id2,])
  if(!(kernel %in% c("naive", "triangular"))){
    pair_dist_input <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE))^2)))))
      return(res_)
    }
  }
  if(kernel == "triangular"){
    pair_dist_input <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE)))))))
      return(res_)
    }
  }
  if(kernel == "naive"){
    pair_dist_input <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE)), 1, max)))))
      return(res_)
    }
  }
  pair_dist_output <- function(M, N){
    res_ <- 1:nrow(N) %>%
      map_dfc(.f = (\(id) tibble('{{id}}' := rowSums(sweep(M, 2, N[id,], FUN = "!=")))))
    return(res_)
  }
  L1 <- 1:n_cv %>%
      map(.f = (\(x) pair_dist_input(df_input[shuffle != x,],
                                     df_input[shuffle == x,])))
  if(output){
    L2 <- 1:n_cv %>%
      map(.f = (\(x) pair_dist_output(df_mach[shuffle != x,],
                                      df_mach[shuffle == x,])))
  } else{
    L2 <- NA
  }
  return(list(dist_input = L1,
              dist_machine = L2,
              id_shuffle = shuffle,
              n_cv = n_cv))
}

Example.3: The method dist_matrix_Mix is implemented on the obtained basic machines built in Example.1 with the corresponding Gaussian kernel function.


dis <- dist_matrix_Mix(basicMachines = basic_machines,
                       n_cv = 3,
                       kernel = "gaussian")
cat("* Number of folds :", dis$n_cv)
* Number of folds : 3

Example.4: From the distance matrix, we can compute the error corresponding to Gaussian kernel function, then use both of the optimization methods to approximate the smoothing paramter in this case.


# Gaussian kernel
gaussian_kern <- function(.ep = c(.05, 0.005),
                          .dist_matrix,
                          .train_response2,
                          .inv_sigma = sqrt(.5),
                          .alpha = 2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(exp(- (x[1]*D1+x[2]*D2)^(.alpha/2)*.inv_sigma^.alpha))
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
}

# Kappa cross-validation error
cost_fun <- function(x,
                     .dist_matrix = dis,
                     .kernel_func = gaussian_kern,
                     .train_response2 = basic_machines$train_data$train_response[basic_machines$id2],
                     .inv_sigma = sqrt(.5),
                     .alpha = 2){
  return(.kernel_func(.ep = x,
                      .dist_matrix = .dist_matrix,
                      .train_response2 = .train_response2,
                      .inv_sigma = .inv_sigma,
                      .alpha = .alpha))
}
# Optimization
opt_param_grid <- gridOptimizer_Mix(obj_fun = cost_fun,
                                    setParameter = setGridParameter_Mix(min_alpha = 0.0001,
                                                                max_alpha = 1,
                                                                min_beta = 0.001,
                                                                max_beta = 5,
                                                                n_beta = 20,
                                                                n_alpha = 20,
                                                                figure = TRUE))

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1e-04, 0.2641053)

3.4 Fitting parameter

This function gathers the constructed machines, then performs an optimization algorithm to approximate the smoothing parameter for the aggregation, using only the remaining part \({\cal D}_{\ell}\) of the training data.

  • Argument:

    • train_input, : a matrix or data frame of the training input data.
    • train_response : a vector of the corresponding response variable of the train_input.
    • machines : a vector of basic machines to be constructed. It must be a subset of {“lasso”, “ridge”, “knn”, “tree”, “rf”, “xgb”}. By default, machines = NULL and all the six types of basic machines are built.
    • scale_input : a logical value specifying whether or not to scale the input data before building the basic classifiers. By default, scale_input = TRUE.
    • splits : the proportion of training data (the proportion of \({\cal D}_k\) in \({\cal D}_n\)) used to build the basic machines. By default, splits = .5.
    • n_cv : the number of cross-validation folds used to tune the smoothing parameter.
    • inv_sigma, alpha : the inverse normalized constant \(\sigma^{-1}>0\) and the exponent \(\alpha>0\) of exponential kernel: \(K(x)=e^{-\|x/\sigma\|^{\alpha}}\) for any \(x\in\mathbb{R}^d\). By default, inv_sigma =\(\sqrt{1/2}\) and alpha = 2 which corresponds to the Gaussian kernel.
    • kernels : the kernel function or vector of kernel functions used for the aggregation. By fault, kernels = "gaussian".
    • setBasicMachineParam : an option used to set the values of the parameters of the basic machines. setBasicParameter_Mix function should be fed to this argument.
    • setGridParam : an option used to set the values of the parameters of the grid search algorithm. setGridParameter_Mix function should be fed to it.
    • silent : a logical value specifying whether or not progressing messages should be printed. Be default, silent = FALSE and the progress of the algorithm is printed.
  • Value:

    This function returns a list of the following objects:

    • opt_parameters : the observed optimal parameters.
    • add_parameters : other aditional parameters such as scaling options, parameters of kernel functions and the optimization methods used.
    • basic_machines : the list of basic machine object.
fit_parameter_Mix <- function(train_input, 
                              train_response,
                              train_predictions = NULL,
                              machines = NULL, 
                              scale_input = TRUE,
                              splits = 0.5, 
                              n_cv = 5,
                              inv_sigma = sqrt(.5),
                              alp = 2,
                              kernels = "gaussian",
                              setBasicMachineParam = setBasicParameter_Mix(),
                              setGridParam = setGridParameter_Mix(),
                              silent = FALSE){
  kernels_lookup <- c("gaussian", "epanechnikov", "biweight", "triweight", "triangular", "naive")
  kernel_real <- kernels %>%
    sapply(FUN = function(x) return(match.arg(x, kernels_lookup)))
  if(is.null(train_predictions)){
    mach2 <- generateMachines_Mix(train_input = train_input,
                              train_response = train_response,
                              scale_input = scale_input,
                              machines = machines,
                              splits = splits,
                              basicMachineParam = setBasicMachineParam)
  }else{
    mach2 <- list(fitted_remain = train_predictions,
                  models = NULL,
                  id2 = rep(TRUE, nrow(train_input)),
                  train_data = list(train_input = train_input, 
                                    train_response = train_response,
                                    predict_remain_org = train_predictions,
                                    min_machine = NULL,
                                    max_machine = NULL,
                                    min_input = NULL,
                                    max_input = NULL))
    if(scale_input){
      min_ <- map_dbl(train_input, .f = min)
      max_ <- map_dbl(train_input, .f = max)
      mach2$train_data$min_input = min_
      mach2$train_data$max_input = max_
      mach2$train_data$train_input <- scale(train_input, 
                                            center = min_, 
                                            scale = max_ - min_)
    }
  }
  # distance matrix to compute loss function
  if_euclid <- FALSE
  id_euclid <- NULL
  n_ker <- length(kernels)
  dist_all <- list()
  id_shuf <- NULL
  out_ <- TRUE
  for (k_ in 1:n_ker){
    ker <- kernel_real[k_]
    if(ker == "naive"){
      dist_all[["naive"]] <- dist_matrix_Mix(basicMachines = mach2,
                                         n_cv = n_cv,
                                         kernel = "naive",
                                         id_shuffle = id_shuf,
                                         output = out_)
    } else{
      if(ker == "triangular"){
        dist_all[["triangular"]] <- dist_matrix_Mix(basicMachines = mach2,
                                                n_cv = n_cv,
                                                kernel = "triangular",
                                                id_shuffle = id_shuf,
                                                output = out_)
      } else{
        if(if_euclid){
          dist_all[[ker]] <- dist_all[[id_euclid]]
        } else{
          dist_all[[ker]] <- dist_matrix_Mix(basicMachines = mach2,
                                         n_cv = n_cv,
                                         kernel = ker,
                                         id_shuffle = id_shuf,
                                         output = out_)
          id_euclid <- ker
          if_euclid <- TRUE
        }
      }
    }
    id_shuf <- dist_all[[1]]$id_shuffle
    out_ <- FALSE
  }
  dist_output <- dist_all[[1]]$dist_machine
  # Kernel functions 
  # ================
  # Gaussian
  gaussian_kernel <- function(.ep,
                              .dist_matrix,
                              .train_response2,
                              .inv_sigma = inv_sigma,
                              .alpha = alp){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(exp(- (x[1]*D1+x[2]*D2)^(.alpha/2)*.inv_sigma^.alpha))
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = dist_output[[.x]]))
  return(Reduce("+", temp))
}

# Epanechnikov
  epanechnikov_kernel <- function(.ep,
                                  .dist_matrix,
                                  .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
   y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = dist_output[[.x]]))
  return(Reduce("+", temp))
  }

# Biweight
  biweight_kernel <- function(.ep,
                              .dist_matrix,
                              .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = dist_output[[.x]]))
  return(Reduce("+", temp))
  }

# Triweight
  triweight_kernel <- function(.ep,
                               .dist_matrix,
                               .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = dist_output[[.x]]))
  return(Reduce("+", temp))
  }

# Triangular
  triangular_kernel <- function(.ep,
                                .dist_matrix,
                                .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] <- 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = dist_output[[.x]]))
    return(Reduce("+", temp))
  }

  # Naive
  naive_kernel <- function(.ep,
                           .dist_matrix,
                           .train_response2){
    kern_fun <- function(x, id, D1, D2){
      tem0 <- (as.matrix((x[1]*D1+x[2]*D2)) < 1)
      y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .train_response2[.dist_matrix$id_shuffle != id],
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
      return(mean(y_hat != .train_response2[.dist_matrix$id_shuffle == id]))
    }
    temp <- map(.x = 1:.dist_matrix$n_cv, 
                .f = ~ kern_fun(x = .ep, 
                                id = .x,
                                D1 = .dist_matrix$dist_input[[.x]], 
                                D2 = dist_output[[.x]]))
    return(Reduce("+", temp))
  }
  
  # list of kernel functions
  list_funs <- list(gaussian = gaussian_kernel,
                    epanechnikov = epanechnikov_kernel,
                    biweight = biweight_kernel,
                    triweight = triweight_kernel,
                    triangular = triangular_kernel,
                    naive = naive_kernel)
  
  # error for all kernel functions
  error_func <- kernel_real %>%
    map(.f = ~ (\(x) list_funs[[.x]](.ep = x,
                                     .dist_matrix = dist_all[[.x]],
                                     .train_response2 = train_response[mach2$id2])/n_cv))
  names(error_func) <- kernel_real

  # Optimization
  parameters <- map(.x = kernel_real,
                    .f = ~ gridOptimizer_Mix(obj_fun = error_func[[.x]],
                                             setParameter = setGridParam,
                                             silent = silent))
  names(parameters) <- kernel_real
  return(list(opt_parameters = parameters,
              add_parameters = list(scale_input = scale_input,
                                    inv_sigma = inv_sigma,
                                    alp = alp),
              basic_machines = mach2))
}

Example.5: We approximate the smoothing parameter of Boston data.


train <- logical(nrow(df))
train[sample(length(train), floor(0.75*nrow(df)))] <- TRUE

param <- fit_parameter_Mix(train_input = df[train, 1:4],
                           train_response = df$Species[train],
                           machines = c("knn", "rf", "xgb"),
                           splits = .50,
                           kernels = c("gaussian", "biweight", "triangular"),
                           setBasicMachineParam = setBasicParameter_Mix(k = 2:6,
                                                                    ntree = 1:5*100,
                                                                    nrounds_xgb = 1:5*100),
                           setGridParam = setGridParameter_Mix(min_alpha = 1e-5,
                                                               max_alpha = 30,
                                                               n_alpha = 20,
                                                               min_beta = 1e-5,
                                                               n_beta = 20,
                                                               max_beta = 0.5))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1.578957, 0.02632526)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (11.05264, 1e-05)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1.578957, 0.02632526)
param$opt_parameters %>%
  map_dfc(.f = ~ .x$opt_param) %>%
  print

4 Prediction

The smoothing parameter obtained from the previous section can be used to make the final predictions.

4.1 Kernel functions

Several types of kernel functions used for the aggregation are defined in this section.

  • Argument:

    • theta : a 2D-vector of the parameter \((\alpha, \beta)\) used.
    • .y2 : the vector of response variable of the second part \(\mathcal{D}_{\ell}\) of the training data, which is used for the aggregation.
    • .distance : the distance matrix object obtained from dist_matrix function.
    • .kern : the string specifying the kernel function. By default, .kern = "gaussian".
    • .inv_sig, .alp : the parameters of exponential kernel function.
    • .meth : the string of optimization methods to be linked to the name of the kernel functions if any perticular kernels are used more than once (maybe with different optimiztaion method such as “gaussian” kernel, can be used with both “grad” and “grid” optimization methods).
  • Value:

    This function returns the predictions of the aggregation method evaluated with the given parameter.

kernel_pred_Mix <- function(theta,
                            .y2, 
                            .dist1,
                            .dist2,
                            .kern = "gaussian",
                            .inv_sig = sqrt(.5), 
                            .alp = 2){
  distD <- as.matrix(theta[1]*.dist1+theta[2]*.dist1)
  # Kernel functions
  # ================
  gaussian_kernel <- function(D,
                              .inv_sigma = .inv_sig,
                              .alpha = .alp){
    tem0 <- exp(- D^(.alpha/2)*.inv_sig^.alpha)
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .y2,
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(as.vector(y_hat))
  }

  # Epanechnikov
  epanechnikov_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .y2,
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(as.vector(y_hat))
  }
  # Biweight
  biweight_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .y2,
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(as.vector(y_hat))
  }

  # Triweight
  triweight_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .y2,
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(as.vector(y_hat))
  }

  # Triangular
  triangular_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] <- 0
    y_hat <- map_dfc(.x = 1:ncol(tem0),
                     .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .y2,
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(as.vector(y_hat))
 }
  # Naive
  naive_kernel <- function(D){
      tem0 <- (D < 1)
      y_hat <- map_dfc(.x = 1:ncol(tem0),
                       .f = (\(x_) tibble("{x_}" := tapply(tem0[, x_], 
                                                         INDEX = .y2,
                                                         FUN = sum)))) %>%
        map_chr(.f = (\(x) names(which.max(x))))
    return(as.vector(y_hat))
  }
  # Prediction
  kernel_list <- list(gaussian = gaussian_kernel,
                      epanechnikov = epanechnikov_kernel,
                      biweight = biweight_kernel,
                      triweight = triweight_kernel,
                      triangular = triangular_kernel,
                      naive = naive_kernel)
  res <- tibble(as.vector(kernel_list[[.kern]](D = distD)))
  names(res) <- .kern
  return(res)
}

4.2 Functions: predict_Mix

  • Argument:

    • fitted_models : the object obtained from fit_parameter_Mix function.
    • new_data : the testing data to be predicted.
    • new_pred : the predictions of the testing data new_data (when the basic machines are not constructed).
    • test_response : the testing response variable, it is optional. If it is given, the mean square error on the testing data is also computed. By default, test_response = NULL.
  • Value:

    This function returns a list of the following objects:

    • fitted_aggregate : the predictions by the aggregation methods.
    • fitted_machine : the predictions given by all the basic machines.
    • mse : the mean square error computed only if the test_reponse argument is note NULL.
# Prediction
predict_Mix <- function(fitted_models,
                        new_data,
                        new_pred = NULL,
                        test_response = NULL){
  opt_param <- fitted_models$opt_parameters
  add_param <- fitted_models$add_parameters
  basic_mach <- fitted_models$basic_machines
  kern0 <- names(opt_param)
  new_data_ <- new_data
  mat_input <- as.matrix(basic_mach$train_data$train_input)
  # if basic machines are built
  if(is.list(basic_mach$models)){
    if(add_param$scale_input){
      new_data_ <- scale(new_data, 
                         center = basic_mach$scale_min, 
                         scale = basic_mach$scale_max - basic_mach$scale_min)
    }
    if(is.matrix(new_data_)){
      mat_test <- new_data_
      df_test <- as_tibble(new_data_)
    } else {
      mat_test <- as.matrix(new_data_)
      df_test <- new_data_
    }
    
    # Prediction test by basic machines
    built_models <- basic_mach$models
    pred_test <- function(meth){
      if(meth == "knn"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := FNN::knn(train = mat_input[!basic_mach$id2,], 
                                                        test = mat_test, 
                                                        cl = basic_mach$train_data$train_response[!basic_mach$id2],
                                                        k = built_models[[meth]][[k]]))))
      }
      if(meth == "xgb"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(basic_mach$train_data$classes[predict(built_models[[meth]][[k]],
                                                                                               mat_test)]))))
      }
      if(meth == "adaboost"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict.boosting(built_models[[meth]][[k]], 
                                                                          as.data.frame(df_test))$class))))
      }
      if(!(meth %in% c("xgb", "knn", "adaboost"))){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict(built_models[[meth]][[k]], 
                                                                 df_test, type = 'class')))))
      }
      colnames(pre) <- names(built_models[[meth]])
      return(pre)
    }
    pred_test_all <- names(built_models) %>%
      map_dfc(.f = pred_test)
  } else{
    pred_test_all <- new_pred
  }
  # Prediction train2
  pred_train_all <- basic_mach$fitted_remain
  colnames(pred_test_all) <- colnames(pred_train_all)
  d_train <- dim(pred_train_all)
  d_test <- dim(pred_test_all)
  d_train_input <- dim(mat_input[basic_mach$id2,])
  d_test_input <- dim(new_data_)
  pred_test_mat <- as.matrix(pred_test_all)
  pred_train_mat <- as.matrix(pred_train_all)
  # Distance matrix
  dist_mat <- function(kernel = "gausian"){
    res_1 <- res_2 <- NULL
    if(!(kernel %in% c("naive", "triangular"))){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                                          d_train_input[1]), 
                                                                                                      ncol = d_train_input[2], 
                                                                                                      byrow = TRUE))^2)))))
    }
    if(kernel == "triangular"){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                                             d_train_input[1]), 
                                                                                                         ncol = d_train_input[2],
                                                                                                         byrow = TRUE)))))))
    }
    if(kernel == "naive"){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                                           d_train_input[1]),
                                                                                                       ncol = d_train_input[2], 
                                                                                                       byrow = TRUE)), 1, max)))))
    }
    return(dist_input = res_1)
  }
  dist_input <- 1:length(kern0) %>%
      map(.f = ~ dist_mat(kern0[.x]))
  dist_preds <- 1:d_test[1] %>%
      map_dfc(.f = (\(id) tibble('{{id}}' := rowSums(sweep(pred_train_mat, 2, 
                                                           pred_test_mat[id,], 
                                                           FUN = "!=")))))
  prediction <- 1:length(kern0) %>% 
    map_dfc(.f = ~ kernel_pred_Mix(theta = opt_param[[kern0[.x]]]$opt_param,
                                   .y2 = basic_mach$train_data$train_response[basic_mach$id2],
                                   .dist1 = dist_input[[.x]],
                                   .dist2 = dist_preds,
                                   .kern = kern0[.x], 
                                   .inv_sig = add_param$inv_sigma, 
                                   .alp = add_param$alp))
  if(is.null(test_response)){
    return(list(fitted_aggregate = prediction,
                fitted_machine = pred_test_all))
  } else{
    error <- cbind(pred_test_all, prediction) %>%
      dplyr::mutate(y_test = test_response) %>%
      dplyr::summarise_all(.funs = ~ (. != y_test)) %>%
      dplyr::select(-y_test) %>%
      dplyr::summarise_all(.funs = ~ mean(.))
    return(list(fitted_aggregate = prediction,
                fitted_machine = pred_test_all,
                mis_error = error,
                accuracy = 1 - error))
  }
}

Example.6 Aggregation on iris dataset.


5 Function : MixCOBRAClassifier (direct aggregation)

This function puts together all the functions above and provides the desire result of MixCobra aggregation method.

MixCOBRAClassifier <- function(train_input, 
                        train_response,
                        test_input,
                        train_predictions = NULL,
                        test_predictions = NULL,
                        test_response = NULL,
                        machines = NULL, 
                        scale_input = TRUE,
                        splits = 0.5, 
                        n_cv = 5,
                        inv_sigma = sqrt(.5),
                        alp = 2,
                        kernels = "gaussian",
                        setBasicMachineParam = setBasicParameter_Mix(),
                        setGridParam = setGridParameter_Mix(),
                        silent = FALSE){
  # build machines + tune parameter
  fit_mod <- fit_parameter_Mix(train_input = train_input, 
                               train_response = train_response,
                               train_predictions = train_predictions,
                               machines = machines, 
                               scale_input = scale_input,
                               splits = splits, 
                               n_cv = n_cv,
                               inv_sigma = inv_sigma,
                               alp = alp,
                               kernels = kernels,
                               setBasicMachineParam = setBasicMachineParam,
                               setGridParam = setGridParam,
                               silent = silent)
  # prediction
  pred <- predict_Mix(fitted_models = fit_mod,
                      new_data = test_input,
                      new_pred = test_predictions,
                      test_response = test_response)
  return(list(fitted_aggregate = pred$fitted_aggregate,
              fitted_machine = pred$fitted_machine,
              pred_train2 = fit_mod$basic_machines$predict2,
              opt_parameter = fit_mod$opt_parameters,
              mis_class = pred$mis_error,
              accuracy = pred$accuracy,
              kernels = kernels,
              ind_D2 = fit_mod$basic_machines$id2))
}

Example.7 A complete aggregation is implemented on iris data. Three types of basic machines, and three different kernel functions are used.


train <- logical(nrow(df))
train[sample(length(train), floor(0.8*nrow(df)))] <- TRUE

agg <- MixCOBRAClassifier(train_input = df[train, 1:4],
                   train_response = df$Species[train],
                   test_input = df[!train,1:4],
                   test_response = df$Species[!train],
                   n_cv = 3,
                   machines = c("knn", "rf", "xgb"),
                   splits = .5,
                   kernels = c("gaussian", 
                               "naive", 
                               "triangular"),
                   setBasicMachineParam = setBasicParameter_Mix(k = c(2,5,7,10),
                                                                ntree = 2:5*100,
                                                                nrounds_xgb = c(1,3,5)*100),
                   setGridParam = setGridParameter_Mix(parameters = list(alpha = seq(1e-10, 
                                                                                     35, 
                                                                                     length.out = 25),
                                                                     beta = c(seq(1e-10, 
                                                                                0.5, 
                                                                                length.out = 10), 
                                                                              seq(1,
                                                                                  20,
                                                                                  length.out = 10)))))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (27.70833, 1e-10)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (2.916667, 0.05555556)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1.458333, 1e-10)
agg$accuracy

References



📖 Read also KernelAggClass, KernelAggReg and MixCobraReg methods.


LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogIzFDODFBQTsnPioqTWl4Q29icmEgZm9yIGNsYXNzaWZpY2F0aW9uKiogLTwvc3Bhbj4gW0Zpc2NoZXIgYW5kIE1vdWdlb3QgKDIwMTkpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMzc4Mzc1ODE4MzAyMzQ5KSINCmF1dGhvcjogIjxzcGFuIHN0eWxlPSdjb2xvcjogI0Q0QTUxQzsnPioqKlNvdGhlYSBIYXMqKio8L3NwYW4+Ig0KZGF0ZTogIjA2LzA5LzIwMjIiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KICAgIHRvY2RlcHRoOiAyDQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgICB0b2NkZXB0aDogMg0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KLS0tDQoNCjxzdHlsZT4NCiAgLmJ0biB7DQogICAgYm9yZGVyLXdpZHRoOiAwIDBweCAwcHggMHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7DQogICAgdGV4dC10cmFuc2Zvcm06IDsNCiAgfQ0KLmJ0bi1kZWZhdWx0IHsNCiAgY29sb3I6ICMyZWNjNzE7DQogICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZmZmZjsNCiAgICBib3JkZXItY29sb3I6ICNmZmZmZmY7DQp9DQo8L3N0eWxlPg0KDQo8IS0tIENvbG9ycw0KYmx1ZSA6ICMxRkFBRTMNCnllbGxvdyA6ICNGMEFFMTQNCmdyZWVuIDogIzU0RDMxOSANCnJlZCA6ICNFNjE4MEENCi0tPg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KIyBDaGVjayBpZiBwYWNrYWdlICJmb250YXdlc29tZSIgaXMgYWxyZWFkeSBpbnN0YWxsZWQgDQoNCmxvb2t1cF9wYWNrYWdlcyA8LSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0NCmlmKCEoImZvbnRhd2Vzb21lIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoImZvbnRhd2Vzb21lIikNCmBgYA0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij4mIzEyODI3MDs8dT4gSG93IHRvIGRvd25sb2FkICYgcnVuIHRoZSBjb2Rlcz88L3U+PC9zcGFuPnstfQ0KPT09DQoNCkFsbCB0aGUgc291cmNlIGNvZGVzIG9mIHRoZSBhZ2dyZWdhdGlvbiBtZXRob2RzIGFyZSBhdmFpbGFibGUgW2hlcmUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj4gYHIgZm9udGF3ZXNvbWU6OmZhKCJnaXRodWIiKWA8L3NwYW4+XShodHRwczovL2dpdGh1Yi5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcykuIFRvIHJ1biB0aGUgY29kZXMsIHlvdSBjYW4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj5gY2xvbmVgPC9zcGFuPiB0aGUgcmVwb3NpdG9yeSBkaXJlY3RseSBvciBzaW1wbHkgbG9hZCB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj5gUiBzY3JpcHRgPC9zcGFuPiBzb3VyY2UgZmlsZSBmcm9tIHRoZSByZXBvc2l0b3J5IHVzaW5nIFtkZXZ0b29sc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RldnRvb2xzL2luZGV4Lmh0bWwpIHBhY2thZ2UgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDI4N0Q4OyI+ICoqUnN0dWRpbyoqIDwvc3Bhbj4gYXMgZm9sbG93Og0KDQoxLiBJbnN0YWxsIFtkZXZ0b29sc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RldnRvb2xzL2luZGV4Lmh0bWwpIHBhY2thZ2UgdXNpbmcgY29tbWFuZDogDQoNCiAgICBgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKWANCg0KMi4gTG9hZGluZyB0aGUgc291cmNlIGNvZGVzIGZyb20gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj5HaXRIdWIgYHIgZm9udGF3ZXNvbWU6OmZhKCJnaXRodWIiKWA8L3NwYW4+IHJlcG9zaXRvcnkgdXNpbmcgYHNvdXJjZV91cmxgIGZ1bmN0aW9uIGJ5OiANCg0KICAgIGBkZXZ0b29sczo6c291cmNlX3VybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvbWFpbi9NaXhDb2JyYVJlZy5SIilgDQoNCi0tLQ0KDQo+KiomIzk5OTg7IE5vdGUqKjogQWxsIGNvZGVzIGNvbnRhaW5lZCBpbiB0aGlzIGBSbWFya2Rvd25gIGFyZSBidWlsdCB3aXRoIHJlY2VudCB2ZXJzaW9uIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMTsiPmByIGZvbnRhd2Vzb21lOjpmYSgici1wcm9qZWN0IilgPC9zcGFuPiAodmVyc2lvbiAkPiQgNC4xLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi93aW5kb3dzL2Jhc2UvKSkgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjogIzAyODdEODsiPiAqKlJzdHVkaW8qKiA8L3NwYW4+ICh2ZXJzaW9uID4gYDIwMjIuMDIuMis0ODVgLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvI2Rvd25sb2FkKSkuIE5vdGUgYWxzbyB0aGF0IHRoZSBjb2RlIGNodWNrcyBhcmUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRTYxODBBOyI+aGlkZGVuPC9zcGFuPiBieSBkZWZhdWx0Lg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQiPiAqKlRvIHNlZSB0aGUgY29kZXMsIHlvdSBjYW46KiogPC9zcGFuPg0KDQotIGNsaWNrIG9uIHRoZSB0b3AtcmlnaHQgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNTREMzE5IDsiPmBDb2RlYDwvc3Bhbj4gYnV0dG9uIG9mIHRoZSBwYWdlLCB0aGVuIGNob29zZSAqKlNob3cgQWxsIENvZGUqKiB0byBzaG93IGFsbCB0aGUgY29kZXMsIG9yIA0KLSBzaW1wbHkgY2xpY2sgb24gdGhlIHJpZ2h0LWNvcm5lciA8c3BhbiBzdHlsZT0iY29sb3I6ICM1NEQzMTkgOyI+YENvZGVgPC9zcGFuPiBidXR0b24gYXQgZWFjaCBzZWN0aW9uIHRvIHNob3cgdGhlIGNvZGVzIG9mIHRoYXQgc3BlY2lmaWMgc2VjdGlvbi4NCg0KLS0tDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PiBNaXhDb2JyYSAmIGltcG9ydGFudCBwYWNrYWdlcyA8L3U+PC9zcGFuPg0KPT09DQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PiBNaXhDb2JyYSBtZXRob2Q8L3U+PC9zcGFuPg0KLS0tDQoNClRoaXMgYFJtYXJrZG93bmAgcHJvdmlkZXMgdGhlIGltcGxlbWVudGF0aW9uIG9mIGFuIGFnZ3JlZ2F0aW9uIG1ldGhvZCB1c2luZyBpbnB1dCBhbmQgb3V0IHRyYWRlLW9mZiBieSA8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij5bRmlzY2hlciBhbmQgTW91Z2VvdCAoMjAxOSldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAzNzgzNzU4MTgzMDIzNDkpPC9zcGFuPi4NCkxldCAkXG1hdGhjYWx7RH1fbj1ceyh4XzEseV8xKSwuLi4sKHhfbix5X24pXH0kIGJlIGEgdHJhaW5pbmcgZGF0YSBvZiBzaXplICRuJCwgd2hlcmUgdGhlIGlucHV0LW91dHB1dCBjb3VwbGVzICQoeF9pLHlfaSlcaW5cbWF0aGJie1J9XmRcdGltZXNcezEsLi4uLE5cfSQgZm9yIGFsbCAkaT0xLC4uLixuJCwgd2l0aCAkTiQgYmUgdGhlIG51bWJlciBvZiB0b3RhbCBjbGFzc2VzLiAkXG1hdGhjYWx7RH1fe259JCBpcyBmaXJzdCByYW5kb21seSBwYXJ0aXRpb25lZCBpbnRvICRcbWF0aGNhbHtEfV97a30kIGFuZCAkXG1hdGhjYWx7RH1fe1xlbGx9JCBvZiBzaXplICRrJCBhbmQgJFxlbGwkIHJlc3BlY3RpdmVseSBzdWNoIHRoYXQgJGsrXGVsbD1uJC4gV2UgY29uc3RydWN0ICRNJCBjbGFzc2lmaWVycyAobWFjaGluZXMpICAkY18xLC4uLixjX00kIHVzaW5nIG9ubHkgJFxtYXRoY2Fse0R9X3trfSQuIExldCAke1xiZiBjfSh4KT0oY18xKHgpLC4uLixjX00oeCkpXlRcaW5cezEsLi4uLE5cfV5NJCBiZSB0aGUgdmVjdG9yIG9mIHByZWRpY3RlZCBjbGFzc2VzIG9mICR4XGluXG1hdGhiYntSfV5kJCAoZ2l2ZW4gYnkgYWxsIGNsYXNzaWZpZXJzICRjXzEsLi4uLGNfTSQpLCB0aGUgcHJlZGljdGVkIGNsYXNzIG9mIHRoZSBhZ2dyZWdhdGlvbiBtZXRob2QgZXZhbHVhdGVkIGF0IHBvaW50ICR4JCBpcyBkZWZpbmVkIGJ5DQoNClxiZWdpbntlcXVhdGlvbn0NCmdfbih4KT1rXio9XHRleHR7YXJnfVxtYXhfezFcbGVxIGtcbGVxIE59XHN1bV97aT0xfV57XGVsbH1LX3tcYWxwaGEsXGJldGF9KHgteF9pLGRfe1xjYWwgSH0oe1xiZiBjfSh4KS17XGJmIGN9KHhfaSkpKVxtYXRoYmJ7MX1fe1x7eV9pPWtcfX0NClxlbmR7ZXF1YXRpb259DQoNCndoZXJlICRLOlxtYXRoYmJ7Un1ee2QrTX1cdG9cbWF0aGJie1J9XyskIGlzIGEgbm9uLWluY3JlYXNpbmcga2VybmVsIGZ1bmN0aW9uIHdpdGggJEtfe1xhbHBoYSxcYmV0YX0odSx2KT1LKFxmcmFje3V9e1xhbHBoYX0sXGZyYWN7dn17XGJldGF9KSQgZm9yIHNvbWUgc21vb3RoaW5nIHBhcmFtZXRlciAkXGFscGhhLFxiZXRhPjAkIHRvIGJlIHR1bmVkLCB3aXRoIHRoZSBjb252ZW50aW9uICQwLzA9MCQuDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+IDx1PiBJbXBvcnRhbnQgcGFja2FnZXM8L3U+PC9zcGFuPg0KLS0tDQoNCldlIHByZXBhcmUgYWxsIHRoZSBuZWNlc3NhcnkgdG9vbHMgZm9yIHRoaXMgYFJtYXJrZG93bmAuIGBwYWNtYW5gIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGxvYWQgKGlmIGV4aXN0cykgb3IgaW5zdGFsbCAoaWYgZG9lcyBub3QgZXhpc3QpIGFueSBhdmFpbGFibGUgcGFja2FnZXMgZnJvbSBbVGhlIENvbXByZWhlbnNpdmUgUiBBcmNoaXZlIE5ldHdvcmsgKENSQU4pXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+YHIgZm9udGF3ZXNvbWU6OmZhKCJyLXByb2plY3QiKWA8L3NwYW4+LiANCg0KDQpgYGB7cn0NCiMgQ2hlY2sgaWYgcGFja2FnZSAicGFjbWFuIiBpcyBhbHJlYWR5IGluc3RhbGxlZCANCg0KbG9va3VwX3BhY2thZ2VzIDwtIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXQ0KaWYoISgicGFjbWFuIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQoNCg0KIyBUbyBiZSBpbnN0YWxsZWQgb3IgbG9hZGVkDQpwYWNtYW46OnBfbG9hZChtYWdyaXR0cikNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSkNCg0KIyMgcGFja2FnZSBmb3IgImdlbmVyYXRlTWFjaGluZXMiDQpwYWNtYW46OnBfbG9hZCh0cmVlKQ0KcGFjbWFuOjpwX2xvYWQobm5ldCkNCnBhY21hbjo6cF9sb2FkKGUxMDcxKQ0KcGFjbWFuOjpwX2xvYWQocmFuZG9tRm9yZXN0KQ0KcGFjbWFuOjpwX2xvYWQoRk5OKQ0KcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCnBhY21hbjo6cF9sb2FkKGFkYWJhZykNCnBhY21hbjo6cF9sb2FkKGtlcmFzKQ0KcGFjbWFuOjpwX2xvYWQocHJhY21hKQ0KcGFjbWFuOjpwX2xvYWQobGF0ZXgyZXhwKQ0KcGFjbWFuOjpwX2xvYWQocGxvdGx5KQ0Kcm0obG9va3VwX3BhY2thZ2VzKQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+QmFzaWMgbWFjaGluZSBnZW5lcmF0b3I8L3U+PC9zcGFuPg0KPT09DQoNClRoaXMgc2VjdGlvbiBwcm92aWRlcyBmdW5jdGlvbnMgdG8gZ2VuZXJhdGUgYmFzaWMgbWFjaGluZXMgKGNsYXNzaWZpZXJzKSB0byBiZSBhZ2dyZWdhdGVkLg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYHNldEJhc2ljUGFyYW1ldGVyX01peGANCi0tLS0NCg0KVGhpcyBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gc2V0IHRoZSB2YWx1ZXMgb2Ygc29tZSBrZXkgcGFyYW1ldGVycyBvZiB0aGUgYmFzaWMgbWFjaGluZXMuDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBga2AgOiB0aGUgcGFyYW1ldGVyICRrJCBvZiAkayROTiAoYGtubmApIGNsYXNzaWZpZXJzIGFuZCB0aGUgZGVmYXVsdCB2YWx1ZSBpcyAkaz0xMCQuDQogICAgLSBgbnRyZWVgIDogdGhlIG51bWJlciBvZiB0cmVlcyBpbiByYW5kb20gZm9yZXN0IChgcmZgKS4gQnkgZGVmYXVsdCwgYG50cmVlID0gMzAwYC4NCiAgICAtIGBtdHJ5YCA6IHRoZSBudW1iZXIgb2YgcmFuZG9tIGZlYXR1cmVzIGNob3NlbiBpbiBlYWNoIHNwbGl0IG9mIHJhbmRvbSBmb3Jlc3QgcHJvY2VkdXJlLiBCeSBkZWZhdWx0LCBgbXRyeSA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB2YWx1ZSBvZiBgbXRyeWAgb2YgKnJhbmRvbUZvcmVzdCogZnVuY3Rpb24gZnJvbSBbcmFuZG9tRm9yZXN0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmFuZG9tRm9yZXN0L2luZGV4Lmh0bWwpIGxpYnJhcnkgaXMgdXNlZC4NCiAgICAtIGBrZXJfc3ZtYCA6IGtlcm5lbCBvcHRpb24gaW4gU1ZNLiBJdCBzaG91bGQgYmUgYSBzdWJzZXQgb2YgYHsibGluZWFyIiwgInBvbHlub21pYWwiLCAicmFkaWFsIiwgInNpZ21vaWQifWAuIEJ5IGRlZmF1bHQsIGBrZXJfc3ZtID0gInJhZGlhbCJgLg0KICAgIC0gYGRlZ19zdm1gIDogZGVncmVlIG9mIHBvbHlub21pYWwga2VybmVsIGluIFNWTS4gQnkgZGVmYXVsdCwgYGRlZ19zdm0gPSAzYC4NCiAgICAtIGBicmVnX2Jvb3N0YCA6IEJyZWdtYW4gZGl2ZXJnZW5jZSB1c2VkIGluIGBib29zdGluZ2Agb2YgW21hYm9vc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9tYWJvb3N0L2luZGV4Lmh0bWwpIHBhY2thZ2UuIEJ5IGRlZmF1bHQsIGBicmVnX2Jvb3N0ID0gImVudHJvcCJgIGFuZCBLTCBkaXZlcmdlbmNlIGlzIHVzZWQsIHJlc3VsdGluZyBBZGFib29zdC1saWtlIGFsZ29yaXRobS4NCiAgICAtIGBpdGVyX2Jvb3N0YCA6IG51bWJlciBvZiBib29zdGluZyBpdGVyYXRpb25zIHRvIHBlcmZvcm0uIERlZmF1bHQgYGl0ZXJfYm9vc3QgPSAxMDBgLg0KICAgIC0gYGV0YV94Z2JgIDogdGhlIGxlYXJuaW5nIHJhdGUgJFxldGE+MCQgaW4gZ3JhZGllbnQgc3RlcCBvZiAqZXh0cmVtZSBncmFkaWVudCBib29zdGluZyogbWV0aG9kIChgeGdiYCkgb2YgW3hnYm9vc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy94Z2Jvb3N0L2luZGV4Lmh0bWwpIGxpYnJhcnkuDQogICAgLSBgbnJvdW5kc194Z2JgIDogdGhlIHBhcmFtZXRlciBgbnJvdW5kc2AgaW5kaWNhdGluZyB0aGUgbWF4IG51bWJlciBvZiBib29zdGluZyBpdGVyYXRpb25zLiBCeSBkZWZhdWx0LCBgbnJvdW5kc194Z2IgPSAxMDBgLg0KICAgIC0gYGVhcmx5X3N0b3BfeGdiYCA6IHRoZSBlYXJseSBzdG9wcGluZyByb3VuZCBjcml0ZXJpb24gb2YgYHhnYm9vc3RgIGZ1bmN0aW9uLiBCeSwgZGVmYXVsdCwgYGVhcmx5X3N0b3BfeGdiID0gTlVMTGAgYW5kIHRoZSBlYXJseSBzdG9wcGluZyBmdW5jdGlvbiBpcyBub3QgdHJpZ2dlcmVkLg0KICAgIC0gYG1heF9kZXB0aF94Z2JgIDogbWF4aW11bSBkZXB0aCBvZiB0cmVlcyBjb25zdHJ1Y3RlZCBpbiBgeGdib29zdGAuIA0KICAgIC0gYHBhcmFtX3hnYmAgOiBsaXN0IG9mIGFkZGl0aW9uYWwgcGFyYW1ldGVycyBvZiBgeGdib29zdGAgY2xhc3NpZmllci4gQnkgZGVmYXVsdCwgYHBhcmFtX3hnYiA9IE5VTExgLiBGb3IgbW9yZSBpbmZvcm1hdGlvbiwgcmVhZCBbb25saW5lIGRvY3VtZW50YXRpb25dKGh0dHBzOi8veGdib29zdC5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvcGFyYW1ldGVyLmh0bWwpLg0KDQotICoqVmFsdWUqKjogDQogICAgDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIGFsbCB0aGUgcGFyYW1ldGVycyBnaXZlbiBpbiBpdHMgYXJndW1lbnRzLCB0byBiZSBmZWQgdG8gdGhlIGBiYXNpY01hY2hpbmVQYXJhbWAgYXJndW1lbnQgb2YgZnVuY3Rpb24gYGdlbmVyYXRlTWFjaGluZXNgIGRlZmluZWQgaW4gdGhlIG5leHQgc2VjdGlvbi4NCg0KLS0tDQoNCj4g8J+nviAqKiBSZW1hcmsuMSoqOiANCmBrYCwgYG50cmVlYCwgYGl0ZXJfYm9vc3RgIGFuZCBgbnJvdW5kc194Z2JgIGNhbiBiZSBhIHNpbmdsZSB2YWx1ZSBvciBhIHZlY3Rvci4gSW4gb3RoZXIgd29yZHMsIGVhY2ggdHlwZSBvZiBtb2RlbHMgY2FuIGJlIGNvbnN0cnVjdGVkIHNldmVyYWwgdGltZXMgYWNjb3JkaW5nIHRvIHRoZSB2YWx1ZXMgb2YgdGhlIGh5cGVycGFyYW1ldGVycyAoYSBzaW5nbGUgdmFsdWUgb3IgdmVjdG9yKS4NCg0KLS0tDQoNCmBgYHtyfQ0Kc2V0QmFzaWNQYXJhbWV0ZXJfTWl4IDwtIGZ1bmN0aW9uKGsgPSAxMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMzAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJfc3ZtID0gInJhZGlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWdfc3ZtID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1maW5hbF9ib29zdCA9IDUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RyYXAgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhX3hnYiA9IDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kc194Z2IgPSAxMDAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcF94Z2IgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoX3hnYiA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbV94Z2IgPSBOVUxMKXsNCiAgcmV0dXJuKGxpc3QoDQogICAgayA9IGssDQogICAgbnRyZWUgPSBudHJlZSwgDQogICAgbXRyeSA9IG10cnksIA0KICAgIGtlcl9zdm0gPSBrZXJfc3ZtLA0KICAgIGRlZ19zdm0gPSBkZWdfc3ZtLA0KICAgIG1maW5hbF9ib29zdCA9IG1maW5hbF9ib29zdCwNCiAgICBib29zdHJhcCA9IGJvb3N0cmFwLA0KICAgIGV0YV94Z2IgPSBldGFfeGdiLCANCiAgICBucm91bmRzX3hnYiA9IG5yb3VuZHNfeGdiLCANCiAgICBlYXJseV9zdG9wX3hnYiA9IGVhcmx5X3N0b3BfeGdiLA0KICAgIG1heF9kZXB0aF94Z2IgPSBtYXhfZGVwdGhfeGdiLA0KICAgIHBhcmFtX3hnYiA9IHBhcmFtX3hnYikNCiAgKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBnZW5lcmF0ZU1hY2hpbmVzX01peGANCi0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGdlbmVyYXRlcyBhbGwgdGhlIGJhc2ljIG1hY2hpbmVzIHRvIGJlIGFnZ3JlZ2F0ZWQuIA0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHRyYWluX2lucHV0YCA6IGEgbWF0cml4IG9yIGRhdGEgZnJhbWUgb2YgdGhlIHRyYWluaW5nIGlucHV0IGRhdGEuDQogICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogYSB2ZWN0b3Igb2YgdHJhaW5pbmcgcmVzcG9uc2UgdmFyaWFibGUgY29ycmVzcG9uZGluZyB0byB0aGUgYHRyYWluX2lucHV0YC4NCiAgICAtIGBzY2FsZV9pbnB1dGAgOiBsb2dpY2FsIHZhbHVlIHNwZWNpZnlpbmcgd2hldGhlciB0byBzY2FsZSB0aGUgaW5wdXQgZGF0YSAodG8gYmUgYmV0d2VlbiAkMCQgYW5kICQxJCkgb3Igbm90LiBCeSBkZWZhdWx0LCBgc2NhbGVfaW5wdXQgPSBGQUxTRWAuDQogICAgLSBgbWFjaGluZXNgIDogdHlwZXMgb2YgYmFzaWMgbWFjaGluZXMgdG8gYmUgY29uc3RydWN0ZWQuIEl0IGlzIGEgc3Vic2V0IG9mIHtgImtubiJgLCBgInRyZWUiYCwgYCJyZiJgLCBgImxvZ2l0ImAsIGAic3ZtImAsIGAieGdiImAsIGAiYWRhYm9vc3QiYH0uIEJ5IGRlZmF1bHQsIGBtYWNoaW5lcyA9IE5VTExgIGFuZCBhbGwgdHlwZXMgb2YgdGhlIGJhc2ljIG1hY2hpbmVzIGFyZSBidWlsdC4NCiAgICAtIGBzcGxpdHNgIDogcmVhbCBudW1iZXIgYmV0d2VlbiAkMCQgYW5kICQxJCBzcGVjaWZ5aW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIGRhdGEgdXNlZCB0byB0cmFpbiB0aGUgYmFzaWMgbWFjaGluZXMgKCRcbWF0aGNhbHtEfV9rJCkuIFRoZSByZW1haW5pbmcgcHJvcG9ydGlvbiBvZiAoJDEtJCBgc3BsaXRzYCkgaXMgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkuIEJ5IGRlZmF1bHQsIGBzcGxpdHMgPSAwLjVgLg0KICAgIC0gYGJhc2ljTWFjaGluZVBhcmFtYCA6IHRoZSBvcHRpb24gdXNlZCB0byBzZXR1cCB0aGUgdmFsdWVzIG9mIHBhcmFtZXRlcnMgb2YgZWFjaCBtYWNoaW5lcy4gT25lIHNob3VsZCBmZWVkIHRoZSBmdW5jdGlvbiBgc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KClgIGRlZmluZWQgYWJvdmUgdG8gdGhpcyBhcmd1bWVudC4NCiAgICAtIGBzaWxlbnRgIDogYSBsb2dpY2FsIHZhbHVlIHNwZWNpZnlpbmcgd2hldGhlciBvciBub3QgcHJvZ3Jlc3NpbmcgbWVzc2FnZXMgc2hvdWxkIGJlIHByaW50ZWQuIEJlIGRlZmF1bHQsIGBzaWxlbnQgPSBGQUxTRWAgYW5kIHRoZSBwcm9ncmVzcyBvZiB0aGUgYWxnb3JpdGhtIGlzIHByaW50ZWQuDQogICAgDQogICAgDQotICoqVmFsdWUqKjogDQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0cy4NCg0KICAgIC0gYGZpdHRlZF9yZW1haW5gIDogdGhlIHByZWRpY3Rpb25zIG9mIHRoZSByZW1haW5pbmcgcGFydCAoJFxtYXRoY2Fse0R9X3tcZWxsfSQpIG9mIHRoZSB0cmFpbmluZyBkYXRhIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbi4NCiAgICAtIGBtb2RlbHNgIDogYWxsIHRoZSBjb25zdHJ1Y3RlZCBiYXNpYyBtYWNoaW5lcyAoaXQgY29udGFpbnMgb25seSB0aGUgdmFsdWVzIG9mIHByYXBldGVyICRrJCBmb3IgYGtubmApLg0KICAgIC0gYGlkMmAgOiBhIGxvZ2ljYWwgdmVjdG9yIG9mIHNpemUgZXF1YWxzIHRvIHRoZSBudW1iZXIgb2YgbGluZXMgb2YgdGhlIHRyYWluaW5nIGRhdGEgaW5kaWNhdGluZyB0aGUgbG9jYXRpb24gb2YgdGhlIHBvaW50cyB1c2VkIHRvIGJ1aWxkIHRoZSBiYXNpYyBtYWNoaW5lcyAoYEZBTFNFYCkgYW5kIHRoZSByZW1haW5pbmcgb25lcyAoYFRSVUVgKS4NCiAgICAtIGB0cmFpbl9kYXRhYCA6IGEgbGlzdCBvZjoNCiAgICAgICAgLSBgdHJhaW5faW5wdXRgIDogdGhlIHRyYWlubmlnIGlucHV0IGRhdGEuDQogICAgICAgIC0gYHRyYWluX3Jlc3BvbnNlYCA6IHRoZSB0cmFpbmluZyByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAgICAgLSBgY2xhc3Nlc2AgOiB0aGUgY2xhc3NlcyAodW5pcXVlKSBvZiByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAtIGBzY2FsZV9tYXhgLCBgc2NhbGVfbWluYCA6IGlmIHRoZSBhcmd1bWVudCBgc2NhbGVfaW5wdXQgPSBUUlVFYCwgdGhlIG1heGltdW4gYW5kIG1pbmltdW4gdmFsdWVzIG9mIGFsbCBjb2x1bW5zIGFyZSBjb250YWluZWQgaW4gdGhlc2UgdmVjdG9ycy4gDQoNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCi0tLQ0KDQo+ICoqJiM5OTk4OyBOb3RlKio6ICpZb3UgbWF5IG5lZWQgdG8gbW9kaWZ5IHRoZSBmdW5jdGlvbiBhY2NvcmRpbmdseSBpZiB5b3Ugd2FudCB0byBidWlsZCBkaWZmZXJlbnQgdHlwZXMgb2YgYmFzaWMgbWFjaGluZXMqLg0KDQotLS0NCg0KYGBge3J9DQpnZW5lcmF0ZU1hY2hpbmVzX01peCA8LSBmdW5jdGlvbih0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IDAuNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhc2ljTWFjaGluZVBhcmFtID0gc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpbGVudCA9IEZBTFNFKXsNCiAgayA8LSBiYXNpY01hY2hpbmVQYXJhbSRrIA0KICBudHJlZSA8LSBiYXNpY01hY2hpbmVQYXJhbSRudHJlZSANCiAgbXRyeSA8LSBiYXNpY01hY2hpbmVQYXJhbSRtdHJ5DQogIGtlcl9zdm0gPC0gYmFzaWNNYWNoaW5lUGFyYW0ka2VyX3N2bQ0KICBkZWdfc3ZtIDwtIGJhc2ljTWFjaGluZVBhcmFtJGRlZ19zdm0NCiAgbWZpbmFsX2Jvb3N0ID0gYmFzaWNNYWNoaW5lUGFyYW0kbWZpbmFsX2Jvb3N0DQogIGJvb3N0cmFwID0gYmFzaWNNYWNoaW5lUGFyYW0kYm9vc3RyYXANCiAgZXRhX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRldGFfeGdiIA0KICBucm91bmRzX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRucm91bmRzX3hnYg0KICBlYXJseV9zdG9wX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRlYXJseV9zdG9wX3hnYg0KICBtYXhfZGVwdGhfeGdiIDwtIGJhc2ljTWFjaGluZVBhcmFtJG1heF9kZXB0aF94Z2INCiAgcGFyYW1feGdiIDwtIGJhc2ljTWFjaGluZVBhcmFtJHBhcmFtX3hnYg0KICBjbGFzc194Z2IgPC0gdW5pcXVlKHRyYWluX3Jlc3BvbnNlKQ0KICBudW1iZXJPZkNsYXNzZXMgPC0gbGVuZ3RoKGNsYXNzX3hnYikNCiAgaWYoaXMubnVsbChwYXJhbV94Z2IpKXsNCiAgICBwYXJhbV94Z2IgPC0gbGlzdCgib2JqZWN0aXZlIiA9ICJtdWx0aTpzb2Z0bWF4IiwNCiAgICAgICAgICAgICAgICAgICAgICAiZXZhbF9tZXRyaWMiID0gIm1sb2dsb3NzIiwNCiAgICAgICAgICAgICAgICAgICAgICAibnVtX2NsYXNzIiA9IG51bWJlck9mQ2xhc3NlcysxKQ0KICB9DQogIA0KICAjIFBhY2thZ2VzDQogIHBhY21hbjo6cF9sb2FkKG5uZXQpDQogIHBhY21hbjo6cF9sb2FkKGUxMDcxKQ0KICBwYWNtYW46OnBfbG9hZCh0cmVlKQ0KICBwYWNtYW46OnBfbG9hZChyYW5kb21Gb3Jlc3QpDQogIHBhY21hbjo6cF9sb2FkKEZOTikNCiAgcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCiAgcGFjbWFuOjpwX2xvYWQobWFib29zdCkNCiAgDQogICMgUHJlcGFyaW5nIGRhdGENCiAgaW5wdXRfbmFtZXMgPC0gY29sbmFtZXModHJhaW5faW5wdXQpDQogIGlucHV0X3NpemUgPC0gZGltKHRyYWluX2lucHV0KQ0KICBkZl9pbnB1dCA8LSB0cmFpbl9pbnB1dF9zY2FsZSA8LSB0cmFpbl9pbnB1dA0KICBpZihzY2FsZV9pbnB1dCl7DQogICAgbWF4cyA8LSBtYXBfZGJsKC54ID0gZGZfaW5wdXQsIC5mID0gbWF4KQ0KICAgIG1pbnMgPC0gbWFwX2RibCgueCA9IGRmX2lucHV0LCAuZiA9IG1pbikNCiAgICB0cmFpbl9pbnB1dF9zY2FsZSA8LSBzY2FsZSh0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0gbWlucywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSBtYXhzIC0gbWlucykNCiAgfQ0KICBpZihpcy5tYXRyaXgodHJhaW5faW5wdXRfc2NhbGUpKXsNCiAgICBkZl9pbnB1dCA8LSBhc190aWJibGUodHJhaW5faW5wdXRfc2NhbGUpDQogICAgbWF0cml4X2lucHV0IDwtIHRyYWluX2lucHV0X3NjYWxlDQogIH0gZWxzZXsNCiAgICBkZl9pbnB1dCA8LSB0cmFpbl9pbnB1dF9zY2FsZQ0KICAgIG1hdHJpeF9pbnB1dCA8LSBhcy5tYXRyaXgodHJhaW5faW5wdXRfc2NhbGUpDQogIH0NCiAgDQogICMgTWFjaGluZXMNCiAgc3ZtX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgcGEgPSBOVUxMKXsNCiAgICBtb2QgPC0gc3ZtKHggPSBkZl90cmFpbl94MSwgDQogICAgICAgICAgICAgICB5ID0gdHJhaW5feTEsDQogICAgICAgICAgICAgICBrZXJuZWwgPSBrZXJfc3ZtLA0KICAgICAgICAgICAgICAgZGVncmVlID0gZGVnX3N2bSwNCiAgICAgICAgICAgICAgIHR5cGUgPSAiQy1jbGFzc2lmaWNhdGlvbiIpDQogICAgcmVzIDwtIHByZWRpY3QobW9kLCANCiAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0geCkNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICB0cmVlX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgcGEgPSBOVUxMKSB7DQogICAgbW9kIDwtIHRyZWUoYXMuZm9ybXVsYShwYXN0ZSgidHJhaW5feTF+IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShpbnB1dF9uYW1lcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIrIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikpLCANCiAgICAgICAgICAgICAgICBkYXRhID0gZGZfdHJhaW5feDEpDQogICAgcmVzIDwtIHByZWRpY3QobW9kLCB4LCB0eXBlID0gJ2NsYXNzJykNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICBrbm5fbWFjaGluZSA8LSBmdW5jdGlvbih4LCBrMCkgew0KICAgIG1vZCA8LSBrbm4odHJhaW4gPSBtYXRyaXhfdHJhaW5feDEsIA0KICAgICAgICAgICAgICAgICAgIHRlc3QgPSB4LCANCiAgICAgICAgICAgICAgICAgICBjbCA9IHRyYWluX3kxLCANCiAgICAgICAgICAgICAgICAgICBrID0gazApDQogICAgcmV0dXJuKGxpc3QocHJlZCA9IG1vZCwNCiAgICAgICAgICAgICAgICBtb2RlbCA9IGswKSkNCiAgfQ0KICBSRl9tYWNoaW5lIDwtIGZ1bmN0aW9uKHgsIG50cmVlMCkgew0KICAgIGlmKGlzLm51bGwobXRyeSkpew0KICAgICAgbW9kIDwtIHJhbmRvbUZvcmVzdCh4ID0gZGZfdHJhaW5feDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gdHJhaW5feTEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IG50cmVlMCkNCiAgICB9ZWxzZXsNCiAgICAgIG1vZCA8LSByYW5kb21Gb3Jlc3QoeCA9IGRmX3RyYWluX3gxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHRyYWluX3kxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSBudHJlZTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gbXRyeSkNCiAgICB9DQogICAgcmVzIDwtIGFzLnZlY3RvcihwcmVkaWN0KG1vZCwgeCkpDQogICAgcmV0dXJuKGxpc3QocHJlZCA9IHJlcywNCiAgICAgICAgICAgICAgICBtb2RlbCA9IG1vZCkpDQogIH0NCiAgeGdiX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgbnJvdW5kc194Z2IwKXsNCiAgICBtb2QgPC0geGdib29zdChkYXRhID0gbWF0cml4X3RyYWluX3gxLA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdHJhaW5feTEsDQogICAgICAgICAgICAgICAgICAgcGFyYW1zID0gcGFyYW1feGdiLA0KICAgICAgICAgICAgICAgICAgIGV0YSA9IGV0YV94Z2IsDQogICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gZWFybHlfc3RvcF94Z2IsDQogICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gbWF4X2RlcHRoX3hnYiwNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gMCwNCiAgICAgICAgICAgICAgICAgICBucm91bmRzID0gbnJvdW5kc194Z2IwKQ0KICAgIHJlcyA8LSBjbGFzc194Z2JbcHJlZGljdChtb2QsIHgpXQ0KICAgIHJldHVybihsaXN0KHByZWQgPSByZXMsDQogICAgICAgICAgICAgICAgbW9kZWwgPSBtb2QpKQ0KICB9DQogIGFkYV9tYWNoaW5lIDwtIGZ1bmN0aW9uKHgsIG1maW5hbDApew0KICAgIGRhdGFfdGVtIDwtIGNiaW5kKGRmX3RyYWluX3gxLCAidGFyZ2V0IiA9IHRyYWluX3kxKQ0KICAgIG1vZF8gPC0gYm9vc3RpbmcodGFyZ2V0IH4gLiwgDQogICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YV90ZW0sDQogICAgICAgICAgICAgICAgICAgICBtZmluYWwgPSBtZmluYWwwLA0KICAgICAgICAgICAgICAgICAgICAgYm9vcyA9IGJvb3N0cmFwKQ0KICAgIHJlcyA8LSBwcmVkaWN0LmJvb3N0aW5nKG1vZF8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBhcy5kYXRhLmZyYW1lKHgpKQ0KICAgIHJldHVybihsaXN0KHByZWQgPSByZXMkY2xhc3MsDQogICAgICAgICAgICAgICAgbW9kZWwgPSBtb2RfKSkNCiAgfQ0KICBsb2dpdF9tYWNoaW5lIDwtIGZ1bmN0aW9uKHgsIHBhID0gTlVMTCl7DQogICAgbW9kIDwtIG11bHRpbm9tKGFzLmZvcm11bGEocGFzdGUoInRyYWluX3kxfiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoaW5wdXRfbmFtZXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiKyIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIpKSwgDQogICAgICAgICAgICAgICAgZGF0YSA9IGRmX3RyYWluX3gxLA0KICAgICAgICAgICAgICAgIHRyYWNlID0gRkFMU0UpDQogICAgcmVzIDwtIHByZWRpY3QobW9kLCANCiAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0geCkNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICAjIEFsbCBtYWNoaW5lcw0KICBhbGxfbWFjaGluZXMgPC0gbGlzdChrbm4gPSBrbm5fbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIHRyZWUgPSB0cmVlX21hY2hpbmUsIA0KICAgICAgICAgICAgICAgICAgICAgICByZiA9IFJGX21hY2hpbmUsDQogICAgICAgICAgICAgICAgICAgICAgIGxvZ2l0ID0gbG9naXRfbWFjaGluZSwNCiAgICAgICAgICAgICAgICAgICAgICAgc3ZtID0gc3ZtX21hY2hpbmUsDQogICAgICAgICAgICAgICAgICAgICAgIGFkYWJvb3N0ID0gYWRhX21hY2hpbmUsDQogICAgICAgICAgICAgICAgICAgICAgIHhnYiA9IHhnYl9tYWNoaW5lKQ0KICAjIEFsbCBwYXJhbWV0ZXJzDQogIGFsbF9wYXJhbWV0ZXJzIDwtIGxpc3Qoa25uID0gaywgDQogICAgICAgICAgICAgICAgICAgICAgICAgdHJlZSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgcmYgPSBudHJlZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBsb2dpdCA9IE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHN2bSA9IGRlZ19zdm0sDQogICAgICAgICAgICAgICAgICAgICAgICAgYWRhYm9vc3QgPSBtZmluYWxfYm9vc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgeGdiID0gbnJvdW5kc194Z2IpDQogIGxvb2t1cF9tYWNoaW5lcyA8LSBjKCJrbm4iLCAidHJlZSIsICJyZiIsICJsb2dpdCIsICJzdm0iLCAieGdiIiwgImFkYWJvb3N0IikNCiAgaWYoaXMubnVsbChtYWNoaW5lcykpew0KICAgIG1hY2ggPC0gbG9va3VwX21hY2hpbmVzDQogIH1lbHNlew0KICAgIG1hY2ggPC0gbWFwX2NocigueCA9IG1hY2hpbmVzLA0KICAgICAgICAgICAgICAgICAgICAuZiA9IH4gbWF0Y2guYXJnKC54LCBsb29rdXBfbWFjaGluZXMpKQ0KICB9DQogICMgRXh0cmFjdGluZyBkYXRhDQogIE0gPC0gbGVuZ3RoKG1hY2gpDQogIHNpemVfRDEgPC0gZmxvb3Ioc3BsaXRzKmlucHV0X3NpemVbMV0pDQogIGlkX0QxIDwtIGxvZ2ljYWwoaW5wdXRfc2l6ZVsxXSkNCiAgaWRfRDFbc2FtcGxlKGlucHV0X3NpemVbMV0sIHNpemVfRDEpXSA8LSBUUlVFDQoNCiAgZGZfdHJhaW5feDEgPC0gZGZfaW5wdXRbaWRfRDEsXQ0KICBtYXRyaXhfdHJhaW5feDEgPC0gbWF0cml4X2lucHV0W2lkX0QxLF0NCiAgdHJhaW5feTEgPC0gdHJhaW5fcmVzcG9uc2VbaWRfRDFdDQogIGRmX3RyYWluX3gyIDwtIGRmX2lucHV0WyFpZF9EMSxdDQogIG1hdHJpeF90cmFpbl94MiA8LSBtYXRyaXhfaW5wdXRbIWlkX0QxLF0NCiAgDQogICMgRnVuY3Rpb24gdG8gZXh0cmFjdCBkZiBhbmQgbW9kZWwgZnJvbSAnbWFwJyBmdW5jdGlvbg0KICBleHRyX2RmIDwtIGZ1bmN0aW9uKHgsIG5hbSwgaWQpew0KICAgIHJldHVybih0aWJibGUoIntuYW19X3tpZH0iIDo9IGFzLnZlY3RvcihwcmVkX21bW3hdXSRwcmVkKSkpDQogIH0NCiAgZXh0cl9tb2QgPC0gZnVuY3Rpb24oeCwgaWQpew0KICAgIHJldHVybihwcmVkX21bW3hdXSRtb2RlbCkNCiAgfQ0KICANCiAgcHJlZF9EMiA8LSBjKCkNCiAgYWxsX21vZCA8LSBjKCkNCiAgaWYoIXNpbGVudCl7DQogICAgY2F0KCJcbiogQnVpbGRpbmcgYmFzaWMgbWFjaGluZXMgLi4uXG4iKQ0KICAgIGNhdCgiXHR+IFByb2dyZXNzOiIpDQogIH0NCiAgZm9yKG0gaW4gMTpNKXsNCiAgICBpZihtYWNoW21dICVpbiUgYygia25uIiwgInhnYiIpKXsNCiAgICAgIHgwX3Rlc3QgPC0gIG1hdHJpeF90cmFpbl94Mg0KICAgIH0gZWxzZSB7DQogICAgICB4MF90ZXN0IDwtIGRmX3RyYWluX3gyDQogICAgfQ0KICAgIGlmKGlzLm51bGwoYWxsX3BhcmFtZXRlcnNbW21hY2hbbV1dXSkpew0KICAgICAgcGFyYV8gPC0gMQ0KICAgIH1lbHNlew0KICAgICAgcGFyYV8gPC0gYWxsX3BhcmFtZXRlcnNbW21hY2hbbV1dXQ0KICAgIH0NCiAgICBwcmVkX20gPC0gIG1hcChwYXJhXywNCiAgICAgICAgICAgICAgICAgICAuZiA9IH4gYWxsX21hY2hpbmVzW1ttYWNoW21dXV0oeDBfdGVzdCwgLngpKQ0KICAgIHRlbTAgPC0gaW1hcF9kZmMoLnggPSAxOmxlbmd0aChwYXJhXyksIA0KICAgICAgICAgICAgICAgICAgICAgLmYgPSB+IGV4dHJfZGYoeCA9IC54LCBuYW0gPSBtYWNoW21dLCBpZCA9IHBhcmFfWy54XSkpDQogICAgdGVtMSA8LSBtYXAoLnggPSAxOmxlbmd0aChwYXJhXyksIA0KICAgICAgICAgICAgICAgICAuZiA9IGV4dHJfbW9kKQ0KICAgIG5hbWVzKHRlbTApIDwtIG5hbWVzKHRlbTEpIDwtIHBhc3RlMChtYWNoW21dLCAxOmxlbmd0aChwYXJhXykpDQogICAgcHJlZF9EMiA8LSBiaW5kX2NvbHMocHJlZF9EMiwgYXNfdGliYmxlKHRlbTApKQ0KICAgIGFsbF9tb2RbW21hY2hbbV1dXSA8LSB0ZW0xDQogICAgaWYoIXNpbGVudCl7DQogICAgICBjYXQoIiAuLi4gIiwgcm91bmQobS9NLCAyKSoxMDBMLCIlIiwgc2VwID0gIiIpDQogICAgfQ0KICB9DQogIGlmKHNjYWxlX2lucHV0KXsNCiAgICByZXR1cm4obGlzdChmaXR0ZWRfcmVtYWluID0gcHJlZF9EMiwNCiAgICAgICAgICAgICAgICBtb2RlbHMgPSBhbGxfbW9kLA0KICAgICAgICAgICAgICAgIGlkMiA9ICFpZF9EMSwNCiAgICAgICAgICAgICAgICB0cmFpbl9kYXRhID0gbGlzdCh0cmFpbl9pbnB1dCA9IHRyYWluX2lucHV0X3NjYWxlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzZXMgPSBjbGFzc194Z2IpLA0KICAgICAgICAgICAgICAgIHNjYWxlX21heCA9IG1heHMsDQogICAgICAgICAgICAgICAgc2NhbGVfbWluID0gbWlucykpDQogIH0gZWxzZXsNCiAgICByZXR1cm4obGlzdChmaXR0ZWRfcmVtYWluID0gcHJlZF9EMiwNCiAgICAgICAgICAgICAgICBtb2RlbHMgPSBhbGxfbW9kLA0KICAgICAgICAgICAgICAgIGlkMiA9ICFpZF9EMSwNCiAgICAgICAgICAgICAgICB0cmFpbl9kYXRhID0gbGlzdCh0cmFpbl9pbnB1dCA9IHRyYWluX2lucHV0X3NjYWxlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzZXMgPSBjbGFzc194Z2IpKSkNCiAgfQ0KfQ0KYGBgDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS4xKio6IEluIHRoaXMgZXhhbXBsZSwgdGhlIG1ldGhvZCBpcyBpbXBsZW1lbnRlZCBvbiBgaXJpc2AgZGF0YXNldC4gQWxsIHR5cGVzIG9mIGJhc2ljIG1hY2hpbmVzIGFyZSBidWlsdCBvbiB0aGUgZmlyc3QgcGFydCBvZiB0aGUgdHJhaW5pbmcgZGF0YSAoJFxtYXRoY2Fse0R9X3trfSQpLCBhbmQgdGhlICphY2N1cmFjeSogZXZhbHVhdGVkIG9uIHRoZSBzZWNvbmQgcGFydCBvZiB0aGUgdHJhaW5pbmcgZGF0YSAoJFxtYXRoY2Fse0R9X3tcZWxsfSQpIHVzZWQgZm9yIGFnZ3JlZ2F0aW9uKSBhcmUgcmVwb3J0ZWQuDQoNCi0tLQ0KDQpgYGB7cn0NCmRmIDwtIGlyaXMNCmJhc2ljX21hY2hpbmVzIDwtIGdlbmVyYXRlTWFjaGluZXNfTWl4KHRyYWluX2lucHV0ID0gZGZbLDE6NF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gZGYkU3BlY2llcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IE5VTEwsICNjKCJrbm4iLCAidHJlZSIsICJyZiIsICJsb2dpdCIsICJzdm0iLCAieGdiIikNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgobnRyZWUgPSAxMDoyMCAqIDI1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGsgPSBjKDI6MTApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1maW5hbF9ib29zdCA9IDEwKSkNCmJhc2ljX21hY2hpbmVzJGZpdHRlZF9yZW1haW4gJT4lDQogIHN3ZWVwKDEsIGRmW2Jhc2ljX21hY2hpbmVzJGlkMiwgIlNwZWNpZXMiXSwgRlVOID0gIj09IikgJT4lDQogIGNvbE1lYW5zICU+JQ0KICB0ICU+JQ0KICBhc190aWJibGUNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1Pk9wdGltaXplciA6IGdyaWQgc2VhcmNoIGFsZ29yaXRobTwvdT48L3NwYW4+DQo9PT0NCg0KVGhpcyBwYXJ0IHByb3ZpZGVzIGZ1bmN0aW9ucyB0byBhcHByb3hpbWF0ZSB0aGUga2V5IHBhcmFtZXRlcnMgJChcYWxwaGEsXGJldGEpXGluKFxtYXRoYmJ7Un1fK14qKV4yJCBvZiB0aGUgYWdncmVnYXRpb24uDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPkZ1bmN0aW9uPC9zcGFuPiA6IGBzZXRHcmlkUGFyYW1ldGVyX01peGANCi0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGFsbG93cyB0byBzZXQgdGhlIHZhbHVlcyBvZiBncmlkIHNlYXJjaCBhbGdvcml0aG0gcGFyYW1ldGVycy4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBtaW5fYWxwaGFgIDogbWluaW51bSB2YWx1ZSBvZiAkXGFscGhhJCBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG1pbl9hbHBoYSA9IDFlLTVgLg0KICAgIC0gYG1heF9hbHBoYWAgOiBtYXhpbnVtIHZhbHVlIG9mICRcYWxwaGEkIGluIHRoZSBncmlkLiBCeSBkZWZ1YWx0LCBgbWF4X2FscGhhID0gNWAuDQogICAgLSBgbWluX2JldGFgIDogbWluaW51bSB2YWx1ZSBvZiAkXGJldGEkIGluIHRoZSBncmlkLiBCeSBkZWZ1YWx0LCBgbWluX2JldGEgPSAwLjFgLg0KICAgIC0gYG1heF9iZXRhYCA6IG1heGltdW0gdmFsdWUgb2YgJFxiZXRhJCBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG1heF9hbHBoYSA9IDUwYC4NCiAgICAtIGBuX2FscGhhYCwgYG5fYmV0YSA9IDMwYCA6IHRoZSBudW1iZXIgb2YgJFxhbHBoYSQgYW5kICRcYmV0YSQgcmVzcGVjdGl2ZWx5IGluIHRoZSBncmlkLiBCeSBkZWZ1YWx0LCBgbl9hbHBoYSA9IG5fYmV0YSA9IDMwYC4NCiAgICAtIGBwYXJhbWV0ZXJzYCA6IGEgbGlzdCBvZiBwYXJhbWV0ZXIgJGFscGhhJCBhbmQgJFxiZXRhJCBpbiBjYXNlIG5vbi11bmlmb3JtIGdyaWQgaXMgY29uc2lkZXJlZC4gSXQgc2hvdWxkIGJlIGEgbGlzdCBvZiB0d28gdmVjdG9ycyBjb250YWluaW5nIHRoZSB2YWx1ZXMgb2YgJFxhbHBoYSQgYW5kICRcYmV0YSQgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgcGFyYW1ldGVycyA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB1bmlmb3JtIGdyaWQgaXMgdXNlZC4NCiAgICAgLSBgYXhlc2AgOiBuYW1lcyBvZiAkeCx5JCBhbmQgJHokLWF4aXMgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgYXhlcyA9IGMoImFscGhhIiwgImJldGEiLCAiUmlzayIpYC4NCiAgICAtIGB0aXRsZWAgOiB0aGUgdGl0bGUgb2YgdGhlIHBsb3QuIEJ5IGRlZmF1bHQsIGB0aXRsZSA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB0aXRsZSBpcyBgQ3Jvc3MtdmFsaWRhdGlvbiByaXNrIGFzIGEgZnVuY3Rpb24gb2ZgICQoXGFscGhhLCBcYmV0YSkkLg0KICAgIC0gYHByaW50X3Jlc3VsdGAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHByaW50IHRoZSBvYnNlcnZlZCByZXN1bHQgb3Igbm90Lg0KICAgIC0gYGZpZ3VyZWAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHBsb3QgdGhlIGdyYXBoaWMgb2YgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvciBvciBub3QuDQoNCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIGFsbCB0aGUgcGFyYW1ldGVycyBnaXZlbiBpbiBpdHMgYXJndW1lbnRzLg0KDQpgYGB7cn0NCnNldEdyaWRQYXJhbWV0ZXJfTWl4IDwtIGZ1bmN0aW9uKG1pbl9hbHBoYSA9IDFlLTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYWxwaGEgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2JldGEgPSAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9hbHBoYSA9IDMwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9iZXRhID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4ZXMgPSBjKCJhbHBoYSIsICJiZXRhIiwgIlJpc2siKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Jlc3VsdCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFKXsNCiAgcmV0dXJuKGxpc3QobWluX2FscGhhID0gbWluX2FscGhhLA0KICAgICAgICAgICAgICBtYXhfYWxwaGEgPSBtYXhfYWxwaGEsDQogICAgICAgICAgICAgIG1pbl9iZXRhID0gbWluX2JldGEsDQogICAgICAgICAgICAgIG1heF9iZXRhID0gbWF4X2JldGEsDQogICAgICAgICAgICAgIG5fYWxwaGEgPSBuX2FscGhhLA0KICAgICAgICAgICAgICBuX2JldGEgPSBuX2JldGEsDQogICAgICAgICAgICAgIGF4ZXMgPSBheGVzLA0KICAgICAgICAgICAgICB0aXRsZSA9IHRpdGxlLA0KICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gcGFyYW1ldGVycywNCiAgICAgICAgICAgICAgcHJpbnRfcmVzdWx0ID0gcHJpbnRfcmVzdWx0LA0KICAgICAgICAgICAgICBmaWd1cmUgPSBmaWd1cmUpKQ0KfQ0KYGBgDQoNCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPkZ1bmN0aW9uPC9zcGFuPiA6IGBncmlkT3B0aW1pemVyX01peGANCg0KLSAqKkFyZ3VtZW50Kio6DQogICAgDQogICAgLSBgb2JqX2Z1bmAgOiB0aGUgb2JqZWN0aXZlIGZ1bmN0aW9uIGZvciB3aGljaCBpdHMgbWluaW1pemVyIGlzIHRvIGJlIGVzdGltYXRlZC4gSXQgc2hvdWxkIGJlIGEgdW5pdmFyYXRlIGZ1bmN0aW9uIG9mIHJlYWwgcG9zaXRpdmUgdmFyaWFibGVzLg0KICAgIC0gYHNldFBhcmFtZXRlcmAgOiB0aGUgY29udHJvbCBvZiBncmlkIHNlYXJjaCBhbGdvcml0aG0gcGFyYW1ldGVycyB3aGljaCBzaG91bGQgYmUgdGhlIGZ1bmN0aW9uIGBzZXRHcmlkUGFyYW1ldGVyX01peCgpYCBkZWZpbmVkIGFib3ZlLg0KICAgIC0gYHNpbGVudGAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIG9yIG5vdCBwcm9ncmVzc2luZyBtZXNzYWdlcyBzaG91bGQgYmUgcHJpbnRlZC4gQmUgZGVmYXVsdCwgYHNpbGVudCA9IEZBTFNFYCBhbmQgdGhlIHByb2dyZXNzIG9mIHRoZSBhbGdvcml0aG0gaXMgcHJpbnRlZC4NCg0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCg0KICAgIC0gYG9wdF9wYXJhbWAgOiB0aGUgb2JzZXJ2ZWQgdmFsdWUgb2YgdGhlIG1pbmltaXplci4NCiAgICAtIGBvcHRfZXJyb3JgIDogdGhlIHZhbHVlIG9mIG9wdGltYWwgcmlzay4NCiAgICAtIGBhbGxfcmlza2AgOiB0aGUgdmVjdG9yIG9mIGFsbCB0aGUgZXJyb3JzIGV2YWx1YXRlZCBhdCBhbGwgdGhlIHZhbHVlcyBvZiBjb25zaWRlcmVkIHBhcmFtZXRlcnMuDQogICAgLSBgcnVuLnRpbWVgIDogdGhlIHJ1bm5pbmcgdGltZSBvZiB0aGUgYWxnb3JpdGhtLiANCg0KYGBge3J9DQpncmlkT3B0aW1pemVyX01peCA8LSBmdW5jdGlvbihvYmpfZnVuYywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWxlbnQgPSBGQUxTRSl7DQogIHQwIDwtIFN5cy50aW1lKCkNCiAgaWYoaXMubnVsbChzZXRQYXJhbWV0ZXIkcGFyYW1ldGVycykpew0KICAgIHBhcmFtX2xpc3QgPC0gbGlzdChhbHBoYSA9ICByZXAoc2VxKHNldFBhcmFtZXRlciRtaW5fYWxwaGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciRtYXhfYWxwaGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoLm91dCA9IHNldFBhcmFtZXRlciRuX2FscGhhKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkbl9iZXRhKSwNCiAgICAgICAgICAgICAgICAgICAgICAgYmV0YSA9ICByZXAoc2VxKHNldFBhcmFtZXRlciRtaW5fYmV0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkbWF4X2JldGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgub3V0ID0gc2V0UGFyYW1ldGVyJG5fYmV0YSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhY2ggPSBzZXRQYXJhbWV0ZXIkbl9hbHBoYSkpDQogIH0gZWxzZXsNCiAgICBwYXJhbV9saXN0IDwtIGxpc3QoYWxwaGEgPSByZXAoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzFdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChzZXRQYXJhbWV0ZXIkcGFyYW1ldGVyc1tbMl1dKSksDQogICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSByZXAoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhY2ggPSBsZW5ndGgoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzFdXSkpKQ0KICB9DQogIHJpc2sgPC0gbWFwMl9kYmwoLnggPSBwYXJhbV9saXN0JGFscGhhLA0KICAgICAgICAgICAgICAgICAgIC55ID0gcGFyYW1fbGlzdCRiZXRhLA0KICAgICAgICAgICAgICAgICAgIC5mID0gfiBvYmpfZnVuYyhjKC54LCAueSkpKQ0KICBpZF9vcHQgPC0gd2hpY2gubWluKHJpc2spDQogIG9wdF9lcCA8LSBjKHBhcmFtX2xpc3QkYWxwaGFbaWRfb3B0XSwgcGFyYW1fbGlzdCRiZXRhW2lkX29wdF0pDQogIG9wdF9yaXNrIDwtIHJpc2tbaWRfb3B0XQ0KICBpZihzZXRQYXJhbWV0ZXIkcHJpbnRfcmVzdWx0ICYgIXNpbGVudCl7DQogICAgY2F0KCJcbiogR3JpZCBzZWFyY2ggYWxnb3JpdGhtLi4uIiwgIlxuIH4gT2JzZXJ2ZWQgcGFyYW1ldGVyOiAoYWxwaGEsIGJldGEpID0gKCIsIG9wdF9lcFsxXSwgDQogICAgICAgICIsICIsIA0KICAgICAgICBvcHRfZXBbMl0sICIpIiwgDQogICAgICAgIHNlcCA9ICIiKQ0KICB9DQogIGlmKHNldFBhcmFtZXRlciRmaWd1cmUpew0KICAgIGlmKGlzLm51bGwoc2V0UGFyYW1ldGVyJHRpdGxlKSl7DQogICAgICB0aXQgPC0gcGFzdGUoIjxiPiBDcm9zcy12YWxpZGF0aW9uIHJpc2sgYXMgYSBmdW5jdGlvbiBvZjwvYj4gKCIsDQogICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyJGF4ZXNbMV0sIiwiLCANCiAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkYXhlc1syXSwNCiAgICAgICAgICAgICAgICAgICAiKSIpDQogICAgfSBlbHNlew0KICAgICAgdGl0IDwtIHNldFBhcmFtZXRlciR0aXRsZQ0KICAgIH0NCiAgICBmaWcgPC0gdGliYmxlKGFscGhhID0gcGFyYW1fbGlzdCRhbHBoYSwgDQogICAgICAgICAgICAgICAgICBiZXRhID0gcGFyYW1fbGlzdCRiZXRhLA0KICAgICAgICAgICAgICAgICAgcmlzayA9IHJpc2spICU+JQ0KICAgICAgcGxvdF9seSh4ID0gfmFscGhhLCB5ID0gfmJldGEsIHogPSB+cmlzaywgdHlwZSA9ICJtZXNoM2QiKSAlPiUNCiAgICAgIGFkZF90cmFjZSh4ID0gYyhvcHRfZXBbMV0sIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMoMCwgb3B0X2VwWzJdKSwNCiAgICAgICAgICAgICAgICB6ID0gYyhvcHRfcmlzaywgb3B0X3Jpc2spLA0KICAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwNCiAgICAgICAgICAgICAgICBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLA0KICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KCANCiAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gIiM1RTg4RkMiLCANCiAgICAgICAgICAgICAgICAgIGRhc2ggPSBUUlVFKSwNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDQsDQogICAgICAgICAgICAgICAgICBjb2xvciA9IH5jKCIjNUU4OEZDIiwgIiMzOERFMjUiKSksDQogICAgICAgICAgICAgICAgbmFtZSA9IHBhc3RlKCJPcHRpbWFsIixzZXRQYXJhbWV0ZXIkYXhlc1sxXSkpICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBjKDAsIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMob3B0X2VwWzJdLCBvcHRfZXBbMl0pLA0KICAgICAgICAgICAgICAgIHogPSBjKG9wdF9yaXNrLCBvcHRfcmlzayksDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiI0YzMTUzNiIsIA0KICAgICAgICAgICAgICAgICAgZGFzaCA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoDQogICAgICAgICAgICAgICAgICBzaXplID0gNCwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gfmMoIiNGMzE1MzYiLCAiIzM4REUyNSIpKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gcGFzdGUoIk9wdGltYWwiLHNldFBhcmFtZXRlciRheGVzWzJdKSkgICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBvcHRfZXBbMV0sDQogICAgICAgICAgICAgICAgeSA9IG9wdF9lcFsyXSwNCiAgICAgICAgICAgICAgICB6ID0gb3B0X3Jpc2ssDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbWFya2VycycsDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdCgNCiAgICAgICAgICAgICAgICAgIHNpemUgPSA1LA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiIzM4REUyNSIpLA0KICAgICAgICAgICAgICAgIG5hbWUgPSAiT3B0aW1hbCBwb2ludCIpICU+JQ0KICAgICAgbGF5b3V0KHRpdGxlID0gbGlzdCh0ZXh0ID0gdGl0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gMC4wNzUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMC45MjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJWZXJkYW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiIzVFODhGQyIpKSwNCiAgICAgICAgICAgICBsZWdlbmQgPSBsaXN0KHggPSAxMDAsIHkgPSAwLjUpLA0KICAgICAgICAgICAgIHNjZW5lID0gbGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSBzZXRQYXJhbWV0ZXIkYXhlc1sxXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzJdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KCB0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzNdKSkpDQogICAgcHJpbnQoZmlnKQ0KICB9DQogIHQxIDwtIFN5cy50aW1lKCkNCiAgcmV0dXJuKGxpc3Qob3B0X3BhcmFtID0gb3B0X2VwLA0KICAgICAgICAgICAgICBvcHRfZXJyb3IgPSBvcHRfcmlzaywNCiAgICAgICAgICAgICAgYWxsX3Jpc2sgPSByaXNrLA0KICAgICAgICAgICAgICBydW4udGltZSA9IGRpZmZ0aW1lKHQxLCB0MCwgdW5pdHMgPSAic2VjcyIpW1sxXV0pDQogICkNCn0NCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PiRca2FwcGEkLWNyb3NzIHZhbGlkYXRpb24gJDAkLSQxJCBsb3N0IGZ1bmN0aW9uPC91Pjwvc3Bhbj4NCi0tLQ0KDQpDb25zdHJ1Y3RpbmcgYWdncmVnYXRpb24gbWV0aG9kIGlzIGVxdWl2YWxlbnQgdG8gYXBwcm94aW1hdGluZyB0aGUgb3B0aW1hbCB2YWx1ZSBvZiBwYXJhbWV0ZXIgJChcYWxwaGEsXGJldGEpXGluKFxtYXRoYmJ7Un1fK14qKV4yJCBpbnRyb2R1Y2VkIGluIHNlY3Rpb24gMS4xIGJ5IG1pbmltaXppbmcgc29tZSBsb3N0IGZ1bmN0aW9uLiBJbiB0aGlzIHN0dWR5LCB3ZSBwcm9wb3NlICRca2FwcGEkLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvciBkZWZpbmVkIGJ5DQoNCiQkDQpcdmFycGhpXntca2FwcGF9KFxhbHBoYSwgXGJldGEpPVxmcmFjezF9e1xrYXBwYX1cc3VtX3trPTF9Xntca2FwcGF9XEJpZyhcZnJhY3sxfXt8Rl9rfH1cc3VtX3soeF9qLHlfailcaW4gRl9rfVxtYXRoYmJ7MX1fe1x7Z19uKHtcYmYgY30oeF9qKSlcbmVxIHlfalx9fVxCaWcpDQokJA0KDQp3aGVyZQ0KDQotIGZvciBhbnkgJGs9MSwuLi4sXGthcHBhJCwgJEZfayQgZGVub3RlcyB0aGUgJGskdGggdmFsaWRhdGlvbiBmb2xkLg0KLSAkZ19uKHtcYmYgY30oeF9qKSkkIGlzIHRoZSBwcmVkaWN0aW9uIG9mICR4X2okIG9mICRGX2skLCBjb21wdXRlZCB1c2luZyB0aGUgZGF0YSBwb2ludHMgZnJvbSB0aGUgcmVtYWluaW5nIHBhcnQgJHtcY2FsIER9X3tcZWxsfS1GX2skIGJ5LA0KDQokJGdfbih7XGJmIGN9KHhfaikpPWteKj1cdGV4dHthcmd9XG1heF97MVxsZXEga1xsZXEgTn1cc3VtX3soeF9pLHlfaSlcaW4gXG1hdGhjYWx7RH1cc2V0bWludXMgRl9rfUtfe1xhbHBoYSwgXGJldGF9KGRfe1xjYWwgSH0oe1xiZiBjfSh4X2opLHtcYmYgY30oeF9pKSkpXG1hdGhiYnsxfV97XHt5X2k9a1x9fS4kJA0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+OiBgZGlzdF9tYXRyaXhfTWl4YA0KLS0tDQoNClRoaXMgZnVuY3Rpb24gY29tcHV0ZXMgc29tZSBkaXN0YW5jZXMgKGlucHV0IHBhcnQpIGFuZCBIYW1taW5nIGRpc3RhbmNlcyAob3V0cHV0IHBhcnQpIGJldHdlZW4gZGF0YSBwb2ludHMgb2YgZWFjaCB0cmFpbmluZyBmb2xkcyAoJFxtYXRoY2Fse0R9X3tcZWxsfS1GX2skKSBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgdmFsaWRhdGlvbiBmb2xkICRGX2skIGZvciBhbnkgJGs9MSxcZG90cyxca2FwcGEkLiBUaGUgZGlzdGFuY2UgbWF0cmljZXMgb2YgaW5wdXQgcGFydCBhcmUgY29tcHV0ZWQgYWNjb3JkaW5nIHRvIGBrZXJuZWxgIHR5cGVzLCB3aGlsZSB0aGUgZGlzdGFuY2VzIG9mIHRoZSBvdXRwdXQgcGFydCBhcmUgYWx3YXlzIHRoZSBIYW1taW5nIGRpc3RhbmNlcyBiZXR3ZWVuIHByZWRpY3RlZCBjbGFzc2VzIG9mIGRhdGEgcG9pbnRzLiBUd28gbGlzdHM6IG9uZSBmb3IgaW5wdXQgcGFydCBhbmQgYW5vdGhlciBmb3Igb3V0cHV0IHBhcnQsIGVhY2ggY29udGFpbnMgJFxrYXBwYSQgZGlzdGFuY2UgbWF0cmljZXMgJERfaz0oZFt7XGJmIHJ9KHhfaSkse1xiZiByfSh4X2opXSlfe2ksan0kIGZvciAkaz0xLFxkb3RzLFxrYXBwYSQsIGNvcnJlc3BvbmRpbmcgdG8gJFxrYXBwYSQgdmFsaWRhdGlvbiBmb2xkcywgYXJlIGNvbXB1dGVkLg0KDQotICoqQXJndW1lbnQqKjoNCiAgICANCiAgICAtIGBiYXNpY01hY2hpbmVzYCA6IHRoZSBiYXNpYyBtYWNoaW5lIG9iamVjdCwgd2hpY2ggaXMgYW4gb3V0cHV0IG9mIGBnZW5lcmF0ZU1hY2hpbmVzX01peGAgZnVuY3Rpb24uDQogICAgLSBgbl9jdmAgOiB0aGUgbnVtYmVyICRca2FwcGEkIG9mIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMuIEJ5IGRlZmF1bHQsIGBuX2N2ID0gNWAuDQogICAgLSBga2VybmVsYCA6IHRoZSBrZXJuZWwgZnVuY3Rpb24gdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLCB3aGljaCBpcyBhbiBlbGVtZW50IG9mIHsiZ2F1c3NpYW4iLCAiZXBhbmVjaG5pa292IiwgImJpd2VpZ2h0IiwgInRyaXdlaWdodCIsICJ0cmlhbmd1bGFyIiwgIm5haXZlIn0uIEJ5IGRlZmF1bHQsIGBrZXJuZWwgPSAiZ2F1c3NpYW4iYC4NCiAgICAtIGBpZF9zaHVmZmxlYCA6IGFuIGludGVnZXIgdmVjdG9yIG9mIGxlbmd0aCBlcXVhbHMgdG8gdGhlIHNpemUgb2YgdGhlIHJlbWFpbmluZyBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhICgke1xjYWwgRH1fe1xlbGx9JCkuIEl0cyBlbGVtZW50cyBhcmUgZnJvbSB7JDEsLi4uLFxrYXBwYSR9LCBpbmRpY2F0aW5nIHRoZSBmb2xkIG1lbWJlcnNoaXAgb2YgdGhlIGRhdGEgcG9pbnRzLiBCeSBkZWZhdWx0LCBgaWRfc2h1ZmZsZSA9IE5VTExgLCBhbmQgdGhlIGRhdGEgcG9pbnRzIHdpbGwgYmUgc2h1ZmZsZWQgcmFuZG9tbHkuDQogICAgLSBgb3V0cHV0YCA6IGEgbG9naWNhbCB2YWx1ZSBzcGVjaWZ5aW5nIHdoZXRoZXIgdGhlIGhhbW1pbmcgZGlzdGFuY2VzIGJldHdlZW4gb3V0cHV0IGNsYXNzZXMgYXJlIGNvbXB1dGVkIG9yIG5vdC4gQnkgZGVmYXVsdCwgYG91dHB1dCA9IEZBTFNFYC4NCiAgICANCi0gKipWYWx1ZSoqOg0KICAgIA0KICAgIFRoaXMgZnVuY3Rpb25zIHJldHVybnMgYSAqbGlzdCogb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIA0KICAgIC0gYGRpc3RfaW5wdXRgIDogYSBsaXN0IG9mIGRhdGEgZnJhbWUgKGB0aWJibGVgKSBjb250YWluaW5nIHN1Ymxpc3RzIGNvcnJlc3BvbmRpbmcgdG8ga2VybmVsIGZ1bmN0aW9ucyB1c2VkIGZvciB0aGUgYWdncmVnYXRpb24uIEVhY2ggc3VibGlzdCBjb250YWlucyBgbl9jdmAgbnVtYmVycyBvZiBpbnB1dC1kaXN0YW5jZSBtYXRyaWNlcyAkRF9rPShkW3tcYmYgcn0oeF9pKSx7XGJmIHJ9KHhfaildKV97aSxqfSQsIGZvciAkaz0xLFxkb3RzLFxrYXBwYSQsIGNvbnRhaW5pbmcgZGlzdGFuY2VzIGJldHdlZW4gdGhlIGRhdGEgcG9pbnRzIGluIHZhbGlhdGlvbiBmb2xkIChhbG9uZyB0aGUgY29sdW1ucykgYW5kIHRoZSAkMS1ca2FwcGEkIHJlbWFpbmluZyBmb2xkcyBvZiB0cmFpbmluZyBkYXRhIChhbG9uZyB0aGUgcm93cykuIFRoZSB0eXBlIG9mIGRpc3RhbmNlIG1hdHJpY2VzIGRlcGVuZHMgb24gdGhlIGtlcm5lbCB1c2VkOg0KICAgICAgICAtIElmIGBrZXJuZWwgPSBuYWl2ZWAsIHRoZSBkaXN0YW5jZSBtYXRyaWNlcyBjb250YWluIHRoZSBtYXhpbXVtIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMsIGkuZS4sICQkRF9rPShcfHtcYmYgcn0oeF9pKS17XGJmIHJ9KHhfailcfF97XG1heH0pX3tpLGp9XHRleHR7IGZvciB9az0xLFxkb3RzLFxrYXBwYS4kJA0KICAgICAgICAtIElmIGBrZXJuZWwgPSB0cmlhbmd1bGFyYCwgdGhlIGRpc3RhbmNlIG1hdHJpY2VzIGNvbnRhaW4gdGhlICRMXzEkIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMsIGkuZS4sIGBkaXN0X21hdHJpeF9NaXhgICQkRF9rPShcfHtcYmYgcn0oeF9pKS17XGJmIHJ9KHhfailcfF8xKV97aSxqfVx0ZXh0eyBmb3IgfWs9MSxcZG90cyxca2FwcGEuJCQNCiAgICAgICAgLSBPdGhlcndpc2UsIHRoZSBkaXN0YW5jZSBtYXRyaWNlcyBjb250YWluIHRoZSBzcXVhcmVkICRMXzIkIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMsIGkuZS4sICQkRF9rPShcfHtcYmYgcn0oeF9pKS17XGJmIHJ9KHhfailcfF8gMl4yKV97aSxqfVx0ZXh0eyBmb3IgfWs9MSxcZG90cyxca2FwcGEuJCQNCiAgICAtIGBkaXN0X21hY2hpbmVgIDogYSBsaXN0IGNvbnRhaW5pbmcgJFxrYXBwYSQgYHRpYmJsZWAgb2YgSGFtbWluZyBkaXN0YW5jZXMgYmV0d2VlbiBwcmVkaWN0ZWQgY2xhc3NlcyBvZiB2YWxpZGF0aW9uIGZvbGRzIGFuZCB0aGUgcmVzdCBvZiBjcm9zcyB2YWxpZGF0aW9uIGZvbGRzLg0KICAgIC0gYGlkX3NodWZmbGVgIDogdGhlIHNodWZmbGVkIGluZGljZXMgaW4gY3Jvc3MtdmFsaWRhdGlvbi4NCiAgICAtIGBuX2N2YCA6IHRoZSBudW1iZXIgJFxrYXBwYSQgb2YgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcy4NCiAgICANCmBgYHtyfQ0KZGlzdF9tYXRyaXhfTWl4IDwtIGZ1bmN0aW9uKGJhc2ljTWFjaGluZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0gImdhdXNpYW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dCA9IFRSVUUpew0KICBuIDwtIG5yb3coYmFzaWNNYWNoaW5lcyRmaXR0ZWRfcmVtYWluKQ0KICBuX2VhY2hfZm9sZCA8LSBmbG9vcihuL25fY3YpDQogICMgc2h1ZmZsZWQgaW5kaWNlcw0KICBpZihpcy5udWxsKGlkX3NodWZmbGUpKXsNCiAgICBzaHVmZmxlIDwtIDE6KG5fY3YtMSkgJT4lDQogICAgICByZXAobl9lYWNoX2ZvbGQpICU+JQ0KICAgICAgYyguLCByZXAobl9jdiwgbiAtIG5fZWFjaF9mb2xkICogKG5fY3YgLSAxKSkpICU+JQ0KICAgICAgc2FtcGxlDQogIH1lbHNlew0KICAgIHNodWZmbGUgPC0gaWRfc2h1ZmZsZQ0KICB9DQogICMgdGhlIHByZWRpY3Rpb24gbWF0cml4IERfbA0KICBkZl9tYWNoIDwtIGFzLm1hdHJpeChiYXNpY01hY2hpbmVzJGZpdHRlZF9yZW1haW4pDQogIGRmX2lucHV0IDwtIGFzLm1hdHJpeChiYXNpY01hY2hpbmVzJHRyYWluX2RhdGEkdHJhaW5faW5wdXRbYmFzaWNNYWNoaW5lcyRpZDIsXSkNCiAgaWYoIShrZXJuZWwgJWluJSBjKCJuYWl2ZSIsICJ0cmlhbmd1bGFyIikpKXsNCiAgICBwYWlyX2Rpc3RfaW5wdXQgPC0gZnVuY3Rpb24oTSwgTil7DQogICAgICBuX04gPC0gZGltKE4pDQogICAgICBuX00gPC0gZGltKE0pDQogICAgICByZXNfIDwtIDE6bnJvdyhOKSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKHJvd1N1bXMoKE0gLSBtYXRyaXgocmVwKE5baWQsXSwgbl9NWzFdKSwgbmNvbCA9IG5fTVsyXSwgYnlyb3cgPSBUUlVFKSleMikpKSkpDQogICAgICByZXR1cm4ocmVzXykNCiAgICB9DQogIH0NCiAgaWYoa2VybmVsID09ICJ0cmlhbmd1bGFyIil7DQogICAgcGFpcl9kaXN0X2lucHV0IDwtIGZ1bmN0aW9uKE0sIE4pew0KICAgICAgbl9OIDwtIGRpbShOKQ0KICAgICAgbl9NIDwtIGRpbShNKQ0KICAgICAgcmVzXyA8LSAxOm5yb3coTikgJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKGFicyhNIC0gbWF0cml4KHJlcChOW2lkLF0sIG5fTVsxXSksIG5jb2wgPSBuX01bMl0sIGJ5cm93ID0gVFJVRSkpKSkpKSkNCiAgICAgIHJldHVybihyZXNfKQ0KICAgIH0NCiAgfQ0KICBpZihrZXJuZWwgPT0gIm5haXZlIil7DQogICAgcGFpcl9kaXN0X2lucHV0IDwtIGZ1bmN0aW9uKE0sIE4pew0KICAgICAgbl9OIDwtIGRpbShOKQ0KICAgICAgbl9NIDwtIGRpbShNKQ0KICAgICAgcmVzXyA8LSAxOm5yb3coTikgJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3RvcihhcHBseShhYnMoTSAtIG1hdHJpeChyZXAoTltpZCxdLCBuX01bMV0pLCBuY29sID0gbl9NWzJdLCBieXJvdyA9IFRSVUUpKSwgMSwgbWF4KSkpKSkNCiAgICAgIHJldHVybihyZXNfKQ0KICAgIH0NCiAgfQ0KICBwYWlyX2Rpc3Rfb3V0cHV0IDwtIGZ1bmN0aW9uKE0sIE4pew0KICAgIHJlc18gPC0gMTpucm93KE4pICU+JQ0KICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gcm93U3Vtcyhzd2VlcChNLCAyLCBOW2lkLF0sIEZVTiA9ICIhPSIpKSkpKQ0KICAgIHJldHVybihyZXNfKQ0KICB9DQogIEwxIDwtIDE6bl9jdiAlPiUNCiAgICAgIG1hcCguZiA9IChcKHgpIHBhaXJfZGlzdF9pbnB1dChkZl9pbnB1dFtzaHVmZmxlICE9IHgsXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9pbnB1dFtzaHVmZmxlID09IHgsXSkpKQ0KICBpZihvdXRwdXQpew0KICAgIEwyIDwtIDE6bl9jdiAlPiUNCiAgICAgIG1hcCguZiA9IChcKHgpIHBhaXJfZGlzdF9vdXRwdXQoZGZfbWFjaFtzaHVmZmxlICE9IHgsXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfbWFjaFtzaHVmZmxlID09IHgsXSkpKQ0KICB9IGVsc2V7DQogICAgTDIgPC0gTkENCiAgfQ0KICByZXR1cm4obGlzdChkaXN0X2lucHV0ID0gTDEsDQogICAgICAgICAgICAgIGRpc3RfbWFjaGluZSA9IEwyLA0KICAgICAgICAgICAgICBpZF9zaHVmZmxlID0gc2h1ZmZsZSwNCiAgICAgICAgICAgICAgbl9jdiA9IG5fY3YpKQ0KfQ0KYGBgDQoNCg0KLS0tDQoNCj4gKipFeGFtcGxlLjMqKjogVGhlIG1ldGhvZCBgZGlzdF9tYXRyaXhfTWl4YCBpcyBpbXBsZW1lbnRlZCBvbiB0aGUgb2J0YWluZWQgYmFzaWMgbWFjaGluZXMgYnVpbHQgaW4gKkV4YW1wbGUuMSogd2l0aCB0aGUgY29ycmVzcG9uZGluZyBHYXVzc2lhbiBrZXJuZWwgZnVuY3Rpb24uDQoNCi0tLQ0KDQpgYGB7cn0NCmRpcyA8LSBkaXN0X21hdHJpeF9NaXgoYmFzaWNNYWNoaW5lcyA9IGJhc2ljX21hY2hpbmVzLA0KICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0gImdhdXNzaWFuIikNCmNhdCgiKiBOdW1iZXIgb2YgZm9sZHMgOiIsIGRpcyRuX2N2KQ0KYGBgDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS40Kio6IEZyb20gdGhlIGRpc3RhbmNlIG1hdHJpeCwgd2UgY2FuIGNvbXB1dGUgdGhlIGVycm9yIGNvcnJlc3BvbmRpbmcgdG8gR2F1c3NpYW4ga2VybmVsIGZ1bmN0aW9uLCB0aGVuIHVzZSBib3RoIG9mIHRoZSBvcHRpbWl6YXRpb24gbWV0aG9kcyB0byBhcHByb3hpbWF0ZSB0aGUgc21vb3RoaW5nIHBhcmFtdGVyIGluIHRoaXMgY2FzZS4NCg0KLS0tDQoNCmBgYHtyfQ0KIyBHYXVzc2lhbiBrZXJuZWwNCmdhdXNzaWFuX2tlcm4gPC0gZnVuY3Rpb24oLmVwID0gYyguMDUsIDAuMDA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIC5hbHBoYSA9IDIpew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeChleHAoLSAoeFsxXSpEMSt4WzJdKkQyKV4oLmFscGhhLzIpKi5pbnZfc2lnbWFeLmFscGhhKSkNCiAgICB5X2hhdCA8LSBtYXBfZGZjKC54ID0gMTpuY29sKHRlbTApLA0KICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4XykgdGliYmxlKCJ7eF99IiA6PSB0YXBwbHkodGVtMFssIHhfXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTkRFWCA9IC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgIT0gaWRdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gc3VtKSkpKSAlPiUNCiAgICAgICAgbWFwX2NociguZiA9IChcKHgpIG5hbWVzKHdoaWNoLm1heCh4KSkpKQ0KICAgIHJldHVybihtZWFuKHlfaGF0ICE9IC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgPT0gaWRdKSkNCiAgfQ0KICB0ZW1wIDwtIG1hcCgueCA9IDE6LmRpc3RfbWF0cml4JG5fY3YsIA0KICAgICAgICAgICAgICAuZiA9IH4ga2Vybl9mdW4oeCA9IC5lcCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDEgPSAuZGlzdF9tYXRyaXgkZGlzdF9pbnB1dFtbLnhdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMiA9IC5kaXN0X21hdHJpeCRkaXN0X21hY2hpbmVbWy54XV0pKQ0KICByZXR1cm4oUmVkdWNlKCIrIiwgdGVtcCkpDQp9DQoNCiMgS2FwcGEgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvcg0KY29zdF9mdW4gPC0gZnVuY3Rpb24oeCwNCiAgICAgICAgICAgICAgICAgICAgIC5kaXN0X21hdHJpeCA9IGRpcywNCiAgICAgICAgICAgICAgICAgICAgIC5rZXJuZWxfZnVuYyA9IGdhdXNzaWFuX2tlcm4sDQogICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyID0gYmFzaWNfbWFjaGluZXMkdHJhaW5fZGF0YSR0cmFpbl9yZXNwb25zZVtiYXNpY19tYWNoaW5lcyRpZDJdLA0KICAgICAgICAgICAgICAgICAgICAgLmludl9zaWdtYSA9IHNxcnQoLjUpLA0KICAgICAgICAgICAgICAgICAgICAgLmFscGhhID0gMil7DQogIHJldHVybigua2VybmVsX2Z1bmMoLmVwID0geCwNCiAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXggPSAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMiA9IC50cmFpbl9yZXNwb25zZTIsDQogICAgICAgICAgICAgICAgICAgICAgLmludl9zaWdtYSA9IC5pbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgLmFscGhhID0gLmFscGhhKSkNCn0NCmBgYA0KDQoNCmBgYHtyLCBvdXQud2lkdGg9JzkwJScsIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgT3B0aW1pemF0aW9uDQpvcHRfcGFyYW1fZ3JpZCA8LSBncmlkT3B0aW1pemVyX01peChvYmpfZnVuID0gY29zdF9mdW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIgPSBzZXRHcmlkUGFyYW1ldGVyX01peChtaW5fYWxwaGEgPSAwLjAwMDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fYmV0YSA9IDAuMDAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9iZXRhID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2FscGhhID0gMjAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlndXJlID0gVFJVRSkpDQpgYGANCg0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZpdHRpbmcgcGFyYW1ldGVyPC91Pjwvc3Bhbj4NCi0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGdhdGhlcnMgdGhlIGNvbnN0cnVjdGVkIG1hY2hpbmVzLCB0aGVuIHBlcmZvcm1zIGFuIG9wdGltaXphdGlvbiBhbGdvcml0aG0gdG8gYXBwcm94aW1hdGUgdGhlIHNtb290aGluZyBwYXJhbWV0ZXIgZm9yIHRoZSBhZ2dyZWdhdGlvbiwgdXNpbmcgb25seSB0aGUgcmVtYWluaW5nIHBhcnQgJHtcY2FsIER9X3tcZWxsfSQgb2YgdGhlIHRyYWluaW5nIGRhdGEuDQoNCi0gKipBcmd1bWVudCoqOg0KICAgIA0KICAgIC0gYHRyYWluX2lucHV0YCwgOiBhIG1hdHJpeCBvciBkYXRhIGZyYW1lIG9mIHRoZSB0cmFpbmluZyAqaW5wdXQgZGF0YSouDQogICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogYSB2ZWN0b3Igb2YgdGhlIGNvcnJlc3BvbmRpbmcgcmVzcG9uc2UgdmFyaWFibGUgb2YgdGhlIGB0cmFpbl9pbnB1dGAuDQogICAgLSBgbWFjaGluZXNgIDogYSB2ZWN0b3Igb2YgYmFzaWMgbWFjaGluZXMgdG8gYmUgY29uc3RydWN0ZWQuIEl0IG11c3QgYmUgYSBzdWJzZXQgb2YgeyJsYXNzbyIsICJyaWRnZSIsICJrbm4iLCAidHJlZSIsICJyZiIsICJ4Z2IifS4gQnkgZGVmYXVsdCwgYG1hY2hpbmVzID0gTlVMTGAgYW5kIGFsbCB0aGUgc2l4IHR5cGVzIG9mIGJhc2ljIG1hY2hpbmVzIGFyZSBidWlsdC4NCiAgICAtIGBzY2FsZV9pbnB1dGAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIG9yIG5vdCB0byBzY2FsZSB0aGUgaW5wdXQgZGF0YSBiZWZvcmUgYnVpbGRpbmcgdGhlIGJhc2ljIGNsYXNzaWZpZXJzLiBCeSBkZWZhdWx0LCBgc2NhbGVfaW5wdXQgPSBUUlVFYC4NCiAgICAtIGBzcGxpdHNgIDogdGhlIHByb3BvcnRpb24gb2YgdHJhaW5pbmcgZGF0YSAodGhlIHByb3BvcnRpb24gb2YgJHtcY2FsIER9X2skIGluICR7XGNhbCBEfV9uJCkgdXNlZCB0byBidWlsZCB0aGUgYmFzaWMgbWFjaGluZXMuIEJ5IGRlZmF1bHQsIGBzcGxpdHMgPSAuNWAuDQogICAgLSBgbl9jdmAgOiB0aGUgbnVtYmVyIG9mIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMgdXNlZCB0byB0dW5lIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyLg0KICAgIC0gYGludl9zaWdtYSwgYWxwaGFgIDogdGhlIGludmVyc2Ugbm9ybWFsaXplZCBjb25zdGFudCAkXHNpZ21hXnstMX0+MCQgYW5kIHRoZSBleHBvbmVudCAkXGFscGhhPjAkIG9mIGV4cG9uZW50aWFsIGtlcm5lbDogJEsoeCk9ZV57LVx8eC9cc2lnbWFcfF57XGFscGhhfX0kIGZvciBhbnkgJHhcaW5cbWF0aGJie1J9XmQkLiBCeSBkZWZhdWx0LCBgaW52X3NpZ21hID0gYCRcc3FydHsxLzJ9JCBhbmQgYGFscGhhID0gMmAgd2hpY2ggY29ycmVzcG9uZHMgdG8gdGhlIEdhdXNzaWFuIGtlcm5lbC4NCiAgICAtIGBrZXJuZWxzYCA6IHRoZSBrZXJuZWwgZnVuY3Rpb24gb3IgdmVjdG9yIG9mIGtlcm5lbCBmdW5jdGlvbnMgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLiBCeSBmYXVsdCwgYGtlcm5lbHMgPSAiZ2F1c3NpYW4iYC4NCiAgICAtIGBzZXRCYXNpY01hY2hpbmVQYXJhbWAgOiBhbiBvcHRpb24gdXNlZCB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgYmFzaWMgbWFjaGluZXMuIGBzZXRCYXNpY1BhcmFtZXRlcl9NaXhgIGZ1bmN0aW9uIHNob3VsZCBiZSBmZWQgdG8gdGhpcyBhcmd1bWVudC4NCiAgICAtIGBzZXRHcmlkUGFyYW1gIDogYW4gb3B0aW9uIHVzZWQgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlIGdyaWQgc2VhcmNoIGFsZ29yaXRobS4gYHNldEdyaWRQYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbiBzaG91bGQgYmUgZmVkIHRvIGl0Lg0KICAgIC0gYHNpbGVudGAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIG9yIG5vdCBwcm9ncmVzc2luZyBtZXNzYWdlcyBzaG91bGQgYmUgcHJpbnRlZC4gQmUgZGVmYXVsdCwgYHNpbGVudCA9IEZBTFNFYCBhbmQgdGhlIHByb2dyZXNzIG9mIHRoZSBhbGdvcml0aG0gaXMgcHJpbnRlZC4NCiAgICANCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCBvZiB0aGUgZm9sbG93aW5nIG9iamVjdHM6DQoNCiAgICAtIGBvcHRfcGFyYW1ldGVyc2AgOiB0aGUgb2JzZXJ2ZWQgb3B0aW1hbCBwYXJhbWV0ZXJzLg0KICAgIC0gYGFkZF9wYXJhbWV0ZXJzYCA6IG90aGVyIGFkaXRpb25hbCBwYXJhbWV0ZXJzIHN1Y2ggYXMgc2NhbGluZyBvcHRpb25zLCBwYXJhbWV0ZXJzIG9mIGtlcm5lbCBmdW5jdGlvbnMgYW5kIHRoZSBvcHRpbWl6YXRpb24gbWV0aG9kcyB1c2VkLg0KICAgIC0gYGJhc2ljX21hY2hpbmVzYCA6IHRoZSBsaXN0IG9mIGJhc2ljIG1hY2hpbmUgb2JqZWN0Lg0KDQpgYGB7cn0NCmZpdF9wYXJhbWV0ZXJfTWl4IDwtIGZ1bmN0aW9uKHRyYWluX2lucHV0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcHJlZGljdGlvbnMgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFjaGluZXMgPSBOVUxMLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IDAuNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludl9zaWdtYSA9IHNxcnQoLjUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbHMgPSAiZ2F1c3NpYW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0QmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWxlbnQgPSBGQUxTRSl7DQogIGtlcm5lbHNfbG9va3VwIDwtIGMoImdhdXNzaWFuIiwgImVwYW5lY2huaWtvdiIsICJiaXdlaWdodCIsICJ0cml3ZWlnaHQiLCAidHJpYW5ndWxhciIsICJuYWl2ZSIpDQogIGtlcm5lbF9yZWFsIDwtIGtlcm5lbHMgJT4lDQogICAgc2FwcGx5KEZVTiA9IGZ1bmN0aW9uKHgpIHJldHVybihtYXRjaC5hcmcoeCwga2VybmVsc19sb29rdXApKSkNCiAgaWYoaXMubnVsbCh0cmFpbl9wcmVkaWN0aW9ucykpew0KICAgIG1hY2gyIDwtIGdlbmVyYXRlTWFjaGluZXNfTWl4KHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBzY2FsZV9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gbWFjaGluZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdHMgPSBzcGxpdHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljTWFjaGluZVBhcmFtKQ0KICB9ZWxzZXsNCiAgICBtYWNoMiA8LSBsaXN0KGZpdHRlZF9yZW1haW4gPSB0cmFpbl9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgIG1vZGVscyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICBpZDIgPSByZXAoVFJVRSwgbnJvdyh0cmFpbl9pbnB1dCkpLA0KICAgICAgICAgICAgICAgICAgdHJhaW5fZGF0YSA9IGxpc3QodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdF9yZW1haW5fb3JnID0gdHJhaW5fcHJlZGljdGlvbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbWFjaGluZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfbWFjaGluZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5faW5wdXQgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2lucHV0ID0gTlVMTCkpDQogICAgaWYoc2NhbGVfaW5wdXQpew0KICAgICAgbWluXyA8LSBtYXBfZGJsKHRyYWluX2lucHV0LCAuZiA9IG1pbikNCiAgICAgIG1heF8gPC0gbWFwX2RibCh0cmFpbl9pbnB1dCwgLmYgPSBtYXgpDQogICAgICBtYWNoMiR0cmFpbl9kYXRhJG1pbl9pbnB1dCA9IG1pbl8NCiAgICAgIG1hY2gyJHRyYWluX2RhdGEkbWF4X2lucHV0ID0gbWF4Xw0KICAgICAgbWFjaDIkdHJhaW5fZGF0YSR0cmFpbl9pbnB1dCA8LSBzY2FsZSh0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IG1pbl8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IG1heF8gLSBtaW5fKQ0KICAgIH0NCiAgfQ0KICAjIGRpc3RhbmNlIG1hdHJpeCB0byBjb21wdXRlIGxvc3MgZnVuY3Rpb24NCiAgaWZfZXVjbGlkIDwtIEZBTFNFDQogIGlkX2V1Y2xpZCA8LSBOVUxMDQogIG5fa2VyIDwtIGxlbmd0aChrZXJuZWxzKQ0KICBkaXN0X2FsbCA8LSBsaXN0KCkNCiAgaWRfc2h1ZiA8LSBOVUxMDQogIG91dF8gPC0gVFJVRQ0KICBmb3IgKGtfIGluIDE6bl9rZXIpew0KICAgIGtlciA8LSBrZXJuZWxfcmVhbFtrX10NCiAgICBpZihrZXIgPT0gIm5haXZlIil7DQogICAgICBkaXN0X2FsbFtbIm5haXZlIl1dIDwtIGRpc3RfbWF0cml4X01peChiYXNpY01hY2hpbmVzID0gbWFjaDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSAibmFpdmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZF9zaHVmZmxlID0gaWRfc2h1ZiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0ID0gb3V0XykNCiAgICB9IGVsc2V7DQogICAgICBpZihrZXIgPT0gInRyaWFuZ3VsYXIiKXsNCiAgICAgICAgZGlzdF9hbGxbWyJ0cmlhbmd1bGFyIl1dIDwtIGRpc3RfbWF0cml4X01peChiYXNpY01hY2hpbmVzID0gbWFjaDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gbl9jdiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9ICJ0cmlhbmd1bGFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0ID0gb3V0XykNCiAgICAgIH0gZWxzZXsNCiAgICAgICAgaWYoaWZfZXVjbGlkKXsNCiAgICAgICAgICBkaXN0X2FsbFtba2VyXV0gPC0gZGlzdF9hbGxbW2lkX2V1Y2xpZF1dDQogICAgICAgIH0gZWxzZXsNCiAgICAgICAgICBkaXN0X2FsbFtba2VyXV0gPC0gZGlzdF9tYXRyaXhfTWl4KGJhc2ljTWFjaGluZXMgPSBtYWNoMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGtlciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRfc2h1ZmZsZSA9IGlkX3NodWYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dCA9IG91dF8pDQogICAgICAgICAgaWRfZXVjbGlkIDwtIGtlcg0KICAgICAgICAgIGlmX2V1Y2xpZCA8LSBUUlVFDQogICAgICAgIH0NCiAgICAgIH0NCiAgICB9DQogICAgaWRfc2h1ZiA8LSBkaXN0X2FsbFtbMV1dJGlkX3NodWZmbGUNCiAgICBvdXRfIDwtIEZBTFNFDQogIH0NCiAgZGlzdF9vdXRwdXQgPC0gZGlzdF9hbGxbWzFdXSRkaXN0X21hY2hpbmUNCiAgIyBLZXJuZWwgZnVuY3Rpb25zIA0KICAjID09PT09PT09PT09PT09PT0NCiAgIyBHYXVzc2lhbg0KICBnYXVzc2lhbl9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnbWEgPSBpbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwaGEgPSBhbHApew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeChleHAoLSAoeFsxXSpEMSt4WzJdKkQyKV4oLmFscGhhLzIpKi5pbnZfc2lnbWFeLmFscGhhKSkNCiAgICB5X2hhdCA8LSBtYXBfZGZjKC54ID0gMTpuY29sKHRlbTApLA0KICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4XykgdGliYmxlKCJ7eF99IiA6PSB0YXBwbHkodGVtMFssIHhfXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTkRFWCA9IC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgIT0gaWRdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gc3VtKSkpKSAlPiUNCiAgICAgICAgbWFwX2NociguZiA9IChcKHgpIG5hbWVzKHdoaWNoLm1heCh4KSkpKQ0KICAgIHJldHVybihtZWFuKHlfaGF0ICE9IC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgPT0gaWRdKSkNCiAgfQ0KICB0ZW1wIDwtIG1hcCgueCA9IDE6LmRpc3RfbWF0cml4JG5fY3YsIA0KICAgICAgICAgICAgICAuZiA9IH4ga2Vybl9mdW4oeCA9IC5lcCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDEgPSAuZGlzdF9tYXRyaXgkZGlzdF9pbnB1dFtbLnhdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMiA9IGRpc3Rfb3V0cHV0W1sueF1dKSkNCiAgcmV0dXJuKFJlZHVjZSgiKyIsIHRlbXApKQ0KfQ0KDQojIEVwYW5lY2huaWtvdg0KICBlcGFuZWNobmlrb3Zfa2VybmVsIDwtIGZ1bmN0aW9uKC5lcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMil7DQogIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgIHRlbTAgPC0gYXMubWF0cml4KDEtICh4WzFdKkQxK3hbMl0qRDIpKQ0KICAgIHRlbTBbdGVtMCA8IDBdID0gMA0KICAgeV9oYXQgPC0gbWFwX2RmYygueCA9IDE6bmNvbCh0ZW0wKSwNCiAgICAgICAgICAgICAgICAgICAgIC5mID0gKFwoeF8pIHRpYmJsZSgie3hffSIgOj0gdGFwcGx5KHRlbTBbLCB4X10sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU5ERVggPSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IHN1bSkpKSkgJT4lDQogICAgICAgIG1hcF9jaHIoLmYgPSAoXCh4KSBuYW1lcyh3aGljaC5tYXgoeCkpKSkNCiAgICByZXR1cm4obWVhbih5X2hhdCAhPSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSkpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSBkaXN0X291dHB1dFtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQojIEJpd2VpZ2h0DQogIGJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbiguZXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyKXsNCiAga2Vybl9mdW4gPC0gZnVuY3Rpb24oeCwgaWQsIEQxLCBEMil7DQogICAgdGVtMCA8LSBhcy5tYXRyaXgoMS0gKHhbMV0qRDEreFsyXSpEMikpDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gbWFwX2RmYygueCA9IDE6bmNvbCh0ZW0wKSwNCiAgICAgICAgICAgICAgICAgICAgIC5mID0gKFwoeF8pIHRpYmJsZSgie3hffSIgOj0gdGFwcGx5KHRlbTBbLCB4X10sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU5ERVggPSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IHN1bSkpKSkgJT4lDQogICAgICAgIG1hcF9jaHIoLmYgPSAoXCh4KSBuYW1lcyh3aGljaC5tYXgoeCkpKSkNCiAgICByZXR1cm4obWVhbih5X2hhdCAhPSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSkpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSBkaXN0X291dHB1dFtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQojIFRyaXdlaWdodA0KICB0cml3ZWlnaHRfa2VybmVsIDwtIGZ1bmN0aW9uKC5lcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMil7DQogIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgIHRlbTAgPC0gYXMubWF0cml4KDEtICh4WzFdKkQxK3hbMl0qRDIpKQ0KICAgIHRlbTBbdGVtMCA8IDBdID0gMA0KICAgIHlfaGF0IDwtIG1hcF9kZmMoLnggPSAxOm5jb2wodGVtMCksDQogICAgICAgICAgICAgICAgICAgICAuZiA9IChcKHhfKSB0aWJibGUoInt4X30iIDo9IHRhcHBseSh0ZW0wWywgeF9dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElOREVYID0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSBzdW0pKSkpICU+JQ0KICAgICAgICBtYXBfY2hyKC5mID0gKFwoeCkgbmFtZXMod2hpY2gubWF4KHgpKSkpDQogICAgcmV0dXJuKG1lYW4oeV9oYXQgIT0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gZGlzdF9vdXRwdXRbWy54XV0pKQ0KICByZXR1cm4oUmVkdWNlKCIrIiwgdGVtcCkpDQogIH0NCg0KIyBUcmlhbmd1bGFyDQogIHRyaWFuZ3VsYXJfa2VybmVsIDwtIGZ1bmN0aW9uKC5lcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyKXsNCiAga2Vybl9mdW4gPC0gZnVuY3Rpb24oeCwgaWQsIEQxLCBEMil7DQogICAgdGVtMCA8LSBhcy5tYXRyaXgoMS0gKHhbMV0qRDEreFsyXSpEMikpDQogICAgdGVtMFt0ZW0wIDwgMF0gPC0gMA0KICAgIHlfaGF0IDwtIG1hcF9kZmMoLnggPSAxOm5jb2wodGVtMCksDQogICAgICAgICAgICAgICAgICAgICAuZiA9IChcKHhfKSB0aWJibGUoInt4X30iIDo9IHRhcHBseSh0ZW0wWywgeF9dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElOREVYID0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSBzdW0pKSkpICU+JQ0KICAgICAgICBtYXBfY2hyKC5mID0gKFwoeCkgbmFtZXMod2hpY2gubWF4KHgpKSkpDQogICAgcmV0dXJuKG1lYW4oeV9oYXQgIT0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gZGlzdF9vdXRwdXRbWy54XV0pKQ0KICAgIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQogICMgTmFpdmUNCiAgbmFpdmVfa2VybmVsIDwtIGZ1bmN0aW9uKC5lcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0X21hdHJpeCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICAgIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgICAgdGVtMCA8LSAoYXMubWF0cml4KCh4WzFdKkQxK3hbMl0qRDIpKSA8IDEpDQogICAgICB5X2hhdCA8LSBtYXBfZGZjKC54ID0gMTpuY29sKHRlbTApLA0KICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4XykgdGliYmxlKCJ7eF99IiA6PSB0YXBwbHkodGVtMFssIHhfXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTkRFWCA9IC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgIT0gaWRdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gc3VtKSkpKSAlPiUNCiAgICAgICAgbWFwX2NociguZiA9IChcKHgpIG5hbWVzKHdoaWNoLm1heCh4KSkpKQ0KICAgICAgcmV0dXJuKG1lYW4oeV9oYXQgIT0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pKQ0KICAgIH0NCiAgICB0ZW1wIDwtIG1hcCgueCA9IDE6LmRpc3RfbWF0cml4JG5fY3YsIA0KICAgICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDEgPSAuZGlzdF9tYXRyaXgkZGlzdF9pbnB1dFtbLnhdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gZGlzdF9vdXRwdXRbWy54XV0pKQ0KICAgIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KICANCiAgIyBsaXN0IG9mIGtlcm5lbCBmdW5jdGlvbnMNCiAgbGlzdF9mdW5zIDwtIGxpc3QoZ2F1c3NpYW4gPSBnYXVzc2lhbl9rZXJuZWwsDQogICAgICAgICAgICAgICAgICAgIGVwYW5lY2huaWtvdiA9IGVwYW5lY2huaWtvdl9rZXJuZWwsDQogICAgICAgICAgICAgICAgICAgIGJpd2VpZ2h0ID0gYml3ZWlnaHRfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICB0cml3ZWlnaHQgPSB0cml3ZWlnaHRfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICB0cmlhbmd1bGFyID0gdHJpYW5ndWxhcl9rZXJuZWwsDQogICAgICAgICAgICAgICAgICAgIG5haXZlID0gbmFpdmVfa2VybmVsKQ0KICANCiAgIyBlcnJvciBmb3IgYWxsIGtlcm5lbCBmdW5jdGlvbnMNCiAgZXJyb3JfZnVuYyA8LSBrZXJuZWxfcmVhbCAlPiUNCiAgICBtYXAoLmYgPSB+IChcKHgpIGxpc3RfZnVuc1tbLnhdXSguZXAgPSB4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0X21hdHJpeCA9IGRpc3RfYWxsW1sueF1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIgPSB0cmFpbl9yZXNwb25zZVttYWNoMiRpZDJdKS9uX2N2KSkNCiAgbmFtZXMoZXJyb3JfZnVuYykgPC0ga2VybmVsX3JlYWwNCg0KICAjIE9wdGltaXphdGlvbg0KICBwYXJhbWV0ZXJzIDwtIG1hcCgueCA9IGtlcm5lbF9yZWFsLA0KICAgICAgICAgICAgICAgICAgICAuZiA9IH4gZ3JpZE9wdGltaXplcl9NaXgob2JqX2Z1biA9IGVycm9yX2Z1bmNbWy54XV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIgPSBzZXRHcmlkUGFyYW0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWxlbnQgPSBzaWxlbnQpKQ0KICBuYW1lcyhwYXJhbWV0ZXJzKSA8LSBrZXJuZWxfcmVhbA0KICByZXR1cm4obGlzdChvcHRfcGFyYW1ldGVycyA9IHBhcmFtZXRlcnMsDQogICAgICAgICAgICAgIGFkZF9wYXJhbWV0ZXJzID0gbGlzdChzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gYWxwKSwNCiAgICAgICAgICAgICAgYmFzaWNfbWFjaGluZXMgPSBtYWNoMikpDQp9DQpgYGANCg0KLS0tDQoNCj4gKipFeGFtcGxlLjUqKjogV2UgYXBwcm94aW1hdGUgdGhlIHNtb290aGluZyBwYXJhbWV0ZXIgb2YgYEJvc3RvbmAgZGF0YS4NCg0KLS0tDQoNCmBgYHtyfQ0KdHJhaW4gPC0gbG9naWNhbChucm93KGRmKSkNCnRyYWluW3NhbXBsZShsZW5ndGgodHJhaW4pLCBmbG9vcigwLjc1Km5yb3coZGYpKSldIDwtIFRSVUUNCg0KcGFyYW0gPC0gZml0X3BhcmFtZXRlcl9NaXgodHJhaW5faW5wdXQgPSBkZlt0cmFpbiwgMTo0XSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gZGYkU3BlY2llc1t0cmFpbl0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IGMoImtubiIsICJyZiIsICJ4Z2IiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IC41MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbHMgPSBjKCJnYXVzc2lhbiIsICJiaXdlaWdodCIsICJ0cmlhbmd1bGFyIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRCYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljUGFyYW1ldGVyX01peChrID0gMjo2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDE6NSoxMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gMTo1KjEwMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peChtaW5fYWxwaGEgPSAxZS01LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2FscGhhID0gMjAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fYmV0YSA9IDFlLTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9iZXRhID0gMC41KSkNCmBgYA0KDQpgYGB7cn0NCnBhcmFtJG9wdF9wYXJhbWV0ZXJzICU+JQ0KICBtYXBfZGZjKC5mID0gfiAueCRvcHRfcGFyYW0pICU+JQ0KICBwcmludA0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+UHJlZGljdGlvbjwvdT48L3NwYW4+DQo9PT0NCg0KVGhlIHNtb290aGluZyBwYXJhbWV0ZXIgb2J0YWluZWQgZnJvbSB0aGUgcHJldmlvdXMgc2VjdGlvbiBjYW4gYmUgdXNlZCB0byBtYWtlIHRoZSBmaW5hbCBwcmVkaWN0aW9ucy4gDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1Pktlcm5lbCBmdW5jdGlvbnM8L3U+PC9zcGFuPg0KLS0tDQoNClNldmVyYWwgdHlwZXMgb2Yga2VybmVsIGZ1bmN0aW9ucyB1c2VkIGZvciB0aGUgYWdncmVnYXRpb24gYXJlIGRlZmluZWQgaW4gdGhpcyBzZWN0aW9uLg0KDQotICoqQXJndW1lbnQqKjoNCiAgICANCiAgICAtIGB0aGV0YWAgOiBhIDJELXZlY3RvciBvZiB0aGUgcGFyYW1ldGVyICQoXGFscGhhLCBcYmV0YSkkIHVzZWQuDQogICAgLSBgLnkyYCA6IHRoZSB2ZWN0b3Igb2YgcmVzcG9uc2UgdmFyaWFibGUgb2YgdGhlIHNlY29uZCBwYXJ0ICRcbWF0aGNhbHtEfV97XGVsbH0kIG9mIHRoZSB0cmFpbmluZyBkYXRhLCB3aGljaCBpcyB1c2VkIGZvciB0aGUgYWdncmVnYXRpb24uDQogICAgLSBgLmRpc3RhbmNlYCA6IHRoZSBkaXN0YW5jZSBtYXRyaXggb2JqZWN0IG9idGFpbmVkIGZyb20gYGRpc3RfbWF0cml4YCBmdW5jdGlvbi4NCiAgICAtIGAua2VybmAgOiB0aGUgc3RyaW5nIHNwZWNpZnlpbmcgdGhlIGtlcm5lbCBmdW5jdGlvbi4gQnkgZGVmYXVsdCwgYC5rZXJuID0gImdhdXNzaWFuImAuDQogICAgLSBgLmludl9zaWcsIC5hbHBgIDogdGhlIHBhcmFtZXRlcnMgb2YgZXhwb25lbnRpYWwga2VybmVsIGZ1bmN0aW9uLg0KICAgIC0gYC5tZXRoYCA6IHRoZSBzdHJpbmcgb2Ygb3B0aW1pemF0aW9uIG1ldGhvZHMgdG8gYmUgbGlua2VkIHRvIHRoZSBuYW1lIG9mIHRoZSBrZXJuZWwgZnVuY3Rpb25zIGlmIGFueSBwZXJ0aWN1bGFyIGtlcm5lbHMgYXJlIHVzZWQgbW9yZSB0aGFuIG9uY2UgKG1heWJlIHdpdGggZGlmZmVyZW50IG9wdGltaXp0YWlvbiBtZXRob2Qgc3VjaCBhcyAiZ2F1c3NpYW4iIGtlcm5lbCwgY2FuIGJlIHVzZWQgd2l0aCBib3RoICJncmFkIiBhbmQgImdyaWQiIG9wdGltaXphdGlvbiBtZXRob2RzKS4NCiAgICANCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIHRoZSBwcmVkaWN0aW9ucyBvZiB0aGUgYWdncmVnYXRpb24gbWV0aG9kIGV2YWx1YXRlZCB3aXRoIHRoZSBnaXZlbiBwYXJhbWV0ZXIuDQoNCg0KYGBge3J9DQprZXJuZWxfcHJlZF9NaXggPC0gZnVuY3Rpb24odGhldGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLnkyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3QyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5rZXJuID0gImdhdXNzaWFuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZyA9IHNxcnQoLjUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwID0gMil7DQogIGRpc3REIDwtIGFzLm1hdHJpeCh0aGV0YVsxXSouZGlzdDErdGhldGFbMl0qLmRpc3QxKQ0KICAjIEtlcm5lbCBmdW5jdGlvbnMNCiAgIyA9PT09PT09PT09PT09PT09DQogIGdhdXNzaWFuX2tlcm5lbCA8LSBmdW5jdGlvbihELA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmludl9zaWdtYSA9IC5pbnZfc2lnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmFscGhhID0gLmFscCl7DQogICAgdGVtMCA8LSBleHAoLSBEXiguYWxwaGEvMikqLmludl9zaWdeLmFscGhhKQ0KICAgIHlfaGF0IDwtIG1hcF9kZmMoLnggPSAxOm5jb2wodGVtMCksDQogICAgICAgICAgICAgICAgICAgICAuZiA9IChcKHhfKSB0aWJibGUoInt4X30iIDo9IHRhcHBseSh0ZW0wWywgeF9dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElOREVYID0gLnkyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gc3VtKSkpKSAlPiUNCiAgICAgICAgbWFwX2NociguZiA9IChcKHgpIG5hbWVzKHdoaWNoLm1heCh4KSkpKQ0KICAgIHJldHVybihhcy52ZWN0b3IoeV9oYXQpKQ0KICB9DQoNCiAgIyBFcGFuZWNobmlrb3YNCiAgZXBhbmVjaG5pa292X2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICB0ZW0wIDwtIDEtIEQNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSBtYXBfZGZjKC54ID0gMTpuY29sKHRlbTApLA0KICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4XykgdGliYmxlKCJ7eF99IiA6PSB0YXBwbHkodGVtMFssIHhfXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTkRFWCA9IC55MiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IHN1bSkpKSkgJT4lDQogICAgICAgIG1hcF9jaHIoLmYgPSAoXCh4KSBuYW1lcyh3aGljaC5tYXgoeCkpKSkNCiAgICByZXR1cm4oYXMudmVjdG9yKHlfaGF0KSkNCiAgfQ0KICAjIEJpd2VpZ2h0DQogIGJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICB0ZW0wIDwtIDEtIEQNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSBtYXBfZGZjKC54ID0gMTpuY29sKHRlbTApLA0KICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4XykgdGliYmxlKCJ7eF99IiA6PSB0YXBwbHkodGVtMFssIHhfXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTkRFWCA9IC55MiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IHN1bSkpKSkgJT4lDQogICAgICAgIG1hcF9jaHIoLmYgPSAoXCh4KSBuYW1lcyh3aGljaC5tYXgoeCkpKSkNCiAgICByZXR1cm4oYXMudmVjdG9yKHlfaGF0KSkNCiAgfQ0KDQogICMgVHJpd2VpZ2h0DQogIHRyaXdlaWdodF9rZXJuZWwgPC0gZnVuY3Rpb24oRCl7DQogICAgdGVtMCA8LSAxLSBEDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gbWFwX2RmYygueCA9IDE6bmNvbCh0ZW0wKSwNCiAgICAgICAgICAgICAgICAgICAgIC5mID0gKFwoeF8pIHRpYmJsZSgie3hffSIgOj0gdGFwcGx5KHRlbTBbLCB4X10sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU5ERVggPSAueTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSBzdW0pKSkpICU+JQ0KICAgICAgICBtYXBfY2hyKC5mID0gKFwoeCkgbmFtZXMod2hpY2gubWF4KHgpKSkpDQogICAgcmV0dXJuKGFzLnZlY3Rvcih5X2hhdCkpDQogIH0NCg0KICAjIFRyaWFuZ3VsYXINCiAgdHJpYW5ndWxhcl9rZXJuZWwgPC0gZnVuY3Rpb24oRCl7DQogICAgdGVtMCA8LSAxLSBEDQogICAgdGVtMFt0ZW0wIDwgMF0gPC0gMA0KICAgIHlfaGF0IDwtIG1hcF9kZmMoLnggPSAxOm5jb2wodGVtMCksDQogICAgICAgICAgICAgICAgICAgICAuZiA9IChcKHhfKSB0aWJibGUoInt4X30iIDo9IHRhcHBseSh0ZW0wWywgeF9dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElOREVYID0gLnkyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gc3VtKSkpKSAlPiUNCiAgICAgICAgbWFwX2NociguZiA9IChcKHgpIG5hbWVzKHdoaWNoLm1heCh4KSkpKQ0KICAgIHJldHVybihhcy52ZWN0b3IoeV9oYXQpKQ0KIH0NCiAgIyBOYWl2ZQ0KICBuYWl2ZV9rZXJuZWwgPC0gZnVuY3Rpb24oRCl7DQogICAgICB0ZW0wIDwtIChEIDwgMSkNCiAgICAgIHlfaGF0IDwtIG1hcF9kZmMoLnggPSAxOm5jb2wodGVtMCksDQogICAgICAgICAgICAgICAgICAgICAgIC5mID0gKFwoeF8pIHRpYmJsZSgie3hffSIgOj0gdGFwcGx5KHRlbTBbLCB4X10sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU5ERVggPSAueTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSBzdW0pKSkpICU+JQ0KICAgICAgICBtYXBfY2hyKC5mID0gKFwoeCkgbmFtZXMod2hpY2gubWF4KHgpKSkpDQogICAgcmV0dXJuKGFzLnZlY3Rvcih5X2hhdCkpDQogIH0NCiAgIyBQcmVkaWN0aW9uDQogIGtlcm5lbF9saXN0IDwtIGxpc3QoZ2F1c3NpYW4gPSBnYXVzc2lhbl9rZXJuZWwsDQogICAgICAgICAgICAgICAgICAgICAgZXBhbmVjaG5pa292ID0gZXBhbmVjaG5pa292X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICBiaXdlaWdodCA9IGJpd2VpZ2h0X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICB0cml3ZWlnaHQgPSB0cml3ZWlnaHRfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICAgIHRyaWFuZ3VsYXIgPSB0cmlhbmd1bGFyX2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICBuYWl2ZSA9IG5haXZlX2tlcm5lbCkNCiAgcmVzIDwtIHRpYmJsZShhcy52ZWN0b3Ioa2VybmVsX2xpc3RbWy5rZXJuXV0oRCA9IGRpc3REKSkpDQogIG5hbWVzKHJlcykgPC0gLmtlcm4NCiAgcmV0dXJuKHJlcykNCn0NCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uczwvdT48L3NwYW4+OiBgcHJlZGljdF9NaXhgDQotLS0NCg0KLSAqKkFyZ3VtZW50Kio6DQogICAgDQogICAgLSBgZml0dGVkX21vZGVsc2AgOiB0aGUgb2JqZWN0IG9idGFpbmVkIGZyb20gYGZpdF9wYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbi4NCiAgICAtIGBuZXdfZGF0YWAgOiB0aGUgdGVzdGluZyBkYXRhIHRvIGJlIHByZWRpY3RlZC4NCiAgICAtIGBuZXdfcHJlZGAgOiB0aGUgcHJlZGljdGlvbnMgb2YgdGhlIHRlc3RpbmcgZGF0YSBgbmV3X2RhdGFgICh3aGVuIHRoZSBiYXNpYyBtYWNoaW5lcyBhcmUgbm90IGNvbnN0cnVjdGVkKS4NCiAgICAtIGB0ZXN0X3Jlc3BvbnNlYCA6IHRoZSB0ZXN0aW5nIHJlc3BvbnNlIHZhcmlhYmxlLCBpdCBpcyBvcHRpb25hbC4gSWYgaXQgaXMgZ2l2ZW4sIHRoZSBtZWFuIHNxdWFyZSBlcnJvciBvbiB0aGUgdGVzdGluZyBkYXRhIGlzIGFsc28gY29tcHV0ZWQuIEJ5IGRlZmF1bHQsIGB0ZXN0X3Jlc3BvbnNlID0gTlVMTGAuDQogICAgDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhICpsaXN0KiBvZiB0aGUgZm9sbG93aW5nIG9iamVjdHM6DQogICAgDQogICAgLSBgZml0dGVkX2FnZ3JlZ2F0ZWAgOiB0aGUgcHJlZGljdGlvbnMgYnkgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMuDQogICAgLSBgZml0dGVkX21hY2hpbmVgIDogdGhlIHByZWRpY3Rpb25zIGdpdmVuIGJ5IGFsbCB0aGUgYmFzaWMgbWFjaGluZXMuDQogICAgLSBgbXNlYCA6IHRoZSBtZWFuIHNxdWFyZSBlcnJvciBjb21wdXRlZCBvbmx5IGlmIHRoZSBgdGVzdF9yZXBvbnNlYCBhcmd1bWVudCBpcyBub3RlIGBOVUxMYC4NCg0KDQpgYGB7cn0NCiMgUHJlZGljdGlvbg0KcHJlZGljdF9NaXggPC0gZnVuY3Rpb24oZml0dGVkX21vZGVscywNCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld19kYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3X3ByZWQgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IE5VTEwpew0KICBvcHRfcGFyYW0gPC0gZml0dGVkX21vZGVscyRvcHRfcGFyYW1ldGVycw0KICBhZGRfcGFyYW0gPC0gZml0dGVkX21vZGVscyRhZGRfcGFyYW1ldGVycw0KICBiYXNpY19tYWNoIDwtIGZpdHRlZF9tb2RlbHMkYmFzaWNfbWFjaGluZXMNCiAga2VybjAgPC0gbmFtZXMob3B0X3BhcmFtKQ0KICBuZXdfZGF0YV8gPC0gbmV3X2RhdGENCiAgIyBpZiBiYXNpYyBtYWNoaW5lcyBhcmUgYnVpbHQNCiAgaWYoaXMubGlzdChiYXNpY19tYWNoJG1vZGVscykpew0KICAgIG1hdF9pbnB1dCA8LSBhcy5tYXRyaXgoYmFzaWNfbWFjaCR0cmFpbl9kYXRhJHRyYWluX2lucHV0KQ0KICAgIGlmKGFkZF9wYXJhbSRzY2FsZV9pbnB1dCl7DQogICAgICBuZXdfZGF0YV8gPC0gc2NhbGUobmV3X2RhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IGJhc2ljX21hY2gkc2NhbGVfbWluLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IGJhc2ljX21hY2gkc2NhbGVfbWF4IC0gYmFzaWNfbWFjaCRzY2FsZV9taW4pDQogICAgfQ0KICAgIGlmKGlzLm1hdHJpeChuZXdfZGF0YV8pKXsNCiAgICAgIG1hdF90ZXN0IDwtIG5ld19kYXRhXw0KICAgICAgZGZfdGVzdCA8LSBhc190aWJibGUobmV3X2RhdGFfKQ0KICAgIH0gZWxzZSB7DQogICAgICBtYXRfdGVzdCA8LSBhcy5tYXRyaXgobmV3X2RhdGFfKQ0KICAgICAgZGZfdGVzdCA8LSBuZXdfZGF0YV8NCiAgICB9DQogICAgDQogICAgIyBQcmVkaWN0aW9uIHRlc3QgYnkgYmFzaWMgbWFjaGluZXMNCiAgICBidWlsdF9tb2RlbHMgPC0gYmFzaWNfbWFjaCRtb2RlbHMNCiAgICBwcmVkX3Rlc3QgPC0gZnVuY3Rpb24obWV0aCl7DQogICAgICBpZihtZXRoID09ICJrbm4iKXsNCiAgICAgICAgcHJlIDwtIDE6bGVuZ3RoKGJ1aWx0X21vZGVsc1tbbWV0aF1dKSAlPiUNCiAgICAgICAgICBtYXBfZGZjKC5mID0gKFwoaykgdGliYmxlKCd7e2t9fScgOj0gRk5OOjprbm4odHJhaW4gPSBtYXRfaW5wdXRbIWJhc2ljX21hY2gkaWQyLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gbWF0X3Rlc3QsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbCA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSR0cmFpbl9yZXNwb25zZVshYmFzaWNfbWFjaCRpZDJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gYnVpbHRfbW9kZWxzW1ttZXRoXV1bW2tdXSkpKSkNCiAgICAgIH0NCiAgICAgIGlmKG1ldGggPT0gInhnYiIpew0KICAgICAgICBwcmUgPC0gMTpsZW5ndGgoYnVpbHRfbW9kZWxzW1ttZXRoXV0pICU+JQ0KICAgICAgICAgIG1hcF9kZmMoLmYgPSAoXChrKSB0aWJibGUoJ3t7a319JyA6PSBhcy52ZWN0b3IoYmFzaWNfbWFjaCR0cmFpbl9kYXRhJGNsYXNzZXNbcHJlZGljdChidWlsdF9tb2RlbHNbW21ldGhdXVtba11dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXRfdGVzdCldKSkpKQ0KICAgICAgfQ0KICAgICAgaWYobWV0aCA9PSAiYWRhYm9vc3QiKXsNCiAgICAgICAgcHJlIDwtIDE6bGVuZ3RoKGJ1aWx0X21vZGVsc1tbbWV0aF1dKSAlPiUNCiAgICAgICAgICBtYXBfZGZjKC5mID0gKFwoaykgdGliYmxlKCd7e2t9fScgOj0gYXMudmVjdG9yKHByZWRpY3QuYm9vc3RpbmcoYnVpbHRfbW9kZWxzW1ttZXRoXV1bW2tdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoZGZfdGVzdCkpJGNsYXNzKSkpKQ0KICAgICAgfQ0KICAgICAgaWYoIShtZXRoICVpbiUgYygieGdiIiwgImtubiIsICJhZGFib29zdCIpKSl7DQogICAgICAgIHByZSA8LSAxOmxlbmd0aChidWlsdF9tb2RlbHNbW21ldGhdXSkgJT4lDQogICAgICAgICAgbWFwX2RmYyguZiA9IChcKGspIHRpYmJsZSgne3trfX0nIDo9IGFzLnZlY3RvcihwcmVkaWN0KGJ1aWx0X21vZGVsc1tbbWV0aF1dW1trXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl90ZXN0LCB0eXBlID0gJ2NsYXNzJykpKSkpDQogICAgICB9DQogICAgICBjb2xuYW1lcyhwcmUpIDwtIG5hbWVzKGJ1aWx0X21vZGVsc1tbbWV0aF1dKQ0KICAgICAgcmV0dXJuKHByZSkNCiAgICB9DQogICAgcHJlZF90ZXN0X2FsbCA8LSBuYW1lcyhidWlsdF9tb2RlbHMpICU+JQ0KICAgICAgbWFwX2RmYyguZiA9IHByZWRfdGVzdCkNCiAgfSBlbHNlew0KICAgIHByZWRfdGVzdF9hbGwgPC0gbmV3X3ByZWQNCiAgfQ0KICAjIFByZWRpY3Rpb24gdHJhaW4yDQogIHByZWRfdHJhaW5fYWxsIDwtIGJhc2ljX21hY2gkZml0dGVkX3JlbWFpbg0KICBjb2xuYW1lcyhwcmVkX3Rlc3RfYWxsKSA8LSBjb2xuYW1lcyhwcmVkX3RyYWluX2FsbCkNCiAgZF90cmFpbiA8LSBkaW0ocHJlZF90cmFpbl9hbGwpDQogIGRfdGVzdCA8LSBkaW0ocHJlZF90ZXN0X2FsbCkNCiAgZF90cmFpbl9pbnB1dCA8LSBkaW0obWF0X2lucHV0W2Jhc2ljX21hY2gkaWQyLF0pDQogIGRfdGVzdF9pbnB1dCA8LSBkaW0obmV3X2RhdGFfKQ0KICBwcmVkX3Rlc3RfbWF0IDwtIGFzLm1hdHJpeChwcmVkX3Rlc3RfYWxsKQ0KICBwcmVkX3RyYWluX21hdCA8LSBhcy5tYXRyaXgocHJlZF90cmFpbl9hbGwpDQogICMgRGlzdGFuY2UgbWF0cml4DQogIGRpc3RfbWF0IDwtIGZ1bmN0aW9uKGtlcm5lbCA9ICJnYXVzaWFuIil7DQogICAgcmVzXzEgPC0gcmVzXzIgPC0gTlVMTA0KICAgIGlmKCEoa2VybmVsICVpbiUgYygibmFpdmUiLCAidHJpYW5ndWxhciIpKSl7DQogICAgICByZXNfMSA8LSAxOmRfdGVzdF9pbnB1dFsxXSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKHJvd1N1bXMoKG1hdF9pbnB1dFtiYXNpY19tYWNoJGlkMixdIC0gbWF0cml4KHJlcChuZXdfZGF0YV9baWQsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZF90cmFpbl9pbnB1dFsxXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGRfdHJhaW5faW5wdXRbMl0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKSleMikpKSkpDQogICAgfQ0KICAgIGlmKGtlcm5lbCA9PSAidHJpYW5ndWxhciIpew0KICAgICAgcmVzXzEgPC0gMTpkX3Rlc3RfaW5wdXRbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKGFicyhtYXRfaW5wdXRbYmFzaWNfbWFjaCRpZDIsXSAtIG1hdHJpeChyZXAobmV3X2RhdGFfW2lkLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfdHJhaW5faW5wdXRbMV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBkX3RyYWluX2lucHV0WzJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKSkpKSkpKQ0KICAgIH0NCiAgICBpZihrZXJuZWwgPT0gIm5haXZlIil7DQogICAgICByZXNfMSA8LSAxOmRfdGVzdF9pbnB1dFsxXSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKGFwcGx5KGFicyhtYXRfaW5wdXRbYmFzaWNfbWFjaCRpZDIsXSAtIG1hdHJpeChyZXAobmV3X2RhdGFfW2lkLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX3RyYWluX2lucHV0WzFdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gZF90cmFpbl9pbnB1dFsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKSksIDEsIG1heCkpKSkpDQogICAgfQ0KICAgIHJldHVybihkaXN0X2lucHV0ID0gcmVzXzEpDQogIH0NCiAgZGlzdF9pbnB1dCA8LSAxOmxlbmd0aChrZXJuMCkgJT4lDQogICAgICBtYXAoLmYgPSB+IGRpc3RfbWF0KGtlcm4wWy54XSkpDQogIGRpc3RfcHJlZHMgPC0gMTpkX3Rlc3RbMV0gJT4lDQogICAgICBtYXBfZGZjKC5mID0gKFwoaWQpIHRpYmJsZSgne3tpZH19JyA6PSByb3dTdW1zKHN3ZWVwKHByZWRfdHJhaW5fbWF0LCAyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZF90ZXN0X21hdFtpZCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gIiE9IikpKSkpDQogIHByZWRpY3Rpb24gPC0gMTpsZW5ndGgoa2VybjApICU+JSANCiAgICBtYXBfZGZjKC5mID0gfiBrZXJuZWxfcHJlZF9NaXgodGhldGEgPSBvcHRfcGFyYW1bW2tlcm4wWy54XV1dJG9wdF9wYXJhbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnkyID0gYmFzaWNfbWFjaCR0cmFpbl9kYXRhJHRyYWluX3Jlc3BvbnNlW2Jhc2ljX21hY2gkaWQyXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3QxID0gZGlzdF9pbnB1dFtbLnhdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3QyID0gZGlzdF9wcmVkcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmtlcm4gPSBrZXJuMFsueF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZyA9IGFkZF9wYXJhbSRpbnZfc2lnbWEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwID0gYWRkX3BhcmFtJGFscCkpDQogIGlmKGlzLm51bGwodGVzdF9yZXNwb25zZSkpew0KICAgIHJldHVybihsaXN0KGZpdHRlZF9hZ2dyZWdhdGUgPSBwcmVkaWN0aW9uLA0KICAgICAgICAgICAgICAgIGZpdHRlZF9tYWNoaW5lID0gcHJlZF90ZXN0X2FsbCkpDQogIH0gZWxzZXsNCiAgICBlcnJvciA8LSBjYmluZChwcmVkX3Rlc3RfYWxsLCBwcmVkaWN0aW9uKSAlPiUNCiAgICAgIGRwbHlyOjptdXRhdGUoeV90ZXN0ID0gdGVzdF9yZXNwb25zZSkgJT4lDQogICAgICBkcGx5cjo6c3VtbWFyaXNlX2FsbCguZnVucyA9IH4gKC4gIT0geV90ZXN0KSkgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KC15X3Rlc3QpICU+JQ0KICAgICAgZHBseXI6OnN1bW1hcmlzZV9hbGwoLmZ1bnMgPSB+IG1lYW4oLikpDQogICAgcmV0dXJuKGxpc3QoZml0dGVkX2FnZ3JlZ2F0ZSA9IHByZWRpY3Rpb24sDQogICAgICAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkX3Rlc3RfYWxsLA0KICAgICAgICAgICAgICAgIG1pc19lcnJvciA9IGVycm9yLA0KICAgICAgICAgICAgICAgIGFjY3VyYWN5ID0gMSAtIGVycm9yKSkNCiAgfQ0KfQ0KYGBgDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS42KiogQWdncmVnYXRpb24gb24gYGlyaXNgIGRhdGFzZXQuDQoNCi0tLQ0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYE1peENvYnJhQ2xhc3NgIChkaXJlY3QgYWdncmVnYXRpb24pDQo9PT0NCg0KVGhpcyBmdW5jdGlvbiBwdXRzIHRvZ2V0aGVyIGFsbCB0aGUgZnVuY3Rpb25zIGFib3ZlIGFuZCBwcm92aWRlcyB0aGUgZGVzaXJlIHJlc3VsdCBvZiBNaXhDb2JyYSBhZ2dyZWdhdGlvbiBtZXRob2QuDQoNCi0gKipBcmd1bWVudCoqOiBhbGwgb2YgaXRzIGFyZ3VtZW50cyBhcmUgYWxyZWFkeSBkZXNjcmliZWQgaW4gdGhlIHByZXZpb3VzIHBhcnRzLg0KICAgIA0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybiBhICpsaXN0KiBvZiB0aGUgZm9sbG93aW5nIG9iamVjdHM6DQogICAgDQogICAgLSBgZml0dGVkX2FnZ3JlZ2F0ZWAgOiB0aGUgcHJlZGljdGVkIHZhbHVlcyBvZiB0aGUgYWdncmVnYXRpb24gbWV0aG9kLg0KICAgIC0gYGZpdHRlZF9tYWNoaW5lYCA6IHRoZSBwcmVkaWN0ZWQgdmFsdWVzIG9mIGFsbCB0aGUgYmFzaWMgbWFjaGluZXMgYnVpbHQgb24gJFxtYXRoY2Fse0R9X3trfSQuDQogICAgLSBgcHJlZF90cmFpbjJgIDogdGhlIHByZWRpY3Rpb24gb2YgJFxtYXRoY2Fse0R9X3tcZWxsfSQsIGdpdmVuIGJ5IGFsbCB0aGUgYmFzaWMgbWFjaGllbnMuDQogICAgLSBgb3B0X3BhcmFtZXRlcmAgOiAgdGhlIG9ic2VydmVkIG9wdGltYWwgcGFyYW1ldGVycy4NCiAgICAtIGBtaXNfY2xhc3NgIDogdGVzdGluZyBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLg0KICAgIC0gYGFjY3VyYWN5YCA6IHRlc3RpbmcgYWNjdXJhY3kuDQogICAgLSBga2VybmVsc2AgOiBhIHZlY3RvciBvZiBrZXJuZWwgZnVuY3Rpb24gdXNlZC4NCiAgICAtIGBpbmRfRDJgIDogYSBsb2dpY2FsIHZlY3RvciBpbmRpY2F0aW5nIHRoZSBwYXJ0IG9mIHRyYWluaW5nIGRhdGEgY29ycmVzcG9uZGluZyB0byAkXG1hdGhjYWx7RH1fe1xlbGx9JCAoYFRSVUVgKS4NCg0KYGBge3J9DQpNaXhDb2JyYUNsYXNzIDwtIGZ1bmN0aW9uKHRyYWluX2lucHV0LCANCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3ByZWRpY3Rpb25zID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcHJlZGljdGlvbnMgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IE5VTEwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gMC41LCANCiAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICAgICAgICBhbHAgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9ICJnYXVzc2lhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICBzZXRCYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtID0gc2V0R3JpZFBhcmFtZXRlcl9NaXgoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNpbGVudCA9IEZBTFNFKXsNCiAgIyBidWlsZCBtYWNoaW5lcyArIHR1bmUgcGFyYW1ldGVyDQogIGZpdF9tb2QgPC0gZml0X3BhcmFtZXRlcl9NaXgodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9wcmVkaWN0aW9ucyA9IHRyYWluX3ByZWRpY3Rpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gbWFjaGluZXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gc3BsaXRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gbl9jdiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBpbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gYWxwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbHMgPSBrZXJuZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEJhc2ljTWFjaGluZVBhcmFtID0gc2V0QmFzaWNNYWNoaW5lUGFyYW0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtID0gc2V0R3JpZFBhcmFtLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpbGVudCA9IHNpbGVudCkNCiAgIyBwcmVkaWN0aW9uDQogIHByZWQgPC0gcHJlZGljdF9NaXgoZml0dGVkX21vZGVscyA9IGZpdF9tb2QsDQogICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSB0ZXN0X2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgIG5ld19wcmVkID0gdGVzdF9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICB0ZXN0X3Jlc3BvbnNlID0gdGVzdF9yZXNwb25zZSkNCiAgcmV0dXJuKGxpc3QoZml0dGVkX2FnZ3JlZ2F0ZSA9IHByZWQkZml0dGVkX2FnZ3JlZ2F0ZSwNCiAgICAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkJGZpdHRlZF9tYWNoaW5lLA0KICAgICAgICAgICAgICBwcmVkX3RyYWluMiA9IGZpdF9tb2QkYmFzaWNfbWFjaGluZXMkcHJlZGljdDIsDQogICAgICAgICAgICAgIG9wdF9wYXJhbWV0ZXIgPSBmaXRfbW9kJG9wdF9wYXJhbWV0ZXJzLA0KICAgICAgICAgICAgICBtaXNfY2xhc3MgPSBwcmVkJG1pc19lcnJvciwNCiAgICAgICAgICAgICAgYWNjdXJhY3kgPSBwcmVkJGFjY3VyYWN5LA0KICAgICAgICAgICAgICBrZXJuZWxzID0ga2VybmVscywNCiAgICAgICAgICAgICAgaW5kX0QyID0gZml0X21vZCRiYXNpY19tYWNoaW5lcyRpZDIpKQ0KfQ0KYGBgDQoNCi0tLQ0KDQo+KipFeGFtcGxlLjcqKiBBIGNvbXBsZXRlIGFnZ3JlZ2F0aW9uIGlzIGltcGxlbWVudGVkIG9uIGBpcmlzYCBkYXRhLiBUaHJlZSB0eXBlcyBvZiBiYXNpYyBtYWNoaW5lcywgYW5kIHRocmVlIGRpZmZlcmVudCBrZXJuZWwgZnVuY3Rpb25zIGFyZSB1c2VkLiANCg0KLS0tDQoNCmBgYHtyfQ0KdHJhaW4gPC0gbG9naWNhbChucm93KGRmKSkNCnRyYWluW3NhbXBsZShsZW5ndGgodHJhaW4pLCBmbG9vcigwLjgqbnJvdyhkZikpKV0gPC0gVFJVRQ0KDQphZ2cgPC0gTWl4Q29icmFDbGFzcyh0cmFpbl9pbnB1dCA9IGRmW3RyYWluLCAxOjRdLA0KICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gZGYkU3BlY2llc1t0cmFpbl0sDQogICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCA9IGRmWyF0cmFpbiwxOjRdLA0KICAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSBkZiRTcGVjaWVzWyF0cmFpbl0sDQogICAgICAgICAgICAgICAgICAgbl9jdiA9IDMsDQogICAgICAgICAgICAgICAgICAgbWFjaGluZXMgPSBjKCJrbm4iLCAicmYiLCAieGdiIiksDQogICAgICAgICAgICAgICAgICAgc3BsaXRzID0gLjUsDQogICAgICAgICAgICAgICAgICAga2VybmVscyA9IGMoImdhdXNzaWFuIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5haXZlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRyaWFuZ3VsYXIiKSwNCiAgICAgICAgICAgICAgICAgICBzZXRCYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljUGFyYW1ldGVyX01peChrID0gYygyLDUsNywxMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSAyOjUqMTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gYygxLDMsNSkqMTAwKSwNCiAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peChwYXJhbWV0ZXJzID0gbGlzdChhbHBoYSA9IHNlcSgxZS0xMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMzUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aC5vdXQgPSAyNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXRhID0gYyhzZXEoMWUtMTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwLjUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgub3V0ID0gMTApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcSgxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDIwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aC5vdXQgPSAxMCkpKSkpDQpgYGANCg0KDQpgYGB7cn0NCmFnZyRhY2N1cmFjeQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+UmVmZXJlbmNlczwvc3Bhbj57LX0NCj09PQ0KDQotLS0NCg0KLSBbTW9qaXJzaGVpYmFuaSAoMTk5OSldKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9kb2kvYWJzLzEwLjEwODAvMDE2MjE0NTkuMTk5OS4xMDQ3NDE1ND9qb3VybmFsQ29kZT11YXNhMjApIGZvciBjbGFzc2lmaWNhdGlvbg0KLSBbTW9qaXJzaGVpYmFuaSAoMjAwMCldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAxNjc3MTUyMDAwMDAyNDkpIGZvciBjbGFzc2lmaWNhdGlvbg0KLSBbTW9qaXJzaGVpYmFuaSBhbmQgS29uZyAoMjAxNildKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAxNjc3MTUyMTYzMDEzMDQpIGZvciBjbGFzc2lmaWNhdGlvbg0KLSBbQmlhdSBldCBhbC4gKDIwMTYpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMDQ3MjU5WDE1MDAwOTUwKSBmb3IgcmVncmVzc2lvbg0KLSBbSGFzICgyMDIxKV0oaHR0cHM6Ly9oYWwuYXJjaGl2ZXMtb3V2ZXJ0ZXMuZnIvaGFsLTAyODg0MzMzdjUpIGZvciByZWdyZXNzaW9uIGZvciByZWdyZXNzaW9uDQotIFtGaXNjaGVyIGFuZCBNb3VnZW90ICgyMDE5KV0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMDM3ODM3NTgxODMwMjM0OSkgZm9yIGJvdGgNCi0gLi4uDQotIFtkcGx5ciB2aWRlb3NdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2hhc2h0YWcvZHBseXIpIGByIGZvbnRhd2Vzb21lOjpmYSgidmlkZW8iKWANCi0gW2dncGxvdDIgdmlkZW8gdHV0b3JpYWxdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2hhc2h0YWcvZ2dwbG90MikgYHIgZm9udGF3ZXNvbWU6OmZhKCJ2aWRlbyIpYA0KLSBbUiBmb3IgZGF0YSBzY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykNCg0KLS0tDQoNCj4gUmVhZCBhbHNvIFtLZXJuZWxBZ2dDbGFzc10oaHR0cHM6Ly9oYXNzb3RoZWEuZ2l0aHViLmlvL2ZpbGVzL0NvZGVzUGhEL0tlcm5lbEFnZ0NsYXNzLmh0bWwpLCBbS2VybmVsQWdnUmVnXShodHRwczovL2hhc3NvdGhlYS5naXRodWIuaW8vZmlsZXMvQ29kZXNQaEQvS2VybmVsQWdnUmVnLmh0bWwpIGFuZCBbTWl4Q29icmFSZWddKGh0dHBzOi8vaGFzc290aGVhLmdpdGh1Yi5pby9maWxlcy9Db2Rlc1BoRC9NaXhDb2JyYVJlZy5odG1sKSBtZXRob2QuDQoNCi0tLQ0K