The Small Business Administration (SBA) was founded in 1953 to assist small businesses in securing loans. The argument is that, as small businesses are a primary source of employment in the United States, then helping small businesses helps with job creation, reducing unemployment. One of the ways the SBA helps small businesses is by guaranteeing bank loans. This guarantee reduces the risk to individual banks and encourages them to lend in situations that they otherwise might not. If the loan defaults, the SBA covers the amount guaranteed, and the bank suffers a loss for the remaining balance.
There have been a few small business success stories like FedEx and Apple. However, the rate of default is somewhat higher than most commercial banks would find attractive. Some economists believe the banking market works better without the assistance of the SBA. On the other hand, supporters claim that the social benefits and job creation outweigh the financial costs to the taxpayer in defaulted loans.
My capstone project in grad school a few years back involved a study of regional banks and account policies across the US financial network. Regulatory submissions to SBA, FDIC, the USDA, and even state banking authorities are a great opportunity to model for decisions for economists and finance. In this case, modeling the amount of default exposure could be useful in providing information to government policymakers or insurers.
The source data set was from the U.S. SBA loan database and includes information on whether the loan was paid off in full or if the SBA had to charge off any amount in default, and how much that amount was. More information on this data set is availble here.
The audience is invited to participate as well. This type of problem is a big challenge. 64k of the 83k loans in the SLICED training dataset did not default at all, so show zero default amount. Separating the signal from the noise here is tough.
Clearly there is a large imbalance here between good and bad loans. This is probably a good thing for the banks, but poses an interesting issue for us because we want to ensure we are sensitive to the bad loans and are not overwhelmed by the number of good ones. One approach that we might take is to downsample
the number of good loans so that the total number of them is more in line with the number of bad loans.
There are likely confounders and other feature transformations required. I also wondered whether approaching this as a classification problem first, and building a regression second might be better. Eben Esterhuizen was kind enough to share his top-scoring Python model code at Kaggle. Interestingly, this was exactly his approach.
My plan here is to re-write his approach in R, make a number of improvements, and explain how it all works. Let’s load up R packages:
Get the Data
A quick reminder before downloading the dataset: Go to the Kaggle web site and accept the competition terms!!!
If you pip install kaggle
, there are (Windows) shell commands available to interact with Kaggle here:
# from the Kaggle api https://github.com/Kaggle/kaggle-api
# the leaderboard
shell(glue::glue("kaggle competitions leaderboard { competition_name } -s"))
# the files to download
shell(glue::glue("kaggle competitions files -c { competition_name }"))
# the command to download files
shell(glue::glue("kaggle competitions download -c { competition_name } -p { data_dir }"))
# unzip the files received
shell(glue::glue("unzip { zipfile } -d { data_dir }"))
Read in the contents of the files to dataframes here. I’ve written an import function to cast the data types and create new features in the same way, both from the provided train data file as well as from the un-labeled file labeled test, that I will call competition_submission_df
. In this case, the competition_submission_df
appears to be a random sample selection across all years and states. We will likely discover that the competition_submission_df
includes years or factor levels, like states or zip codes, that were not present in the training set.
cast_transform <- function(tbl) {
tbl %>%
mutate(
FranchiseCode = if_else(FranchiseCode %in% c(0, 1),
"noFranchise",
"Franchise"
),
NAICS2 = str_sub(NAICS, 1L, 2L), # Industry Class Hierarchy
NAICS3 = str_sub(NAICS, 1L, 3L), # Industry Class Hierarchy
NAICS4 = str_sub(NAICS, 1L, 4L), # Industry Class Hierarchy
NewExist = if_else(is.na(NewExist), "Unknown", NewExist)
) %>%
mutate(across(
c(
"Name",
"City",
"State",
"Zip",
"Bank",
"Sector",
"NAICS",
"BankState",
"UrbanRural",
"NewExist",
"FranchiseCode",
"NAICS2", "NAICS3", "NAICS4"
),
as_factor
)) %>%
mutate(
UrbanRural = fct_recode(
UrbanRural,
"Urban" = "1",
"Rural" = "2",
"Unknown" = "0"
),
NewExist = fct_recode(NewExist,
"Existing" = "1.0",
"New" = "2.0",
"Unknown" = "0.0"
),
loan_per_employee = GrAppv / (NoEmp + 1),
SBA_Appv_vs_Gross = SBA_Appv / GrAppv,
Gross_minus_SBA_Appv = GrAppv - SBA_Appv,
Gross_minus_SBA_Appv_vs_Employee = Gross_minus_SBA_Appv / (NoEmp + 1),
same_state = State == BankState
)
}
train_df <-
read_csv(
file = glue::glue(
{
data_dir
},
"/train.csv"
),
col_types = "ccccccccciiciiccdddd",
show_col_types = FALSE
) %>%
mutate(
default_flag = as_factor(if_else(
default_amount > 0,
"default",
"nondefault"
)),
default_proportion = default_amount / GrAppv
) %>%
cast_transform() %>%
dplyr::select(
default_amount,
default_flag,
default_proportion,
everything()
)
competition_submission_df <-
read_csv(
file = glue::glue(
{
data_dir
},
"/test.csv"
),
col_types = "ccccccccciiciiccddd",
show_col_types = FALSE
) %>%
cast_transform()
Exploratory Data Analysis (EDA)
Some questions to answer upfront:
What features have missing data, and imputations may be required?
What does the outcome variable look like, in terms of imbalance?
In competition I use skimr::skim()
for quick checks. We will craft much nicer plots below.
skimr::skim(train_df)
The Outcome Variable
Lets characterize the default_amount
as a histogram for all data in the set, and as a treemap to better understand the distributions by a few of the category factor levels.
train_df %>%
group_by(State, Sector = fct_lump(Sector, 10)) %>%
summarise(
n = n(),
mean_default = mean(default_amount > 0, na.rm = TRUE),
.groups = "drop"
) %>%
ggplot(aes(
area = n,
fill = mean_default,
label = Sector,
subgroup = State
)) +
geom_treemap() +
geom_treemap_subgroup_border() +
geom_treemap_text(color = "black", place = "top", reflow = TRUE) +
geom_treemap_subgroup_text(
color = "gray40",
place = "bottomright",
fontface = "italic",
min.size = 0,
alpha = 0.3
) +
scale_fill_fermenter(
palette = "PuRd",
direction = 1,
guide = "colorsteps",
labels = scales::percent_format(accuracy = 1),
breaks = seq(0, 1, 0.1)
) +
labs(
fill = "Loans\nThat Default",
title = "US Small Business Admin Loans 2003-2010",
subtitle = "Box area is # of loans for sectors within ea State.\nThe vast majority of individual loans do not default.",
caption = "Data Source: Kaggle"
)
train_df %>%
dplyr::select(GrAppv, default_flag) %>%
bind_rows(dplyr::select(competition_submission_df, GrAppv) %>% mutate(default_flag = "holdout set")) %>%
ggplot(aes(GrAppv, fill = default_flag)) +
geom_histogram(bins = 50) +
scale_x_log10(labels = scales::dollar_format()) +
labs(
title = "Loans tend to fall between $10k to 1M",
fill = NULL, x = "Gross Approved Loan Value",
subtitle = "SBA must have value threshold limits"
)
Categorical Variables
summarize_defaults <- function(tbl) {
tbl %>%
summarize(
n_loans = n(),
pct_default = mean(default_amount > 0),
total_gr_appv = sum(GrAppv),
total_default_amount = sum(default_amount),
pct_default_amount = total_default_amount / total_gr_appv,
.groups = "drop"
) %>%
arrange(desc(n_loans))
}
withfreq <- function(x) {
tibble(x) %>%
add_count(x) %>%
mutate(combined = glue::glue("{ str_sub(x, end = 18) } ({ n })")) %>%
pull(combined)
}
plot_category <- function(tbl, category, n_categories = 7) {
tbl %>%
group_by({{ category }} := withfreq(fct_lump({{ category }}, n_categories))) %>%
summarize_defaults() %>%
mutate({{ category }} := fct_reorder({{ category }}, pct_default)) %>%
ggplot(aes(pct_default, {{ category }})) +
geom_point(aes(color = pct_default_amount)) +
geom_text(aes(
x = pct_default - 0.02,
label = glue::glue("$ {round(total_default_amount/10^6,0)} M")
),
hjust = 1
) +
scale_color_viridis_b(
name = "% of Loan Amount\nin Default",
labels = scales::percent_format(accuracy = 1),
breaks = c(0, 0.05, 0.1, 0.15),
option = "H"
) +
guides(color = guide_bins()) +
scale_x_continuous(labels = scales::percent_format(accuracy = 1)) +
expand_limits(x = 0, size = 0) +
labs(
x = "% of Loans Originated that Defaulted", y = NULL,
subtitle = "(# of loans) & Total Defaulted Amount on Labels"
)
}
train_df %>%
plot_category(State) +
labs(title = "Florida, Illinois, New York")
train_df %>%
plot_category(City) +
labs(title = "Miami leads")
train_df %>%
plot_category(Sector) +
labs(title = "Construction leads")
train_df %>%
plot_category(Bank) +
labs(title = "BBCN is the leader")
train_df %>%
mutate(same_state = if_else(BankState == State,
"Same State",
"Different"
)) %>%
plot_category(same_state) +
labs(title = "Banks in States different than the Borrower")
train_df %>%
plot_category(NewExist) +
labs(title = "New Borrowers")
train_df %>%
plot_category(FranchiseCode) +
labs(title = "When they do default, Franchises aren't much different")
train_df %>%
plot_category(NAICS2) +
labs(title = "NAICS codes starting with 23")
train_df %>%
plot_category(NAICS3) +
labs(title = "NAICS codes starting with 238")
train_df %>%
plot_category(NAICS4) +
labs(title = "NAICS codes starting with 8121")
train_df %>%
plot_category(NAICS) +
labs(title = "NAICS code 722211")
train_df %>%
plot_category(UrbanRural) +
labs(title = "Urban borrowers")
train_df %>%
plot_category(Name) +
labs(title = "The bad business names")
Time Series
train_df %>%
group_by(ApprovalFY, State = fct_lump(State, 5)) %>%
summarize_defaults() %>%
ggplot(aes(ApprovalFY, pct_default, color = State)) +
geom_line(size = 2) +
scale_y_continuous(labels = scales::percent_format()) +
scale_size_continuous(labels = scales::dollar_format()) +
expand_limits(y = 0, size = 0) +
labs(
x = "year approved", y = "Default Percentage",
title = "SBA Loans in the 2008 financial crisis",
size = "Loan Approvals",
caption = "Based on David Robinson @drob example"
)
Numeric Features
train_numeric <- train_df %>%
keep(is.numeric) %>%
colnames()
train_df %>%
bind_rows(competition_submission_df) %>%
select_at(all_of(train_numeric)) %>%
dplyr::select(-default_amount) %>%
pivot_longer(
cols = everything(),
names_to = "key",
values_to = "value"
) %>%
filter(!is.na(value)) %>%
ggplot(mapping = aes(value, fill = key)) +
geom_histogram(
position = "identity",
bins = 30,
show.legend = FALSE
) +
scale_y_continuous(
labels = scales::comma_format(),
n.breaks = 3
) +
scale_x_continuous(n.breaks = 3) +
facet_wrap(~key, scales = "free", ncol = 3) +
labs(
title = "Numeric Feature Histogram Distributions",
subtitle = "Training and Holdout datasets combined",
x = NULL,
y = "Count"
) +
theme(aspect.ratio = 1.3) +
theme_jim(base_size = 8)
train_df %>%
filter(default_amount > 1) %>%
select_at(all_of(train_numeric)) %>%
pivot_longer(
cols = -c(default_amount, ApprovalFY),
names_to = "key",
values_to = "value"
) %>%
ggplot(aes(ApprovalFY, value, z = default_amount)) +
stat_summary_hex(alpha = 0.9, bins = 10) +
scale_fill_viridis_c(option = "H", labels = scales::dollar_format()) +
scale_x_continuous(n.breaks = 3) +
scale_y_continuous(n.breaks = 3) +
facet_wrap(~key, scales = "free", ncol = 3) +
labs(
subtitle = "Mean Default Value Where > $1",
title = "Numeric Features by Loan Origination Year",
y = NULL, fill = "Default $"
) +
theme(aspect.ratio = 1.3) +
theme_jim(base_size = 8)
tmwr_cols <- viridis::viridis_pal(option = "H")
train_df %>%
select_at(all_of(train_numeric)) %>%
cor() %>%
heatmap(main = "Numeric Correlations")
The highly correlated numeric variables are going to have to be dealt with, either through some sort of regularization or pre-processing.
Maps
by_state <- train_df %>%
group_by(State) %>%
summarize_defaults()
map_data("state") %>%
as_tibble() %>%
mutate(State = state.abb[match(region, str_to_lower(state.name))]) %>%
inner_join(by_state, by = "State") %>%
ggplot(aes(long, lat, group = group, fill = pct_default)) +
geom_polygon() +
theme_map() +
coord_map() +
scale_fill_viridis_c(option = "H", labels = scales::percent_format()) +
labs(
title = "SBA Loan Defaults 2003-2010: Florida, Georgia, Illinois, Nevada, & Michigan lead",
fill = "Percent of Loan\nOriginations Ending\nin Default",
caption = "Data Source: Kaggle. Based on David Robinson's example."
)
So, yes, we will first model to predict for the default_flag
class. If it performs well, we will then go on to model the default_amount
separately for the loans predicted to default.
Machine Learning: Classification
Here’s an often overlooked piece of advice for all ML practitioners, & especially newcomers:
👉 Build a non-ML baseline first.
You’ll be surprised how often you cannot easily improve a basic solution by throwing ML at it, and you’ll learn something about the problem too.
Your best alternative to any complex model isn’t “no model.” Your alternative is a simple model. I see this over and over. Before you build a Rube Goldberg model of the universe, build a brain dead simple model. THAT parsimonious piece of junk is your bogie. Out perform THAT in a way that justifies your complexity and time.
Once you’re comparing a complicated model to a parsimonious model you can intelligently discuss the “marginal benefit (or lift) from complexity.”
round(100 * prop.table(table(train_df$default_flag)), 1)
nondefault default
77.1 22.9
In the loan default dataset, predicting a nondefault class for every case results in 77.1% accuracy, doing nothing. Let’s call this the NULL model.
Spending the Data
We will split train_df
randomly into a training portion (90%) and a labeled testing portion (10%). The training portion is then divided into 5-fold cross validation sets and stratified on the default_flag
to ensure that each CV fold has some proportion of the imbalanced outcomes.
set.seed(2021)
split <- initial_split(train_df, prop = 0.9)
training <- training(split)
testing <- testing(split)
class_folds <- vfold_cv(training, v = 5, strata = default_flag)
The Recipes
We are going to start by taking a survey of a handful of models that train fast, have little tuning required, and are generally interpretable. The choice of pre-processor, or tidymodels
recipe()
steps, transforms the dataframe into formats that machine learning algorithms can work with.
Our strategy here is to pre-process at three levels. The first, classification_rec
is a raw dataset with all categorical predictors intact. The second, dummies_classification_rec
tunes a threshold for lumping categorical levels together and then creates dummy variables so everything is numeric. With so many category levels, this training set could be thousands of columns wide, and sparse. There is a cardinality tradeoff here, where each additional column may not contribute much to the model. The third pre-processor, pls_classification_rec
, works as a sort of supervised learning version of principal components analysis, called Partial Least Squares, that simultaneously maximize the variation in the predictors while also maximizing the relationship between those components and the outcome.
classification_rec <-
recipe(
default_flag ~ LoanNr_ChkDgt + Name + Sector + City + State + Zip +
Bank + BankState + NAICS + ApprovalFY + NoEmp +
NewExist + CreateJob + RetainedJob + FranchiseCode + UrbanRural +
DisbursementGross + GrAppv + SBA_Appv + NAICS2 + NAICS3 + NAICS4 +
loan_per_employee + SBA_Appv_vs_Gross + Gross_minus_SBA_Appv +
Gross_minus_SBA_Appv_vs_Employee + same_state,
data = training
) %>%
update_role(LoanNr_ChkDgt, new_role = "ID")
dummies_classification_rec <-
classification_rec %>%
step_novel(
Name,
Sector,
City,
State,
Zip,
Bank,
BankState,
NAICS,
NAICS2,
NAICS3,
NAICS4
) %>%
step_other(
Name,
Sector,
City,
State,
Zip,
Bank,
BankState,
NAICS,
NAICS2,
NAICS3,
NAICS4,
threshold = tune()
) %>%
step_dummy(all_nominal_predictors()) %>%
step_interact(~ starts_with("Bank"):ApprovalFY) %>%
# Add important interactions
step_orderNorm(all_numeric_predictors()) %>%
step_normalize(all_numeric_predictors())
pls_classification_rec <- dummies_classification_rec %>%
step_pls(all_numeric_predictors(),
num_comp = tune(),
outcome = "default_flag"
) %>%
step_normalize(all_numeric_predictors())
GLM Model Tune
We are going to quickly race through a grid of 6 parameter combinations with workflowsets
. I will set reasonable parameters ranges in advance for the categorical variable lumping and the number of PLS components.
ctrl <- control_race(
parallel_over = "everything",
save_pred = TRUE
)
all_cores <- parallelly::availableCores(omit = 1)
all_cores
future::plan("multisession", workers = all_cores) # on Windows
workflows <- workflow_set(
preproc = list(
factor = dummies_classification_rec,
pls = pls_classification_rec
),
models = list(
glmnet = logistic_reg_glm_spec,
fda = fda_spec,
rda = rda_spec
)
)
factor_glmnet_params <- workflows %>%
extract_workflow("factor_glmnet") %>%
parameters() %>%
update(
penalty = penalty(),
mixture = mixture(),
threshold = threshold(range = c(0.1, 0.005))
)
factor_fda_params <- workflows %>%
extract_workflow("factor_fda") %>%
parameters() %>%
update(
prod_degree = prod_degree(),
threshold = threshold(range = c(0.1, 0.005))
)
factor_rda_params <- workflows %>%
extract_workflow("factor_rda") %>%
parameters() %>%
update(
frac_common_cov = frac_common_cov(),
frac_identity = frac_identity(),
threshold = threshold(range = c(0.1, 0.005))
)
pls_glmnet_params <- workflows %>%
extract_workflow("pls_glmnet") %>%
parameters() %>%
update(
penalty = penalty(),
mixture = mixture(),
threshold = threshold(range = c(0.1, 0.005)),
num_comp = num_comp(range = c(6L, 11L))
)
pls_fda_params <- workflows %>%
extract_workflow("pls_fda") %>%
parameters() %>%
update(
prod_degree = prod_degree(),
threshold = threshold(range = c(0.1, 0.005)),
num_comp = num_comp(range = c(6L, 11L))
)
pls_rda_params <- workflows %>%
extract_workflow("pls_rda") %>%
parameters() %>%
update(
frac_common_cov = frac_common_cov(),
frac_identity = frac_identity(),
threshold = threshold(range = c(0.1, 0.005)),
num_comp = num_comp(range = c(6L, 11L))
)
workflows <- workflows %>%
option_add(param_info = factor_glmnet_params, id = "factor_glmnet") %>%
option_add(param_info = factor_fda_params, id = "factor_fda") %>%
option_add(param_info = factor_rda_params, id = "factor_rda") %>%
option_add(param_info = pls_glmnet_params, id = "pls_glmnet") %>%
option_add(param_info = pls_fda_params, id = "pls_fda") %>%
option_add(param_info = pls_rda_params, id = "pls_rda")
We then tune each of the models, running across all validation folds for every grid combination. We are using a time-saving technique here with tune_race_anova()
, where poor performing candidates are dropped. For 5-fold cross validation, the reduction in time is as much as 40%.
default_res <- workflows %>%
workflow_map(
fn = "tune_race_anova",
verbose = TRUE,
seed = 2021,
resamples = class_folds,
grid = 21,
control = ctrl,
metrics = metric_set(mn_log_loss)
)
i 1 of 6 tuning: factor_glmnet
v 1 of 6 tuning: factor_glmnet (1h 1m 15.9s)
i 2 of 6 tuning: factor_fda
v 2 of 6 tuning: factor_fda (57m 56.7s)
i 3 of 6 tuning: factor_rda
v 3 of 6 tuning: factor_rda (13m 13.9s)
i 4 of 6 tuning: pls_glmnet
v 4 of 6 tuning: pls_glmnet (9m 10.9s)
i 5 of 6 tuning: pls_fda
v 5 of 6 tuning: pls_fda (10m 18.3s)
i 6 of 6 tuning: pls_rda
v 6 of 6 tuning: pls_rda (11m 49.3s)
rankings <-
rank_results(default_res, select_best = TRUE) %>%
mutate(method = map_chr(wflow_id, ~ str_split(.x, "_", simplify = TRUE)[1]))
filter(rankings, rank <= 5) %>% dplyr::select(rank, mean, model, method)
rankings %>%
ggplot(aes(x = rank, y = mean, pch = method, col = model)) +
geom_point(cex = 3) +
theme(legend.position = "right") +
labs(y = "Mean Log Loss")
The best of the best were the regularlized glm
with the simple dummy preprocessor. We can visualize the benefit of tune_race_anova()
, where only the best combinations were run through the entire tuning routine.
plot_race(default_res %>% extract_workflow_set_result("factor_glmnet"))
Let’s extract the hyperparameter settings for that top model.
(best_params <- default_res %>%
extract_workflow_set_result("factor_glmnet") %>%
select_best())
best_wf <- default_res %>%
extract_workflow("factor_glmnet") %>%
finalize_workflow(best_params)
And take a close look at the classification performance of that best glmnet
model on the 10% testing data that was held out.
lastFit <- last_fit(best_wf, split, metrics = metric_set(mn_log_loss))
collect_metrics(lastFit)
Note here the the mean log loss classification error is comparable to that of the cross validation figure above, so we have more confidence that the model works on unseen data. Let’s move forward then.
Fit
Recall that our original dataset was imbalanced. There are far more non-defaults than defaults. Because of this, dialing in the cutoff, balancing between specificity and sensitivity, warrants more attention. Let’s fit the best model on all of the training data (omitting the cross validation folds).
glm_classification_fit <- fit(best_wf, data = training)
Let’s simply predict the probabilities on the labeled testing data.
lending_test_pred <- glm_classification_fit %>%
predict(new_data = testing, type = "prob") %>%
bind_cols(testing)
With our class probabilities in hand, we can use make_two_class_pred()
to convert these probabilities into hard predictions using a threshold. A threshold of 0.5 just says that if the predicted probability is above 0.5, then classify this prediction as a “nondefault” loan, otherwise, default.
lending_test_pred %>%
mutate(.pred = make_two_class_pred(.pred_nondefault, levels(default_flag), threshold = .5)) %>%
dplyr::select(default_flag, contains(".pred")) %>%
conf_mat(truth = default_flag, estimate = .pred) %>%
autoplot() +
labs(title = "GLM Confusion Matrix at 0.5 Cutoff")
With a 0.5 threshold, almost all of the loans were predicted as “nondefault”. Perhaps this has something to do with the large class imbalance. On the other hand, the bank might want to be more stringent with what is classified as a “good” loan, and might require a probability of 0.75 as the threshold.
lending_test_pred %>%
mutate(.pred = make_two_class_pred(.pred_nondefault, levels(default_flag), threshold = .75)) %>%
dplyr::select(default_flag, contains(".pred")) %>%
conf_mat(truth = default_flag, estimate = .pred) %>%
autoplot() +
labs(title = "GLM Confusion Matrix at 0.75 Cutoff")
In this case, many the bad loans were correctly classified as bad, but more of the good loans were also misclassified as bad now. There is a tradeoff here, which can be somewhat captured by the metrics sensitivity
and specificity
.
In this example, as we increased specificity, we lowered sensitivity. It would be nice to have some combination of these metrics to represent this tradeoff. Luckily, j_index
is exactly that.
Now, this is not the only way to optimize things. If you care about low false positives, you might be more interested in keeping sensitivity high, and this wouldn’t be the best way to tackle this problem. There are other ways to weigh economic consequences. For now, let’s see how we can use probably to optimize the j_index
.
With ggplot2
, we can easily visualize this varying performance to find our optimal threshold for maximizing j_index
.
threshold_data <- lending_test_pred %>%
threshold_perf(default_flag,
.pred_nondefault,
thresholds = seq(0.5, 1, by = 0.0025)
) %>%
filter(.metric != "distance") %>%
mutate(group = case_when(
.metric == "sens" | .metric == "spec" ~ "1",
TRUE ~ "2"
))
max_j_index_threshold <- threshold_data %>%
filter(.metric == "j_index") %>%
filter(.estimate == max(.estimate)) %>%
pull(.threshold)
ggplot(
threshold_data,
aes(
x = .threshold,
y = .estimate,
color = .metric,
alpha = group
)
) +
geom_line() +
scale_alpha_manual(values = c(.7, 1), guide = "none") +
geom_vline(
xintercept = max_j_index_threshold,
alpha = .6, size = 2,
color = "grey30"
) +
labs(
x = "'Good' Threshold (above this value is considered 'non-default')",
y = "Metric Estimate",
title = "Balancing performance by varying the threshold",
subtitle = "Sensitivity or specificity alone might not be enough!\nVertical line = Max J-Index"
)
From this visual, the optimal threshold is exactly 0.78. As an interesting view, let’s run that GLM model on all of the train_df
file with the new cutoff.
augment(glm_classification_fit, train_df, type = "prob") %>%
mutate(.pred = make_two_class_pred(.pred_nondefault,
levels(default_flag),
threshold = max_j_index_threshold
)) %>%
conf_mat(truth = default_flag, estimate = .pred_class) %>%
autoplot() +
labs(
title = "GLM Confusion Matrix on Entire Training Set",
subtitle = glue::glue("{ augment(glm_classification_fit, train_df, type = 'prob') %>% mutate(.pred = make_two_class_pred(.pred_nondefault, levels(default_flag), threshold = max_j_index_threshold)) %>% accuracy(truth = default_flag, estimate = .pred_class) %>% pull(.estimate) %>% round(2)*100 }% ", "Accuracy with regularized general linear model.\nOnly 4 accuracy points better than the NULL model.")
)
After all of that work, the results are disappointing, as the accuracy is not much better than the NULL model.
glm_classification_fit %>%
extract_fit_parsnip() %>%
vip::vip(metric = "mn_log_loss", num_features = 25L) +
labs(title = "Regularized GLM Variable Importance Scores")
Let’s use the insights gained above, including the variable importance scores, to build a better boosted model.
Catboost Model Tune
Catboost is a high-performance open source library for gradient boosting on decision trees. The algorithm has advantages in automatically handling deep categorical and hieararchies of categorical features and leveraging the residuals of the weak learners to boost additional rounds of model training. The catboost specification requires five tuning parameters, so it often takes more time to find optimal combinations.
catboost_classification_spec <- boost_tree(
trees = tune(),
mtry = tune(),
min_n = tune(),
learn_rate = tune(),
tree_depth = tune()
) %>%
set_engine("catboost") %>%
set_mode("classification") %>%
step_novel(all_nominal_predictors())
Given the variable importance scores above, let’s build a new pre-processor recipe()
.
catboost_class_rec <- recipe(
default_flag ~ GrAppv + SBA_Appv + ApprovalFY + Bank + BankState +
Name + NAICS + NAICS2 + NAICS3 + NAICS4 + UrbanRural + State + City,
data = training
)
Let’s set boundaries for the hyper-parameter search and build a grid of possible values.
catboost_classification_wf <-
workflow() %>%
add_recipe(catboost_class_rec) %>%
add_model(catboost_classification_spec)
set.seed(2021)
(race_grid <-
grid_latin_hypercube(
finalize(mtry(), catboost_class_rec %>% prep() %>% juice()),
trees(range = c(500, 1500)),
learn_rate(range = c(-2, -1)),
tree_depth(range = c(3L, 10L)),
min_n(range = c(10L, 40L)),
size = 5
))
catboost_classification_rs <- tune_race_anova(
catboost_classification_wf,
resamples = class_folds,
grid = race_grid,
metrics = metric_set(mn_log_loss),
control = control_race(
verbose = TRUE,
save_pred = TRUE,
save_workflow = TRUE,
extract = extract_model,
parallel_over = "resamples"
)
)
autoplot(catboost_classification_rs)
collect_metrics(catboost_classification_rs) %>%
dplyr::select(mtry,
trees,
min_n,
tree_depth,
learn_rate,
mn_log_loss = mean
) %>%
arrange(mn_log_loss)
After modeling on 5 resample sets for every combination of parameters, we collect metrics on the model’s performance on the samples of data held out in cross validation. This is a substantial improvement.
Performance Checks
catboost_classification_best_wf <-
catboost_classification_wf %>%
finalize_workflow(select_best(catboost_classification_rs))
catboost_classification_last_fit <-
catboost_classification_best_wf %>%
last_fit(split)
collect_metrics(catboost_classification_last_fit)
The accuracy and area under the roc curve are moving in the right direction, but 81% is not much better than the 80% that we achieved with the GLM PLS model above.
collect_predictions(catboost_classification_last_fit) %>%
conf_mat(default_flag, .pred_class) %>%
autoplot() +
labs(title = "Confusion Matrix on Resamples Held Out")
Here again, the algorithm is predicting “nondefault” when it should be finding defaults. Let’s press ahead explore a more suitable j_index.
classification_fit <- fit(catboost_classification_best_wf,
data = training
)
lending_test_pred <- classification_fit %>%
predict(new_data = testing, type = "prob") %>%
bind_cols(testing)
threshold_data <- lending_test_pred %>%
threshold_perf(default_flag,
.pred_nondefault,
thresholds = seq(0.5, 1, by = 0.0025)
) %>%
filter(.metric != "distance") %>%
mutate(group = case_when(
.metric == "sens" | .metric == "spec" ~ "1",
TRUE ~ "2"
))
max_j_index_threshold <- threshold_data %>%
filter(.metric == "j_index") %>%
filter(.estimate == max(.estimate)) %>%
pull(.threshold)
ggplot(
threshold_data,
aes(
x = .threshold,
y = .estimate,
color = .metric,
alpha = group
)
) +
geom_line() +
scale_color_viridis_d(end = 0.9) +
scale_alpha_manual(values = c(.4, 1), guide = "none") +
geom_vline(
xintercept = max_j_index_threshold,
alpha = .6,
color = "grey30"
) +
labs(
x = "'Good' Threshold\n(above this value is considered 'good')",
y = "Metric Estimate",
title = "Balancing performance by varying the threshold",
subtitle = "Sensitivity or specificity alone might not be enough!\nVertical line = Max J-Index"
)
From this visual, the optimal threshold is exactly 0.785. As an interesting view, let’s run that GLM model on all of the train_df
file with the new cutoff.
augment(classification_fit, train_df, type = "prob") %>%
mutate(.pred = make_two_class_pred(.pred_nondefault,
levels(default_flag),
threshold = max_j_index_threshold
)) %>%
conf_mat(truth = default_flag, estimate = .pred_class) %>%
autoplot() +
labs(
title = "Catboost Confusion Matrix on Entire Training Set",
subtitle = glue::glue("{ augment(classification_fit, train_df, type = 'prob') %>% mutate(.pred = make_two_class_pred(.pred_nondefault, levels(default_flag), threshold = max_j_index_threshold)) %>% accuracy(truth = default_flag, estimate = .pred_class) %>% pull(.estimate) %>% round(2)*100 }% ", "Accuracy with catboost classification model.\nMuch better than the NULL model.")
)
Good enough to move forward. Loans predicted in the classifier to default will be pushed through the regression model below.
Variable Importance
Let’s look at the features that contributed the most to this classification model.
classification_fit %>%
extract_fit_engine() %>%
catboost.get_feature_importance(
pool = NULL,
type = "FeatureImportance",
thread_count = -1
) %>%
as_tibble(rownames = "Feature") %>%
mutate(Feature = fct_reorder(Feature, V1)) %>%
ggplot(aes(V1, Feature)) +
geom_col() +
labs(
title = "Catboost SBA Default Classification Feature Importance",
y = NULL, x = NULL
)
The interaction feature that built by binding the bank to the approval year ended up as the most important feature, and the second most important is the ration between SBA_Approval value and the Gross Loan value.
Machine Learning: Regression
👉 Build a non-ML baseline first.
Recall that our goal is to predict the default_amount
. For our NULL
model, if we apply our classifier above, and then simply take the mean of all default_amounts
, the Mean Average Error is:
augment(classification_fit, train_df) %>%
mutate(.pred_default_amount = if_else(default_flag == "default", default_amount, 0)) %>%
filter(.pred_class == "default") %>%
mutate(.pred_mean = mean(.pred_default_amount)) %>%
mae(truth = default_amount, estimate = .pred_mean)
As an aside: the train_df
we used above contains 51 loans where the default_amount
was greater than the Loan Approval amount. Most of them are small value loans, for which there are thousands of others in the dataset.
EDA
Let’s make more exploratory plots to learn what features might yield good numeric predictions.
train_df_regression <- augment(classification_fit, train_df) %>%
filter(.pred_class == "default")
summarize_default_amounts <- function(tbl) {
tbl %>%
summarize(
n_loans = n(),
mean_default = mean(default_amount),
total_gr_appv = sum(GrAppv),
total_default_amount = sum(default_amount),
pct_default_amount = total_default_amount / total_gr_appv,
.groups = "drop"
) %>%
arrange(desc(n_loans))
}
plot_category_amounts <- function(tbl, category, n_categories = 7) {
tbl %>%
group_by({{ category }} := withfreq(fct_lump({{ category }}, n_categories))) %>%
summarize_default_amounts() %>%
mutate({{ category }} := fct_reorder({{ category }}, mean_default)) %>%
ggplot(aes(mean_default, {{ category }})) +
geom_point(aes(
color = pct_default_amount,
size = pct_default_amount
)) +
geom_text(aes(
x = mean_default - 6000,
label = glue::glue("$ {round(total_default_amount/10^6,0)} M")
),
hjust = 1
) +
scale_color_viridis_b(
name = "% of Loan Amount\nin Default",
labels = scales::percent_format(accuracy = 1),
breaks = seq(0, 1, 0.1),
option = "H"
) +
scale_size_binned(
name = "% of Loan\nin Default",
labels = scales::percent_format(accuracy = 1),
breaks = seq(0, 1, 0.1),
guide = "none"
) +
guides(color = guide_bins()) +
scale_x_continuous(labels = scales::dollar_format()) +
expand_limits(x = 0, size = 0) +
labs(
x = "Mean Value of the Defaults", y = NULL,
title = "US Small Business Administration Loans that defaulted 2003-2010",
subtitle = "# of loans in parenthesis and Total Defaulted $ on Labels"
)
}
train_df_regression %>%
plot_category_amounts(State) +
labs(title = "When they do default, Arizona has the highest mean Default Value")
train_df_regression %>%
plot_category_amounts(City) +
labs(title = "When they do default, Phoenix has the highest mean Default Value")
train_df_regression %>%
plot_category_amounts(Sector) +
labs(title = "When they do default, Accomodation have the highest mean Default Value")
train_df_regression %>%
plot_category_amounts(Bank) +
labs(title = "When they do default, ReadyCap Lending has the highest mean Default Value")
train_df_regression %>%
plot_category_amounts(NewExist) +
labs(title = "When they do default, Unknown have the highest mean Default Value")
train_df_regression %>%
plot_category_amounts(FranchiseCode) +
labs(title = "When they do default, Franchises have mean Default Value")
train_df_regression %>%
plot_category_amounts(NAICS2) +
labs(title = "When they do default, NAICS group 72 have mean Default Value")
train_df_regression %>%
plot_category_amounts(NAICS3) +
labs(title = "When they do default, NAICS group 722 have mean Default Value")
train_df_regression %>%
plot_category_amounts(NAICS4) +
labs(title = "When they do default, NAICS group 7222 have mean Default Value")
train_df_regression %>%
plot_category_amounts(NAICS) +
labs(title = "When they do default, NAICS group 722211 have mean Default Value")
train_df_regression %>%
plot_category_amounts(UrbanRural) +
labs(title = "When they do default, Unknown businesses have the highest mean Default Value")
train_df_regression %>%
plot_category_amounts(Name) +
labs(title = "When they do default, Domino's Pizza have the highest mean Default Value")
train_df_regression %>%
select_at(all_of(train_numeric)) %>%
cor() %>%
heatmap(main = "Numeric Correlations")
As with the classification, the regression will rely heavily on categorical features, and some of the numeric features are highly correlated with one another.
We will use 5-fold cross validation and stratify on the default_amount
this time.
set.seed(2021)
regression_split <- initial_split(augment(classification_fit, train_df) %>%
filter(.pred_class == "default"),
prop = 0.9
)
regression_training <- training(split)
regression_testing <- testing(split)
(regression_folds <-
vfold_cv(
regression_training,
v = 5,
strata = default_amount
))
The Recipe
For the regression, our outcome variable is the default_amount
. All other features are the same as above.
regression_rec <- recipe(
default_amount ~ LoanNr_ChkDgt + Name + Sector + City + State +
Bank + NAICS + ApprovalFY + NoEmp +
NewExist + CreateJob + RetainedJob + FranchiseCode + UrbanRural +
DisbursementGross + GrAppv + SBA_Appv + NAICS2 + NAICS3 + NAICS4 +
loan_per_employee + SBA_Appv_vs_Gross + Gross_minus_SBA_Appv +
Gross_minus_SBA_Appv_vs_Employee + same_state,
data = regression_training
) %>%
update_role(LoanNr_ChkDgt, new_role = "ID") %>%
step_zv(all_numeric_predictors()) %>%
step_novel(
Name,
Sector,
City,
State,
Bank,
NAICS,
NAICS2,
NAICS3,
NAICS4
) %>%
step_other(all_nominal_predictors(), threshold = tune::tune()) %>%
step_dummy(all_nominal_predictors()) %>%
step_interact(~ starts_with("Bank"):ApprovalFY) %>%
# Add important interactions
step_orderNorm(all_numeric_predictors()) %>%
step_normalize(all_numeric_predictors())
Linear Regression
linear_reg_lm_spec <-
linear_reg(
penalty = tune(),
mixture = tune()
) %>%
set_engine("glmnet")
linear_regression_wf <-
workflow() %>%
add_recipe(regression_rec) %>%
add_model(linear_reg_lm_spec)
linear_regression_rs <- tune_race_anova(
linear_regression_wf,
resamples = regression_folds,
grid = 30,
metrics = mset,
control = control_race(
verbose = TRUE,
save_pred = TRUE,
save_workflow = TRUE,
extract = extract_model,
parallel_over = "everything"
)
)
collect_metrics(linear_regression_rs) %>%
arrange(mean)
linear_regression_best_wf <-
linear_regression_wf %>%
finalize_workflow(select_best(linear_regression_rs))
glm_regression_fit <- fit(linear_regression_best_wf, data = regression_training)
glm_regression_fit %>%
extract_fit_parsnip() %>%
vip::vip(metric = "mae", num_features = 25L) +
labs(title = "Regularized GLM Variable Importance Scores")
Linear regression yields an Mean Average Error on cross validation samples held out that is about 1/3 less than the NULL model.
Catboost
Much like above, but now for regression.
catboost_regression_rec <- recipe(
default_amount ~ LoanNr_ChkDgt + Name + Sector + City + State +
Bank + NAICS + ApprovalFY + NoEmp +
NewExist + CreateJob + RetainedJob + FranchiseCode + UrbanRural +
DisbursementGross + GrAppv + SBA_Appv + NAICS2 + NAICS3 + NAICS4 +
loan_per_employee + SBA_Appv_vs_Gross + Gross_minus_SBA_Appv +
Gross_minus_SBA_Appv_vs_Employee + same_state,
data = regression_training
) %>%
update_role(LoanNr_ChkDgt, new_role = "ID") %>%
step_zv(all_numeric_predictors())
catboost_regression_spec <- boost_tree(
trees = tune(),
mtry = tune(),
min_n = tune(),
learn_rate = tune(),
tree_depth = tune()
) %>%
set_engine("catboost") %>%
set_mode("regression")
catboost_regression_wf <-
workflow() %>%
add_recipe(catboost_regression_rec) %>%
add_model(catboost_regression_spec)
set.seed(2021)
race_grid <-
grid_latin_hypercube(
finalize(mtry(), catboost_regression_rec %>% prep() %>% juice()),
trees(range = c(500, 1800)),
learn_rate(range = c(-2, -1)),
tree_depth(range = c(6L, 12L)),
min_n(range = c(10L, 45L)),
size = 15
)
catboost_regression_rs <- tune_race_anova(
catboost_regression_wf,
resamples = regression_folds,
grid = race_grid,
metrics = mset,
control = control_race(
verbose = FALSE,
save_pred = FALSE,
save_workflow = TRUE,
extract = extract_model,
parallel_over = "everything"
)
)
autoplot(catboost_regression_rs)
collect_metrics(catboost_regression_rs) %>%
select(mtry,
trees,
min_n,
tree_depth,
learn_rate,
"MAE" = mean
) %>%
arrange(MAE)
Not bad. Let’s make a final fit at these hyperparameter settings.
catboost_regression_best_wf <-
catboost_regression_wf %>%
finalize_workflow(select_best(catboost_regression_rs))
regression_fit <- fit(catboost_regression_best_wf,
data = augment(classification_fit, train_df) %>%
filter(.pred_class == "default")
)
Performance Checks
First, a Mean Absolute Error on the labeled testing
data that was held out:
augment(regression_fit, augment(classification_fit, testing)) %>%
mutate(.pred = if_else(default_flag == "default", .pred, 0)) %>%
mae(truth = default_amount, estimate = .pred)
Next, a MAE on the predictions on training
, to assess whether we have over-fit:
augment(regression_fit, augment(classification_fit, training)) %>%
mutate(.pred = if_else(default_flag == "default", .pred, 0)) %>%
mae(truth = default_amount, estimate = .pred)
Yep, we did. Given more time, increasing the number of folds and adding more repeats would help resolve this. A visual of our predictions of default_amounts
versus the truth:
augment(regression_fit, augment(classification_fit, train_df)) %>%
mutate(.pred = if_else(default_flag == "default", .pred, 0)) %>%
ggplot(aes(default_amount, .pred)) +
geom_point(alpha = 0.2, color = "blue") +
geom_abline(slope = 1, color = "red", alpha = 0.5) +
scale_x_continuous(labels = scales::dollar) +
scale_y_continuous(labels = scales::dollar) +
labs(
title = "Catboost Model Performance",
x = "Truth",
y = "Prediction"
)
Variable Importance
I like to come back to either these scores, or partial dependence plots, to be able to tell the story of what the model is doing and ask questions about the incoming data sample. If, for example, the SBA changes policies like the Approval thresholds, we would certainly expect the model to move dramatially.
vip_regressor <-
DALEXtra::explain_tidymodels(
regression_fit,
data = augment(regression_fit, augment(classification_fit, train_df)) %>%
mutate(.pred = if_else(default_flag == "default", .pred, 0)) %>%
select(-default_amount),
y = augment(regression_fit, augment(classification_fit, train_df)) %>%
mutate(.pred = if_else(default_flag == "default", .pred, 0)) %>%
select(default_amount),
label = "Catboost Regression",
verbose = FALSE
) %>%
DALEX::model_parts()
ggplot_imp(vip_regressor) +
theme(aspect.ratio = 1.2) +
labs(title = "SBA Default Amount Regression Feature Importance")
LS0tDQp0aXRsZTogIlNsaWNlZCBTZWFzb24gMSBGaW5hbGU6IFNCQSBMb2FuIERlZmF1bHRzIg0KYXV0aG9yOiAiSmltIEdydW1hbiINCmRhdGU6ICJBdWd1c3QgMTcsIDIwMjEiDQpvdXRwdXQ6DQogIHdvcmtmbG93cjo6d2Zsb3dfaHRtbDoNCiAgICB0b2M6IG5vDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KVGhlIFtTZWFzb24gMSBGaW5hbGVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYy9zbGljZWQtczAxZTEyLWNoYW1waW9uc2hpcCkgb2YgI1NMSUNFRCBmZWF0dXJlZCBhIGNoYWxsZW5nZSB0byBwcmVkaWN0IGRlZmF1bHQgZXhwb3N1cmUgaW4gYSBwb3J0Zm9saW8gb2YgYmFuayBsb2FucyBpbiBhIGRhdGFzZXQgZnJvbSB0aGUgW1UuUy4gU21hbGwgQnVzaW5lc3MgQWRtaW5pc3RyYXRpb25dKGh0dHBzOi8vZGF0YS5zYmEuZ292L29yZ2FuaXphdGlvbi9vY2lvKSAoU0JBKS4gDQoNCjxpbWcgYWx0PSJTbWFsbCBCdXNpbmVzcyBBZG1pbmlzdHJhdGlvbiIgYXJpYS1sYWJlbD0iaG9tZXBhZ2UiIHNyYz0iaHR0cHM6Ly93d3cuc2JhLmdvdi9idWlsZC9lMGUxOTlhY2QxNWIyYzU0YTE3ZGU2YmU1MDdjZjdmNy5wbmciPg0KDQpUaGUgU21hbGwgQnVzaW5lc3MgQWRtaW5pc3RyYXRpb24gKFNCQSkgd2FzIGZvdW5kZWQgaW4gMTk1MyB0byBhc3Npc3Qgc21hbGwgYnVzaW5lc3NlcyBpbiBzZWN1cmluZyBsb2Fucy4gVGhlIGFyZ3VtZW50IGlzIHRoYXQsIGFzIHNtYWxsIGJ1c2luZXNzZXMgYXJlIGEgcHJpbWFyeSBzb3VyY2Ugb2YgZW1wbG95bWVudCBpbiB0aGUgVW5pdGVkIFN0YXRlcywgdGhlbiBoZWxwaW5nIHNtYWxsIGJ1c2luZXNzZXMgaGVscHMgd2l0aCBqb2IgY3JlYXRpb24sIHJlZHVjaW5nIHVuZW1wbG95bWVudC4gT25lIG9mIHRoZSB3YXlzIHRoZSBTQkEgaGVscHMgc21hbGwgYnVzaW5lc3NlcyBpcyBieSBndWFyYW50ZWVpbmcgYmFuayBsb2Fucy4gVGhpcyBndWFyYW50ZWUgcmVkdWNlcyB0aGUgcmlzayB0byBpbmRpdmlkdWFsIGJhbmtzIGFuZCBlbmNvdXJhZ2VzIHRoZW0gdG8gbGVuZCBpbiBzaXR1YXRpb25zIHRoYXQgdGhleSBvdGhlcndpc2UgbWlnaHQgbm90LiBJZiB0aGUgbG9hbiBkZWZhdWx0cywgdGhlIFNCQSBjb3ZlcnMgdGhlIGFtb3VudCBndWFyYW50ZWVkLCBhbmQgdGhlIGJhbmsgc3VmZmVycyBhIGxvc3MgZm9yIHRoZSByZW1haW5pbmcgYmFsYW5jZS4NCg0KVGhlcmUgaGF2ZSBiZWVuIGEgZmV3IHNtYWxsIGJ1c2luZXNzIHN1Y2Nlc3Mgc3RvcmllcyBsaWtlIEZlZEV4IGFuZCBBcHBsZS4gSG93ZXZlciwgdGhlIHJhdGUgb2YgZGVmYXVsdCBpcyBzb21ld2hhdCBoaWdoZXIgdGhhbiBtb3N0IGNvbW1lcmNpYWwgYmFua3Mgd291bGQgZmluZCBhdHRyYWN0aXZlLiBTb21lIGVjb25vbWlzdHMgYmVsaWV2ZSB0aGUgYmFua2luZyBtYXJrZXQgd29ya3MgYmV0dGVyIHdpdGhvdXQgdGhlIGFzc2lzdGFuY2Ugb2YgdGhlIFNCQS4gT24gdGhlIG90aGVyIGhhbmQsIHN1cHBvcnRlcnMgY2xhaW0gdGhhdCB0aGUgc29jaWFsIGJlbmVmaXRzIGFuZCBqb2IgY3JlYXRpb24gb3V0d2VpZ2ggdGhlIGZpbmFuY2lhbCBjb3N0cyB0byB0aGUgdGF4cGF5ZXIgaW4gZGVmYXVsdGVkIGxvYW5zLg0KDQpNeSBjYXBzdG9uZSBwcm9qZWN0IGluIGdyYWQgc2Nob29sIGEgZmV3IHllYXJzIGJhY2sgaW52b2x2ZWQgYSBzdHVkeSBvZiByZWdpb25hbCBiYW5rcyBhbmQgYWNjb3VudCBwb2xpY2llcyBhY3Jvc3MgdGhlIFVTIGZpbmFuY2lhbCBuZXR3b3JrLiBSZWd1bGF0b3J5IHN1Ym1pc3Npb25zIHRvIFNCQSwgRkRJQywgdGhlIFVTREEsIGFuZCBldmVuIHN0YXRlIGJhbmtpbmcgYXV0aG9yaXRpZXMgYXJlIGEgZ3JlYXQgb3Bwb3J0dW5pdHkgdG8gbW9kZWwgZm9yIGRlY2lzaW9ucyBmb3IgZWNvbm9taXN0cyBhbmQgZmluYW5jZS4gSW4gdGhpcyBjYXNlLCBtb2RlbGluZyB0aGUgYW1vdW50IG9mIGRlZmF1bHQgZXhwb3N1cmUgY291bGQgYmUgdXNlZnVsIGluIHByb3ZpZGluZyBpbmZvcm1hdGlvbiB0byBnb3Zlcm5tZW50IHBvbGljeW1ha2VycyBvciBpbnN1cmVycy4NCg0KVGhlIHNvdXJjZSBkYXRhIHNldCB3YXMgZnJvbSB0aGUgVS5TLiBTQkEgbG9hbiBkYXRhYmFzZSBhbmQgaW5jbHVkZXMgaW5mb3JtYXRpb24gb24gd2hldGhlciB0aGUgbG9hbiB3YXMgcGFpZCBvZmYgaW4gZnVsbCBvciBpZiB0aGUgU0JBIGhhZCB0byBjaGFyZ2Ugb2ZmIGFueSBhbW91bnQgaW4gZGVmYXVsdCwgYW5kIGhvdyBtdWNoIHRoYXQgYW1vdW50IHdhcy4gTW9yZSBpbmZvcm1hdGlvbiBvbiB0aGlzIFtkYXRhIHNldCBpcyBhdmFpbGJsZSBoZXJlXShodHRwczovL2Ftc3RhdC50YW5kZm9ubGluZS5jb20vZG9pL2Z1bGwvMTAuMTA4MC8xMDY5MTg5OC4yMDE4LjE0MzQzNDIpLg0KDQpbU0xJQ0VEXShodHRwczovL3d3dy5ub3Rpb24uc28vU0xJQ0VELVNob3ctYzdiZDI2MzU2ZTNhNDIyNzllMmRmYmFmYjA0ODAwNzMpIGlzIGxpa2UgdGhlIFRWIFNob3cgQ2hvcHBlZCBidXQgZm9yIGRhdGEgc2NpZW5jZS4gVGhlIGNvbXBldGl0b3JzIGdldCBhIG5ldmVyLWJlZm9yZS1zZWVuIGRhdGFzZXQgYW5kIHR3by1ob3VycyB0byBjb2RlIGEgc29sdXRpb24gdG8gYSBwcmVkaWN0aW9uIGNoYWxsZW5nZS4gQ29udGVzdGFudHMgZ2V0IHBvaW50cyBmb3IgdGhlIGJlc3QgbW9kZWwgcGx1cyBib251cyBwb2ludHMgZm9yIGRhdGEgdmlzdWFsaXphdGlvbiwgdm90ZXMgZnJvbSB0aGUgYXVkaWVuY2UsIGFuZCBtb3JlLiBUaGUgZXZhbHVhdGlvbiBtZXRyaWMgaXMgYE1lYW4gQWJzb2x1dGUgRXJyb3JgLg0KDQpUaGUgYXVkaWVuY2UgaXMgaW52aXRlZCB0byBwYXJ0aWNpcGF0ZSBhcyB3ZWxsLiBUaGlzIHR5cGUgb2YgcHJvYmxlbSBpcyBhIGJpZyBjaGFsbGVuZ2UuIDY0ayBvZiB0aGUgODNrIGxvYW5zIGluIHRoZSBTTElDRUQgdHJhaW5pbmcgZGF0YXNldCBkaWQgbm90IGRlZmF1bHQgYXQgYWxsLCBzbyBzaG93IHplcm8gZGVmYXVsdCBhbW91bnQuIFNlcGFyYXRpbmcgdGhlIHNpZ25hbCBmcm9tIHRoZSBub2lzZSBoZXJlIGlzIHRvdWdoLiANCg0KIVtdKGh0dHBzOi8vd3d3Lm5vdGlvbi5zby9pbWFnZS9odHRwcyUzQSUyRiUyRnMzLXVzLXdlc3QtMi5hbWF6b25hd3MuY29tJTJGc2VjdXJlLm5vdGlvbi1zdGF0aWMuY29tJTJGN2Y3YmE1ZjktZDdiZC00MTAxLTg5MzMtYTExMmI0Zjc4NTcwJTJGRnJhbWVfMy5wbmc/dGFibGU9YmxvY2smaWQ9YzdiZDI2MzUtNmUzYS00MjI3LTllMmQtZmJhZmIwNDgwMDczJnNwYWNlSWQ9MmNjNDA0ZTYtZmUyMC00ODNkLTllYTUtNWQ0NGViM2RkNTg2JndpZHRoPTE1MTAmdXNlcklkPSZjYWNoZT12MikNCg0KQ2xlYXJseSB0aGVyZSBpcyBhIGxhcmdlIGltYmFsYW5jZSBoZXJlIGJldHdlZW4gZ29vZCBhbmQgYmFkIGxvYW5zLiBUaGlzIGlzIHByb2JhYmx5IGEgZ29vZCB0aGluZyBmb3IgdGhlIGJhbmtzLCBidXQgcG9zZXMgYW4gaW50ZXJlc3RpbmcgaXNzdWUgZm9yIHVzIGJlY2F1c2Ugd2Ugd2FudCB0byBlbnN1cmUgd2UgYXJlIHNlbnNpdGl2ZSB0byB0aGUgYmFkIGxvYW5zIGFuZCBhcmUgbm90IG92ZXJ3aGVsbWVkIGJ5IHRoZSBudW1iZXIgb2YgZ29vZCBvbmVzLiBPbmUgYXBwcm9hY2ggdGhhdCB3ZSBtaWdodCB0YWtlIGlzIHRvIGBkb3duc2FtcGxlYCB0aGUgbnVtYmVyIG9mIGdvb2QgbG9hbnMgc28gdGhhdCB0aGUgdG90YWwgbnVtYmVyIG9mIHRoZW0gaXMgbW9yZSBpbiBsaW5lIHdpdGggdGhlIG51bWJlciBvZiBiYWQgbG9hbnMuDQoNClRoZXJlIGFyZSBsaWtlbHkgY29uZm91bmRlcnMgYW5kIG90aGVyIGZlYXR1cmUgdHJhbnNmb3JtYXRpb25zIHJlcXVpcmVkLiAgSSBhbHNvIHdvbmRlcmVkIHdoZXRoZXIgYXBwcm9hY2hpbmcgdGhpcyBhcyBhIGNsYXNzaWZpY2F0aW9uIHByb2JsZW0gZmlyc3QsIGFuZCBidWlsZGluZyBhIHJlZ3Jlc3Npb24gc2Vjb25kIG1pZ2h0IGJlIGJldHRlci4gRWJlbiBFc3Rlcmh1aXplbiB3YXMga2luZCBlbm91Z2ggdG8gc2hhcmUgaGlzIHRvcC1zY29yaW5nIFB5dGhvbiBtb2RlbCBjb2RlIGF0IEthZ2dsZS4gSW50ZXJlc3RpbmdseSwgdGhpcyB3YXMgZXhhY3RseSBoaXMgYXBwcm9hY2guDQoNCmBgYHtyfQ0KdHdlZXRybWQ6OmluY2x1ZGVfdHdlZXQoImh0dHBzOi8vdHdpdHRlci5jb20vRWJlbkVzdGVyaHVpemVuL3N0YXR1cy8xNDI3ODM3MzAzNDIzMTg0OTAwIikNCg0KYGBgDQoNCmBgYHtjc3MsIGVjaG89RkFMU0V9DQouc2Nyb2xsLTEwMCB7DQogIG1heC1oZWlnaHQ6IDEwMHB4Ow0KICBvdmVyZmxvdy15OiBhdXRvOw0KICBiYWNrZ3JvdW5kLWNvbG9yOiBpbmhlcml0Ow0KfQ0KYGBgDQoNCk15IHBsYW4gaGVyZSBpcyB0byByZS13cml0ZSBoaXMgYXBwcm9hY2ggaW4gUiwgbWFrZSBhIG51bWJlciBvZiBpbXByb3ZlbWVudHMsIGFuZCBleHBsYWluIGhvdyBpdCBhbGwgd29ya3MuIExldCdzIGxvYWQgdXAgUiBwYWNrYWdlczoNCg0KYGBge3Igc2V0dXAsIGNsYXNzLm91dHB1dD0ic2Nyb2xsLTEwMCJ9DQoNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBjbGVhbiBhbmQgdHJhbnNmb3JtIHJlY3Rhbmd1bGFyIGRhdGENCg0KbGlicmFyeShocmJydGhlbWVzKSAjIHBsb3QgdGhlbWluZw0KbGlicmFyeShnZ3RoZW1lcykNCmxpYnJhcnkoZ2dmb3JjZSkNCmxpYnJhcnkodHJlZW1hcGlmeSkNCg0KbGlicmFyeSh0aWR5bW9kZWxzKSAjIG1hY2hpbmUgbGVhcm5pbmcgdG9vbHMNCmxpYnJhcnkoZmluZXR1bmUpICMgcmFjaW5nIG1ldGhvZHMgZm9yIGFjY2VsZXJhdGluZyBoeXBlcnBhcmFtZXRlciB0dW5pbmcNCg0KbGlicmFyeShkaXNjcmltKQ0KbGlicmFyeShwcm9iYWJseSkNCmxpYnJhcnkodHJlZXNuaXApICMgYSBib29zdCB0cmVlIGFkYXB0b3IgZm9yIGNhdGJvb3N0IGFuZCBvdGhlciBlbmdpbmVzDQpsaWJyYXJ5KGNhdGJvb3N0KSAjIG91ciBib29zdGVkIHRyZWUgZW5naW5lDQpsaWJyYXJ5KGVtYmVkKQ0KICANCmxpYnJhcnkoYmVzdE5vcm1hbGl6ZSkgIyBoYXMgYSBzdGVwIHRoYXQgY2FuIGVuZm9yY2UgYSBzeW1tZXRyaWMgZGlzdHJpYnV0aW9uIGZvciB0aGUgcHJlZGljdG9ycy4gV2XigJlsbCB1c2UgdGhpcyB0byBtaXRpZ2F0ZSB0aGUgaXNzdWUgb2Ygc2tld2VkIGRpc3RyaWJ1dGlvbnMuDQpsaWJyYXJ5KHRoZW1pcykgIyBtbCBwcmVwIHRvb2xzIGZvciBoYW5kbGluZyB1bmJhbGFuY2VkIGRhdGFzZXRzDQoNCiAgDQp9KQ0KDQpzb3VyY2UoaGVyZTo6aGVyZSgiY29kZSIsIl9jb21tb24uUiIpLA0KICAgICAgIHZlcmJvc2UgPSBGQUxTRSkNCg0KdGhlbWVfc2V0KHRoZW1lX2ppbShiYXNlX3NpemUgPSAxNCkpDQoNCiNjcmVhdGUgYSBkYXRhIGRpcmVjdG9yeQ0KZGF0YV9kaXIgPC0gaGVyZTo6aGVyZSgiZGF0YSIsU3lzLkRhdGUoKSkNCmlmICghZmlsZS5leGlzdHMoZGF0YV9kaXIpKSBkaXIuY3JlYXRlKGRhdGFfZGlyKQ0KDQojIHNldCBhIGNvbXBldGl0aW9uIG1ldHJpYw0KbXNldCA8LSBtZXRyaWNfc2V0KG1hZSkNCg0KIyBzZXQgdGhlIGNvbXBldGl0aW9uIG5hbWUgZnJvbSB0aGUgd2ViIGFkZHJlc3MNCmNvbXBldGl0aW9uX25hbWUgPC0gInNsaWNlZC1zMDFlMTItY2hhbXBpb25zaGlwIg0KDQp6aXBmaWxlIDwtIHBhc3RlMChkYXRhX2RpciwiLyIsIGNvbXBldGl0aW9uX25hbWUsICIuemlwIikNCg0KcGF0aF9leHBvcnQgPC0gaGVyZTo6aGVyZSgiZGF0YSIsU3lzLkRhdGUoKSxwYXN0ZTAoY29tcGV0aXRpb25fbmFtZSwiLmNzdiIpKQ0KYGBgDQoNCiMjIEdldCB0aGUgRGF0YQ0KDQpBIHF1aWNrIHJlbWluZGVyIGJlZm9yZSBkb3dubG9hZGluZyB0aGUgZGF0YXNldDogIEdvIHRvIHRoZSBLYWdnbGUgd2ViIHNpdGUgYW5kIGFjY2VwdCB0aGUgY29tcGV0aXRpb24gdGVybXMhISENCg0KSWYgeW91IGBwaXAgaW5zdGFsbCBrYWdnbGVgLCB0aGVyZSBhcmUgKFdpbmRvd3MpIHNoZWxsIGNvbW1hbmRzIGF2YWlsYWJsZSB0byBpbnRlcmFjdCB3aXRoIEthZ2dsZSBoZXJlOg0KDQpgYGB7ciBrYWdnbGUgY29tcGV0aXRpb25zIHRlcm1pbmFsIGNvbW1hbmRzLCBldmFsPUZBTFNFLCBjbGFzcy5vdXRwdXQ9InNjcm9sbC0xMDAifQ0KIyBmcm9tIHRoZSBLYWdnbGUgYXBpIGh0dHBzOi8vZ2l0aHViLmNvbS9LYWdnbGUva2FnZ2xlLWFwaQ0KDQojIHRoZSBsZWFkZXJib2FyZA0Kc2hlbGwoZ2x1ZTo6Z2x1ZSgna2FnZ2xlIGNvbXBldGl0aW9ucyBsZWFkZXJib2FyZCB7IGNvbXBldGl0aW9uX25hbWUgfSAtcycpKQ0KDQojIHRoZSBmaWxlcyB0byBkb3dubG9hZA0Kc2hlbGwoZ2x1ZTo6Z2x1ZSgna2FnZ2xlIGNvbXBldGl0aW9ucyBmaWxlcyAtYyB7IGNvbXBldGl0aW9uX25hbWUgfScpKQ0KDQojIHRoZSBjb21tYW5kIHRvIGRvd25sb2FkIGZpbGVzDQpzaGVsbChnbHVlOjpnbHVlKCdrYWdnbGUgY29tcGV0aXRpb25zIGRvd25sb2FkIC1jIHsgY29tcGV0aXRpb25fbmFtZSB9IC1wIHsgZGF0YV9kaXIgfScpKQ0KDQojIHVuemlwIHRoZSBmaWxlcyByZWNlaXZlZA0Kc2hlbGwoZ2x1ZTo6Z2x1ZSgndW56aXAgeyB6aXBmaWxlIH0gLWQgeyBkYXRhX2RpciB9JykpDQoNCmBgYA0KDQpSZWFkIGluIHRoZSBjb250ZW50cyBvZiB0aGUgZmlsZXMgdG8gZGF0YWZyYW1lcyBoZXJlLiBJJ3ZlIHdyaXR0ZW4gYW4gaW1wb3J0IGZ1bmN0aW9uIHRvIGNhc3QgdGhlIGRhdGEgdHlwZXMgYW5kIGNyZWF0ZSBuZXcgZmVhdHVyZXMgaW4gdGhlIHNhbWUgd2F5LCBib3RoIGZyb20gdGhlIHByb3ZpZGVkIHRyYWluIGRhdGEgZmlsZSBhcyB3ZWxsIGFzIGZyb20gdGhlIHVuLWxhYmVsZWQgZmlsZSBsYWJlbGVkIHRlc3QsIHRoYXQgSSB3aWxsIGNhbGwgYGNvbXBldGl0aW9uX3N1Ym1pc3Npb25fZGZgLiAgSW4gdGhpcyBjYXNlLCB0aGUgYGNvbXBldGl0aW9uX3N1Ym1pc3Npb25fZGZgIGFwcGVhcnMgdG8gYmUgYSByYW5kb20gc2FtcGxlIHNlbGVjdGlvbiBhY3Jvc3MgYWxsIHllYXJzIGFuZCBzdGF0ZXMuIFdlIHdpbGwgbGlrZWx5IGRpc2NvdmVyIHRoYXQgdGhlIGBjb21wZXRpdGlvbl9zdWJtaXNzaW9uX2RmYCBpbmNsdWRlcyB5ZWFycyBvciBmYWN0b3IgbGV2ZWxzLCBsaWtlIHN0YXRlcyBvciB6aXAgY29kZXMsIHRoYXQgd2VyZSBub3QgcHJlc2VudCBpbiB0aGUgdHJhaW5pbmcgc2V0Lg0KDQpgYGB7ciByZWFkIGthZ2dsZSBmaWxlcywgY2xhc3Mub3V0cHV0PSJzY3JvbGwtMTAwIn0NCg0KY2FzdF90cmFuc2Zvcm0gPC0gZnVuY3Rpb24odGJsKXsNCiAgdGJsICU+JSANCiAgIG11dGF0ZShGcmFuY2hpc2VDb2RlID0gaWZfZWxzZShGcmFuY2hpc2VDb2RlICVpbiUgYygwLDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5vRnJhbmNoaXNlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGcmFuY2hpc2UiKSwNCiAgICBOQUlDUzIgPSBzdHJfc3ViKE5BSUNTLCAxTCwgMkwpLCAjSW5kdXN0cnkgQ2xhc3MgSGllcmFyY2h5DQogICAgTkFJQ1MzID0gc3RyX3N1YihOQUlDUywgMUwsIDNMKSwgI0luZHVzdHJ5IENsYXNzIEhpZXJhcmNoeQ0KICAgIE5BSUNTNCA9IHN0cl9zdWIoTkFJQ1MsIDFMLCA0TCksICNJbmR1c3RyeSBDbGFzcyBIaWVyYXJjaHkNCiAgICBOZXdFeGlzdCA9IGlmX2Vsc2UoaXMubmEoTmV3RXhpc3QpLCAiVW5rbm93biIsIE5ld0V4aXN0KQ0KICAgICkgJT4lIA0KICBtdXRhdGUoYWNyb3NzKA0KICAgIGMoIk5hbWUiLA0KICAgICAgIkNpdHkiLA0KICAgICAgIlN0YXRlIiwNCiAgICAgICJaaXAiLA0KICAgICAgIkJhbmsiLA0KICAgICAgIlNlY3RvciIsDQogICAgICAiTkFJQ1MiLA0KICAgICAgIkJhbmtTdGF0ZSIsDQogICAgICAiVXJiYW5SdXJhbCIsDQogICAgICAiTmV3RXhpc3QiLA0KICAgICAgIkZyYW5jaGlzZUNvZGUiLCANCiAgICAgICJOQUlDUzIiLCAiTkFJQ1MzIiwgIk5BSUNTNCINCiAgICApLA0KICAgIGFzX2ZhY3Rvcg0KICApKSAlPiUNCiAgbXV0YXRlKA0KICAgIFVyYmFuUnVyYWwgPSBmY3RfcmVjb2RlKA0KICAgICAgVXJiYW5SdXJhbCwNCiAgICAgICJVcmJhbiIgPSAiMSIsDQogICAgICAiUnVyYWwiID0gIjIiLA0KICAgICAgIlVua25vd24iID0gIjAiDQogICAgKSwNCiAgICBOZXdFeGlzdCA9IGZjdF9yZWNvZGUoTmV3RXhpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICJFeGlzdGluZyIgPSAiMS4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIk5ldyIgPSAiMi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIlVua25vd24iID0gIjAuMCIpLA0KICAgIGxvYW5fcGVyX2VtcGxveWVlID0gR3JBcHB2IC8gKE5vRW1wICsgMSksDQogICAgU0JBX0FwcHZfdnNfR3Jvc3MgPSBTQkFfQXBwdiAvIEdyQXBwdiwNCiAgICBHcm9zc19taW51c19TQkFfQXBwdiA9IEdyQXBwdiAtIFNCQV9BcHB2LA0KICAgIEdyb3NzX21pbnVzX1NCQV9BcHB2X3ZzX0VtcGxveWVlID0gR3Jvc3NfbWludXNfU0JBX0FwcHYgLyAoTm9FbXAgKyAxKSwNCiAgICBzYW1lX3N0YXRlID0gU3RhdGUgPT0gQmFua1N0YXRlDQogICApDQp9DQoNCnRyYWluX2RmIDwtDQogIHJlYWRfY3N2KA0KICAgIGZpbGUgPSBnbHVlOjpnbHVlKHsNCiAgICAgIGRhdGFfZGlyDQogICAgfSwgIi90cmFpbi5jc3YiKSwNCiAgICBjb2xfdHlwZXMgPSAiY2NjY2NjY2NjaWljaWljY2RkZGQiLA0KICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UNCiAgKSAlPiUNCiAgbXV0YXRlKGRlZmF1bHRfZmxhZyA9IGFzX2ZhY3RvcihpZl9lbHNlKA0KICAgIGRlZmF1bHRfYW1vdW50ID4gMCwNCiAgICAiZGVmYXVsdCIsDQogICAgIm5vbmRlZmF1bHQiDQogICkpLA0KICBkZWZhdWx0X3Byb3BvcnRpb24gPSBkZWZhdWx0X2Ftb3VudCAvIEdyQXBwdikgJT4lDQogIGNhc3RfdHJhbnNmb3JtKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoZGVmYXVsdF9hbW91bnQsDQogICAgICAgICAgICAgICAgZGVmYXVsdF9mbGFnLA0KICAgICAgICAgICAgICAgIGRlZmF1bHRfcHJvcG9ydGlvbiwNCiAgICAgICAgICAgICAgICBldmVyeXRoaW5nKCkpDQoNCmNvbXBldGl0aW9uX3N1Ym1pc3Npb25fZGYgPC0NCiAgcmVhZF9jc3YoDQogICAgZmlsZSA9IGdsdWU6OmdsdWUoew0KICAgICAgZGF0YV9kaXINCiAgICB9LCAiL3Rlc3QuY3N2IiksDQogICAgY29sX3R5cGVzID0gImNjY2NjY2NjY2lpY2lpY2NkZGQiLA0KICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UNCiAgKSAlPiUNCiAgY2FzdF90cmFuc2Zvcm0oKQ0KDQpgYGANCg0KLS0tLQ0KDQojIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpDQoNClNvbWUgcXVlc3Rpb25zIHRvIGFuc3dlciB1cGZyb250Og0KDQotIFdoYXQgZmVhdHVyZXMgaGF2ZSBtaXNzaW5nIGRhdGEsIGFuZCBpbXB1dGF0aW9ucyBtYXkgYmUgcmVxdWlyZWQ/DQoNCi0gV2hhdCBkb2VzIHRoZSBvdXRjb21lIHZhcmlhYmxlIGxvb2sgbGlrZSwgaW4gdGVybXMgb2YgaW1iYWxhbmNlPw0KDQpJbiBjb21wZXRpdGlvbiBJIHVzZSBgc2tpbXI6OnNraW0oKWAgZm9yIHF1aWNrIGNoZWNrcy4gV2Ugd2lsbCBjcmFmdCBtdWNoIG5pY2VyIHBsb3RzIGJlbG93Lg0KDQpgYGB7ciBza2ltLCBldmFsID0gRkFMU0UsIGNsYXNzLm91dHB1dD0ic2Nyb2xsLTEwMCJ9DQpza2ltcjo6c2tpbSh0cmFpbl9kZikNCmBgYA0KDQojIyMgVGhlIE91dGNvbWUgVmFyaWFibGUNCg0KTGV0cyBjaGFyYWN0ZXJpemUgdGhlIGBkZWZhdWx0X2Ftb3VudGAgYXMgYSBoaXN0b2dyYW0gZm9yIGFsbCBkYXRhIGluIHRoZSBzZXQsIGFuZCBhcyBhIHRyZWVtYXAgdG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhlIGRpc3RyaWJ1dGlvbnMgYnkgYSBmZXcgb2YgdGhlIGNhdGVnb3J5IGZhY3RvciBsZXZlbHMuDQoNCmBgYHtyIG91dGNvbWUgdmFyaWFibGUgZWRhLCBvcHRzLkxhYmVsPSdmaWcubGFyZ2UnfQ0KdHJhaW5fZGYgJT4lIA0KICBncm91cF9ieShTdGF0ZSwgU2VjdG9yID0gZmN0X2x1bXAoU2VjdG9yLCAxMCkpICU+JSANCiAgc3VtbWFyaXNlKG4gPSBuKCksIA0KICAgICAgICAgICAgbWVhbl9kZWZhdWx0ID0gbWVhbihkZWZhdWx0X2Ftb3VudCA+IDAsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAuZ3JvdXBzID0gImRyb3AiKSAlPiUgDQogIGdncGxvdChhZXMoYXJlYSA9IG4sIA0KICAgICAgICAgICAgIGZpbGwgPSBtZWFuX2RlZmF1bHQsIA0KICAgICAgICAgICAgIGxhYmVsID0gU2VjdG9yLCANCiAgICAgICAgICAgICBzdWJncm91cCA9IFN0YXRlKSkgKw0KICBnZW9tX3RyZWVtYXAoKSArDQogIGdlb21fdHJlZW1hcF9zdWJncm91cF9ib3JkZXIoKSArDQogIGdlb21fdHJlZW1hcF90ZXh0KGNvbG9yID0gImJsYWNrIiwgcGxhY2UgPSAidG9wIiwgcmVmbG93ID0gVFJVRSkgKw0KICBnZW9tX3RyZWVtYXBfc3ViZ3JvdXBfdGV4dCgNCiAgICBjb2xvciA9ICJncmF5NDAiLCANCiAgICBwbGFjZSA9ICJib3R0b21yaWdodCIsDQogICAgZm9udGZhY2UgPSAiaXRhbGljIiwgDQogICAgbWluLnNpemUgPSAwLCANCiAgICBhbHBoYSA9IDAuMw0KICApICArDQogIHNjYWxlX2ZpbGxfZmVybWVudGVyKA0KICAgIHBhbGV0dGUgPSAiUHVSZCIsIA0KICAgIGRpcmVjdGlvbiA9IDEsDQogICAgZ3VpZGUgPSAiY29sb3JzdGVwcyIsIA0KICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSwNCiAgICBicmVha3MgPSBzZXEoMCwxLDAuMSkNCiAgKSArDQogIGxhYnMoDQogICAgZmlsbCA9ICJMb2Fuc1xuVGhhdCBEZWZhdWx0IiwgDQogICAgdGl0bGUgPSAiVVMgU21hbGwgQnVzaW5lc3MgQWRtaW4gTG9hbnMgMjAwMy0yMDEwIiwNCiAgICBzdWJ0aXRsZSA9ICJCb3ggYXJlYSBpcyAjIG9mIGxvYW5zIGZvciBzZWN0b3JzIHdpdGhpbiBlYSBTdGF0ZS5cblRoZSB2YXN0IG1ham9yaXR5IG9mIGluZGl2aWR1YWwgbG9hbnMgZG8gbm90IGRlZmF1bHQuIiwNCiAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBLYWdnbGUiDQogICkNCmBgYA0KDQpgYGB7ciBncm9zcyBhcHByb3ZlZCB2YWx1ZSBoaXN0b2dyYW19DQp0cmFpbl9kZiAlPiUgDQogIGRwbHlyOjpzZWxlY3QoR3JBcHB2LCBkZWZhdWx0X2ZsYWcgKSAlPiUgDQogIGJpbmRfcm93cyhkcGx5cjo6c2VsZWN0KGNvbXBldGl0aW9uX3N1Ym1pc3Npb25fZGYsIEdyQXBwdikgJT4lIG11dGF0ZShkZWZhdWx0X2ZsYWcgPSAiaG9sZG91dCBzZXQiKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKEdyQXBwdiwgZmlsbCA9IGRlZmF1bHRfZmxhZykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDUwKSArDQogIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpkb2xsYXJfZm9ybWF0KCkpICsNCiAgbGFicyh0aXRsZSA9ICJMb2FucyB0ZW5kIHRvIGZhbGwgYmV0d2VlbiAkMTBrIHRvIDFNIiwNCiAgICAgICBmaWxsID0gTlVMTCwgeCA9ICJHcm9zcyBBcHByb3ZlZCBMb2FuIFZhbHVlIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJTQkEgbXVzdCBoYXZlIHZhbHVlIHRocmVzaG9sZCBsaW1pdHMiKQ0KYGBgDQoNCi0tLS0NCg0KIyMjIENhdGVnb3JpY2FsIFZhcmlhYmxlcw0KDQpgYGB7ciBzdW1tYXJpemUgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCBvcHRzLkxhYmVsPSdmaWcubGFyZ2UnfQ0Kc3VtbWFyaXplX2RlZmF1bHRzIDwtIGZ1bmN0aW9uKHRibCl7DQogIHRibCAlPiUgDQogIHN1bW1hcml6ZShuX2xvYW5zID0gbigpLA0KICAgICAgICAgICAgcGN0X2RlZmF1bHQgPSBtZWFuKGRlZmF1bHRfYW1vdW50ID4gMCksDQogICAgICAgICAgICB0b3RhbF9ncl9hcHB2ID0gc3VtKEdyQXBwdiksDQogICAgICAgICAgICB0b3RhbF9kZWZhdWx0X2Ftb3VudCA9IHN1bShkZWZhdWx0X2Ftb3VudCApLA0KICAgICAgICAgICAgcGN0X2RlZmF1bHRfYW1vdW50ID0gdG90YWxfZGVmYXVsdF9hbW91bnQgLyB0b3RhbF9ncl9hcHB2LA0KICAgICAgICAgICAgLmdyb3VwcyA9ICJkcm9wIikgJT4lIA0KICBhcnJhbmdlKGRlc2Mobl9sb2FucykpDQp9DQoNCndpdGhmcmVxIDwtIGZ1bmN0aW9uKHgpew0KICB0aWJibGUoeCkgJT4lIA0KICAgIGFkZF9jb3VudCh4KSAlPiUgDQogICAgbXV0YXRlKGNvbWJpbmVkID0gZ2x1ZTo6Z2x1ZSgieyBzdHJfc3ViKHgsIGVuZCA9IDE4KSB9ICh7IG4gfSkiKSkgJT4lIA0KICAgIHB1bGwoY29tYmluZWQpDQp9DQoNCnBsb3RfY2F0ZWdvcnkgPC0gZnVuY3Rpb24odGJsLCBjYXRlZ29yeSwgbl9jYXRlZ29yaWVzID0gNyl7DQogIHRibCAlPiUgDQogIGdyb3VwX2J5KHt7IGNhdGVnb3J5IH19IDo9IHdpdGhmcmVxKGZjdF9sdW1wKHt7IGNhdGVnb3J5IH19LCBuX2NhdGVnb3JpZXMpKSkgJT4lIA0KICBzdW1tYXJpemVfZGVmYXVsdHMoKSAlPiUgDQogIG11dGF0ZSh7eyBjYXRlZ29yeSB9fSA6PSBmY3RfcmVvcmRlcih7eyBjYXRlZ29yeSB9fSwgcGN0X2RlZmF1bHQpKSAlPiUgDQogIGdncGxvdChhZXMocGN0X2RlZmF1bHQsIHt7IGNhdGVnb3J5IH19KSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHBjdF9kZWZhdWx0X2Ftb3VudCkpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gcGN0X2RlZmF1bHQgLSAwLjAyLCANCiAgICAgICAgICAgICAgICBsYWJlbCA9IGdsdWU6OmdsdWUoIiQge3JvdW5kKHRvdGFsX2RlZmF1bHRfYW1vdW50LzEwXjYsMCl9IE0iKSksDQogICAgICAgICAgICBoanVzdCA9IDEpICsNCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2IoDQogICAgICBuYW1lID0gIiUgb2YgTG9hbiBBbW91bnRcbmluIERlZmF1bHQiLA0KICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpLA0KICAgICAgYnJlYWtzID0gYygwLCAwLjA1LCAwLjEsIDAuMTUpLA0KICAgICAgb3B0aW9uID0gIkgiDQogICAgKSArDQogICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfYmlucygpKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICAgIGV4cGFuZF9saW1pdHMoeCA9IDAsIHNpemUgPSAwKSArDQogICAgbGFicyh4ID0gIiUgb2YgTG9hbnMgT3JpZ2luYXRlZCB0aGF0IERlZmF1bHRlZCIsIHkgPSBOVUxMLA0KICAgICAgICAgc3VidGl0bGUgPSAiKCMgb2YgbG9hbnMpICYgVG90YWwgRGVmYXVsdGVkIEFtb3VudCBvbiBMYWJlbHMiKQ0KICANCn0NCg0KdHJhaW5fZGYgJT4lIA0KICAgcGxvdF9jYXRlZ29yeShTdGF0ZSkgKw0KICAgbGFicyh0aXRsZSA9ICJGbG9yaWRhLCBJbGxpbm9pcywgTmV3IFlvcmsiKQ0KDQp0cmFpbl9kZiAlPiUgDQogIHBsb3RfY2F0ZWdvcnkoQ2l0eSkgKw0KICBsYWJzKHRpdGxlID0gIk1pYW1pIGxlYWRzIikNCg0KdHJhaW5fZGYgJT4lIA0KICBwbG90X2NhdGVnb3J5KFNlY3RvcikgKw0KICBsYWJzKHRpdGxlID0gIkNvbnN0cnVjdGlvbiBsZWFkcyIpDQoNCnRyYWluX2RmICU+JSANCiAgcGxvdF9jYXRlZ29yeShCYW5rKSArDQogIGxhYnModGl0bGUgPSAiQkJDTiBpcyB0aGUgbGVhZGVyIikNCg0KdHJhaW5fZGYgJT4lIA0KICBtdXRhdGUoc2FtZV9zdGF0ZSA9IGlmX2Vsc2UoQmFua1N0YXRlID09IFN0YXRlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTYW1lIFN0YXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJEaWZmZXJlbnQiKSkgJT4lIA0KICBwbG90X2NhdGVnb3J5KHNhbWVfc3RhdGUpICsNCiAgbGFicyh0aXRsZSA9ICJCYW5rcyBpbiBTdGF0ZXMgZGlmZmVyZW50IHRoYW4gdGhlIEJvcnJvd2VyIikNCg0KdHJhaW5fZGYgJT4lIA0KICBwbG90X2NhdGVnb3J5KE5ld0V4aXN0KSArDQogIGxhYnModGl0bGUgPSAiTmV3IEJvcnJvd2VycyIpDQoNCnRyYWluX2RmICU+JSANCiAgcGxvdF9jYXRlZ29yeShGcmFuY2hpc2VDb2RlKSArDQogIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIEZyYW5jaGlzZXMgYXJlbid0IG11Y2ggZGlmZmVyZW50IikNCg0KdHJhaW5fZGYgJT4lIA0KICBwbG90X2NhdGVnb3J5KE5BSUNTMikgKw0KICBsYWJzKHRpdGxlID0gIk5BSUNTIGNvZGVzIHN0YXJ0aW5nIHdpdGggMjMiKQ0KDQp0cmFpbl9kZiAlPiUgDQogIHBsb3RfY2F0ZWdvcnkoTkFJQ1MzKSArDQogIGxhYnModGl0bGUgPSAiTkFJQ1MgY29kZXMgc3RhcnRpbmcgd2l0aCAyMzgiKQ0KDQp0cmFpbl9kZiAlPiUgDQogIHBsb3RfY2F0ZWdvcnkoTkFJQ1M0KSArDQogIGxhYnModGl0bGUgPSAiTkFJQ1MgY29kZXMgc3RhcnRpbmcgd2l0aCA4MTIxIikNCg0KdHJhaW5fZGYgJT4lIA0KICBwbG90X2NhdGVnb3J5KE5BSUNTKSArDQogIGxhYnModGl0bGUgPSAiTkFJQ1MgY29kZSA3MjIyMTEiKQ0KDQp0cmFpbl9kZiAlPiUgDQogIHBsb3RfY2F0ZWdvcnkoVXJiYW5SdXJhbCkgKw0KICBsYWJzKHRpdGxlID0gIlVyYmFuIGJvcnJvd2VycyIpDQoNCnRyYWluX2RmICU+JSANCiAgcGxvdF9jYXRlZ29yeShOYW1lKSArDQogIGxhYnModGl0bGUgPSAiVGhlIGJhZCBidXNpbmVzcyBuYW1lcyIpDQoNCmBgYA0KDQotLS0tDQoNCiMjIyBUaW1lIFNlcmllcw0KDQpgYGB7ciB0aW1lIHNlcmllcywgb3B0cy5MYWJlbD0nZmlnLmxvbmcnfQ0KdHJhaW5fZGYgJT4lIA0KICBncm91cF9ieShBcHByb3ZhbEZZLCBTdGF0ZSA9IGZjdF9sdW1wKFN0YXRlLCA1KSkgJT4lIA0KICBzdW1tYXJpemVfZGVmYXVsdHMoKSAlPiUgDQogIGdncGxvdChhZXMoQXBwcm92YWxGWSwgcGN0X2RlZmF1bHQsIGNvbG9yID0gU3RhdGUpKSArDQogIGdlb21fbGluZShzaXplID0gMikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIHNjYWxlX3NpemVfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcl9mb3JtYXQoKSkgKw0KICBleHBhbmRfbGltaXRzKHkgPSAwLCBzaXplID0gMCkgKw0KICBsYWJzKHggPSAieWVhciBhcHByb3ZlZCIsIHkgPSAiRGVmYXVsdCBQZXJjZW50YWdlIiwNCiAgICAgICB0aXRsZSA9ICJTQkEgTG9hbnMgaW4gdGhlIDIwMDggZmluYW5jaWFsIGNyaXNpcyIsDQogICAgICAgc2l6ZSA9ICJMb2FuIEFwcHJvdmFscyIsDQogICAgICAgY2FwdGlvbiA9ICJCYXNlZCBvbiBEYXZpZCBSb2JpbnNvbiBAZHJvYiBleGFtcGxlIikNCiAgDQpgYGANCg0KLS0tLQ0KDQojIyMgTnVtZXJpYyBGZWF0dXJlcw0KDQpgYGB7ciBzdW1tYXJpemUgbnVtZXJpYyBvdXRjb21lLCBvcHRzLkxhYmVsPSdmaWcubG9uZyd9DQp0cmFpbl9udW1lcmljIDwtIHRyYWluX2RmICU+JSBrZWVwKGlzLm51bWVyaWMpICU+JSBjb2xuYW1lcygpDQoNCnRyYWluX2RmICU+JQ0KICBiaW5kX3Jvd3MoY29tcGV0aXRpb25fc3VibWlzc2lvbl9kZikgJT4lIA0KICBzZWxlY3RfYXQoYWxsX29mKHRyYWluX251bWVyaWMpKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtZGVmYXVsdF9hbW91bnQpICU+JQ0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IGV2ZXJ5dGhpbmcoKSwNCiAgICBuYW1lc190byA9ICJrZXkiLA0KICAgIHZhbHVlc190byA9ICJ2YWx1ZSINCiAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYSh2YWx1ZSkpICU+JQ0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh2YWx1ZSwgZmlsbCA9IGtleSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oDQogICAgcG9zaXRpb24gPSAiaWRlbnRpdHkiLA0KICAgIGJpbnMgPSAzMCwNCiAgICBzaG93LmxlZ2VuZCA9IEZBTFNFDQogICkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYV9mb3JtYXQoKSwNCiAgICAgICAgICAgICAgICAgICAgIG4uYnJlYWtzID0gMykgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobi5icmVha3MgPSAzKSArIA0KICBmYWNldF93cmFwKCB+IGtleSwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gMykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk51bWVyaWMgRmVhdHVyZSBIaXN0b2dyYW0gRGlzdHJpYnV0aW9ucyIsDQogICAgc3VidGl0bGUgPSAiVHJhaW5pbmcgYW5kIEhvbGRvdXQgZGF0YXNldHMgY29tYmluZWQiLA0KICAgIHggPSBOVUxMLA0KICAgIHkgPSAiQ291bnQiDQogICkgKw0KICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLjMpICsNCiAgdGhlbWVfamltKGJhc2Vfc2l6ZSA9IDgpDQoNCnRyYWluX2RmICU+JSANCiAgZmlsdGVyKGRlZmF1bHRfYW1vdW50ID4gMSkgJT4lIA0KICBzZWxlY3RfYXQoYWxsX29mKHRyYWluX251bWVyaWMpKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtYyhkZWZhdWx0X2Ftb3VudCwgQXBwcm92YWxGWSksDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJrZXkiLA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIA0KICBnZ3Bsb3QoYWVzKEFwcHJvdmFsRlksIHZhbHVlLCB6ID0gZGVmYXVsdF9hbW91bnQpKSArDQogIHN0YXRfc3VtbWFyeV9oZXgoYWxwaGEgPSAwLjksIGJpbnMgPSAxMCkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiSCIsbGFiZWxzID0gc2NhbGVzOjpkb2xsYXJfZm9ybWF0KCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKG4uYnJlYWtzID0gMykgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobi5icmVha3MgPSAzKSArDQogIGZhY2V0X3dyYXAofiBrZXksIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDMgKSArDQogIGxhYnMoc3VidGl0bGUgPSAiTWVhbiBEZWZhdWx0IFZhbHVlIFdoZXJlID4gJDEiLCANCiAgICAgICB0aXRsZSA9ICJOdW1lcmljIEZlYXR1cmVzIGJ5IExvYW4gT3JpZ2luYXRpb24gWWVhciIsDQogICAgICAgeSA9IE5VTEwsIGZpbGwgPSAiRGVmYXVsdCAkIikgKw0KICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLjMpICsNCiAgdGhlbWVfamltKGJhc2Vfc2l6ZSA9IDgpDQoNCnRtd3JfY29scyA8LSB2aXJpZGlzOjp2aXJpZGlzX3BhbChvcHRpb24gPSAiSCIpDQoNCnRyYWluX2RmICU+JSANCiAgICBzZWxlY3RfYXQoYWxsX29mKHRyYWluX251bWVyaWMpKSAlPiUNCiAgICBjb3IoKSAlPiUgDQogICAgaGVhdG1hcChtYWluID0gIk51bWVyaWMgQ29ycmVsYXRpb25zIikNCg0KYGBgDQoNClRoZSBoaWdobHkgY29ycmVsYXRlZCBudW1lcmljIHZhcmlhYmxlcyBhcmUgZ29pbmcgdG8gaGF2ZSB0byBiZSBkZWFsdCB3aXRoLCBlaXRoZXIgdGhyb3VnaCBzb21lIHNvcnQgb2YgcmVndWxhcml6YXRpb24gb3IgcHJlLXByb2Nlc3NpbmcuDQoNCi0tLS0NCg0KIyMjIE1hcHMNCg0KYGBge3IgbWFwLCBvcHRzLkxhYmVsPSdmaWcubGFyZ2UnfQ0KYnlfc3RhdGUgPC0gdHJhaW5fZGYgJT4lIA0KICBncm91cF9ieShTdGF0ZSkgJT4lIA0KICBzdW1tYXJpemVfZGVmYXVsdHMoKQ0KDQptYXBfZGF0YSgic3RhdGUiKSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JSANCiAgbXV0YXRlKFN0YXRlID0gc3RhdGUuYWJiW21hdGNoKHJlZ2lvbiwgc3RyX3RvX2xvd2VyKHN0YXRlLm5hbWUpKV0pICU+JSANCiAgaW5uZXJfam9pbihieV9zdGF0ZSwgYnkgPSAiU3RhdGUiKSAlPiUgDQogIGdncGxvdChhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gcGN0X2RlZmF1bHQpKSArDQogIGdlb21fcG9seWdvbigpICsNCiAgdGhlbWVfbWFwKCkgKw0KICBjb29yZF9tYXAoKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJIIiwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIGxhYnModGl0bGUgPSAiU0JBIExvYW4gRGVmYXVsdHMgMjAwMy0yMDEwOiBGbG9yaWRhLCBHZW9yZ2lhLCBJbGxpbm9pcywgTmV2YWRhLCAmIE1pY2hpZ2FuIGxlYWQiLA0KICAgICAgIGZpbGwgPSAiUGVyY2VudCBvZiBMb2FuXG5PcmlnaW5hdGlvbnMgRW5kaW5nXG5pbiBEZWZhdWx0IiwNCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBLYWdnbGUuIEJhc2VkIG9uIERhdmlkIFJvYmluc29uJ3MgZXhhbXBsZS4iKQ0KDQpgYGANCg0KU28sIHllcywgd2Ugd2lsbCBmaXJzdCBtb2RlbCB0byBwcmVkaWN0IGZvciB0aGUgYGRlZmF1bHRfZmxhZ2AgY2xhc3MuIElmIGl0IHBlcmZvcm1zIHdlbGwsIHdlIHdpbGwgdGhlbiBnbyBvbiB0byBtb2RlbCB0aGUgYGRlZmF1bHRfYW1vdW50YCBzZXBhcmF0ZWx5IGZvciB0aGUgbG9hbnMgcHJlZGljdGVkIHRvIGRlZmF1bHQuDQoNCiMgTWFjaGluZSBMZWFybmluZzogQ2xhc3NpZmljYXRpb24gey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KSGVyZSdzIGFuIG9mdGVuIG92ZXJsb29rZWQgcGllY2Ugb2YgYWR2aWNlIGZvciBhbGwgTUwgcHJhY3RpdGlvbmVycywgJiBlc3BlY2lhbGx5IG5ld2NvbWVyczoNCg0KYHIgZW1vamlmb250OjplbW9qaSgicG9pbnRfcmlnaHQiKWAgIEJ1aWxkIGEgbm9uLU1MIGJhc2VsaW5lIGZpcnN0Lg0KDQpZb3UnbGwgYmUgc3VycHJpc2VkIGhvdyBvZnRlbiB5b3UgY2Fubm90IGVhc2lseSBpbXByb3ZlIGEgYmFzaWMgc29sdXRpb24gYnkgdGhyb3dpbmcgTUwgYXQgaXQsIGFuZCB5b3UnbGwgbGVhcm4gc29tZXRoaW5nIGFib3V0IHRoZSBwcm9ibGVtIHRvby4NCg0KWW91ciBiZXN0IGFsdGVybmF0aXZlIHRvIGFueSBjb21wbGV4IG1vZGVsIGlzbuKAmXQg4oCcbm8gbW9kZWwu4oCdIFlvdXIgYWx0ZXJuYXRpdmUgaXMgYSBzaW1wbGUgbW9kZWwuIEkgc2VlIHRoaXMgb3ZlciBhbmQgb3Zlci4gQmVmb3JlIHlvdSBidWlsZCBhIFJ1YmUgR29sZGJlcmcgbW9kZWwgb2YgdGhlIHVuaXZlcnNlLCBidWlsZCBhIGJyYWluIGRlYWQgc2ltcGxlIG1vZGVsLiBUSEFUIHBhcnNpbW9uaW91cyBwaWVjZSBvZiBqdW5rIGlzIHlvdXIgYm9naWUuIE91dCBwZXJmb3JtIFRIQVQgaW4gYSB3YXkgdGhhdCBqdXN0aWZpZXMgeW91ciBjb21wbGV4aXR5IGFuZCB0aW1lLg0KDQpPbmNlIHlvdeKAmXJlIGNvbXBhcmluZyBhIGNvbXBsaWNhdGVkIG1vZGVsIHRvIGEgcGFyc2ltb25pb3VzIG1vZGVsIHlvdSBjYW4gaW50ZWxsaWdlbnRseSBkaXNjdXNzIHRoZSDigJxtYXJnaW5hbCBiZW5lZml0IChvciBsaWZ0KSBmcm9tIGNvbXBsZXhpdHku4oCdDQoNCmBgYHtyIG51bGwgYWNjdXJhY3l9DQpyb3VuZCgxMDAqcHJvcC50YWJsZSh0YWJsZSh0cmFpbl9kZiRkZWZhdWx0X2ZsYWcpKSwxKSANCmBgYA0KDQpJbiB0aGUgbG9hbiBkZWZhdWx0IGRhdGFzZXQsIHByZWRpY3RpbmcgYSBub25kZWZhdWx0IGNsYXNzIGZvciBldmVyeSBjYXNlIHJlc3VsdHMgaW4gNzcuMSUgYWNjdXJhY3ksIGRvaW5nIG5vdGhpbmcuIExldCdzIGNhbGwgdGhpcyB0aGUgTlVMTCBtb2RlbC4NCg0KLS0tLQ0KDQojIyBTcGVuZGluZyB0aGUgRGF0YQ0KDQpXZSB3aWxsIHNwbGl0IGB0cmFpbl9kZmAgcmFuZG9tbHkgaW50byBhIHRyYWluaW5nIHBvcnRpb24gKDkwJSkgYW5kIGEgbGFiZWxlZCB0ZXN0aW5nIHBvcnRpb24gKDEwJSkuIFRoZSB0cmFpbmluZyBwb3J0aW9uIGlzIHRoZW4gZGl2aWRlZCBpbnRvIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHNldHMgYW5kIHN0cmF0aWZpZWQgb24gdGhlIGBkZWZhdWx0X2ZsYWdgIHRvIGVuc3VyZSB0aGF0IGVhY2ggQ1YgZm9sZCBoYXMgc29tZSBwcm9wb3J0aW9uIG9mIHRoZSBpbWJhbGFuY2VkIG91dGNvbWVzLiAgDQoNCmBgYHtyIHNwZW5kaW5nIHRoZSBkYXRhfQ0Kc2V0LnNlZWQoMjAyMSkNCg0Kc3BsaXQgPC0gaW5pdGlhbF9zcGxpdCh0cmFpbl9kZiwgcHJvcCA9IDAuOSkNCg0KdHJhaW5pbmcgPC0gdHJhaW5pbmcoc3BsaXQpDQp0ZXN0aW5nIDwtIHRlc3Rpbmcoc3BsaXQpDQoNCmNsYXNzX2ZvbGRzIDwtIHZmb2xkX2N2KHRyYWluaW5nLCB2ID0gNSwgc3RyYXRhID0gZGVmYXVsdF9mbGFnICkNCg0KYGBgDQoNCiMjIFRoZSBSZWNpcGVzDQoNCldlIGFyZSBnb2luZyB0byBzdGFydCBieSB0YWtpbmcgYSBzdXJ2ZXkgb2YgYSBoYW5kZnVsIG9mIG1vZGVscyB0aGF0IHRyYWluIGZhc3QsIGhhdmUgbGl0dGxlIHR1bmluZyByZXF1aXJlZCwgYW5kIGFyZSBnZW5lcmFsbHkgaW50ZXJwcmV0YWJsZS4gVGhlIGNob2ljZSBvZiBwcmUtcHJvY2Vzc29yLCBvciBgdGlkeW1vZGVsc2AgYHJlY2lwZSgpYCBzdGVwcywgdHJhbnNmb3JtcyB0aGUgZGF0YWZyYW1lIGludG8gZm9ybWF0cyB0aGF0IG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBjYW4gd29yayB3aXRoLiANCg0KT3VyIHN0cmF0ZWd5IGhlcmUgaXMgdG8gcHJlLXByb2Nlc3MgYXQgdGhyZWUgbGV2ZWxzLiBUaGUgZmlyc3QsIGBjbGFzc2lmaWNhdGlvbl9yZWNgIGlzIGEgcmF3IGRhdGFzZXQgd2l0aCBhbGwgY2F0ZWdvcmljYWwgcHJlZGljdG9ycyBpbnRhY3QuIFRoZSBzZWNvbmQsIGBkdW1taWVzX2NsYXNzaWZpY2F0aW9uX3JlY2AgdHVuZXMgYSB0aHJlc2hvbGQgZm9yIGx1bXBpbmcgY2F0ZWdvcmljYWwgbGV2ZWxzIHRvZ2V0aGVyIGFuZCB0aGVuIGNyZWF0ZXMgZHVtbXkgdmFyaWFibGVzIHNvIGV2ZXJ5dGhpbmcgaXMgbnVtZXJpYy4gV2l0aCBzbyBtYW55IGNhdGVnb3J5IGxldmVscywgdGhpcyB0cmFpbmluZyBzZXQgY291bGQgYmUgdGhvdXNhbmRzIG9mIGNvbHVtbnMgd2lkZSwgYW5kIHNwYXJzZS4gVGhlcmUgaXMgYSBjYXJkaW5hbGl0eSB0cmFkZW9mZiBoZXJlLCB3aGVyZSBlYWNoIGFkZGl0aW9uYWwgY29sdW1uIG1heSBub3QgY29udHJpYnV0ZSBtdWNoIHRvIHRoZSBtb2RlbC4gVGhlIHRoaXJkIHByZS1wcm9jZXNzb3IsIGBwbHNfY2xhc3NpZmljYXRpb25fcmVjYCwgd29ya3MgYXMgYSBzb3J0IG9mIHN1cGVydmlzZWQgbGVhcm5pbmcgdmVyc2lvbiBvZiBwcmluY2lwYWwgY29tcG9uZW50cyBhbmFseXNpcywgY2FsbGVkIFtQYXJ0aWFsIExlYXN0IFNxdWFyZXNdKGh0dHBzOi8vd3d3LnRtd3Iub3JnL2RpbWVuc2lvbmFsaXR5Lmh0bWwjcGFydGlhbC1sZWFzdC1zcXVhcmVzKSwgdGhhdCBzaW11bHRhbmVvdXNseSBtYXhpbWl6ZSB0aGUgdmFyaWF0aW9uIGluIHRoZSBwcmVkaWN0b3JzIHdoaWxlIGFsc28gbWF4aW1pemluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhvc2UgY29tcG9uZW50cyBhbmQgdGhlIG91dGNvbWUuDQoNCmBgYHtyIGNsYXNzaWZpY2F0aW9uIHJlY2lwZXN9DQoNCmNsYXNzaWZpY2F0aW9uX3JlYyA8LQ0KICByZWNpcGUoDQogICAgZGVmYXVsdF9mbGFnIH4gTG9hbk5yX0Noa0RndCArIE5hbWUgKyBTZWN0b3IgKyBDaXR5ICsgU3RhdGUgKyBaaXAgKw0KICAgICAgQmFuayArIEJhbmtTdGF0ZSArIE5BSUNTICsgQXBwcm92YWxGWSArIE5vRW1wICsNCiAgICAgIE5ld0V4aXN0ICsgQ3JlYXRlSm9iICsgUmV0YWluZWRKb2IgKyBGcmFuY2hpc2VDb2RlICsgVXJiYW5SdXJhbCArDQogICAgICBEaXNidXJzZW1lbnRHcm9zcyArIEdyQXBwdiArIFNCQV9BcHB2ICsgTkFJQ1MyICsgTkFJQ1MzICsgTkFJQ1M0ICsNCiAgICAgIGxvYW5fcGVyX2VtcGxveWVlICsgU0JBX0FwcHZfdnNfR3Jvc3MgKyBHcm9zc19taW51c19TQkFfQXBwdiArDQogICAgICBHcm9zc19taW51c19TQkFfQXBwdl92c19FbXBsb3llZSArIHNhbWVfc3RhdGUsDQogICAgZGF0YSA9IHRyYWluaW5nDQogICkgJT4lDQogIHVwZGF0ZV9yb2xlKExvYW5Ocl9DaGtEZ3QsIG5ld19yb2xlID0gIklEIikgDQoNCmR1bW1pZXNfY2xhc3NpZmljYXRpb25fcmVjIDwtDQogIGNsYXNzaWZpY2F0aW9uX3JlYyAlPiUNCiAgc3RlcF9ub3ZlbCgNCiAgICBOYW1lLA0KICAgIFNlY3RvciwNCiAgICBDaXR5LA0KICAgIFN0YXRlLA0KICAgIFppcCwNCiAgICBCYW5rLA0KICAgIEJhbmtTdGF0ZSwNCiAgICBOQUlDUywNCiAgICBOQUlDUzIsDQogICAgTkFJQ1MzLA0KICAgIE5BSUNTNA0KICApICU+JQ0KICBzdGVwX290aGVyKA0KICAgIE5hbWUsDQogICAgU2VjdG9yLA0KICAgIENpdHksDQogICAgU3RhdGUsDQogICAgWmlwLA0KICAgIEJhbmssDQogICAgQmFua1N0YXRlLA0KICAgIE5BSUNTLA0KICAgIE5BSUNTMiwNCiAgICBOQUlDUzMsDQogICAgTkFJQ1M0LA0KICAgIHRocmVzaG9sZCA9IHR1bmUoKSkgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUNCiAgc3RlcF9pbnRlcmFjdCh+IHN0YXJ0c193aXRoKCJCYW5rIik6QXBwcm92YWxGWSkgJT4lICMgQWRkIGltcG9ydGFudCBpbnRlcmFjdGlvbnMNCiAgc3RlcF9vcmRlck5vcm0oYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUNCiAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSANCg0KcGxzX2NsYXNzaWZpY2F0aW9uX3JlYyA8LSBkdW1taWVzX2NsYXNzaWZpY2F0aW9uX3JlYyAlPiUNCiAgc3RlcF9wbHMoYWxsX251bWVyaWNfcHJlZGljdG9ycygpLA0KICAgICAgICAgICBudW1fY29tcCA9IHR1bmUoKSwNCiAgICAgICAgICAgb3V0Y29tZSA9ICJkZWZhdWx0X2ZsYWciKSAlPiUNCiAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSANCg0KYGBgDQoNCiMjIEZlYXR1cmUgRXh0cmFjdGlvbg0KDQpgYGB7ciBwbG90IHZhbGlkYXRpb24gcmVzdWx0cyBmdW5jdGlvbn0NCnBsb3RfdmFsaWRhdGlvbl9yZXN1bHRzIDwtIGZ1bmN0aW9uKHJlY2lwZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXQgPSB0cmFpbl9kZiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lID0gZGVmYXVsdF9mbGFnKSB7DQogIHJlY2lwZSAlPiUNCiAgICAjIEVzdGltYXRlIGFueSBhZGRpdGlvbmFsIHN0ZXBzDQogICAgcHJlcCgpICU+JQ0KICAgICMgUHJvY2VzcyB0aGUgZGF0YSAodGhlIHZhbGlkYXRpb24gc2V0IGJ5IGRlZmF1bHQpDQogICAgYmFrZShuZXdfZGF0YSA9IGRhdCkgJT4lDQogICAgZHBseXI6OnNlbGVjdCh7eyBvdXRjb21lIH19LCBzdGFydHNfd2l0aCgiUExTIikpICU+JSANCiAgICAjIENyZWF0ZSB0aGUgc2NhdHRlcnBsb3QgbWF0cml4DQogICAgZ2dwbG90KGFlcyh4ID0gLnBhbmVsX3gsIA0KICAgICAgICAgICAgICAgeSA9IC5wYW5lbF95LCANCiAgICAgICAgICAgICAgIGNvbCA9IHt7IG91dGNvbWUgfX0sIA0KICAgICAgICAgICAgICAgZmlsbCA9IHt7IG91dGNvbWUgfX0pKSArDQogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNCwgc2l6ZSA9IDAuNSkgKw0KICAgIGdlb21fYXV0b2RlbnNpdHkoYWxwaGEgPSAuMykgKw0KICAgIGZhY2V0X21hdHJpeCh2YXJzKC17eyBvdXRjb21lIH19KSwgbGF5ZXIuZGlhZyA9IDIpIA0KfQ0KYGBgDQoNCkxldCdzIGxvb2sgZm9yIHNlcGFyYXRpb24gb2YgdGhlIGNsYXNzZXMgaW4gdGhlIG5ldywgdHJhbnNmb3JtZWQgZmVhdHVyZXMgZnJvbSB0aGUgUExTIHByZXByb2Nlc3Nvci4gT3VyIG1vZGVsaW5nIHN5c3RlbSB3aWxsIGB0dW5lKClgIGJvdGggZm9yIHRoZSBudW1iZXIgb2YgZmFjdG9ycyB0byBiZSBsdW1wZWQgdG9nZXRoZXIgYW5kIGZvciB0aGUgbnVtYmVyIG9mIFBMUyBjb21wb25lbnRzLg0KDQpgYGB7ciBwbHMgZmVhdHVyZSBleHRyYWN0aW9uIHBsb3RzLCBvcHRzLkxhYmVsPSdmaWcubG9uZyd9DQpwbHNfY2xhc3NpZmljYXRpb25fcmVjICU+JQ0KICBmaW5hbGl6ZV9yZWNpcGUobGlzdChudW1fY29tcCA9IDQsDQogICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDAuMDEpKSAlPiUgDQogIHBsb3RfdmFsaWRhdGlvbl9yZXN1bHRzKCkgKw0KICBnZ3RpdGxlKCJQYXJ0aWFsIExlYXN0IFNxdWFyZXMiKQ0KDQpwbHNfY2xhc3NpZmljYXRpb25fcmVjICU+JSANCiAgICBmaW5hbGl6ZV9yZWNpcGUobGlzdChudW1fY29tcCA9IDQsDQogICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDAuMDEpKSAlPiUgDQogICAgcHJlcCgpICU+JSANCiAgICBsZWFybnRpZHltb2RlbHM6OnBsb3RfdG9wX2xvYWRpbmdzKGNvbXBvbmVudF9udW1iZXIgPD0gNCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA2LCB0eXBlID0gInBscyIpICsNCiAgZ2d0aXRsZSgiUGFydGlhbCBMZWFzdCBTcXVhcmVzIENvbXBvbmVudHMiKSArDQogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDAuOSkgKw0KICB0aGVtZV9qaW0oYmFzZV9zaXplID0gNykNCg0KYGBgDQoNCkVhY2ggY29tcG9uZW50IGNvbnNpc3RzIG9mIGNvbWJpbmF0aW9ucyBvZiB0aGUgb3JpZ2luYWwgZmVhdHVyZXMuIERlcGVuZGluZyB1cG9uIHRoZSB0aHJlc2hvbGQgZm9yIGx1bXBpbmcgY2F0ZWdvcmljYWwgbGV2ZWxzIHRvZ2V0aGVyLCB0aGUgUExTIHByZS1wcm9jZXNzaW5nIG1heSBoZWxwIHRvIHNlcGFyYXRlIHRoZSBzaWduYWwgZnJvbSB0aGUgbm9pc2UuIE9uIHRoZSBvdGhlciBoYW5kLCB3ZSB3aWxsIHVzZSBtYWNoaW5lIGxlYXJuaW5nIHRlY2huaXF1ZXMgdGhhdCBpbmNsdWRlIHJlZ3VsYXJpemF0aW9uLCBhbm90aGVyIHRlY2huaXF1ZSBmb3IgZGVhbGluZyB3aXRoIGhhdmluZyB0b28gbWFueSBub2lzeSB2YXJpYWJsZXMuDQoNClRoZSBzaXplIG9mIHRoZSBsb2FuIGFuZCBhcHByb3ZhbCB5ZWFyIGRyaXZlIHRoZSBmaXJzdCBQTFMgY29tcG9uZW50LiBPdGhlcnMgUExTIGNvbXBvbmVudHMgYXJlIGRyaXZlbiBieSBncm91cHMgb2Ygc3RhdGVzIGFuZCBhIGZldyBvZiB0aGUgYmFua3Mgd2hlcmUgdGhlIGxvYW4gd2FzIG9yaWdpbmF0ZWQuIA0KDQojIyMgQmFzZWxpbmUgTW9kZWwgU3BlY2lmaWNhdGlvbnMNCg0KT3VyIG1vZGVsaW5nIGVuZ2luZXMgd2lsbCBmaXJzdCBpbmNsdWRlIGEgW0dlbmVyYWxpemVkIExpbmVhciBNb2RlbCB3aXRoIFJlZ3VsYXJpemF0aW9uXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01ML3JlZ3VsYXJpemVkLXJlZ3Jlc3Npb24uaHRtbCkgdG8gY29uc3RyYWluIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgYW5kIGNvbnN0cmFpbiB0aGUgb3V0IG9mIHNhbXBsZSBlcnJvci4gVGhpcyBhcHByb2FjaCwgd2l0aCBMYXNzbyBhbmQgUmlkZ2UgcGVuYWx0eSBwYXJhbWV0ZXJzLCBpcyBzb21ldGltZXMgY2FsbGVkIEVsYXN0aWMgTmV0IG9yIEdMTU5FVC4NCg0KVGhlIHNlY29uZCBidWlsZHMgYSBkaXNjcmltaW5hbnQgYW5hbHlzaXMgbW9kZWwgdGhhdCB1c2VzIG5vbmxpbmVhciBmZWF0dXJlcyBjcmVhdGVkIHVzaW5nIG11bHRpdmFyaWF0ZSBhZGFwdGl2ZSByZWdyZXNzaW9uIHNwbGluZXMsIG9yIE1BUlMuIEl0IGlzIGEgbm9uLXBhcmFtZXRyaWMgcmVncmVzc2lvbiB0ZWNobmlxdWUgYW5kIGNhbiBiZSBzZWVuIGFzIGFuIGV4dGVuc2lvbiBvZiBsaW5lYXIgbW9kZWxzIHRoYXQgYXV0b21hdGljYWxseSBtb2RlbHMgbm9ubGluZWFyaXRpZXMgYW5kIGludGVyYWN0aW9ucyBiZXR3ZWVuIHZhcmlhYmxlcy4gVGhlIHRlcm0gIk1BUlMiIGlzIHRyYWRlbWFya2VkIGFuZCBsaWNlbnNlZCB0byBTYWxmb3JkIFN5c3RlbXMuIEluIG9yZGVyIHRvIGF2b2lkIHRyYWRlbWFyayBpbmZyaW5nZW1lbnRzLCBvcGVuLXNvdXJjZSBpbXBsZW1lbnRhdGlvbnMgb2YgTUFSUyBhcmUgY2FsbGVkICJFYXJ0aCIuDQoNClRoZSB0aGlyZCwgcmVndWxhcml6ZWQgZGlzY3JpbWluYW50IGFuYWx5c2lzLCBidWlsZHMgYSBtb2RlbCB0aGF0IGVzdGltYXRlcyBhIG11bHRpdmFyaWF0ZSBkaXN0cmlidXRpb24gZm9yIHRoZSBwcmVkaWN0b3JzIHNlcGFyYXRlbHkgZm9yIHRoZSBkYXRhIGluIGVhY2ggY2xhc3MuIA0KDQpgYGB7ciBiYXNlbGluZSBjbGFzc2lmaWNhdGlvbiBzcGVjc30NCmxvZ2lzdGljX3JlZ19nbG1fc3BlYyA8LQ0KICBsb2dpc3RpY19yZWcocGVuYWx0eSA9IHR1bmUoKSwNCiAgICAgICAgICAgICAgIG1peHR1cmUgPSB0dW5lKCkpICU+JQ0KICBzZXRfZW5naW5lKCdnbG1uZXQnKQ0KDQpmZGFfc3BlYyA8LQ0KICBkaXNjcmltX2ZsZXhpYmxlKHByb2RfZGVncmVlID0gdHVuZSgpKSAlPiUNCiAgc2V0X2VuZ2luZSgnZWFydGgnKQ0KDQpyZGFfc3BlYyA8LQ0KICBkaXNjcmltX3JlZ3VsYXJpemVkKGZyYWNfY29tbW9uX2NvdiA9IHR1bmUoKSwNCiAgICAgICAgICAgICAgICAgICAgICBmcmFjX2lkZW50aXR5ID0gdHVuZSgpKSAlPiUNCiAgc2V0X2VuZ2luZSgna2xhUicpDQoNCmBgYA0KDQojIyBHTE0gTW9kZWwgVHVuZQ0KDQpXZSBhcmUgZ29pbmcgdG8gcXVpY2tseSByYWNlIHRocm91Z2ggYSBncmlkIG9mIDYgcGFyYW1ldGVyIGNvbWJpbmF0aW9ucyB3aXRoIGB3b3JrZmxvd3NldHNgLiBJIHdpbGwgc2V0IHJlYXNvbmFibGUgcGFyYW1ldGVycyByYW5nZXMgaW4gYWR2YW5jZSBmb3IgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGx1bXBpbmcgYW5kIHRoZSBudW1iZXIgb2YgUExTIGNvbXBvbmVudHMuDQoNCmBgYHtyIHBhcmFsbGVsLCBldmFsID0gRkFMU0V9DQpjdHJsIDwtIGNvbnRyb2xfcmFjZShwYXJhbGxlbF9vdmVyID0gImV2ZXJ5dGhpbmciLA0KICAgICAgICAgICAgICAgICAgICAgc2F2ZV9wcmVkID0gVFJVRSkNCg0KYWxsX2NvcmVzIDwtIHBhcmFsbGVsbHk6OmF2YWlsYWJsZUNvcmVzKG9taXQgPSAxKQ0KYWxsX2NvcmVzDQoNCmZ1dHVyZTo6cGxhbigibXVsdGlzZXNzaW9uIiwgd29ya2VycyA9IGFsbF9jb3JlcykgIyBvbiBXaW5kb3dzDQpgYGANCg0KYGBge3IgYmFzZWxpbmUgd29ya2Zsb3dzZXRzfQ0Kd29ya2Zsb3dzIDwtICB3b3JrZmxvd19zZXQoDQogICAgcHJlcHJvYyA9IGxpc3QoZmFjdG9yID0gZHVtbWllc19jbGFzc2lmaWNhdGlvbl9yZWMsDQogICAgICAgICAgICAgICAgICAgcGxzID0gcGxzX2NsYXNzaWZpY2F0aW9uX3JlYyksIA0KICAgIG1vZGVscyA9IGxpc3QoZ2xtbmV0ID0gbG9naXN0aWNfcmVnX2dsbV9zcGVjLA0KICAgICAgICAgICAgICAgICAgZmRhID0gZmRhX3NwZWMsDQogICAgICAgICAgICAgICAgICByZGEgPSByZGFfc3BlYykNCiAgKSANCg0KZmFjdG9yX2dsbW5ldF9wYXJhbXMgPC0gd29ya2Zsb3dzICU+JSANCiAgZXh0cmFjdF93b3JrZmxvdygiZmFjdG9yX2dsbW5ldCIpICU+JSANCiAgcGFyYW1ldGVycygpICU+JSANCiAgdXBkYXRlKHBlbmFsdHkgPSBwZW5hbHR5KCksDQogICAgICAgICBtaXh0dXJlID0gbWl4dHVyZSgpLA0KICAgICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkKHJhbmdlID0gYygwLjEsIDAuMDA1KSkpDQoNCmZhY3Rvcl9mZGFfcGFyYW1zIDwtIHdvcmtmbG93cyAlPiUgDQogIGV4dHJhY3Rfd29ya2Zsb3coImZhY3Rvcl9mZGEiKSAlPiUgDQogIHBhcmFtZXRlcnMoKSAlPiUgDQogIHVwZGF0ZShwcm9kX2RlZ3JlZSA9IHByb2RfZGVncmVlKCksDQogICAgICAgICB0aHJlc2hvbGQgPSB0aHJlc2hvbGQocmFuZ2UgPSBjKDAuMSwgMC4wMDUpKSkNCg0KZmFjdG9yX3JkYV9wYXJhbXMgPC0gd29ya2Zsb3dzICU+JSANCiAgZXh0cmFjdF93b3JrZmxvdygiZmFjdG9yX3JkYSIpICU+JSANCiAgcGFyYW1ldGVycygpICU+JSANCiAgdXBkYXRlKGZyYWNfY29tbW9uX2NvdiA9IGZyYWNfY29tbW9uX2NvdigpLA0KICAgICAgICAgZnJhY19pZGVudGl0eSA9IGZyYWNfaWRlbnRpdHkoKSwNCiAgICAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZChyYW5nZSA9IGMoMC4xLCAwLjAwNSkpKQ0KDQpwbHNfZ2xtbmV0X3BhcmFtcyA8LSB3b3JrZmxvd3MgJT4lIA0KICBleHRyYWN0X3dvcmtmbG93KCJwbHNfZ2xtbmV0IikgJT4lIA0KICBwYXJhbWV0ZXJzKCkgJT4lIA0KICB1cGRhdGUocGVuYWx0eSA9IHBlbmFsdHkoKSwNCiAgICAgICAgIG1peHR1cmUgPSBtaXh0dXJlKCksDQogICAgICAgICB0aHJlc2hvbGQgPSB0aHJlc2hvbGQocmFuZ2UgPSBjKDAuMSwgMC4wMDUpKSwNCiAgICAgICAgIG51bV9jb21wID0gbnVtX2NvbXAocmFuZ2UgPSBjKDZMLDExTCkpKQ0KDQpwbHNfZmRhX3BhcmFtcyA8LSB3b3JrZmxvd3MgJT4lIA0KICBleHRyYWN0X3dvcmtmbG93KCJwbHNfZmRhIikgJT4lIA0KICBwYXJhbWV0ZXJzKCkgJT4lIA0KICB1cGRhdGUocHJvZF9kZWdyZWUgPSBwcm9kX2RlZ3JlZSgpLA0KICAgICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkKHJhbmdlID0gYygwLjEsIDAuMDA1KSksDQogICAgICAgICBudW1fY29tcCA9IG51bV9jb21wKHJhbmdlID0gYyg2TCwxMUwpKSkNCg0KcGxzX3JkYV9wYXJhbXMgPC0gd29ya2Zsb3dzICU+JSANCiAgZXh0cmFjdF93b3JrZmxvdygicGxzX3JkYSIpICU+JSANCiAgcGFyYW1ldGVycygpICU+JSANCiAgdXBkYXRlKGZyYWNfY29tbW9uX2NvdiA9IGZyYWNfY29tbW9uX2NvdigpLA0KICAgICAgICAgZnJhY19pZGVudGl0eSA9IGZyYWNfaWRlbnRpdHkoKSwNCiAgICAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZChyYW5nZSA9IGMoMC4xLCAwLjAwNSkpLA0KICAgICAgICAgbnVtX2NvbXAgPSBudW1fY29tcChyYW5nZSA9IGMoNkwsMTFMKSkpDQoNCndvcmtmbG93cyA8LSB3b3JrZmxvd3MgJT4lIA0KICBvcHRpb25fYWRkKHBhcmFtX2luZm8gPSBmYWN0b3JfZ2xtbmV0X3BhcmFtcywgaWQgPSAiZmFjdG9yX2dsbW5ldCIpICU+JQ0KICBvcHRpb25fYWRkKHBhcmFtX2luZm8gPSBmYWN0b3JfZmRhX3BhcmFtcywgaWQgPSAiZmFjdG9yX2ZkYSIpICU+JSANCiAgb3B0aW9uX2FkZChwYXJhbV9pbmZvID0gZmFjdG9yX3JkYV9wYXJhbXMsIGlkID0gImZhY3Rvcl9yZGEiKSAlPiUgDQogIG9wdGlvbl9hZGQocGFyYW1faW5mbyA9IHBsc19nbG1uZXRfcGFyYW1zLCBpZCA9ICJwbHNfZ2xtbmV0IikgJT4lDQogIG9wdGlvbl9hZGQocGFyYW1faW5mbyA9IHBsc19mZGFfcGFyYW1zLCBpZCA9ICJwbHNfZmRhIikgJT4lIA0KICBvcHRpb25fYWRkKHBhcmFtX2luZm8gPSBwbHNfcmRhX3BhcmFtcywgaWQgPSAicGxzX3JkYSIpIA0KDQpgYGANCg0KV2UgdGhlbiB0dW5lIGVhY2ggb2YgdGhlIG1vZGVscywgcnVubmluZyBhY3Jvc3MgYWxsIHZhbGlkYXRpb24gZm9sZHMgZm9yIGV2ZXJ5IGdyaWQgY29tYmluYXRpb24uIFdlIGFyZSB1c2luZyBhIHRpbWUtc2F2aW5nIHRlY2huaXF1ZSBoZXJlIHdpdGggYHR1bmVfcmFjZV9hbm92YSgpYCwgd2hlcmUgcG9vciBwZXJmb3JtaW5nIGNhbmRpZGF0ZXMgYXJlIGRyb3BwZWQuIEZvciA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiwgdGhlIHJlZHVjdGlvbiBpbiB0aW1lIGlzIGFzIG11Y2ggYXMgNDAlLiANCg0KYGBge3IgYmFzZWxpbmUgd29ya2Zsb3dzZXRzIHR1bmluZywgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KDQpkZWZhdWx0X3JlcyA8LSB3b3JrZmxvd3MgJT4lIA0KICB3b3JrZmxvd19tYXAoDQogICAgZm4gPSAidHVuZV9yYWNlX2Fub3ZhIiwNCiAgICB2ZXJib3NlID0gVFJVRSwNCiAgICBzZWVkID0gMjAyMSwNCiAgICByZXNhbXBsZXMgPSBjbGFzc19mb2xkcywNCiAgICBncmlkID0gMjEsDQogICAgY29udHJvbCA9IGN0cmwsDQogICAgbWV0cmljcyA9IG1ldHJpY19zZXQobW5fbG9nX2xvc3MpDQogICkNCg0KYGBgDQoNCmBgYHtyIGJhc2VsaW5lIHdvcmtmbG93c2V0cyB0dW5pbmcgbm9JbmNsdWRlLCBpbmNsdWRlPUZBTFNFfQ0KaWYgKGZpbGUuZXhpc3RzKGhlcmU6OmhlcmUoZGF0YV9kaXIsICJiYXNlbGluZXMucmRzIikpKSB7IA0KICANCmRlZmF1bHRfcmVzIDwtIHJlYWRfcmRzKGhlcmU6OmhlcmUoZGF0YV9kaXIsICJiYXNlbGluZXMucmRzIikpDQoNCn0gZWxzZSB7DQoNCmRlZmF1bHRfcmVzIDwtIHdvcmtmbG93cyAlPiUgDQogIHdvcmtmbG93X21hcCgNCiAgICBmbiA9ICJ0dW5lX3JhY2VfYW5vdmEiLA0KICAgIHZlcmJvc2UgPSBUUlVFLA0KICAgIHNlZWQgPSAyMDIxLA0KICAgIHJlc2FtcGxlcyA9IGNsYXNzX2ZvbGRzLA0KICAgIGdyaWQgPSAyMSwNCiAgICBjb250cm9sID0gY3RybCwNCiAgICBtZXRyaWNzID0gbWV0cmljX3NldChtbl9sb2dfbG9zcykNCiAgKQ0KDQp3cml0ZV9yZHMoZGVmYXVsdF9yZXMsIGhlcmU6OmhlcmUoZGF0YV9kaXIsICJiYXNlbGluZXMucmRzIikpDQoNCn0NCg0KYGBgDQoNCmBpIDEgb2YgNiB0dW5pbmc6ICAgICBmYWN0b3JfZ2xtbmV0YA0KDQpgdiAxIG9mIDYgdHVuaW5nOiAgICAgZmFjdG9yX2dsbW5ldCAoMWggMW0gMTUuOXMpYA0KDQpgaSAyIG9mIDYgdHVuaW5nOiAgICAgZmFjdG9yX2ZkYWANCg0KYHYgMiBvZiA2IHR1bmluZzogICAgIGZhY3Rvcl9mZGEgKDU3bSA1Ni43cylgDQoNCmBpIDMgb2YgNiB0dW5pbmc6ICAgICBmYWN0b3JfcmRhYA0KDQpgdiAzIG9mIDYgdHVuaW5nOiAgICAgZmFjdG9yX3JkYSAoMTNtIDEzLjlzKWANCg0KYGkgNCBvZiA2IHR1bmluZzogICAgIHBsc19nbG1uZXRgDQoNCmB2IDQgb2YgNiB0dW5pbmc6ICAgICBwbHNfZ2xtbmV0ICg5bSAxMC45cylgDQoNCmBpIDUgb2YgNiB0dW5pbmc6ICAgICBwbHNfZmRhYA0KDQpgdiA1IG9mIDYgdHVuaW5nOiAgICAgcGxzX2ZkYSAoMTBtIDE4LjNzKWANCg0KYGkgNiBvZiA2IHR1bmluZzogICAgIHBsc19yZGFgDQoNCmB2IDYgb2YgNiB0dW5pbmc6ICAgICBwbHNfcmRhICgxMW0gNDkuM3MpYA0KDQpgYGB7ciBiYXNlbGluZSB3b3JrZmxvd3NldHMgY29sbGVjdF9tZXRyaWNzfQ0KcmFua2luZ3MgPC0gDQogIHJhbmtfcmVzdWx0cyhkZWZhdWx0X3Jlcywgc2VsZWN0X2Jlc3QgPSBUUlVFKSAlPiUgDQogIG11dGF0ZShtZXRob2QgPSBtYXBfY2hyKHdmbG93X2lkLCB+IHN0cl9zcGxpdCgueCwgIl8iLCBzaW1wbGlmeSA9IFRSVUUpWzFdKSkgDQoNCmZpbHRlcihyYW5raW5ncywgcmFuayA8PSA1KSAlPiUgZHBseXI6OnNlbGVjdChyYW5rLCBtZWFuLCBtb2RlbCwgbWV0aG9kKQ0KDQpyYW5raW5ncyAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJhbmssIHkgPSBtZWFuLCBwY2ggPSBtZXRob2QsIGNvbCA9IG1vZGVsKSkgKyANCiAgZ2VvbV9wb2ludChjZXggPSAzKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArDQogIGxhYnMoeSA9ICJNZWFuIExvZyBMb3NzIikNCmBgYA0KDQpUaGUgYmVzdCBvZiB0aGUgYmVzdCB3ZXJlIHRoZSByZWd1bGFybGl6ZWQgYGdsbWAgd2l0aCB0aGUgc2ltcGxlIGR1bW15IHByZXByb2Nlc3Nvci4gV2UgY2FuIHZpc3VhbGl6ZSB0aGUgYmVuZWZpdCBvZiBgdHVuZV9yYWNlX2Fub3ZhKClgLCB3aGVyZSBvbmx5IHRoZSBiZXN0IGNvbWJpbmF0aW9ucyB3ZXJlIHJ1biB0aHJvdWdoIHRoZSBlbnRpcmUgdHVuaW5nIHJvdXRpbmUuDQoNCmBgYHtyIGJhc2VsaW5lIHdvcmtmbG93c2V0cyBwbG90X3JhY2V9DQpwbG90X3JhY2UoZGVmYXVsdF9yZXMgJT4lIGV4dHJhY3Rfd29ya2Zsb3dfc2V0X3Jlc3VsdCgiZmFjdG9yX2dsbW5ldCIpKQ0KYGBgDQoNCkxldCdzIGV4dHJhY3QgdGhlIGh5cGVycGFyYW1ldGVyIHNldHRpbmdzIGZvciB0aGF0IHRvcCBtb2RlbC4NCg0KYGBge3IgYmFzZWxpbmUgd29ya2Zsb3dzZXRzIHNlbGVjdCBiZXN0fQ0KKGJlc3RfcGFyYW1zIDwtIGRlZmF1bHRfcmVzICU+JSANCiAgZXh0cmFjdF93b3JrZmxvd19zZXRfcmVzdWx0KCJmYWN0b3JfZ2xtbmV0IikgJT4lIA0KICBzZWxlY3RfYmVzdCgpKQ0KDQpiZXN0X3dmIDwtIGRlZmF1bHRfcmVzICU+JSANCiAgZXh0cmFjdF93b3JrZmxvdygiZmFjdG9yX2dsbW5ldCIpICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9wYXJhbXMpDQoNCmBgYA0KDQpBbmQgdGFrZSBhIGNsb3NlIGxvb2sgYXQgdGhlIGNsYXNzaWZpY2F0aW9uIHBlcmZvcm1hbmNlIG9mIHRoYXQgYmVzdCBgZ2xtbmV0YCBtb2RlbCBvbiB0aGUgMTAlIHRlc3RpbmcgZGF0YSB0aGF0IHdhcyBoZWxkIG91dC4NCg0KYGBge3J9DQpsYXN0Rml0IDwtIGxhc3RfZml0KGJlc3Rfd2YsIHNwbGl0LCBtZXRyaWNzID0gbWV0cmljX3NldChtbl9sb2dfbG9zcykpDQoNCmNvbGxlY3RfbWV0cmljcyhsYXN0Rml0KSANCmBgYA0KDQpOb3RlIGhlcmUgdGhlIHRoZSBtZWFuIGxvZyBsb3NzIGNsYXNzaWZpY2F0aW9uIGVycm9yIGlzIGNvbXBhcmFibGUgdG8gdGhhdCBvZiB0aGUgY3Jvc3MgdmFsaWRhdGlvbiBmaWd1cmUgYWJvdmUsIHNvIHdlIGhhdmUgbW9yZSBjb25maWRlbmNlIHRoYXQgdGhlIG1vZGVsIHdvcmtzIG9uIHVuc2VlbiBkYXRhLiBMZXQncyBtb3ZlIGZvcndhcmQgdGhlbi4NCg0KIyMgRml0DQoNClJlY2FsbCB0aGF0IG91ciBvcmlnaW5hbCBkYXRhc2V0IHdhcyBpbWJhbGFuY2VkLiBUaGVyZSBhcmUgZmFyIG1vcmUgbm9uLWRlZmF1bHRzIHRoYW4gZGVmYXVsdHMuIEJlY2F1c2Ugb2YgdGhpcywgZGlhbGluZyBpbiB0aGUgY3V0b2ZmLCBiYWxhbmNpbmcgYmV0d2VlbiBzcGVjaWZpY2l0eSBhbmQgc2Vuc2l0aXZpdHksIHdhcnJhbnRzIG1vcmUgYXR0ZW50aW9uLiBMZXQncyBmaXQgdGhlIGJlc3QgbW9kZWwgb24gYWxsIG9mIHRoZSB0cmFpbmluZyBkYXRhIChvbWl0dGluZyB0aGUgY3Jvc3MgdmFsaWRhdGlvbiBmb2xkcykuDQoNCmBgYHtyIGZpdCB0aGUgZ2xtIHRvIHRyYWluaW5nfQ0KDQpnbG1fY2xhc3NpZmljYXRpb25fZml0IDwtIGZpdChiZXN0X3dmLCBkYXRhID0gdHJhaW5pbmcpDQoNCmBgYA0KDQpMZXQncyBzaW1wbHkgcHJlZGljdCB0aGUgcHJvYmFiaWxpdGllcyBvbiB0aGUgbGFiZWxlZCB0ZXN0aW5nIGRhdGEuDQoNCmBgYHtyIGxlbmRpbmcgdGVzdCBwcmVkaWN0aW9ufQ0KbGVuZGluZ190ZXN0X3ByZWQgPC0gZ2xtX2NsYXNzaWZpY2F0aW9uX2ZpdCAlPiUNCiAgICBwcmVkaWN0KG5ld19kYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIikgJT4lIA0KICAgIGJpbmRfY29scyh0ZXN0aW5nKQ0KYGBgDQoNCldpdGggb3VyIGNsYXNzIHByb2JhYmlsaXRpZXMgaW4gaGFuZCwgd2UgY2FuIHVzZSBgbWFrZV90d29fY2xhc3NfcHJlZCgpYCB0byBjb252ZXJ0IHRoZXNlIHByb2JhYmlsaXRpZXMgaW50byBoYXJkIHByZWRpY3Rpb25zIHVzaW5nIGEgdGhyZXNob2xkLiBBIHRocmVzaG9sZCBvZiAwLjUganVzdCBzYXlzIHRoYXQgaWYgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBpcyBhYm92ZSAwLjUsIHRoZW4gY2xhc3NpZnkgdGhpcyBwcmVkaWN0aW9uIGFzIGEg4oCcbm9uZGVmYXVsdOKAnSBsb2FuLCBvdGhlcndpc2UsIGRlZmF1bHQuDQoNCmBgYHtyIGZpZnR5IHRocmVzaG9sZH0NCmxlbmRpbmdfdGVzdF9wcmVkICU+JQ0KICBtdXRhdGUoLnByZWQgPSBtYWtlX3R3b19jbGFzc19wcmVkKC5wcmVkX25vbmRlZmF1bHQsIGxldmVscyhkZWZhdWx0X2ZsYWcpLCB0aHJlc2hvbGQgPSAuNSkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGRlZmF1bHRfZmxhZywgY29udGFpbnMoIi5wcmVkIikpICU+JSANCiAgY29uZl9tYXQodHJ1dGggPSBkZWZhdWx0X2ZsYWcsIGVzdGltYXRlID0gLnByZWQpICU+JSANCiAgYXV0b3Bsb3QoKSArDQogIGxhYnModGl0bGUgPSAiR0xNIENvbmZ1c2lvbiBNYXRyaXggYXQgMC41IEN1dG9mZiIpDQoNCmBgYA0KDQpXaXRoIGEgMC41IHRocmVzaG9sZCwgYWxtb3N0IGFsbCBvZiB0aGUgbG9hbnMgd2VyZSBwcmVkaWN0ZWQgYXMg4oCcbm9uZGVmYXVsdOKAnS4gUGVyaGFwcyB0aGlzIGhhcyBzb21ldGhpbmcgdG8gZG8gd2l0aCB0aGUgbGFyZ2UgY2xhc3MgaW1iYWxhbmNlLiBPbiB0aGUgb3RoZXIgaGFuZCwgdGhlIGJhbmsgbWlnaHQgd2FudCB0byBiZSBtb3JlIHN0cmluZ2VudCB3aXRoIHdoYXQgaXMgY2xhc3NpZmllZCBhcyBhIOKAnGdvb2TigJ0gbG9hbiwgYW5kIG1pZ2h0IHJlcXVpcmUgYSBwcm9iYWJpbGl0eSBvZiAwLjc1IGFzIHRoZSB0aHJlc2hvbGQuDQoNCmBgYHtyIHNldmVudHkgZml2ZSB0aHJlc2hvbGR9DQpsZW5kaW5nX3Rlc3RfcHJlZCAlPiUNCiAgbXV0YXRlKC5wcmVkID0gbWFrZV90d29fY2xhc3NfcHJlZCgucHJlZF9ub25kZWZhdWx0LCBsZXZlbHMoZGVmYXVsdF9mbGFnKSwgdGhyZXNob2xkID0gLjc1KSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoZGVmYXVsdF9mbGFnLCBjb250YWlucygiLnByZWQiKSkgJT4lIA0KICBjb25mX21hdCh0cnV0aCA9IGRlZmF1bHRfZmxhZywgZXN0aW1hdGUgPSAucHJlZCkgJT4lIA0KICBhdXRvcGxvdCgpICsNCiAgbGFicyh0aXRsZSA9ICJHTE0gQ29uZnVzaW9uIE1hdHJpeCBhdCAwLjc1IEN1dG9mZiIpDQpgYGANCg0KSW4gdGhpcyBjYXNlLCBtYW55IHRoZSBiYWQgbG9hbnMgd2VyZSBjb3JyZWN0bHkgY2xhc3NpZmllZCBhcyBiYWQsIGJ1dCBtb3JlIG9mIHRoZSBnb29kIGxvYW5zIHdlcmUgYWxzbyBtaXNjbGFzc2lmaWVkIGFzIGJhZCBub3cuIFRoZXJlIGlzIGEgdHJhZGVvZmYgaGVyZSwgd2hpY2ggY2FuIGJlIHNvbWV3aGF0IGNhcHR1cmVkIGJ5IHRoZSBtZXRyaWNzIGBzZW5zaXRpdml0eWAgYW5kIGBzcGVjaWZpY2l0eWAuDQoNCkluIHRoaXMgZXhhbXBsZSwgYXMgd2UgaW5jcmVhc2VkIHNwZWNpZmljaXR5LCB3ZSBsb3dlcmVkIHNlbnNpdGl2aXR5LiBJdCB3b3VsZCBiZSBuaWNlIHRvIGhhdmUgc29tZSBjb21iaW5hdGlvbiBvZiB0aGVzZSBtZXRyaWNzIHRvIHJlcHJlc2VudCB0aGlzIHRyYWRlb2ZmLiBMdWNraWx5LCBgal9pbmRleGAgaXMgZXhhY3RseSB0aGF0Lg0KDQpOb3csIHRoaXMgaXMgbm90IHRoZSBvbmx5IHdheSB0byBvcHRpbWl6ZSB0aGluZ3MuIElmIHlvdSBjYXJlIGFib3V0IGxvdyBmYWxzZSBwb3NpdGl2ZXMsIHlvdSBtaWdodCBiZSBtb3JlIGludGVyZXN0ZWQgaW4ga2VlcGluZyBzZW5zaXRpdml0eSBoaWdoLCBhbmQgdGhpcyB3b3VsZG7igJl0IGJlIHRoZSBiZXN0IHdheSB0byB0YWNrbGUgdGhpcyBwcm9ibGVtLiBUaGVyZSBhcmUgb3RoZXIgd2F5cyB0byB3ZWlnaCBlY29ub21pYyBjb25zZXF1ZW5jZXMuIEZvciBub3csIGxldOKAmXMgc2VlIGhvdyB3ZSBjYW4gdXNlIHByb2JhYmx5IHRvIG9wdGltaXplIHRoZSBgal9pbmRleGAuDQoNCldpdGggYGdncGxvdDJgLCB3ZSBjYW4gZWFzaWx5IHZpc3VhbGl6ZSB0aGlzIHZhcnlpbmcgcGVyZm9ybWFuY2UgdG8gZmluZCBvdXIgb3B0aW1hbCB0aHJlc2hvbGQgZm9yIG1heGltaXppbmcgYGpfaW5kZXhgLg0KDQpgYGB7ciBqX2luZGV4fQ0KdGhyZXNob2xkX2RhdGEgPC0gbGVuZGluZ190ZXN0X3ByZWQgJT4lDQogIHRocmVzaG9sZF9wZXJmKGRlZmF1bHRfZmxhZywgDQogICAgICAgICAgICAgICAgIC5wcmVkX25vbmRlZmF1bHQsIA0KICAgICAgICAgICAgICAgICB0aHJlc2hvbGRzID0gc2VxKDAuNSwgMSwgYnkgPSAwLjAwMjUpKSAlPiUNCiAgZmlsdGVyKC5tZXRyaWMgIT0gImRpc3RhbmNlIikgJT4lDQogIG11dGF0ZShncm91cCA9IGNhc2Vfd2hlbigNCiAgICAubWV0cmljID09ICJzZW5zIiB8IC5tZXRyaWMgPT0gInNwZWMiIH4gIjEiLA0KICAgIFRSVUUgfiAiMiINCiAgKSkNCg0KbWF4X2pfaW5kZXhfdGhyZXNob2xkIDwtIHRocmVzaG9sZF9kYXRhICU+JQ0KICBmaWx0ZXIoLm1ldHJpYyA9PSAial9pbmRleCIpICU+JQ0KICBmaWx0ZXIoLmVzdGltYXRlID09IG1heCguZXN0aW1hdGUpKSAlPiUNCiAgcHVsbCgudGhyZXNob2xkKQ0KDQpnZ3Bsb3QodGhyZXNob2xkX2RhdGEsDQogICAgICAgYWVzKA0KICAgICAgICAgeCA9IC50aHJlc2hvbGQsDQogICAgICAgICB5ID0gLmVzdGltYXRlLA0KICAgICAgICAgY29sb3IgPSAubWV0cmljLA0KICAgICAgICAgYWxwaGEgPSBncm91cA0KICAgICAgICkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gYyguNywgMSksIGd1aWRlID0gIm5vbmUiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IG1heF9qX2luZGV4X3RocmVzaG9sZCwNCiAgICAgICAgICAgICBhbHBoYSA9IC42LCBzaXplID0gMiwNCiAgICAgICAgICAgICBjb2xvciA9ICJncmV5MzAiKSArDQogIGxhYnMoDQogICAgeCA9ICInR29vZCcgVGhyZXNob2xkIChhYm92ZSB0aGlzIHZhbHVlIGlzIGNvbnNpZGVyZWQgJ25vbi1kZWZhdWx0JykiLA0KICAgIHkgPSAiTWV0cmljIEVzdGltYXRlIiwNCiAgICB0aXRsZSA9ICJCYWxhbmNpbmcgcGVyZm9ybWFuY2UgYnkgdmFyeWluZyB0aGUgdGhyZXNob2xkIiwNCiAgICBzdWJ0aXRsZSA9ICJTZW5zaXRpdml0eSBvciBzcGVjaWZpY2l0eSBhbG9uZSBtaWdodCBub3QgYmUgZW5vdWdoIVxuVmVydGljYWwgbGluZSA9IE1heCBKLUluZGV4Ig0KICApDQpgYGANCg0KRnJvbSB0aGlzIHZpc3VhbCwgdGhlIG9wdGltYWwgdGhyZXNob2xkIGlzIGV4YWN0bHkgYHIgbWF4X2pfaW5kZXhfdGhyZXNob2xkYC4gQXMgYW4gaW50ZXJlc3RpbmcgdmlldywgbGV0J3MgcnVuIHRoYXQgR0xNIG1vZGVsIG9uIGFsbCBvZiB0aGUgYHRyYWluX2RmYCBmaWxlIHdpdGggdGhlIG5ldyBjdXRvZmYuDQoNCmBgYHtyIGNvZnVzaW9uIG1hdHJpeH0NCmF1Z21lbnQoZ2xtX2NsYXNzaWZpY2F0aW9uX2ZpdCwgdHJhaW5fZGYsIHR5cGUgPSAicHJvYiIpICU+JSANCiAgIG11dGF0ZSgucHJlZCA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfbm9uZGVmYXVsdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzKGRlZmF1bHRfZmxhZyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlc2hvbGQgPSBtYXhfal9pbmRleF90aHJlc2hvbGQpKSAlPiUgDQogIGNvbmZfbWF0KHRydXRoID0gZGVmYXVsdF9mbGFnLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSAlPiUgDQogIGF1dG9wbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIkdMTSBDb25mdXNpb24gTWF0cml4IG9uIEVudGlyZSBUcmFpbmluZyBTZXQiLA0KICAgICAgIHN1YnRpdGxlID0gZ2x1ZTo6Z2x1ZSgieyBhdWdtZW50KGdsbV9jbGFzc2lmaWNhdGlvbl9maXQsIHRyYWluX2RmLCB0eXBlID0gJ3Byb2InKSAlPiUgbXV0YXRlKC5wcmVkID0gbWFrZV90d29fY2xhc3NfcHJlZCgucHJlZF9ub25kZWZhdWx0LCBsZXZlbHMoZGVmYXVsdF9mbGFnKSwgdGhyZXNob2xkID0gbWF4X2pfaW5kZXhfdGhyZXNob2xkKSkgJT4lICBhY2N1cmFjeSh0cnV0aCA9IGRlZmF1bHRfZmxhZywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgJT4lIHB1bGwoLmVzdGltYXRlKSAlPiUgcm91bmQoMikqMTAwIH0lICIsICJBY2N1cmFjeSB3aXRoIHJlZ3VsYXJpemVkIGdlbmVyYWwgbGluZWFyIG1vZGVsLlxuT25seSA0IGFjY3VyYWN5IHBvaW50cyBiZXR0ZXIgdGhhbiB0aGUgTlVMTCBtb2RlbC4iKSkNCg0KYGBgDQoNCkFmdGVyIGFsbCBvZiB0aGF0IHdvcmssIHRoZSByZXN1bHRzIGFyZSBkaXNhcHBvaW50aW5nLCBhcyB0aGUgYWNjdXJhY3kgaXMgbm90IG11Y2ggYmV0dGVyIHRoYW4gdGhlIE5VTEwgbW9kZWwuDQoNCmBgYHtyfQ0KZ2xtX2NsYXNzaWZpY2F0aW9uX2ZpdCAlPiUgDQogIGV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUgDQogIHZpcDo6dmlwKG1ldHJpYyA9ICJtbl9sb2dfbG9zcyIsIG51bV9mZWF0dXJlcyA9IDI1TCkgKw0KICBsYWJzKHRpdGxlID0gIlJlZ3VsYXJpemVkIEdMTSBWYXJpYWJsZSBJbXBvcnRhbmNlIFNjb3JlcyIpDQoNCmBgYA0KDQpMZXQncyB1c2UgdGhlIGluc2lnaHRzIGdhaW5lZCBhYm92ZSwgaW5jbHVkaW5nIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlcywgdG8gYnVpbGQgYSBiZXR0ZXIgYm9vc3RlZCBtb2RlbC4NCg0KIyMgQ2F0Ym9vc3QgTW9kZWwgVHVuZQ0KDQpbQ2F0Ym9vc3RdKGh0dHBzOi8vY2F0Ym9vc3QuYWkvKSBpcyBhIGhpZ2gtcGVyZm9ybWFuY2Ugb3BlbiBzb3VyY2UgbGlicmFyeSBmb3IgZ3JhZGllbnQgYm9vc3Rpbmcgb24gZGVjaXNpb24gdHJlZXMuIFRoZSBhbGdvcml0aG0gaGFzIGFkdmFudGFnZXMgaW4gYXV0b21hdGljYWxseSBoYW5kbGluZyBkZWVwIGNhdGVnb3JpY2FsIGFuZCBoaWVhcmFyY2hpZXMgb2YgY2F0ZWdvcmljYWwgZmVhdHVyZXMgYW5kIGxldmVyYWdpbmcgdGhlIHJlc2lkdWFscyBvZiB0aGUgd2VhayBsZWFybmVycyB0byBib29zdCBhZGRpdGlvbmFsIHJvdW5kcyBvZiBtb2RlbCB0cmFpbmluZy4gVGhlIGNhdGJvb3N0IHNwZWNpZmljYXRpb24gcmVxdWlyZXMgZml2ZSB0dW5pbmcgcGFyYW1ldGVycywgc28gaXQgb2Z0ZW4gdGFrZXMgbW9yZSB0aW1lIHRvIGZpbmQgb3B0aW1hbCBjb21iaW5hdGlvbnMuDQoNCmBgYHtyIGNhdGJvb3N0IGNsYXNzaWZpY2F0aW9uIHNwZWN9DQpjYXRib29zdF9jbGFzc2lmaWNhdGlvbl9zcGVjIDwtIGJvb3N0X3RyZWUoDQogIHRyZWVzID0gdHVuZSgpLA0KICBtdHJ5ID0gdHVuZSgpLA0KICBtaW5fbiA9IHR1bmUoKSwNCiAgbGVhcm5fcmF0ZSA9IHR1bmUoKSwNCiAgdHJlZV9kZXB0aCA9IHR1bmUoKQ0KKSAlPiUgDQpzZXRfZW5naW5lKCJjYXRib29zdCIpICU+JQ0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUgDQogIHN0ZXBfbm92ZWwoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKQ0KDQpgYGANCg0KR2l2ZW4gdGhlIHZhcmlhYmxlIGltcG9ydGFuY2Ugc2NvcmVzIGFib3ZlLCBsZXQncyBidWlsZCBhIG5ldyBwcmUtcHJvY2Vzc29yIGByZWNpcGUoKWAuDQoNCmBgYHtyfQ0KDQpjYXRib29zdF9jbGFzc19yZWMgPC0gcmVjaXBlKA0KICBkZWZhdWx0X2ZsYWcgfiBHckFwcHYgKyBTQkFfQXBwdiArIEFwcHJvdmFsRlkgKyBCYW5rICsgQmFua1N0YXRlICsNCiAgICBOYW1lICsgTkFJQ1MgKyBOQUlDUzIgKyBOQUlDUzMgKyBOQUlDUzQgKyBVcmJhblJ1cmFsICsgU3RhdGUgKyBDaXR5LA0KICAgIGRhdGEgPSB0cmFpbmluZw0KICAgICkgDQoNCmBgYA0KDQpMZXQncyBzZXQgYm91bmRhcmllcyBmb3IgdGhlIGh5cGVyLXBhcmFtZXRlciBzZWFyY2ggYW5kIGJ1aWxkIGEgZ3JpZCBvZiBwb3NzaWJsZSB2YWx1ZXMuIA0KDQpgYGB7ciBjYXRib29zdCBjbGFzc2lmaWNhdGlvbiB3Zn0NCmNhdGJvb3N0X2NsYXNzaWZpY2F0aW9uX3dmIDwtDQogIHdvcmtmbG93KCkgJT4lDQogIGFkZF9yZWNpcGUoY2F0Ym9vc3RfY2xhc3NfcmVjKSAlPiUNCiAgYWRkX21vZGVsKGNhdGJvb3N0X2NsYXNzaWZpY2F0aW9uX3NwZWMpDQoNCnNldC5zZWVkKDIwMjEpDQoNCihyYWNlX2dyaWQgPC0NCiAgIGdyaWRfbGF0aW5faHlwZXJjdWJlKA0KICAgICBmaW5hbGl6ZShtdHJ5KCksIGNhdGJvb3N0X2NsYXNzX3JlYyAlPiUgcHJlcCgpICU+JSBqdWljZSgpKSwNCiAgICAgdHJlZXMocmFuZ2UgPSBjKDUwMCwxNTAwKSksDQogICAgIGxlYXJuX3JhdGUocmFuZ2UgPSBjKC0yLCAtMSkpLA0KICAgICB0cmVlX2RlcHRoKHJhbmdlID0gYygzTCwgMTBMKSksDQogICAgIG1pbl9uKHJhbmdlID0gYygxMEwsIDQwTCkpLA0KICAgIHNpemUgPSA1DQogICApKQ0KDQpgYGANCg0KDQpgYGB7ciBmaXQgY2F0Ym9vc3QgY2xhc3NpZmljYXRpb24gdHVuZSBncmlkLCBldmFsID0gRkFMU0V9DQpjYXRib29zdF9jbGFzc2lmaWNhdGlvbl9ycyA8LSB0dW5lX3JhY2VfYW5vdmEoDQogICBjYXRib29zdF9jbGFzc2lmaWNhdGlvbl93ZiwNCiAgIHJlc2FtcGxlcyA9IGNsYXNzX2ZvbGRzLA0KICAgZ3JpZCA9IHJhY2VfZ3JpZCwNCiAgIG1ldHJpY3MgPSBtZXRyaWNfc2V0KG1uX2xvZ19sb3NzKSwNCiAgIGNvbnRyb2wgPSBjb250cm9sX3JhY2UodmVyYm9zZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmVfcHJlZCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmVfd29ya2Zsb3cgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBleHRyYWN0ID0gZXh0cmFjdF9tb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYWxsZWxfb3ZlciA9ICJyZXNhbXBsZXMiKQ0KICAgICkNCg0KICAgICAgICAgDQphdXRvcGxvdChjYXRib29zdF9jbGFzc2lmaWNhdGlvbl9ycykNCg0KY29sbGVjdF9tZXRyaWNzKGNhdGJvb3N0X2NsYXNzaWZpY2F0aW9uX3JzKSAlPiUNCiAgZHBseXI6OnNlbGVjdChtdHJ5LCANCiAgICAgICAgIHRyZWVzLCANCiAgICAgICAgIG1pbl9uLCANCiAgICAgICAgIHRyZWVfZGVwdGgsIA0KICAgICAgICAgbGVhcm5fcmF0ZSwgDQogICAgICAgICBtbl9sb2dfbG9zcyA9IG1lYW4pICU+JQ0KICBhcnJhbmdlKG1uX2xvZ19sb3NzKQ0KDQpgYGANCg0KYGBge3IgZml0IGNhdGJvb3N0IGNsYXNzaWZpY2F0aW9uIHR1bmUgZ3JpZCBub0luY2x1ZGUsIGluY2x1ZGUgPSBGQUxTRX0NCg0KaWYgKGZpbGUuZXhpc3RzKGhlcmU6OmhlcmUoZGF0YV9kaXIsICJjbGFzc2lmaWVyLnJkcyIpKSkgeyANCiAgDQpjYXRib29zdF9jbGFzc2lmaWNhdGlvbl9ycyA8LSByZWFkX3JkcyhoZXJlOjpoZXJlKGRhdGFfZGlyLCAiY2xhc3NpZmllci5yZHMiKSkNCg0KfSBlbHNlIHsNCg0KY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fcnMgPC0gdHVuZV9yYWNlX2Fub3ZhKA0KICAgY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fd2YsDQogICByZXNhbXBsZXMgPSBjbGFzc19mb2xkcywNCiAgIGdyaWQgPSByYWNlX2dyaWQsDQogICBtZXRyaWNzID0gbWV0cmljX3NldChtbl9sb2dfbG9zcyksDQogICBjb250cm9sID0gY29udHJvbF9yYWNlKHZlcmJvc2UgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzYXZlX3ByZWQgPSBUUlVFLCAgICMgd2lsbCBpdCB3b3JrIGFzIHdlbGwgdG8gbWFrZSBGQUxTRT8NCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZV93b3JrZmxvdyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGV4dHJhY3QgPSBleHRyYWN0X21vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbGxlbF9vdmVyID0gInJlc2FtcGxlcyIpDQogICAgKQ0KDQp3cml0ZV9yZHMoY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fcnMsIGhlcmU6OmhlcmUoZGF0YV9kaXIsICJjbGFzc2lmaWVyLnJkcyIpKQ0KDQp9DQoNCmF1dG9wbG90KGNhdGJvb3N0X2NsYXNzaWZpY2F0aW9uX3JzKQ0KDQpjb2xsZWN0X21ldHJpY3MoY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fcnMpICU+JSANCiAgZHBseXI6OnNlbGVjdChtdHJ5LCB0cmVlcywgbWluX24sIHRyZWVfZGVwdGgsIGxlYXJuX3JhdGUsIG1uX2xvZ19sb3NzID0gbWVhbikgJT4lIA0KICBhcnJhbmdlKG1uX2xvZ19sb3NzKQ0KDQpgYGANCg0KQWZ0ZXIgbW9kZWxpbmcgb24gNSByZXNhbXBsZSBzZXRzIGZvciBldmVyeSBjb21iaW5hdGlvbiBvZiBwYXJhbWV0ZXJzLCB3ZSBjb2xsZWN0IG1ldHJpY3Mgb24gdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2Ugb24gdGhlIHNhbXBsZXMgb2YgZGF0YSBoZWxkIG91dCBpbiBjcm9zcyB2YWxpZGF0aW9uLiAgVGhpcyBpcyBhIHN1YnN0YW50aWFsIGltcHJvdmVtZW50Lg0KDQojIyBQZXJmb3JtYW5jZSBDaGVja3Mgey5hY3RpdmV9DQoNCmBgYHtyIGNhdGJvb3N0IGNsYXNzaWZpY2F0aW9uIHBlcmZvcm1hbmNlIGNoZWNrfQ0KY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fYmVzdF93ZiA8LSAgIA0KICBjYXRib29zdF9jbGFzc2lmaWNhdGlvbl93ZiAlPiUgDQogIGZpbmFsaXplX3dvcmtmbG93KHNlbGVjdF9iZXN0KGNhdGJvb3N0X2NsYXNzaWZpY2F0aW9uX3JzKSkNCg0KY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fbGFzdF9maXQgPC0gDQogICAgICBjYXRib29zdF9jbGFzc2lmaWNhdGlvbl9iZXN0X3dmICU+JSANCiAgICAgIGxhc3RfZml0KHNwbGl0KQ0KDQpjb2xsZWN0X21ldHJpY3MoY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fbGFzdF9maXQpDQoNCmBgYA0KDQpUaGUgYWNjdXJhY3kgYW5kIGFyZWEgdW5kZXIgdGhlIHJvYyBjdXJ2ZSBhcmUgbW92aW5nIGluIHRoZSByaWdodCBkaXJlY3Rpb24sIGJ1dCA4MSUgaXMgbm90IG11Y2ggYmV0dGVyIHRoYW4gdGhlIDgwJSB0aGF0IHdlIGFjaGlldmVkIHdpdGggdGhlIEdMTSBQTFMgbW9kZWwgYWJvdmUuIA0KDQpgYGB7ciBjbGFzc2lmaWNhdGlvbiBjb25mdXNpb24gbWF0cml4IG9uIGZvbGQgaG9sZG91dHN9DQoNCmNvbGxlY3RfcHJlZGljdGlvbnMoY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fbGFzdF9maXQpICU+JQ0KICBjb25mX21hdChkZWZhdWx0X2ZsYWcsIC5wcmVkX2NsYXNzKSAlPiUgDQogIGF1dG9wbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIkNvbmZ1c2lvbiBNYXRyaXggb24gUmVzYW1wbGVzIEhlbGQgT3V0IikNCg0KYGBgDQoNCkhlcmUgYWdhaW4sIHRoZSBhbGdvcml0aG0gaXMgcHJlZGljdGluZyAibm9uZGVmYXVsdCIgd2hlbiBpdCBzaG91bGQgYmUgZmluZGluZyBkZWZhdWx0cy4gTGV0J3MgcHJlc3MgYWhlYWQgZXhwbG9yZSBhIG1vcmUgc3VpdGFibGUgal9pbmRleC4NCg0KYGBge3IgY2F0Ym9vc3Qgal9pbmRleH0NCmNsYXNzaWZpY2F0aW9uX2ZpdCA8LSBmaXQoY2F0Ym9vc3RfY2xhc3NpZmljYXRpb25fYmVzdF93ZiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbmluZykNCg0KbGVuZGluZ190ZXN0X3ByZWQgPC0gY2xhc3NpZmljYXRpb25fZml0ICU+JQ0KICAgIHByZWRpY3QobmV3X2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInByb2IiKSAlPiUgDQogICAgYmluZF9jb2xzKHRlc3RpbmcpDQoNCnRocmVzaG9sZF9kYXRhIDwtIGxlbmRpbmdfdGVzdF9wcmVkICU+JQ0KICB0aHJlc2hvbGRfcGVyZihkZWZhdWx0X2ZsYWcsIA0KICAgICAgICAgICAgICAgICAucHJlZF9ub25kZWZhdWx0LCANCiAgICAgICAgICAgICAgICAgdGhyZXNob2xkcyA9IHNlcSgwLjUsIDEsIGJ5ID0gMC4wMDI1KSkgJT4lDQogIGZpbHRlcigubWV0cmljICE9ICJkaXN0YW5jZSIpICU+JQ0KICBtdXRhdGUoZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgLm1ldHJpYyA9PSAic2VucyIgfCAubWV0cmljID09ICJzcGVjIiB+ICIxIiwNCiAgICBUUlVFIH4gIjIiDQogICkpDQoNCm1heF9qX2luZGV4X3RocmVzaG9sZCA8LSB0aHJlc2hvbGRfZGF0YSAlPiUNCiAgZmlsdGVyKC5tZXRyaWMgPT0gImpfaW5kZXgiKSAlPiUNCiAgZmlsdGVyKC5lc3RpbWF0ZSA9PSBtYXgoLmVzdGltYXRlKSkgJT4lDQogIHB1bGwoLnRocmVzaG9sZCkNCg0KZ2dwbG90KHRocmVzaG9sZF9kYXRhLA0KICAgICAgIGFlcygNCiAgICAgICAgIHggPSAudGhyZXNob2xkLA0KICAgICAgICAgeSA9IC5lc3RpbWF0ZSwNCiAgICAgICAgIGNvbG9yID0gLm1ldHJpYywNCiAgICAgICAgIGFscGhhID0gZ3JvdXANCiAgICAgICApKSArDQogIGdlb21fbGluZSgpICsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKGVuZCA9IDAuOSkgKw0KICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gYyguNCwgMSksIGd1aWRlID0gIm5vbmUiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IG1heF9qX2luZGV4X3RocmVzaG9sZCwNCiAgICAgICAgICAgICBhbHBoYSA9IC42LA0KICAgICAgICAgICAgIGNvbG9yID0gImdyZXkzMCIpICsNCiAgbGFicygNCiAgICB4ID0gIidHb29kJyBUaHJlc2hvbGRcbihhYm92ZSB0aGlzIHZhbHVlIGlzIGNvbnNpZGVyZWQgJ2dvb2QnKSIsDQogICAgeSA9ICJNZXRyaWMgRXN0aW1hdGUiLA0KICAgIHRpdGxlID0gIkJhbGFuY2luZyBwZXJmb3JtYW5jZSBieSB2YXJ5aW5nIHRoZSB0aHJlc2hvbGQiLA0KICAgIHN1YnRpdGxlID0gIlNlbnNpdGl2aXR5IG9yIHNwZWNpZmljaXR5IGFsb25lIG1pZ2h0IG5vdCBiZSBlbm91Z2ghXG5WZXJ0aWNhbCBsaW5lID0gTWF4IEotSW5kZXgiDQogICkNCmBgYA0KDQpGcm9tIHRoaXMgdmlzdWFsLCB0aGUgb3B0aW1hbCB0aHJlc2hvbGQgaXMgZXhhY3RseSBgciBtYXhfal9pbmRleF90aHJlc2hvbGRgLiBBcyBhbiBpbnRlcmVzdGluZyB2aWV3LCBsZXQncyBydW4gdGhhdCBHTE0gbW9kZWwgb24gYWxsIG9mIHRoZSBgdHJhaW5fZGZgIGZpbGUgd2l0aCB0aGUgbmV3IGN1dG9mZi4NCg0KYGBge3IgY2F0Ym9vc3QgY2xhc3NpZmllciBjb25mdXNpb24gbWF0cml4fQ0KYXVnbWVudChjbGFzc2lmaWNhdGlvbl9maXQsIHRyYWluX2RmLCB0eXBlID0gInByb2IiKSAlPiUgDQogICBtdXRhdGUoLnByZWQgPSBtYWtlX3R3b19jbGFzc19wcmVkKC5wcmVkX25vbmRlZmF1bHQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyhkZWZhdWx0X2ZsYWcpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyZXNob2xkID0gbWF4X2pfaW5kZXhfdGhyZXNob2xkKSkgJT4lIA0KICBjb25mX21hdCh0cnV0aCA9IGRlZmF1bHRfZmxhZywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgJT4lIA0KICBhdXRvcGxvdCgpICsNCiAgbGFicyh0aXRsZSA9ICJDYXRib29zdCBDb25mdXNpb24gTWF0cml4IG9uIEVudGlyZSBUcmFpbmluZyBTZXQiLA0KICAgICAgIHN1YnRpdGxlID0gZ2x1ZTo6Z2x1ZSgieyBhdWdtZW50KGNsYXNzaWZpY2F0aW9uX2ZpdCwgdHJhaW5fZGYsIHR5cGUgPSAncHJvYicpICU+JSBtdXRhdGUoLnByZWQgPSBtYWtlX3R3b19jbGFzc19wcmVkKC5wcmVkX25vbmRlZmF1bHQsIGxldmVscyhkZWZhdWx0X2ZsYWcpLCB0aHJlc2hvbGQgPSBtYXhfal9pbmRleF90aHJlc2hvbGQpKSAlPiUgIGFjY3VyYWN5KHRydXRoID0gZGVmYXVsdF9mbGFnLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSAlPiUgcHVsbCguZXN0aW1hdGUpICU+JSByb3VuZCgyKSoxMDAgfSUgIiwgIkFjY3VyYWN5IHdpdGggY2F0Ym9vc3QgY2xhc3NpZmljYXRpb24gbW9kZWwuXG5NdWNoIGJldHRlciB0aGFuIHRoZSBOVUxMIG1vZGVsLiIpKQ0KYGBgDQoNCkdvb2QgZW5vdWdoIHRvIG1vdmUgZm9yd2FyZC4gTG9hbnMgcHJlZGljdGVkIGluIHRoZSBjbGFzc2lmaWVyIHRvIGRlZmF1bHQgd2lsbCBiZSBwdXNoZWQgdGhyb3VnaCB0aGUgcmVncmVzc2lvbiBtb2RlbCBiZWxvdy4gDQoNCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KTGV0J3MgbG9vayBhdCB0aGUgZmVhdHVyZXMgdGhhdCBjb250cmlidXRlZCB0aGUgbW9zdCB0byB0aGlzIGNsYXNzaWZpY2F0aW9uIG1vZGVsLiANCg0KYGBge3IgY2xhc3NpZmllciB2YXJpYWJsZSBpbXBvcnRhbmNlfQ0KDQpjbGFzc2lmaWNhdGlvbl9maXQgJT4lIA0KICBleHRyYWN0X2ZpdF9lbmdpbmUoKSAlPiUgDQogIGNhdGJvb3N0LmdldF9mZWF0dXJlX2ltcG9ydGFuY2UocG9vbCA9IE5VTEwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAnRmVhdHVyZUltcG9ydGFuY2UnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVhZF9jb3VudCA9IC0xKSAlPiUgDQogIGFzX3RpYmJsZShyb3duYW1lcyA9ICJGZWF0dXJlIikgJT4lIA0KICBtdXRhdGUoRmVhdHVyZSA9IGZjdF9yZW9yZGVyKEZlYXR1cmUsIFYxKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKFYxLCBGZWF0dXJlKSkgKw0KICAgIGdlb21fY29sKCkgKw0KICBsYWJzKHRpdGxlID0gIkNhdGJvb3N0IFNCQSBEZWZhdWx0IENsYXNzaWZpY2F0aW9uIEZlYXR1cmUgSW1wb3J0YW5jZSIsDQogICAgICAgeSA9IE5VTEwsIHggPSBOVUxMKQ0KDQpgYGANCg0KVGhlIGludGVyYWN0aW9uIGZlYXR1cmUgdGhhdCBidWlsdCBieSBiaW5kaW5nIHRoZSBiYW5rIHRvIHRoZSBhcHByb3ZhbCB5ZWFyIGVuZGVkIHVwIGFzIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlLCBhbmQgdGhlIHNlY29uZCBtb3N0IGltcG9ydGFudCBpcyB0aGUgcmF0aW9uIGJldHdlZW4gU0JBX0FwcHJvdmFsIHZhbHVlIGFuZCB0aGUgR3Jvc3MgTG9hbiB2YWx1ZS4gDQoNCiMgey19DQoNCi0tLS0NCg0KIyBNYWNoaW5lIExlYXJuaW5nOiBSZWdyZXNzaW9uIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCmByIGVtb2ppZm9udDo6ZW1vamkoInBvaW50X3JpZ2h0IilgICBCdWlsZCBhIG5vbi1NTCBiYXNlbGluZSBmaXJzdC4NCg0KUmVjYWxsIHRoYXQgb3VyIGdvYWwgaXMgdG8gcHJlZGljdCB0aGUgYGRlZmF1bHRfYW1vdW50YC4gRm9yIG91ciBgTlVMTGAgbW9kZWwsIGlmIHdlIGFwcGx5IG91ciBjbGFzc2lmaWVyIGFib3ZlLCBhbmQgdGhlbiBzaW1wbHkgdGFrZSB0aGUgbWVhbiBvZiBhbGwgYGRlZmF1bHRfYW1vdW50c2AsIHRoZSBNZWFuIEF2ZXJhZ2UgRXJyb3IgaXM6DQoNCmBgYHtyfQ0KDQphdWdtZW50KGNsYXNzaWZpY2F0aW9uX2ZpdCwgdHJhaW5fZGYpICU+JSANCiAgbXV0YXRlKC5wcmVkX2RlZmF1bHRfYW1vdW50ID0gaWZfZWxzZShkZWZhdWx0X2ZsYWcgPT0gImRlZmF1bHQiLCBkZWZhdWx0X2Ftb3VudCwwKSkgJT4lICAgDQogIGZpbHRlcigucHJlZF9jbGFzcyA9PSAiZGVmYXVsdCIpICU+JSANCiAgbXV0YXRlKC5wcmVkX21lYW4gPSBtZWFuKC5wcmVkX2RlZmF1bHRfYW1vdW50KSkgJT4lIA0KICBtYWUodHJ1dGggPSBkZWZhdWx0X2Ftb3VudCwgZXN0aW1hdGUgPSAucHJlZF9tZWFuKSANCg0KYGBgDQoNCkFzIGFuIGFzaWRlOiB0aGUgYHRyYWluX2RmYCB3ZSB1c2VkIGFib3ZlIGNvbnRhaW5zIDUxIGxvYW5zIHdoZXJlIHRoZSBgZGVmYXVsdF9hbW91bnRgIHdhcyBncmVhdGVyIHRoYW4gdGhlIExvYW4gQXBwcm92YWwgYW1vdW50LiBNb3N0IG9mIHRoZW0gYXJlIHNtYWxsIHZhbHVlIGxvYW5zLCBmb3Igd2hpY2ggdGhlcmUgYXJlIHRob3VzYW5kcyBvZiBvdGhlcnMgaW4gdGhlIGRhdGFzZXQuIA0KDQojIyBFREENCg0KTGV0J3MgbWFrZSBtb3JlIGV4cGxvcmF0b3J5IHBsb3RzIHRvIGxlYXJuIHdoYXQgZmVhdHVyZXMgbWlnaHQgeWllbGQgZ29vZCBudW1lcmljIHByZWRpY3Rpb25zLg0KDQpgYGB7ciByZWdyZXNzaW9uIG51bWVyaWMgZWRhLCBvcHRzLkxhYmVsPSdmaWcubG9uZyd9DQp0cmFpbl9kZl9yZWdyZXNzaW9uIDwtIGF1Z21lbnQoY2xhc3NpZmljYXRpb25fZml0LCB0cmFpbl9kZikgJT4lIA0KICAgIGZpbHRlcigucHJlZF9jbGFzcyA9PSAiZGVmYXVsdCIpDQoNCnN1bW1hcml6ZV9kZWZhdWx0X2Ftb3VudHMgPC0gZnVuY3Rpb24odGJsKXsNCiAgdGJsICU+JSANCiAgc3VtbWFyaXplKG5fbG9hbnMgPSBuKCksDQogICAgICAgICAgICBtZWFuX2RlZmF1bHQgPSBtZWFuKGRlZmF1bHRfYW1vdW50KSwNCiAgICAgICAgICAgIHRvdGFsX2dyX2FwcHYgPSBzdW0oR3JBcHB2KSwNCiAgICAgICAgICAgIHRvdGFsX2RlZmF1bHRfYW1vdW50ID0gc3VtKGRlZmF1bHRfYW1vdW50ICksDQogICAgICAgICAgICBwY3RfZGVmYXVsdF9hbW91bnQgPSB0b3RhbF9kZWZhdWx0X2Ftb3VudCAvIHRvdGFsX2dyX2FwcHYsDQogICAgICAgICAgICAuZ3JvdXBzID0gImRyb3AiKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhuX2xvYW5zKSkNCn0NCg0KcGxvdF9jYXRlZ29yeV9hbW91bnRzIDwtIGZ1bmN0aW9uKHRibCwgY2F0ZWdvcnksIG5fY2F0ZWdvcmllcyA9IDcpew0KICB0YmwgJT4lIA0KICBncm91cF9ieSh7eyBjYXRlZ29yeSB9fSA6PSB3aXRoZnJlcShmY3RfbHVtcCh7eyBjYXRlZ29yeSB9fSwgbl9jYXRlZ29yaWVzKSkpICU+JSANCiAgc3VtbWFyaXplX2RlZmF1bHRfYW1vdW50cygpICU+JSANCiAgbXV0YXRlKHt7IGNhdGVnb3J5IH19IDo9IGZjdF9yZW9yZGVyKHt7IGNhdGVnb3J5IH19LCBtZWFuX2RlZmF1bHQpKSAlPiUgDQogIGdncGxvdChhZXMobWVhbl9kZWZhdWx0LCB7eyBjYXRlZ29yeSB9fSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBwY3RfZGVmYXVsdF9hbW91bnQsDQogICAgICAgICAgICAgICAgIHNpemUgPSBwY3RfZGVmYXVsdF9hbW91bnQpKSArDQogIGdlb21fdGV4dChhZXMoeCA9IG1lYW5fZGVmYXVsdCAtIDYwMDAsIA0KICAgICAgICAgICAgICAgIGxhYmVsID0gZ2x1ZTo6Z2x1ZSgiJCB7cm91bmQodG90YWxfZGVmYXVsdF9hbW91bnQvMTBeNiwwKX0gTSIpKSwNCiAgICAgICAgICAgIGhqdXN0ID0gMSkgKw0KICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYigNCiAgICAgIG5hbWUgPSAiJSBvZiBMb2FuIEFtb3VudFxuaW4gRGVmYXVsdCIsDQogICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSksDQogICAgICBicmVha3MgPSBzZXEoMCwxLDAuMSksDQogICAgICBvcHRpb24gPSAiSCINCiAgICApICsNCiAgICBzY2FsZV9zaXplX2Jpbm5lZCgNCiAgICAgIG5hbWUgPSAiJSBvZiBMb2FuXG5pbiBEZWZhdWx0IiwNCiAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSwNCiAgICAgIGJyZWFrcyA9IHNlcSgwLDEsMC4xKSwNCiAgICAgIGd1aWRlID0gIm5vbmUiDQogICAgKSArDQogICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfYmlucygpKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyX2Zvcm1hdCgpKSArDQogICAgZXhwYW5kX2xpbWl0cyh4ID0gMCwgc2l6ZSA9IDApICsNCiAgICBsYWJzKHggPSAiTWVhbiBWYWx1ZSBvZiB0aGUgRGVmYXVsdHMiLCB5ID0gTlVMTCwNCiAgICAgICAgIHRpdGxlID0gIlVTIFNtYWxsIEJ1c2luZXNzIEFkbWluaXN0cmF0aW9uIExvYW5zIHRoYXQgZGVmYXVsdGVkIDIwMDMtMjAxMCIsDQogICAgICAgICBzdWJ0aXRsZSA9ICIjIG9mIGxvYW5zIGluIHBhcmVudGhlc2lzIGFuZCBUb3RhbCBEZWZhdWx0ZWQgJCBvbiBMYWJlbHMiKQ0KICANCn0NCg0KDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKFN0YXRlKSArDQogICBsYWJzKHRpdGxlID0gIldoZW4gdGhleSBkbyBkZWZhdWx0LCBBcml6b25hIGhhcyB0aGUgaGlnaGVzdCBtZWFuIERlZmF1bHQgVmFsdWUiKQ0KDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKENpdHkpICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIFBob2VuaXggaGFzIHRoZSBoaWdoZXN0IG1lYW4gRGVmYXVsdCBWYWx1ZSIpDQoNCnRyYWluX2RmX3JlZ3Jlc3Npb24gJT4lIA0KICBwbG90X2NhdGVnb3J5X2Ftb3VudHMoU2VjdG9yKSArDQogICBsYWJzKHRpdGxlID0gIldoZW4gdGhleSBkbyBkZWZhdWx0LCBBY2NvbW9kYXRpb24gaGF2ZSB0aGUgaGlnaGVzdCBtZWFuIERlZmF1bHQgVmFsdWUiKQ0KDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKEJhbmspICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIFJlYWR5Q2FwIExlbmRpbmcgaGFzIHRoZSBoaWdoZXN0IG1lYW4gRGVmYXVsdCBWYWx1ZSIpDQoNCnRyYWluX2RmX3JlZ3Jlc3Npb24gJT4lIA0KICBwbG90X2NhdGVnb3J5X2Ftb3VudHMoTmV3RXhpc3QpICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIFVua25vd24gaGF2ZSB0aGUgaGlnaGVzdCBtZWFuIERlZmF1bHQgVmFsdWUiKQ0KDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKEZyYW5jaGlzZUNvZGUpICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIEZyYW5jaGlzZXMgaGF2ZSBtZWFuIERlZmF1bHQgVmFsdWUiKQ0KDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKE5BSUNTMikgKw0KICAgbGFicyh0aXRsZSA9ICJXaGVuIHRoZXkgZG8gZGVmYXVsdCwgTkFJQ1MgZ3JvdXAgNzIgaGF2ZSBtZWFuIERlZmF1bHQgVmFsdWUiKQ0KDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKE5BSUNTMykgKw0KICAgbGFicyh0aXRsZSA9ICJXaGVuIHRoZXkgZG8gZGVmYXVsdCwgTkFJQ1MgZ3JvdXAgNzIyIGhhdmUgbWVhbiBEZWZhdWx0IFZhbHVlIikNCg0KdHJhaW5fZGZfcmVncmVzc2lvbiAlPiUgDQogIHBsb3RfY2F0ZWdvcnlfYW1vdW50cyhOQUlDUzQpICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIE5BSUNTIGdyb3VwIDcyMjIgaGF2ZSBtZWFuIERlZmF1bHQgVmFsdWUiKQ0KICANCnRyYWluX2RmX3JlZ3Jlc3Npb24gJT4lIA0KICBwbG90X2NhdGVnb3J5X2Ftb3VudHMoTkFJQ1MpICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIE5BSUNTIGdyb3VwIDcyMjIxMSBoYXZlIG1lYW4gRGVmYXVsdCBWYWx1ZSIpDQoNCnRyYWluX2RmX3JlZ3Jlc3Npb24gJT4lIA0KICBwbG90X2NhdGVnb3J5X2Ftb3VudHMoVXJiYW5SdXJhbCkgKw0KICAgbGFicyh0aXRsZSA9ICJXaGVuIHRoZXkgZG8gZGVmYXVsdCwgVW5rbm93biBidXNpbmVzc2VzIGhhdmUgdGhlIGhpZ2hlc3QgbWVhbiBEZWZhdWx0IFZhbHVlIikNCiAgDQp0cmFpbl9kZl9yZWdyZXNzaW9uICU+JSANCiAgcGxvdF9jYXRlZ29yeV9hbW91bnRzKE5hbWUpICsNCiAgIGxhYnModGl0bGUgPSAiV2hlbiB0aGV5IGRvIGRlZmF1bHQsIERvbWlubydzIFBpenphIGhhdmUgdGhlIGhpZ2hlc3QgbWVhbiBEZWZhdWx0IFZhbHVlIikNCg0KdHJhaW5fZGZfcmVncmVzc2lvbiAlPiUgDQogICAgc2VsZWN0X2F0KGFsbF9vZih0cmFpbl9udW1lcmljKSkgJT4lDQogICAgY29yKCkgJT4lIA0KICAgIGhlYXRtYXAobWFpbiA9ICJOdW1lcmljIENvcnJlbGF0aW9ucyIpDQpgYGANCg0KQXMgd2l0aCB0aGUgY2xhc3NpZmljYXRpb24sIHRoZSByZWdyZXNzaW9uIHdpbGwgcmVseSBoZWF2aWx5IG9uIGNhdGVnb3JpY2FsIGZlYXR1cmVzLCBhbmQgc29tZSBvZiB0aGUgbnVtZXJpYyBmZWF0dXJlcyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBvbmUgYW5vdGhlci4NCg0KV2Ugd2lsbCB1c2UgNS1mb2xkIGNyb3NzIHZhbGlkYXRpb24gYW5kIHN0cmF0aWZ5IG9uIHRoZSBgZGVmYXVsdF9hbW91bnRgIHRoaXMgdGltZS4gIA0KDQpgYGB7ciByZWdyZXNzaW9uIGNyb3NzIHZhbGlkYXRpb259DQpzZXQuc2VlZCgyMDIxKQ0KDQpyZWdyZXNzaW9uX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoYXVnbWVudChjbGFzc2lmaWNhdGlvbl9maXQsIHRyYWluX2RmKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKC5wcmVkX2NsYXNzID09ICJkZWZhdWx0IikgLCANCiAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IDAuOSkNCg0KcmVncmVzc2lvbl90cmFpbmluZyA8LSB0cmFpbmluZyhzcGxpdCkNCnJlZ3Jlc3Npb25fdGVzdGluZyA8LSB0ZXN0aW5nKHNwbGl0KQ0KDQoocmVncmVzc2lvbl9mb2xkcyA8LQ0KICAgIHZmb2xkX2N2KA0KICAgICAgcmVncmVzc2lvbl90cmFpbmluZywNCiAgICAgIHYgPSA1LA0KICAgICAgc3RyYXRhID0gZGVmYXVsdF9hbW91bnQNCiAgICApKQ0KDQpgYGANCiMjIFRoZSBSZWNpcGUNCg0KRm9yIHRoZSByZWdyZXNzaW9uLCBvdXIgb3V0Y29tZSB2YXJpYWJsZSBpcyB0aGUgYGRlZmF1bHRfYW1vdW50YC4gQWxsIG90aGVyIGZlYXR1cmVzIGFyZSB0aGUgc2FtZSBhcyBhYm92ZS4NCg0KYGBge3IgcmVncmVzc2lvbiByZWNpcGV9DQpyZWdyZXNzaW9uX3JlYyA8LSByZWNpcGUoDQogICAgZGVmYXVsdF9hbW91bnQgfiBMb2FuTnJfQ2hrRGd0ICsgTmFtZSArIFNlY3RvciArIENpdHkgKyBTdGF0ZSArIA0KICAgICAgQmFuayArICBOQUlDUyArIEFwcHJvdmFsRlkgKyBOb0VtcCArDQogICAgICBOZXdFeGlzdCArIENyZWF0ZUpvYiArIFJldGFpbmVkSm9iICsgRnJhbmNoaXNlQ29kZSArIFVyYmFuUnVyYWwgKw0KICAgICAgRGlzYnVyc2VtZW50R3Jvc3MgKyBHckFwcHYgKyBTQkFfQXBwdiArIE5BSUNTMiArIE5BSUNTMyArIE5BSUNTNCArDQogICAgICBsb2FuX3Blcl9lbXBsb3llZSArIFNCQV9BcHB2X3ZzX0dyb3NzICsgR3Jvc3NfbWludXNfU0JBX0FwcHYgKw0KICAgICAgR3Jvc3NfbWludXNfU0JBX0FwcHZfdnNfRW1wbG95ZWUgKyBzYW1lX3N0YXRlICwNCiAgICBkYXRhID0gcmVncmVzc2lvbl90cmFpbmluZw0KICApICU+JQ0KICB1cGRhdGVfcm9sZShMb2FuTnJfQ2hrRGd0LCBuZXdfcm9sZSA9ICJJRCIpICU+JQ0KICBzdGVwX3p2KGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lIA0KICBzdGVwX25vdmVsKA0KICAgIE5hbWUsDQogICAgU2VjdG9yLA0KICAgIENpdHksDQogICAgU3RhdGUsDQogICAgQmFuaywNCiAgICBOQUlDUywNCiAgICBOQUlDUzIsDQogICAgTkFJQ1MzLA0KICAgIE5BSUNTNCApICU+JQ0KICBzdGVwX290aGVyKGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSwgdGhyZXNob2xkID0gdHVuZTo6dHVuZSgpKSAlPiUNCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JQ0KICBzdGVwX2ludGVyYWN0KH4gc3RhcnRzX3dpdGgoIkJhbmsiKTpBcHByb3ZhbEZZKSAlPiUgIyBBZGQgaW1wb3J0YW50IGludGVyYWN0aW9ucw0KICBzdGVwX29yZGVyTm9ybShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpICU+JQ0KICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpIA0KDQpgYGANCg0KIyMgTGluZWFyIFJlZ3Jlc3Npb24NCg0KYGBge3IgbGluZWFyIHJlZ3Jlc3Npb24gc3BlY2lmaWNhdGlvbn0NCmxpbmVhcl9yZWdfbG1fc3BlYyA8LQ0KICBsaW5lYXJfcmVnKHBlbmFsdHkgPSB0dW5lKCksIA0KICAgICAgICAgICAgIG1peHR1cmUgPSB0dW5lKCkpICU+JQ0KICBzZXRfZW5naW5lKCdnbG1uZXQnKQ0KDQpsaW5lYXJfcmVncmVzc2lvbl93ZiA8LQ0KICB3b3JrZmxvdygpICU+JQ0KICBhZGRfcmVjaXBlKHJlZ3Jlc3Npb25fcmVjKSAlPiUNCiAgYWRkX21vZGVsKGxpbmVhcl9yZWdfbG1fc3BlYykNCmBgYA0KDQpgYGB7ciBsaW5lYXIgcmVncmVzc2lvbiB0dW5lLCBldmFsID0gRkFMU0V9DQpsaW5lYXJfcmVncmVzc2lvbl9ycyA8LSB0dW5lX3JhY2VfYW5vdmEoDQogICBsaW5lYXJfcmVncmVzc2lvbl93ZiwNCiAgIHJlc2FtcGxlcyA9IHJlZ3Jlc3Npb25fZm9sZHMsDQogICBncmlkID0gMzAsDQogICBtZXRyaWNzID0gbXNldCwNCiAgIGNvbnRyb2wgPSBjb250cm9sX3JhY2UodmVyYm9zZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmVfcHJlZCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmVfd29ya2Zsb3cgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBleHRyYWN0ID0gZXh0cmFjdF9tb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIikNCiAgICApDQoNCmNvbGxlY3RfbWV0cmljcyhsaW5lYXJfcmVncmVzc2lvbl9ycykgJT4lIA0KICBhcnJhbmdlKG1lYW4pDQoNCmBgYA0KDQpgYGB7ciBsaW5lYXIgcmVncmVzc2lvbiB0dW5lIG5vSW5jbHVkZSwgaW5jbHVkZSA9IEZBTFNFfQ0KaWYgKGZpbGUuZXhpc3RzKGhlcmU6OmhlcmUoZGF0YV9kaXIsICJsaW5lYXJfcmVncmVzc29yLnJkcyIpKSkgew0KICANCmxpbmVhcl9yZWdyZXNzaW9uX3JzIDwtIHJlYWRfcmRzKGhlcmU6OmhlcmUoZGF0YV9kaXIsICJsaW5lYXJfcmVncmVzc29yLnJkcyIpKQ0KICANCn0gZWxzZSB7DQoNCmxpbmVhcl9yZWdyZXNzaW9uX3JzIDwtIHR1bmVfcmFjZV9hbm92YSgNCiAgIGxpbmVhcl9yZWdyZXNzaW9uX3dmLA0KICAgcmVzYW1wbGVzID0gcmVncmVzc2lvbl9mb2xkcywNCiAgIGdyaWQgPSAzMCwNCiAgIG1ldHJpY3MgPSBtc2V0LA0KICAgY29udHJvbCA9IGNvbnRyb2xfcmFjZSh2ZXJib3NlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZV9wcmVkID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZV93b3JrZmxvdyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGV4dHJhY3QgPSBleHRyYWN0X21vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbGxlbF9vdmVyID0gImV2ZXJ5dGhpbmciKQ0KICAgICkNCg0Kd3JpdGVfcmRzKGxpbmVhcl9yZWdyZXNzaW9uX3JzLCBoZXJlOjpoZXJlKCBkYXRhX2RpciwgImxpbmVhcl9yZWdyZXNzb3IucmRzIikpDQp9DQoNCmNvbGxlY3RfbWV0cmljcyhsaW5lYXJfcmVncmVzc2lvbl9ycykgJT4lIA0KICBhcnJhbmdlKG1lYW4pDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpsaW5lYXJfcmVncmVzc2lvbl9iZXN0X3dmIDwtICAgDQogIGxpbmVhcl9yZWdyZXNzaW9uX3dmICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coc2VsZWN0X2Jlc3QobGluZWFyX3JlZ3Jlc3Npb25fcnMpKQ0KDQpnbG1fcmVncmVzc2lvbl9maXQgPC0gZml0KGxpbmVhcl9yZWdyZXNzaW9uX2Jlc3Rfd2YsIGRhdGEgPSByZWdyZXNzaW9uX3RyYWluaW5nKQ0KDQpnbG1fcmVncmVzc2lvbl9maXQgJT4lIA0KICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIA0KICB2aXA6OnZpcChtZXRyaWMgPSAibWFlIiwgbnVtX2ZlYXR1cmVzID0gMjVMKSArDQogIGxhYnModGl0bGUgPSAiUmVndWxhcml6ZWQgR0xNIFZhcmlhYmxlIEltcG9ydGFuY2UgU2NvcmVzIikNCmBgYA0KDQpMaW5lYXIgcmVncmVzc2lvbiB5aWVsZHMgYW4gTWVhbiBBdmVyYWdlIEVycm9yIG9uIGNyb3NzIHZhbGlkYXRpb24gc2FtcGxlcyBoZWxkIG91dCB0aGF0IGlzIGFib3V0IDEvMyBsZXNzIHRoYW4gdGhlIE5VTEwgbW9kZWwuIA0KDQojIyBDYXRib29zdA0KDQpNdWNoIGxpa2UgYWJvdmUsIGJ1dCBub3cgZm9yIHJlZ3Jlc3Npb24uDQoNCmBgYHtyIGNhdGJvb3N0IHJlZ3Jlc3Npb24gc3BlY30NCg0KY2F0Ym9vc3RfcmVncmVzc2lvbl9yZWMgPC0gcmVjaXBlKA0KICAgIGRlZmF1bHRfYW1vdW50IH4gTG9hbk5yX0Noa0RndCArIE5hbWUgKyBTZWN0b3IgKyBDaXR5ICsgU3RhdGUgKyANCiAgICAgIEJhbmsgKyAgTkFJQ1MgKyBBcHByb3ZhbEZZICsgTm9FbXAgKw0KICAgICAgTmV3RXhpc3QgKyBDcmVhdGVKb2IgKyBSZXRhaW5lZEpvYiArIEZyYW5jaGlzZUNvZGUgKyBVcmJhblJ1cmFsICsNCiAgICAgIERpc2J1cnNlbWVudEdyb3NzICsgR3JBcHB2ICsgU0JBX0FwcHYgKyBOQUlDUzIgKyBOQUlDUzMgKyBOQUlDUzQgKw0KICAgICAgbG9hbl9wZXJfZW1wbG95ZWUgKyBTQkFfQXBwdl92c19Hcm9zcyArIEdyb3NzX21pbnVzX1NCQV9BcHB2ICsNCiAgICAgIEdyb3NzX21pbnVzX1NCQV9BcHB2X3ZzX0VtcGxveWVlICsgc2FtZV9zdGF0ZSAsDQogICAgZGF0YSA9IHJlZ3Jlc3Npb25fdHJhaW5pbmcNCiAgKSAlPiUNCiAgdXBkYXRlX3JvbGUoTG9hbk5yX0Noa0RndCwgbmV3X3JvbGUgPSAiSUQiKSAlPiUNCiAgc3RlcF96dihhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpIA0KDQpjYXRib29zdF9yZWdyZXNzaW9uX3NwZWMgPC0gYm9vc3RfdHJlZSh0cmVlcyA9IHR1bmUoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gdHVuZSgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9uID0gdHVuZSgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlYXJuX3JhdGUgPSAgdHVuZSgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVfZGVwdGggPSB0dW5lKCkpICU+JSANCiAgc2V0X2VuZ2luZSgiY2F0Ym9vc3QiKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KYGBgDQoNCmBgYHtyIGZpdCBjYXRib29zdCByZWdyZXNzaW9uIHdvcmtmbG93fQ0KY2F0Ym9vc3RfcmVncmVzc2lvbl93ZiA8LQ0KICB3b3JrZmxvdygpICU+JQ0KICBhZGRfcmVjaXBlKGNhdGJvb3N0X3JlZ3Jlc3Npb25fcmVjKSAlPiUNCiAgYWRkX21vZGVsKGNhdGJvb3N0X3JlZ3Jlc3Npb25fc3BlYykNCg0Kc2V0LnNlZWQoMjAyMSkNCnJhY2VfZ3JpZCA8LQ0KICAgZ3JpZF9sYXRpbl9oeXBlcmN1YmUoDQogICAgIGZpbmFsaXplKG10cnkoKSwgY2F0Ym9vc3RfcmVncmVzc2lvbl9yZWMgJT4lIHByZXAoKSAlPiUganVpY2UoKSksDQogICAgIHRyZWVzKHJhbmdlID0gYyg1MDAsMTgwMCkpLA0KICAgICBsZWFybl9yYXRlKHJhbmdlID0gYygtMiwgLTEpKSwNCiAgICAgdHJlZV9kZXB0aChyYW5nZSA9IGMoNkwsIDEyTCkpLA0KICAgICBtaW5fbihyYW5nZSA9IGMoMTBMLCA0NUwpKSwNCiAgICBzaXplID0gMTUNCiAgICkNCmBgYA0KIA0KYGBge3IgZml0IHR1bmVncmlkIHJlZ3Jlc3Npb24sIGV2YWwgPSBGQUxTRX0NCmNhdGJvb3N0X3JlZ3Jlc3Npb25fcnMgPC0gdHVuZV9yYWNlX2Fub3ZhKA0KICAgY2F0Ym9vc3RfcmVncmVzc2lvbl93ZiwNCiAgIHJlc2FtcGxlcyA9IHJlZ3Jlc3Npb25fZm9sZHMsDQogICBncmlkID0gcmFjZV9ncmlkLA0KICAgbWV0cmljcyA9IG1zZXQsDQogICBjb250cm9sID0gY29udHJvbF9yYWNlKHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYXZlX3ByZWQgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZV93b3JrZmxvdyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZXh0cmFjdCA9IGV4dHJhY3RfbW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIikNCiAgICApDQoNCmF1dG9wbG90KGNhdGJvb3N0X3JlZ3Jlc3Npb25fcnMpDQoNCmNvbGxlY3RfbWV0cmljcyhjYXRib29zdF9yZWdyZXNzaW9uX3JzKSAlPiUNCiAgc2VsZWN0KG10cnksIA0KICAgICAgICAgdHJlZXMsIA0KICAgICAgICAgbWluX24sIA0KICAgICAgICAgdHJlZV9kZXB0aCwgDQogICAgICAgICBsZWFybl9yYXRlLCANCiAgICAgICAgICJNQUUiID0gbWVhbikgJT4lDQogIGFycmFuZ2UoTUFFKQ0KYGBgDQoNCg0KYGBge3IgZml0IGNhdGJvb3N0IHJlZ3Jlc3Npb24gdHVuZSBncmlkIG5vSW5jbHVkZSwgaW5jbHVkZSA9IEZBTFNFfQ0KDQppZiAoZmlsZS5leGlzdHMoaGVyZTo6aGVyZShkYXRhX2RpciwgInJlZ3Jlc3Nvci5yZHMiKSkpIHsgDQogIA0KY2F0Ym9vc3RfcmVncmVzc2lvbl9ycyA8LSByZWFkX3JkcyhoZXJlOjpoZXJlKGRhdGFfZGlyLCAicmVncmVzc29yLnJkcyIpKQ0KDQp9IGVsc2Ugew0KDQpjYXRib29zdF9yZWdyZXNzaW9uX3JzIDwtIHR1bmVfcmFjZV9hbm92YSgNCiAgIGNhdGJvb3N0X3JlZ3Jlc3Npb25fd2YsDQogICByZXNhbXBsZXMgPSByZWdyZXNzaW9uX2ZvbGRzLA0KICAgZ3JpZCA9IHJhY2VfZ3JpZCwNCiAgIG1ldHJpY3MgPSBtc2V0LA0KICAgY29udHJvbCA9IGNvbnRyb2xfcmFjZSh2ZXJib3NlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZV9wcmVkID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmVfd29ya2Zsb3cgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBleHRyYWN0ID0gZXh0cmFjdF9tb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIikNCiAgICApDQoNCndyaXRlX3JkcyhjYXRib29zdF9yZWdyZXNzaW9uX3JzLCBoZXJlOjpoZXJlKGRhdGFfZGlyLCAicmVncmVzc29yLnJkcyIpKQ0KDQp9DQoNCmF1dG9wbG90KGNhdGJvb3N0X3JlZ3Jlc3Npb25fcnMpDQoNCmNvbGxlY3RfbWV0cmljcyhjYXRib29zdF9yZWdyZXNzaW9uX3JzKSAlPiUNCiAgc2VsZWN0KG10cnksIHRyZWVzLCBtaW5fbiwgdHJlZV9kZXB0aCwgbGVhcm5fcmF0ZSwgIk1BRSIgPSBtZWFuKSAlPiUNCiAgYXJyYW5nZShNQUUpDQoNCmBgYA0KDQpOb3QgYmFkLiBMZXQncyBtYWtlIGEgZmluYWwgZml0IGF0IHRoZXNlIGh5cGVycGFyYW1ldGVyIHNldHRpbmdzLg0KDQpgYGB7ciBmaW5hbCBmaXQgcmVncmVzc2lvbn0NCmNhdGJvb3N0X3JlZ3Jlc3Npb25fYmVzdF93ZiA8LSAgIA0KICBjYXRib29zdF9yZWdyZXNzaW9uX3dmICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coc2VsZWN0X2Jlc3QoY2F0Ym9vc3RfcmVncmVzc2lvbl9ycykpDQoNCnJlZ3Jlc3Npb25fZml0IDwtIGZpdChjYXRib29zdF9yZWdyZXNzaW9uX2Jlc3Rfd2YsIA0KICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBhdWdtZW50KGNsYXNzaWZpY2F0aW9uX2ZpdCwgdHJhaW5fZGYpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoLnByZWRfY2xhc3MgPT0gImRlZmF1bHQiKSkNCmBgYA0KDQojIyBQZXJmb3JtYW5jZSBDaGVja3Mgey5hY3RpdmV9DQoNCkZpcnN0LCBhIE1lYW4gQWJzb2x1dGUgRXJyb3Igb24gdGhlIGxhYmVsZWQgYHRlc3RpbmdgIGRhdGEgdGhhdCB3YXMgaGVsZCBvdXQ6DQoNCmBgYHtyIGZpbmFsIHBlcmZvcm1hbmNlIGV2YWwxfQ0KYXVnbWVudChyZWdyZXNzaW9uX2ZpdCwgYXVnbWVudChjbGFzc2lmaWNhdGlvbl9maXQsIHRlc3RpbmcpKSAlPiUgDQogIG11dGF0ZSgucHJlZCA9IGlmX2Vsc2UoZGVmYXVsdF9mbGFnID09ICJkZWZhdWx0IiwgLnByZWQsMCkpICU+JSANCiAgbWFlKHRydXRoID0gZGVmYXVsdF9hbW91bnQsIGVzdGltYXRlID0gLnByZWQpIA0KYGBgDQoNCk5leHQsIGEgTUFFIG9uIHRoZSBwcmVkaWN0aW9ucyBvbiBgdHJhaW5pbmdgLCB0byBhc3Nlc3Mgd2hldGhlciB3ZSBoYXZlIG92ZXItZml0Og0KDQpgYGB7ciBmaW5hbCBwZXJmb3JtYW5jZSBldmFsMn0NCmF1Z21lbnQocmVncmVzc2lvbl9maXQsIGF1Z21lbnQoY2xhc3NpZmljYXRpb25fZml0LCB0cmFpbmluZykpICU+JSANCiAgbXV0YXRlKC5wcmVkID0gaWZfZWxzZShkZWZhdWx0X2ZsYWcgPT0gImRlZmF1bHQiLCAucHJlZCwwKSkgJT4lIA0KICBtYWUodHJ1dGggPSBkZWZhdWx0X2Ftb3VudCwgZXN0aW1hdGUgPSAucHJlZCkgDQpgYGANCg0KWWVwLCB3ZSBkaWQuIEdpdmVuIG1vcmUgdGltZSwgaW5jcmVhc2luZyB0aGUgbnVtYmVyIG9mIGZvbGRzIGFuZCBhZGRpbmcgbW9yZSByZXBlYXRzIHdvdWxkIGhlbHAgcmVzb2x2ZSB0aGlzLiAgQSB2aXN1YWwgb2Ygb3VyIHByZWRpY3Rpb25zIG9mIGBkZWZhdWx0X2Ftb3VudHNgIHZlcnN1cyB0aGUgdHJ1dGg6DQoNCmBgYHtyIGZpbmFsIHBlcmZvcm1hbmNlIGV2YWwzfQ0KYXVnbWVudChyZWdyZXNzaW9uX2ZpdCwgYXVnbWVudChjbGFzc2lmaWNhdGlvbl9maXQsIHRyYWluX2RmKSkgJT4lDQogIG11dGF0ZSgucHJlZCA9IGlmX2Vsc2UoZGVmYXVsdF9mbGFnID09ICJkZWZhdWx0IiwgLnByZWQsIDApKSAlPiUgDQogIGdncGxvdChhZXMoZGVmYXVsdF9hbW91bnQsIC5wcmVkKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC4yLCBjb2xvciA9ICJibHVlIikgKw0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGFscGhhID0gMC41KSArDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsNCiAgbGFicyh0aXRsZSA9ICJDYXRib29zdCBNb2RlbCBQZXJmb3JtYW5jZSIsDQogICAgICAgeCA9ICJUcnV0aCIsDQogICAgICAgeSA9ICJQcmVkaWN0aW9uIikNCg0KYGBgDQoNCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KSSBsaWtlIHRvIGNvbWUgYmFjayB0byBlaXRoZXIgdGhlc2Ugc2NvcmVzLCBvciBwYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdHMsIHRvIGJlIGFibGUgdG8gdGVsbCB0aGUgc3Rvcnkgb2Ygd2hhdCB0aGUgbW9kZWwgaXMgZG9pbmcgYW5kIGFzayBxdWVzdGlvbnMgYWJvdXQgdGhlIGluY29taW5nIGRhdGEgc2FtcGxlLiBJZiwgZm9yIGV4YW1wbGUsIHRoZSBTQkEgY2hhbmdlcyBwb2xpY2llcyBsaWtlIHRoZSBBcHByb3ZhbCB0aHJlc2hvbGRzLCB3ZSB3b3VsZCBjZXJ0YWlubHkgZXhwZWN0IHRoZSBtb2RlbCB0byBtb3ZlIGRyYW1hdGlhbGx5Lg0KDQpgYGB7ciByZWdyZXNzaW9uIHZhcmlhYmxlIGltcG9ydGFuY2V9DQoNCnZpcF9yZWdyZXNzb3IgPC0gDQogIERBTEVYdHJhOjpleHBsYWluX3RpZHltb2RlbHMoDQogICAgcmVncmVzc2lvbl9maXQsDQogICAgZGF0YSA9IGF1Z21lbnQocmVncmVzc2lvbl9maXQsIGF1Z21lbnQoY2xhc3NpZmljYXRpb25fZml0LCB0cmFpbl9kZikpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZSgucHJlZCA9IGlmX2Vsc2UoZGVmYXVsdF9mbGFnID09ICJkZWZhdWx0IiwgLnByZWQsIDApKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWRlZmF1bHRfYW1vdW50KSwNCiAgICB5ID0gYXVnbWVudChyZWdyZXNzaW9uX2ZpdCwgYXVnbWVudChjbGFzc2lmaWNhdGlvbl9maXQsIHRyYWluX2RmKSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKC5wcmVkID0gaWZfZWxzZShkZWZhdWx0X2ZsYWcgPT0gImRlZmF1bHQiLCAucHJlZCwgMCkpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChkZWZhdWx0X2Ftb3VudCksDQogICAgbGFiZWwgPSAiQ2F0Ym9vc3QgUmVncmVzc2lvbiIsDQogICAgdmVyYm9zZSA9IEZBTFNFDQogICkgJT4lIA0KICBEQUxFWDo6bW9kZWxfcGFydHMoKQ0KDQpnZ3Bsb3RfaW1wKHZpcF9yZWdyZXNzb3IpICsNCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMS4yKSArDQogIGxhYnModGl0bGUgPSAiU0JBIERlZmF1bHQgQW1vdW50IFJlZ3Jlc3Npb24gRmVhdHVyZSBJbXBvcnRhbmNlIikNCg0KYGBgDQoNCiMgey19DQoNCldpdGggYWxsIG1vZGVscyBidWlsdCBhbmQgZml0LCB3ZSBjYW4gbm93IHBpcGVsaW5lIHRoZSB3aG9sZSBwcm9jZXNzIGZvciB0aGUgaG9sZG91dCBhbmQgbWFrZSB0aGUgS2FnZ2xlIHN1Ym1pc3Npb24uDQoNCg0KYGBge3IgcG9zdCBjc3YgeGdib29zdDIsIGV2YWwgPSBGQUxTRX0NCnNoZWxsKGdsdWU6OmdsdWUoJ2thZ2dsZSBjb21wZXRpdGlvbnMgc3VibWl0IC1jIHsgY29tcGV0aXRpb25fbmFtZSB9IC1mIHsgcGF0aF9leHBvcnQgfSAtbSAiQ2F0Ym9vc3Qgd2l0aCBhZHZhbmNlZCBwcmVwcm9jZXNzaW5nIG1vZGVsIDIiJykpDQpgYGANCg0KDQo=