A gentle introduction to classification
Classification is a form of machine learning in which you train a model to predict which category an item belongs to. Categorical data has distinct ‘classes’, rather than numeric values.
For example, a health clinic might use diagnostic data such as a
patient’s height, weight, blood pressure, blood-glucose level to predict
whether or not the patient is diabetic.

Classification is an example of a supervised machine learning technique, which means it relies on data that includes known feature values (for example, diagnostic measurements for patients) as well as known label
values (for example, a classification of non-diabetic or diabetic). A
classification algorithm is used to fit a subset of the data to a
function that can calculate the probability for each class
label from the feature values. The remaining data is used to evaluate
the model by comparing the predictions it generates from the features to
the known class labels.
The simplest form of classification is binary
classification, in which the label is 0 or 1, representing one of two
classes; for example, “True” or “False”; “Internal” or “External”;
“Profitable” or “Non-Profitable”; and so on.
The class prediction is made by determining the probability
for each possible class as a value between 0 -impossible - and 1 -
certain. The total probability for all classes is 1, as the patient is
definitely either diabetic or non-diabetic. So, if the predicted
probability of a patient being diabetic is 0.3, then there is a
corresponding probability of 0.7 that the patient is non-diabetic.
A threshold value, usually 0.5, is used to determine the predicted class - so if the positive
class (in this case, diabetic) has a predicted probability greater than
the threshold, then a classification of diabetic is predicted.
The best way to learn about classification is to try it for yourself, so that’s what you’ll do in this exercise.
We’ll require some packages to knock-off this module. You can have them installed as: install.packages(c('tidyverse', 'tidymodels', 'ranger', 'vip', 'palmerpenguins', 'skimr', 'paletteer', 'nnet', 'here'))
Alternatively, the script below checks whether you have the packages
required to complete this module and installs them for you in case some
are missing.
suppressWarnings(if(!require("pacman")) install.packages("pacman"))
## Loading required package: pacman
pacman::p_load('tidyverse', 'tidymodels', 'ranger',
'vip', 'skimr', 'here','palmerpenguins', 'kernlab',
'janitor', 'paletteer', 'nnet')
1. Binary classification
Let’s start by looking at an example of binary classification,
where the model must predict a label that belongs to one of two
classes. In this exercise, we’ll train a binary classifier to predict
whether or not a patient should be tested for diabetes based on some
medical data.
Explore the data
The first step in any machine learning project is to explore the data that you will use to train a model. And before we can explore the data, we must first have it in our environment, right?
So, let’s begin by importing a CSV file of patent data into a tibble (a modern a modern reimagining of the data frame):
Citation: The diabetes dataset used in this exercise
is based on data originally collected by the National Institute of
Diabetes and Digestive and Kidney Diseases.
# Load the core tidyverse and make it available in your current R session
library(tidyverse)
# Read the csv file into a tibble
diabetes <- read_csv(file = "https://raw.githubusercontent.com/MicrosoftDocs/ml-basics/master/data/diabetes.csv")
# Print the first 10 rows of the data
diabetes %>%
slice_head(n = 10)
| | | | | |
|---|
| 1354778 | 0 | 171 | 80 | 34 | |
| 1147438 | 8 | 92 | 93 | 47 | |
| 1640031 | 7 | 115 | 47 | 52 | |
| 1883350 | 9 | 103 | 78 | 25 | |
| 1424119 | 1 | 85 | 59 | 27 | |
| 1619297 | 0 | 82 | 92 | 9 | |
| 1660149 | 0 | 133 | 47 | 19 | |
| 1458769 | 0 | 67 | 87 | 43 | |
| 1201647 | 8 | 80 | 95 | 33 | |
| 1403912 | 1 | 72 | 31 | 40 | |
Sometimes, we may want some little more information on our data. We can have a look at the data, its structure and the data type of its features by using the glimpse() function as below:
# Take a quick glance at the data
diabetes %>%
glimpse()
## Rows: 15,000
## Columns: 10
## $ PatientID <dbl> 1354778, 1147438, 1640031, 1883350, 1424119, 16~
## $ Pregnancies <dbl> 0, 8, 7, 9, 1, 0, 0, 0, 8, 1, 1, 3, 5, 7, 0, 3,~
## $ PlasmaGlucose <dbl> 171, 92, 115, 103, 85, 82, 133, 67, 80, 72, 88,~
## $ DiastolicBloodPressure <dbl> 80, 93, 47, 78, 59, 92, 47, 87, 95, 31, 86, 96,~
## $ TricepsThickness <dbl> 34, 47, 52, 25, 27, 9, 19, 43, 33, 40, 11, 31, ~
## $ SerumInsulin <dbl> 23, 36, 35, 304, 35, 253, 227, 36, 24, 42, 58, ~
## $ BMI <dbl> 43.50973, 21.24058, 41.51152, 29.58219, 42.6045~
## $ DiabetesPedigree <dbl> 1.21319135, 0.15836498, 0.07901857, 1.28286985,~
## $ Age <dbl> 21, 23, 23, 43, 22, 26, 21, 26, 53, 26, 22, 23,~
## $ Diabetic <dbl> 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1,~
This data consists of diagnostic information about some patients who
have been tested for diabetes. Note that the final column in the dataset
(Diabetic) contains the value 0 for patients who tested negative for diabetes, and 1 for patients who tested positive. This is the label that we will train our model to predict; most of the other columns (Pregnancies, PlasmaGlucose, DiastolicBloodPressure, BMI and so on) are the features we will use to predict the Diabetic label.
Let’s kick off our adventure by reformatting the data to make it
easier for a model to use effectively. For now, let’s drop the PatientID
column, encode the Diabetic column as a categorical variable, and make
the column names a bit friend_lieR:
# Load the janitor package for cleaning data
library(janitor)
# Clean data a bit
diabetes_select <- diabetes %>%
# Encode Diabetic as category
mutate(Diabetic = factor(Diabetic, levels = c("1","0"))) %>%
# Drop PatientID column
select(-PatientID) %>%
# Clean column names
clean_names()
# View data set
diabetes_select %>%
slice_head(n = 10)
| | | | |
|---|
| 0 | 171 | 80 | 34 | |
| 8 | 92 | 93 | 47 | |
| 7 | 115 | 47 | 52 | |
| 9 | 103 | 78 | 25 | |
| 1 | 85 | 59 | 27 | |
| 0 | 82 | 92 | 9 | |
| 0 | 133 | 47 | 19 | |
| 0 | 67 | 87 | 43 | |
| 8 | 80 | 95 | 33 | |
| 1 | 72 | 31 | 40 | |
The goal of this exploration is to try to understand the relationships between its attributes; in particular, any apparent correlation between the features and the label your model will try to predict. One way of doing this is by using data visualization.
Now let’s compare the feature distributions for each label value. We’ll begin by formatting the data to a long format to make it somewhat easier to make multiple facets.
# Pivot data to a long format
diabetes_select_long <- diabetes_select %>%
pivot_longer(!diabetic, names_to = "features", values_to = "values")
# Print the first 10 rows
diabetes_select_long %>%
slice_head(n = 10)
| | | | |
|---|
| 0 | pregnancies | 0.000000 | | |
| 0 | plasma_glucose | 171.000000 | | |
| 0 | diastolic_blood_pressure | 80.000000 | | |
| 0 | triceps_thickness | 34.000000 | | |
| 0 | serum_insulin | 23.000000 | | |
| 0 | bmi | 43.509726 | | |
| 0 | diabetes_pedigree | 1.213191 | | |
| 0 | age | 21.000000 | | |
| 0 | pregnancies | 8.000000 | | |
| 0 | plasma_glucose | 92.000000 | | |
Perfect! Now, let’s make some plots.
theme_set(theme_light())
# Make a box plot for each predictor feature
diabetes_select_long %>%
ggplot(mapping = aes(x = diabetic, y = values, fill = features)) +
geom_boxplot() +
facet_wrap(~ features, scales = "free", ncol = 4) +
scale_color_viridis_d(option = "plasma", end = .7) +
theme(legend.position = "none")

Amazing🤩! For some of the features, there’s a noticeable difference in the distribution for each label value. In particular, Pregnancies and Age
show markedly different distributions for diabetic patients than for
non-diabetic patients. These features may help predict whether or not a
patient is diabetic.
Split the data
Our dataset includes known values for the label, so we can use this
to train a classifier so that it finds a statistical relationship
between the features and the label value; but how will we know if our
model is any good? How do we know it will predict correctly when we use
it with new data that it wasn’t trained with?
It is best practice to hold out some of your data for testing
in order to get a better estimate of how your models will perform on
new data by comparing the predicted labels with the already known labels
in the test set.
Well, we can take advantage of the fact we have a large dataset with
known label values, use only some of it to train the model, and hold
back some to test the trained model - enabling us to compare the
predicted labels with the already known labels in the test set.
In R, the amazing Tidymodels framework provides a collection of packages for modeling and machine learning using tidyverse principles. For instance, rsample, a package in Tidymodels, provides infrastructure for efficient data splitting and resampling:
use ?initial_split() for more details.
Here is a great place to get started with Tidymodels: Get Started
# Load the Tidymodels packages
library(tidymodels)
# Split data into 70% for training and 30% for testing
set.seed(2056)
diabetes_split <- diabetes_select %>%
initial_split(prop = 0.70)
# Extract the data in each split
diabetes_train <- training(diabetes_split)
diabetes_test <- testing(diabetes_split)
# Print the number of cases in each split
cat("Training cases: ", nrow(diabetes_train), "\n",
"Test cases: ", nrow(diabetes_test), sep = "")
## Training cases: 10500
## Test cases: 4500
# Print out the first 5 rows of the training set
diabetes_train %>%
slice_head(n = 5)
| | | | |
|---|
| 0 | 84 | 101 | 9 | |
| 1 | 157 | 56 | 11 | |
| 0 | 102 | 56 | 15 | |
| 1 | 81 | 90 | 10 | |
| 8 | 97 | 65 | 24 | |
Train and Evaluate a Binary Classification Model
OK, now we’re ready to train our model by fitting the training features to the training labels (diabetic). There are various algorithms we can use to train the model. In this example, we’ll use Logistic Regression,
which (despite its name) is a well-established algorithm for
classification. Logistic regression is a binary classification
algorithm, meaning it predicts 2 categories.
There are quite a number of ways to fit a logistic regression model in Tidymodels. See ?logistic_reg() For now, let’s fit a logistic regression model via the default stats::glm() engine.
# Make a model specifcation
logreg_spec <- logistic_reg() %>%
set_engine("glm") %>%
set_mode("classification")
# Print the model specification
logreg_spec
## Logistic Regression Model Specification (classification)
##
## Computational engine: glm
After a model has been specified, the model can be estimated or trained using the fit() function, typically using a symbolic description of the model (a formula) and some data.
# Train a logistic regression model
logreg_fit <- logreg_spec %>%
fit(diabetic ~ ., data = diabetes_train)
# Print the model object
logreg_fit
## parsnip model object
##
## Fit time: 51ms
##
## Call: stats::glm(formula = diabetic ~ ., family = stats::binomial,
## data = data)
##
## Coefficients:
## (Intercept) pregnancies plasma_glucose
## 8.624243 -0.266296 -0.009615
## diastolic_blood_pressure triceps_thickness serum_insulin
## -0.012297 -0.022807 -0.003932
## bmi diabetes_pedigree age
## -0.049028 -0.923262 -0.056876
##
## Degrees of Freedom: 10499 Total (i.e. Null); 10491 Residual
## Null Deviance: 13290
## Residual Deviance: 9221 AIC: 9239
The model print out shows the coefficients learned during training.
Now we’ve trained the model using the training data, we can use the
test data we held back to evaluate how well it predicts using parsnip::predict(). Let’s start by using the model to predict labels for our test set, and compare the predicted labels to the known labels:
# Make predictions then bind them to the test set
results <- diabetes_test %>% select(diabetic) %>%
bind_cols(logreg_fit %>% predict(new_data = diabetes_test))
# Compare predictions
results %>%
slice_head(n = 10)
Comparing each prediction with its corresponding “ground truth”
actual value isn’t a very efficient way to determine how well the model
is predicting. Fortunately, Tidymodels has a few more tricks up its
sleeve: yardstick - a package used to measure the effectiveness of models using performance metrics.
The most obvious thing you might want to do is to check the accuracy of the predictions - in simple terms, what proportion of the labels did the model predict correctly?
yardstick::accuracy() does just that!
# Calculate accuracy: proportion of data predicted correctly
accuracy(data = results, truth = diabetic, estimate = .pred_class)
The accuracy is returned as a decimal value - a value of 1.0 would
mean that the model got 100% of the predictions right; while an accuracy
of 0.0 is, well, pretty useless 😐!
Accuracy seems like a sensible metric to evaluate (and to a certain
extent it is), but you need to be careful about drawing too many
conclusions from the accuracy of a classifier. Remember that it’s simply
a measure of how many cases were predicted correctly. Suppose only 3%
of the population is diabetic. You could create a classifier that always
just predicts 0, and it would be 97% accurate - but not terribly
helpful in identifying patients with diabetes!
Fortunately, there are some other metrics that reveal a little more about how our classification model is performing.
One performance metric associated with classification problems is the confusion matrix.
A confusion matrix describes how well a classification model performs
by tabulating how many examples in each class were correctly classified
by a model. In our case, it will show you how many cases were classified
as negative (0) and how many as positive (1); the confusion matrix also
shows you how many were classified into the wrong categories.
The conf_mat() function from yardstick calculates this cross-tabulation of observed and predicted classes.
# Confusion matrix for prediction results
conf_mat(data = results, truth = diabetic, estimate = .pred_class)
## Truth
## Prediction 1 0
## 1 897 293
## 0 657 2653
Awesome!
Let’s interpret the confusion matrix. Our model is asked to classify cases between two binary categories, category 1 for patients who tested positive for diabetes and category 0 for patients who tested negative.
If your model predicts a patient as 1 (positive) and they belong to category 1 (positive) in reality we call this a true positive, shown by the top left number 897.
If your model predicts a patient as 0 (negative) and they belong to category 1 (positive) in reality we call this a false negative, shown by the bottom left number 657.
If your model predicts a patient as 1 (positive) and they belong to category 0 (negative) in reality we call this a false positive, shown by the top right number 293.
If your model predicts a patient as 0 (negative) and they belong to category 0 (negative) in reality we call this a true negative, shown by the bottom right number 2653.
Our confusion matrix can thus be expressed in the following form:
| Predicted |
1 |
0 |
| 1 |
897 TP |
293 FP |
| 0 |
657 FN |
2653 TN |
Note that the correct (true) predictions form a diagonal line from top left to bottom right - these figures should be significantly higher than the false predictions if the model is any good.
As you might have guessed it’s preferable to have a larger number of
true positives and true negatives and a lower number of false positives
and false negatives, which implies that the model performs better.
The confusion matrix is helpful since it gives rise to other metrics
that can help us better evaluate the performance of a classification
model. Let’s go through some of them:
🎓 Precision: TP/(TP + FP) defined as the proportion of predicted positives that are actually positive. Also called positive predictive value
🎓 Recall: TP/(TP + FN) defined as the proportion of positive results out of the number of samples which were actually positive. Also known as sensitivity.
🎓 Specificity: TN/(TN + FP) defined as the proportion of negative results out of the number of samples which were actually negative.
🎓 Accuracy: TP + TN/(TP + TN + FP + FN) The percentage of labels predicted accurately for a sample.
🎓 F Measure: A weighted average of the precision and recall, with best being 1 and worst being 0.
Tidymodels provides yet another succinct way of evaluating all these metrics. Using yardstick::metric_set(), you can combine multiple metrics together into a new function that calculates all of them at once.
# Combine metrics and evaluate them all at once
eval_metrics <- metric_set(ppv, recall, accuracy, f_meas)
eval_metrics(data = results, truth = diabetic, estimate = .pred_class)
| | | | |
|---|
| ppv | binary | 0.7537815 | | |
| recall | binary | 0.5772201 | | |
| accuracy | binary | 0.7888889 | | |
| f_meas | binary | 0.6537901 | | |
Using the precision (ppv) metric, we are able to answer the question:
- Of all the patients the model predicted are diabetic, how many are actually diabetic?
Using the recall metric, we are able to answer the question:
- Of all the patients that are actually diabetic, how many did the model identify?
Great job, we just made predictions and evaluated them using a number of metrics.
Until now, we’ve considered the predictions from the model as being
either 1 or 0 class labels. Actually, things are a little more complex
than that. Statistical machine learning algorithms, like logistic
regression, are based on probability; so what actually gets predicted by a binary classifier is the probability that the label is true (P(y)) and the probability that the label is false (1−P(y)). A threshold value of 0.5 is used to decide whether the predicted label is a 1 (P(y)>0.5) or a 0 (P(y)<=0.5). Let’s see the probability pairs for each case:
# Predict class probabilities and bind them to results
results <- results %>%
bind_cols(logreg_fit %>%
predict(new_data = diabetes_test, type = "prob"))
# Print out the results
results %>%
slice_head(n = 10)
| | | | |
|---|
| 0 | 0 | 0.41666247 | 0.5833375 | |
| 0 | 0 | 0.09848140 | 0.9015186 | |
| 0 | 0 | 0.04694784 | 0.9530522 | |
| 0 | 0 | 0.05606136 | 0.9439386 | |
| 1 | 1 | 0.58055760 | 0.4194424 | |
| 0 | 0 | 0.33066116 | 0.6693388 | |
| 0 | 0 | 0.28826501 | 0.7117350 | |
| 1 | 0 | 0.26991877 | 0.7300812 | |
| 0 | 0 | 0.27501792 | 0.7249821 | |
| 0 | 0 | 0.13127572 | 0.8687243 | |
The decision to score a prediction as a 1 or a 0 depends on the
threshold to which the predicted probabilities are compared. If we were
to change the threshold, it would affect the predictions; and therefore
change the metrics in the confusion matrix. A common way to evaluate a
classifier is to examine the true positive rate (which is another name for recall) and the false positive rate
(1 - specificity) for a range of possible thresholds. These rates are
then plotted against all possible thresholds to form a chart known as a received operator characteristic (ROC) chart, like this:
# Make a roc_chart
results %>%
roc_curve(truth = diabetic, .pred_1) %>%
autoplot()

The ROC chart shows the curve of the true and false positive rates
for different threshold values between 0 and 1. A perfect classifier
would have a curve that goes straight up the left side and straight
across the top. The diagonal line across the chart represents the
probability of predicting correctly with a 50/50 random prediction; so
you obviously want the curve to be higher than that (or your model is no
better than simply guessing!).
The area under the curve (AUC) is a value between 0 and 1 that
quantifies the overall performance of the model. One way of interpreting
AUC is as the probability that the model ranks a random positive
example more highly than a random negative example. The closer to 1 this
value is, the better the model. Once again, Tidymodels includes a
function to calculate this metric: yardstick::roc_auc()
# Compute the AUC
results %>%
roc_auc(diabetic, .pred_1)
2. Recipes and workflows
Data preprocessing with recipes
In this case, the ROC curve and its AUC indicate that the model
performs better than a random guess which is not bad considering we
performed very little preprocessing of the data.
In practice, it’s common to perform some preprocessing of the data to
make it easier for the algorithm to fit a model to it. There’s a huge
range of preprocessing transformations you can perform to get your data
ready for modeling, but we’ll limit ourselves to a few common
techniques:
Scaling numeric features so they’re on the same scale. This
prevents features with large values from producing coefficients that
disproportionately affect the predictions.
Encoding categorical variables. For example, by using a one hot encoding technique you can create “dummy” or indicator variables which replace the original categorical feature with numeric columns whose values are either 1 or 0.
Tidymodels provides yet another neat package: recipes-
a package for preprocessing data. Let’s specify a recipe that encodes
the age column then normalizes the rest of the predictor features.
# Preprocess the data for modelling
diabetes_recipe <- recipe(diabetic ~ ., data = diabetes_train) %>%
step_mutate(age = factor(age)) %>%
step_normalize(all_numeric_predictors()) %>%
step_dummy(age)
# Print the recipe
diabetes_recipe
## Data Recipe
##
## Inputs:
##
## role #variables
## outcome 1
## predictor 8
##
## Operations:
##
## Variable mutation for age
## Centering and scaling for all_numeric_predictors()
## Dummy variables from age
We just created a recipe containing an outcome and its corresponding
predictors, specifying that the age variable should be converted to a
categorical variable (factor), all the numeric predictors normalized and
creating dummy variables for the nominal predictor (age) 🙌.
Bundling it all together using a workflow
Now that we have a recipe and a model specification we defined
previously, we need to find a way of bundling them together into an
object that will first preprocess the data, fit the model on the
preprocessed data and also allow for potential post-processing
activities.
In Tidymodels, this convenient object is called a workflow and conveniently holds your modeling components.
The workflows
package allows the user to bind modeling and preprocessing objects
together. You can then fit the entire workflow to the data, such that
the model encapsulates all of the preprocessing steps as well as the
algorithm.
# Redefine the model specification
logreg_spec <- logistic_reg() %>%
set_engine("glm") %>%
set_mode("classification")
# Bundle the recipe and model specification
lr_wf <- workflow() %>%
add_recipe(diabetes_recipe) %>%
add_model(logreg_spec)
# Print the workflow
lr_wf
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: logistic_reg()
##
## -- Preprocessor ----------------------------------------------------------------
## 3 Recipe Steps
##
## * step_mutate()
## * step_normalize()
## * step_dummy()
##
## -- Model -----------------------------------------------------------------------
## Logistic Regression Model Specification (classification)
##
## Computational engine: glm
After a workflow has been specified, a model can be trained using the fit() function.
# Fit a workflow object
lr_wf_fit <- lr_wf %>%
fit(data = diabetes_train)
# Print wf object
lr_wf_fit
## == Workflow [trained] ==========================================================
## Preprocessor: Recipe
## Model: logistic_reg()
##
## -- Preprocessor ----------------------------------------------------------------
## 3 Recipe Steps
##
## * step_mutate()
## * step_normalize()
## * step_dummy()
##
## -- Model -----------------------------------------------------------------------
##
## Call: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)
##
## Coefficients:
## (Intercept) pregnancies plasma_glucose
## 0.8687 -0.9424 -0.3204
## diastolic_blood_pressure triceps_thickness serum_insulin
## -0.1974 -0.3358 -0.5528
## bmi diabetes_pedigree age_X22
## -0.4849 -0.3669 0.1369
## age_X23 age_X24 age_X25
## 1.0163 18.9613 18.7737
## age_X26 age_X28 age_X29
## 18.8089 -19.4492 -19.9201
## age_X30 age_X31 age_X32
## 0.5942 0.8685 0.9317
## age_X33 age_X34 age_X35
## 0.4930 0.5297 0.7340
## age_X36 age_X37 age_X38
## -1.3050 -1.7518 -2.5862
## age_X39 age_X40 age_X41
## -1.9703 -2.1639 -2.2658
## age_X42 age_X43 age_X44
## -0.8612 -1.9764 -1.9533
## age_X45 age_X46 age_X47
## -1.8894 -2.0556 -1.9258
## age_X48 age_X49 age_X50
## 0.7884 2.9357 1.2163
## age_X51 age_X52 age_X53
## 0.2695 0.3483 -1.0652
## age_X54 age_X55 age_X56
## -2.2427 -2.6624 -2.4732
## age_X57 age_X58 age_X59
## -3.0821 -2.2541 -2.0212
## age_X60 age_X61 age_X62
## -3.1082 -2.1049 -1.9737
## age_X63 age_X64 age_X65
## -1.6991 -0.6576 -1.8492
## age_X66 age_X67 age_X68
## 1.1912 0.8770 18.3957
## age_X69 age_X70 age_X71
## 18.6030 20.1025 18.7479
## age_X72 age_X73 age_X74
## 18.4269 19.3626 19.3518
## age_X75 age_X76 age_X77
## 18.6483 19.0067 18.0447
##
## Degrees of Freedom: 10499 Total (i.e. Null); 10437 Residual
## Null Deviance: 13290
## Residual Deviance: 6838 AIC: 6964
##
## ...
## and 0 more lines.
Good job👏! We now have a trained workflow. The workflow print out shows the coefficients learned during training.
This allows us to use the model trained by this workflow to predict
labels for our test set, and compare the performance metrics with the
basic model we created previously.
# Make predictions on the test set
results <- diabetes_test %>% select(diabetic) %>%
bind_cols(lr_wf_fit %>%
predict(new_data = diabetes_test)) %>%
bind_cols(lr_wf_fit %>%
predict(new_data = diabetes_test, type = "prob"))
# Print the results
results %>%
slice_head(n = 10)
| | | | |
|---|
| 0 | 0 | 3.181359e-01 | 0.68186413 | |
| 0 | 0 | 1.456229e-01 | 0.85437708 | |
| 0 | 0 | 8.747333e-02 | 0.91252667 | |
| 0 | 0 | 5.700564e-10 | 1.00000000 | |
| 1 | 1 | 9.310216e-01 | 0.06897839 | |
| 0 | 0 | 5.451350e-09 | 0.99999999 | |
| 0 | 1 | 5.697916e-01 | 0.43020837 | |
| 1 | 1 | 6.938903e-01 | 0.30610969 | |
| 0 | 0 | 4.401889e-09 | 1.00000000 | |
| 0 | 0 | 1.913352e-01 | 0.80866481 | |
Let’s take a look at the confusion matrix:
# Confusion matrix for prediction results
results %>%
conf_mat(truth = diabetic, estimate = .pred_class)
## Truth
## Prediction 1 0
## 1 1116 245
## 0 438 2701
🤩🤩 Look at those metrics!
Can we visualize this? Of course, nothing is impaRsible!
# Visualize conf mat
update_geom_defaults(geom = "rect", new = list(fill = "midnightblue", alpha = 0.7))
results %>%
conf_mat(diabetic, .pred_class) %>%
autoplot()

What about our other metrics such as ppv, sensitivity etc?
# Evaluate other desired metrics
eval_metrics(data = results, truth = diabetic, estimate = .pred_class)
| | | | |
|---|
| ppv | binary | 0.8199853 | | |
| recall | binary | 0.7181467 | | |
| accuracy | binary | 0.8482222 | | |
| f_meas | binary | 0.7656947 | | |
# Evaluate ROC_AUC metrics
results %>%
roc_auc(diabetic, .pred_1)
# Plot ROC_CURVE
results %>%
roc_curve(diabetic, .pred_1) %>%
autoplot()

Comparing with previous predictions, the metrics look better, so clearly preprocessing the data has made a difference.
3. Try a different algorithm
Now let’s try a different algorithm. Previously we used a logistic regression algorithm, which is a linear algorithm. There are many kinds of classification algorithm we could try, including:
Support Vector Machine algorithms: Algorithms that define a hyperplane that separates classes.
Tree-based algorithms: Algorithms that build a decision tree to reach a prediction
Ensemble algorithms: Algorithms that combine the outputs of multiple base algorithms to improve generalizability.
This time, we’ll train the model using an ensemble algorithm named Random Forest
that averages the outputs of multiple random decision trees. Random
forests help to reduce tree correlation by injecting more randomness
into the tree-growing process. More specifically, instead of considering
all predictors in the data, for calculating a given split, random
forests pick a random sample of predictors to be considered for that
split.
For further reading on Tree based models, please see:
Machine Learning for Social Scientists
As we already have a gist of how to perform classification using
Tidymodels, let’s get right into specifying and fitting a random forest
algorithm.
# Preprocess the data for modelling
diabetes_recipe <- recipe(diabetic ~ ., data = diabetes_train) %>%
step_mutate(age = factor(age)) %>%
step_normalize(all_numeric_predictors()) %>%
step_dummy(age)
# Build a random forest model specification
rf_spec <- rand_forest() %>%
set_engine("ranger", importance = "impurity") %>%
set_mode("classification")
# Bundle recipe and model spec into a workflow
rf_wf <- workflow() %>%
add_recipe(diabetes_recipe) %>%
add_model(rf_spec)
# Fit a model
rf_wf_fit <- rf_wf %>%
fit(data = diabetes_train)
# Make predictions on test data
results <- diabetes_test %>% select(diabetic) %>%
bind_cols(rf_wf_fit %>%
predict(new_data = diabetes_test)) %>%
bind_cols(rf_wf_fit %>%
predict(new_data = diabetes_test, type = "prob"))
# Print out predictions
results %>%
slice_head(n = 10)
| | | | |
|---|
| 0 | 0 | 0.284647131 | 0.7153529 | |
| 0 | 0 | 0.012075218 | 0.9879248 | |
| 0 | 0 | 0.009816002 | 0.9901840 | |
| 0 | 0 | 0.010763838 | 0.9892362 | |
| 1 | 1 | 0.892012626 | 0.1079874 | |
| 0 | 0 | 0.113799606 | 0.8862004 | |
| 0 | 0 | 0.229868076 | 0.7701319 | |
| 1 | 0 | 0.266318709 | 0.7336813 | |
| 0 | 0 | 0.052735319 | 0.9472647 | |
| 0 | 0 | 0.038482162 | 0.9615178 | |
💪 There goes our random_forest model. Is it any good? Let’s evaluate its metrics!
# Confusion metrics for rf_predictions
results %>%
conf_mat(diabetic, .pred_class)
## Truth
## Prediction 1 0
## 1 1371 100
## 0 183 2846
# Confusion matrix plot
results %>%
conf_mat(diabetic, .pred_class) %>%
autoplot()

There is a considerable increase in the number of True Positives and True Negatives, which is a step in the right direction.
Let’s take a look at other evaluation metrics
# Evaluate other intuitive classification metrics
rf_met <- results %>%
eval_metrics(truth = diabetic, estimate = .pred_class)
# Evaluate ROC_AOC
auc <- results %>%
roc_auc(diabetic, .pred_1)
# Plot ROC_CURVE
curve <- results %>%
roc_curve(diabetic, .pred_1) %>%
autoplot
# Return metrics
list(rf_met, auc, curve)
## [[1]]
## # A tibble: 4 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 ppv binary 0.932
## 2 recall binary 0.882
## 3 accuracy binary 0.937
## 4 f_meas binary 0.906
##
## [[2]]
## # A tibble: 1 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 roc_auc binary 0.985
##
## [[3]]

For the sheer sake of adventure, let’s make a Variable Importance
Plot to see which predictor variables have the most impact in our model.
# Load vip
library(vip)
# Extract the fitted model from the workflow
rf_wf_fit %>%
extract_fit_parsnip() %>%
# Make VIP plot
vip()

Just as we had anticipated from our data exploration 😊! This goes to show the importance of data exploration.
As revealed by the performance metrics, the random forest model
seemed to have done a great job in increasing the True
Positives/Negatives and reducing the False Positives/Negatives.
Use the model for inferencing
Now that we have a reasonably useful trained model, we can save it for use later to predict labels for new data:
# Save trained workflow
saveRDS(rf_wf_fit, "diabetes_rf_model.rds")
Now, we can load it whenever we need it, and use it to predict labels for new data. This is often called scoring or inferencing.
For example, lets create a simulated data set by picking a random
value for each column in our test set then make predictions using the
saved model.
# Load the model into the current R session
loaded_model <- readRDS("diabetes_rf_model.rds")
# Create new simulated data
new_data <- lapply(diabetes_test, function(x){sample(x, size = 2)}) %>%
as_tibble()
new_data
# Make predictions
predictions <- new_data %>%
bind_cols(loaded_model %>% predict(new_data))
predictions
4. Multiclass Classification
Binary classification techniques work well when the data observations belong to one of two classes or categories, such as True or False. When the data can be categorized into more than two classes, you must use a multiclass classification algorithm.
Multiclass classification can be thought of as a combination of multiple binary classifiers. There are two ways in which you approach the problem:
One vs Rest (OVR), in which a classifier is created for each possible class value, with a positive outcome for cases where the prediction is this
class, and negative predictions for cases where the prediction is any
other class. A classification problem with four possible shape classes (square, circle, triangle, hexagon) would require four classifiers that predict:
square or not
circle or not
triangle or not
hexagon or not
One vs One (OVO), in which a classifier for each
possible pair of classes is created. The classification problem with
four shape classes would require the following binary classifiers:
square or circle
square or triangle
square or hexagon
circle or triangle
circle or hexagon
triangle or hexagon
In both approaches, the overall model that combines the classifiers
generates a vector of predictions in which the probabilities generated
from the individual binary classifiers are used to determine which class
to predict.
Fortunately, in most machine learning frameworks, including
Tidymodels, implementing a multiclass classification model is not
significantly more complex than binary classification.
Explore the data
# Load the dataset
library(palmerpenguins)
# Take a peek into the data
glimpse(penguins)
## Rows: 344
## Columns: 8
## $ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel~
## $ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse~
## $ bill_length_mm <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, ~
## $ bill_depth_mm <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, ~
## $ flipper_length_mm <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186~
## $ body_mass_g <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, ~
## $ sex <fct> male, female, female, NA, female, male, female, male~
## $ year <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007~
The data contains the following columns:
species: a factor denoting the penguin species (Adelie, Chinstrap, or Gentoo)
island: a factor denoting the island (in Palmer Archipelago, Antarctica) where observed
bill_length_mm (aka culmen_length): a number denoting length of the dorsal ridge of penguin bill (millimeters)
bill_depth_mm (aka culmen_depth): a number denoting the depth of the penguin bill (millimeters)
flipper_length_mm: an integer denoting penguin flipper length (millimeters)
body_mass_g: an integer denoting penguin body mass (grams)
sex: a factor denoting penguin sex (male, female)
year: an integer denoting the study year (2007, 2008, or 2009)
The species column containing penguin species Adelie, Chinstrap, or Gentoo, is the label we want to train a model to predict.
The corresponding Python learning module used a data set with the following variables: bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g, species.
Let’s narrow down to those and make some summary statistics while at it. The skimr package provides a strong set of summary statistics that are generated for a variety of different data types.
# Select desired columns
penguins_select <- penguins %>%
select(c(bill_length_mm, bill_depth_mm, flipper_length_mm,
body_mass_g, species))
# Dso some summary statistics
penguins_select %>%
skim()
Data summary
| Name |
Piped data |
| Number of rows |
344 |
| Number of columns |
5 |
| _______________________ |
|
| Column type frequency: |
|
| factor |
1 |
| numeric |
4 |
| ________________________ |
|
| Group variables |
None |
Variable type: factor
| species |
0 |
1 |
FALSE |
3 |
Ade: 152, Gen: 124, Chi: 68 |
Variable type: numeric
| bill_length_mm |
2 |
0.99 |
43.92 |
5.46 |
32.1 |
39.23 |
44.45 |
48.5 |
59.6 |
▃▇▇▆▁ |
| bill_depth_mm |
2 |
0.99 |
17.15 |
1.97 |
13.1 |
15.60 |
17.30 |
18.7 |
21.5 |
▅▅▇▇▂ |
| flipper_length_mm |
2 |
0.99 |
200.92 |
14.06 |
172.0 |
190.00 |
197.00 |
213.0 |
231.0 |
▂▇▃▅▂ |
| body_mass_g |
2 |
0.99 |
4201.75 |
801.95 |
2700.0 |
3550.00 |
4050.00 |
4750.0 |
6300.0 |
▃▇▆▃▂ |
From the neat summary provided by skimr, we can see that each our
predictor columns contains missing 2 values while our label/outcome
column contains none.
Let’s dig a little deeper and filter the rows that contain missing values.
penguins_select %>%
filter(if_any(everything(), is.na))
| | | | |
|---|
| NA | NA | NA | NA | Adelie |
| NA | NA | NA | NA | Gentoo |
There are two rows that contain no feature values at all (NA stands for Not Available ), so these won’t be useful in training a model. Let’s discard them from the dataset.
# Drop rows containing missing values
penguins_select <- penguins_select %>%
drop_na()
# Confirm there are no missing values
penguins_select %>%
anyNA()
## [1] FALSE
# Proportion of each species in the data
penguins_select %>%
count(species)
| | | | |
|---|
| Adelie | 151 | | | |
| Chinstrap | 68 | | | |
| Gentoo | 123 | | | |
Now that we’ve dealt with the missing values, let’s explore how the features relate to the label by creating some box charts.
# Pivot data to a long format
penguins_select_long <- penguins_select %>%
pivot_longer(!species, names_to = "predictors", values_to = "values")
# Make box plots
penguins_select_long %>%
ggplot(mapping = aes(x = species, y = values, fill = predictors)) +
geom_boxplot() +
facet_wrap(~predictors, scales = "free") +
scale_fill_paletteer_d("nbapalettes::supersonics_holiday") +
theme(legend.position = "none")

From the box plots, it looks like species Adelie and Chinstrap have similar data profiles for bill_depth, flipper_length, and body_mass, but Chinstraps tend to have longer bill_length. Gentoo tends to have fairly clearly differentiated features from the others; which should help us train a good classification model.
Prepare the data
Just as for binary classification, before training the model, we need
to split the data into subsets for training and validation. We’ll also
apply a stratification technique when splitting the data to maintain the proportion of each label value in the training and validation datasets.
set.seed(2056)
# Split data 70%-30% into training set and test set
penguins_split <- penguins_select %>%
initial_split(prop = 0.70, strata = species)
# Extract data in each split
penguins_train <- training(penguins_split)
penguins_test <- testing(penguins_split)
# Print the number of observations in each split
cat("Training cases: ", nrow(penguins_train), "\n",
"Test cases: ", nrow(penguins_test), sep = "")
## Training cases: 238
## Test cases: 104
Train and evaluate a multiclass classifier
Now that we have a set of training features and corresponding
training labels, we can fit a multiclass classification algorithm to the
data to create a model.
parsnip::multinom_reg() defines a model that uses linear predictors to predict multiclass data using the multinomial distribution.
Let’s fit Multinomial regression via nnet package. This model usually has 1 tuning hyperparameter, penalty,
which describes the amount of regularization. This is used to
counteract any bias in the sample, and help the model generalize well by
avoiding overfitting the model to the training data. We can of
course tune this parameter, like we will later on in this lesson, but
for now, let’s choose an arbitrary value of 1
# Specify a multinomial regression via nnet
multireg_spec <- multinom_reg(penalty = 1) %>%
set_engine("nnet") %>%
set_mode("classification")
# Train a multinomial regression model without any preprocessing
set.seed(2056)
multireg_fit <- multireg_spec %>%
fit(species ~ ., data = penguins_train)
# Print the model
multireg_fit
## parsnip model object
##
## Fit time: 20ms
## Call:
## nnet::multinom(formula = species ~ ., data = data, decay = ~1,
## trace = FALSE)
##
## Coefficients:
## (Intercept) bill_length_mm bill_depth_mm flipper_length_mm
## Chinstrap -0.09689103 1.3965690 -0.7974207 -0.153233777
## Gentoo -0.03813310 0.2345437 -1.7038451 0.006228115
## body_mass_g
## Chinstrap -0.004706975
## Gentoo 0.003833670
##
## Residual Deviance: 31.38783
## AIC: 51.38783
Now we can use the trained model to predict the labels for the test features, and evaluate performance:
# Make predictions for the test set
penguins_results <- penguins_test %>% select(species) %>%
bind_cols(multireg_fit %>%
predict(new_data = penguins_test)) %>%
bind_cols(multireg_fit %>%
predict(new_data = penguins_test, type = "prob"))
# Print predictions
penguins_results %>%
slice_head(n = 5)
| | | | |
|---|
| Adelie | Adelie | 0.9999058 | 4.334555e-05 | 5.085677e-05 |
| Adelie | Adelie | 0.9996200 | 3.584026e-04 | 2.157623e-05 |
| Adelie | Adelie | 0.9895737 | 8.454760e-03 | 1.971491e-03 |
| Adelie | Adelie | 0.9916696 | 6.837429e-03 | 1.493014e-03 |
| Adelie | Adelie | 0.9236931 | 7.613199e-02 | 1.748642e-04 |
Now, let’s look at the confusion matrix for our model
# Confusion matrix
penguins_results %>%
conf_mat(species, .pred_class)
## Truth
## Prediction Adelie Chinstrap Gentoo
## Adelie 46 1 0
## Chinstrap 0 20 0
## Gentoo 0 0 37
The confusion matrix shows the intersection of predicted and actual
label values for each class - in simple terms, the diagonal
intersections from top-left to bottom-right indicate the number of
correct predictions.
When dealing with multiple classes, it’s generally more intuitive to visualize this as a heat map, like this:
update_geom_defaults(geom = "tile", new = list(color = "black", alpha = 0.7))
# Visualize confusion matrix
penguins_results %>%
conf_mat(species, .pred_class) %>%
autoplot(type = "heatmap")

The darker squares in the confusion matrix plot indicate high numbers
of cases, and you can hopefully see a diagonal line of darker squares
indicating cases where the predicted and actual label are the same.
Let’s now calculate summary statistics for the confusion matrix.
# Statistical summaries for the confusion matrix
conf_mat(data = penguins_results, truth = species, estimate = .pred_class) %>%
summary()
| | | | |
|---|
| accuracy | multiclass | 0.9903846 | | |
| kap | multiclass | 0.9848507 | | |
| sens | macro | 0.9841270 | | |
| spec | macro | 0.9942529 | | |
| ppv | macro | 0.9929078 | | |
| npv | macro | 0.9960317 | | |
| mcc | multiclass | 0.9850012 | | |
| j_index | macro | 0.9783799 | | |
| bal_accuracy | macro | 0.9891899 | | |
| detection_prevalence | macro | 0.3333333 | | |
The tibble shows the overall metrics of how well the model performs across all three classes.
Let’s evaluate the ROC metrics. In the case of a multiclass
classification model, a single ROC curve showing true positive rate vs
false positive rate is not possible. However, you can use the rates for
each class in a One vs Rest (OVR) comparison to create a ROC chart for
each class.
# Make a ROC_CURVE
penguins_results %>%
roc_curve(species, c(.pred_Adelie, .pred_Chinstrap, .pred_Gentoo)) %>%
ggplot(aes(x = 1 - specificity, y = sensitivity, color = .level)) +
geom_abline(lty = 2, color = "gray80", size = 0.9) +
geom_path(show.legend = T, alpha = 0.6, size = 1.2) +
coord_equal()

To quantify the ROC performance, you can calculate an aggregate area
under the curve score that is averaged across all of the OVR curves.
# Calculate ROC_AOC
penguins_results %>%
roc_auc(species, c(.pred_Adelie, .pred_Chinstrap, .pred_Gentoo))
That went down well! The model did a great job in classifying the
penguins. What kind of adventure would it be, if we didn’t preprocess
the data?
Workflows + A different algorithm
Again, just like with binary classification, you can use a workflow
to apply preprocessing steps to the data before fitting it to an
algorithm to train a model. Let’s scale the numeric features in a
transformation step before training, try a different algorithm (a
support vector machine) and tune some model hyperparameters, just to
show that we can!
Support Vector Machines try to find a hyperplane in some feature space that “best” separates the classes. Please see:
We’ll fit a radial basis function support vector machine to these data and tune the SVM cost parameter and the σ parameter in the kernel function (The margin parameter does not apply to classification models)
A cost argument allows us to specify the cost of a violation to
the margin. When the cost argument is small, then the margins will be
wide and many support vectors will be on the margin or will violate the
margin. This could make the model more robust and lead to
better classification. When the cost argument is large, then the margins
will be narrow and there will be few support vectors on the margin or
violating the margin.
As σ decreases, the fit becomes more non-linear, and the model becomes more flexible.
Both parameters can have a profound effect on the model complexity and performance.
The radial basis kernel is extremely flexible and as a rule of thumb,
we generally start with this kernel when fitting SVMs in practice.
Parameters are marked for tuning by assigning them a value of tune(). Also, let’s try out a new succinct way of creating workflows that minimizes a lot of piping steps as suggested by David’s blog post (winner of sliced!!)
# Create a model specification
svm_spec <- svm_rbf(mode = "classification", engine = "kernlab",
cost = tune(), rbf_sigma = tune())
# Create a workflow that encapsulates a recipe and a model
svm_wflow <- recipe(species ~ ., data = penguins_train) %>%
step_normalize(all_numeric_predictors()) %>%
workflow(svm_spec)
# Print out workflow
svm_wflow
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: svm_rbf()
##
## -- Preprocessor ----------------------------------------------------------------
## 1 Recipe Step
##
## * step_normalize()
##
## -- Model -----------------------------------------------------------------------
## Radial Basis Function Support Vector Machine Specification (classification)
##
## Main Arguments:
## cost = tune()
## rbf_sigma = tune()
##
## Computational engine: kernlab
Pretty neat, right ✨?
Now that we have specified what parameter to tune, we’ll need to
figure out a set of possible values to try out then choose the best.
To do this, we’ll create a grid! In this example, we’ll work through a
regular grid of hyperparameter values, try them out, and see what pair
results in the best model performance.
set.seed(2056)
# Create regular grid of 6 values for each tuning parameters
svm_grid <- grid_regular(parameters(svm_spec), levels = 6)
# Print out some parameters in our grid
svm_grid %>%
slice_head(n = 10)
| | | | |
|---|
| 9.765625e-04 | 1e-10 | | | |
| 7.812500e-03 | 1e-10 | | | |
| 6.250000e-02 | 1e-10 | | | |
| 5.000000e-01 | 1e-10 | | | |
| 4.000000e+00 | 1e-10 | | | |
| 3.200000e+01 | 1e-10 | | | |
| 9.765625e-04 | 1e-08 | | | |
| 7.812500e-03 | 1e-08 | | | |
| 6.250000e-02 | 1e-08 | | | |
| 5.000000e-01 | 1e-08 | | | |
Awesome! One thing about hyperparameters is that they are not learned
directly from the training set. Instead, they are estimated using simulated data sets created from a process called resampling. In our previous, we used cross-validation resampling method. Let’s try out another resampling technique: bootstrap resampling.
Bootstrap resampling means drawing with replacement from our original dataset then then fit a model on that new set that contains some duplicates, and evaluate the model on the data points that were not included.
Then we do that again (default behaviour is 25 boostraps but this can be changed). Okay, let’s create some simulated data sets.
set.seed(2056)
# Bootstrap resampling
penguins_bs <- bootstraps(penguins_train, times = 10)
penguins_bs
| | | | |
|---|
| <S3: boot_split> | Bootstrap01 | | | |
| <S3: boot_split> | Bootstrap02 | | | |
| <S3: boot_split> | Bootstrap03 | | | |
| <S3: boot_split> | Bootstrap04 | | | |
| <S3: boot_split> | Bootstrap05 | | | |
| <S3: boot_split> | Bootstrap06 | | | |
| <S3: boot_split> | Bootstrap07 | | | |
| <S3: boot_split> | Bootstrap08 | | | |
| <S3: boot_split> | Bootstrap09 | | | |
| <S3: boot_split> | Bootstrap10 | | | |
Model tuning via grid search.
We are ready to tune! Let’s use tune_grid() to fit models at all the different values we chose for each tuned hyperparameter.
doParallel::registerDoParallel()
# Model tuning via a grid search
set.seed(2056)
svm_res <- tune_grid(
object = svm_wflow,
resamples = penguins_bs,
grid = svm_grid
)
Now that we have our tuning results, we can extract the performance metrics using collect_metrics():
# Obtain performance metrics
svm_res %>%
collect_metrics() %>%
slice_head(n = 7)
| | | | | | | |
|---|
| 0.0009765625 | 1e-10 | accuracy | multiclass | 0.4279212 | 10 | 0.010790273 | |
| 0.0009765625 | 1e-10 | roc_auc | hand_till | 0.9809805 | 10 | 0.003231725 | |
| 0.0078125000 | 1e-10 | accuracy | multiclass | 0.4279212 | 10 | 0.010790273 | |
| 0.0078125000 | 1e-10 | roc_auc | hand_till | 0.9809669 | 10 | 0.003253624 | |
| 0.0625000000 | 1e-10 | accuracy | multiclass | 0.4279212 | 10 | 0.010790273 | |
| 0.0625000000 | 1e-10 | roc_auc | hand_till | 0.9803196 | 10 | 0.003260246 | |
| 0.5000000000 | 1e-10 | accuracy | multiclass | 0.4279212 | 10 | 0.010790273 | |
🤩🤩 Let’s see if we could get more by visualizing the results:
# Visualize tuning metrics
svm_res %>%
collect_metrics() %>%
mutate(rbf_sigma = factor(rbf_sigma)) %>%
ggplot(mapping = aes(x = cost, y = mean, color = rbf_sigma)) +
geom_line(size = 1.5, alpha = 0.7) +
geom_point(size = 2) +
facet_wrap(~.metric, scales = "free", nrow = 2) +
scale_x_log10(labels = scales::label_number()) +
scale_color_viridis_d(option = "viridis", begin = .1)

It seems that an SVM with an rbf_sigma of 1 and 0.01 really performed well across all candidate values of cost. The show_best() function can help us make a clearer distinction:
# Show best submodel
svm_res %>%
show_best("accuracy")
| | | | | | | |
|---|
| 32.0 | 1.00 | accuracy | multiclass | 0.9751752 | 10 | 0.005042863 | |
| 32.0 | 0.01 | accuracy | multiclass | 0.9749459 | 10 | 0.004247809 | |
| 4.0 | 1.00 | accuracy | multiclass | 0.9739020 | 10 | 0.005514761 | |
| 4.0 | 0.01 | accuracy | multiclass | 0.9706938 | 10 | 0.004727220 | |
| 0.5 | 1.00 | accuracy | multiclass | 0.9694269 | 10 | 0.004825592 | |
Much better! Let’s now use the select_best() function to pull out the single set of hyperparameter values in the best sub-model:
# Select best model hyperparameters
best_svm <- svm_res %>%
select_best("accuracy")
best_svm
Perfect! These are the values for rbf_sigma and cost that maximize accuracy for our penguins!
We can now finalize our workflow such that the parameters we had marked for tuning by assigning them a value of tune() can get updated with the values from best_svm
# Finalize workflow
final_wflow <- svm_wflow %>%
finalize_workflow(best_svm)
final_wflow
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: svm_rbf()
##
## -- Preprocessor ----------------------------------------------------------------
## 1 Recipe Step
##
## * step_normalize()
##
## -- Model -----------------------------------------------------------------------
## Radial Basis Function Support Vector Machine Specification (classification)
##
## Main Arguments:
## cost = 32
## rbf_sigma = 1
##
## Computational engine: kernlab
That marks the end of tuning 💃!
The last fit
Finally, let’s fit this final model to the training data and evaluate
how it would perform on new data using our test data. We can use the
function last_fit() with our finalized model; this function fits the finalized model on the full training data set and evaluates it on the testing data.
# The last fit
final_fit <- last_fit(object = final_wflow, split = penguins_split)
# Collect metrics
final_fit %>%
collect_metrics()
| | | | |
|---|
| accuracy | multiclass | 0.9711538 | Preprocessor1_Model1 | |
| roc_auc | hand_till | 0.9981021 | Preprocessor1_Model1 | |
Much better 🤩! You can of course go ahead and obtain the hard class and probability predictions using collect predictions() and you will be well on your way to computing the confusion matrix and other summaries that come with it.
# Collect predictions and make confusion matrix
final_fit %>%
collect_predictions() %>%
conf_mat(truth = species, estimate = .pred_class)
## Truth
## Prediction Adelie Chinstrap Gentoo
## Adelie 45 2 0
## Chinstrap 1 19 0
## Gentoo 0 0 37
Use the model with new data observations
Now let’s save our trained model so we can use it again later. Begin by extracting the trained workflow object from final_fit object.
# Extract trained workflow
penguins_svm_model <- final_fit %>%
extract_workflow()
# Save workflow
saveRDS(penguins_svm_model, "penguins_svm_model.rds")
OK, so now we have a trained model. Let’s use it to predict the class of a new penguin observation:
# Load model
loaded_psvm_model <- readRDS("penguins_svm_model.rds")
# Create new tibble of observations
new_obs <- tibble(
bill_length_mm = c(49.5, 38.2),
bill_depth_mm = c(18.4, 20.1),
flipper_length_mm = c(195, 190),
body_mass_g = c(3600, 3900))
# Make predictions
new_results <- new_obs %>%
bind_cols(loaded_psvm_model %>%
predict(new_data = new_obs))
# Show predictions
new_results
| | | | |
|---|
| 49.5 | 18.4 | 195 | 3600 | Chinstrap |
| 38.2 | 20.1 | 190 | 3900 | Adelie |
Good job! A working model 🐧🐧!
LS0tDQp0aXRsZTogJ1RyYWluIGFuZCBFdmFsdWF0ZSBDbGFzc2lmaWNhdGlvbiBNb2RlbHMgdXNpbmcgVGlkeW1vZGVscycNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjc3M6IHN0eWxlXzcuY3NzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIGhpZ2hsaWdodDogYnJlZXplZGFyaw0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgZXZhbD1UfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KHJhbmdlcikNCmxpYnJhcnkodmlwKQ0KbGlicmFyeShwYWxtZXJwZW5ndWlucykNCmxpYnJhcnkocGFsZXR0ZWVyKQ0KbGlicmFyeShubmV0KQ0KbGlicmFyeShza2ltcikNCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkoa2VybmxhYikNCiMgc2xpY2UgPC0gZHBseXI6OnNsaWNlDQojIGV2YWxfbWV0cmljcyA8LSBtZXRyaWNfc2V0KHJtc2UsIHJzcSkNCg0KYGBgDQoNCiMjIEJ1Y2tsZSB1cCDwn5qADQoNCkluIHRoaXMgbGVhcm5pbmcgcGF0aCwgd2UnbGwgbGVhcm4gaG93IHRvIGNyZWF0ZSBNYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB1c2luZyBgUmAg8J+Yii4gTWFjaGluZSBsZWFybmluZyBpcyB0aGUgZm91bmRhdGlvbiBmb3IgcHJlZGljdGl2ZSBtb2RlbGluZyBhbmQgYXJ0aWZpY2lhbCBpbnRlbGxpZ2VuY2UuIFdlJ2xsIGxlYXJuIHRoZSBjb3JlIHByaW5jaXBsZXMgb2YgbWFjaGluZSBsZWFybmluZyBhbmQgaG93IHRvIHVzZSBjb21tb24gdG9vbHMgYW5kIGZyYW1ld29ya3MgdG8gdHJhaW4sIGV2YWx1YXRlLCBhbmQgdXNlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLg0KDQpNb2R1bGVzIHRoYXQgd2lsbCBiZSBjb3ZlcmVkIGluIHRoaXMgbGVhcm5pbmcgcGF0aCBpbmNsdWRlOg0KDQotICAgRXhwbG9yZSBhbmQgYW5hbHl6ZSBkYXRhIHdpdGggUg0KDQotICAgVHJhaW4gYW5kIGV2YWx1YXRlIHJlZ3Jlc3Npb24gbW9kZWxzDQoNCi0gICBUcmFpbiBhbmQgZXZhbHVhdGUgY2xhc3NpZmljYXRpb24gbW9kZWxzDQoNCi0gICAqVHJhaW4gYW5kIGV2YWx1YXRlIGNsdXN0ZXJpbmcgbW9kZWxzICh1bmRlciBkZXZlbG9wbWVudCkqDQoNCi0gICAqVHJhaW4gYW5kIGV2YWx1YXRlIGRlZXAgbGVhcm5pbmcgbW9kZWxzICh1bmRlciBkZXZlbG9wbWVudCkqDQoNCiMjIyAqKlByZXJlcXVpc2l0ZXMqKg0KDQpUaGlzIGxlYXJuaW5nIHBhdGggYXNzdW1lcyBrbm93bGVkZ2Ugb2YgYmFzaWMgbWF0aGVtYXRpY2FsIGNvbmNlcHRzLiBTb21lIGV4cGVyaWVuY2Ugd2l0aCBgUiBhbmQgdGhlIHRpZHl2ZXJzZWAgaXMgYWxzbyBiZW5lZmljaWFsIHRob3VnaCB3ZSdsbCB0cnkgYXMgbXVjaCBhcyBwb3NzaWJsZSB0byBza2ltIHRocm91Z2ggdGhlIGNvcmUgY29uY2VwdHMuIFRvIGdldCBzdGFydGVkIHdpdGggUiBhbmQgdGhlIHRpZHl2ZXJzZSwgdGhlIGJlc3QgcGxhY2Ugd291bGQgYmUgW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cDovL3I0ZHMuaGFkLmNvLm56LykgYW4gTydSZWlsbHkgYm9vayB3cml0dGVuIGJ5IEhhZGxleSBXaWNraGFtIGFuZCBHYXJyZXR0IEdyb2xlbXVuZC4gSXQncyBkZXNpZ25lZCB0byB0YWtlIHlvdSBmcm9tIGtub3dpbmcgbm90aGluZyBhYm91dCBSIG9yIHRoZSB0aWR5dmVyc2UgdG8gaGF2aW5nIGFsbCB0aGUgYmFzaWMgdG9vbHMgb2YgZGF0YSBzY2llbmNlIGF0IHlvdXIgZmluZ2VydGlwcy4NCg0KVGhlIGBQeXRob25gIGVkaXRpb24gb2YgdGhlIGxlYXJuaW5nIHBhdGggY2FuIGJlIGZvdW5kIGF0IFt0aGlzIGxlYXJuaW5nIHBhdGhdKGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2xlYXJuL3BhdGhzL2NyZWF0ZS1tYWNoaW5lLWxlYXJuLW1vZGVscy8pLg0KDQoqKldoeSBSPyoqDQoNCj4gRmlyc3QsIHdoaWxlIG5vdCB0aGUgb25seSBnb29kIG9wdGlvbiwgUiBoYXMgYmVlbiBzaG93biB0byBiZSBwb3B1bGFyIGFuZCBlZmZlY3RpdmUgaW4gbW9kZXJuIGRhdGEgYW5hbHlzaXMuIFNlY29uZCwgUiBpcyBmcmVlIGFuZCBvcGVuLXNvdXJjZS4gWW91IGNhbiBpbnN0YWxsIGl0IGFueXdoZXJlLCBtb2RpZnkgdGhlIGNvZGUsIGFuZCBoYXZlIHRoZSBhYmlsaXR5IHRvIHNlZSBleGFjdGx5IGhvdyBjb21wdXRhdGlvbnMgYXJlIHBlcmZvcm1lZC4gVGhpcmQsIGl0IGhhcyBleGNlbGxlbnQgY29tbXVuaXR5IHN1cHBvcnQgdmlhIHRoZSBjYW5vbmljYWwgUiBtYWlsaW5nIGxpc3RzIGFuZCwgbW9yZSBpbXBvcnRhbnRseSwgd2l0aCBUd2l0dGVyLCBTdGFja092ZXJmbG93LCBhbmQgUlN0dWRpbyBDb21tdW5pdHkuIEFueW9uZSB3aG8gYXNrcyBhIHJlYXNvbmFibGUsIHJlcHJvZHVjaWJsZSBxdWVzdGlvbiBoYXMgYSBwcmV0dHkgZ29vZCBjaGFuY2Ugb2YgZ2V0dGluZyBhbiBhbnN3ZXIuIC0gW2BGZWF0dXJlIEVuZ2luZWVyaW5nIGFuZCBTZWxlY3Rpb246IEEgUHJhY3RpY2FsIEFwcHJvYWNoIGZvciBQcmVkaWN0aXZlIE1vZGVscywgTWF4IEt1aG4gYW5kIEtqZWxsIEpvaG5zb25gXShodHRwczovL2Jvb2tkb3duLm9yZy9tYXgvRkVTLykNCg0KTm93LCBsZXQncyBnZXQgc3RhcnRlZCENCg0KIVtBcnR3b3JrIGJ5IFxAYWxsaXNvbl9ob3JzdF0oaW1hZ2VzL2VuY291UmFnZS5qcGcpe3dpZHRoPSI2MzAifQ0KDQojIyBBIGdlbnRsZSBpbnRyb2R1Y3Rpb24gdG8gY2xhc3NpZmljYXRpb24NCg0KKkNsYXNzaWZpY2F0aW9uKiBpcyBhIGZvcm0gb2YgbWFjaGluZSBsZWFybmluZyBpbiB3aGljaCB5b3UgdHJhaW4gYSBtb2RlbCB0byBwcmVkaWN0IHdoaWNoIGNhdGVnb3J5IGFuIGl0ZW0gYmVsb25ncyB0by4gKkNhdGVnb3JpY2FsKiBkYXRhIGhhcyBkaXN0aW5jdCAnY2xhc3NlcycsIHJhdGhlciB0aGFuIG51bWVyaWMgdmFsdWVzLg0KDQpGb3IgZXhhbXBsZSwgYSBoZWFsdGggY2xpbmljIG1pZ2h0IHVzZSBkaWFnbm9zdGljIGRhdGEgc3VjaCBhcyBhIHBhdGllbnQncyBoZWlnaHQsIHdlaWdodCwgYmxvb2QgcHJlc3N1cmUsIGJsb29kLWdsdWNvc2UgbGV2ZWwgdG8gcHJlZGljdCB3aGV0aGVyIG9yIG5vdCB0aGUgcGF0aWVudCBpcyBkaWFiZXRpYy4NCg0KIVtdKGltYWdlcy9kaWFiZXRlcy1jbGFzc2lmaWNhdGlvbi5wbmcpDQoNCkNsYXNzaWZpY2F0aW9uIGlzIGFuIGV4YW1wbGUgb2YgYSAqc3VwZXJ2aXNlZCogbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWUsIHdoaWNoIG1lYW5zIGl0IHJlbGllcyBvbiBkYXRhIHRoYXQgaW5jbHVkZXMga25vd24gKmZlYXR1cmUqIHZhbHVlcyAoZm9yIGV4YW1wbGUsIGRpYWdub3N0aWMgbWVhc3VyZW1lbnRzIGZvciBwYXRpZW50cykgYXMgd2VsbCBhcyBrbm93biAqbGFiZWwqIHZhbHVlcyAoZm9yIGV4YW1wbGUsIGEgY2xhc3NpZmljYXRpb24gb2Ygbm9uLWRpYWJldGljIG9yIGRpYWJldGljKS4gQSBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0gaXMgdXNlZCB0byBmaXQgYSBzdWJzZXQgb2YgdGhlIGRhdGEgdG8gYSBmdW5jdGlvbiB0aGF0IGNhbiBjYWxjdWxhdGUgdGhlIGBwcm9iYWJpbGl0eWAgZm9yIGVhY2ggY2xhc3MgbGFiZWwgZnJvbSB0aGUgZmVhdHVyZSB2YWx1ZXMuIFRoZSByZW1haW5pbmcgZGF0YSBpcyB1c2VkIHRvIGV2YWx1YXRlIHRoZSBtb2RlbCBieSBjb21wYXJpbmcgdGhlIHByZWRpY3Rpb25zIGl0IGdlbmVyYXRlcyBmcm9tIHRoZSBmZWF0dXJlcyB0byB0aGUga25vd24gY2xhc3MgbGFiZWxzLg0KDQpUaGUgc2ltcGxlc3QgZm9ybSBvZiBjbGFzc2lmaWNhdGlvbiBpcyAqYmluYXJ5KiBjbGFzc2lmaWNhdGlvbiwgaW4gd2hpY2ggdGhlIGxhYmVsIGlzIDAgb3IgMSwgcmVwcmVzZW50aW5nIG9uZSBvZiB0d28gY2xhc3NlczsgZm9yIGV4YW1wbGUsICJUcnVlIiBvciAiRmFsc2UiOyAiSW50ZXJuYWwiIG9yICJFeHRlcm5hbCI7ICJQcm9maXRhYmxlIiBvciAiTm9uLVByb2ZpdGFibGUiOyBhbmQgc28gb24uDQoNClRoZSBjbGFzcyBwcmVkaWN0aW9uIGlzIG1hZGUgYnkgZGV0ZXJtaW5pbmcgdGhlICpwcm9iYWJpbGl0eSogZm9yIGVhY2ggcG9zc2libGUgY2xhc3MgYXMgYSB2YWx1ZSBiZXR3ZWVuIDAgLWltcG9zc2libGUgLSBhbmQgMSAtIGNlcnRhaW4uIFRoZSB0b3RhbCBwcm9iYWJpbGl0eSBmb3IgYWxsIGNsYXNzZXMgaXMgMSwgYXMgdGhlIHBhdGllbnQgaXMgZGVmaW5pdGVseSBlaXRoZXIgZGlhYmV0aWMgb3Igbm9uLWRpYWJldGljLiBTbywgaWYgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBvZiBhIHBhdGllbnQgYmVpbmcgZGlhYmV0aWMgaXMgMC4zLCB0aGVuIHRoZXJlIGlzIGEgY29ycmVzcG9uZGluZyBwcm9iYWJpbGl0eSBvZiAwLjcgdGhhdCB0aGUgcGF0aWVudCBpcyBub24tZGlhYmV0aWMuDQoNCkEgdGhyZXNob2xkIHZhbHVlLCB1c3VhbGx5IDAuNSwgaXMgdXNlZCB0byBkZXRlcm1pbmUgdGhlIHByZWRpY3RlZCBjbGFzcyAtIHNvIGlmIHRoZSAqcG9zaXRpdmUqIGNsYXNzIChpbiB0aGlzIGNhc2UsIGRpYWJldGljKSBoYXMgYSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZ3JlYXRlciB0aGFuIHRoZSB0aHJlc2hvbGQsIHRoZW4gYSBjbGFzc2lmaWNhdGlvbiBvZiBkaWFiZXRpYyBpcyBwcmVkaWN0ZWQuDQoNClRoZSBiZXN0IHdheSB0byBsZWFybiBhYm91dCBjbGFzc2lmaWNhdGlvbiBpcyB0byB0cnkgaXQgZm9yIHlvdXJzZWxmLCBzbyB0aGF0J3Mgd2hhdCB5b3UnbGwgZG8gaW4gdGhpcyBleGVyY2lzZS4NCg0KPiBXZSdsbCByZXF1aXJlIHNvbWUgcGFja2FnZXMgdG8ga25vY2stb2ZmIHRoaXMgbW9kdWxlLiBZb3UgY2FuIGhhdmUgdGhlbSBpbnN0YWxsZWQgYXM6IGBpbnN0YWxsLnBhY2thZ2VzKGMoJ3RpZHl2ZXJzZScsICd0aWR5bW9kZWxzJywgJ3JhbmdlcicsICd2aXAnLCAncGFsbWVycGVuZ3VpbnMnLCAnc2tpbXInLCAncGFsZXR0ZWVyJywgJ25uZXQnLCAnaGVyZScpKWANCg0KQWx0ZXJuYXRpdmVseSwgdGhlIHNjcmlwdCBiZWxvdyBjaGVja3Mgd2hldGhlciB5b3UgaGF2ZSB0aGUgcGFja2FnZXMgcmVxdWlyZWQgdG8gY29tcGxldGUgdGhpcyBtb2R1bGUgYW5kIGluc3RhbGxzIHRoZW0gZm9yIHlvdSBpbiBjYXNlIHNvbWUgYXJlIG1pc3NpbmcuDQoNCmBgYHtyfQ0Kc3VwcHJlc3NXYXJuaW5ncyhpZighcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpKQ0KDQpwYWNtYW46OnBfbG9hZCgndGlkeXZlcnNlJywgJ3RpZHltb2RlbHMnLCAncmFuZ2VyJywNCiAgICAgICAgICAgICAgICd2aXAnLCAnc2tpbXInLCAnaGVyZScsJ3BhbG1lcnBlbmd1aW5zJywgJ2tlcm5sYWInLA0KICAgICAgICAgICAgICAgJ2phbml0b3InLCAncGFsZXR0ZWVyJywgJ25uZXQnKQ0KYGBgDQoNCiMjIDEuIEJpbmFyeSBjbGFzc2lmaWNhdGlvbg0KDQpMZXQncyBzdGFydCBieSBsb29raW5nIGF0IGFuIGV4YW1wbGUgb2YgKmJpbmFyeSBjbGFzc2lmaWNhdGlvbiosIHdoZXJlIHRoZSBtb2RlbCBtdXN0IHByZWRpY3QgYSBsYWJlbCB0aGF0IGJlbG9uZ3MgdG8gb25lIG9mIHR3byBjbGFzc2VzLiBJbiB0aGlzIGV4ZXJjaXNlLCB3ZSdsbCB0cmFpbiBhIGJpbmFyeSBjbGFzc2lmaWVyIHRvIHByZWRpY3Qgd2hldGhlciBvciBub3QgYSBwYXRpZW50IHNob3VsZCBiZSB0ZXN0ZWQgZm9yIGRpYWJldGVzIGJhc2VkIG9uIHNvbWUgbWVkaWNhbCBkYXRhLg0KDQojIyMgRXhwbG9yZSB0aGUgZGF0YQ0KDQpUaGUgZmlyc3Qgc3RlcCBpbiBhbnkgbWFjaGluZSBsZWFybmluZyBwcm9qZWN0IGlzIHRvIGBleHBsb3JlIHRoZSBkYXRhYCB0aGF0IHlvdSB3aWxsIHVzZSB0byB0cmFpbiBhIG1vZGVsLiBBbmQgYmVmb3JlIHdlIGNhbiBleHBsb3JlIHRoZSBkYXRhLCB3ZSBtdXN0IGZpcnN0IGhhdmUgaXQgaW4gb3VyIGVudmlyb25tZW50LCByaWdodD8NCg0KU28sIGxldCdzIGJlZ2luIGJ5IGltcG9ydGluZyBhIENTViBmaWxlIG9mIHBhdGVudCBkYXRhIGludG8gYSBgdGliYmxlYCAoYSBtb2Rlcm4gYSBtb2Rlcm4gcmVpbWFnaW5pbmcgb2YgdGhlIGRhdGEgZnJhbWUpOg0KDQo+ICoqQ2l0YXRpb24qKjogVGhlIGRpYWJldGVzIGRhdGFzZXQgdXNlZCBpbiB0aGlzIGV4ZXJjaXNlIGlzIGJhc2VkIG9uIGRhdGEgb3JpZ2luYWxseSBjb2xsZWN0ZWQgYnkgdGhlIE5hdGlvbmFsIEluc3RpdHV0ZSBvZiBEaWFiZXRlcyBhbmQgRGlnZXN0aXZlIGFuZCBLaWRuZXkgRGlzZWFzZXMuDQoNCmBgYHtyIHJlYWRfdXJsLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZXhlcmNpc2Uuc2V0dXAgPSAic2V0dXBBIn0NCiMgTG9hZCB0aGUgY29yZSB0aWR5dmVyc2UgYW5kIG1ha2UgaXQgYXZhaWxhYmxlIGluIHlvdXIgY3VycmVudCBSIHNlc3Npb24NCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQoNCiMgUmVhZCB0aGUgY3N2IGZpbGUgaW50byBhIHRpYmJsZQ0KZGlhYmV0ZXMgPC0gcmVhZF9jc3YoZmlsZSA9ICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vTWljcm9zb2Z0RG9jcy9tbC1iYXNpY3MvbWFzdGVyL2RhdGEvZGlhYmV0ZXMuY3N2IikNCg0KDQojIFByaW50IHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSBkYXRhDQpkaWFiZXRlcyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDEwKQ0KDQoNCg0KYGBgDQoNClNvbWV0aW1lcywgd2UgbWF5IHdhbnQgc29tZSBsaXR0bGUgbW9yZSBpbmZvcm1hdGlvbiBvbiBvdXIgZGF0YS4gV2UgY2FuIGhhdmUgYSBsb29rIGF0IHRoZSBgZGF0YWAsIGBpdHMgc3RydWN0dXJlYCBhbmQgdGhlIGBkYXRhIHR5cGVgIG9mIGl0cyBmZWF0dXJlcyBieSB1c2luZyB0aGUgWypnbGltcHNlKCkqXShodHRwczovL3BpbGxhci5yLWxpYi5vcmcvcmVmZXJlbmNlL2dsaW1wc2UuaHRtbCkgZnVuY3Rpb24gYXMgYmVsb3c6DQoNCmBgYHtyIGdsaW1wc2UsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBUYWtlIGEgcXVpY2sgZ2xhbmNlIGF0IHRoZSBkYXRhDQpkaWFiZXRlcyAlPiUgDQogIGdsaW1wc2UoKQ0KYGBgDQoNClRoaXMgZGF0YSBjb25zaXN0cyBvZiBkaWFnbm9zdGljIGluZm9ybWF0aW9uIGFib3V0IHNvbWUgcGF0aWVudHMgd2hvIGhhdmUgYmVlbiB0ZXN0ZWQgZm9yIGRpYWJldGVzLiBOb3RlIHRoYXQgdGhlIGZpbmFsIGNvbHVtbiBpbiB0aGUgZGF0YXNldCAoYERpYWJldGljYCkgY29udGFpbnMgdGhlIHZhbHVlICpgMGAqIGZvciBwYXRpZW50cyB3aG8gdGVzdGVkIGBuZWdhdGl2ZWAgZm9yIGRpYWJldGVzLCBhbmQgKmAxYCogZm9yIHBhdGllbnRzIHdobyB0ZXN0ZWQgcG9zaXRpdmUuIFRoaXMgaXMgdGhlIGxhYmVsIHRoYXQgd2Ugd2lsbCB0cmFpbiBvdXIgbW9kZWwgdG8gcHJlZGljdDsgbW9zdCBvZiB0aGUgb3RoZXIgY29sdW1ucyAoKipQcmVnbmFuY2llcyoqLCAqKlBsYXNtYUdsdWNvc2UqKiwgKipEaWFzdG9saWNCbG9vZFByZXNzdXJlKiosICoqQk1JKiogYW5kIHNvIG9uKSBhcmUgdGhlIGZlYXR1cmVzIHdlIHdpbGwgdXNlIHRvIHByZWRpY3QgdGhlICoqRGlhYmV0aWMqKiBsYWJlbC4NCg0KTGV0J3Mga2ljayBvZmYgb3VyIGFkdmVudHVyZSBieSByZWZvcm1hdHRpbmcgdGhlIGRhdGEgdG8gbWFrZSBpdCBlYXNpZXIgZm9yIGEgbW9kZWwgdG8gdXNlIGVmZmVjdGl2ZWx5LiBGb3Igbm93LCBsZXQncyBkcm9wIHRoZSBQYXRpZW50SUQgY29sdW1uLCBlbmNvZGUgdGhlIERpYWJldGljIGNvbHVtbiBhcyBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlLCBhbmQgbWFrZSB0aGUgY29sdW1uIG5hbWVzIGEgYml0IGZyaWVuZF9saWVSOg0KDQpgYGB7ciByZWZvcm1hdH0NCiMgTG9hZCB0aGUgamFuaXRvciBwYWNrYWdlIGZvciBjbGVhbmluZyBkYXRhDQpsaWJyYXJ5KGphbml0b3IpDQoNCiMgQ2xlYW4gZGF0YSBhIGJpdA0KZGlhYmV0ZXNfc2VsZWN0IDwtIGRpYWJldGVzICU+JQ0KICAjIEVuY29kZSBEaWFiZXRpYyBhcyBjYXRlZ29yeQ0KICBtdXRhdGUoRGlhYmV0aWMgPSBmYWN0b3IoRGlhYmV0aWMsIGxldmVscyA9IGMoIjEiLCIwIikpKSAlPiUgDQogICMgRHJvcCBQYXRpZW50SUQgY29sdW1uDQogIHNlbGVjdCgtUGF0aWVudElEKSAlPiUgDQogICMgQ2xlYW4gY29sdW1uIG5hbWVzDQogIGNsZWFuX25hbWVzKCkNCg0KDQojIFZpZXcgZGF0YSBzZXQNCmRpYWJldGVzX3NlbGVjdCAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDEwKQ0KYGBgDQoNClRoZSBnb2FsIG9mIHRoaXMgZXhwbG9yYXRpb24gaXMgdG8gdHJ5IHRvIHVuZGVyc3RhbmQgdGhlIGByZWxhdGlvbnNoaXBzYCBiZXR3ZWVuIGl0cyBhdHRyaWJ1dGVzOyBpbiBwYXJ0aWN1bGFyLCBhbnkgYXBwYXJlbnQgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgKmZlYXR1cmVzKiBhbmQgdGhlICpsYWJlbCogeW91ciBtb2RlbCB3aWxsIHRyeSB0byBwcmVkaWN0LiBPbmUgd2F5IG9mIGRvaW5nIHRoaXMgaXMgYnkgdXNpbmcgZGF0YSB2aXN1YWxpemF0aW9uLg0KDQpOb3cgbGV0J3MgY29tcGFyZSB0aGUgZmVhdHVyZSBkaXN0cmlidXRpb25zIGZvciBlYWNoIGxhYmVsIHZhbHVlLiBXZSdsbCBiZWdpbiBieSBmb3JtYXR0aW5nIHRoZSBkYXRhIHRvIGEgKmxvbmcqIGZvcm1hdCB0byBtYWtlIGl0IHNvbWV3aGF0IGVhc2llciB0byBtYWtlIG11bHRpcGxlIGZhY2V0cy4NCg0KYGBge3IgbG9uZ19mb3JtYXQsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBQaXZvdCBkYXRhIHRvIGEgbG9uZyBmb3JtYXQNCmRpYWJldGVzX3NlbGVjdF9sb25nIDwtIGRpYWJldGVzX3NlbGVjdCAlPiUgDQogICAgcGl2b3RfbG9uZ2VyKCFkaWFiZXRpYywgbmFtZXNfdG8gPSAiZmVhdHVyZXMiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikNCg0KDQojIFByaW50IHRoZSBmaXJzdCAxMCByb3dzDQpkaWFiZXRlc19zZWxlY3RfbG9uZyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDEwKQ0KYGBgDQoNClBlcmZlY3QhIE5vdywgbGV0J3MgbWFrZSBzb21lIHBsb3RzLg0KDQpgYGB7ciBwbG90X2xvbmcsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KdGhlbWVfc2V0KHRoZW1lX2xpZ2h0KCkpDQojIE1ha2UgYSBib3ggcGxvdCBmb3IgZWFjaCBwcmVkaWN0b3IgZmVhdHVyZQ0KZGlhYmV0ZXNfc2VsZWN0X2xvbmcgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gZGlhYmV0aWMsIHkgPSB2YWx1ZXMsIGZpbGwgPSBmZWF0dXJlcykpICsNCiAgZ2VvbV9ib3hwbG90KCkgKyANCiAgZmFjZXRfd3JhcCh+IGZlYXR1cmVzLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA0KSArDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb24gPSAicGxhc21hIiwgZW5kID0gLjcpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpgYGANCg0KQW1hemluZ/CfpKkhIEZvciBzb21lIG9mIHRoZSBmZWF0dXJlcywgdGhlcmUncyBhIG5vdGljZWFibGUgZGlmZmVyZW5jZSBpbiB0aGUgZGlzdHJpYnV0aW9uIGZvciBlYWNoIGxhYmVsIHZhbHVlLiBJbiBwYXJ0aWN1bGFyLCBgUHJlZ25hbmNpZXNgIGFuZCBgQWdlYCBzaG93IG1hcmtlZGx5IGRpZmZlcmVudCBkaXN0cmlidXRpb25zIGZvciBkaWFiZXRpYyBwYXRpZW50cyB0aGFuIGZvciBub24tZGlhYmV0aWMgcGF0aWVudHMuIFRoZXNlIGZlYXR1cmVzIG1heSBoZWxwIHByZWRpY3Qgd2hldGhlciBvciBub3QgYSBwYXRpZW50IGlzIGRpYWJldGljLg0KDQojIyMgU3BsaXQgdGhlIGRhdGENCg0KT3VyIGRhdGFzZXQgaW5jbHVkZXMga25vd24gdmFsdWVzIGZvciB0aGUgbGFiZWwsIHNvIHdlIGNhbiB1c2UgdGhpcyB0byB0cmFpbiBhIGNsYXNzaWZpZXIgc28gdGhhdCBpdCBmaW5kcyBhIHN0YXRpc3RpY2FsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBmZWF0dXJlcyBhbmQgdGhlIGxhYmVsIHZhbHVlOyBidXQgaG93IHdpbGwgd2Uga25vdyBpZiBvdXIgbW9kZWwgaXMgYW55IGdvb2Q/IEhvdyBkbyB3ZSBrbm93IGl0IHdpbGwgcHJlZGljdCBjb3JyZWN0bHkgd2hlbiB3ZSB1c2UgaXQgd2l0aCBuZXcgZGF0YSB0aGF0IGl0IHdhc24ndCB0cmFpbmVkIHdpdGg/DQoNCkl0IGlzIGJlc3QgcHJhY3RpY2UgdG8gaG9sZCBvdXQgc29tZSBvZiB5b3VyIGRhdGEgZm9yICoqdGVzdGluZyoqIGluIG9yZGVyIHRvIGdldCBhIGJldHRlciBlc3RpbWF0ZSBvZiBob3cgeW91ciBtb2RlbHMgd2lsbCBwZXJmb3JtIG9uIG5ldyBkYXRhIGJ5IGNvbXBhcmluZyB0aGUgcHJlZGljdGVkIGxhYmVscyB3aXRoIHRoZSBhbHJlYWR5IGtub3duIGxhYmVscyBpbiB0aGUgdGVzdCBzZXQuDQoNCldlbGwsIHdlIGNhbiB0YWtlIGFkdmFudGFnZSBvZiB0aGUgZmFjdCB3ZSBoYXZlIGEgbGFyZ2UgZGF0YXNldCB3aXRoIGtub3duIGxhYmVsIHZhbHVlcywgdXNlIG9ubHkgc29tZSBvZiBpdCB0byB0cmFpbiB0aGUgbW9kZWwsIGFuZCBob2xkIGJhY2sgc29tZSB0byB0ZXN0IHRoZSB0cmFpbmVkIG1vZGVsIC0gZW5hYmxpbmcgdXMgdG8gY29tcGFyZSB0aGUgcHJlZGljdGVkIGxhYmVscyB3aXRoIHRoZSBhbHJlYWR5IGtub3duIGxhYmVscyBpbiB0aGUgdGVzdCBzZXQuDQoNCkluIFIsIHRoZSBhbWF6aW5nIFRpZHltb2RlbHMgZnJhbWV3b3JrIHByb3ZpZGVzIGEgY29sbGVjdGlvbiBvZiBwYWNrYWdlcyBmb3IgbW9kZWxpbmcgYW5kIG1hY2hpbmUgbGVhcm5pbmcgdXNpbmcgKip0aWR5dmVyc2UqKiBwcmluY2lwbGVzLiBGb3IgaW5zdGFuY2UsIFtyc2FtcGxlXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvKSwgYSBwYWNrYWdlIGluIFRpZHltb2RlbHMsIHByb3ZpZGVzIGluZnJhc3RydWN0dXJlIGZvciBlZmZpY2llbnQgZGF0YSBzcGxpdHRpbmcgYW5kIHJlc2FtcGxpbmc6DQoNCi0gICBgaW5pdGlhbF9zcGxpdCgpYDogc3BlY2lmaWVzIGhvdyBkYXRhIHdpbGwgYmUgc3BsaXQgaW50byBhIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldA0KDQotICAgYHRyYWluaW5nKClgIGFuZCBgdGVzdGluZygpYCBmdW5jdGlvbnMgZXh0cmFjdCB0aGUgZGF0YSBpbiBlYWNoIHNwbGl0DQoNCnVzZSBgP2luaXRpYWxfc3BsaXQoKWAgZm9yIG1vcmUgZGV0YWlscy4NCg0KPiBIZXJlIGlzIGEgZ3JlYXQgcGxhY2UgdG8gZ2V0IHN0YXJ0ZWQgd2l0aCBUaWR5bW9kZWxzOiBbR2V0IFN0YXJ0ZWRdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnL3N0YXJ0LykNCg0KYGBge3IgcGxvdCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIExvYWQgdGhlIFRpZHltb2RlbHMgcGFja2FnZXMNCmxpYnJhcnkodGlkeW1vZGVscykNCg0KDQoNCiMgU3BsaXQgZGF0YSBpbnRvIDcwJSBmb3IgdHJhaW5pbmcgYW5kIDMwJSBmb3IgdGVzdGluZw0Kc2V0LnNlZWQoMjA1NikNCmRpYWJldGVzX3NwbGl0IDwtIGRpYWJldGVzX3NlbGVjdCAlPiUgDQogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuNzApDQoNCg0KIyBFeHRyYWN0IHRoZSBkYXRhIGluIGVhY2ggc3BsaXQNCmRpYWJldGVzX3RyYWluIDwtIHRyYWluaW5nKGRpYWJldGVzX3NwbGl0KQ0KZGlhYmV0ZXNfdGVzdCA8LSB0ZXN0aW5nKGRpYWJldGVzX3NwbGl0KQ0KDQoNCiMgUHJpbnQgdGhlIG51bWJlciBvZiBjYXNlcyBpbiBlYWNoIHNwbGl0DQpjYXQoIlRyYWluaW5nIGNhc2VzOiAiLCBucm93KGRpYWJldGVzX3RyYWluKSwgIlxuIiwNCiAgICAiVGVzdCBjYXNlczogIiwgbnJvdyhkaWFiZXRlc190ZXN0KSwgc2VwID0gIiIpDQoNCg0KIyBQcmludCBvdXQgdGhlIGZpcnN0IDUgcm93cyBvZiB0aGUgdHJhaW5pbmcgc2V0DQpkaWFiZXRlc190cmFpbiAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQoNCmBgYA0KDQojIyMgVHJhaW4gYW5kIEV2YWx1YXRlIGEgQmluYXJ5IENsYXNzaWZpY2F0aW9uIE1vZGVsDQoNCk9LLCBub3cgd2UncmUgcmVhZHkgdG8gdHJhaW4gb3VyIG1vZGVsIGJ5IGZpdHRpbmcgdGhlIHRyYWluaW5nIGZlYXR1cmVzIHRvIHRoZSB0cmFpbmluZyBsYWJlbHMgKGBkaWFiZXRpY2ApLiBUaGVyZSBhcmUgdmFyaW91cyBhbGdvcml0aG1zIHdlIGNhbiB1c2UgdG8gdHJhaW4gdGhlIG1vZGVsLiBJbiB0aGlzIGV4YW1wbGUsIHdlJ2xsIHVzZSAqYExvZ2lzdGljIFJlZ3Jlc3Npb25gKiwgd2hpY2ggKGRlc3BpdGUgaXRzIG5hbWUpIGlzIGEgd2VsbC1lc3RhYmxpc2hlZCBhbGdvcml0aG0gZm9yIGNsYXNzaWZpY2F0aW9uLiBMb2dpc3RpYyByZWdyZXNzaW9uIGlzIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSwgbWVhbmluZyBpdCBwcmVkaWN0cyAyIGNhdGVnb3JpZXMuDQoNClRoZXJlIGFyZSBxdWl0ZSBhIG51bWJlciBvZiB3YXlzIHRvIGZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgaW4gVGlkeW1vZGVscy4gU2VlIGA/bG9naXN0aWNfcmVnKClgIEZvciBub3csIGxldCdzIGZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdmlhIHRoZSBkZWZhdWx0IGBzdGF0czo6Z2xtKClgIGVuZ2luZS4NCg0KYGBge3IgbG9nX2dsbSwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIE1ha2UgYSBtb2RlbCBzcGVjaWZjYXRpb24NCmxvZ3JlZ19zcGVjIDwtIGxvZ2lzdGljX3JlZygpICU+JSANCiAgc2V0X2VuZ2luZSgiZ2xtIikgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQoNCiMgUHJpbnQgdGhlIG1vZGVsIHNwZWNpZmljYXRpb24NCmxvZ3JlZ19zcGVjDQpgYGANCg0KQWZ0ZXIgYSBtb2RlbCBoYXMgYmVlbiAqc3BlY2lmaWVkKiwgdGhlIG1vZGVsIGNhbiBiZSBgZXN0aW1hdGVkYCBvciBgdHJhaW5lZGAgdXNpbmcgdGhlIFtgZml0KClgXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3BhcnNuaXAvcmVmZXJlbmNlL2ZpdC5odG1sKSBmdW5jdGlvbiwgdHlwaWNhbGx5IHVzaW5nIGEgc3ltYm9saWMgZGVzY3JpcHRpb24gb2YgdGhlIG1vZGVsIChhIGZvcm11bGEpIGFuZCBzb21lIGRhdGEuDQoNCmBgYHtyIGxvZ19nbG1fZml0LCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgVHJhaW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQpsb2dyZWdfZml0IDwtIGxvZ3JlZ19zcGVjICU+JSANCiAgZml0KGRpYWJldGljIH4gLiwgZGF0YSA9IGRpYWJldGVzX3RyYWluKQ0KDQoNCiMgUHJpbnQgdGhlIG1vZGVsIG9iamVjdA0KbG9ncmVnX2ZpdA0KDQoNCmBgYA0KDQpUaGUgbW9kZWwgcHJpbnQgb3V0IHNob3dzIHRoZSBjb2VmZmljaWVudHMgbGVhcm5lZCBkdXJpbmcgdHJhaW5pbmcuDQoNCk5vdyB3ZSd2ZSB0cmFpbmVkIHRoZSBtb2RlbCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSwgd2UgY2FuIHVzZSB0aGUgdGVzdCBkYXRhIHdlIGhlbGQgYmFjayB0byBldmFsdWF0ZSBob3cgd2VsbCBpdCBwcmVkaWN0cyB1c2luZyBbcGFyc25pcDo6cHJlZGljdCgpXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3ByZWRpY3QubW9kZWxfZml0Lmh0bWwpLiBMZXQncyBzdGFydCBieSB1c2luZyB0aGUgbW9kZWwgdG8gcHJlZGljdCBsYWJlbHMgZm9yIG91ciB0ZXN0IHNldCwgYW5kIGNvbXBhcmUgdGhlIHByZWRpY3RlZCBsYWJlbHMgdG8gdGhlIGtub3duIGxhYmVsczoNCg0KYGBge3IgbW9kZWxfZXZhbCxtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBNYWtlIHByZWRpY3Rpb25zIHRoZW4gYmluZCB0aGVtIHRvIHRoZSB0ZXN0IHNldA0KcmVzdWx0cyA8LSBkaWFiZXRlc190ZXN0ICU+JSBzZWxlY3QoZGlhYmV0aWMpICU+JSANCiAgYmluZF9jb2xzKGxvZ3JlZ19maXQgJT4lIHByZWRpY3QobmV3X2RhdGEgPSBkaWFiZXRlc190ZXN0KSkNCg0KDQojIENvbXBhcmUgcHJlZGljdGlvbnMNCnJlc3VsdHMgJT4lIA0KICBzbGljZV9oZWFkKG4gPSAxMCkNCmBgYA0KDQpDb21wYXJpbmcgZWFjaCBwcmVkaWN0aW9uIHdpdGggaXRzIGNvcnJlc3BvbmRpbmcgImdyb3VuZCB0cnV0aCIgYWN0dWFsIHZhbHVlIGlzbid0IGEgdmVyeSBlZmZpY2llbnQgd2F5IHRvIGRldGVybWluZSBob3cgd2VsbCB0aGUgbW9kZWwgaXMgcHJlZGljdGluZy4gRm9ydHVuYXRlbHksIFRpZHltb2RlbHMgaGFzIGEgZmV3IG1vcmUgdHJpY2tzIHVwIGl0cyBzbGVldmU6IFtgeWFyZHN0aWNrYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvKSAtIGEgcGFja2FnZSB1c2VkIHRvIG1lYXN1cmUgdGhlIGVmZmVjdGl2ZW5lc3Mgb2YgbW9kZWxzIHVzaW5nIHBlcmZvcm1hbmNlIG1ldHJpY3MuDQoNClRoZSBtb3N0IG9idmlvdXMgdGhpbmcgeW91IG1pZ2h0IHdhbnQgdG8gZG8gaXMgdG8gY2hlY2sgdGhlICphY2N1cmFjeSogb2YgdGhlIHByZWRpY3Rpb25zIC0gaW4gc2ltcGxlIHRlcm1zLCB3aGF0IHByb3BvcnRpb24gb2YgdGhlIGxhYmVscyBkaWQgdGhlIG1vZGVsIHByZWRpY3QgY29ycmVjdGx5Pw0KDQpgeWFyZHN0aWNrOjphY2N1cmFjeSgpYCBkb2VzIGp1c3QgdGhhdCENCg0KYGBge3IgYWNjLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIENhbGN1bGF0ZSBhY2N1cmFjeTogcHJvcG9ydGlvbiBvZiBkYXRhIHByZWRpY3RlZCBjb3JyZWN0bHkNCmFjY3VyYWN5KGRhdGEgPSByZXN1bHRzLCB0cnV0aCA9IGRpYWJldGljLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQ0KYGBgDQoNClRoZSBhY2N1cmFjeSBpcyByZXR1cm5lZCBhcyBhIGRlY2ltYWwgdmFsdWUgLSBhIHZhbHVlIG9mIDEuMCB3b3VsZCBtZWFuIHRoYXQgdGhlIG1vZGVsIGdvdCAxMDAlIG9mIHRoZSBwcmVkaWN0aW9ucyByaWdodDsgd2hpbGUgYW4gYWNjdXJhY3kgb2YgMC4wIGlzLCB3ZWxsLCBwcmV0dHkgdXNlbGVzcyDwn5iQIQ0KDQpBY2N1cmFjeSBzZWVtcyBsaWtlIGEgc2Vuc2libGUgbWV0cmljIHRvIGV2YWx1YXRlIChhbmQgdG8gYSBjZXJ0YWluIGV4dGVudCBpdCBpcyksIGJ1dCB5b3UgbmVlZCB0byBiZSBjYXJlZnVsIGFib3V0IGRyYXdpbmcgdG9vIG1hbnkgY29uY2x1c2lvbnMgZnJvbSB0aGUgYWNjdXJhY3kgb2YgYSBjbGFzc2lmaWVyLiBSZW1lbWJlciB0aGF0IGl0J3Mgc2ltcGx5IGEgbWVhc3VyZSBvZiBob3cgbWFueSBjYXNlcyB3ZXJlIHByZWRpY3RlZCBjb3JyZWN0bHkuIFN1cHBvc2Ugb25seSAzJSBvZiB0aGUgcG9wdWxhdGlvbiBpcyBkaWFiZXRpYy4gWW91IGNvdWxkIGNyZWF0ZSBhIGNsYXNzaWZpZXIgdGhhdCBhbHdheXMganVzdCBwcmVkaWN0cyAwLCBhbmQgaXQgd291bGQgYmUgOTclIGFjY3VyYXRlIC0gYnV0IG5vdCB0ZXJyaWJseSBoZWxwZnVsIGluIGlkZW50aWZ5aW5nIHBhdGllbnRzIHdpdGggZGlhYmV0ZXMhDQoNCkZvcnR1bmF0ZWx5LCB0aGVyZSBhcmUgc29tZSBvdGhlciBtZXRyaWNzIHRoYXQgcmV2ZWFsIGEgbGl0dGxlIG1vcmUgYWJvdXQgaG93IG91ciBjbGFzc2lmaWNhdGlvbiBtb2RlbCBpcyBwZXJmb3JtaW5nLg0KDQpPbmUgcGVyZm9ybWFuY2UgbWV0cmljIGFzc29jaWF0ZWQgd2l0aCBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyBpcyB0aGUgW2Bjb25mdXNpb24gbWF0cml4YF0oaHR0cHM6Ly93aWtpcGVkaWEub3JnL3dpa2kvQ29uZnVzaW9uX21hdHJpeCkuIEEgY29uZnVzaW9uIG1hdHJpeCBkZXNjcmliZXMgaG93IHdlbGwgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBwZXJmb3JtcyBieSB0YWJ1bGF0aW5nIGhvdyBtYW55IGV4YW1wbGVzIGluIGVhY2ggY2xhc3Mgd2VyZSBjb3JyZWN0bHkgY2xhc3NpZmllZCBieSBhIG1vZGVsLiBJbiBvdXIgY2FzZSwgaXQgd2lsbCBzaG93IHlvdSBob3cgbWFueSBjYXNlcyB3ZXJlIGNsYXNzaWZpZWQgYXMgbmVnYXRpdmUgKDApIGFuZCBob3cgbWFueSBhcyBwb3NpdGl2ZSAoMSk7IHRoZSBjb25mdXNpb24gbWF0cml4IGFsc28gc2hvd3MgeW91IGhvdyBtYW55IHdlcmUgY2xhc3NpZmllZCBpbnRvIHRoZSAqd3JvbmcqIGNhdGVnb3JpZXMuDQoNClRoZSBbYGNvbmZfbWF0KClgXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3lhcmRzdGljay9yZWZlcmVuY2UvY29uZl9tYXQuaHRtbCkgZnVuY3Rpb24gZnJvbSB5YXJkc3RpY2sgY2FsY3VsYXRlcyB0aGlzIGNyb3NzLXRhYnVsYXRpb24gb2Ygb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBjbGFzc2VzLg0KDQpgYGB7ciBjb25mX21hdH0NCiMgQ29uZnVzaW9uIG1hdHJpeCBmb3IgcHJlZGljdGlvbiByZXN1bHRzDQpjb25mX21hdChkYXRhID0gcmVzdWx0cywgdHJ1dGggPSBkaWFiZXRpYywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykNCg0KYGBgDQoNCkF3ZXNvbWUhDQoNCkxldCdzIGludGVycHJldCB0aGUgY29uZnVzaW9uIG1hdHJpeC4gT3VyIG1vZGVsIGlzIGFza2VkIHRvIGNsYXNzaWZ5IGNhc2VzIGJldHdlZW4gdHdvIGJpbmFyeSBjYXRlZ29yaWVzLCBjYXRlZ29yeSBgMWAgZm9yIHBhdGllbnRzIHdobyB0ZXN0ZWQgcG9zaXRpdmUgZm9yIGRpYWJldGVzIGFuZCBjYXRlZ29yeSBgMGAgZm9yIHBhdGllbnRzIHdobyB0ZXN0ZWQgbmVnYXRpdmUuDQoNCi0gICBJZiB5b3VyIG1vZGVsIHByZWRpY3RzIGEgcGF0aWVudCBhcyBgMWAgKHBvc2l0aXZlKSBhbmQgdGhleSBiZWxvbmcgdG8gY2F0ZWdvcnkgYDFgIChwb3NpdGl2ZSkgaW4gcmVhbGl0eSB3ZSBjYWxsIHRoaXMgYSBgdHJ1ZSBwb3NpdGl2ZWAsIHNob3duIGJ5IHRoZSB0b3AgbGVmdCBudW1iZXIgYDg5N2AuDQoNCi0gICBJZiB5b3VyIG1vZGVsIHByZWRpY3RzIGEgcGF0aWVudCBhcyBgMGAgKG5lZ2F0aXZlKSBhbmQgdGhleSBiZWxvbmcgdG8gY2F0ZWdvcnkgYDFgIChwb3NpdGl2ZSkgaW4gcmVhbGl0eSB3ZSBjYWxsIHRoaXMgYSBgZmFsc2UgbmVnYXRpdmVgLCBzaG93biBieSB0aGUgYm90dG9tIGxlZnQgbnVtYmVyIGA2NTdgLg0KDQotICAgSWYgeW91ciBtb2RlbCBwcmVkaWN0cyBhIHBhdGllbnQgYXMgYDFgIChwb3NpdGl2ZSkgYW5kIHRoZXkgYmVsb25nIHRvIGNhdGVnb3J5IGAwYCAobmVnYXRpdmUpIGluIHJlYWxpdHkgd2UgY2FsbCB0aGlzIGEgYGZhbHNlIHBvc2l0aXZlYCwgc2hvd24gYnkgdGhlIHRvcCByaWdodCBudW1iZXIgYDI5M2AuDQoNCi0gICBJZiB5b3VyIG1vZGVsIHByZWRpY3RzIGEgcGF0aWVudCBhcyBgMGAgKG5lZ2F0aXZlKSBhbmQgdGhleSBiZWxvbmcgdG8gY2F0ZWdvcnkgYDBgIChuZWdhdGl2ZSkgaW4gcmVhbGl0eSB3ZSBjYWxsIHRoaXMgYSBgdHJ1ZSBuZWdhdGl2ZWAsIHNob3duIGJ5IHRoZSBib3R0b20gcmlnaHQgbnVtYmVyIGAyNjUzYC4NCg0KT3VyIGNvbmZ1c2lvbiBtYXRyaXggY2FuIHRodXMgYmUgZXhwcmVzc2VkIGluIHRoZSBmb2xsb3dpbmcgZm9ybToNCg0KfCBUcnV0aCB8DQp8Oi0tLS0tOnwNCg0KfCAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgIHwNCnw6LS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS0tLS0tLS0tLTp8DQp8ICoqUHJlZGljdGVkKiogfCAgICAgICAgMSAgICAgICAgIHwgICAgICAgICAwICAgICAgICAgfA0KfCAgICAgICAxICAgICAgIHwgJDg5N197XCBcIFwgVFB9JCB8ICQyOTNfe1wgXCBcIEZQfSQgIHwNCnwgICAgICAgMCAgICAgICB8ICQ2NTdfe1wgXCBcIEZOfSQgfCAkMjY1M197XCBcIFwgVE59JCB8DQoNCk5vdGUgdGhhdCB0aGUgY29ycmVjdCAoKmB0cnVlYCopIHByZWRpY3Rpb25zIGZvcm0gYSBkaWFnb25hbCBsaW5lIGZyb20gdG9wIGxlZnQgdG8gYm90dG9tIHJpZ2h0IC0gdGhlc2UgZmlndXJlcyBzaG91bGQgYmUgc2lnbmlmaWNhbnRseSBoaWdoZXIgdGhhbiB0aGUgKmZhbHNlKiBwcmVkaWN0aW9ucyBpZiB0aGUgbW9kZWwgaXMgYW55IGdvb2QuDQoNCkFzIHlvdSBtaWdodCBoYXZlIGd1ZXNzZWQgaXQncyBwcmVmZXJhYmxlIHRvIGhhdmUgYSBsYXJnZXIgbnVtYmVyIG9mIHRydWUgcG9zaXRpdmVzIGFuZCB0cnVlIG5lZ2F0aXZlcyBhbmQgYSBsb3dlciBudW1iZXIgb2YgZmFsc2UgcG9zaXRpdmVzIGFuZCBmYWxzZSBuZWdhdGl2ZXMsIHdoaWNoIGltcGxpZXMgdGhhdCB0aGUgbW9kZWwgcGVyZm9ybXMgYmV0dGVyLg0KDQpUaGUgY29uZnVzaW9uIG1hdHJpeCBpcyBoZWxwZnVsIHNpbmNlIGl0IGdpdmVzIHJpc2UgdG8gb3RoZXIgbWV0cmljcyB0aGF0IGNhbiBoZWxwIHVzIGJldHRlciBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbC4gTGV0J3MgZ28gdGhyb3VnaCBzb21lIG9mIHRoZW06DQoNCvCfjpMgUHJlY2lzaW9uOiBgVFAvKFRQICsgRlApYCBkZWZpbmVkIGFzIHRoZSBwcm9wb3J0aW9uIG9mIHByZWRpY3RlZCBwb3NpdGl2ZXMgdGhhdCBhcmUgYWN0dWFsbHkgcG9zaXRpdmUuIEFsc28gY2FsbGVkIFtwb3NpdGl2ZSBwcmVkaWN0aXZlIHZhbHVlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qb3NpdGl2ZV9wcmVkaWN0aXZlX3ZhbHVlICJQb3NpdGl2ZSBwcmVkaWN0aXZlIHZhbHVlIikNCg0K8J+OkyBSZWNhbGw6IGBUUC8oVFAgKyBGTilgIGRlZmluZWQgYXMgdGhlIHByb3BvcnRpb24gb2YgcG9zaXRpdmUgcmVzdWx0cyBvdXQgb2YgdGhlIG51bWJlciBvZiBzYW1wbGVzIHdoaWNoIHdlcmUgYWN0dWFsbHkgcG9zaXRpdmUuIEFsc28ga25vd24gYXMgYHNlbnNpdGl2aXR5YC4NCg0K8J+OkyBTcGVjaWZpY2l0eTogYFROLyhUTiArIEZQKWAgZGVmaW5lZCBhcyB0aGUgcHJvcG9ydGlvbiBvZiBuZWdhdGl2ZSByZXN1bHRzIG91dCBvZiB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgd2hpY2ggd2VyZSBhY3R1YWxseSBuZWdhdGl2ZS4NCg0K8J+OkyBBY2N1cmFjeTogYFRQICsgVE4vKFRQICsgVE4gKyBGUCArIEZOKWAgVGhlIHBlcmNlbnRhZ2Ugb2YgbGFiZWxzIHByZWRpY3RlZCBhY2N1cmF0ZWx5IGZvciBhIHNhbXBsZS4NCg0K8J+OkyBGIE1lYXN1cmU6IEEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgcHJlY2lzaW9uIGFuZCByZWNhbGwsIHdpdGggYmVzdCBiZWluZyAxIGFuZCB3b3JzdCBiZWluZyAwLg0KDQpUaWR5bW9kZWxzIHByb3ZpZGVzIHlldCBhbm90aGVyIHN1Y2NpbmN0IHdheSBvZiBldmFsdWF0aW5nIGFsbCB0aGVzZSBtZXRyaWNzLiBVc2luZyBgeWFyZHN0aWNrOjptZXRyaWNfc2V0KClgLCB5b3UgY2FuIGNvbWJpbmUgbXVsdGlwbGUgbWV0cmljcyB0b2dldGhlciBpbnRvIGEgbmV3IGZ1bmN0aW9uIHRoYXQgY2FsY3VsYXRlcyBhbGwgb2YgdGhlbSBhdCBvbmNlLg0KDQpgYGB7ciBtZXRyaWNfc2V0fQ0KIyBDb21iaW5lIG1ldHJpY3MgYW5kIGV2YWx1YXRlIHRoZW0gYWxsIGF0IG9uY2UNCmV2YWxfbWV0cmljcyA8LSBtZXRyaWNfc2V0KHBwdiwgcmVjYWxsLCBhY2N1cmFjeSwgZl9tZWFzKQ0KZXZhbF9tZXRyaWNzKGRhdGEgPSByZXN1bHRzLCB0cnV0aCA9IGRpYWJldGljLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQ0KDQpgYGANCg0KVXNpbmcgdGhlIHByZWNpc2lvbiAocHB2KSBtZXRyaWMsIHdlIGFyZSBhYmxlIHRvIGFuc3dlciB0aGUgcXVlc3Rpb246DQoNCi0gICBPZiBhbGwgdGhlIHBhdGllbnRzIHRoZSBtb2RlbCBwcmVkaWN0ZWQgYXJlIGRpYWJldGljLCBob3cgbWFueSBhcmUgYWN0dWFsbHkgZGlhYmV0aWM/DQoNClVzaW5nIHRoZSByZWNhbGwgbWV0cmljLCB3ZSBhcmUgYWJsZSB0byBhbnN3ZXIgdGhlIHF1ZXN0aW9uOg0KDQotICAgT2YgYWxsIHRoZSBwYXRpZW50cyB0aGF0IGFyZSBhY3R1YWxseSBkaWFiZXRpYywgaG93IG1hbnkgZGlkIHRoZSBtb2RlbCBpZGVudGlmeT8NCg0KR3JlYXQgam9iLCB3ZSBqdXN0IG1hZGUgcHJlZGljdGlvbnMgYW5kIGV2YWx1YXRlZCB0aGVtIHVzaW5nIGEgbnVtYmVyIG9mIG1ldHJpY3MuDQoNClVudGlsIG5vdywgd2UndmUgY29uc2lkZXJlZCB0aGUgcHJlZGljdGlvbnMgZnJvbSB0aGUgbW9kZWwgYXMgYmVpbmcgZWl0aGVyIDEgb3IgMCBjbGFzcyBsYWJlbHMuIEFjdHVhbGx5LCB0aGluZ3MgYXJlIGEgbGl0dGxlIG1vcmUgY29tcGxleCB0aGFuIHRoYXQuIFN0YXRpc3RpY2FsIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcywgbGlrZSBsb2dpc3RpYyByZWdyZXNzaW9uLCBhcmUgYmFzZWQgb24gYHByb2JhYmlsaXR5YDsgc28gd2hhdCBhY3R1YWxseSBnZXRzIHByZWRpY3RlZCBieSBhIGJpbmFyeSBjbGFzc2lmaWVyIGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBsYWJlbCBpcyB0cnVlICgkUCh5KSQpIGFuZCB0aGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgbGFiZWwgaXMgZmFsc2UgKCQxLVAoeSkkKS4gQSB0aHJlc2hvbGQgdmFsdWUgb2YgMC41IGlzIHVzZWQgdG8gZGVjaWRlIHdoZXRoZXIgdGhlIHByZWRpY3RlZCBsYWJlbCBpcyBhIGAxYCAoJFAoeSk+MC41JCkgb3IgYSBgMGAgKCRQKHkpPD0wLjUkKS4gTGV0J3Mgc2VlIHRoZSBwcm9iYWJpbGl0eSBwYWlycyBmb3IgZWFjaCBjYXNlOg0KDQpgYGB7ciBwcm9ifQ0KIyBQcmVkaWN0IGNsYXNzIHByb2JhYmlsaXRpZXMgYW5kIGJpbmQgdGhlbSB0byByZXN1bHRzDQpyZXN1bHRzIDwtIHJlc3VsdHMgJT4lIA0KICBiaW5kX2NvbHMobG9ncmVnX2ZpdCAlPiUgDQogICAgICAgICAgICAgIHByZWRpY3QobmV3X2RhdGEgPSBkaWFiZXRlc190ZXN0LCB0eXBlID0gInByb2IiKSkNCg0KICANCg0KDQojIFByaW50IG91dCB0aGUgcmVzdWx0cw0KcmVzdWx0cyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDEwKQ0KDQoNCmBgYA0KDQpUaGUgZGVjaXNpb24gdG8gc2NvcmUgYSBwcmVkaWN0aW9uIGFzIGEgMSBvciBhIDAgZGVwZW5kcyBvbiB0aGUgdGhyZXNob2xkIHRvIHdoaWNoIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBhcmUgY29tcGFyZWQuIElmIHdlIHdlcmUgdG8gY2hhbmdlIHRoZSB0aHJlc2hvbGQsIGl0IHdvdWxkIGFmZmVjdCB0aGUgcHJlZGljdGlvbnM7IGFuZCB0aGVyZWZvcmUgY2hhbmdlIHRoZSBtZXRyaWNzIGluIHRoZSBjb25mdXNpb24gbWF0cml4LiBBIGNvbW1vbiB3YXkgdG8gZXZhbHVhdGUgYSBjbGFzc2lmaWVyIGlzIHRvIGV4YW1pbmUgdGhlICp0cnVlIHBvc2l0aXZlIHJhdGUqICh3aGljaCBpcyBhbm90aGVyIG5hbWUgZm9yIHJlY2FsbCkgYW5kIHRoZSAqZmFsc2UgcG9zaXRpdmUgcmF0ZSogKDEgLSBzcGVjaWZpY2l0eSkgZm9yIGEgcmFuZ2Ugb2YgcG9zc2libGUgdGhyZXNob2xkcy4gVGhlc2UgcmF0ZXMgYXJlIHRoZW4gcGxvdHRlZCBhZ2FpbnN0IGFsbCBwb3NzaWJsZSB0aHJlc2hvbGRzIHRvIGZvcm0gYSBjaGFydCBrbm93biBhcyBhICpyZWNlaXZlZCBvcGVyYXRvciBjaGFyYWN0ZXJpc3RpYyAoUk9DKSBjaGFydCosIGxpa2UgdGhpczoNCg0KYGBge3Igcm9jX2N1cnZlfQ0KIyBNYWtlIGEgcm9jX2NoYXJ0DQpyZXN1bHRzICU+JSANCiAgcm9jX2N1cnZlKHRydXRoID0gZGlhYmV0aWMsIC5wcmVkXzEpICU+JSANCiAgYXV0b3Bsb3QoKQ0KDQpgYGANCg0KVGhlIFJPQyBjaGFydCBzaG93cyB0aGUgY3VydmUgb2YgdGhlIHRydWUgYW5kIGZhbHNlIHBvc2l0aXZlIHJhdGVzIGZvciBkaWZmZXJlbnQgdGhyZXNob2xkIHZhbHVlcyBiZXR3ZWVuIDAgYW5kIDEuIEEgcGVyZmVjdCBjbGFzc2lmaWVyIHdvdWxkIGhhdmUgYSBjdXJ2ZSB0aGF0IGdvZXMgc3RyYWlnaHQgdXAgdGhlIGxlZnQgc2lkZSBhbmQgc3RyYWlnaHQgYWNyb3NzIHRoZSB0b3AuIFRoZSBkaWFnb25hbCBsaW5lIGFjcm9zcyB0aGUgY2hhcnQgcmVwcmVzZW50cyB0aGUgcHJvYmFiaWxpdHkgb2YgcHJlZGljdGluZyBjb3JyZWN0bHkgd2l0aCBhIDUwLzUwIHJhbmRvbSBwcmVkaWN0aW9uOyBzbyB5b3Ugb2J2aW91c2x5IHdhbnQgdGhlIGN1cnZlIHRvIGJlIGhpZ2hlciB0aGFuIHRoYXQgKG9yIHlvdXIgbW9kZWwgaXMgbm8gYmV0dGVyIHRoYW4gc2ltcGx5IGd1ZXNzaW5nISkuDQoNClRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSAoQVVDKSBpcyBhIHZhbHVlIGJldHdlZW4gMCBhbmQgMSB0aGF0IHF1YW50aWZpZXMgdGhlIG92ZXJhbGwgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsLiBPbmUgd2F5IG9mIGludGVycHJldGluZyBBVUMgaXMgYXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIG1vZGVsIHJhbmtzIGEgcmFuZG9tIHBvc2l0aXZlIGV4YW1wbGUgbW9yZSBoaWdobHkgdGhhbiBhIHJhbmRvbSBuZWdhdGl2ZSBleGFtcGxlLiBUaGUgY2xvc2VyIHRvIDEgdGhpcyB2YWx1ZSBpcywgdGhlIGJldHRlciB0aGUgbW9kZWwuIE9uY2UgYWdhaW4sIFRpZHltb2RlbHMgaW5jbHVkZXMgYSBmdW5jdGlvbiB0byBjYWxjdWxhdGUgdGhpcyBtZXRyaWM6IGB5YXJkc3RpY2s6OnJvY19hdWMoKWANCg0KYGBge3IgYXVjfQ0KIyBDb21wdXRlIHRoZSBBVUMNCnJlc3VsdHMgJT4lIA0KICByb2NfYXVjKGRpYWJldGljLCAucHJlZF8xKQ0KDQpgYGANCg0KIyMgMi4gUmVjaXBlcyBhbmQgd29ya2Zsb3dzDQoNCiMjIyMgRGF0YSBwcmVwcm9jZXNzaW5nIHdpdGggcmVjaXBlcw0KDQpJbiB0aGlzIGNhc2UsIHRoZSBST0MgY3VydmUgYW5kIGl0cyBBVUMgaW5kaWNhdGUgdGhhdCB0aGUgbW9kZWwgcGVyZm9ybXMgYmV0dGVyIHRoYW4gYSByYW5kb20gZ3Vlc3Mgd2hpY2ggaXMgbm90IGJhZCBjb25zaWRlcmluZyB3ZSBwZXJmb3JtZWQgdmVyeSBsaXR0bGUgcHJlcHJvY2Vzc2luZyBvZiB0aGUgZGF0YS4NCg0KSW4gcHJhY3RpY2UsIGl0J3MgY29tbW9uIHRvIHBlcmZvcm0gc29tZSBwcmVwcm9jZXNzaW5nIG9mIHRoZSBkYXRhIHRvIG1ha2UgaXQgZWFzaWVyIGZvciB0aGUgYWxnb3JpdGhtIHRvIGZpdCBhIG1vZGVsIHRvIGl0LiBUaGVyZSdzIGEgaHVnZSByYW5nZSBvZiBwcmVwcm9jZXNzaW5nIHRyYW5zZm9ybWF0aW9ucyB5b3UgY2FuIHBlcmZvcm0gdG8gZ2V0IHlvdXIgZGF0YSByZWFkeSBmb3IgbW9kZWxpbmcsIGJ1dCB3ZSdsbCBsaW1pdCBvdXJzZWx2ZXMgdG8gYSBmZXcgY29tbW9uIHRlY2huaXF1ZXM6DQoNCi0gICBTY2FsaW5nIG51bWVyaWMgZmVhdHVyZXMgc28gdGhleSdyZSBvbiB0aGUgc2FtZSBzY2FsZS4gVGhpcyBwcmV2ZW50cyBmZWF0dXJlcyB3aXRoIGxhcmdlIHZhbHVlcyBmcm9tIHByb2R1Y2luZyBjb2VmZmljaWVudHMgdGhhdCBkaXNwcm9wb3J0aW9uYXRlbHkgYWZmZWN0IHRoZSBwcmVkaWN0aW9ucy4NCg0KLSAgIEVuY29kaW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gRm9yIGV4YW1wbGUsIGJ5IHVzaW5nIGEgKm9uZSBob3QgZW5jb2RpbmcqIHRlY2huaXF1ZSB5b3UgY2FuIGNyZWF0ZSAiKmR1bW15KiIgb3IgKmluZGljYXRvciB2YXJpYWJsZXMqIHdoaWNoIHJlcGxhY2UgdGhlIG9yaWdpbmFsIGNhdGVnb3JpY2FsIGZlYXR1cmUgd2l0aCBudW1lcmljIGNvbHVtbnMgd2hvc2UgdmFsdWVzIGFyZSBlaXRoZXIgMSBvciAwLg0KDQpUaWR5bW9kZWxzIHByb3ZpZGVzIHlldCBhbm90aGVyIG5lYXQgcGFja2FnZTogW3JlY2lwZXNdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy8pLSBhIHBhY2thZ2UgZm9yIHByZXByb2Nlc3NpbmcgZGF0YS4gTGV0J3Mgc3BlY2lmeSBhIHJlY2lwZSB0aGF0IGVuY29kZXMgdGhlIGFnZSBjb2x1bW4gdGhlbiBub3JtYWxpemVzIHRoZSByZXN0IG9mIHRoZSBwcmVkaWN0b3IgZmVhdHVyZXMuDQoNCmBgYHtyIHJlY2lwZXN9DQojIFByZXByb2Nlc3MgdGhlIGRhdGEgZm9yIG1vZGVsbGluZw0KZGlhYmV0ZXNfcmVjaXBlIDwtIHJlY2lwZShkaWFiZXRpYyB+IC4sIGRhdGEgPSBkaWFiZXRlc190cmFpbikgJT4lIA0KICBzdGVwX211dGF0ZShhZ2UgPSBmYWN0b3IoYWdlKSkgJT4lIA0KICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpICU+JSANCiAgc3RlcF9kdW1teShhZ2UpDQoNCiMgUHJpbnQgdGhlIHJlY2lwZQ0KZGlhYmV0ZXNfcmVjaXBlDQoNCmBgYA0KDQpXZSBqdXN0IGNyZWF0ZWQgYSByZWNpcGUgY29udGFpbmluZyBhbiBvdXRjb21lIGFuZCBpdHMgY29ycmVzcG9uZGluZyBwcmVkaWN0b3JzLCBzcGVjaWZ5aW5nIHRoYXQgdGhlIGFnZSB2YXJpYWJsZSBzaG91bGQgYmUgY29udmVydGVkIHRvIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgKGZhY3RvciksIGFsbCB0aGUgbnVtZXJpYyBwcmVkaWN0b3JzIG5vcm1hbGl6ZWQgYW5kIGNyZWF0aW5nIGR1bW15IHZhcmlhYmxlcyBmb3IgdGhlIG5vbWluYWwgcHJlZGljdG9yIChhZ2UpIPCfmYwuDQoNCiMjIyMgQnVuZGxpbmcgaXQgYWxsIHRvZ2V0aGVyIHVzaW5nIGEgd29ya2Zsb3cNCg0KTm93IHRoYXQgd2UgaGF2ZSBhIHJlY2lwZSBhbmQgYSBtb2RlbCBzcGVjaWZpY2F0aW9uIHdlIGRlZmluZWQgcHJldmlvdXNseSwgd2UgbmVlZCB0byBmaW5kIGEgd2F5IG9mIGJ1bmRsaW5nIHRoZW0gdG9nZXRoZXIgaW50byBhbiBvYmplY3QgdGhhdCB3aWxsIGZpcnN0IHByZXByb2Nlc3MgdGhlIGRhdGEsIGZpdCB0aGUgbW9kZWwgb24gdGhlIHByZXByb2Nlc3NlZCBkYXRhIGFuZCBhbHNvIGFsbG93IGZvciBwb3RlbnRpYWwgcG9zdC1wcm9jZXNzaW5nIGFjdGl2aXRpZXMuDQoNCkluIFRpZHltb2RlbHMsIHRoaXMgY29udmVuaWVudCBvYmplY3QgaXMgY2FsbGVkIGEgW2B3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnLykgYW5kIGNvbnZlbmllbnRseSBob2xkcyB5b3VyIG1vZGVsaW5nIGNvbXBvbmVudHMuDQoNClRoZSBbKip3b3JrZmxvd3MqKl0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvKSBwYWNrYWdlIGFsbG93cyB0aGUgdXNlciB0byBiaW5kIG1vZGVsaW5nIGFuZCBwcmVwcm9jZXNzaW5nIG9iamVjdHMgdG9nZXRoZXIuIFlvdSBjYW4gdGhlbiBmaXQgdGhlIGVudGlyZSB3b3JrZmxvdyB0byB0aGUgZGF0YSwgc3VjaCB0aGF0IHRoZSBtb2RlbCBlbmNhcHN1bGF0ZXMgYWxsIG9mIHRoZSBwcmVwcm9jZXNzaW5nIHN0ZXBzIGFzIHdlbGwgYXMgdGhlIGFsZ29yaXRobS4NCg0KYGBge3Igd29ya2Zsb3d9DQojIFJlZGVmaW5lIHRoZSBtb2RlbCBzcGVjaWZpY2F0aW9uDQpsb2dyZWdfc3BlYyA8LSBsb2dpc3RpY19yZWcoKSAlPiUgDQogIHNldF9lbmdpbmUoImdsbSIpICU+JSANCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCg0KIyBCdW5kbGUgdGhlIHJlY2lwZSBhbmQgbW9kZWwgc3BlY2lmaWNhdGlvbg0KbHJfd2YgPC0gd29ya2Zsb3coKSAlPiUgDQogIGFkZF9yZWNpcGUoZGlhYmV0ZXNfcmVjaXBlKSAlPiUgDQogIGFkZF9tb2RlbChsb2dyZWdfc3BlYykNCg0KIyBQcmludCB0aGUgd29ya2Zsb3cNCmxyX3dmDQoNCmBgYA0KDQpBZnRlciBhIHdvcmtmbG93IGhhcyBiZWVuICpzcGVjaWZpZWQqLCBhIG1vZGVsIGNhbiBiZSBgdHJhaW5lZGAgdXNpbmcgdGhlIFtgZml0KClgXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3BhcnNuaXAvcmVmZXJlbmNlL2ZpdC5odG1sKSBmdW5jdGlvbi4NCg0KYGBge3IgZml0X3dmfQ0KIyBGaXQgYSB3b3JrZmxvdyBvYmplY3QNCmxyX3dmX2ZpdCA8LSBscl93ZiAlPiUgDQogIGZpdChkYXRhID0gZGlhYmV0ZXNfdHJhaW4pDQoNCiMgUHJpbnQgd2Ygb2JqZWN0DQpscl93Zl9maXQNCg0KYGBgDQoNCkdvb2Qgam9i8J+RjyEgV2Ugbm93IGhhdmUgYSB0cmFpbmVkIHdvcmtmbG93LiBUaGUgd29ya2Zsb3cgcHJpbnQgb3V0IHNob3dzIHRoZSBjb2VmZmljaWVudHMgbGVhcm5lZCBkdXJpbmcgdHJhaW5pbmcuDQoNClRoaXMgYWxsb3dzIHVzIHRvIHVzZSB0aGUgbW9kZWwgdHJhaW5lZCBieSB0aGlzIHdvcmtmbG93IHRvIHByZWRpY3QgbGFiZWxzIGZvciBvdXIgdGVzdCBzZXQsIGFuZCBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBtZXRyaWNzIHdpdGggdGhlIGJhc2ljIG1vZGVsIHdlIGNyZWF0ZWQgcHJldmlvdXNseS4NCg0KYGBge3IgZXZhbF93Zn0NCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQNCnJlc3VsdHMgPC0gZGlhYmV0ZXNfdGVzdCAlPiUgc2VsZWN0KGRpYWJldGljKSAlPiUgDQogIGJpbmRfY29scyhscl93Zl9maXQgJT4lIA0KICAgICAgICAgICAgICBwcmVkaWN0KG5ld19kYXRhID0gZGlhYmV0ZXNfdGVzdCkpICU+JSANCiAgYmluZF9jb2xzKGxyX3dmX2ZpdCAlPiUgDQogICAgICAgICAgICAgIHByZWRpY3QobmV3X2RhdGEgPSBkaWFiZXRlc190ZXN0LCB0eXBlID0gInByb2IiKSkNCg0KIyBQcmludCB0aGUgcmVzdWx0cw0KcmVzdWx0cyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDEwKQ0KDQpgYGANCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyIGNvbmZfbWF0Mn0NCiMgQ29uZnVzaW9uIG1hdHJpeCBmb3IgcHJlZGljdGlvbiByZXN1bHRzDQpyZXN1bHRzICU+JSANCiAgY29uZl9tYXQodHJ1dGggPSBkaWFiZXRpYywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykNCg0KDQpgYGANCg0K8J+kqfCfpKkgTG9vayBhdCB0aG9zZSBtZXRyaWNzIQ0KDQpDYW4gd2UgdmlzdWFsaXplIHRoaXM/IE9mIGNvdXJzZSwgbm90aGluZyBpcyBpbXBhUnNpYmxlIQ0KDQpgYGB7ciBjb25mX21hdF92aXp9DQojIFZpc3VhbGl6ZSBjb25mIG1hdA0KdXBkYXRlX2dlb21fZGVmYXVsdHMoZ2VvbSA9ICJyZWN0IiwgbmV3ID0gbGlzdChmaWxsID0gIm1pZG5pZ2h0Ymx1ZSIsIGFscGhhID0gMC43KSkNCg0KcmVzdWx0cyAlPiUgDQogIGNvbmZfbWF0KGRpYWJldGljLCAucHJlZF9jbGFzcykgJT4lIA0KICBhdXRvcGxvdCgpDQoNCmBgYA0KDQpXaGF0IGFib3V0IG91ciBvdGhlciBtZXRyaWNzIHN1Y2ggYXMgcHB2LCBzZW5zaXRpdml0eSBldGM/DQoNCmBgYHtyIGV2YWxfbWV0fQ0KIyBFdmFsdWF0ZSBvdGhlciBkZXNpcmVkIG1ldHJpY3MNCmV2YWxfbWV0cmljcyhkYXRhID0gcmVzdWx0cywgdHJ1dGggPSBkaWFiZXRpYywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykNCg0KIyBFdmFsdWF0ZSBST0NfQVVDIG1ldHJpY3MNCnJlc3VsdHMgJT4lIA0KICByb2NfYXVjKGRpYWJldGljLCAucHJlZF8xKQ0KDQojIFBsb3QgUk9DX0NVUlZFDQpyZXN1bHRzICU+JSANCiAgcm9jX2N1cnZlKGRpYWJldGljLCAucHJlZF8xKSAlPiUgDQogIGF1dG9wbG90KCkNCmBgYA0KDQpDb21wYXJpbmcgd2l0aCBwcmV2aW91cyBwcmVkaWN0aW9ucywgdGhlIG1ldHJpY3MgbG9vayBiZXR0ZXIsIHNvIGNsZWFybHkgcHJlcHJvY2Vzc2luZyB0aGUgZGF0YSBoYXMgbWFkZSBhIGRpZmZlcmVuY2UuDQoNCiMjIDMuIFRyeSBhIGRpZmZlcmVudCBhbGdvcml0aG0NCg0KTm93IGxldCdzIHRyeSBhIGRpZmZlcmVudCBhbGdvcml0aG0uIFByZXZpb3VzbHkgd2UgdXNlZCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYWxnb3JpdGhtLCB3aGljaCBpcyBhICpsaW5lYXIqIGFsZ29yaXRobS4gVGhlcmUgYXJlIG1hbnkga2luZHMgb2YgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtIHdlIGNvdWxkIHRyeSwgaW5jbHVkaW5nOg0KDQotICAgKipTdXBwb3J0IFZlY3RvciBNYWNoaW5lIGFsZ29yaXRobXMqKjogQWxnb3JpdGhtcyB0aGF0IGRlZmluZSBhICpoeXBlcnBsYW5lKiB0aGF0IHNlcGFyYXRlcyBjbGFzc2VzLg0KDQotICAgKipUcmVlLWJhc2VkIGFsZ29yaXRobXMqKjogQWxnb3JpdGhtcyB0aGF0IGJ1aWxkIGEgZGVjaXNpb24gdHJlZSB0byByZWFjaCBhIHByZWRpY3Rpb24NCg0KLSAgICoqRW5zZW1ibGUgYWxnb3JpdGhtcyoqOiBBbGdvcml0aG1zIHRoYXQgY29tYmluZSB0aGUgb3V0cHV0cyBvZiBtdWx0aXBsZSBiYXNlIGFsZ29yaXRobXMgdG8gaW1wcm92ZSBnZW5lcmFsaXphYmlsaXR5Lg0KDQpUaGlzIHRpbWUsIHdlJ2xsIHRyYWluIHRoZSBtb2RlbCB1c2luZyBhbiAqZW5zZW1ibGUqIGFsZ29yaXRobSBuYW1lZCAqUmFuZG9tIEZvcmVzdCogdGhhdCBhdmVyYWdlcyB0aGUgb3V0cHV0cyBvZiBtdWx0aXBsZSByYW5kb20gZGVjaXNpb24gdHJlZXMuIFJhbmRvbSBmb3Jlc3RzIGhlbHAgdG8gcmVkdWNlIHRyZWUgY29ycmVsYXRpb24gYnkgaW5qZWN0aW5nIG1vcmUgcmFuZG9tbmVzcyBpbnRvIHRoZSB0cmVlLWdyb3dpbmcgcHJvY2Vzcy4gTW9yZSBzcGVjaWZpY2FsbHksIGluc3RlYWQgb2YgY29uc2lkZXJpbmcgYWxsIHByZWRpY3RvcnMgaW4gdGhlIGRhdGEsIGZvciBjYWxjdWxhdGluZyBhIGdpdmVuIHNwbGl0LCByYW5kb20gZm9yZXN0cyBwaWNrIGEgcmFuZG9tIHNhbXBsZSBvZiBwcmVkaWN0b3JzIHRvIGJlIGNvbnNpZGVyZWQgZm9yIHRoYXQgc3BsaXQuDQoNCj4gRm9yIGZ1cnRoZXIgcmVhZGluZyBvbiBUcmVlIGJhc2VkIG1vZGVscywgcGxlYXNlIHNlZToNCj4NCj4gW01hY2hpbmUgTGVhcm5pbmcgZm9yIFNvY2lhbCBTY2llbnRpc3RzXShodHRwczovL2NpbWVudGFkYWouZ2l0aHViLmlvL21sX3NvY3NjaS90cmVlLWJhc2VkLW1ldGhvZHMuaHRtbCNyYW5kb20tZm9yZXN0cykNCg0KQXMgd2UgYWxyZWFkeSBoYXZlIGEgZ2lzdCBvZiBob3cgdG8gcGVyZm9ybSBjbGFzc2lmaWNhdGlvbiB1c2luZyBUaWR5bW9kZWxzLCBsZXQncyBnZXQgcmlnaHQgaW50byBzcGVjaWZ5aW5nIGFuZCBmaXR0aW5nIGEgcmFuZG9tIGZvcmVzdCBhbGdvcml0aG0uDQoNCmBgYHtyIHJhbmRfZm9yZXN0fQ0KIyBQcmVwcm9jZXNzIHRoZSBkYXRhIGZvciBtb2RlbGxpbmcNCmRpYWJldGVzX3JlY2lwZSA8LSByZWNpcGUoZGlhYmV0aWMgfiAuLCBkYXRhID0gZGlhYmV0ZXNfdHJhaW4pICU+JSANCiAgc3RlcF9tdXRhdGUoYWdlID0gZmFjdG9yKGFnZSkpICU+JSANCiAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgDQogIHN0ZXBfZHVtbXkoYWdlKQ0KDQojIEJ1aWxkIGEgcmFuZG9tIGZvcmVzdCBtb2RlbCBzcGVjaWZpY2F0aW9uDQpyZl9zcGVjIDwtIHJhbmRfZm9yZXN0KCkgJT4lIA0KICBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQojIEJ1bmRsZSByZWNpcGUgYW5kIG1vZGVsIHNwZWMgaW50byBhIHdvcmtmbG93DQpyZl93ZiA8LSB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShkaWFiZXRlc19yZWNpcGUpICU+JSANCiAgYWRkX21vZGVsKHJmX3NwZWMpDQoNCiMgRml0IGEgbW9kZWwNCnJmX3dmX2ZpdCA8LSByZl93ZiAlPiUgDQogIGZpdChkYXRhID0gZGlhYmV0ZXNfdHJhaW4pDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0ZXN0IGRhdGENCnJlc3VsdHMgPC0gZGlhYmV0ZXNfdGVzdCAlPiUgc2VsZWN0KGRpYWJldGljKSAlPiUgDQogIGJpbmRfY29scyhyZl93Zl9maXQgJT4lIA0KICAgICAgICAgICAgICBwcmVkaWN0KG5ld19kYXRhID0gZGlhYmV0ZXNfdGVzdCkpICU+JSANCiAgYmluZF9jb2xzKHJmX3dmX2ZpdCAlPiUgDQogICAgICAgICAgICAgIHByZWRpY3QobmV3X2RhdGEgPSBkaWFiZXRlc190ZXN0LCB0eXBlID0gInByb2IiKSkNCg0KIyBQcmludCBvdXQgcHJlZGljdGlvbnMNCnJlc3VsdHMgJT4lIA0KICBzbGljZV9oZWFkKG4gPSAxMCkNCg0KYGBgDQoNCvCfkqogVGhlcmUgZ29lcyBvdXIgcmFuZG9tX2ZvcmVzdCBtb2RlbC4gSXMgaXQgYW55IGdvb2Q/IExldCdzIGV2YWx1YXRlIGl0cyBtZXRyaWNzIQ0KDQpgYGB7ciBldmFsX3JmfQ0KIyBDb25mdXNpb24gbWV0cmljcyBmb3IgcmZfcHJlZGljdGlvbnMNCnJlc3VsdHMgJT4lIA0KICBjb25mX21hdChkaWFiZXRpYywgLnByZWRfY2xhc3MpDQoNCiMgQ29uZnVzaW9uIG1hdHJpeCBwbG90DQpyZXN1bHRzICU+JSANCiAgY29uZl9tYXQoZGlhYmV0aWMsIC5wcmVkX2NsYXNzKSAlPiUgDQogIGF1dG9wbG90KCkNCg0KYGBgDQoNClRoZXJlIGlzIGEgY29uc2lkZXJhYmxlIGluY3JlYXNlIGluIHRoZSBudW1iZXIgb2YgYFRydWUgUG9zaXRpdmVzYCBhbmQgYFRydWUgTmVnYXRpdmVzYCwgd2hpY2ggaXMgYSBzdGVwIGluIHRoZSByaWdodCBkaXJlY3Rpb24uDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IG90aGVyIGV2YWx1YXRpb24gbWV0cmljcw0KDQpgYGB7ciBvdGhlcl9tZXR9DQojIEV2YWx1YXRlIG90aGVyIGludHVpdGl2ZSBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzDQpyZl9tZXQgPC0gcmVzdWx0cyAlPiUgDQogIGV2YWxfbWV0cmljcyh0cnV0aCA9IGRpYWJldGljLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQ0KDQojIEV2YWx1YXRlIFJPQ19BT0MNCmF1YyA8LSByZXN1bHRzICU+JSANCiAgcm9jX2F1YyhkaWFiZXRpYywgLnByZWRfMSkNCg0KIyBQbG90IFJPQ19DVVJWRQ0KY3VydmUgPC0gcmVzdWx0cyAlPiUgDQogIHJvY19jdXJ2ZShkaWFiZXRpYywgLnByZWRfMSkgJT4lIA0KICBhdXRvcGxvdA0KDQojIFJldHVybiBtZXRyaWNzDQpsaXN0KHJmX21ldCwgYXVjLCBjdXJ2ZSkNCg0KDQpgYGANCg0KRm9yIHRoZSBzaGVlciBzYWtlIG9mIGFkdmVudHVyZSwgbGV0J3MgbWFrZSBhIFZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdCB0byBzZWUgd2hpY2ggcHJlZGljdG9yIHZhcmlhYmxlcyBoYXZlIHRoZSBtb3N0IGltcGFjdCBpbiBvdXIgbW9kZWwuDQoNCmBgYHtyfQ0KIyBMb2FkIHZpcA0KbGlicmFyeSh2aXApDQoNCiMgRXh0cmFjdCB0aGUgZml0dGVkIG1vZGVsIGZyb20gdGhlIHdvcmtmbG93DQpyZl93Zl9maXQgJT4lIA0KICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIA0KIyBNYWtlIFZJUCBwbG90DQogIHZpcCgpDQpgYGANCg0KSnVzdCBhcyB3ZSBoYWQgYW50aWNpcGF0ZWQgZnJvbSBvdXIgZGF0YSBleHBsb3JhdGlvbiDwn5iKISBUaGlzIGdvZXMgdG8gc2hvdyB0aGUgaW1wb3J0YW5jZSBvZiBkYXRhIGV4cGxvcmF0aW9uLg0KDQpBcyByZXZlYWxlZCBieSB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcywgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgc2VlbWVkIHRvIGhhdmUgZG9uZSBhIGdyZWF0IGpvYiBpbiBpbmNyZWFzaW5nIHRoZSBUcnVlIFBvc2l0aXZlcy9OZWdhdGl2ZXMgYW5kIHJlZHVjaW5nIHRoZSBGYWxzZSBQb3NpdGl2ZXMvTmVnYXRpdmVzLg0KDQojIyMjIFVzZSB0aGUgbW9kZWwgZm9yIGluZmVyZW5jaW5nDQoNCk5vdyB0aGF0IHdlIGhhdmUgYSByZWFzb25hYmx5IHVzZWZ1bCB0cmFpbmVkIG1vZGVsLCB3ZSBjYW4gc2F2ZSBpdCBmb3IgdXNlIGxhdGVyIHRvIHByZWRpY3QgbGFiZWxzIGZvciBuZXcgZGF0YToNCg0KYGBge3IgcmZfc2F2ZX0NCiMgU2F2ZSB0cmFpbmVkIHdvcmtmbG93DQpzYXZlUkRTKHJmX3dmX2ZpdCwgImRpYWJldGVzX3JmX21vZGVsLnJkcyIpDQoNCmBgYA0KDQpOb3csIHdlIGNhbiBsb2FkIGl0IHdoZW5ldmVyIHdlIG5lZWQgaXQsIGFuZCB1c2UgaXQgdG8gcHJlZGljdCBsYWJlbHMgZm9yIG5ldyBkYXRhLiBUaGlzIGlzIG9mdGVuIGNhbGxlZCAqYHNjb3JpbmdgKiBvciAqYGluZmVyZW5jaW5nYCouDQoNCkZvciBleGFtcGxlLCBsZXRzIGNyZWF0ZSBhIHNpbXVsYXRlZCBkYXRhIHNldCBieSBwaWNraW5nIGEgcmFuZG9tIHZhbHVlIGZvciBlYWNoIGNvbHVtbiBpbiBvdXIgdGVzdCBzZXQgdGhlbiBtYWtlIHByZWRpY3Rpb25zIHVzaW5nIHRoZSBzYXZlZCBtb2RlbC4NCg0KYGBge3IgcmZfaW5mZXJlbmNlfQ0KIyBMb2FkIHRoZSBtb2RlbCBpbnRvIHRoZSBjdXJyZW50IFIgc2Vzc2lvbg0KbG9hZGVkX21vZGVsIDwtIHJlYWRSRFMoImRpYWJldGVzX3JmX21vZGVsLnJkcyIpDQoNCiMgQ3JlYXRlIG5ldyBzaW11bGF0ZWQgZGF0YQ0KbmV3X2RhdGEgPC0gbGFwcGx5KGRpYWJldGVzX3Rlc3QsIGZ1bmN0aW9uKHgpe3NhbXBsZSh4LCBzaXplID0gMil9KSAlPiUgDQogIGFzX3RpYmJsZSgpDQoNCm5ld19kYXRhDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KcHJlZGljdGlvbnMgPC0gbmV3X2RhdGEgJT4lIA0KICBiaW5kX2NvbHMobG9hZGVkX21vZGVsICU+JSBwcmVkaWN0KG5ld19kYXRhKSkNCg0KcHJlZGljdGlvbnMNCmBgYA0KDQojIyA0LiBNdWx0aWNsYXNzIENsYXNzaWZpY2F0aW9uDQoNCkJpbmFyeSBjbGFzc2lmaWNhdGlvbiB0ZWNobmlxdWVzIHdvcmsgd2VsbCB3aGVuIHRoZSBkYXRhIG9ic2VydmF0aW9ucyBiZWxvbmcgdG8gb25lIG9mIHR3byBjbGFzc2VzIG9yIGNhdGVnb3JpZXMsIHN1Y2ggYXMgYFRydWVgIG9yIGBGYWxzZWAuIFdoZW4gdGhlIGRhdGEgY2FuIGJlIGNhdGVnb3JpemVkIGludG8gYG1vcmUgdGhhbiB0d28gY2xhc3Nlc2AsIHlvdSBtdXN0IHVzZSBhIGBtdWx0aWNsYXNzIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobWAuDQoNCk11bHRpY2xhc3MgY2xhc3NpZmljYXRpb24gY2FuIGJlIHRob3VnaHQgb2YgYXMgYSBgY29tYmluYXRpb25gIG9mIGBtdWx0aXBsZSBiaW5hcnkgY2xhc3NpZmllcnNgLiBUaGVyZSBhcmUgdHdvIHdheXMgaW4gd2hpY2ggeW91IGFwcHJvYWNoIHRoZSBwcm9ibGVtOg0KDQotICAgKipPbmUgdnMgUmVzdCAoT1ZSKSoqLCBpbiB3aGljaCBhIGNsYXNzaWZpZXIgaXMgY3JlYXRlZCBmb3IgZWFjaCBwb3NzaWJsZSBjbGFzcyB2YWx1ZSwgd2l0aCBhIHBvc2l0aXZlIG91dGNvbWUgZm9yIGNhc2VzIHdoZXJlIHRoZSBwcmVkaWN0aW9uIGlzICp0aGlzKiBjbGFzcywgYW5kIG5lZ2F0aXZlIHByZWRpY3Rpb25zIGZvciBjYXNlcyB3aGVyZSB0aGUgcHJlZGljdGlvbiBpcyBhbnkgb3RoZXIgY2xhc3MuIEEgY2xhc3NpZmljYXRpb24gcHJvYmxlbSB3aXRoIGZvdXIgcG9zc2libGUgc2hhcGUgY2xhc3NlcyAoKnNxdWFyZSosICpjaXJjbGUqLCAqdHJpYW5nbGUqLCAqaGV4YWdvbiopIHdvdWxkIHJlcXVpcmUgZm91ciBjbGFzc2lmaWVycyB0aGF0IHByZWRpY3Q6DQoNCiAgICAtICAgKnNxdWFyZSogb3Igbm90DQoNCiAgICAtICAgKmNpcmNsZSogb3Igbm90DQoNCiAgICAtICAgKnRyaWFuZ2xlKiBvciBub3QNCg0KICAgIC0gICAqaGV4YWdvbiogb3Igbm90DQoNCi0gICAqKk9uZSB2cyBPbmUgKE9WTykqKiwgaW4gd2hpY2ggYSBjbGFzc2lmaWVyIGZvciBlYWNoIHBvc3NpYmxlIHBhaXIgb2YgY2xhc3NlcyBpcyBjcmVhdGVkLiBUaGUgY2xhc3NpZmljYXRpb24gcHJvYmxlbSB3aXRoIGZvdXIgc2hhcGUgY2xhc3NlcyB3b3VsZCByZXF1aXJlIHRoZSBmb2xsb3dpbmcgYmluYXJ5IGNsYXNzaWZpZXJzOg0KDQogICAgLSAgICpzcXVhcmUqIG9yICpjaXJjbGUqDQoNCiAgICAtICAgKnNxdWFyZSogb3IgKnRyaWFuZ2xlKg0KDQogICAgLSAgICpzcXVhcmUqIG9yICpoZXhhZ29uKg0KDQogICAgLSAgICpjaXJjbGUqIG9yICp0cmlhbmdsZSoNCg0KICAgIC0gICAqY2lyY2xlKiBvciAqaGV4YWdvbioNCg0KICAgIC0gICAqdHJpYW5nbGUqIG9yICpoZXhhZ29uKg0KDQpJbiBib3RoIGFwcHJvYWNoZXMsIHRoZSBvdmVyYWxsIG1vZGVsIHRoYXQgY29tYmluZXMgdGhlIGNsYXNzaWZpZXJzIGdlbmVyYXRlcyBhIHZlY3RvciBvZiBwcmVkaWN0aW9ucyBpbiB3aGljaCB0aGUgcHJvYmFiaWxpdGllcyBnZW5lcmF0ZWQgZnJvbSB0aGUgaW5kaXZpZHVhbCBiaW5hcnkgY2xhc3NpZmllcnMgYXJlIHVzZWQgdG8gZGV0ZXJtaW5lIHdoaWNoIGNsYXNzIHRvIHByZWRpY3QuDQoNCkZvcnR1bmF0ZWx5LCBpbiBtb3N0IG1hY2hpbmUgbGVhcm5pbmcgZnJhbWV3b3JrcywgaW5jbHVkaW5nIFRpZHltb2RlbHMsIGltcGxlbWVudGluZyBhIG11bHRpY2xhc3MgY2xhc3NpZmljYXRpb24gbW9kZWwgaXMgbm90IHNpZ25pZmljYW50bHkgbW9yZSBjb21wbGV4IHRoYW4gYmluYXJ5IGNsYXNzaWZpY2F0aW9uLg0KDQojIyMjIE1lZXQgdGhlIGRhdGENCg0KIVtUaGUgUGFsbWVyIEFyY2hpcGVsYWdvIHBlbmd1aW5zLiBBcnR3b3JrIGJ5IFxAYWxsaXNvbl9ob3JzdC5dKGltYWdlcy9sdGVyX3Blbmd1aW5zLnBuZyl7d2lkdGg9IjYwMCJ9DQoNCkluIHRoaXMgc2VjdGlvbnMsIHdlJ2xsIGJ1aWxkIGEgbXVsdGljbGFzcyBjbGFzc2lmaWVyIGZvciBjbGFzc2lmeWluZyBwZW5ndWlucyENCg0KVGhlIGBwYWxtZXJwZW5ndWluc2AgZGF0YSBjb250YWlucyBzaXplIG1lYXN1cmVtZW50cyBmb3IgdGhyZWUgcGVuZ3VpbiBzcGVjaWVzIG9ic2VydmVkIG9uIHRocmVlIGlzbGFuZHMgaW4gdGhlIFBhbG1lciBBcmNoaXBlbGFnbywgQW50YXJjdGljYS4NCg0KPiBUaGVzZSBkYXRhIHdlcmUgY29sbGVjdGVkIGZyb20gMjAwNyAtIDIwMDkgYnkgRHIuwqBLcmlzdGVuIEdvcm1hbiB3aXRoIHRoZSBbUGFsbWVyIFN0YXRpb24gTG9uZyBUZXJtIEVjb2xvZ2ljYWwgUmVzZWFyY2ggUHJvZ3JhbV0oaHR0cHM6Ly9wYWwubHRlcm5ldC5lZHUvKSwgcGFydCBvZiB0aGUgW1VTIExvbmcgVGVybSBFY29sb2dpY2FsIFJlc2VhcmNoIE5ldHdvcmtdKGh0dHBzOi8vbHRlcm5ldC5lZHUvKS4gVGhlIGRhdGEgd2VyZSBpbXBvcnRlZCBkaXJlY3RseSBmcm9tIHRoZSBbRW52aXJvbm1lbnRhbCBEYXRhIEluaXRpYXRpdmVdKGh0dHBzOi8vZW52aXJvbm1lbnRhbGRhdGFpbml0aWF0aXZlLm9yZy8pIChFREkpIERhdGEgUG9ydGFsLCBhbmQgYXJlIGF2YWlsYWJsZSBmb3IgdXNlIGJ5IENDMCBsaWNlbnNlICgiTm8gUmlnaHRzIFJlc2VydmVkIikgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBbUGFsbWVyIFN0YXRpb24gRGF0YSBQb2xpY3ldKGh0dHBzOi8vcGFsLmx0ZXJuZXQuZWR1L2RhdGEvcG9saWNpZXMpLg0KDQpJbiBSLCB0aGUgcGFja2FnZSBgcGFsbWVycGVuZ3VpbnNgIGJ5IFtBbGxpc29uIE1hcmllIEhvcnN0IGFuZCBBbGlzb24gUHJlc21hbmVzIEhpbGwgYW5kIEtyaXN0ZW4gQiBHb3JtYW5dKGh0dHBzOi8vYWxsaXNvbmhvcnN0LmdpdGh1Yi5pby9wYWxtZXJwZW5ndWlucy9hcnRpY2xlcy9pbnRyby5odG1sKSBwcm92aWRlcyB1cyB3aXRoIGRhdGEgcmVsYXRlZCB0byB0aGVzZSBhZG9yYWJsZSBjcmVhdHVyZXMuDQoNClRoZSBjb3JyZXNwb25kaW5nIGNzdiBkYXRhIHVzZWQgaW4gdGhlIFB5dGhvbiB0dXRvcmlhbCBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcy90cmVlL21hc3Rlci9kYXRhKS4NCg0KQ2FyZSBmb3IgYSBwdW4/DQoNCiAgICBXaGF0IGlzIGEgcGVuZ3VpbuKAmXMgZmF2b3JpdGUgbW92aWU/DQoNCiAgICBGcm96ZW4g4p2E77iP8J+Rpy4NCg0KV2l0aCB0aGF0LCBsZXQncyBnZXQgc3RhcnRlZCBleHBsb3Jpbmcgc29tZSBwZW5ndWlucyDwn5Cn8J+QpyENCg0KIyMjIyBFeHBsb3JlIHRoZSBkYXRhDQoNCmBgYHtyIHBlbmd1aW5zfQ0KIyBMb2FkIHRoZSBkYXRhc2V0DQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQ0KDQojIFRha2UgYSBwZWVrIGludG8gdGhlIGRhdGENCmdsaW1wc2UocGVuZ3VpbnMpDQoNCg0KYGBgDQoNClRoZSBkYXRhIGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgY29sdW1uczoNCg0KLSAgICoqc3BlY2llczoqKiBhIGZhY3RvciBkZW5vdGluZyB0aGUgcGVuZ3VpbiBzcGVjaWVzIChgQWRlbGllYCwgYENoaW5zdHJhcGAsIG9yIGBHZW50b29gKQ0KDQotICAgKippc2xhbmQ6KiogYSBmYWN0b3IgZGVub3RpbmcgdGhlIGlzbGFuZCAoaW4gUGFsbWVyIEFyY2hpcGVsYWdvLCBBbnRhcmN0aWNhKSB3aGVyZSBvYnNlcnZlZA0KDQotICAgKipiaWxsX2xlbmd0aF9tbSAoYWthIGN1bG1lbl9sZW5ndGgpOioqIGEgbnVtYmVyIGRlbm90aW5nIGxlbmd0aCBvZiB0aGUgZG9yc2FsIHJpZGdlIG9mIHBlbmd1aW4gYmlsbCAobWlsbGltZXRlcnMpDQoNCi0gICAqKmJpbGxfZGVwdGhfbW0gKGFrYSBjdWxtZW5fZGVwdGgpOioqIGEgbnVtYmVyIGRlbm90aW5nIHRoZSBkZXB0aCBvZiB0aGUgcGVuZ3VpbiBiaWxsIChtaWxsaW1ldGVycykNCg0KLSAgICoqZmxpcHBlcl9sZW5ndGhfbW06KiogYW4gaW50ZWdlciBkZW5vdGluZyBwZW5ndWluIGZsaXBwZXIgbGVuZ3RoIChtaWxsaW1ldGVycykNCg0KLSAgICoqYm9keV9tYXNzX2c6KiogYW4gaW50ZWdlciBkZW5vdGluZyBwZW5ndWluIGJvZHkgbWFzcyAoZ3JhbXMpDQoNCi0gICAqKnNleDoqKiBhIGZhY3RvciBkZW5vdGluZyBwZW5ndWluIHNleCAobWFsZSwgZmVtYWxlKQ0KDQotICAgKip5ZWFyOioqIGFuIGludGVnZXIgZGVub3RpbmcgdGhlIHN0dWR5IHllYXIgKDIwMDcsIDIwMDgsIG9yIDIwMDkpDQoNClRoZSAqKnNwZWNpZXMqKiBjb2x1bW4gY29udGFpbmluZyBwZW5ndWluIHNwZWNpZXMgYEFkZWxpZWAsIGBDaGluc3RyYXBgLCBvciBgR2VudG9vYCwgaXMgdGhlIGxhYmVsIHdlIHdhbnQgdG8gdHJhaW4gYSBtb2RlbCB0byBwcmVkaWN0Lg0KDQpUaGUgY29ycmVzcG9uZGluZyBbUHl0aG9uIGxlYXJuaW5nIG1vZHVsZV0oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vZW4tdXMvbGVhcm4vbW9kdWxlcy90cmFpbi1ldmFsdWF0ZS1jbGFzc2lmaWNhdGlvbi1tb2RlbHMvKSB1c2VkIGEgZGF0YSBzZXQgd2l0aCB0aGUgZm9sbG93aW5nIHZhcmlhYmxlczogKipiaWxsX2xlbmd0aF9tbSoqLCAqKmJpbGxfZGVwdGhfbW0qKiwgKipmbGlwcGVyX2xlbmd0aF9tbSoqLCAqKmJvZHlfbWFzc19nKiosICoqc3BlY2llcyoqLg0KDQpMZXQncyBuYXJyb3cgZG93biB0byB0aG9zZSBhbmQgbWFrZSBzb21lIHN1bW1hcnkgc3RhdGlzdGljcyB3aGlsZSBhdCBpdC4gVGhlIFtza2ltciBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc2tpbXIvdmlnbmV0dGVzL3NraW1yLmh0bWwpIHByb3ZpZGVzIGEgc3Ryb25nIHNldCBvZiBzdW1tYXJ5IHN0YXRpc3RpY3MgdGhhdCBhcmUgZ2VuZXJhdGVkIGZvciBhIHZhcmlldHkgb2YgZGlmZmVyZW50IGRhdGEgdHlwZXMuDQoNCmBgYHtyIHNraW1yfQ0KIyBTZWxlY3QgZGVzaXJlZCBjb2x1bW5zDQpwZW5ndWluc19zZWxlY3QgPC0gcGVuZ3VpbnMgJT4lIA0KICBzZWxlY3QoYyhiaWxsX2xlbmd0aF9tbSwgYmlsbF9kZXB0aF9tbSwgZmxpcHBlcl9sZW5ndGhfbW0sDQogICAgICAgICAgIGJvZHlfbWFzc19nLCBzcGVjaWVzKSkNCg0KIyBEc28gc29tZSBzdW1tYXJ5IHN0YXRpc3RpY3MNCnBlbmd1aW5zX3NlbGVjdCAlPiUgDQogIHNraW0oKQ0KDQpgYGANCg0KRnJvbSB0aGUgbmVhdCBzdW1tYXJ5IHByb3ZpZGVkIGJ5IHNraW1yLCB3ZSBjYW4gc2VlIHRoYXQgZWFjaCBvdXIgcHJlZGljdG9yIGNvbHVtbnMgY29udGFpbnMgbWlzc2luZyAyIHZhbHVlcyB3aGlsZSBvdXIgbGFiZWwvb3V0Y29tZSBjb2x1bW4gY29udGFpbnMgbm9uZS4NCg0KTGV0J3MgZGlnIGEgbGl0dGxlIGRlZXBlciBhbmQgZmlsdGVyIHRoZSByb3dzIHRoYXQgY29udGFpbiBtaXNzaW5nIHZhbHVlcy4NCg0KYGBge3IgbWlzc2luZ19yb3dzfQ0KcGVuZ3VpbnNfc2VsZWN0ICU+JSANCiAgZmlsdGVyKGlmX2FueShldmVyeXRoaW5nKCksIGlzLm5hKSkNCg0KYGBgDQoNClRoZXJlIGFyZSB0d28gcm93cyB0aGF0IGNvbnRhaW4gbm8gZmVhdHVyZSB2YWx1ZXMgYXQgYWxsIChgTkFgIHN0YW5kcyBmb3IgTm90IEF2YWlsYWJsZSApLCBzbyB0aGVzZSB3b24ndCBiZSB1c2VmdWwgaW4gdHJhaW5pbmcgYSBtb2RlbC4gTGV0J3MgZGlzY2FyZCB0aGVtIGZyb20gdGhlIGRhdGFzZXQuDQoNCmBgYHtyIGRyb3BfbmF9DQojIERyb3Agcm93cyBjb250YWluaW5nIG1pc3NpbmcgdmFsdWVzDQpwZW5ndWluc19zZWxlY3QgPC0gcGVuZ3VpbnNfc2VsZWN0ICU+JSANCiAgZHJvcF9uYSgpDQoNCiMgQ29uZmlybSB0aGVyZSBhcmUgbm8gbWlzc2luZyB2YWx1ZXMNCnBlbmd1aW5zX3NlbGVjdCAlPiUgDQogIGFueU5BKCkNCg0KIyBQcm9wb3J0aW9uIG9mIGVhY2ggc3BlY2llcyBpbiB0aGUgZGF0YQ0KcGVuZ3VpbnNfc2VsZWN0ICU+JSANCiAgY291bnQoc3BlY2llcykNCg0KYGBgDQoNCk5vdyB0aGF0IHdlJ3ZlIGRlYWx0IHdpdGggdGhlIG1pc3NpbmcgdmFsdWVzLCBsZXQncyBleHBsb3JlIGhvdyB0aGUgZmVhdHVyZXMgcmVsYXRlIHRvIHRoZSBsYWJlbCBieSBjcmVhdGluZyBzb21lIGJveCBjaGFydHMuDQoNCmBgYHtyIGJveF9wbHRfcGVuZ3VpbnN9DQojIFBpdm90IGRhdGEgdG8gYSBsb25nIGZvcm1hdA0KcGVuZ3VpbnNfc2VsZWN0X2xvbmcgPC0gcGVuZ3VpbnNfc2VsZWN0ICU+JSANCiAgcGl2b3RfbG9uZ2VyKCFzcGVjaWVzLCBuYW1lc190byA9ICJwcmVkaWN0b3JzIiwgdmFsdWVzX3RvID0gInZhbHVlcyIpDQoNCiMgTWFrZSBib3ggcGxvdHMNCnBlbmd1aW5zX3NlbGVjdF9sb25nICU+JQ0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gc3BlY2llcywgeSA9IHZhbHVlcywgZmlsbCA9IHByZWRpY3RvcnMpKSArDQogIGdlb21fYm94cGxvdCgpICsNCiAgZmFjZXRfd3JhcCh+cHJlZGljdG9ycywgc2NhbGVzID0gImZyZWUiKSArDQogIHNjYWxlX2ZpbGxfcGFsZXR0ZWVyX2QoIm5iYXBhbGV0dGVzOjpzdXBlcnNvbmljc19ob2xpZGF5IikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQogIA0KDQpgYGANCg0KRnJvbSB0aGUgYm94IHBsb3RzLCBpdCBsb29rcyBsaWtlIHNwZWNpZXMgYEFkZWxpZWAgYW5kIGBDaGluc3RyYXBgIGhhdmUgc2ltaWxhciBkYXRhIHByb2ZpbGVzIGZvciBiaWxsX2RlcHRoLCBmbGlwcGVyX2xlbmd0aCwgYW5kIGJvZHlfbWFzcywgYnV0IENoaW5zdHJhcHMgdGVuZCB0byBoYXZlIGxvbmdlciBiaWxsX2xlbmd0aC4gYEdlbnRvb2AgdGVuZHMgdG8gaGF2ZSBmYWlybHkgY2xlYXJseSBkaWZmZXJlbnRpYXRlZCBmZWF0dXJlcyBmcm9tIHRoZSBvdGhlcnM7IHdoaWNoIHNob3VsZCBoZWxwIHVzIHRyYWluIGEgZ29vZCBjbGFzc2lmaWNhdGlvbiBtb2RlbC4NCg0KIyMjIyBQcmVwYXJlIHRoZSBkYXRhDQoNCkp1c3QgYXMgZm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiwgYmVmb3JlIHRyYWluaW5nIHRoZSBtb2RlbCwgd2UgbmVlZCB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIHN1YnNldHMgZm9yIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uLiBXZSdsbCBhbHNvIGFwcGx5IGEgYHN0cmF0aWZpY2F0aW9uYCB0ZWNobmlxdWUgd2hlbiBzcGxpdHRpbmcgdGhlIGRhdGEgdG8gYG1haW50YWluIHRoZSBwcm9wb3J0aW9uIG9mIGVhY2ggbGFiZWwgdmFsdWVgIGluIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhc2V0cy4NCg0KYGBge3IgcGVuZ3Vpbl9zcGxpdH0NCnNldC5zZWVkKDIwNTYpDQojIFNwbGl0IGRhdGEgNzAlLTMwJSBpbnRvIHRyYWluaW5nIHNldCBhbmQgdGVzdCBzZXQNCnBlbmd1aW5zX3NwbGl0IDwtIHBlbmd1aW5zX3NlbGVjdCAlPiUgDQogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuNzAsIHN0cmF0YSA9IHNwZWNpZXMpDQoNCiMgRXh0cmFjdCBkYXRhIGluIGVhY2ggc3BsaXQNCnBlbmd1aW5zX3RyYWluIDwtIHRyYWluaW5nKHBlbmd1aW5zX3NwbGl0KQ0KcGVuZ3VpbnNfdGVzdCA8LSB0ZXN0aW5nKHBlbmd1aW5zX3NwbGl0KQ0KDQojIFByaW50IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggc3BsaXQNCmNhdCgiVHJhaW5pbmcgY2FzZXM6ICIsIG5yb3cocGVuZ3VpbnNfdHJhaW4pLCAiXG4iLA0KICAgICJUZXN0IGNhc2VzOiAiLCBucm93KHBlbmd1aW5zX3Rlc3QpLCBzZXAgPSAiIikNCmBgYA0KDQojIyMjIFRyYWluIGFuZCBldmFsdWF0ZSBhIG11bHRpY2xhc3MgY2xhc3NpZmllcg0KDQpOb3cgdGhhdCB3ZSBoYXZlIGEgc2V0IG9mIHRyYWluaW5nIGZlYXR1cmVzIGFuZCBjb3JyZXNwb25kaW5nIHRyYWluaW5nIGxhYmVscywgd2UgY2FuIGZpdCBhIG11bHRpY2xhc3MgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtIHRvIHRoZSBkYXRhIHRvIGNyZWF0ZSBhIG1vZGVsLg0KDQpgcGFyc25pcDo6bXVsdGlub21fcmVnKClgIGRlZmluZXMgYSBtb2RlbCB0aGF0IHVzZXMgbGluZWFyIHByZWRpY3RvcnMgdG8gcHJlZGljdCBtdWx0aWNsYXNzIGRhdGEgdXNpbmcgdGhlIG11bHRpbm9taWFsIGRpc3RyaWJ1dGlvbi4NCg0KTGV0J3MgZml0IE11bHRpbm9taWFsIHJlZ3Jlc3Npb24gdmlhIFtubmV0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbm5ldC9ubmV0LnBkZikgcGFja2FnZS4gVGhpcyBtb2RlbCB1c3VhbGx5IGhhcyAxIHR1bmluZyBoeXBlcnBhcmFtZXRlciwgYHBlbmFsdHlgLCB3aGljaCBkZXNjcmliZXMgdGhlIGFtb3VudCBvZiByZWd1bGFyaXphdGlvbi4gVGhpcyBpcyB1c2VkIHRvIGNvdW50ZXJhY3QgYW55IGJpYXMgaW4gdGhlIHNhbXBsZSwgYW5kIGhlbHAgdGhlIG1vZGVsIGdlbmVyYWxpemUgd2VsbCBieSBhdm9pZGluZyAqb3ZlcmZpdHRpbmcqIHRoZSBtb2RlbCB0byB0aGUgdHJhaW5pbmcgZGF0YS4gV2UgY2FuIG9mIGNvdXJzZSB0dW5lIHRoaXMgcGFyYW1ldGVyLCBsaWtlIHdlIHdpbGwgbGF0ZXIgb24gaW4gdGhpcyBsZXNzb24sIGJ1dCBmb3Igbm93LCBsZXQncyBjaG9vc2UgYW4gYXJiaXRyYXJ5IHZhbHVlIG9mIGAxYA0KDQpgYGB7ciBtcl9zcGVjfQ0KIyBTcGVjaWZ5IGEgbXVsdGlub21pYWwgcmVncmVzc2lvbiB2aWEgbm5ldA0KbXVsdGlyZWdfc3BlYyA8LSBtdWx0aW5vbV9yZWcocGVuYWx0eSA9IDEpICU+JSANCiAgc2V0X2VuZ2luZSgibm5ldCIpICU+JSANCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCg0KIyBUcmFpbiBhIG11bHRpbm9taWFsIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aG91dCBhbnkgcHJlcHJvY2Vzc2luZw0Kc2V0LnNlZWQoMjA1NikNCm11bHRpcmVnX2ZpdCA8LSBtdWx0aXJlZ19zcGVjICU+JSANCiAgZml0KHNwZWNpZXMgfiAuLCBkYXRhID0gcGVuZ3VpbnNfdHJhaW4pDQoNCiMgUHJpbnQgdGhlIG1vZGVsDQptdWx0aXJlZ19maXQNCg0KDQpgYGANCg0KTm93IHdlIGNhbiB1c2UgdGhlIHRyYWluZWQgbW9kZWwgdG8gcHJlZGljdCB0aGUgbGFiZWxzIGZvciB0aGUgdGVzdCBmZWF0dXJlcywgYW5kIGV2YWx1YXRlIHBlcmZvcm1hbmNlOg0KDQpgYGB7ciBwZW5ndWluc19ldmFsfQ0KIyBNYWtlIHByZWRpY3Rpb25zIGZvciB0aGUgdGVzdCBzZXQNCnBlbmd1aW5zX3Jlc3VsdHMgPC0gcGVuZ3VpbnNfdGVzdCAlPiUgc2VsZWN0KHNwZWNpZXMpICU+JSANCiAgYmluZF9jb2xzKG11bHRpcmVnX2ZpdCAlPiUgDQogICAgICAgICAgICAgIHByZWRpY3QobmV3X2RhdGEgPSBwZW5ndWluc190ZXN0KSkgJT4lIA0KICBiaW5kX2NvbHMobXVsdGlyZWdfZml0ICU+JSANCiAgICAgICAgICAgICAgcHJlZGljdChuZXdfZGF0YSA9IHBlbmd1aW5zX3Rlc3QsIHR5cGUgPSAicHJvYiIpKQ0KDQojIFByaW50IHByZWRpY3Rpb25zDQpwZW5ndWluc19yZXN1bHRzICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCg0KYGBgDQoNCk5vdywgbGV0J3MgbG9vayBhdCB0aGUgY29uZnVzaW9uIG1hdHJpeCBmb3Igb3VyIG1vZGVsDQoNCmBgYHtyIG1yX2NvbmZ9DQojIENvbmZ1c2lvbiBtYXRyaXgNCnBlbmd1aW5zX3Jlc3VsdHMgJT4lIA0KICBjb25mX21hdChzcGVjaWVzLCAucHJlZF9jbGFzcykNCg0KDQpgYGANCg0KVGhlIGNvbmZ1c2lvbiBtYXRyaXggc2hvd3MgdGhlIGludGVyc2VjdGlvbiBvZiBwcmVkaWN0ZWQgYW5kIGFjdHVhbCBsYWJlbCB2YWx1ZXMgZm9yIGVhY2ggY2xhc3MgLSBpbiBzaW1wbGUgdGVybXMsIHRoZSBkaWFnb25hbCBpbnRlcnNlY3Rpb25zIGZyb20gdG9wLWxlZnQgdG8gYm90dG9tLXJpZ2h0IGluZGljYXRlIHRoZSBudW1iZXIgb2YgY29ycmVjdCBwcmVkaWN0aW9ucy4NCg0KV2hlbiBkZWFsaW5nIHdpdGggbXVsdGlwbGUgY2xhc3NlcywgaXQncyBnZW5lcmFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gdmlzdWFsaXplIHRoaXMgYXMgYSBoZWF0IG1hcCwgbGlrZSB0aGlzOg0KDQpgYGB7ciBtcl9jb25mX3Zpen0NCnVwZGF0ZV9nZW9tX2RlZmF1bHRzKGdlb20gPSAidGlsZSIsIG5ldyA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuNykpDQojIFZpc3VhbGl6ZSBjb25mdXNpb24gbWF0cml4DQpwZW5ndWluc19yZXN1bHRzICU+JSANCiAgY29uZl9tYXQoc3BlY2llcywgLnByZWRfY2xhc3MpICU+JSANCiAgYXV0b3Bsb3QodHlwZSA9ICJoZWF0bWFwIikNCmBgYA0KDQpUaGUgZGFya2VyIHNxdWFyZXMgaW4gdGhlIGNvbmZ1c2lvbiBtYXRyaXggcGxvdCBpbmRpY2F0ZSBoaWdoIG51bWJlcnMgb2YgY2FzZXMsIGFuZCB5b3UgY2FuIGhvcGVmdWxseSBzZWUgYSBkaWFnb25hbCBsaW5lIG9mIGRhcmtlciBzcXVhcmVzIGluZGljYXRpbmcgY2FzZXMgd2hlcmUgdGhlIHByZWRpY3RlZCBhbmQgYWN0dWFsIGxhYmVsIGFyZSB0aGUgc2FtZS4NCg0KTGV0J3Mgbm93IGNhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIHRoZSBjb25mdXNpb24gbWF0cml4Lg0KDQpgYGB7ciBwZW5ndWluc19zdW1tfQ0KIyBTdGF0aXN0aWNhbCBzdW1tYXJpZXMgZm9yIHRoZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdChkYXRhID0gcGVuZ3VpbnNfcmVzdWx0cywgdHJ1dGggPSBzcGVjaWVzLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSAlPiUgDQogIHN1bW1hcnkoKQ0KDQpgYGANCg0KVGhlIHRpYmJsZSBzaG93cyB0aGUgb3ZlcmFsbCBtZXRyaWNzIG9mIGhvdyB3ZWxsIHRoZSBtb2RlbCBwZXJmb3JtcyBhY3Jvc3MgYWxsIHRocmVlIGNsYXNzZXMuDQoNCkxldCdzIGV2YWx1YXRlIHRoZSBST0MgbWV0cmljcy4gSW4gdGhlIGNhc2Ugb2YgYSBtdWx0aWNsYXNzIGNsYXNzaWZpY2F0aW9uIG1vZGVsLCBhIHNpbmdsZSBST0MgY3VydmUgc2hvd2luZyB0cnVlIHBvc2l0aXZlIHJhdGUgdnMgZmFsc2UgcG9zaXRpdmUgcmF0ZSBpcyBub3QgcG9zc2libGUuIEhvd2V2ZXIsIHlvdSBjYW4gdXNlIHRoZSByYXRlcyBmb3IgZWFjaCBjbGFzcyBpbiBhIE9uZSB2cyBSZXN0IChPVlIpIGNvbXBhcmlzb24gdG8gY3JlYXRlIGEgUk9DIGNoYXJ0IGZvciBlYWNoIGNsYXNzLg0KDQpgYGB7ciBwZW5ndWluc19yb2N9DQojIE1ha2UgYSBST0NfQ1VSVkUNCnBlbmd1aW5zX3Jlc3VsdHMgJT4lIA0KICByb2NfY3VydmUoc3BlY2llcywgYygucHJlZF9BZGVsaWUsIC5wcmVkX0NoaW5zdHJhcCwgLnByZWRfR2VudG9vKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSAxIC0gc3BlY2lmaWNpdHksIHkgPSBzZW5zaXRpdml0eSwgY29sb3IgPSAubGV2ZWwpKSArDQogIGdlb21fYWJsaW5lKGx0eSA9IDIsIGNvbG9yID0gImdyYXk4MCIsIHNpemUgPSAwLjkpICsNCiAgZ2VvbV9wYXRoKHNob3cubGVnZW5kID0gVCwgYWxwaGEgPSAwLjYsIHNpemUgPSAxLjIpICsNCiAgY29vcmRfZXF1YWwoKQ0KDQpgYGANCg0KVG8gcXVhbnRpZnkgdGhlIFJPQyBwZXJmb3JtYW5jZSwgeW91IGNhbiBjYWxjdWxhdGUgYW4gYWdncmVnYXRlIGFyZWEgdW5kZXIgdGhlIGN1cnZlIHNjb3JlIHRoYXQgaXMgYXZlcmFnZWQgYWNyb3NzIGFsbCBvZiB0aGUgT1ZSIGN1cnZlcy4NCg0KYGBge3IgcGVuZ3VpbnNfQU9DfQ0KIyBDYWxjdWxhdGUgUk9DX0FPQw0KcGVuZ3VpbnNfcmVzdWx0cyAlPiUgDQogIHJvY19hdWMoc3BlY2llcywgYygucHJlZF9BZGVsaWUsIC5wcmVkX0NoaW5zdHJhcCwgLnByZWRfR2VudG9vKSkNCg0KYGBgDQoNClRoYXQgd2VudCBkb3duIHdlbGwhIFRoZSBtb2RlbCBkaWQgYSBncmVhdCBqb2IgaW4gY2xhc3NpZnlpbmcgdGhlIHBlbmd1aW5zLiBXaGF0IGtpbmQgb2YgYWR2ZW50dXJlIHdvdWxkIGl0IGJlLCBpZiB3ZSBkaWRuJ3QgcHJlcHJvY2VzcyB0aGUgZGF0YT8NCg0KIyMjIyBXb3JrZmxvd3MgKyBBIGRpZmZlcmVudCBhbGdvcml0aG0NCg0KQWdhaW4sIGp1c3QgbGlrZSB3aXRoIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiwgeW91IGNhbiB1c2UgYSB3b3JrZmxvdyB0byBhcHBseSBwcmVwcm9jZXNzaW5nIHN0ZXBzIHRvIHRoZSBkYXRhIGJlZm9yZSBmaXR0aW5nIGl0IHRvIGFuIGFsZ29yaXRobSB0byB0cmFpbiBhIG1vZGVsLiBMZXQncyBzY2FsZSB0aGUgbnVtZXJpYyBmZWF0dXJlcyBpbiBhIHRyYW5zZm9ybWF0aW9uIHN0ZXAgYmVmb3JlIHRyYWluaW5nLCB0cnkgYSBkaWZmZXJlbnQgYWxnb3JpdGhtIChhIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmUpIGFuZCB0dW5lIHNvbWUgbW9kZWwgaHlwZXJwYXJhbWV0ZXJzLCBqdXN0IHRvIHNob3cgdGhhdCB3ZSBjYW4hDQoNCmBTdXBwb3J0IFZlY3RvciBNYWNoaW5lc2AgdHJ5IHRvIGZpbmQgYSAqaHlwZXJwbGFuZSogaW4gc29tZSBmZWF0dXJlIHNwYWNlIHRoYXQgImJlc3QiIHNlcGFyYXRlcyB0aGUgY2xhc3Nlcy4gUGxlYXNlIHNlZToNCg0KLSAgIFsqU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMqXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01ML3N2bS5odG1sKSwgSGFuZHMtb24gTWFjaGluZSBMZWFybmluZyB3aXRoIFINCg0KLSAgICpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyosIFtBbiBJbnRyb2R1Y3Rpb24gdG8gU3RhdGlzdGljYWwgTGVhcm5pbmcgd2l0aCBBcHBsaWNhdGlvbnMgaW4gUl0oaHR0cHM6Ly93d3cuc3RhdGxlYXJuaW5nLmNvbS8pDQoNCi0gICBbU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgdW5kZXIgdGhlIGhvb2RdKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9zdXBwb3J0LXZlY3Rvci1tYWNoaW5lcy11bmRlci10aGUtaG9vZC1jNjA5ZTU3YTRiMDkpDQoNCi0gICBbU1ZNIGtlcm5lbHNdKGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvYXV0b19leGFtcGxlcy9zdm0vcGxvdF9zdm1fa2VybmVscy5odG1sKSwgU2Npa2l0IGxlYXJuDQoNCldlJ2xsIGZpdCBhIGByYWRpYWwgYmFzaXMgZnVuY3Rpb25gIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmUgdG8gdGhlc2UgZGF0YSBhbmQgdHVuZSB0aGUgYFNWTSBjb3N0IHBhcmFtZXRlcmAgYW5kIHRoZSBgz4MgcGFyYW1ldGVyYCBpbiB0aGUga2VybmVsIGZ1bmN0aW9uIChUaGUgbWFyZ2luIHBhcmFtZXRlciBkb2VzIG5vdCBhcHBseSB0byBjbGFzc2lmaWNhdGlvbiBtb2RlbHMpDQoNCi0gICBBIGNvc3QgYXJndW1lbnQgYWxsb3dzIHVzIHRvIHNwZWNpZnkgdGhlIGNvc3Qgb2YgYSB2aW9sYXRpb24gdG8gdGhlIG1hcmdpbi4gV2hlbiB0aGUgY29zdCBhcmd1bWVudCBpcyBzbWFsbCwgdGhlbiB0aGUgbWFyZ2lucyB3aWxsIGJlIHdpZGUgYW5kIG1hbnkgc3VwcG9ydCB2ZWN0b3JzIHdpbGwgYmUgb24gdGhlIG1hcmdpbiBvciB3aWxsIHZpb2xhdGUgdGhlIG1hcmdpbi4gVGhpcyAqY291bGQqIG1ha2UgdGhlIG1vZGVsIG1vcmUgcm9idXN0IGFuZCBsZWFkIHRvIGJldHRlciBjbGFzc2lmaWNhdGlvbi4gV2hlbiB0aGUgY29zdCBhcmd1bWVudCBpcyBsYXJnZSwgdGhlbiB0aGUgbWFyZ2lucyB3aWxsIGJlIG5hcnJvdyBhbmQgdGhlcmUgd2lsbCBiZSBmZXcgc3VwcG9ydCB2ZWN0b3JzIG9uIHRoZSBtYXJnaW4gb3IgdmlvbGF0aW5nIHRoZSBtYXJnaW4uDQoNCi0gICBBcyBgz4NgIGRlY3JlYXNlcywgdGhlIGZpdCBiZWNvbWVzIG1vcmUgbm9uLWxpbmVhciwgYW5kIHRoZSBtb2RlbCAqYmVjb21lcyogbW9yZSBmbGV4aWJsZS4NCg0KQm90aCBwYXJhbWV0ZXJzIGNhbiBoYXZlIGEgcHJvZm91bmQgZWZmZWN0IG9uIHRoZSBtb2RlbCBjb21wbGV4aXR5IGFuZCBwZXJmb3JtYW5jZS4NCg0KPiBUaGUgcmFkaWFsIGJhc2lzIGtlcm5lbCBpcyBleHRyZW1lbHkgZmxleGlibGUgYW5kIGFzIGEgcnVsZSBvZiB0aHVtYiwgd2UgZ2VuZXJhbGx5IHN0YXJ0IHdpdGggdGhpcyBrZXJuZWwgd2hlbiBmaXR0aW5nIFNWTXMgaW4gcHJhY3RpY2UuDQoNClBhcmFtZXRlcnMgYXJlIG1hcmtlZCBmb3IgdHVuaW5nIGJ5IGFzc2lnbmluZyB0aGVtIGEgdmFsdWUgb2YgYHR1bmUoKWAuIEFsc28sIGxldCdzIHRyeSBvdXQgYSBuZXcgc3VjY2luY3Qgd2F5IG9mIGNyZWF0aW5nIHdvcmtmbG93cyB0aGF0IG1pbmltaXplcyBhIGxvdCBvZiBwaXBpbmcgc3RlcHMgYXMgc3VnZ2VzdGVkIGJ5IFtEYXZpZCdzIGJsb2cgcG9zdF0oaHR0cDovL3ZhcmlhbmNlZXhwbGFpbmVkLm9yZy9yL3NsaWNlZC1tbC8pICh3aW5uZXIgb2YgW3NsaWNlZF0oaHR0cHM6Ly9tb2JpbGUudHdpdHRlci5jb20vbWVnYW5yaXNkYWwvc3RhdHVzLzE0MjgwMzkzNjUwMDgwNjA0MjQpISEpDQoNCmBgYHtyIHR1bmVfc3ZtfQ0KIyBDcmVhdGUgYSBtb2RlbCBzcGVjaWZpY2F0aW9uDQpzdm1fc3BlYyA8LSBzdm1fcmJmKG1vZGUgPSAiY2xhc3NpZmljYXRpb24iLCBlbmdpbmUgPSAia2VybmxhYiIsDQogICAgICAgICAgICBjb3N0ID0gdHVuZSgpLCByYmZfc2lnbWEgPSB0dW5lKCkpDQoNCg0KIyBDcmVhdGUgYSB3b3JrZmxvdyB0aGF0IGVuY2Fwc3VsYXRlcyBhIHJlY2lwZSBhbmQgYSBtb2RlbA0Kc3ZtX3dmbG93IDwtIHJlY2lwZShzcGVjaWVzIH4gLiwgZGF0YSA9IHBlbmd1aW5zX3RyYWluKSAlPiUgDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lIA0KICB3b3JrZmxvdyhzdm1fc3BlYykNCg0KIyBQcmludCBvdXQgd29ya2Zsb3cNCnN2bV93Zmxvdw0KYGBgDQoNClByZXR0eSBuZWF0LCByaWdodCDinKg/DQoNCk5vdyB0aGF0IHdlIGhhdmUgc3BlY2lmaWVkIHdoYXQgcGFyYW1ldGVyIHRvIHR1bmUsIHdlJ2xsIG5lZWQgdG8gZmlndXJlIG91dCBhIHNldCBvZiBwb3NzaWJsZSB2YWx1ZXMgdG8gdHJ5IG91dCB0aGVuIGNob29zZSB0aGUgYmVzdC4NCg0KVG8gZG8gdGhpcywgd2UnbGwgY3JlYXRlIGEgZ3JpZCEgSW4gdGhpcyBleGFtcGxlLCB3ZSdsbCB3b3JrIHRocm91Z2ggYSByZWd1bGFyIGdyaWQgb2YgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzLCB0cnkgdGhlbSBvdXQsIGFuZCBzZWUgd2hhdCBwYWlyIHJlc3VsdHMgaW4gdGhlIGJlc3QgbW9kZWwgcGVyZm9ybWFuY2UuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjA1NikNCiMgQ3JlYXRlIHJlZ3VsYXIgZ3JpZCBvZiA2IHZhbHVlcyBmb3IgZWFjaCB0dW5pbmcgcGFyYW1ldGVycw0Kc3ZtX2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBhcmFtZXRlcnMoc3ZtX3NwZWMpLCBsZXZlbHMgPSA2KQ0KDQojIFByaW50IG91dCBzb21lIHBhcmFtZXRlcnMgaW4gb3VyIGdyaWQNCnN2bV9ncmlkICU+JSANCiAgc2xpY2VfaGVhZChuID0gMTApDQoNCmBgYA0KDQpBd2Vzb21lISBPbmUgdGhpbmcgYWJvdXQgaHlwZXJwYXJhbWV0ZXJzIGlzIHRoYXQgdGhleSBhcmUgbm90IGxlYXJuZWQgZGlyZWN0bHkgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LiBJbnN0ZWFkLCB0aGV5IGFyZSBlc3RpbWF0ZWQgdXNpbmcgYHNpbXVsYXRlZCBkYXRhIHNldHNgIGNyZWF0ZWQgZnJvbSBhIHByb2Nlc3MgY2FsbGVkIGByZXNhbXBsaW5nYC4gSW4gb3VyIHByZXZpb3VzLCB3ZSB1c2VkIGBjcm9zcy12YWxpZGF0aW9uYCByZXNhbXBsaW5nIG1ldGhvZC4gTGV0J3MgdHJ5IG91dCBhbm90aGVyIHJlc2FtcGxpbmcgdGVjaG5pcXVlOiBgYm9vdHN0cmFwIHJlc2FtcGxpbmdgLg0KDQpCb290c3RyYXAgcmVzYW1wbGluZyBtZWFucyBkcmF3aW5nIHdpdGggYHJlcGxhY2VtZW50YCBmcm9tIG91ciBvcmlnaW5hbCBkYXRhc2V0IHRoZW4gdGhlbiBmaXQgYSBtb2RlbCBvbiB0aGF0IG5ldyBzZXQgdGhhdCBgY29udGFpbnMgc29tZSBkdXBsaWNhdGVzYCwgYW5kIGV2YWx1YXRlIHRoZSBtb2RlbCBvbiB0aGUgYGRhdGEgcG9pbnRzIHRoYXQgd2VyZSBub3QgaW5jbHVkZWRgLg0KDQpUaGVuIHdlIGRvIHRoYXQgYWdhaW4gKGRlZmF1bHQgYmVoYXZpb3VyIGlzIDI1IGJvb3N0cmFwcyBidXQgdGhpcyBjYW4gYmUgY2hhbmdlZCkuIE9rYXksIGxldCdzIGNyZWF0ZSBzb21lIHNpbXVsYXRlZCBkYXRhIHNldHMuDQoNCmBgYHtyIGJvb3RzdHJhcHN9DQpzZXQuc2VlZCgyMDU2KQ0KIyBCb290c3RyYXAgcmVzYW1wbGluZw0KcGVuZ3VpbnNfYnMgPC0gYm9vdHN0cmFwcyhwZW5ndWluc190cmFpbiwgdGltZXMgPSAxMCkNCg0KcGVuZ3VpbnNfYnMNCg0KYGBgDQoNCiMjIyMgTW9kZWwgdHVuaW5nIHZpYSBncmlkIHNlYXJjaC4NCg0KV2UgYXJlIHJlYWR5IHRvIHR1bmUhIExldCdzIHVzZSBbYHR1bmVfZ3JpZCgpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lX2dyaWQuaHRtbCkgdG8gZml0IG1vZGVscyBhdCBhbGwgdGhlIGRpZmZlcmVudCB2YWx1ZXMgd2UgY2hvc2UgZm9yIGVhY2ggdHVuZWQgaHlwZXJwYXJhbWV0ZXIuDQoNCmBgYHtyIGdyaWRfc2VhcmNofQ0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkNCg0KIyBNb2RlbCB0dW5pbmcgdmlhIGEgZ3JpZCBzZWFyY2gNCnNldC5zZWVkKDIwNTYpDQpzdm1fcmVzIDwtIHR1bmVfZ3JpZCgNCiAgb2JqZWN0ID0gc3ZtX3dmbG93LA0KICByZXNhbXBsZXMgPSBwZW5ndWluc19icywNCiAgZ3JpZCA9IHN2bV9ncmlkDQopDQpgYGANCg0KTm93IHRoYXQgd2UgaGF2ZSBvdXIgdHVuaW5nIHJlc3VsdHMsIHdlIGNhbiBleHRyYWN0IHRoZSBwZXJmb3JtYW5jZSBtZXRyaWNzIHVzaW5nIGBjb2xsZWN0X21ldHJpY3MoKWA6DQoNCmBgYHtyIGNvbGxlY3RfbWV0cmljc30NCiMgT2J0YWluIHBlcmZvcm1hbmNlIG1ldHJpY3MNCnN2bV9yZXMgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDcpDQoNCmBgYA0KDQrwn6Sp8J+kqSBMZXQncyBzZWUgaWYgd2UgY291bGQgZ2V0IG1vcmUgYnkgdmlzdWFsaXppbmcgdGhlIHJlc3VsdHM6DQoNCmBgYHtyfQ0KIyBWaXN1YWxpemUgdHVuaW5nIG1ldHJpY3MNCnN2bV9yZXMgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIG11dGF0ZShyYmZfc2lnbWEgPSBmYWN0b3IocmJmX3NpZ21hKSkgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gY29zdCwgeSA9IG1lYW4sIGNvbG9yID0gcmJmX3NpZ21hKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuNSwgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMikgKw0KICBmYWNldF93cmFwKH4ubWV0cmljLCBzY2FsZXMgPSAiZnJlZSIsIG5yb3cgPSAyKSArDQogIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoKSkgKw0KICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uID0gInZpcmlkaXMiLCBiZWdpbiA9IC4xKQ0KDQpgYGANCg0KSXQgc2VlbXMgdGhhdCBhbiBTVk0gd2l0aCBhbiByYmZfc2lnbWEgb2YgMSBhbmQgMC4wMSByZWFsbHkgcGVyZm9ybWVkIHdlbGwgYWNyb3NzIGFsbCBjYW5kaWRhdGUgdmFsdWVzIG9mIGNvc3QuIFRoZSBbYHNob3dfYmVzdCgpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCkgZnVuY3Rpb24gY2FuIGhlbHAgdXMgbWFrZSBhIGNsZWFyZXIgZGlzdGluY3Rpb246DQoNCmBgYHtyIHNob3dfYmVzdH0NCiMgU2hvdyBiZXN0IHN1Ym1vZGVsIA0Kc3ZtX3JlcyAlPiUgDQogIHNob3dfYmVzdCgiYWNjdXJhY3kiKQ0KDQoNCmBgYA0KDQpNdWNoIGJldHRlciEgTGV0J3Mgbm93IHVzZSB0aGUgW2BzZWxlY3RfYmVzdCgpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCkgZnVuY3Rpb24gdG8gcHVsbCBvdXQgdGhlIHNpbmdsZSBzZXQgb2YgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzIGluIHRoZSBiZXN0IHN1Yi1tb2RlbDoNCg0KYGBge3Igc2VsZWN0X2Jlc3R9DQojIFNlbGVjdCBiZXN0IG1vZGVsIGh5cGVycGFyYW1ldGVycw0KYmVzdF9zdm0gPC0gc3ZtX3JlcyAlPiUgDQogIHNlbGVjdF9iZXN0KCJhY2N1cmFjeSIpDQoNCmJlc3Rfc3ZtDQoNCmBgYA0KDQpQZXJmZWN0ISBUaGVzZSBhcmUgdGhlIHZhbHVlcyBmb3IgYHJiZl9zaWdtYWAgYW5kIGBjb3N0YCB0aGF0IG1heGltaXplIGFjY3VyYWN5IGZvciBvdXIgcGVuZ3VpbnMhDQoNCldlIGNhbiBub3cgYGZpbmFsaXplYCBvdXIgd29ya2Zsb3cgc3VjaCB0aGF0IHRoZSBwYXJhbWV0ZXJzIHdlIGhhZCBtYXJrZWQgZm9yIHR1bmluZyBieSBhc3NpZ25pbmcgdGhlbSBhIHZhbHVlIG9mIGB0dW5lKClgIGNhbiBnZXQgdXBkYXRlZCB3aXRoIHRoZSB2YWx1ZXMgZnJvbSBgYmVzdF9zdm1gDQoNCmBgYHtyfQ0KIyBGaW5hbGl6ZSB3b3JrZmxvdw0KZmluYWxfd2Zsb3cgPC0gc3ZtX3dmbG93ICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9zdm0pDQoNCmZpbmFsX3dmbG93DQogIA0KYGBgDQoNClRoYXQgbWFya3MgdGhlIGVuZCBvZiB0dW5pbmcg8J+SgyENCg0KIyMjIyBUaGUgbGFzdCBmaXQNCg0KRmluYWxseSwgbGV0J3MgZml0IHRoaXMgZmluYWwgbW9kZWwgdG8gdGhlIHRyYWluaW5nIGRhdGEgYW5kIGV2YWx1YXRlIGhvdyBpdCB3b3VsZCBwZXJmb3JtIG9uIG5ldyBkYXRhIHVzaW5nIG91ciB0ZXN0IGRhdGEuIFdlIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIFtgbGFzdF9maXQoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbGFzdF9maXQuaHRtbCkgd2l0aCBvdXIgZmluYWxpemVkIG1vZGVsOyB0aGlzIGZ1bmN0aW9uICpmaXRzKiB0aGUgZmluYWxpemVkIG1vZGVsIG9uIHRoZSBmdWxsIHRyYWluaW5nIGRhdGEgc2V0IGFuZCAqZXZhbHVhdGVzKiBpdCBvbiB0aGUgdGVzdGluZyBkYXRhLg0KDQpgYGB7ciBsYXN0X2ZpdH0NCiMgVGhlIGxhc3QgZml0DQpmaW5hbF9maXQgPC0gbGFzdF9maXQob2JqZWN0ID0gZmluYWxfd2Zsb3csIHNwbGl0ID0gcGVuZ3VpbnNfc3BsaXQpDQoNCiMgQ29sbGVjdCBtZXRyaWNzDQpmaW5hbF9maXQgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKQ0KYGBgDQoNCk11Y2ggYmV0dGVyIPCfpKkhIFlvdSBjYW4gb2YgY291cnNlIGdvIGFoZWFkIGFuZCBvYnRhaW4gdGhlIGhhcmQgY2xhc3MgYW5kIHByb2JhYmlsaXR5IHByZWRpY3Rpb25zIHVzaW5nIGBjb2xsZWN0IHByZWRpY3Rpb25zKClgIGFuZCB5b3Ugd2lsbCBiZSB3ZWxsIG9uIHlvdXIgd2F5IHRvIGNvbXB1dGluZyB0aGUgY29uZnVzaW9uIG1hdHJpeCBhbmQgb3RoZXIgc3VtbWFyaWVzIHRoYXQgY29tZSB3aXRoIGl0Lg0KDQpgYGB7ciBjb2xsZWN0X3ByZWRpY3Rpb25zfQ0KIyBDb2xsZWN0IHByZWRpY3Rpb25zIGFuZCBtYWtlIGNvbmZ1c2lvbiBtYXRyaXgNCmZpbmFsX2ZpdCAlPiUgDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUgDQogIGNvbmZfbWF0KHRydXRoID0gc3BlY2llcywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykNCmBgYA0KDQojIyMjIFVzZSB0aGUgbW9kZWwgd2l0aCBuZXcgZGF0YSBvYnNlcnZhdGlvbnMNCg0KTm93IGxldCdzIHNhdmUgb3VyIHRyYWluZWQgbW9kZWwgc28gd2UgY2FuIHVzZSBpdCBhZ2FpbiBsYXRlci4gQmVnaW4gYnkgZXh0cmFjdGluZyB0aGUgKnRyYWluZWQgd29ya2Zsb3cqIG9iamVjdCBmcm9tIGBmaW5hbF9maXRgIG9iamVjdC4NCg0KYGBge3Igc2F2ZV93Zn0NCiMgRXh0cmFjdCB0cmFpbmVkIHdvcmtmbG93DQpwZW5ndWluc19zdm1fbW9kZWwgPC0gZmluYWxfZml0ICU+JSANCiAgZXh0cmFjdF93b3JrZmxvdygpDQoNCiMgU2F2ZSB3b3JrZmxvdw0Kc2F2ZVJEUyhwZW5ndWluc19zdm1fbW9kZWwsICJwZW5ndWluc19zdm1fbW9kZWwucmRzIikNCmBgYA0KDQpPSywgc28gbm93IHdlIGhhdmUgYSB0cmFpbmVkIG1vZGVsLiBMZXQncyB1c2UgaXQgdG8gcHJlZGljdCB0aGUgY2xhc3Mgb2YgYSBuZXcgcGVuZ3VpbiBvYnNlcnZhdGlvbjoNCg0KYGBge3IgbG9hZF9tb2RlbH0NCiMgTG9hZCBtb2RlbA0KbG9hZGVkX3Bzdm1fbW9kZWwgPC0gcmVhZFJEUygicGVuZ3VpbnNfc3ZtX21vZGVsLnJkcyIpIA0KDQojIENyZWF0ZSBuZXcgdGliYmxlIG9mIG9ic2VydmF0aW9ucw0KbmV3X29icyA8LSB0aWJibGUoDQogIGJpbGxfbGVuZ3RoX21tID0gYyg0OS41LCAzOC4yKSwNCiAgYmlsbF9kZXB0aF9tbSA9IGMoMTguNCwgMjAuMSksDQogIGZsaXBwZXJfbGVuZ3RoX21tID0gYygxOTUsIDE5MCksDQogIGJvZHlfbWFzc19nID0gYygzNjAwLCAzOTAwKSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpuZXdfcmVzdWx0cyA8LSBuZXdfb2JzICU+JSANCiAgYmluZF9jb2xzKGxvYWRlZF9wc3ZtX21vZGVsICU+JSANCiAgICAgICAgICAgICAgcHJlZGljdChuZXdfZGF0YSA9IG5ld19vYnMpKQ0KDQojIFNob3cgcHJlZGljdGlvbnMNCm5ld19yZXN1bHRzDQpgYGANCg0KR29vZCBqb2IhIEEgd29ya2luZyBtb2RlbCDwn5Cn8J+QpyENCg0KIyMgNy4gU3VtbWFyeQ0KDQpXZSBuZWVkIHRvICpjaGlsbCosIHJpZ2h0PyDwn5iFIFdlIGhvcGUgeW91IGhhZCBhIGZsaXBwaW4nIGdvb2QgdGltZSENCg0KSW4gdGhpcyBtb2R1bGUsIHlvdSBsZWFybmVkIGhvdyBjbGFzc2lmaWNhdGlvbiBjYW4gYmUgdXNlZCB0byBjcmVhdGUgYSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIHRoYXQgcHJlZGljdHMgY2F0ZWdvcmllcywgb3IgKmNsYXNzZXMqLiBZb3UgdGhlbiB1c2VkIHRoZSBhbWF6aW5nICoqVGlkeW1vZGVscyoqIGZyYW1ld29yayBpbiBgUmAgdG8gdHJhaW4gYW5kIGV2YWx1YXRlIGEgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgZGlmZmVyZW50IGFsZ29yaXRobXMsIGRvIHNvbWUgZGF0YSBwcmVwcm9jZXNzaW5nLCB0dW5lZCBzb21lIGh5cGVycGFyYW1ldGVycyBhbmQgbWFkZSBiZXR0ZXIgcHJlZGljdGlvbnMuDQoNCldoaWxlIGBUaWR5bW9kZWxzYCBhbmQgYHNjaWtpdC1sZWFybmAgKFB5dGhvbikgYXJlIHBvcHVsYXIgZnJhbWV3b3JrIGZvciB3cml0aW5nIGNvZGUgdG8gdHJhaW4gY2xhc3NpZmljYXRpb24gbW9kZWxzLCB5b3UgY2FuIGFsc28gY3JlYXRlIG1hY2hpbmUgbGVhcm5pbmcgc29sdXRpb25zIGZvciBjbGFzc2lmaWNhdGlvbiB1c2luZyB0aGUgZ3JhcGhpY2FsIHRvb2xzIGluIE1pY3Jvc29mdCBBenVyZSBNYWNoaW5lIExlYXJuaW5nLiBZb3UgY2FuIGxlYXJuIG1vcmUgYWJvdXQgbm8tY29kZSBkZXZlbG9wbWVudCBvZiBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgdXNpbmcgQXp1cmUgTWFjaGluZSBMZWFybmluZyBpbiBbQ3JlYXRlIGEgY2xhc3NpZmljYXRpb24gbW9kZWwgd2l0aCBBenVyZSBNYWNoaW5lIExlYXJuaW5nIGRlc2lnbmVyXShodHRwczovL2RvY3MubWljcm9zb2Z0LmNvbS9lbi11cy9sZWFybi9tb2R1bGVzL2NyZWF0ZS1jbGFzc2lmaWNhdGlvbi1tb2RlbC1henVyZS1tYWNoaW5lLWxlYXJuaW5nLWRlc2lnbmVyLykgbW9kdWxlLg0KDQojIyMjICoqQ2hhbGxlbmdlOiBQcmVkaWN0IFJlYWwgRXN0YXRlIFByaWNlcyoqDQoNCkZlZWwgbGlrZSBjaGFsbGVuZ2luZyB5b3Vyc2VsZiB0byB0cmFpbiBhIGNsYXNzaWZpY2F0aW9uIG1vZGVsPyBUcnkgdGhlIGNoYWxsZW5nZSBpbiB0aGUgWy9jaGFsbGVuZ2VzLzAzIC0gV2luZSBDbGFzc2lmaWNhdGlvbiBDaGFsbGVuZ2UuaXB5bmJdKGh0dHBzOi8vZ2l0aHViLmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcy9ibG9iL21hc3Rlci9jaGFsbGVuZ2VzLzAzJTIwLSUyMFdpbmUlMjBDbGFzc2lmaWNhdGlvbiUyMENoYWxsZW5nZS5pcHluYikgbm90ZWJvb2sgdG8gc2VlIGlmIHlvdSBjYW4gY2xhc3NpZnkgd2luZXMgaW50byB0aGVpciBncmFwZSB2YXJpZXRhbHMhIEZpbmQgdGhlIGRhdGEgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcy90cmVlL21hc3Rlci9jaGFsbGVuZ2VzL2RhdGEpLg0KDQojIyMjIFRIQU5LIFlPVSBUTzoNCg0KYEFsbGlzb24gSG9yc3RgIGZvciBjcmVhdGluZyB0aGUgYW1hemluZyBpbGx1c3RyYXRpb25zIHRoYXQgbWFrZSBSIG1vcmUgd2VsY29taW5nIGFuZCBlbmdhZ2luZy4gRmluZCBtb3JlIGlsbHVzdHJhdGlvbnMgYXQgaGVyIFtnYWxsZXJ5XShodHRwczovL3d3dy5nb29nbGUuY29tL3VybD9xPWh0dHBzOi8vZ2l0aHViLmNvbS9hbGxpc29uaG9yc3Qvc3RhdHMtaWxsdXN0cmF0aW9ucyZzYT1EJnNvdXJjZT1lZGl0b3JzJnVzdD0xNjI2MzgwNzcyNTMwMDAwJnVzZz1BT3ZWYXczemNmeUNpekZRWnBrU0x6eGlpUUVNKS4NCg0KYEJldGhhbnlgLCAqR29sZCBNaWNyb3NvZnQgTGVhcm4gU3R1ZGVudCBBbWJhc3NhZG9yKiwgZm9yIGhlciB2YWx1YWJsZSBmZWVkYmFjayBhbmQgc3VnZ2VzdGlvbnMuDQoNCiMjIyMgRlVSVEhFUiBSRUFESU5HDQoNCi0gICBHYXJldGggSmFtZXMsIERhbmllbGEgV2l0dGVuLCBUcmV2b3IgSGFzdGllLCBSb2JlcnQgVGlic2hpcmFuaS4gWypBbiBpbnRyb2R1Y3Rpb24gdG8gc3RhdGlzdGljYWwgbGVhcm5pbmcgOiB3aXRoIGFwcGxpY2F0aW9ucyBpbiBSKl0oaHR0cHM6Ly93d3cuc3RhdGxlYXJuaW5nLmNvbS8pKi4qIENvcnJlc3BvbmRpbmcgVGlkeW1vZGVscyBsYWJzIGJ5IEVtaWwgSHZpdGZlbGR0IGNhbiBiZSBmb3VuZCBbKmhlcmUqXShodHRwczovL2VtaWxodml0ZmVsZHQuZ2l0aHViLmlvL0lTTFItdGlkeW1vZGVscy1sYWJzL2luZGV4Lmh0bWwpKi4qDQoNCi0gICBNYXggS3VobiBhbmQgSnVsaWEgU2lsZ2UsIFsqVGlkeSBNb2RlbGluZyB3aXRoIFIqXShodHRwczovL3d3dy50bXdyLm9yZy8pKi4qDQoNCi0gICBLdWhuLCBNLCBhbmQgSyBKb2huc29uLiAyMDEzLiAqQXBwbGllZCBQcmVkaWN0aXZlIE1vZGVsaW5nKi4gU3ByaW5nZXIuDQoNCi0gICBCcmFkbGV5IEJvZWhta2UgJiBCcmFuZG9uIEdyZWVud2VsbCwgWypIYW5kcy1PbiBNYWNoaW5lIExlYXJuaW5nIHdpdGggUipdKGh0dHBzOi8vYnJhZGxleWJvZWhta2UuZ2l0aHViLmlvL0hPTUwvKSouKg0KDQotICAgSm9yZ2UgQ2ltZW50YWRhLCBbKk1hY2hpbmUgTGVhcm5pbmcgZm9yIFNvY2lhbCBTY2llbnRpc3RzKl0oaHR0cHM6Ly9jaW1lbnRhZGFqLmdpdGh1Yi5pby9tbF9zb2NzY2kvKSouKg0KDQotICAgVGlkeW1vZGVscyBbcmVmZXJlbmNlIHdlYnNpdGVdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnL3N0YXJ0LykuDQoNCi0gICBILiBXaWNraGFtIGFuZCBHLiBHcm9sZW11bmQsIFsqUiBmb3IgRGF0YSBTY2llbmNlOiBWaXN1YWxpemUsIE1vZGVsLCBUcmFuc2Zvcm0sIFRpZHksIGFuZCBJbXBvcnQgRGF0YSpdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovKS4NCg0KSGFwcHkgbGVhUm5pbmcsDQoNCltFcmljIChSX2ljKV0oaHR0cHM6Ly90d2l0dGVyLmNvbS9lcmljbnRheSksICpHb2xkIE1pY3Jvc29mdCBMZWFybiBTdHVkZW50IEFtYmFzc2Fkb3IqLg0KDQpgYGB7ciBtdWx0aV9zcGVjLCBldmFsPUZBTFNFLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9VFJVRSwgaW5jbHVkZT1GQUxTRX0NCiMgIyBTcGVjaWZ5IGEgbXVsdGlub21pYWwgcmVncmVzc2lvbiB2aWEgbm5ldA0KIyBtdWx0aXJlZ19zcGVjIDwtIG11bHRpbm9tX3JlZyhwZW5hbHR5ID0gdHVuZSgpKSAlPiUNCiMgICBzZXRfZW5naW5lKCJubmV0IikgJT4lDQojICAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCiMgDQojICMgU3BlY2lmeSBhIHJlY2lwZQ0KIyBtdWx0aXJlZ19yZWNpcGUgPC0gcmVjaXBlKHNwZWNpZXMgfiAuLCBkYXRhID0gcGVuZ3VpbnNfdHJhaW4pIA0KIyANCiMgIyBCdW5kbGUgbW9kZWwgc3BlY2lmaWNhdGlvbiBhbmQgcmVjaXBlIGludG8gYSB3b3JrZmxvdw0KIyBtdWx0aXJlZ193ZiA8LSB3b3JrZmxvdygpICU+JQ0KIyAgIGFkZF9yZWNpcGUobXVsdGlyZWdfcmVjaXBlKSAlPiUNCiMgICBhZGRfbW9kZWwobXVsdGlyZWdfc3BlYykNCiMgDQojICMgR3JpZCBzZWFyY2gNCiMgdHJlZV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksIGxldmVscyA9IDUwKQ0KIyANCiMgIyBSZXNhbXBsZXMNCiMgZm9sZHMgPC0gdmZvbGRfY3YocGVuZ3VpbnNfdHJhaW4sIHYgPSA1LCByZXBlYXRzID0gMSkNCg0KDQoNCg0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQojIGRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbCgpDQojIA0KIyB0cmVlX3JlcyA8LSB0dW5lX2dyaWQoDQojICAgbXVsdGlyZWdfd2YsDQojICAgcmVzYW1wbGVzID0gZm9sZHMsDQojICAgZ3JpZCA9IHRyZWVfZ3JpZA0KIyApDQpgYGANCg==