1 Introduction

This is the third notebook of a series of three that outlines and elaborates upon code used to replicate the central scenario in the paper of Maximilien Baudry “NON-PARAMETRIC INDIVIDUAL CLAIM RESERVING IN INSURANCE”

https://www.institutdesactuaires.com/global/gene/link.php?doc_id=11747&fg=1
http://chaire-dami.fr/files/2016/10/Reserving-article.pdf

In this notebook we step through the process to apply machine learning techniques in order to create reserve estimates following the techniques set out in sections 3 and 4 of Baudry’s paper.

2 Preparations before modelling

The reserving data structures built in this notebook are from a simulated phone insurance dataset. The creation of that simulated data and machine learning data structures for reserving have been set out in the first and second notebooks of this series.

Rather than repeat that code in this notebook the key routines and functions have been put into the r script Baudry_functions_v2.R enabling us to quickly load them for use in this notebook.

# Importing packages

library(knitr)
library(rmdformats)
library(data.table)
library(magrittr)
library(lubridate)
library(ggplot2)
library(cowplot)
library(repr)
library(kableExtra)
library(formattable)

library(xgboost)
library(tidymodels)
library(SHAPforxgboost)

source("./Baudry_functions_v2.R")

3 Simulate data and prepare database

We can now quickly repeat the process from Notebooks 1 and 2 using the functions in the script.

3.1 Simulate policy and claim data

We start with a simulated phone insurance policy and claim dataset. I am calling the function from Notebook 1 of this series to create the dataset. Using a fixed seed will ensure you get a reproducible simulated dataset.

dt_PhoneData <- simulate_central_scenario(1234)

3.2 Join policy and claim data

We then join the simulated phone insurance policy and claim dataset. I have taken the code from Notebook 2 and wrapped it in to a function to perform the join and subsequent data tidying.

Note that if you are working with your own company data you would likely need to amend this function to work with the specifics of your data.

dt_polclaim <- join_policy_claim(dt_PhoneData,
                                 date_pol_start = "date_UW",
                                 date_pol_end = "date_lapse",
                                 date_occur = "date_occur")

3.3 Timeslice policy claim

We then time slice the joined policy and claim dataset. Again, I have taken the code from Notebook 2 and wrapped it into a function to perform the time slicing, given a set of dates that define the time slice periods.

Again, note that this function would need to be re-written to work with your own company data and more importantly it currently would not work correctly with partial payment claims.

lst_Date_slice <- floor_date(seq(as.Date("2016/1/1"), as.Date("2019/06/30"), by = 30), unit= "second") 
dt_polclaim <- time_slice_polclaim(dt_polclaim, lst_Date_slice)

3.4 Select valuation date

Before we create the RBNS and IBNER datasets we must pick a valuation point from the available timeslices. I’m selecting the 10th point for illustration, which is the 10th 30 day period from 01/01/2016, ie a valuation date of 27/09/2016.

i <- valuation <- 10
t_i <- lst_Date_slice[i] 
delta <- min(i, length(lst_Date_slice) - i + 1)

3.5 Creating RBNS dataset

We start with the RBNS dataset and by defining the list of features that will be used in the RBNS model dataset.

As you follow this example you may observe that we are not using the historic claim payment information as an explanatory feature in the RBNS reserve prediction model. Baudry’s paper and approach does allow for the inclusion of such information so it may seem somewhat strange not to include it in this worked example.

I believe the reason that it is not included is a result of the way data has been simulated, specifically that simulated claims are settled with a single payment. That assumption combined with the fact that this method is being applied to individual claim level data prevents the previous time period’s payment information being used by a machine learning model. That is because the previous period’s payment amount can only ever take the value zero for a claim requiring an RBNS reserve. (Claims where the previous paid is non-zero have been paid and settled ie no longer require an RBNS reserve). Consequently, in this central simulated example, prior payment amount is uninformative in the machine learning process and therefore cannot be included as a feature in the RBNS dataset.

The final point to note is that the code shared here deals only with the simplified payment process from Baudry’s central scenario, ie claims are settled with a single payment. Real world data with partial payments and prior payments as explanatory features would require material changes to be made to the code we have shared. Such changes are left for interested readers to make.

#define modelVars
RBNS_model_vars <- c("clm_number",
                     "pol_number",
                     "j",
                     "k",
                     "date_pol_start",
                     "date_occur",
                     "date_report",
                     "date_pay",
                     "Cover",
                     "claim_type",
                     "Brand",
                     "Model",
                     "Price",
                     "target"
    )


# Create a combined TRAIN dataset for k = 1 and all valid j delay values
dt_RBNS_train <- RBNS_Train(dt_polclaim, t_i, i, k = 1, lst_Date_slice, RBNS_model_vars)

# Create a combined TEST dataset for k = 1 and all valid j delay values
dt_RBNS_test <- RBNS_Test(dt_polclaim, t_i, delta, k = 1, lst_Date_slice, RBNS_model_vars)

The train and test datasets are then joined into a single dataset and a small amount of tidying is done to make them ready for use.

# Add a flag to determine which rows are from the trainset and which from the test set
dt_RBNS_train[, flgTrain := 1]
dt_RBNS_test[, flgTrain := 0]

# combine into a single RBNS dataset   
dt_All_RBNS <- rbind(dt_RBNS_train, dt_RBNS_test)
#write.csv(dt_All_RBNS,"dt_All_RBNS.csv", row.names = F)

The important aspects of the tidying relate to creating useable delay metrics from the numerous dates and converting some character features such as cover and claim type into factors.

# order and create some delay fields
setkey(dt_All_RBNS, clm_number, k, j)
    
dt_All_RBNS[, Count := .N , by =clm_number]

#create delay measures and convert time measures from intervals of seconds to intervals of days
dt_All_RBNS[, ':='(
  delay_uw_occ = ifelse(year(date_occur) == 2199,
                        -1,
                        ceiling((as.numeric(date_occur) - as.numeric(date_pol_start)) / (24 * 60 * 60))
                        ),
  delay_occ_rep = ifelse(year(date_occur) == 2199,
                         -1,
                         ceiling((as.numeric(date_report) - as.numeric(date_occur)) / (24 * 60 * 60))
                         ),
  delay_uw_val = ceiling((as.numeric(t_i) - as.numeric(date_pol_start)) / (24 * 60 * 60)),
  delay_rep_pay = ceiling((as.numeric(date_pay) - as.numeric(date_report)) / (24 * 60 * 60)),
  
  date_uw = ceiling(as.numeric(date_pol_start) / (24 *  60 * 60)),
  Cover = as.factor(Cover),
  claim_type = as.factor(claim_type)
  )]

3.6 Creating IBNR dataset

We can then create the IBNR dataset following a similar procedure starting by defining the list of features that will be used in the IBNR model. Then we call the functions that convert the timesliced policy and claim data into the IBNR train and test datasets.

#define IBNR modelVars
IBNR_model_vars <- c("clm_number",
                     "pol_number",
                     "j",
                     "k",
                     "exposure",
                     "date_pol_start",
                     "date_occur",
                     "date_report",
                     "date_pay",
                     "Cover",
                     "Brand",
                     "Model",
                     "Price",
                     "target")
    
# Create a combined TRAIN dataset for k = 1 and all valid j delay values
lst_IBNR_train <- IBNR_Train(dt_polclaim, t_i, i, k = 1,lst_Date_slice, IBNR_model_vars)

# Create a combined TEST dataset for k = 1 and all valid j delay values
dt_IBNR_test <- IBNR_Test(dt_polclaim, t_i, delta, k = 1,lst_Date_slice, IBNR_model_vars)

The train and test datasets are then joined into a single dataset and a small amount of tidying is done to make them ready for use.

lst_IBNR_train$Freq[, flgTrain := 1]
lst_IBNR_train$Loss[, flgTrain := 2]
dt_IBNR_test[, flgTrain := 0]

dt_All_IBNR <- rbind(lst_IBNR_train$Freq,
                     lst_IBNR_train$Loss,
                     dt_IBNR_test)
#write.csv(dt_All_IBNR,"dt_All_IBNR.csv", row.names = F)

The important aspects of the tidying relate to creating useable delay metrics from the numerous dates and converting some character features such as cover and claim type into factors.

# order and create some delay fields
setkey(dt_All_IBNR, clm_number, k, j)
    
dt_All_IBNR[, Count := .N , by =clm_number]
dt_All_IBNR[,':='( delay_uw_occ = ifelse(year(date_occur) == 2199,
                                        -1,
                                        ceiling((as.numeric(date_occur) - as.numeric(date_pol_start))
                                                  /(24*60*60))
                                          ),
                   delay_occ_rep = ifelse(year(date_occur) == 2199,
                                          -1,
                                          ceiling((as.numeric(date_report) - as.numeric(date_occur))
                                                  /(24*60*60))
                                          ),
                   delay_rep_pay = ifelse(year(date_occur) == 2199,
                                          -1,
                                          ceiling((as.numeric(date_pay) - as.numeric(date_report))
                                                  /(24*60*60))
                                          ),
                   delay_uw_val = ceiling((as.numeric(t_i) - as.numeric(date_pol_start))/(24*60*60)),
                   date_uw = ceiling(as.numeric(date_pol_start)/(24*60*60)),
                   Cover = as.factor(Cover))]

That finishes the data preparation. We can now move onto the process of building models of RBNS and IBNR and using them to predict the current reserve requirements.

4 Model build

I will use the xgboost R machine learning package to build the models. In Baudry’s original paper he used the extraTrees package; a variant upon Random Forests.

Xgboost has been selected as it is the most popular implementation of the gradient boosting machine (GBM) algorithm. GBMs have been known since around 2015 to be the most accurate algorithmic technique for regression and classification tasks.

The code and modeling process for each reserve type is very similar so they are shown in the tabbed sections below rather that repeating in-line.

4.1 RBNS model

Starting with the RBNS reserves we will first build a model upon the training data using a cross validated approach to enable selection of key xgboost hyperparameters.

Then using optimal parameters we retrain on the complete training dataset. This trained model is then scored against the test data in order to create predictions from which the RBNS reserve can be calculated.

4.1.1 Creating xgboost dataset

Xgboost requires it’s data to be in a specific format; a special matrix form called a DMatrix, for which there is a function xgb.DMatrix. Not all variable types can be passed to xgb.DMatrix in particular categorical or nominal variables such as Brand have to be converted from text to numeric values.

For this example I’ve chosen to use the parsnip package and create a recipe that converts nominal predictor values into a one-hot-encoded form.

RBNS_predictors <- c("j",
                     "k",
                     "Cover",
                     "claim_type",
                     "Brand",
                     "Model",
                     "Price",
                     #"date_uw",
                     #"delay_uw_occ",
                     "delay_occ_rep")

rowList_RBNS <- list(train=dt_All_RBNS[, which(flgTrain==1)],
                test=dt_All_RBNS[, which(flgTrain==0)],
                all = 1:nrow(dt_All_RBNS))


RBNS_rec <- recipe( ~ ., data = dt_All_RBNS[, RBNS_predictors, with = FALSE]) %>%
  step_dummy(all_nominal(), one_hot = TRUE) %>%
  prep()


df.RBNS_train <- bake(RBNS_rec, new_data = dt_All_RBNS[rowList_RBNS$train,] )
df.RBNS_test <- bake(RBNS_rec, new_data = dt_All_RBNS[rowList_RBNS$test,] )
df.RBNS_all <- bake(RBNS_rec, new_data = dt_All_RBNS )


xgb.RBNS_DMat.train <- xgb.DMatrix(data = as.matrix(df.RBNS_train),
                              label = dt_All_RBNS[rowList_RBNS$train, target])

xgb.RBNS_DMat.test <- xgb.DMatrix(data = as.matrix(df.RBNS_test),
                              label = dt_All_RBNS[rowList_RBNS$test, target])

xgb.RBNS_DMat.all <- xgb.DMatrix(data = as.matrix(df.RBNS_all),
                             label = dt_All_RBNS[, target])

4.1.2 Fit initial model using cross validation

Having prepared the data for xgboost I can now fit an initial model. I’ve used a simple set of hyper-parameters and used cross validation to select the optimal number of boosted trees (nrounds) for these hyper-parameter selections by calling the xgb.cv function with early stopping.

I have used the reg:tweedie objective function based upon inspection of the target variable.

summary(dt_All_RBNS[rowList_RBNS$train, target])
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00    0.00    0.00   76.44    0.00  913.00
hist(dt_All_RBNS[rowList_RBNS$train, target])

In a real world example a process of hyper-parameter tuning would be undertaken in order to select more optimal xgboost hyper-parameters.

param <- list(
  objective = "reg:tweedie",
  max_depth = 2L,            # tree-depth
  subsample = 0.75,          # randomly sample rows before fitting each tree
  colsample_bytree = 0.8,    # randomly sample columns before fitting each tree
  min.child.weight = 10,     # minimum weight per leaf
  eta = 0.1                  # Learning rate
)


# Train model with cross validation
set.seed(1984) # for repeatability

xgb_RBNS_CV <- xgb.cv(
  params                 = param,
  data                   = xgb.RBNS_DMat.train,
  nrounds                = 500,        # Maximum number of trees to build
  nfold = 5,
  early_stopping_rounds  = 10L,        # Stops algorithm early if performance has not improved in 
  print_every_n          = 10L,        # How often to print to console
  prediction             = TRUE        # Keeps the predictions
)
## [1]  train-tweedie-nloglik@1.5:197.331256+2.882719   test-tweedie-nloglik@1.5:197.332748+11.169201 
## Multiple eval metrics are present. Will use test_tweedie_nloglik@1.5 for early stopping.
## Will train until test_tweedie_nloglik@1.5 hasn't improved in 10 rounds.
## 
## [11] train-tweedie-nloglik@1.5:76.005264+0.635454    test-tweedie-nloglik@1.5:76.023500+4.728197 
## [21] train-tweedie-nloglik@1.5:34.399348+0.339311    test-tweedie-nloglik@1.5:34.444048+2.235487 
## [31] train-tweedie-nloglik@1.5:22.642844+0.121015    test-tweedie-nloglik@1.5:22.659766+1.318212 
## [41] train-tweedie-nloglik@1.5:20.103612+0.194808    test-tweedie-nloglik@1.5:20.130332+1.028970 
## [51] train-tweedie-nloglik@1.5:19.614061+0.213816    test-tweedie-nloglik@1.5:19.648131+0.982517 
## [61] train-tweedie-nloglik@1.5:19.488429+0.224332    test-tweedie-nloglik@1.5:19.531806+0.963364 
## [71] train-tweedie-nloglik@1.5:19.438496+0.229845    test-tweedie-nloglik@1.5:19.484217+0.961241 
## [81] train-tweedie-nloglik@1.5:19.411388+0.236892    test-tweedie-nloglik@1.5:19.464310+0.958345 
## [91] train-tweedie-nloglik@1.5:19.392717+0.238659    test-tweedie-nloglik@1.5:19.457403+0.962381 
## [101]    train-tweedie-nloglik@1.5:19.378791+0.238025    test-tweedie-nloglik@1.5:19.453831+0.959511 
## [111]    train-tweedie-nloglik@1.5:19.367772+0.238858    test-tweedie-nloglik@1.5:19.450139+0.962756 
## [121]    train-tweedie-nloglik@1.5:19.356093+0.239343    test-tweedie-nloglik@1.5:19.458886+0.961838 
## Stopping. Best iteration:
## [113]    train-tweedie-nloglik@1.5:19.364408+0.238899    test-tweedie-nloglik@1.5:19.449566+0.961121

Having fit the model we store the out-of-fold predictions.

dt_All_RBNS[rowList_RBNS$train, preds_oof := xgb_RBNS_CV$pred]

4.1.3 Fit final model on all training data

Having fit the model using 5 fold cross validation we observe the optimum number of fitting rounds to be 113.

We can then use this to train a final model on all the data.

xgb_RBNS_Fit <- xgb.train(
  params                 = param,
  data                   = xgb.RBNS_DMat.train,
  nrounds                = xgb_RBNS_CV$best_iteration,
# base_score             = 1,
  watchlist              = list(train=xgb.RBNS_DMat.train, test=xgb.RBNS_DMat.test) ,
  print_every_n          = 10
)
## [1]  train-tweedie-nloglik@1.5:197.150055    test-tweedie-nloglik@1.5:82.789551 
## [11] train-tweedie-nloglik@1.5:75.865128 test-tweedie-nloglik@1.5:32.100452 
## [21] train-tweedie-nloglik@1.5:34.546490 test-tweedie-nloglik@1.5:14.854555 
## [31] train-tweedie-nloglik@1.5:22.767673 test-tweedie-nloglik@1.5:9.884780 
## [41] train-tweedie-nloglik@1.5:20.138584 test-tweedie-nloglik@1.5:8.672418 
## [51] train-tweedie-nloglik@1.5:19.639982 test-tweedie-nloglik@1.5:8.421473 
## [61] train-tweedie-nloglik@1.5:19.497528 test-tweedie-nloglik@1.5:8.333148 
## [71] train-tweedie-nloglik@1.5:19.442001 test-tweedie-nloglik@1.5:8.295179 
## [81] train-tweedie-nloglik@1.5:19.415724 test-tweedie-nloglik@1.5:8.274471 
## [91] train-tweedie-nloglik@1.5:19.400574 test-tweedie-nloglik@1.5:8.264827 
## [101]    train-tweedie-nloglik@1.5:19.389402 test-tweedie-nloglik@1.5:8.261550 
## [111]    train-tweedie-nloglik@1.5:19.382881 test-tweedie-nloglik@1.5:8.261103 
## [113]    train-tweedie-nloglik@1.5:19.381304 test-tweedie-nloglik@1.5:8.260632
dt_All_RBNS[, preds_full := predict(xgb_RBNS_Fit,xgb.RBNS_DMat.all)]

4.1.4 Inspect model fit

Having fitted the full model we can then inspect the model fit. The traditional way of inspecting global model feature importance is to use the gains chart.

#default feature importance by gain
featImp_RBNS <- xgb.importance(xgb_RBNS_Fit, feature_names = colnames(xgb.RBNS_DMat.train))
xgb.plot.importance(featImp_RBNS, main="Feature Importance - RBNS")

An increasingly popular and more robust approach is to use SHAP values https://github.com/slundberg/shap. The SHAP equivalent of the feature importance chart is shown below.

# Return the SHAP values and ranked features by mean|SHAP|
shap_values <- shap.values(xgb_model = xgb_RBNS_Fit, X_train = as.matrix(df.RBNS_train))

# Prepare the long-format data:
shap_long <- shap.prep(shap_contrib = shap_values$shap_score, X_train =  as.matrix(df.RBNS_train))

# **SHAP summary plot**
shap.plot.summary(shap_long, dilute = nrow(df.RBNS_train)/10000)

A second useful chart is the partial dependence plot. This shows how the values of a predictive (input) feature influence the predicted (output) value, while holding the values of all other predictive (input) features constant. It is also known as the marginal effect.

Here we show the partial dependence plots for the top 4 SHAP features.

fig_list <- lapply(names(shap_values$mean_shap_score)[1:4], 
                   shap.plot.dependence,
                   data_long = shap_long,
                   dilute = nrow(shap_long)/ 10000)

gridExtra::grid.arrange(grobs = fig_list, ncol = 2)

The feature importance and partial dependency plots provide quick insight into the model.

  • We see that claim development period, j, is the most important feature and that the RBNS reserve is smaller for larger values of j.
  • The second most important feature is claimtype where breakage claims are associated with lower RBNS reserves. This is as expected from the data generating process where breakage claims come from a Beta distribution with a lower mean.
  • The third most important feature is phone price where there is linear increase in RBNS reserve with increasing phone price. This is also as expected from the data generating process.
  • The fourth feature is phone brand which again follows expectations from the data generating process.

SHAP values can also be used to show the components of a single prediction. In the following plot we show the top 4 components for each row of the data and zoom in at row 500.

# choose to show top 4 features by setting `top_n = 4`, set 6 clustering groups.  
plot_data <- shap.prep.stack.data(shap_contrib = shap_values$shap_score,
                                  data_percent = 10000/nrow(shap_long),
                                  top_n = 4,
                                  n_groups = 6)
  
# choose to zoom in at location 500, set y-axis limit using `y_parent_limit`  
# it is also possible to set y-axis limit for zoom-in part alone using `y_zoomin_limit`  
shap.plot.force_plot(plot_data, zoom_in_location = 500, y_parent_limit = c(-1,1))

4.1.5 Summarising RBNS reserves

By comparing the model predictions to the simulated claims run-off we can get a feel for the accuracy of the machine learning approach. Below I aggregate the RBNS predictions by claim occurrence month and compare them to the known simulated claim run-off.

dt_All_RBNS [, date_occur_YYYYMM := as.character(year(date_occur) + month(date_occur)/100 )]

dt_RBNS_summary <- dt_All_RBNS[rowList_RBNS$test,.(preds = sum(preds_full), target = sum(target)), keyby = date_occur_YYYYMM]
date_occur_YYYYMM preds target Diff Diff_pcnt
2016.04 1,444 911 533 58.5%
2016.05 5,056 5,188 -132 -2.5%
2016.06 12,569 14,257 -1,688 -11.8%
2016.07 56,427 53,815 2,612 4.9%
2016.08 325,836 326,631 -795 -0.2%
2016.09 622,619 622,179 440 0.1%
Total 1,023,951 1,022,981 970 0.1%

You can now jump back up to the beginning of the modeling section and select the IBNR frequency modeling tab.

4.2 IBNR Frequency model

IBNR reserves are built by multiplying the outpts of a claim frequency and claim severity model. The general process of building the model follows that of the RBNS reserves.

4.2.1 Creating xgboost dataset

Again we convert the data into a specific matrix form called a DMatrix. However before doing so we aggregate the data for efficiency of model fit times. There could be a loss of accuracy in aggregating data, so in practice this is something you would want to experiment with.

The other point to note is the values of flgTrain used to identify the training and test rows in our dataset. Recall from Notebook 2, in the IBNR dataset training rows for the frequency model have a flgtrain value of 1 whereas the Severity training rows have a value of 2.

IBNR_predictors <- c("j",
                     "k",
                     "Cover",
                     "Brand",
                     "Model",
                     "Price",
                     "date_uw")

# aggregate the data ... does this lead to loss of variance and accuracy?
dt_All_IBNR [, date_pol_start_YYYYMM := as.character(year(date_pol_start) + month(date_pol_start)/100 )]

dt_All_IBNR_F <- dt_All_IBNR[, .(exposure = sum(exposure),
                                   target_cost = sum(target),
                                   target_count = sum(target>0)),
                               by= c(IBNR_predictors, "date_pol_start_YYYYMM", "flgTrain")]

dt_All_IBNR_F <- dt_All_IBNR_F[exposure>0]


# setup train and test rows
rowList_IBNR_F <- list(train=dt_All_IBNR_F[, which(flgTrain==1)],
                     test=dt_All_IBNR_F[, which(flgTrain==0)],
                     all = dt_All_IBNR_F[, which(flgTrain!=2)])

# setup data for xgboost
IBNR_rec <- recipe( ~ ., data = dt_All_IBNR_F[, IBNR_predictors, with = FALSE]) %>%
  step_dummy(all_nominal(), one_hot = TRUE) %>%
  prep()

df.IBNR_F_train <- bake(IBNR_rec, new_data = dt_All_IBNR_F[rowList_IBNR_F$train,] )
df.IBNR_F_test <- bake(IBNR_rec, new_data = dt_All_IBNR_F[rowList_IBNR_F$test,] )
df.IBNR_F_all <- bake(IBNR_rec, new_data = dt_All_IBNR_F[rowList_IBNR_F$all,] )

xgb.IBNR_F_DMat.train <- xgb.DMatrix(data = as.matrix(df.IBNR_F_train),
                              weight = dt_All_IBNR_F[rowList_IBNR_F$train, exposure],
                              label = dt_All_IBNR_F[rowList_IBNR_F$train, target_count])

xgb.IBNR_F_DMat.test <- xgb.DMatrix(data = as.matrix(df.IBNR_F_test),
                             weight = dt_All_IBNR_F[rowList_IBNR_F$test, exposure],
                             label = dt_All_IBNR_F[rowList_IBNR_F$test, target_count])

xgb.IBNR_F_DMat.all <- xgb.DMatrix(data = as.matrix(df.IBNR_F_all),
                            weight = dt_All_IBNR_F[rowList_IBNR_F$all, exposure],
                            label = dt_All_IBNR_F[rowList_IBNR_F$all, target_count])

4.2.2 Fit initial model using cross validation

Having prepared the data for xgboost I can now fit an initial model. I’ve used a simple set of hyper-parameters and used cross validation to select the optimal number of boosted trees (nrounds) for these hyper-parameter selections by calling the xgb.cv function with early stopping.

I have used the count:poisson objective function based upon inspection of the target variable.

hist(dt_All_IBNR_F[rowList_IBNR_F$train, target_count])

param <- list(
  objective = "count:poisson",
  max_depth = 2L,           # tree-depth
  subsample = 0.7,          # randomly sample rows before fitting each tree
  colsample_bytree = 0.8,   # randomly sample columns before fitting each tree
  min.child.weight = 10,    # minimum weight per leaf
  eta = 0.1               # Learning rate
  #monotone_constraints = monotone_Vec # Monotonicity constraints
)


# Train model with cross validation
set.seed(1984) # for repeatability

xgb_IBNR_F_CV <- xgb.cv(
 params                 = param,
 data                   = xgb.IBNR_F_DMat.train,
 nrounds                = 2000,        # Maximum number of trees to build
 nfold = 5,

 early_stopping_rounds  = 50L,        # Stops algorithm early if performance has not improved in n rounds
 print_every_n          = 50L,        # How often to print to console
 #base_score             = 0.001,       # Model starting point
   prediction             = TRUE        # Keeps the predictions
)
## [1]  train-poisson-nloglik:0.509656+0.000600 test-poisson-nloglik:0.509657+0.002292 
## Multiple eval metrics are present. Will use test_poisson_nloglik for early stopping.
## Will train until test_poisson_nloglik hasn't improved in 50 rounds.
## 
## [51] train-poisson-nloglik:0.162205+0.001478 test-poisson-nloglik:0.162599+0.004882 
## [101]    train-poisson-nloglik:0.136502+0.001222 test-poisson-nloglik:0.137285+0.005015 
## [151]    train-poisson-nloglik:0.132466+0.001164 test-poisson-nloglik:0.133936+0.004689 
## [201]    train-poisson-nloglik:0.130754+0.001130 test-poisson-nloglik:0.132831+0.004558 
## [251]    train-poisson-nloglik:0.129548+0.001087 test-poisson-nloglik:0.132461+0.004548 
## [301]    train-poisson-nloglik:0.128583+0.001078 test-poisson-nloglik:0.132294+0.004463 
## [351]    train-poisson-nloglik:0.127722+0.001084 test-poisson-nloglik:0.132064+0.004437 
## [401]    train-poisson-nloglik:0.126975+0.001102 test-poisson-nloglik:0.131920+0.004426 
## [451]    train-poisson-nloglik:0.126253+0.001077 test-poisson-nloglik:0.131680+0.004414 
## [501]    train-poisson-nloglik:0.125648+0.001070 test-poisson-nloglik:0.131608+0.004397 
## [551]    train-poisson-nloglik:0.125077+0.001016 test-poisson-nloglik:0.131505+0.004303 
## [601]    train-poisson-nloglik:0.124579+0.000978 test-poisson-nloglik:0.131449+0.004381 
## Stopping. Best iteration:
## [579]    train-poisson-nloglik:0.124774+0.001003 test-poisson-nloglik:0.131380+0.004327

Having fit the model we store the out-of-fold predictions for both the claim frequency and the claim counts.

 dt_All_IBNR_F[rowList_IBNR_F$train, preds_oof_IBNR_F := xgb_IBNR_F_CV$pred]
 dt_All_IBNR_F[rowList_IBNR_F$train, preds_oof_IBNR_Nos := exposure * preds_oof_IBNR_F]

4.2.3 Fit final model on all training data

Having fit the model using 5 fold cross validation we observe the optimum number of fitting rounds to be 579.

We can then use this to train a final model on all the data.

xgb_IBNR_F_Fit <- xgb.train(
   params                 = param,
   data                   = xgb.IBNR_F_DMat.train,
   nrounds                = xgb_IBNR_F_CV$best_iteration,
# base_score             = 1,
   watchlist              = list(train=xgb.IBNR_F_DMat.train, test=xgb.IBNR_F_DMat.test) ,
   print_every_n          = 50
 )
## [1]  train-poisson-nloglik:0.509601  test-poisson-nloglik:0.497376 
## [51] train-poisson-nloglik:0.163437  test-poisson-nloglik:0.122574 
## [101]    train-poisson-nloglik:0.136768  test-poisson-nloglik:0.088578 
## [151]    train-poisson-nloglik:0.132694  test-poisson-nloglik:0.084188 
## [201]    train-poisson-nloglik:0.131227  test-poisson-nloglik:0.083316 
## [251]    train-poisson-nloglik:0.130050  test-poisson-nloglik:0.082979 
## [301]    train-poisson-nloglik:0.129073  test-poisson-nloglik:0.083072 
## [351]    train-poisson-nloglik:0.128206  test-poisson-nloglik:0.083234 
## [401]    train-poisson-nloglik:0.127501  test-poisson-nloglik:0.083556 
## [451]    train-poisson-nloglik:0.126870  test-poisson-nloglik:0.083565 
## [501]    train-poisson-nloglik:0.126326  test-poisson-nloglik:0.083704 
## [551]    train-poisson-nloglik:0.125827  test-poisson-nloglik:0.083802 
## [579]    train-poisson-nloglik:0.125509  test-poisson-nloglik:0.083817
 dt_All_IBNR_F[rowList_IBNR_F$all, preds_full_IBNR_Nos := predict(xgb_IBNR_F_Fit,xgb.IBNR_F_DMat.all)]

4.2.4 Inspect model fit

Having fitted the full model we can then inspect the model fit. The traditional way of inspecting global model feature importance is to use the gains chart.

#default feature importance by gain
featImp_IBNR_F <- xgb.importance(xgb_IBNR_F_Fit, feature_names = colnames(xgb.IBNR_F_DMat.train))
xgb.plot.importance(featImp_IBNR_F, main="Feature Importance - IBNR Frequency")

An increasingly popular and more robust approach is to use SHAP values https://github.com/slundberg/shap. The SHAP equivalent of the feature importance chart is shown below.

# Return the SHAP values and ranked features by mean|SHAP|
shap_values <- shap.values(xgb_model = xgb_IBNR_F_Fit, X_train = as.matrix(df.IBNR_F_train))

# Prepare the long-format data:
shap_long <- shap.prep(shap_contrib = shap_values$shap_score, X_train =  as.matrix(df.IBNR_F_train))

# **SHAP summary plot**
shap.plot.summary(shap_long, dilute = nrow(df.IBNR_F_train)/10000)

A second useful chart is the partial dependence plot. This shows how the values of a predictive (input) feature influence the predicted (output) value, while holding the values of all other predictive (input) features constant. It is also known as the marginal effect.

Here we show the partial dependence plots for the top 4 SHAP features.

fig_list <- lapply(names(shap_values$mean_shap_score)[1:4], 
                   shap.plot.dependence,
                   data_long = shap_long,
                   dilute = nrow(shap_long)/ 10000)

gridExtra::grid.arrange(grobs = fig_list, ncol = 2)

The feature importance and partial dependency plots provide quick insight into the model.

  • We see that claim development period, j, is the most important feature and that the IBNR frequency is smaller for larger values of j.
  • The second most important feature is phone price with the IBNR claim count increasing as price increases. Although this is not a direct feature in the data generating process there is a link with higher theft frequencies and higher phone prices.
  • The third most important feature is phone model with IBNR frequency increasing with model type. This is expected from the data generating process as theft claim frequency increases with model type.
  • The fourth feature is phone cover and it should be no suprise to see that Breakage only, the most basic cover level is associated with lower IBNR claim counts.

SHAP values can also be used to show the components of a single predction. In the following plot we show the top 4 components for each row of the data and zoom in at row 500.

# choose to show top 4 features by setting `top_n = 4`, set 6 clustering groups.  
plot_data <- shap.prep.stack.data(shap_contrib = shap_values$shap_score,
                                  data_percent = 10000/nrow(shap_long),
                                  top_n = 4,
                                  n_groups = 6)
  
# choose to zoom in at location 500, set y-axis limit using `y_parent_limit`  
# it is also possible to set y-axis limit for zoom-in part alone using `y_zoomin_limit`  
shap.plot.force_plot(plot_data, zoom_in_location = 500, y_parent_limit = c(-1,1))

4.2.5 Summarising IBNR claim counts

By comparing the model predictions to the simulated claims run-off we can get a feel for the accuracy of the machine learning approach. Below I aggregate the IBNR claim count predictions by claim occurrence month and compare them to the known simulated claim run-off.

dt_All_IBNR_F_summary <- dt_All_IBNR_F[rowList_IBNR_F$test,.(preds = sum(preds_full_IBNR_Nos), target = sum(target_count)), keyby = date_pol_start_YYYYMM]
date_pol_start_YYYYMM preds target Diff Diff_pcnt
2016.01 206 168 38 22.6%
2016.02 178 176 2 1.4%
2016.03 184 160 24 14.8%
2016.04 174 164 10 6.2%
2016.05 155 195 -40 -20.7%
2016.06 149 179 -30 -16.7%
2016.07 89 170 -81 -47.9%
2016.08 66 162 -96 -59.2%
2016.09 66 59 7 12.3%
Total 1,267 1,433 -166 -11.6%

You can now jump back up to the beginning of the modeling section and select the IBNR severity modeling tab.

4.3 IBNR Severity model

IBNR reserves are built by multiplying the outputs of a claim frequency and claim severity model. The general process of building the model follows that of the RBNS reserves.

4.3.1 Creating xgboost dataset

Again we convert the data into a specific matrix form called a DMatrix.

The other point to note is the values of flgTrain used to identify the training and test rows in our dataset. Recall from Notebook 2, in the IBNR dataset training rows for the frequency model have a flgtrain value of 1 whereas the Severity training roes have a value of 2.

IBNR_predictors <- c("j",
                     "k",
                     "Cover",
                     "Brand",
                     "Model",
                     "Price",
                     "date_uw")

dt_All_IBNR [, date_pol_start_YYYYMM := as.character(year(date_pol_start) + month(date_pol_start)/100 )]

dt_All_IBNR_S <- dt_All_IBNR[, .(exposure = sum(target>0),
                                 target_cost = sum(target)),
                            by= c(IBNR_predictors, "date_pol_start_YYYYMM", "flgTrain")]

dt_All_IBNR_S <- dt_All_IBNR_S[exposure>0]

# setup train and test rows
rowList_IBNR_S <- list(train=dt_All_IBNR_S[, which(flgTrain==2)],
                     test=dt_All_IBNR_S[, which(flgTrain==0)],
                     all = dt_All_IBNR_S[, which(flgTrain!=1)])

# setup data for xgboost

IBNR_rec <- recipe( ~ ., data = dt_All_IBNR_S[, IBNR_predictors, with = FALSE]) %>%
  step_dummy(all_nominal(), one_hot = TRUE) %>%
  prep()

df.IBNR_S_train <- bake(IBNR_rec, new_data = dt_All_IBNR_S[rowList_IBNR_S$train,] )
df.IBNR_S_test <- bake(IBNR_rec, new_data = dt_All_IBNR_S[rowList_IBNR_S$test,] )
df.IBNR_S_all <- bake(IBNR_rec, new_data = dt_All_IBNR_S[rowList_IBNR_S$all,] )



xgb.IBNR_S_DMat.train <- xgb.DMatrix(data = as.matrix(df.IBNR_S_train),
                              weight = dt_All_IBNR_S[rowList_IBNR_S$train, exposure],
                              label = dt_All_IBNR_S[rowList_IBNR_S$train, target_cost])

xgb.IBNR_S_DMat.test <- xgb.DMatrix(data = as.matrix(df.IBNR_S_test),
                             weight = dt_All_IBNR_S[rowList_IBNR_S$test, exposure],
                             label = dt_All_IBNR_S[rowList_IBNR_S$test, target_cost])

xgb.IBNR_S_DMat.all <- xgb.DMatrix(data = as.matrix(df.IBNR_S_all),
                            weight = dt_All_IBNR_S[rowList_IBNR_S$all, exposure],
                            label = dt_All_IBNR_S[rowList_IBNR_S$all, target_cost])

4.3.2 Fit initial model using cross validation

Having prepared the data for xgboost I can now fit an initial model. I’ve used a simple set of hyper-parameters and used cross validation to select the optimal number of boosted trees (nrounds) for these hyper-parameter selections by calling the xgb.cv function with early stopping.

I have used the reg:gamma objective function based upon inspection of the target variable.

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     3.0   125.5   255.0   353.9   486.5  2011.0

param <- list(
  objective = "reg:gamma",
  max_depth = 2L,           # tree-depth
  subsample = 0.7,          # randomly sample rows before fitting each tree
  colsample_bytree = 0.8,   # randomly sample columns before fitting each tree
  min.child.weight = 10,    # minimum weight per leaf
  eta = 0.1               # Learning rate
  #monotone_constraints = monotone_Vec # Monotonicity constraints
)

# Train model with cross validation
set.seed(1984) # for repeatability

xgb_IBNR_S_CV <- xgb.cv(
 params                 = param,
 data                   = xgb.IBNR_S_DMat.train,
 nrounds                = 2000,        # Maximum number of trees to build
 nfold = 5,

 early_stopping_rounds  = 50L,        # Stops algorithm early if performance has not improved in n rounds
 print_every_n          = 50L,        # How often to print to console
 #base_score             = 0.001,       # Model starting point
   prediction             = TRUE        # Keeps the predictions
)
## [1]  train-gamma-nloglik:725.805481+6.987979 test-gamma-nloglik:725.496777+28.009135 
## Multiple eval metrics are present. Will use test_gamma_nloglik for early stopping.
## Will train until test_gamma_nloglik hasn't improved in 50 rounds.
## 
## [51] train-gamma-nloglik:10.027304+0.047412  test-gamma-nloglik:10.030309+0.237108 
## [101]    train-gamma-nloglik:6.820591+0.008904   test-gamma-nloglik:6.828172+0.038464 
## [151]    train-gamma-nloglik:6.810284+0.008580   test-gamma-nloglik:6.826305+0.036504 
## Stopping. Best iteration:
## [141]    train-gamma-nloglik:6.811574+0.008407   test-gamma-nloglik:6.825954+0.036434

Having fitted an initial model the out-of-fold predictions are stored.

 dt_All_IBNR_S[rowList_IBNR_S$train, preds_oof_IBNR_S := xgb_IBNR_S_CV$pred]
 dt_All_IBNR_S[rowList_IBNR_S$train, preds_oof_IBNR_Cost := exposure * preds_oof_IBNR_S]

4.3.3 Fit final model on all training data

Having fit the model using 5 fold cross validation we observe the optimum number of fitting rounds to be 141.

We can then us this to train a final model on all the data.

xgb_IBNR_S_Fit <- xgb.train(
   params                 = param,
   data                   = xgb.IBNR_S_DMat.train,
   nrounds                = xgb_IBNR_S_CV$best_iteration,
# base_score             = 1,
   watchlist              = list(train=xgb.IBNR_F_DMat.train, test=xgb.IBNR_F_DMat.test) ,
   print_every_n          = 50
 )
## [1]  train-gamma-nloglik:NaN test-gamma-nloglik:NaN 
## [51] train-gamma-nloglik:NaN test-gamma-nloglik:NaN 
## [101]    train-gamma-nloglik:NaN test-gamma-nloglik:NaN 
## [141]    train-gamma-nloglik:NaN test-gamma-nloglik:NaN

Having trained the model the predictions are stored.

 dt_All_IBNR_S[rowList_IBNR_S$all, preds_full_IBNR_Cost := predict(xgb_IBNR_S_Fit,xgb.IBNR_S_DMat.all)]

4.3.4 Inspect model fit

Having fitted the full model we can then inspect the model fit. The traditional way of inspecting global model feature importance is to use the gains chart.

#default feature importance by gain
featImp_IBNR_S <- xgb.importance(xgb_IBNR_S_Fit, feature_names = colnames(xgb.IBNR_S_DMat.train))
xgb.plot.importance(featImp_IBNR_S, main="Feature Importance - IBNR Severity")

An increasingly popular and more robust approach is to use SHAP values https://github.com/slundberg/shap. The SHAP equivalent of the feature importance chart is shown below.

# Return the SHAP values and ranked features by mean|SHAP|
shap_values <- shap.values(xgb_model = xgb_IBNR_S_Fit, X_train = as.matrix(df.IBNR_S_train))

# Prepare the long-format data:
shap_long <- shap.prep(shap_contrib = shap_values$shap_score, X_train =  as.matrix(df.IBNR_S_train))

# **SHAP summary plot**
shap.plot.summary(shap_long, dilute = max(nrow(df.IBNR_S_train),10000)/10000)

A second useful chart is the partial dependence plot. This shows how the values of a predictive (input) feature influence the predicted (output) value, while holding the values of all other predictive (input) features constant. It is also known as the marginal effect.

Here we show the partial dependence plots for the top 4 SHAP features.

fig_list <- lapply(names(shap_values$mean_shap_score)[1:4], 
                   shap.plot.dependence,
                   data_long = shap_long,
                   dilute = nrow(shap_long)/ 10000)

gridExtra::grid.arrange(grobs = fig_list, ncol = 2)

The feature importance and partial dependency plots provide quick insight into the model.

  • We see that phone price is the most important feature and has a linear relationship with IBNR severity as we would expect from the data generating process.
  • The second and third most important features relate to phone cover. We see that cover BOT is associated with higher IBNR costs whereas cover B is associated with lower.
  • The fourth feature is phone brand and follows the pattern we would expect from the data simulation process.

SHAP values can also be used to show the components of a single prediction. In the following plot we show the top 4 components for each row of the data and zoom in at row 500.

# choose to show top 4 features by setting `top_n = 4`, set 6 clustering groups.  
plot_data <- shap.prep.stack.data(shap_contrib = shap_values$shap_score,
                                  data_percent = 10000/max(nrow(shap_long),10000),
                                  top_n = 4,
                                  n_groups = 6)
## The SHAP values of the Rest 5 features were summed into variable 'rest_variables'.
# choose to zoom in at location 500, set y-axis limit using `y_parent_limit`  
# it is also possible to set y-axis limit for zoom-in part alone using `y_zoomin_limit`  
shap.plot.force_plot(plot_data, zoom_in_location = 500, y_parent_limit = c(-1,1))
## Data has N = 1111 | zoom in length is 111 at location 500.

4.3.5 Summarising IBNR claim costs

By comparing the model predictions to the simulated claims run-off we can get a feel for the accuracy of the machine learning approach. Below I aggregate the IBNR claim cost predictions by claim occurrence month and compare them to the known simulated claim run-off.

dt_All_IBNR_S_summary <- dt_All_IBNR_S[rowList_IBNR_S$test,.(preds = sum(preds_full_IBNR_Cost), target = sum(target_cost)), keyby = date_pol_start_YYYYMM]
date_pol_start_YYYYMM preds target Diff Diff_pcnt
2016.01 57,279 52,782 4,497 8.5%
2016.02 54,144 55,615 -1,471 -2.6%
2016.03 51,057 47,200 3,857 8.2%
2016.04 53,099 56,975 -3,876 -6.8%
2016.05 63,855 64,833 -978 -1.5%
2016.06 52,763 52,989 -226 -0.4%
2016.07 51,072 50,276 796 1.6%
2016.08 51,240 53,502 -2,262 -4.2%
2016.09 18,392 18,989 -597 -3.1%
Total 452,900 453,161 -261 -0.1%

5 Summary

In this third notebook we have stepped through the process to apply machine learning techniques in order to create reserve estimates following the techniques set out in sections 3 and 4 of Baudry’s paper.

Specifically we have shown how to apply the xgboost machine learning algorithm and illustrated how it can, with little human input, create reasonable reserve estimates as shown below.

Reserve Prediction Ground_Truth Diff Diff_pcnt
RBNS 1,023,951 1,022,981 970 0.1%
IBNR 452,900 453,161 -261 -0.1%
TOTAL 1,476,851 1,476,142 709 0.0%

In addition we have seen how we can create explanations for the reserve predictions using feature importance and partial dependence plots. We have also used SHAP values which can be used to explain both global features and individual predictions.

In so doing, it is hoped that this series of notebooks encourage further exploration of Baudry’s paper and the wider field of machine learning in reserving. The code supporting this notebook along with the supporting files and folders available in a zip file here.

Download and extract the zip file to a local directory and then open the R project file Baudry_3.rproj in your local R software installation. In the root of the project folder you will see two files;

  • Notebook_3_ApplyMachineLearningReserving_v1.Rmd - which is the source code used to recreate this notebook
  • Notebook_3_ApplyMachineLearningReserving_v1.R - the equivalent code provided as an R script

Please note that, depending upon your R installation , you may have to install R libraries before you can run the code provided. R will warn you if you have missing dependencies and you can then install them from CRAN.

LS0tCnRpdGxlOiAiTk9OLVBBUkFNRVRSSUMgSU5ESVZJRFVBTCBDTEFJTSBSRVNFUlZJTkcgSU4gSU5TVVJBTkNFIgpzdWJ0aXRsZTogJ05vdGVib29rIDMgb2YgMzogQXBwbHlpbmcgbWFjaGluZSBsZWFybmluZyBmb3IgcmVzZXJ2aW5nJwpkYXRlOiAiTm92ZW1iZXIgMjAyMCIKb3V0cHV0OgogIHJtZGZvcm1hdHM6OnJlYWR0aGVkb3duOgogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgY3NzOiBiYXVkcnkuY3NzCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCiMgSW50cm9kdWN0aW9uCgpUaGlzIGlzIHRoZSB0aGlyZCBub3RlYm9vayBvZiBhIHNlcmllcyBvZiB0aHJlZSB0aGF0IG91dGxpbmVzIGFuZCBlbGFib3JhdGVzIHVwb24gY29kZSB1c2VkIHRvIHJlcGxpY2F0ZSB0aGUgY2VudHJhbCBzY2VuYXJpbyBpbiB0aGUgcGFwZXIgb2YgTWF4aW1pbGllbiBCYXVkcnkgIk5PTi1QQVJBTUVUUklDIElORElWSURVQUwKQ0xBSU0gUkVTRVJWSU5HIElOIElOU1VSQU5DRSIKCgpbaHR0cHM6Ly93d3cuaW5zdGl0dXRkZXNhY3R1YWlyZXMuY29tL2dsb2JhbC9nZW5lL2xpbmsucGhwP2RvY19pZD0xMTc0NyZmZz0xXShodHRwczovL3d3dy5pbnN0aXR1dGRlc2FjdHVhaXJlcy5jb20vZ2xvYmFsL2dlbmUvbGluay5waHA/ZG9jX2lkPTExNzQ3JmZnPTEpICAKW2h0dHA6Ly9jaGFpcmUtZGFtaS5mci9maWxlcy8yMDE2LzEwL1Jlc2VydmluZy1hcnRpY2xlLnBkZl0oaHR0cDovL2NoYWlyZS1kYW1pLmZyL2ZpbGVzLzIwMTYvMTAvUmVzZXJ2aW5nLWFydGljbGUucGRmKQoKSW4gdGhpcyBub3RlYm9vayB3ZSBzdGVwIHRocm91Z2ggdGhlIHByb2Nlc3MgdG8gYXBwbHkgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIGluIG9yZGVyIHRvIGNyZWF0ZSByZXNlcnZlIGVzdGltYXRlcyBmb2xsb3dpbmcgdGhlIHRlY2huaXF1ZXMgc2V0IG91dCBpbiBzZWN0aW9ucyAzIGFuZCA0IG9mIEJhdWRyeSdzIHBhcGVyLiAKCiMgUHJlcGFyYXRpb25zIGJlZm9yZSBtb2RlbGxpbmcKClRoZSByZXNlcnZpbmcgZGF0YSBzdHJ1Y3R1cmVzIGJ1aWx0IGluIHRoaXMgbm90ZWJvb2sgYXJlIGZyb20gYSBzaW11bGF0ZWQgcGhvbmUgaW5zdXJhbmNlIGRhdGFzZXQuIFRoZSBjcmVhdGlvbiBvZiB0aGF0IHNpbXVsYXRlZCBkYXRhIGFuZCBtYWNoaW5lIGxlYXJuaW5nIGRhdGEgc3RydWN0dXJlcyBmb3IgcmVzZXJ2aW5nIGhhdmUgYmVlbiBzZXQgb3V0IGluIHRoZSBmaXJzdCBhbmQgc2Vjb25kIG5vdGVib29rcyBvZiB0aGlzIHNlcmllcy4KClJhdGhlciB0aGFuIHJlcGVhdCB0aGF0IGNvZGUgaW4gdGhpcyBub3RlYm9vayB0aGUga2V5IHJvdXRpbmVzIGFuZCBmdW5jdGlvbnMgaGF2ZSBiZWVuIHB1dCBpbnRvIHRoZSByIHNjcmlwdCAqQmF1ZHJ5X2Z1bmN0aW9uc192Mi5SKiBlbmFibGluZyB1cyB0byBxdWlja2x5IGxvYWQgdGhlbSBmb3IgdXNlIGluIHRoaXMgbm90ZWJvb2suCgoKYGBge3IgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgSW1wb3J0aW5nIHBhY2thZ2VzCgpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHJtZGZvcm1hdHMpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShtYWdyaXR0cikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHJlcHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShmb3JtYXR0YWJsZSkKCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KFNIQVBmb3J4Z2Jvb3N0KQoKc291cmNlKCIuL0JhdWRyeV9mdW5jdGlvbnNfdjIuUiIpCmBgYAoKIyBTaW11bGF0ZSBkYXRhIGFuZCBwcmVwYXJlIGRhdGFiYXNlCgpXZSBjYW4gbm93IHF1aWNrbHkgcmVwZWF0IHRoZSBwcm9jZXNzIGZyb20gTm90ZWJvb2tzIDEgYW5kIDIgdXNpbmcgdGhlIGZ1bmN0aW9ucyBpbiB0aGUgc2NyaXB0LgoKIyMgU2ltdWxhdGUgcG9saWN5IGFuZCBjbGFpbSBkYXRhCgpXZSBzdGFydCB3aXRoIGEgc2ltdWxhdGVkIHBob25lIGluc3VyYW5jZSBwb2xpY3kgYW5kIGNsYWltIGRhdGFzZXQuIEkgYW0gY2FsbGluZyB0aGUgZnVuY3Rpb24gZnJvbSBOb3RlYm9vayAxIG9mIHRoaXMgc2VyaWVzIHRvIGNyZWF0ZSB0aGUgZGF0YXNldC4gVXNpbmcgYSBmaXhlZCBzZWVkIHdpbGwgZW5zdXJlIHlvdSBnZXQgYSByZXByb2R1Y2libGUgc2ltdWxhdGVkIGRhdGFzZXQuCgpgYGB7ciB9CgpkdF9QaG9uZURhdGEgPC0gc2ltdWxhdGVfY2VudHJhbF9zY2VuYXJpbygxMjM0KQoKYGBgCgoKIyMgSm9pbiBwb2xpY3kgYW5kIGNsYWltIGRhdGEKCldlIHRoZW4gam9pbiB0aGUgc2ltdWxhdGVkIHBob25lIGluc3VyYW5jZSBwb2xpY3kgYW5kIGNsYWltIGRhdGFzZXQuIEkgaGF2ZSB0YWtlbiB0aGUgY29kZSBmcm9tIE5vdGVib29rIDIgYW5kIHdyYXBwZWQgaXQgaW4gdG8gYSBmdW5jdGlvbiB0byBwZXJmb3JtIHRoZSBqb2luICBhbmQgc3Vic2VxdWVudCBkYXRhIHRpZHlpbmcuIAoKTm90ZSB0aGF0IGlmIHlvdSBhcmUgd29ya2luZyB3aXRoIHlvdXIgb3duIGNvbXBhbnkgZGF0YSB5b3Ugd291bGQgbGlrZWx5IG5lZWQgdG8gYW1lbmQgdGhpcyBmdW5jdGlvbiB0byB3b3JrIHdpdGggdGhlIHNwZWNpZmljcyBvZiB5b3VyIGRhdGEuICAKCgoKYGBge3J9CgpkdF9wb2xjbGFpbSA8LSBqb2luX3BvbGljeV9jbGFpbShkdF9QaG9uZURhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGVfcG9sX3N0YXJ0ID0gImRhdGVfVVciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlX3BvbF9lbmQgPSAiZGF0ZV9sYXBzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGVfb2NjdXIgPSAiZGF0ZV9vY2N1ciIpCgpgYGAKCiMjIFRpbWVzbGljZSBwb2xpY3kgY2xhaW0gCgpXZSB0aGVuIHRpbWUgc2xpY2UgdGhlIGpvaW5lZCBwb2xpY3kgYW5kIGNsYWltIGRhdGFzZXQuIEFnYWluLCBJIGhhdmUgdGFrZW4gdGhlIGNvZGUgZnJvbSBOb3RlYm9vayAyIGFuZCB3cmFwcGVkIGl0IGludG8gYSBmdW5jdGlvbiB0byBwZXJmb3JtIHRoZSB0aW1lIHNsaWNpbmcsIGdpdmVuIGEgc2V0IG9mIGRhdGVzIHRoYXQgZGVmaW5lIHRoZSB0aW1lIHNsaWNlIHBlcmlvZHMuCgpBZ2Fpbiwgbm90ZSB0aGF0IHRoaXMgZnVuY3Rpb24gd291bGQgbmVlZCB0byBiZSByZS13cml0dGVuIHRvIHdvcmsgd2l0aCB5b3VyIG93biBjb21wYW55IGRhdGEgYW5kIG1vcmUgaW1wb3J0YW50bHkgaXQgY3VycmVudGx5IHdvdWxkIG5vdCB3b3JrIGNvcnJlY3RseSB3aXRoIHBhcnRpYWwgcGF5bWVudCBjbGFpbXMuIAoKCmBgYCB7cn0KbHN0X0RhdGVfc2xpY2UgPC0gZmxvb3JfZGF0ZShzZXEoYXMuRGF0ZSgiMjAxNi8xLzEiKSwgYXMuRGF0ZSgiMjAxOS8wNi8zMCIpLCBieSA9IDMwKSwgdW5pdD0gInNlY29uZCIpIApkdF9wb2xjbGFpbSA8LSB0aW1lX3NsaWNlX3BvbGNsYWltKGR0X3BvbGNsYWltLCBsc3RfRGF0ZV9zbGljZSkKCmBgYAoKIyMgU2VsZWN0IHZhbHVhdGlvbiBkYXRlCgpCZWZvcmUgd2UgY3JlYXRlIHRoZSBSQk5TIGFuZCBJQk5FUiBkYXRhc2V0cyB3ZSBtdXN0IHBpY2sgYSB2YWx1YXRpb24gcG9pbnQgZnJvbSB0aGUgYXZhaWxhYmxlIHRpbWVzbGljZXMuIEknbSBzZWxlY3RpbmcgdGhlIDEwdGggcG9pbnQgZm9yIGlsbHVzdHJhdGlvbiwgd2hpY2ggaXMgdGhlIDEwdGggMzAgZGF5IHBlcmlvZCBmcm9tIDAxLzAxLzIwMTYsIGllIGEgdmFsdWF0aW9uIGRhdGUgb2YgMjcvMDkvMjAxNi4KCmBgYHtyfQoKaSA8LSB2YWx1YXRpb24gPC0gMTAKdF9pIDwtIGxzdF9EYXRlX3NsaWNlW2ldIApkZWx0YSA8LSBtaW4oaSwgbGVuZ3RoKGxzdF9EYXRlX3NsaWNlKSAtIGkgKyAxKQoKCmBgYAoKIyMgQ3JlYXRpbmcgUkJOUyBkYXRhc2V0CgpXZSBzdGFydCB3aXRoIHRoZSBSQk5TIGRhdGFzZXQgYW5kIGJ5IGRlZmluaW5nIHRoZSBsaXN0IG9mIGZlYXR1cmVzIHRoYXQgd2lsbCBiZSB1c2VkIGluIHRoZSBSQk5TIG1vZGVsIGRhdGFzZXQuCgpBcyB5b3UgZm9sbG93IHRoaXMgZXhhbXBsZSB5b3UgbWF5IG9ic2VydmUgdGhhdCB3ZSBhcmUgbm90IHVzaW5nIHRoZSBoaXN0b3JpYyBjbGFpbSBwYXltZW50IGluZm9ybWF0aW9uIGFzIGFuIGV4cGxhbmF0b3J5IGZlYXR1cmUgaW4gdGhlIFJCTlMgcmVzZXJ2ZSBwcmVkaWN0aW9uIG1vZGVsLiBCYXVkcnkncyBwYXBlciBhbmQgYXBwcm9hY2ggZG9lcyBhbGxvdyBmb3IgdGhlIGluY2x1c2lvbiBvZiBzdWNoIGluZm9ybWF0aW9uIHNvIGl0IG1heSBzZWVtIHNvbWV3aGF0IHN0cmFuZ2Ugbm90IHRvIGluY2x1ZGUgaXQgaW4gdGhpcyB3b3JrZWQgZXhhbXBsZS4gIAoKSSBiZWxpZXZlIHRoZSByZWFzb24gdGhhdCBpdCBpcyBub3QgaW5jbHVkZWQgaXMgYSByZXN1bHQgb2YgdGhlIHdheSBkYXRhIGhhcyBiZWVuIHNpbXVsYXRlZCwgc3BlY2lmaWNhbGx5IHRoYXQgc2ltdWxhdGVkIGNsYWltcyBhcmUgc2V0dGxlZCB3aXRoIGEgc2luZ2xlIHBheW1lbnQuIFRoYXQgYXNzdW1wdGlvbiBjb21iaW5lZCB3aXRoIHRoZSBmYWN0IHRoYXQgdGhpcyBtZXRob2QgaXMgYmVpbmcgYXBwbGllZCB0byBpbmRpdmlkdWFsIGNsYWltIGxldmVsIGRhdGEgcHJldmVudHMgdGhlIHByZXZpb3VzIHRpbWUgcGVyaW9kJ3MgcGF5bWVudCBpbmZvcm1hdGlvbiBiZWluZyB1c2VkIGJ5IGEgbWFjaGluZSBsZWFybmluZyBtb2RlbC4gVGhhdCBpcyBiZWNhdXNlIHRoZSBwcmV2aW91cyBwZXJpb2QncyBwYXltZW50IGFtb3VudCBjYW4gb25seSBldmVyIHRha2UgdGhlIHZhbHVlIHplcm8gZm9yIGEgY2xhaW0gcmVxdWlyaW5nIGFuIFJCTlMgcmVzZXJ2ZS4gKENsYWltcyB3aGVyZSB0aGUgcHJldmlvdXMgcGFpZCBpcyBub24temVybyBoYXZlIGJlZW4gcGFpZCBhbmQgc2V0dGxlZCBpZSBubyBsb25nZXIgcmVxdWlyZSBhbiBSQk5TIHJlc2VydmUpLiBDb25zZXF1ZW50bHksIGluIHRoaXMgY2VudHJhbCBzaW11bGF0ZWQgZXhhbXBsZSwgcHJpb3IgcGF5bWVudCBhbW91bnQgaXMgdW5pbmZvcm1hdGl2ZSBpbiB0aGUgbWFjaGluZSBsZWFybmluZyBwcm9jZXNzIGFuZCB0aGVyZWZvcmUgY2Fubm90IGJlIGluY2x1ZGVkIGFzIGEgZmVhdHVyZSBpbiB0aGUgUkJOUyBkYXRhc2V0LgoKVGhlIGZpbmFsIHBvaW50IHRvIG5vdGUgaXMgdGhhdCB0aGUgY29kZSBzaGFyZWQgaGVyZSBkZWFscyBvbmx5IHdpdGggdGhlIHNpbXBsaWZpZWQgcGF5bWVudCBwcm9jZXNzIGZyb20gQmF1ZHJ54oCZcyBjZW50cmFsIHNjZW5hcmlvLCBpZSBjbGFpbXMgYXJlIHNldHRsZWQgd2l0aCBhIHNpbmdsZSBwYXltZW50LiBSZWFsIHdvcmxkIGRhdGEgd2l0aCBwYXJ0aWFsIHBheW1lbnRzIGFuZCBwcmlvciBwYXltZW50cyBhcyBleHBsYW5hdG9yeSBmZWF0dXJlcyB3b3VsZCByZXF1aXJlIG1hdGVyaWFsIGNoYW5nZXMgdG8gYmUgbWFkZSB0byB0aGUgY29kZSB3ZSBoYXZlIHNoYXJlZC4gU3VjaCBjaGFuZ2VzIGFyZSBsZWZ0IGZvciBpbnRlcmVzdGVkIHJlYWRlcnMgdG8gbWFrZS4KCgpgYGB7cn0KCiNkZWZpbmUgbW9kZWxWYXJzClJCTlNfbW9kZWxfdmFycyA8LSBjKCJjbG1fbnVtYmVyIiwKICAgICAgICAgICAgICAgICAgICAgInBvbF9udW1iZXIiLAogICAgICAgICAgICAgICAgICAgICAiaiIsCiAgICAgICAgICAgICAgICAgICAgICJrIiwKICAgICAgICAgICAgICAgICAgICAgImRhdGVfcG9sX3N0YXJ0IiwKICAgICAgICAgICAgICAgICAgICAgImRhdGVfb2NjdXIiLAogICAgICAgICAgICAgICAgICAgICAiZGF0ZV9yZXBvcnQiLAogICAgICAgICAgICAgICAgICAgICAiZGF0ZV9wYXkiLAogICAgICAgICAgICAgICAgICAgICAiQ292ZXIiLAogICAgICAgICAgICAgICAgICAgICAiY2xhaW1fdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICJCcmFuZCIsCiAgICAgICAgICAgICAgICAgICAgICJNb2RlbCIsCiAgICAgICAgICAgICAgICAgICAgICJQcmljZSIsCiAgICAgICAgICAgICAgICAgICAgICJ0YXJnZXQiCiAgICApCgoKIyBDcmVhdGUgYSBjb21iaW5lZCBUUkFJTiBkYXRhc2V0IGZvciBrID0gMSBhbmQgYWxsIHZhbGlkIGogZGVsYXkgdmFsdWVzCmR0X1JCTlNfdHJhaW4gPC0gUkJOU19UcmFpbihkdF9wb2xjbGFpbSwgdF9pLCBpLCBrID0gMSwgbHN0X0RhdGVfc2xpY2UsIFJCTlNfbW9kZWxfdmFycykKCiMgQ3JlYXRlIGEgY29tYmluZWQgVEVTVCBkYXRhc2V0IGZvciBrID0gMSBhbmQgYWxsIHZhbGlkIGogZGVsYXkgdmFsdWVzCmR0X1JCTlNfdGVzdCA8LSBSQk5TX1Rlc3QoZHRfcG9sY2xhaW0sIHRfaSwgZGVsdGEsIGsgPSAxLCBsc3RfRGF0ZV9zbGljZSwgUkJOU19tb2RlbF92YXJzKQoKYGBgCgpUaGUgdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMgYXJlIHRoZW4gam9pbmVkIGludG8gYSBzaW5nbGUgZGF0YXNldCBhbmQgYSBzbWFsbCBhbW91bnQgb2YgdGlkeWluZyBpcyBkb25lIHRvIG1ha2UgdGhlbSByZWFkeSBmb3IgdXNlLgoKYGBge3J9CgojIEFkZCBhIGZsYWcgdG8gZGV0ZXJtaW5lIHdoaWNoIHJvd3MgYXJlIGZyb20gdGhlIHRyYWluc2V0IGFuZCB3aGljaCBmcm9tIHRoZSB0ZXN0IHNldApkdF9SQk5TX3RyYWluWywgZmxnVHJhaW4gOj0gMV0KZHRfUkJOU190ZXN0WywgZmxnVHJhaW4gOj0gMF0KCiMgY29tYmluZSBpbnRvIGEgc2luZ2xlIFJCTlMgZGF0YXNldCAgIApkdF9BbGxfUkJOUyA8LSByYmluZChkdF9SQk5TX3RyYWluLCBkdF9SQk5TX3Rlc3QpCiN3cml0ZS5jc3YoZHRfQWxsX1JCTlMsImR0X0FsbF9SQk5TLmNzdiIsIHJvdy5uYW1lcyA9IEYpCgpgYGAKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgdGlkeSB1cApybShkdF9SQk5TX3RyYWluKQpybShkdF9SQk5TX3Rlc3QpCmdjKCkKICAgIApgYGAKCgpUaGUgaW1wb3J0YW50IGFzcGVjdHMgb2YgdGhlIHRpZHlpbmcgcmVsYXRlIHRvIGNyZWF0aW5nIHVzZWFibGUgZGVsYXkgbWV0cmljcyBmcm9tIHRoZSBudW1lcm91cyBkYXRlcyBhbmQgY29udmVydGluZyBzb21lIGNoYXJhY3RlciBmZWF0dXJlcyBzdWNoIGFzIGNvdmVyIGFuZCBjbGFpbSB0eXBlIGludG8gZmFjdG9ycy4KCmBgYCB7cn0KIyBvcmRlciBhbmQgY3JlYXRlIHNvbWUgZGVsYXkgZmllbGRzCnNldGtleShkdF9BbGxfUkJOUywgY2xtX251bWJlciwgaywgaikKICAgIApkdF9BbGxfUkJOU1ssIENvdW50IDo9IC5OICwgYnkgPWNsbV9udW1iZXJdCgojY3JlYXRlIGRlbGF5IG1lYXN1cmVzIGFuZCBjb252ZXJ0IHRpbWUgbWVhc3VyZXMgZnJvbSBpbnRlcnZhbHMgb2Ygc2Vjb25kcyB0byBpbnRlcnZhbHMgb2YgZGF5cwpkdF9BbGxfUkJOU1ssICc6PScoCiAgZGVsYXlfdXdfb2NjID0gaWZlbHNlKHllYXIoZGF0ZV9vY2N1cikgPT0gMjE5OSwKICAgICAgICAgICAgICAgICAgICAgICAgLTEsCiAgICAgICAgICAgICAgICAgICAgICAgIGNlaWxpbmcoKGFzLm51bWVyaWMoZGF0ZV9vY2N1cikgLSBhcy5udW1lcmljKGRhdGVfcG9sX3N0YXJ0KSkgLyAoMjQgKiA2MCAqIDYwKSkKICAgICAgICAgICAgICAgICAgICAgICAgKSwKICBkZWxheV9vY2NfcmVwID0gaWZlbHNlKHllYXIoZGF0ZV9vY2N1cikgPT0gMjE5OSwKICAgICAgICAgICAgICAgICAgICAgICAgIC0xLAogICAgICAgICAgICAgICAgICAgICAgICAgY2VpbGluZygoYXMubnVtZXJpYyhkYXRlX3JlcG9ydCkgLSBhcy5udW1lcmljKGRhdGVfb2NjdXIpKSAvICgyNCAqIDYwICogNjApKQogICAgICAgICAgICAgICAgICAgICAgICAgKSwKICBkZWxheV91d192YWwgPSBjZWlsaW5nKChhcy5udW1lcmljKHRfaSkgLSBhcy5udW1lcmljKGRhdGVfcG9sX3N0YXJ0KSkgLyAoMjQgKiA2MCAqIDYwKSksCiAgZGVsYXlfcmVwX3BheSA9IGNlaWxpbmcoKGFzLm51bWVyaWMoZGF0ZV9wYXkpIC0gYXMubnVtZXJpYyhkYXRlX3JlcG9ydCkpIC8gKDI0ICogNjAgKiA2MCkpLAogIAogIGRhdGVfdXcgPSBjZWlsaW5nKGFzLm51bWVyaWMoZGF0ZV9wb2xfc3RhcnQpIC8gKDI0ICogIDYwICogNjApKSwKICBDb3ZlciA9IGFzLmZhY3RvcihDb3ZlciksCiAgY2xhaW1fdHlwZSA9IGFzLmZhY3RvcihjbGFpbV90eXBlKQogICldCiAgCiAgIApgYGAKCiMjIENyZWF0aW5nIElCTlIgZGF0YXNldAoKV2UgY2FuIHRoZW4gY3JlYXRlIHRoZSBJQk5SIGRhdGFzZXQgZm9sbG93aW5nIGEgc2ltaWxhciBwcm9jZWR1cmUgc3RhcnRpbmcgYnkgZGVmaW5pbmcgdGhlIGxpc3Qgb2YgZmVhdHVyZXMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhlIElCTlIgbW9kZWwuIFRoZW4gd2UgY2FsbCB0aGUgZnVuY3Rpb25zIHRoYXQgY29udmVydCB0aGUgdGltZXNsaWNlZCBwb2xpY3kgYW5kIGNsYWltIGRhdGEgaW50byB0aGUgSUJOUiB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cy4KCmBgYHtyIH0KI2RlZmluZSBJQk5SIG1vZGVsVmFycwpJQk5SX21vZGVsX3ZhcnMgPC0gYygiY2xtX251bWJlciIsCiAgICAgICAgICAgICAgICAgICAgICJwb2xfbnVtYmVyIiwKICAgICAgICAgICAgICAgICAgICAgImoiLAogICAgICAgICAgICAgICAgICAgICAiayIsCiAgICAgICAgICAgICAgICAgICAgICJleHBvc3VyZSIsCiAgICAgICAgICAgICAgICAgICAgICJkYXRlX3BvbF9zdGFydCIsCiAgICAgICAgICAgICAgICAgICAgICJkYXRlX29jY3VyIiwKICAgICAgICAgICAgICAgICAgICAgImRhdGVfcmVwb3J0IiwKICAgICAgICAgICAgICAgICAgICAgImRhdGVfcGF5IiwKICAgICAgICAgICAgICAgICAgICAgIkNvdmVyIiwKICAgICAgICAgICAgICAgICAgICAgIkJyYW5kIiwKICAgICAgICAgICAgICAgICAgICAgIk1vZGVsIiwKICAgICAgICAgICAgICAgICAgICAgIlByaWNlIiwKICAgICAgICAgICAgICAgICAgICAgInRhcmdldCIpCiAgICAKIyBDcmVhdGUgYSBjb21iaW5lZCBUUkFJTiBkYXRhc2V0IGZvciBrID0gMSBhbmQgYWxsIHZhbGlkIGogZGVsYXkgdmFsdWVzCmxzdF9JQk5SX3RyYWluIDwtIElCTlJfVHJhaW4oZHRfcG9sY2xhaW0sIHRfaSwgaSwgayA9IDEsbHN0X0RhdGVfc2xpY2UsIElCTlJfbW9kZWxfdmFycykKCiMgQ3JlYXRlIGEgY29tYmluZWQgVEVTVCBkYXRhc2V0IGZvciBrID0gMSBhbmQgYWxsIHZhbGlkIGogZGVsYXkgdmFsdWVzCmR0X0lCTlJfdGVzdCA8LSBJQk5SX1Rlc3QoZHRfcG9sY2xhaW0sIHRfaSwgZGVsdGEsIGsgPSAxLGxzdF9EYXRlX3NsaWNlLCBJQk5SX21vZGVsX3ZhcnMpCgpgYGAKClRoZSB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cyBhcmUgdGhlbiBqb2luZWQgaW50byBhIHNpbmdsZSBkYXRhc2V0IGFuZCBhIHNtYWxsIGFtb3VudCBvZiB0aWR5aW5nIGlzIGRvbmUgdG8gbWFrZSB0aGVtIHJlYWR5IGZvciB1c2UuCgpgYGB7ciB9Cgpsc3RfSUJOUl90cmFpbiRGcmVxWywgZmxnVHJhaW4gOj0gMV0KbHN0X0lCTlJfdHJhaW4kTG9zc1ssIGZsZ1RyYWluIDo9IDJdCmR0X0lCTlJfdGVzdFssIGZsZ1RyYWluIDo9IDBdCgpkdF9BbGxfSUJOUiA8LSByYmluZChsc3RfSUJOUl90cmFpbiRGcmVxLAogICAgICAgICAgICAgICAgICAgICBsc3RfSUJOUl90cmFpbiRMb3NzLAogICAgICAgICAgICAgICAgICAgICBkdF9JQk5SX3Rlc3QpCiN3cml0ZS5jc3YoZHRfQWxsX0lCTlIsImR0X0FsbF9JQk5SLmNzdiIsIHJvdy5uYW1lcyA9IEYpCmBgYAoKCmBgYCB7ciBpbmNsdWRlID0gRkFMU0V9ICAKIyB0aWR5IHVwCnJtKGxzdF9JQk5SX3RyYWluKQpybShkdF9JQk5SX3Rlc3QpCmdjKCkKCmBgYAoKCgpUaGUgaW1wb3J0YW50IGFzcGVjdHMgb2YgdGhlIHRpZHlpbmcgcmVsYXRlIHRvIGNyZWF0aW5nIHVzZWFibGUgZGVsYXkgbWV0cmljcyBmcm9tIHRoZSBudW1lcm91cyBkYXRlcyBhbmQgY29udmVydGluZyBzb21lIGNoYXJhY3RlciBmZWF0dXJlcyBzdWNoIGFzIGNvdmVyIGFuZCBjbGFpbSB0eXBlIGludG8gZmFjdG9ycy4KCmBgYCB7ciB9CiMgb3JkZXIgYW5kIGNyZWF0ZSBzb21lIGRlbGF5IGZpZWxkcwpzZXRrZXkoZHRfQWxsX0lCTlIsIGNsbV9udW1iZXIsIGssIGopCiAgICAKZHRfQWxsX0lCTlJbLCBDb3VudCA6PSAuTiAsIGJ5ID1jbG1fbnVtYmVyXQpkdF9BbGxfSUJOUlssJzo9JyggZGVsYXlfdXdfb2NjID0gaWZlbHNlKHllYXIoZGF0ZV9vY2N1cikgPT0gMjE5OSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VpbGluZygoYXMubnVtZXJpYyhkYXRlX29jY3VyKSAtIGFzLm51bWVyaWMoZGF0ZV9wb2xfc3RhcnQpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8oMjQqNjAqNjApKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgZGVsYXlfb2NjX3JlcCA9IGlmZWxzZSh5ZWFyKGRhdGVfb2NjdXIpID09IDIxOTksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWlsaW5nKChhcy5udW1lcmljKGRhdGVfcmVwb3J0KSAtIGFzLm51bWVyaWMoZGF0ZV9vY2N1cikpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLygyNCo2MCo2MCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICBkZWxheV9yZXBfcGF5ID0gaWZlbHNlKHllYXIoZGF0ZV9vY2N1cikgPT0gMjE5OSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLTEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlaWxpbmcoKGFzLm51bWVyaWMoZGF0ZV9wYXkpIC0gYXMubnVtZXJpYyhkYXRlX3JlcG9ydCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLygyNCo2MCo2MCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICBkZWxheV91d192YWwgPSBjZWlsaW5nKChhcy5udW1lcmljKHRfaSkgLSBhcy5udW1lcmljKGRhdGVfcG9sX3N0YXJ0KSkvKDI0KjYwKjYwKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX3V3ID0gY2VpbGluZyhhcy5udW1lcmljKGRhdGVfcG9sX3N0YXJ0KS8oMjQqNjAqNjApKSwKICAgICAgICAgICAgICAgICAgIENvdmVyID0gYXMuZmFjdG9yKENvdmVyKSldCgoKYGBgCgpUaGF0IGZpbmlzaGVzIHRoZSBkYXRhIHByZXBhcmF0aW9uLiBXZSBjYW4gbm93IG1vdmUgb250byB0aGUgcHJvY2VzcyBvZiBidWlsZGluZyBtb2RlbHMgb2YgUkJOUyBhbmQgSUJOUiBhbmQgdXNpbmcgdGhlbSB0byBwcmVkaWN0IHRoZSBjdXJyZW50IHJlc2VydmUgcmVxdWlyZW1lbnRzLgoKIyBNb2RlbCBidWlsZCB7LnRhYnNldH0KCkkgd2lsbCB1c2UgdGhlIHhnYm9vc3QgUiBtYWNoaW5lIGxlYXJuaW5nICBwYWNrYWdlIHRvIGJ1aWxkIHRoZSBtb2RlbHMuIEluIEJhdWRyeSdzIG9yaWdpbmFsIHBhcGVyIGhlIHVzZWQgdGhlIGV4dHJhVHJlZXMgcGFja2FnZTsgYSB2YXJpYW50IHVwb24gUmFuZG9tIEZvcmVzdHMuIAoKWGdib29zdCBoYXMgYmVlbiBzZWxlY3RlZCBhcyBpdCBpcyB0aGUgbW9zdCBwb3B1bGFyIGltcGxlbWVudGF0aW9uIG9mIHRoZSBncmFkaWVudCBib29zdGluZyBtYWNoaW5lIChHQk0pIGFsZ29yaXRobS4gR0JNcyBoYXZlIGJlZW4ga25vd24gc2luY2UgYXJvdW5kIDIwMTUgdG8gYmUgdGhlIG1vc3QgYWNjdXJhdGUgYWxnb3JpdGhtaWMgdGVjaG5pcXVlIGZvciByZWdyZXNzaW9uIGFuZCBjbGFzc2lmaWNhdGlvbiB0YXNrcy4gIAoKVGhlIGNvZGUgYW5kIG1vZGVsaW5nIHByb2Nlc3MgZm9yIGVhY2ggcmVzZXJ2ZSB0eXBlIGlzIHZlcnkgc2ltaWxhciBzbyB0aGV5IGFyZSBzaG93biBpbiB0aGUgdGFiYmVkIHNlY3Rpb25zIGJlbG93IHJhdGhlciB0aGF0IHJlcGVhdGluZyBpbi1saW5lLgoKCiMjIFJCTlMgbW9kZWwKClN0YXJ0aW5nIHdpdGggdGhlIFJCTlMgcmVzZXJ2ZXMgd2Ugd2lsbCBmaXJzdCBidWlsZCBhIG1vZGVsIHVwb24gdGhlIHRyYWluaW5nIGRhdGEgdXNpbmcgYSBjcm9zcyB2YWxpZGF0ZWQgYXBwcm9hY2ggdG8gZW5hYmxlIHNlbGVjdGlvbiBvZiBrZXkgeGdib29zdCBoeXBlcnBhcmFtZXRlcnMuIAoKVGhlbiB1c2luZyBvcHRpbWFsIHBhcmFtZXRlcnMgd2UgcmV0cmFpbiBvbiB0aGUgY29tcGxldGUgdHJhaW5pbmcgZGF0YXNldC4gVGhpcyB0cmFpbmVkIG1vZGVsIGlzIHRoZW4gc2NvcmVkIGFnYWluc3QgdGhlIHRlc3QgZGF0YSBpbiBvcmRlciB0byBjcmVhdGUgcHJlZGljdGlvbnMgZnJvbSB3aGljaCB0aGUgUkJOUyByZXNlcnZlIGNhbiBiZSBjYWxjdWxhdGVkLgoKIyMjIENyZWF0aW5nIHhnYm9vc3QgZGF0YXNldAoKWGdib29zdCByZXF1aXJlcyBpdCdzIGRhdGEgdG8gYmUgaW4gYSBzcGVjaWZpYyBmb3JtYXQ7IGEgc3BlY2lhbCBtYXRyaXggZm9ybSBjYWxsZWQgYSBETWF0cml4LCBmb3Igd2hpY2ggdGhlcmUgaXMgYSBmdW5jdGlvbiBgeGdiLkRNYXRyaXhgLiBOb3QgYWxsIHZhcmlhYmxlIHR5cGVzIGNhbiBiZSBwYXNzZWQgdG8gYHhnYi5ETWF0cml4YCBpbiBwYXJ0aWN1bGFyIGNhdGVnb3JpY2FsIG9yIG5vbWluYWwgdmFyaWFibGVzIHN1Y2ggYXMgKipCcmFuZCoqIGhhdmUgdG8gYmUgY29udmVydGVkIGZyb20gdGV4dCB0byBudW1lcmljIHZhbHVlcy4gIAoKRm9yIHRoaXMgZXhhbXBsZSBJJ3ZlIGNob3NlbiB0byB1c2UgdGhlIGBwYXJzbmlwYCBwYWNrYWdlIGFuZCBjcmVhdGUgYSByZWNpcGUgdGhhdCBjb252ZXJ0cyBub21pbmFsIHByZWRpY3RvciB2YWx1ZXMgaW50byBhICoqb25lLWhvdC1lbmNvZGVkKiogZm9ybS4gCgoKYGBgIHtyIH0KClJCTlNfcHJlZGljdG9ycyA8LSBjKCJqIiwKICAgICAgICAgICAgICAgICAgICAgImsiLAogICAgICAgICAgICAgICAgICAgICAiQ292ZXIiLAogICAgICAgICAgICAgICAgICAgICAiY2xhaW1fdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICJCcmFuZCIsCiAgICAgICAgICAgICAgICAgICAgICJNb2RlbCIsCiAgICAgICAgICAgICAgICAgICAgICJQcmljZSIsCiAgICAgICAgICAgICAgICAgICAgICMiZGF0ZV91dyIsCiAgICAgICAgICAgICAgICAgICAgICMiZGVsYXlfdXdfb2NjIiwKICAgICAgICAgICAgICAgICAgICAgImRlbGF5X29jY19yZXAiKQoKcm93TGlzdF9SQk5TIDwtIGxpc3QodHJhaW49ZHRfQWxsX1JCTlNbLCB3aGljaChmbGdUcmFpbj09MSldLAogICAgICAgICAgICAgICAgdGVzdD1kdF9BbGxfUkJOU1ssIHdoaWNoKGZsZ1RyYWluPT0wKV0sCiAgICAgICAgICAgICAgICBhbGwgPSAxOm5yb3coZHRfQWxsX1JCTlMpKQoKClJCTlNfcmVjIDwtIHJlY2lwZSggfiAuLCBkYXRhID0gZHRfQWxsX1JCTlNbLCBSQk5TX3ByZWRpY3RvcnMsIHdpdGggPSBGQUxTRV0pICU+JQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgb25lX2hvdCA9IFRSVUUpICU+JQogIHByZXAoKQoKCmRmLlJCTlNfdHJhaW4gPC0gYmFrZShSQk5TX3JlYywgbmV3X2RhdGEgPSBkdF9BbGxfUkJOU1tyb3dMaXN0X1JCTlMkdHJhaW4sXSApCmRmLlJCTlNfdGVzdCA8LSBiYWtlKFJCTlNfcmVjLCBuZXdfZGF0YSA9IGR0X0FsbF9SQk5TW3Jvd0xpc3RfUkJOUyR0ZXN0LF0gKQpkZi5SQk5TX2FsbCA8LSBiYWtlKFJCTlNfcmVjLCBuZXdfZGF0YSA9IGR0X0FsbF9SQk5TICkKCgp4Z2IuUkJOU19ETWF0LnRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgoZGYuUkJOU190cmFpbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gZHRfQWxsX1JCTlNbcm93TGlzdF9SQk5TJHRyYWluLCB0YXJnZXRdKQoKeGdiLlJCTlNfRE1hdC50ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgoZGYuUkJOU190ZXN0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBkdF9BbGxfUkJOU1tyb3dMaXN0X1JCTlMkdGVzdCwgdGFyZ2V0XSkKCnhnYi5SQk5TX0RNYXQuYWxsIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgoZGYuUkJOU19hbGwpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gZHRfQWxsX1JCTlNbLCB0YXJnZXRdKQoKYGBgYAoKCgoKIyMjIEZpdCBpbml0aWFsIG1vZGVsIHVzaW5nIGNyb3NzIHZhbGlkYXRpb24KCkhhdmluZyBwcmVwYXJlZCB0aGUgZGF0YSBmb3IgeGdib29zdCBJIGNhbiBub3cgZml0IGFuIGluaXRpYWwgbW9kZWwuIEkndmUgdXNlZCBhIHNpbXBsZSBzZXQgb2YgaHlwZXItcGFyYW1ldGVycyBhbmQgdXNlZCBjcm9zcyB2YWxpZGF0aW9uIHRvIHNlbGVjdCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgYm9vc3RlZCB0cmVlcyAobnJvdW5kcykgZm9yIHRoZXNlIGh5cGVyLXBhcmFtZXRlciBzZWxlY3Rpb25zIGJ5IGNhbGxpbmcgdGhlIHhnYi5jdiBmdW5jdGlvbiB3aXRoIGVhcmx5IHN0b3BwaW5nLgoKSSBoYXZlIHVzZWQgdGhlICoqcmVnOnR3ZWVkaWUqKiBvYmplY3RpdmUgZnVuY3Rpb24gYmFzZWQgdXBvbiBpbnNwZWN0aW9uIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUuCgoKYGBgIHtyfQoKc3VtbWFyeShkdF9BbGxfUkJOU1tyb3dMaXN0X1JCTlMkdHJhaW4sIHRhcmdldF0pCmhpc3QoZHRfQWxsX1JCTlNbcm93TGlzdF9SQk5TJHRyYWluLCB0YXJnZXRdKQoKYGBgCgoKSW4gYSByZWFsIHdvcmxkIGV4YW1wbGUgYSBwcm9jZXNzIG9mIGh5cGVyLXBhcmFtZXRlciB0dW5pbmcgd291bGQgYmUgdW5kZXJ0YWtlbiBpbiBvcmRlciB0byBzZWxlY3QgbW9yZSBvcHRpbWFsIHhnYm9vc3QgaHlwZXItcGFyYW1ldGVycy4KCgpgYGAge3J9CgpwYXJhbSA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6dHdlZWRpZSIsCiAgbWF4X2RlcHRoID0gMkwsICAgICAgICAgICAgIyB0cmVlLWRlcHRoCiAgc3Vic2FtcGxlID0gMC43NSwgICAgICAgICAgIyByYW5kb21seSBzYW1wbGUgcm93cyBiZWZvcmUgZml0dGluZyBlYWNoIHRyZWUKICBjb2xzYW1wbGVfYnl0cmVlID0gMC44LCAgICAjIHJhbmRvbWx5IHNhbXBsZSBjb2x1bW5zIGJlZm9yZSBmaXR0aW5nIGVhY2ggdHJlZQogIG1pbi5jaGlsZC53ZWlnaHQgPSAxMCwgICAgICMgbWluaW11bSB3ZWlnaHQgcGVyIGxlYWYKICBldGEgPSAwLjEgICAgICAgICAgICAgICAgICAjIExlYXJuaW5nIHJhdGUKKQoKCiMgVHJhaW4gbW9kZWwgd2l0aCBjcm9zcyB2YWxpZGF0aW9uCnNldC5zZWVkKDE5ODQpICMgZm9yIHJlcGVhdGFiaWxpdHkKCnhnYl9SQk5TX0NWIDwtIHhnYi5jdigKICBwYXJhbXMgICAgICAgICAgICAgICAgID0gcGFyYW0sCiAgZGF0YSAgICAgICAgICAgICAgICAgICA9IHhnYi5SQk5TX0RNYXQudHJhaW4sCiAgbnJvdW5kcyAgICAgICAgICAgICAgICA9IDUwMCwgICAgICAgICMgTWF4aW11bSBudW1iZXIgb2YgdHJlZXMgdG8gYnVpbGQKICBuZm9sZCA9IDUsCiAgZWFybHlfc3RvcHBpbmdfcm91bmRzICA9IDEwTCwgICAgICAgICMgU3RvcHMgYWxnb3JpdGhtIGVhcmx5IGlmIHBlcmZvcm1hbmNlIGhhcyBub3QgaW1wcm92ZWQgaW4gCiAgcHJpbnRfZXZlcnlfbiAgICAgICAgICA9IDEwTCwgICAgICAgICMgSG93IG9mdGVuIHRvIHByaW50IHRvIGNvbnNvbGUKICBwcmVkaWN0aW9uICAgICAgICAgICAgID0gVFJVRSAgICAgICAgIyBLZWVwcyB0aGUgcHJlZGljdGlvbnMKKQpgYGAKCkhhdmluZyBmaXQgdGhlIG1vZGVsIHdlIHN0b3JlIHRoZSBvdXQtb2YtZm9sZCBwcmVkaWN0aW9ucy4KCmBgYCB7cn0KZHRfQWxsX1JCTlNbcm93TGlzdF9SQk5TJHRyYWluLCBwcmVkc19vb2YgOj0geGdiX1JCTlNfQ1YkcHJlZF0KCmBgYGAKCiMjIyBGaXQgZmluYWwgbW9kZWwgb24gYWxsIHRyYWluaW5nIGRhdGEKCkhhdmluZyBmaXQgdGhlIG1vZGVsIHVzaW5nIDUgZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHdlIG9ic2VydmUgdGhlIG9wdGltdW0gbnVtYmVyIG9mIGZpdHRpbmcgcm91bmRzIHRvIGJlIGByIHhnYl9SQk5TX0NWJGJlc3RfaXRlcmF0aW9uYC4gIAoKV2UgY2FuIHRoZW4gdXNlIHRoaXMgdG8gdHJhaW4gYSBmaW5hbCBtb2RlbCBvbiBhbGwgdGhlIGRhdGEuIAoKYGBgIHtyfQoKeGdiX1JCTlNfRml0IDwtIHhnYi50cmFpbigKICBwYXJhbXMgICAgICAgICAgICAgICAgID0gcGFyYW0sCiAgZGF0YSAgICAgICAgICAgICAgICAgICA9IHhnYi5SQk5TX0RNYXQudHJhaW4sCiAgbnJvdW5kcyAgICAgICAgICAgICAgICA9IHhnYl9SQk5TX0NWJGJlc3RfaXRlcmF0aW9uLAojIGJhc2Vfc2NvcmUgICAgICAgICAgICAgPSAxLAogIHdhdGNobGlzdCAgICAgICAgICAgICAgPSBsaXN0KHRyYWluPXhnYi5SQk5TX0RNYXQudHJhaW4sIHRlc3Q9eGdiLlJCTlNfRE1hdC50ZXN0KSAsCiAgcHJpbnRfZXZlcnlfbiAgICAgICAgICA9IDEwCikKCmR0X0FsbF9SQk5TWywgcHJlZHNfZnVsbCA6PSBwcmVkaWN0KHhnYl9SQk5TX0ZpdCx4Z2IuUkJOU19ETWF0LmFsbCldCgpgYGBgCgoKIyMjIEluc3BlY3QgbW9kZWwgZml0CgpIYXZpbmcgZml0dGVkIHRoZSBmdWxsIG1vZGVsIHdlIGNhbiB0aGVuIGluc3BlY3QgdGhlIG1vZGVsIGZpdC4gVGhlIHRyYWRpdGlvbmFsIHdheSBvZiBpbnNwZWN0aW5nIGdsb2JhbCBtb2RlbCBmZWF0dXJlIGltcG9ydGFuY2UgaXMgdG8gdXNlIHRoZSBnYWlucyBjaGFydC4gCgpgYGAge3J9CiNkZWZhdWx0IGZlYXR1cmUgaW1wb3J0YW5jZSBieSBnYWluCmZlYXRJbXBfUkJOUyA8LSB4Z2IuaW1wb3J0YW5jZSh4Z2JfUkJOU19GaXQsIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyh4Z2IuUkJOU19ETWF0LnRyYWluKSkKeGdiLnBsb3QuaW1wb3J0YW5jZShmZWF0SW1wX1JCTlMsIG1haW49IkZlYXR1cmUgSW1wb3J0YW5jZSAtIFJCTlMiKQoKYGBgCgoKQW4gaW5jcmVhc2luZ2x5IHBvcHVsYXIgYW5kIG1vcmUgcm9idXN0IGFwcHJvYWNoIGlzIHRvIHVzZSBTSEFQIHZhbHVlcyBbaHR0cHM6Ly9naXRodWIuY29tL3NsdW5kYmVyZy9zaGFwXShodHRwOi8vKS4gIFRoZSBTSEFQIGVxdWl2YWxlbnQgb2YgdGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBjaGFydCBpcyBzaG93biBiZWxvdy4KCmBgYCB7cn0KCiMgUmV0dXJuIHRoZSBTSEFQIHZhbHVlcyBhbmQgcmFua2VkIGZlYXR1cmVzIGJ5IG1lYW58U0hBUHwKc2hhcF92YWx1ZXMgPC0gc2hhcC52YWx1ZXMoeGdiX21vZGVsID0geGdiX1JCTlNfRml0LCBYX3RyYWluID0gYXMubWF0cml4KGRmLlJCTlNfdHJhaW4pKQoKIyBQcmVwYXJlIHRoZSBsb25nLWZvcm1hdCBkYXRhOgpzaGFwX2xvbmcgPC0gc2hhcC5wcmVwKHNoYXBfY29udHJpYiA9IHNoYXBfdmFsdWVzJHNoYXBfc2NvcmUsIFhfdHJhaW4gPSAgYXMubWF0cml4KGRmLlJCTlNfdHJhaW4pKQoKIyAqKlNIQVAgc3VtbWFyeSBwbG90KioKc2hhcC5wbG90LnN1bW1hcnkoc2hhcF9sb25nLCBkaWx1dGUgPSBucm93KGRmLlJCTlNfdHJhaW4pLzEwMDAwKQoKYGBgCiAgICAKQSBzZWNvbmQgdXNlZnVsIGNoYXJ0IGlzIHRoZSBwYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdC4gVGhpcyBzaG93cyBob3cgdGhlIHZhbHVlcyBvZiBhIHByZWRpY3RpdmUgKGlucHV0KSBmZWF0dXJlIGluZmx1ZW5jZSB0aGUgcHJlZGljdGVkIChvdXRwdXQpIHZhbHVlLCB3aGlsZSBob2xkaW5nIHRoZSB2YWx1ZXMgb2YgYWxsIG90aGVyIHByZWRpY3RpdmUgKGlucHV0KSBmZWF0dXJlcyBjb25zdGFudC4gSXQgaXMgYWxzbyBrbm93biBhcyB0aGUgbWFyZ2luYWwgZWZmZWN0LgoKSGVyZSB3ZSBzaG93IHRoZSBwYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdHMgZm9yIHRoZSB0b3AgNCBTSEFQIGZlYXR1cmVzLgoKYGBgIHtyIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKZmlnX2xpc3QgPC0gbGFwcGx5KG5hbWVzKHNoYXBfdmFsdWVzJG1lYW5fc2hhcF9zY29yZSlbMTo0XSwgCiAgICAgICAgICAgICAgICAgICBzaGFwLnBsb3QuZGVwZW5kZW5jZSwKICAgICAgICAgICAgICAgICAgIGRhdGFfbG9uZyA9IHNoYXBfbG9uZywKICAgICAgICAgICAgICAgICAgIGRpbHV0ZSA9IG5yb3coc2hhcF9sb25nKS8gMTAwMDApCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShncm9icyA9IGZpZ19saXN0LCBuY29sID0gMikKCmBgYAoKVGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBhbmQgcGFydGlhbCBkZXBlbmRlbmN5IHBsb3RzIHByb3ZpZGUgcXVpY2sgaW5zaWdodCBpbnRvIHRoZSBtb2RlbC4gIAoKKiBXZSBzZWUgdGhhdCBjbGFpbSBkZXZlbG9wbWVudCBwZXJpb2QsIGosIGlzIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlIGFuZCB0aGF0IHRoZSBSQk5TIHJlc2VydmUgaXMgc21hbGxlciBmb3IgbGFyZ2VyIHZhbHVlcyBvZiBqLgoqIFRoZSBzZWNvbmQgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZSBpcyBjbGFpbXR5cGUgd2hlcmUgYnJlYWthZ2UgY2xhaW1zIGFyZSBhc3NvY2lhdGVkIHdpdGggbG93ZXIgUkJOUyByZXNlcnZlcy4gVGhpcyBpcyBhcyBleHBlY3RlZCBmcm9tIHRoZSBkYXRhIGdlbmVyYXRpbmcgcHJvY2VzcyB3aGVyZSBicmVha2FnZSBjbGFpbXMgY29tZSBmcm9tIGEgQmV0YSBkaXN0cmlidXRpb24gd2l0aCBhIGxvd2VyIG1lYW4uCiogVGhlIHRoaXJkIG1vc3QgaW1wb3J0YW50IGZlYXR1cmUgaXMgcGhvbmUgcHJpY2Ugd2hlcmUgdGhlcmUgaXMgbGluZWFyIGluY3JlYXNlIGluIFJCTlMgcmVzZXJ2ZSB3aXRoIGluY3JlYXNpbmcgcGhvbmUgcHJpY2UuIFRoaXMgaXMgYWxzbyBhcyBleHBlY3RlZCBmcm9tIHRoZSBkYXRhIGdlbmVyYXRpbmcgcHJvY2Vzcy4KKiBUaGUgZm91cnRoIGZlYXR1cmUgaXMgcGhvbmUgYnJhbmQgd2hpY2ggYWdhaW4gZm9sbG93cyBleHBlY3RhdGlvbnMgZnJvbSB0aGUgZGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MuIAoKClNIQVAgdmFsdWVzIGNhbiBhbHNvIGJlIHVzZWQgdG8gc2hvdyB0aGUgY29tcG9uZW50cyBvZiBhIHNpbmdsZSBwcmVkaWN0aW9uLiBJbiB0aGUgZm9sbG93aW5nIHBsb3Qgd2Ugc2hvdyB0aGUgdG9wIDQgY29tcG9uZW50cyBmb3IgZWFjaCByb3cgb2YgdGhlIGRhdGEgYW5kIHpvb20gaW4gYXQgcm93IDUwMC4gCgpgYGAge3Igd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgY2hvb3NlIHRvIHNob3cgdG9wIDQgZmVhdHVyZXMgYnkgc2V0dGluZyBgdG9wX24gPSA0YCwgc2V0IDYgY2x1c3RlcmluZyBncm91cHMuICAKcGxvdF9kYXRhIDwtIHNoYXAucHJlcC5zdGFjay5kYXRhKHNoYXBfY29udHJpYiA9IHNoYXBfdmFsdWVzJHNoYXBfc2NvcmUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BlcmNlbnQgPSAxMDAwMC9ucm93KHNoYXBfbG9uZyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BfbiA9IDQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2dyb3VwcyA9IDYpCiAgCiMgY2hvb3NlIHRvIHpvb20gaW4gYXQgbG9jYXRpb24gNTAwLCBzZXQgeS1heGlzIGxpbWl0IHVzaW5nIGB5X3BhcmVudF9saW1pdGAgIAojIGl0IGlzIGFsc28gcG9zc2libGUgdG8gc2V0IHktYXhpcyBsaW1pdCBmb3Igem9vbS1pbiBwYXJ0IGFsb25lIHVzaW5nIGB5X3pvb21pbl9saW1pdGAgIApzaGFwLnBsb3QuZm9yY2VfcGxvdChwbG90X2RhdGEsIHpvb21faW5fbG9jYXRpb24gPSA1MDAsIHlfcGFyZW50X2xpbWl0ID0gYygtMSwxKSkKICAKCmBgYAoKIyMjIFN1bW1hcmlzaW5nIFJCTlMgcmVzZXJ2ZXMKCkJ5IGNvbXBhcmluZyB0aGUgbW9kZWwgcHJlZGljdGlvbnMgdG8gdGhlIHNpbXVsYXRlZCBjbGFpbXMgcnVuLW9mZiB3ZSBjYW4gZ2V0IGEgZmVlbCBmb3IgdGhlIGFjY3VyYWN5IG9mIHRoZSBtYWNoaW5lIGxlYXJuaW5nIGFwcHJvYWNoLiBCZWxvdyBJIGFnZ3JlZ2F0ZSB0aGUgUkJOUyBwcmVkaWN0aW9ucyBieSBjbGFpbSBvY2N1cnJlbmNlIG1vbnRoIGFuZCBjb21wYXJlIHRoZW0gdG8gdGhlIGtub3duIHNpbXVsYXRlZCBjbGFpbSBydW4tb2ZmLgoKCmBgYCB7cn0KCmR0X0FsbF9SQk5TIFssIGRhdGVfb2NjdXJfWVlZWU1NIDo9IGFzLmNoYXJhY3Rlcih5ZWFyKGRhdGVfb2NjdXIpICsgbW9udGgoZGF0ZV9vY2N1cikvMTAwICldCgpkdF9SQk5TX3N1bW1hcnkgPC0gZHRfQWxsX1JCTlNbcm93TGlzdF9SQk5TJHRlc3QsLihwcmVkcyA9IHN1bShwcmVkc19mdWxsKSwgdGFyZ2V0ID0gc3VtKHRhcmdldCkpLCBrZXlieSA9IGRhdGVfb2NjdXJfWVlZWU1NXQoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojIFN1bSB0aGUgbGFzdCByb3cgb2YgZWFjaCBjb2x1bW4gaWYgbnVtZXJpYyAKc3Vtcm93IDwtIGNiaW5kKGRhdGEudGFibGUoZGF0ZV9vY2N1cl9ZWVlZTU0gPSAiVG90YWwiKSwgZHRfUkJOU19zdW1tYXJ5WywgbGFwcGx5KC5TRCwgc3VtKSwgLlNEY29scyA9IGMoInByZWRzIiwgInRhcmdldCIpXSkKCmR0X1JCTlNfc3VtbWFyeSA8LSByYmluZChkdF9SQk5TX3N1bW1hcnksIHN1bXJvdywgZmlsbCA9IFRSVUUpCgpkdF9SQk5TX3N1bW1hcnlbLCAnOj0nKERpZmYgPSAocHJlZHMgLSB0YXJnZXQpLAogICAgICAgICAgICAgICAgICAgICAgIERpZmZfcGNudCA9ICBzY2FsZXM6OnBlcmNlbnQoKHByZWRzIC0gdGFyZ2V0KSAvIHRhcmdldCwgYWNjdXJhY3kgPSAwLjEpKV0KCgprYWJsZShkdF9SQk5TX3N1bW1hcnksICJodG1sIiwKICAgICAgZGlnaXRzID0gYygyLDAsMCwwLDEpLAogICAgICBmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAiLCIsIAogICAgICAgIHNjaWVudGlmaWMgPSBGQUxTRSkKICAgICAgKSAlPiUgCiAga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JSBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiKQoKYGBgCgpZb3UgY2FuIG5vdyBqdW1wIGJhY2sgdXAgdG8gdGhlIGJlZ2lubmluZyBvZiB0aGUgbW9kZWxpbmcgc2VjdGlvbiBhbmQgc2VsZWN0IHRoZSBJQk5SIGZyZXF1ZW5jeSBtb2RlbGluZyB0YWIuIAoKIyMgSUJOUiBGcmVxdWVuY3kgbW9kZWwKCklCTlIgcmVzZXJ2ZXMgYXJlIGJ1aWx0IGJ5IG11bHRpcGx5aW5nIHRoZSBvdXRwdHMgb2YgYSBjbGFpbSBmcmVxdWVuY3kgYW5kIGNsYWltIHNldmVyaXR5IG1vZGVsLiBUaGUgZ2VuZXJhbCBwcm9jZXNzIG9mIGJ1aWxkaW5nIHRoZSBtb2RlbCBmb2xsb3dzIHRoYXQgb2YgdGhlIFJCTlMgcmVzZXJ2ZXMuIAoKIyMjIENyZWF0aW5nIHhnYm9vc3QgZGF0YXNldAoKQWdhaW4gd2UgY29udmVydCB0aGUgZGF0YSBpbnRvIGEgc3BlY2lmaWMgbWF0cml4IGZvcm0gY2FsbGVkIGEgRE1hdHJpeC4gSG93ZXZlciBiZWZvcmUgZG9pbmcgc28gd2UgYWdncmVnYXRlIHRoZSBkYXRhIGZvciBlZmZpY2llbmN5IG9mIG1vZGVsIGZpdCB0aW1lcy4gVGhlcmUgY291bGQgYmUgYSBsb3NzIG9mIGFjY3VyYWN5IGluIGFnZ3JlZ2F0aW5nIGRhdGEsIHNvIGluIHByYWN0aWNlIHRoaXMgaXMgc29tZXRoaW5nIHlvdSB3b3VsZCB3YW50IHRvIGV4cGVyaW1lbnQgd2l0aC4gCgpUaGUgb3RoZXIgcG9pbnQgdG8gbm90ZSBpcyB0aGUgdmFsdWVzIG9mICoqZmxnVHJhaW4qKiB1c2VkIHRvIGlkZW50aWZ5IHRoZSB0cmFpbmluZyBhbmQgdGVzdCByb3dzIGluIG91ciBkYXRhc2V0LiBSZWNhbGwgZnJvbSBOb3RlYm9vayAyLCBpbiB0aGUgSUJOUiBkYXRhc2V0IHRyYWluaW5nIHJvd3MgZm9yIHRoZSBmcmVxdWVuY3kgbW9kZWwgaGF2ZSBhIGZsZ3RyYWluIHZhbHVlIG9mIDEgd2hlcmVhcyB0aGUgU2V2ZXJpdHkgdHJhaW5pbmcgcm93cyBoYXZlIGEgdmFsdWUgb2YgMi4gCgoKYGBgIHtyIH0KCklCTlJfcHJlZGljdG9ycyA8LSBjKCJqIiwKICAgICAgICAgICAgICAgICAgICAgImsiLAogICAgICAgICAgICAgICAgICAgICAiQ292ZXIiLAogICAgICAgICAgICAgICAgICAgICAiQnJhbmQiLAogICAgICAgICAgICAgICAgICAgICAiTW9kZWwiLAogICAgICAgICAgICAgICAgICAgICAiUHJpY2UiLAogICAgICAgICAgICAgICAgICAgICAiZGF0ZV91dyIpCgojIGFnZ3JlZ2F0ZSB0aGUgZGF0YSAuLi4gZG9lcyB0aGlzIGxlYWQgdG8gbG9zcyBvZiB2YXJpYW5jZSBhbmQgYWNjdXJhY3k/CmR0X0FsbF9JQk5SIFssIGRhdGVfcG9sX3N0YXJ0X1lZWVlNTSA6PSBhcy5jaGFyYWN0ZXIoeWVhcihkYXRlX3BvbF9zdGFydCkgKyBtb250aChkYXRlX3BvbF9zdGFydCkvMTAwICldCgpkdF9BbGxfSUJOUl9GIDwtIGR0X0FsbF9JQk5SWywgLihleHBvc3VyZSA9IHN1bShleHBvc3VyZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X2Nvc3QgPSBzdW0odGFyZ2V0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfY291bnQgPSBzdW0odGFyZ2V0PjApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5PSBjKElCTlJfcHJlZGljdG9ycywgImRhdGVfcG9sX3N0YXJ0X1lZWVlNTSIsICJmbGdUcmFpbiIpXQoKZHRfQWxsX0lCTlJfRiA8LSBkdF9BbGxfSUJOUl9GW2V4cG9zdXJlPjBdCgoKIyBzZXR1cCB0cmFpbiBhbmQgdGVzdCByb3dzCnJvd0xpc3RfSUJOUl9GIDwtIGxpc3QodHJhaW49ZHRfQWxsX0lCTlJfRlssIHdoaWNoKGZsZ1RyYWluPT0xKV0sCiAgICAgICAgICAgICAgICAgICAgIHRlc3Q9ZHRfQWxsX0lCTlJfRlssIHdoaWNoKGZsZ1RyYWluPT0wKV0sCiAgICAgICAgICAgICAgICAgICAgIGFsbCA9IGR0X0FsbF9JQk5SX0ZbLCB3aGljaChmbGdUcmFpbiE9MildKQoKIyBzZXR1cCBkYXRhIGZvciB4Z2Jvb3N0CklCTlJfcmVjIDwtIHJlY2lwZSggfiAuLCBkYXRhID0gZHRfQWxsX0lCTlJfRlssIElCTlJfcHJlZGljdG9ycywgd2l0aCA9IEZBTFNFXSkgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCBvbmVfaG90ID0gVFJVRSkgJT4lCiAgcHJlcCgpCgpkZi5JQk5SX0ZfdHJhaW4gPC0gYmFrZShJQk5SX3JlYywgbmV3X2RhdGEgPSBkdF9BbGxfSUJOUl9GW3Jvd0xpc3RfSUJOUl9GJHRyYWluLF0gKQpkZi5JQk5SX0ZfdGVzdCA8LSBiYWtlKElCTlJfcmVjLCBuZXdfZGF0YSA9IGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkdGVzdCxdICkKZGYuSUJOUl9GX2FsbCA8LSBiYWtlKElCTlJfcmVjLCBuZXdfZGF0YSA9IGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkYWxsLF0gKQoKeGdiLklCTlJfRl9ETWF0LnRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgoZGYuSUJOUl9GX3RyYWluKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gZHRfQWxsX0lCTlJfRltyb3dMaXN0X0lCTlJfRiR0cmFpbiwgZXhwb3N1cmVdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkdHJhaW4sIHRhcmdldF9jb3VudF0pCgp4Z2IuSUJOUl9GX0RNYXQudGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gYXMubWF0cml4KGRmLklCTlJfRl90ZXN0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSBkdF9BbGxfSUJOUl9GW3Jvd0xpc3RfSUJOUl9GJHRlc3QsIGV4cG9zdXJlXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkdGVzdCwgdGFyZ2V0X2NvdW50XSkKCnhnYi5JQk5SX0ZfRE1hdC5hbGwgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeChkZi5JQk5SX0ZfYWxsKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkYWxsLCBleHBvc3VyZV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkYWxsLCB0YXJnZXRfY291bnRdKQoKYGBgYAoKIyMjIEZpdCBpbml0aWFsIG1vZGVsIHVzaW5nIGNyb3NzIHZhbGlkYXRpb24KCkhhdmluZyBwcmVwYXJlZCB0aGUgZGF0YSBmb3IgeGdib29zdCBJIGNhbiBub3cgZml0IGFuIGluaXRpYWwgbW9kZWwuIEnigJl2ZSB1c2VkIGEgc2ltcGxlIHNldCBvZiBoeXBlci1wYXJhbWV0ZXJzIGFuZCB1c2VkIGNyb3NzIHZhbGlkYXRpb24gdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBib29zdGVkIHRyZWVzIChucm91bmRzKSBmb3IgdGhlc2UgaHlwZXItcGFyYW1ldGVyIHNlbGVjdGlvbnMgYnkgY2FsbGluZyB0aGUgeGdiLmN2IGZ1bmN0aW9uIHdpdGggZWFybHkgc3RvcHBpbmcuIAoKSSBoYXZlIHVzZWQgdGhlICoqY291bnQ6cG9pc3NvbioqIG9iamVjdGl2ZSBmdW5jdGlvbiBiYXNlZCB1cG9uIGluc3BlY3Rpb24gb2YgdGhlIHRhcmdldCB2YXJpYWJsZS4KCgpgYGAge3J9CgpoaXN0KGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkdHJhaW4sIHRhcmdldF9jb3VudF0pCgpgYGAKCgpgYGAge3J9CgpwYXJhbSA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJjb3VudDpwb2lzc29uIiwKICBtYXhfZGVwdGggPSAyTCwgICAgICAgICAgICMgdHJlZS1kZXB0aAogIHN1YnNhbXBsZSA9IDAuNywgICAgICAgICAgIyByYW5kb21seSBzYW1wbGUgcm93cyBiZWZvcmUgZml0dGluZyBlYWNoIHRyZWUKICBjb2xzYW1wbGVfYnl0cmVlID0gMC44LCAgICMgcmFuZG9tbHkgc2FtcGxlIGNvbHVtbnMgYmVmb3JlIGZpdHRpbmcgZWFjaCB0cmVlCiAgbWluLmNoaWxkLndlaWdodCA9IDEwLCAgICAjIG1pbmltdW0gd2VpZ2h0IHBlciBsZWFmCiAgZXRhID0gMC4xICAgICAgICAgICAgICAgIyBMZWFybmluZyByYXRlCiAgI21vbm90b25lX2NvbnN0cmFpbnRzID0gbW9ub3RvbmVfVmVjICMgTW9ub3RvbmljaXR5IGNvbnN0cmFpbnRzCikKCgojIFRyYWluIG1vZGVsIHdpdGggY3Jvc3MgdmFsaWRhdGlvbgpzZXQuc2VlZCgxOTg0KSAjIGZvciByZXBlYXRhYmlsaXR5Cgp4Z2JfSUJOUl9GX0NWIDwtIHhnYi5jdigKIHBhcmFtcyAgICAgICAgICAgICAgICAgPSBwYXJhbSwKIGRhdGEgICAgICAgICAgICAgICAgICAgPSB4Z2IuSUJOUl9GX0RNYXQudHJhaW4sCiBucm91bmRzICAgICAgICAgICAgICAgID0gMjAwMCwgICAgICAgICMgTWF4aW11bSBudW1iZXIgb2YgdHJlZXMgdG8gYnVpbGQKIG5mb2xkID0gNSwKCiBlYXJseV9zdG9wcGluZ19yb3VuZHMgID0gNTBMLCAgICAgICAgIyBTdG9wcyBhbGdvcml0aG0gZWFybHkgaWYgcGVyZm9ybWFuY2UgaGFzIG5vdCBpbXByb3ZlZCBpbiBuIHJvdW5kcwogcHJpbnRfZXZlcnlfbiAgICAgICAgICA9IDUwTCwgICAgICAgICMgSG93IG9mdGVuIHRvIHByaW50IHRvIGNvbnNvbGUKICNiYXNlX3Njb3JlICAgICAgICAgICAgID0gMC4wMDEsICAgICAgICMgTW9kZWwgc3RhcnRpbmcgcG9pbnQKICAgcHJlZGljdGlvbiAgICAgICAgICAgICA9IFRSVUUgICAgICAgICMgS2VlcHMgdGhlIHByZWRpY3Rpb25zCikKYGBgCgpIYXZpbmcgZml0IHRoZSBtb2RlbCB3ZSBzdG9yZSB0aGUgb3V0LW9mLWZvbGQgcHJlZGljdGlvbnMgZm9yIGJvdGggdGhlIGNsYWltIGZyZXF1ZW5jeSBhbmQgdGhlIGNsYWltIGNvdW50cy4KCmBgYCB7cn0KIGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkdHJhaW4sIHByZWRzX29vZl9JQk5SX0YgOj0geGdiX0lCTlJfRl9DViRwcmVkXQogZHRfQWxsX0lCTlJfRltyb3dMaXN0X0lCTlJfRiR0cmFpbiwgcHJlZHNfb29mX0lCTlJfTm9zIDo9IGV4cG9zdXJlICogcHJlZHNfb29mX0lCTlJfRl0KCmBgYAoKIyMjIEZpdCBmaW5hbCBtb2RlbCBvbiBhbGwgdHJhaW5pbmcgZGF0YQoKSGF2aW5nIGZpdCB0aGUgbW9kZWwgdXNpbmcgNSBmb2xkIGNyb3NzIHZhbGlkYXRpb24gd2Ugb2JzZXJ2ZSB0aGUgb3B0aW11bSBudW1iZXIgb2YgZml0dGluZyByb3VuZHMgdG8gYmUgYHIgeGdiX0lCTlJfRl9DViRiZXN0X2l0ZXJhdGlvbmAuIAoKV2UgY2FuIHRoZW4gdXNlIHRoaXMgdG8gdHJhaW4gYSBmaW5hbCBtb2RlbCBvbiBhbGwgdGhlIGRhdGEuIAoKYGBgIHtyfQoKeGdiX0lCTlJfRl9GaXQgPC0geGdiLnRyYWluKAogICBwYXJhbXMgICAgICAgICAgICAgICAgID0gcGFyYW0sCiAgIGRhdGEgICAgICAgICAgICAgICAgICAgPSB4Z2IuSUJOUl9GX0RNYXQudHJhaW4sCiAgIG5yb3VuZHMgICAgICAgICAgICAgICAgPSB4Z2JfSUJOUl9GX0NWJGJlc3RfaXRlcmF0aW9uLAojIGJhc2Vfc2NvcmUgICAgICAgICAgICAgPSAxLAogICB3YXRjaGxpc3QgICAgICAgICAgICAgID0gbGlzdCh0cmFpbj14Z2IuSUJOUl9GX0RNYXQudHJhaW4sIHRlc3Q9eGdiLklCTlJfRl9ETWF0LnRlc3QpICwKICAgcHJpbnRfZXZlcnlfbiAgICAgICAgICA9IDUwCiApCiAKIGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkYWxsLCBwcmVkc19mdWxsX0lCTlJfTm9zIDo9IHByZWRpY3QoeGdiX0lCTlJfRl9GaXQseGdiLklCTlJfRl9ETWF0LmFsbCldCgpgYGBgCgoKIyMjIEluc3BlY3QgbW9kZWwgZml0CgpIYXZpbmcgZml0dGVkIHRoZSBmdWxsIG1vZGVsIHdlIGNhbiB0aGVuIGluc3BlY3QgdGhlIG1vZGVsIGZpdC4gVGhlIHRyYWRpdGlvbmFsIHdheSBvZiBpbnNwZWN0aW5nIGdsb2JhbCBtb2RlbCBmZWF0dXJlIGltcG9ydGFuY2UgaXMgdG8gdXNlIHRoZSBnYWlucyBjaGFydC4KCmBgYCB7cn0KI2RlZmF1bHQgZmVhdHVyZSBpbXBvcnRhbmNlIGJ5IGdhaW4KZmVhdEltcF9JQk5SX0YgPC0geGdiLmltcG9ydGFuY2UoeGdiX0lCTlJfRl9GaXQsIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyh4Z2IuSUJOUl9GX0RNYXQudHJhaW4pKQp4Z2IucGxvdC5pbXBvcnRhbmNlKGZlYXRJbXBfSUJOUl9GLCBtYWluPSJGZWF0dXJlIEltcG9ydGFuY2UgLSBJQk5SIEZyZXF1ZW5jeSIpCgpgYGAKCkFuIGluY3JlYXNpbmdseSBwb3B1bGFyIGFuZCBtb3JlIHJvYnVzdCBhcHByb2FjaCBpcyB0byB1c2UgU0hBUCAgdmFsdWVzIFtodHRwczovL2dpdGh1Yi5jb20vc2x1bmRiZXJnL3NoYXBdKGh0dHA6Ly8pLiAgVGhlIFNIQVAgZXF1aXZhbGVudCBvZiB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIGNoYXJ0IGlzIHNob3duIGJlbG93LgoKYGBgIHtyfQoKIyBSZXR1cm4gdGhlIFNIQVAgdmFsdWVzIGFuZCByYW5rZWQgZmVhdHVyZXMgYnkgbWVhbnxTSEFQfApzaGFwX3ZhbHVlcyA8LSBzaGFwLnZhbHVlcyh4Z2JfbW9kZWwgPSB4Z2JfSUJOUl9GX0ZpdCwgWF90cmFpbiA9IGFzLm1hdHJpeChkZi5JQk5SX0ZfdHJhaW4pKQoKIyBQcmVwYXJlIHRoZSBsb25nLWZvcm1hdCBkYXRhOgpzaGFwX2xvbmcgPC0gc2hhcC5wcmVwKHNoYXBfY29udHJpYiA9IHNoYXBfdmFsdWVzJHNoYXBfc2NvcmUsIFhfdHJhaW4gPSAgYXMubWF0cml4KGRmLklCTlJfRl90cmFpbikpCgojICoqU0hBUCBzdW1tYXJ5IHBsb3QqKgpzaGFwLnBsb3Quc3VtbWFyeShzaGFwX2xvbmcsIGRpbHV0ZSA9IG5yb3coZGYuSUJOUl9GX3RyYWluKS8xMDAwMCkKCmBgYAogIApBIHNlY29uZCB1c2VmdWwgY2hhcnQgaXMgdGhlIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90LiBUaGlzIHNob3dzIGhvdyB0aGUgdmFsdWVzIG9mIGEgcHJlZGljdGl2ZSAoaW5wdXQpIGZlYXR1cmUgaW5mbHVlbmNlIHRoZSBwcmVkaWN0ZWQgKG91dHB1dCkgdmFsdWUsIHdoaWxlIGhvbGRpbmcgdGhlIHZhbHVlcyBvZiBhbGwgb3RoZXIgcHJlZGljdGl2ZSAoaW5wdXQpIGZlYXR1cmVzIGNvbnN0YW50LiBJdCBpcyBhbHNvIGtub3duIGFzIHRoZSBtYXJnaW5hbCBlZmZlY3QuCgpIZXJlIHdlIHNob3cgdGhlIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cyBmb3IgdGhlIHRvcCA0IFNIQVAgZmVhdHVyZXMuCgpgYGAge3Igd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CgpmaWdfbGlzdCA8LSBsYXBwbHkobmFtZXMoc2hhcF92YWx1ZXMkbWVhbl9zaGFwX3Njb3JlKVsxOjRdLCAKICAgICAgICAgICAgICAgICAgIHNoYXAucGxvdC5kZXBlbmRlbmNlLAogICAgICAgICAgICAgICAgICAgZGF0YV9sb25nID0gc2hhcF9sb25nLAogICAgICAgICAgICAgICAgICAgZGlsdXRlID0gbnJvdyhzaGFwX2xvbmcpLyAxMDAwMCkKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGdyb2JzID0gZmlnX2xpc3QsIG5jb2wgPSAyKQoKYGBgCgoKVGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBhbmQgcGFydGlhbCBkZXBlbmRlbmN5IHBsb3RzIHByb3ZpZGUgcXVpY2sgaW5zaWdodCBpbnRvIHRoZSBtb2RlbC4gIAoKKiBXZSBzZWUgdGhhdCBjbGFpbSBkZXZlbG9wbWVudCBwZXJpb2QsIGosIGlzIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlIGFuZCB0aGF0IHRoZSBJQk5SIGZyZXF1ZW5jeSBpcyBzbWFsbGVyIGZvciBsYXJnZXIgdmFsdWVzIG9mIGouCiogVGhlIHNlY29uZCBtb3N0IGltcG9ydGFudCBmZWF0dXJlIGlzIHBob25lIHByaWNlIHdpdGggdGhlIElCTlIgY2xhaW0gY291bnQgaW5jcmVhc2luZyBhcyBwcmljZSBpbmNyZWFzZXMuIEFsdGhvdWdoIHRoaXMgaXMgbm90IGEgZGlyZWN0IGZlYXR1cmUgaW4gdGhlIGRhdGEgZ2VuZXJhdGluZyBwcm9jZXNzIHRoZXJlIGlzIGEgbGluayB3aXRoIGhpZ2hlciB0aGVmdCBmcmVxdWVuY2llcyBhbmQgaGlnaGVyIHBob25lIHByaWNlcy4gCiogVGhlIHRoaXJkIG1vc3QgaW1wb3J0YW50IGZlYXR1cmUgaXMgcGhvbmUgbW9kZWwgd2l0aCBJQk5SIGZyZXF1ZW5jeSBpbmNyZWFzaW5nIHdpdGggbW9kZWwgdHlwZS4gVGhpcyBpcyBleHBlY3RlZCBmcm9tIHRoZSBkYXRhIGdlbmVyYXRpbmcgcHJvY2VzcyBhcyB0aGVmdCBjbGFpbSBmcmVxdWVuY3kgaW5jcmVhc2VzIHdpdGggbW9kZWwgdHlwZS4KKiBUaGUgZm91cnRoIGZlYXR1cmUgaXMgcGhvbmUgY292ZXIgYW5kIGl0IHNob3VsZCBiZSBubyBzdXByaXNlIHRvIHNlZSB0aGF0IEJyZWFrYWdlIG9ubHksIHRoZSBtb3N0IGJhc2ljIGNvdmVyIGxldmVsIGlzIGFzc29jaWF0ZWQgd2l0aCBsb3dlciBJQk5SIGNsYWltIGNvdW50cy4gCgoKU0hBUCB2YWx1ZXMgY2FuIGFsc28gYmUgdXNlZCB0byBzaG93IHRoZSBjb21wb25lbnRzIG9mIGEgc2luZ2xlIHByZWRjdGlvbi4gSW4gdGhlIGZvbGxvd2luZyBwbG90IHdlIHNob3cgdGhlIHRvcCA0IGNvbXBvbmVudHMgZm9yIGVhY2ggcm93IG9mIHRoZSBkYXRhIGFuZCB6b29tIGluIGF0IHJvdyA1MDAuIAoKYGBgIHtyIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIGNob29zZSB0byBzaG93IHRvcCA0IGZlYXR1cmVzIGJ5IHNldHRpbmcgYHRvcF9uID0gNGAsIHNldCA2IGNsdXN0ZXJpbmcgZ3JvdXBzLiAgCnBsb3RfZGF0YSA8LSBzaGFwLnByZXAuc3RhY2suZGF0YShzaGFwX2NvbnRyaWIgPSBzaGFwX3ZhbHVlcyRzaGFwX3Njb3JlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wZXJjZW50ID0gMTAwMDAvbnJvdyhzaGFwX2xvbmcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wX24gPSA0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9ncm91cHMgPSA2KQogIAojIGNob29zZSB0byB6b29tIGluIGF0IGxvY2F0aW9uIDUwMCwgc2V0IHktYXhpcyBsaW1pdCB1c2luZyBgeV9wYXJlbnRfbGltaXRgICAKIyBpdCBpcyBhbHNvIHBvc3NpYmxlIHRvIHNldCB5LWF4aXMgbGltaXQgZm9yIHpvb20taW4gcGFydCBhbG9uZSB1c2luZyBgeV96b29taW5fbGltaXRgICAKc2hhcC5wbG90LmZvcmNlX3Bsb3QocGxvdF9kYXRhLCB6b29tX2luX2xvY2F0aW9uID0gNTAwLCB5X3BhcmVudF9saW1pdCA9IGMoLTEsMSkpCiAgCmBgYAoKIyMjIFN1bW1hcmlzaW5nIElCTlIgY2xhaW0gY291bnRzCgpCeSBjb21wYXJpbmcgdGhlIG1vZGVsIHByZWRpY3Rpb25zIHRvIHRoZSBzaW11bGF0ZWQgY2xhaW1zIHJ1bi1vZmYgd2UgY2FuIGdldCBhIGZlZWwgZm9yIHRoZSBhY2N1cmFjeSBvZiB0aGUgbWFjaGluZSBsZWFybmluZyBhcHByb2FjaC4gQmVsb3cgSSBhZ2dyZWdhdGUgdGhlIElCTlIgY2xhaW0gY291bnQgcHJlZGljdGlvbnMgYnkgY2xhaW0gb2NjdXJyZW5jZSBtb250aCBhbmQgY29tcGFyZSB0aGVtIHRvIHRoZSBrbm93biBzaW11bGF0ZWQgY2xhaW0gcnVuLW9mZi4KCgpgYGAge3J9CgoKZHRfQWxsX0lCTlJfRl9zdW1tYXJ5IDwtIGR0X0FsbF9JQk5SX0Zbcm93TGlzdF9JQk5SX0YkdGVzdCwuKHByZWRzID0gc3VtKHByZWRzX2Z1bGxfSUJOUl9Ob3MpLCB0YXJnZXQgPSBzdW0odGFyZ2V0X2NvdW50KSksIGtleWJ5ID0gZGF0ZV9wb2xfc3RhcnRfWVlZWU1NXQoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojIFN1bSB0aGUgbGFzdCByb3cgb2YgZWFjaCBjb2x1bW4gaWYgbnVtZXJpYyAKc3Vtcm93IDwtIGNiaW5kKGRhdGEudGFibGUoZGF0ZV9wb2xfc3RhcnRfWVlZWU1NID0gIlRvdGFsIiksIGR0X0FsbF9JQk5SX0Zfc3VtbWFyeVssIGxhcHBseSguU0QsIHN1bSksIC5TRGNvbHMgPSBjKCJwcmVkcyIsICJ0YXJnZXQiKV0pCgpkdF9BbGxfSUJOUl9GX3N1bW1hcnkgPC0gcmJpbmQoZHRfQWxsX0lCTlJfRl9zdW1tYXJ5LCBzdW1yb3csIGZpbGwgPSBUUlVFKQoKZHRfQWxsX0lCTlJfRl9zdW1tYXJ5WywgJzo9JyhEaWZmID0gKHByZWRzIC0gdGFyZ2V0KSwKICAgICAgICAgICAgICAgICAgICAgICAgRGlmZl9wY250ID0gc2NhbGVzOjpwZXJjZW50KChwcmVkcyAtIHRhcmdldCkgLyB0YXJnZXQsIGFjY3VyYWN5ID0gMC4xKSldCgoKa2FibGUoZHRfQWxsX0lCTlJfRl9zdW1tYXJ5LCAiaHRtbCIsCiAgICAgIGRpZ2l0cyA9IGMoMiwwLDAsMCwxKSwKICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiLCAKICAgICAgICBzY2llbnRpZmljID0gRkFMU0UpCiAgICAgICkgJT4lIAogIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikKCgpgYGAKICAKWW91IGNhbiBub3cganVtcCBiYWNrIHVwIHRvIHRoZSBiZWdpbm5pbmcgb2YgdGhlIG1vZGVsaW5nIHNlY3Rpb24gYW5kIHNlbGVjdCB0aGUgSUJOUiBzZXZlcml0eSBtb2RlbGluZyB0YWIuIAogIAogIAojIyBJQk5SIFNldmVyaXR5IG1vZGVsCgpJQk5SIHJlc2VydmVzIGFyZSBidWlsdCBieSBtdWx0aXBseWluZyB0aGUgb3V0cHV0cyBvZiBhIGNsYWltIGZyZXF1ZW5jeSBhbmQgY2xhaW0gc2V2ZXJpdHkgbW9kZWwuIFRoZSBnZW5lcmFsIHByb2Nlc3Mgb2YgYnVpbGRpbmcgdGhlIG1vZGVsIGZvbGxvd3MgdGhhdCBvZiB0aGUgUkJOUyByZXNlcnZlcy4gCgojIyMgQ3JlYXRpbmcgeGdib29zdCBkYXRhc2V0CgpBZ2FpbiB3ZSBjb252ZXJ0IHRoZSBkYXRhIGludG8gYSBzcGVjaWZpYyBtYXRyaXggZm9ybSBjYWxsZWQgYSBETWF0cml4LiAKClRoZSBvdGhlciBwb2ludCB0byBub3RlIGlzIHRoZSB2YWx1ZXMgb2YgKmZsZ1RyYWluKiB1c2VkIHRvIGlkZW50aWZ5IHRoZSB0cmFpbmluZyBhbmQgdGVzdCByb3dzIGluIG91ciBkYXRhc2V0LiBSZWNhbGwgZnJvbSBOb3RlYm9vayAyLCBpbiB0aGUgSUJOUiBkYXRhc2V0IHRyYWluaW5nIHJvd3MgZm9yIHRoZSBmcmVxdWVuY3kgbW9kZWwgaGF2ZSBhIGZsZ3RyYWluIHZhbHVlIG9mIDEgd2hlcmVhcyB0aGUgU2V2ZXJpdHkgdHJhaW5pbmcgcm9lcyBoYXZlIGEgdmFsdWUgb2YgMi4gCgoKYGBgIHtyIH0KCklCTlJfcHJlZGljdG9ycyA8LSBjKCJqIiwKICAgICAgICAgICAgICAgICAgICAgImsiLAogICAgICAgICAgICAgICAgICAgICAiQ292ZXIiLAogICAgICAgICAgICAgICAgICAgICAiQnJhbmQiLAogICAgICAgICAgICAgICAgICAgICAiTW9kZWwiLAogICAgICAgICAgICAgICAgICAgICAiUHJpY2UiLAogICAgICAgICAgICAgICAgICAgICAiZGF0ZV91dyIpCgpkdF9BbGxfSUJOUiBbLCBkYXRlX3BvbF9zdGFydF9ZWVlZTU0gOj0gYXMuY2hhcmFjdGVyKHllYXIoZGF0ZV9wb2xfc3RhcnQpICsgbW9udGgoZGF0ZV9wb2xfc3RhcnQpLzEwMCApXQoKZHRfQWxsX0lCTlJfUyA8LSBkdF9BbGxfSUJOUlssIC4oZXhwb3N1cmUgPSBzdW0odGFyZ2V0PjApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfY29zdCA9IHN1bSh0YXJnZXQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5PSBjKElCTlJfcHJlZGljdG9ycywgImRhdGVfcG9sX3N0YXJ0X1lZWVlNTSIsICJmbGdUcmFpbiIpXQoKZHRfQWxsX0lCTlJfUyA8LSBkdF9BbGxfSUJOUl9TW2V4cG9zdXJlPjBdCgojIHNldHVwIHRyYWluIGFuZCB0ZXN0IHJvd3MKcm93TGlzdF9JQk5SX1MgPC0gbGlzdCh0cmFpbj1kdF9BbGxfSUJOUl9TWywgd2hpY2goZmxnVHJhaW49PTIpXSwKICAgICAgICAgICAgICAgICAgICAgdGVzdD1kdF9BbGxfSUJOUl9TWywgd2hpY2goZmxnVHJhaW49PTApXSwKICAgICAgICAgICAgICAgICAgICAgYWxsID0gZHRfQWxsX0lCTlJfU1ssIHdoaWNoKGZsZ1RyYWluIT0xKV0pCgojIHNldHVwIGRhdGEgZm9yIHhnYm9vc3QKCklCTlJfcmVjIDwtIHJlY2lwZSggfiAuLCBkYXRhID0gZHRfQWxsX0lCTlJfU1ssIElCTlJfcHJlZGljdG9ycywgd2l0aCA9IEZBTFNFXSkgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCBvbmVfaG90ID0gVFJVRSkgJT4lCiAgcHJlcCgpCgpkZi5JQk5SX1NfdHJhaW4gPC0gYmFrZShJQk5SX3JlYywgbmV3X2RhdGEgPSBkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRyYWluLF0gKQpkZi5JQk5SX1NfdGVzdCA8LSBiYWtlKElCTlJfcmVjLCBuZXdfZGF0YSA9IGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkdGVzdCxdICkKZGYuSUJOUl9TX2FsbCA8LSBiYWtlKElCTlJfcmVjLCBuZXdfZGF0YSA9IGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkYWxsLF0gKQoKCgp4Z2IuSUJOUl9TX0RNYXQudHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeChkZi5JQk5SX1NfdHJhaW4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSBkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRyYWluLCBleHBvc3VyZV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gZHRfQWxsX0lCTlJfU1tyb3dMaXN0X0lCTlJfUyR0cmFpbiwgdGFyZ2V0X2Nvc3RdKQoKeGdiLklCTlJfU19ETWF0LnRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeChkZi5JQk5SX1NfdGVzdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gZHRfQWxsX0lCTlJfU1tyb3dMaXN0X0lCTlJfUyR0ZXN0LCBleHBvc3VyZV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRlc3QsIHRhcmdldF9jb3N0XSkKCnhnYi5JQk5SX1NfRE1hdC5hbGwgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeChkZi5JQk5SX1NfYWxsKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkYWxsLCBleHBvc3VyZV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkYWxsLCB0YXJnZXRfY29zdF0pCgpgYGBgCgojIyMgRml0IGluaXRpYWwgbW9kZWwgdXNpbmcgY3Jvc3MgdmFsaWRhdGlvbgoKSGF2aW5nIHByZXBhcmVkIHRoZSBkYXRhIGZvciB4Z2Jvb3N0IEkgY2FuIG5vdyBmaXQgYW4gaW5pdGlhbCBtb2RlbC4gSeKAmXZlIHVzZWQgYSBzaW1wbGUgc2V0IG9mIGh5cGVyLXBhcmFtZXRlcnMgYW5kIHVzZWQgY3Jvc3MgdmFsaWRhdGlvbiB0byBzZWxlY3QgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGJvb3N0ZWQgdHJlZXMgKG5yb3VuZHMpIGZvciB0aGVzZSBoeXBlci1wYXJhbWV0ZXIgc2VsZWN0aW9ucyBieSBjYWxsaW5nIHRoZSB4Z2IuY3YgZnVuY3Rpb24gd2l0aCBlYXJseSBzdG9wcGluZy4KCkkgaGF2ZSB1c2VkIHRoZSAqKnJlZzpnYW1tYSoqIG9iamVjdGl2ZSBmdW5jdGlvbiBiYXNlZCB1cG9uIGluc3BlY3Rpb24gb2YgdGhlIHRhcmdldCB2YXJpYWJsZS4KCgpgYGB7ciBlY2hvPUZBTFNFfQoKc3VtbWFyeShkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRyYWluLCB0YXJnZXRfY29zdF0pCmhpc3QoZHRfQWxsX0lCTlJfU1tyb3dMaXN0X0lCTlJfUyR0cmFpbiwgdGFyZ2V0X2Nvc3RdKQoKYGBgCgoKYGBgIHtyfQoKcGFyYW0gPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOmdhbW1hIiwKICBtYXhfZGVwdGggPSAyTCwgICAgICAgICAgICMgdHJlZS1kZXB0aAogIHN1YnNhbXBsZSA9IDAuNywgICAgICAgICAgIyByYW5kb21seSBzYW1wbGUgcm93cyBiZWZvcmUgZml0dGluZyBlYWNoIHRyZWUKICBjb2xzYW1wbGVfYnl0cmVlID0gMC44LCAgICMgcmFuZG9tbHkgc2FtcGxlIGNvbHVtbnMgYmVmb3JlIGZpdHRpbmcgZWFjaCB0cmVlCiAgbWluLmNoaWxkLndlaWdodCA9IDEwLCAgICAjIG1pbmltdW0gd2VpZ2h0IHBlciBsZWFmCiAgZXRhID0gMC4xICAgICAgICAgICAgICAgIyBMZWFybmluZyByYXRlCiAgI21vbm90b25lX2NvbnN0cmFpbnRzID0gbW9ub3RvbmVfVmVjICMgTW9ub3RvbmljaXR5IGNvbnN0cmFpbnRzCikKCiMgVHJhaW4gbW9kZWwgd2l0aCBjcm9zcyB2YWxpZGF0aW9uCnNldC5zZWVkKDE5ODQpICMgZm9yIHJlcGVhdGFiaWxpdHkKCnhnYl9JQk5SX1NfQ1YgPC0geGdiLmN2KAogcGFyYW1zICAgICAgICAgICAgICAgICA9IHBhcmFtLAogZGF0YSAgICAgICAgICAgICAgICAgICA9IHhnYi5JQk5SX1NfRE1hdC50cmFpbiwKIG5yb3VuZHMgICAgICAgICAgICAgICAgPSAyMDAwLCAgICAgICAgIyBNYXhpbXVtIG51bWJlciBvZiB0cmVlcyB0byBidWlsZAogbmZvbGQgPSA1LAoKIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyAgPSA1MEwsICAgICAgICAjIFN0b3BzIGFsZ29yaXRobSBlYXJseSBpZiBwZXJmb3JtYW5jZSBoYXMgbm90IGltcHJvdmVkIGluIG4gcm91bmRzCiBwcmludF9ldmVyeV9uICAgICAgICAgID0gNTBMLCAgICAgICAgIyBIb3cgb2Z0ZW4gdG8gcHJpbnQgdG8gY29uc29sZQogI2Jhc2Vfc2NvcmUgICAgICAgICAgICAgPSAwLjAwMSwgICAgICAgIyBNb2RlbCBzdGFydGluZyBwb2ludAogICBwcmVkaWN0aW9uICAgICAgICAgICAgID0gVFJVRSAgICAgICAgIyBLZWVwcyB0aGUgcHJlZGljdGlvbnMKKQpgYGAKICAKSGF2aW5nIGZpdHRlZCBhbiBpbml0aWFsIG1vZGVsIHRoZSBvdXQtb2YtZm9sZCBwcmVkaWN0aW9ucyBhcmUgc3RvcmVkLgoKCmBgYCB7cn0KCiBkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRyYWluLCBwcmVkc19vb2ZfSUJOUl9TIDo9IHhnYl9JQk5SX1NfQ1YkcHJlZF0KIGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkdHJhaW4sIHByZWRzX29vZl9JQk5SX0Nvc3QgOj0gZXhwb3N1cmUgKiBwcmVkc19vb2ZfSUJOUl9TXQoKYGBgYAoKIyMjIEZpdCBmaW5hbCBtb2RlbCBvbiBhbGwgdHJhaW5pbmcgZGF0YQoKSGF2aW5nIGZpdCB0aGUgbW9kZWwgdXNpbmcgNSBmb2xkIGNyb3NzIHZhbGlkYXRpb24gd2Ugb2JzZXJ2ZSB0aGUgb3B0aW11bSBudW1iZXIgb2YgZml0dGluZyByb3VuZHMgdG8gYmUgYHIgeGdiX0lCTlJfU19DViRiZXN0X2l0ZXJhdGlvbmAuIAoKV2UgY2FuIHRoZW4gdXMgdGhpcyB0byB0cmFpbiBhIGZpbmFsIG1vZGVsIG9uIGFsbCB0aGUgZGF0YS4gCgpgYGAge3J9Cgp4Z2JfSUJOUl9TX0ZpdCA8LSB4Z2IudHJhaW4oCiAgIHBhcmFtcyAgICAgICAgICAgICAgICAgPSBwYXJhbSwKICAgZGF0YSAgICAgICAgICAgICAgICAgICA9IHhnYi5JQk5SX1NfRE1hdC50cmFpbiwKICAgbnJvdW5kcyAgICAgICAgICAgICAgICA9IHhnYl9JQk5SX1NfQ1YkYmVzdF9pdGVyYXRpb24sCiMgYmFzZV9zY29yZSAgICAgICAgICAgICA9IDEsCiAgIHdhdGNobGlzdCAgICAgICAgICAgICAgPSBsaXN0KHRyYWluPXhnYi5JQk5SX0ZfRE1hdC50cmFpbiwgdGVzdD14Z2IuSUJOUl9GX0RNYXQudGVzdCkgLAogICBwcmludF9ldmVyeV9uICAgICAgICAgID0gNTAKICkKYGBgIAoKSGF2aW5nIHRyYWluZWQgdGhlIG1vZGVsIHRoZSBwcmVkaWN0aW9ucyBhcmUgc3RvcmVkLiAKCmBgYCB7cn0KIGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkYWxsLCBwcmVkc19mdWxsX0lCTlJfQ29zdCA6PSBwcmVkaWN0KHhnYl9JQk5SX1NfRml0LHhnYi5JQk5SX1NfRE1hdC5hbGwpXQoKYGBgYAoKCiMjIyBJbnNwZWN0IG1vZGVsIGZpdAoKSGF2aW5nIGZpdHRlZCB0aGUgZnVsbCBtb2RlbCB3ZSBjYW4gdGhlbiBpbnNwZWN0IHRoZSBtb2RlbCBmaXQuIFRoZSB0cmFkaXRpb25hbCB3YXkgb2YgaW5zcGVjdGluZyBnbG9iYWwgbW9kZWwgZmVhdHVyZSBpbXBvcnRhbmNlIGlzIHRvIHVzZSB0aGUgZ2FpbnMgY2hhcnQuIAoKYGBgIHtyfQojZGVmYXVsdCBmZWF0dXJlIGltcG9ydGFuY2UgYnkgZ2FpbgpmZWF0SW1wX0lCTlJfUyA8LSB4Z2IuaW1wb3J0YW5jZSh4Z2JfSUJOUl9TX0ZpdCwgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKHhnYi5JQk5SX1NfRE1hdC50cmFpbikpCnhnYi5wbG90LmltcG9ydGFuY2UoZmVhdEltcF9JQk5SX1MsIG1haW49IkZlYXR1cmUgSW1wb3J0YW5jZSAtIElCTlIgU2V2ZXJpdHkiKQoKYGBgCgpBbiBpbmNyZWFzaW5nbHkgcG9wdWxhciBhbmQgbW9yZSByb2J1c3QgYXBwcm9hY2ggaXMgdG8gdXNlIFNIQVAgdmFsdWVzIFtodHRwczovL2dpdGh1Yi5jb20vc2x1bmRiZXJnL3NoYXBdKGh0dHA6Ly8pLiAgVGhlIFNIQVAgZXF1aXZhbGVudCBvZiB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIGNoYXJ0IGlzIHNob3duIGJlbG93LgoKYGBgIHtyIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKIyBSZXR1cm4gdGhlIFNIQVAgdmFsdWVzIGFuZCByYW5rZWQgZmVhdHVyZXMgYnkgbWVhbnxTSEFQfApzaGFwX3ZhbHVlcyA8LSBzaGFwLnZhbHVlcyh4Z2JfbW9kZWwgPSB4Z2JfSUJOUl9TX0ZpdCwgWF90cmFpbiA9IGFzLm1hdHJpeChkZi5JQk5SX1NfdHJhaW4pKQoKIyBQcmVwYXJlIHRoZSBsb25nLWZvcm1hdCBkYXRhOgpzaGFwX2xvbmcgPC0gc2hhcC5wcmVwKHNoYXBfY29udHJpYiA9IHNoYXBfdmFsdWVzJHNoYXBfc2NvcmUsIFhfdHJhaW4gPSAgYXMubWF0cml4KGRmLklCTlJfU190cmFpbikpCgojICoqU0hBUCBzdW1tYXJ5IHBsb3QqKgpzaGFwLnBsb3Quc3VtbWFyeShzaGFwX2xvbmcsIGRpbHV0ZSA9IG1heChucm93KGRmLklCTlJfU190cmFpbiksMTAwMDApLzEwMDAwKQoKYGBgCgpBIHNlY29uZCB1c2VmdWwgY2hhcnQgaXMgdGhlIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90LiBUaGlzIHNob3dzIGhvdyB0aGUgdmFsdWVzIG9mIGEgcHJlZGljdGl2ZSAoaW5wdXQpIGZlYXR1cmUgaW5mbHVlbmNlIHRoZSBwcmVkaWN0ZWQgKG91dHB1dCkgdmFsdWUsIHdoaWxlIGhvbGRpbmcgdGhlIHZhbHVlcyBvZiBhbGwgb3RoZXIgcHJlZGljdGl2ZSAoaW5wdXQpIGZlYXR1cmVzIGNvbnN0YW50LiBJdCBpcyBhbHNvIGtub3duIGFzIHRoZSBtYXJnaW5hbCBlZmZlY3QuCgpIZXJlIHdlIHNob3cgdGhlIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cyBmb3IgdGhlIHRvcCA0IFNIQVAgZmVhdHVyZXMuCgpgYGAge3Igd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CgpmaWdfbGlzdCA8LSBsYXBwbHkobmFtZXMoc2hhcF92YWx1ZXMkbWVhbl9zaGFwX3Njb3JlKVsxOjRdLCAKICAgICAgICAgICAgICAgICAgIHNoYXAucGxvdC5kZXBlbmRlbmNlLAogICAgICAgICAgICAgICAgICAgZGF0YV9sb25nID0gc2hhcF9sb25nLAogICAgICAgICAgICAgICAgICAgZGlsdXRlID0gbnJvdyhzaGFwX2xvbmcpLyAxMDAwMCkKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGdyb2JzID0gZmlnX2xpc3QsIG5jb2wgPSAyKQoKYGBgCgpUaGUgZmVhdHVyZSBpbXBvcnRhbmNlIGFuZCBwYXJ0aWFsIGRlcGVuZGVuY3kgcGxvdHMgcHJvdmlkZSBxdWljayBpbnNpZ2h0IGludG8gdGhlIG1vZGVsLiAgCgoqIFdlIHNlZSB0aGF0IHBob25lIHByaWNlIGlzIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlIGFuZCBoYXMgYSBsaW5lYXIgcmVsYXRpb25zaGlwIHdpdGggSUJOUiBzZXZlcml0eSBhcyB3ZSB3b3VsZCBleHBlY3QgZnJvbSB0aGUgZGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MuCiogVGhlIHNlY29uZCBhbmQgdGhpcmQgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMgcmVsYXRlIHRvIHBob25lIGNvdmVyLiBXZSBzZWUgdGhhdCBjb3ZlciBCT1QgaXMgYXNzb2NpYXRlZCB3aXRoIGhpZ2hlciBJQk5SIGNvc3RzIHdoZXJlYXMgY292ZXIgQiBpcyBhc3NvY2lhdGVkIHdpdGggbG93ZXIuICAKKiBUaGUgZm91cnRoIGZlYXR1cmUgaXMgcGhvbmUgYnJhbmQgYW5kIGZvbGxvd3MgdGhlIHBhdHRlcm4gd2Ugd291bGQgZXhwZWN0IGZyb20gdGhlIGRhdGEgc2ltdWxhdGlvbiBwcm9jZXNzLgoKU0hBUCB2YWx1ZXMgY2FuIGFsc28gYmUgdXNlZCB0byBzaG93IHRoZSBjb21wb25lbnRzIG9mIGEgc2luZ2xlIHByZWRpY3Rpb24uIEluIHRoZSBmb2xsb3dpbmcgcGxvdCB3ZSBzaG93IHRoZSB0b3AgNCBjb21wb25lbnRzIGZvciBlYWNoIHJvdyBvZiB0aGUgZGF0YSBhbmQgem9vbSBpbiBhdCByb3cgNTAwLiAKCmBgYCB7cn0KIyBjaG9vc2UgdG8gc2hvdyB0b3AgNCBmZWF0dXJlcyBieSBzZXR0aW5nIGB0b3BfbiA9IDRgLCBzZXQgNiBjbHVzdGVyaW5nIGdyb3Vwcy4gIApwbG90X2RhdGEgPC0gc2hhcC5wcmVwLnN0YWNrLmRhdGEoc2hhcF9jb250cmliID0gc2hhcF92YWx1ZXMkc2hhcF9zY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGVyY2VudCA9IDEwMDAwL21heChucm93KHNoYXBfbG9uZyksMTAwMDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wX24gPSA0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9ncm91cHMgPSA2KQogIAojIGNob29zZSB0byB6b29tIGluIGF0IGxvY2F0aW9uIDUwMCwgc2V0IHktYXhpcyBsaW1pdCB1c2luZyBgeV9wYXJlbnRfbGltaXRgICAKIyBpdCBpcyBhbHNvIHBvc3NpYmxlIHRvIHNldCB5LWF4aXMgbGltaXQgZm9yIHpvb20taW4gcGFydCBhbG9uZSB1c2luZyBgeV96b29taW5fbGltaXRgICAKc2hhcC5wbG90LmZvcmNlX3Bsb3QocGxvdF9kYXRhLCB6b29tX2luX2xvY2F0aW9uID0gNTAwLCB5X3BhcmVudF9saW1pdCA9IGMoLTEsMSkpCiAgCmBgYAoKIyMjIFN1bW1hcmlzaW5nIElCTlIgY2xhaW0gY29zdHMKCkJ5IGNvbXBhcmluZyB0aGUgbW9kZWwgcHJlZGljdGlvbnMgdG8gdGhlIHNpbXVsYXRlZCBjbGFpbXMgcnVuLW9mZiB3ZSBjYW4gZ2V0IGEgZmVlbCBmb3IgdGhlIGFjY3VyYWN5IG9mIHRoZSBtYWNoaW5lIGxlYXJuaW5nIGFwcHJvYWNoLiBCZWxvdyBJIGFnZ3JlZ2F0ZSB0aGUgSUJOUiBjbGFpbSBjb3N0IHByZWRpY3Rpb25zIGJ5IGNsYWltIG9jY3VycmVuY2UgbW9udGggYW5kIGNvbXBhcmUgdGhlbSB0byB0aGUga25vd24gc2ltdWxhdGVkIGNsYWltIHJ1bi1vZmYuCgpgYGAge3J9CgoKZHRfQWxsX0lCTlJfU19zdW1tYXJ5IDwtIGR0X0FsbF9JQk5SX1Nbcm93TGlzdF9JQk5SX1MkdGVzdCwuKHByZWRzID0gc3VtKHByZWRzX2Z1bGxfSUJOUl9Db3N0KSwgdGFyZ2V0ID0gc3VtKHRhcmdldF9jb3N0KSksIGtleWJ5ID0gZGF0ZV9wb2xfc3RhcnRfWVlZWU1NXQoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojIFN1bSB0aGUgbGFzdCByb3cgb2YgZWFjaCBjb2x1bW4gaWYgbnVtZXJpYyAKc3Vtcm93IDwtIGNiaW5kKGRhdGEudGFibGUoZGF0ZV9wb2xfc3RhcnRfWVlZWU1NID0gIlRvdGFsIiksIGR0X0FsbF9JQk5SX1Nfc3VtbWFyeVssIGxhcHBseSguU0QsIHN1bSksIC5TRGNvbHMgPSBjKCJwcmVkcyIsICJ0YXJnZXQiKV0pCgpkdF9BbGxfSUJOUl9TX3N1bW1hcnkgPC0gcmJpbmQoZHRfQWxsX0lCTlJfU19zdW1tYXJ5LCBzdW1yb3csIGZpbGwgPSBUUlVFKQoKZHRfQWxsX0lCTlJfU19zdW1tYXJ5WywgJzo9JyhEaWZmID0gKHByZWRzIC0gdGFyZ2V0KSwKICAgICAgICAgICAgICAgICAgICAgRGlmZl9wY250ID0gc2NhbGVzOjpwZXJjZW50KChwcmVkcyAtIHRhcmdldCkgLyB0YXJnZXQsIGFjY3VyYWN5ID0gMC4xKSldCgoKa2FibGUoZHRfQWxsX0lCTlJfU19zdW1tYXJ5LCAiaHRtbCIsCiAgICAgIGRpZ2l0cyA9IGMoMiwwLDAsMCwxKSwKICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiLCAKICAgICAgICBzY2llbnRpZmljID0gRkFMU0UpCiAgICAgICkgJT4lIAogIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikKCgpgYGAKCiMgU3VtbWFyeQoKSW4gdGhpcyB0aGlyZCBub3RlYm9vayB3ZSBoYXZlIHN0ZXBwZWQgdGhyb3VnaCB0aGUgcHJvY2VzcyB0byBhcHBseSBtYWNoaW5lIGxlYXJuaW5nIHRlY2huaXF1ZXMgaW4gb3JkZXIgdG8gY3JlYXRlIHJlc2VydmUgZXN0aW1hdGVzIGZvbGxvd2luZyB0aGUgdGVjaG5pcXVlcyBzZXQgb3V0IGluIHNlY3Rpb25zIDMgYW5kIDQgb2YgQmF1ZHJ5J3MgcGFwZXIuCgpTcGVjaWZpY2FsbHkgd2UgaGF2ZSBzaG93biBob3cgdG8gYXBwbHkgdGhlIHhnYm9vc3QgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gYW5kIGlsbHVzdHJhdGVkIGhvdyBpdCBjYW4sIHdpdGggbGl0dGxlIGh1bWFuIGlucHV0LCBjcmVhdGUgcmVhc29uYWJsZSByZXNlcnZlIGVzdGltYXRlcyBhcyBzaG93biBiZWxvdy4gCgpgYGB7ciBlY2hvPUZBTFNFfQoKc3VtbWFyeV90YWJsZSA8LSBkYXRhLnRhYmxlKAogIFJlc2VydmUgPSBjKCJSQk5TIiwgIklCTlIiLCAiVE9UQUwiKSwKICBQcmVkaWN0aW9uID0gYyhkdF9BbGxfUkJOU1tyb3dMaXN0X1JCTlMkdGVzdCwgc3VtKHByZWRzX2Z1bGwpXSwKICAgICAgICAgICAgICAgICBkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRlc3QsIHN1bShwcmVkc19mdWxsX0lCTlJfQ29zdCldLAogICAgICAgICAgICAgICAgIGR0X0FsbF9SQk5TW3Jvd0xpc3RfUkJOUyR0ZXN0LCBzdW0ocHJlZHNfZnVsbCldICsgZHRfQWxsX0lCTlJfU1tyb3dMaXN0X0lCTlJfUyR0ZXN0LCBzdW0ocHJlZHNfZnVsbF9JQk5SX0Nvc3QpXSksCiAgR3JvdW5kX1RydXRoID0gYyhkdF9BbGxfUkJOU1tyb3dMaXN0X1JCTlMkdGVzdCwgc3VtKHRhcmdldCldLAogICAgICAgICAgICAgICAgICAgZHRfQWxsX0lCTlJfU1tyb3dMaXN0X0lCTlJfUyR0ZXN0LCBzdW0odGFyZ2V0X2Nvc3QpXSwKICAgICAgICAgICAgICAgICAgIGR0X0FsbF9SQk5TW3Jvd0xpc3RfUkJOUyR0ZXN0LCBzdW0odGFyZ2V0KV0gKyBkdF9BbGxfSUJOUl9TW3Jvd0xpc3RfSUJOUl9TJHRlc3QsIHN1bSh0YXJnZXRfY29zdCldKQopCgpzdW1tYXJ5X3RhYmxlWywgJzo9JygKICBEaWZmID0gKFByZWRpY3Rpb24gLSBHcm91bmRfVHJ1dGgpLAogIERpZmZfcGNudCA9ICBzY2FsZXM6OnBlcmNlbnQoKFByZWRpY3Rpb24gLSBHcm91bmRfVHJ1dGgpIC8gR3JvdW5kX1RydXRoLCBhY2N1cmFjeSA9IDAuMSkKKV0KCgprYWJsZSgKICBzdW1tYXJ5X3RhYmxlLAogICJodG1sIiwKICBkaWdpdHMgPSBjKDAsIDAsIDAsIDAsIDEpLAogIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiwKICAgICAgICAgICAgICAgICAgICAgc2NpZW50aWZpYyA9IEZBTFNFKQopICU+JQogIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikKCgpgYGAKCkluIGFkZGl0aW9uIHdlIGhhdmUgc2VlbiBob3cgd2UgY2FuIGNyZWF0ZSBleHBsYW5hdGlvbnMgZm9yIHRoZSByZXNlcnZlIHByZWRpY3Rpb25zIHVzaW5nIGZlYXR1cmUgaW1wb3J0YW5jZSBhbmQgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzLiBXZSBoYXZlIGFsc28gdXNlZCBTSEFQIHZhbHVlcyB3aGljaCBjYW4gYmUgdXNlZCB0byBleHBsYWluIGJvdGggZ2xvYmFsIGZlYXR1cmVzIGFuZCBpbmRpdmlkdWFsIHByZWRpY3Rpb25zLiAKIApJbiBzbyBkb2luZywgaXQgaXMgaG9wZWQgdGhhdCB0aGlzIHNlcmllcyBvZiBub3RlYm9va3MgZW5jb3VyYWdlIGZ1cnRoZXIgZXhwbG9yYXRpb24gb2YgQmF1ZHJ5J3MgcGFwZXIgYW5kIHRoZSB3aWRlciBmaWVsZCBvZiBtYWNoaW5lIGxlYXJuaW5nIGluIHJlc2VydmluZy4gVGhlIGNvZGUgc3VwcG9ydGluZyB0aGlzIG5vdGVib29rIGFsb25nIHdpdGggdGhlIHN1cHBvcnRpbmcgZmlsZXMgYW5kIGZvbGRlcnMgYXZhaWxhYmxlIGluIGEgemlwIGZpbGUgW2hlcmVdKC9tbHItYmxvZy9sX2JhdWRyeS9CYXVkcnlfMy56aXApLgoKRG93bmxvYWQgYW5kIGV4dHJhY3QgdGhlIHppcCBmaWxlIHRvIGEgbG9jYWwgZGlyZWN0b3J5IGFuZCB0aGVuIG9wZW4gdGhlIFIgcHJvamVjdCBmaWxlIEJhdWRyeV8zLnJwcm9qIGluIHlvdXIgbG9jYWwgUiBzb2Z0d2FyZSBpbnN0YWxsYXRpb24uIEluIHRoZSByb290IG9mIHRoZSBwcm9qZWN0IGZvbGRlciB5b3Ugd2lsbCBzZWUgdHdvIGZpbGVzOwoKKiBOb3RlYm9va18zX0FwcGx5TWFjaGluZUxlYXJuaW5nUmVzZXJ2aW5nX3YxLlJtZCAtIHdoaWNoIGlzIHRoZSBzb3VyY2UgY29kZSB1c2VkIHRvIHJlY3JlYXRlIHRoaXMgbm90ZWJvb2sgIAoqIE5vdGVib29rXzNfQXBwbHlNYWNoaW5lTGVhcm5pbmdSZXNlcnZpbmdfdjEuUiAtIHRoZSBlcXVpdmFsZW50IGNvZGUgcHJvdmlkZWQgYXMgYW4gUiBzY3JpcHQKClBsZWFzZSBub3RlIHRoYXQsIGRlcGVuZGluZyB1cG9uIHlvdXIgUiBpbnN0YWxsYXRpb24gLCB5b3UgbWF5IGhhdmUgdG8gaW5zdGFsbCBSIGxpYnJhcmllcyBiZWZvcmUgeW91IGNhbiBydW4gdGhlIGNvZGUgcHJvdmlkZWQuIFIgd2lsbCB3YXJuIHlvdSBpZiB5b3UgaGF2ZSBtaXNzaW5nIGRlcGVuZGVuY2llcyBhbmQgeW91IGNhbiB0aGVuIGluc3RhbGwgdGhlbSBmcm9tIENSQU4uCgoKCg==