• Buckle up 🚀
    • Prerequisites
  • A gentle introduction to classification
  • 1. Binary classification
    • Explore the data
    • Split the data
    • Train and Evaluate a Binary Classification Model
  • 2. Recipes and workflows
  • 3. Try a different algorithm
  • 4. Multiclass Classification
  • 7. Summary

Buckle up 🚀

In this learning path, we’ll learn how to create Machine learning models using R 😊. Machine learning is the foundation for predictive modeling and artificial intelligence. We’ll learn the core principles of machine learning and how to use common tools and frameworks to train, evaluate, and use machine learning models.

Modules that will be covered in this learning path include:

  • Explore and analyze data with R

  • Train and evaluate regression models

  • Train and evaluate classification models

  • Train and evaluate clustering models (under development)

  • Train and evaluate deep learning models (under development)

Prerequisites

This learning path assumes knowledge of basic mathematical concepts. Some experience with R and the tidyverse is also beneficial though we’ll try as much as possible to skim through the core concepts. To get started with R and the tidyverse, the best place would be R for Data Science an O’Reilly book written by Hadley Wickham and Garrett Grolemund. It’s designed to take you from knowing nothing about R or the tidyverse to having all the basic tools of data science at your fingertips.

The Python edition of the learning path can be found at this learning path.

Why R?

First, while not the only good option, R has been shown to be popular and effective in modern data analysis. Second, R is free and open-source. You can install it anywhere, modify the code, and have the ability to see exactly how computations are performed. Third, it has excellent community support via the canonical R mailing lists and, more importantly, with Twitter, StackOverflow, and RStudio Community. Anyone who asks a reasonable, reproducible question has a pretty good chance of getting an answer. - Feature Engineering and Selection: A Practical Approach for Predictive Models, Max Kuhn and Kjell Johnson

Now, let’s get started!

Artwork by @allison_horst

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)
ABCDEFGHIJ0123456789
PatientID
<dbl>
Pregnancies
<dbl>
PlasmaGlucose
<dbl>
DiastolicBloodPressure
<dbl>
TricepsThickness
<dbl>
135477801718034
11474388929347
164003171154752
188335091037825
14241191855927
1619297082929
166014901334719
14587690678743
12016478809533
14039121723140

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)
ABCDEFGHIJ0123456789
pregnancies
<dbl>
plasma_glucose
<dbl>
diastolic_blood_pressure
<dbl>
triceps_thickness
<dbl>
01718034
8929347
71154752
91037825
1855927
082929
01334719
0678743
8809533
1723140

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)
ABCDEFGHIJ0123456789
diabetic
<fct>
features
<chr>
values
<dbl>
0pregnancies0.000000
0plasma_glucose171.000000
0diastolic_blood_pressure80.000000
0triceps_thickness34.000000
0serum_insulin23.000000
0bmi43.509726
0diabetes_pedigree1.213191
0age21.000000
0pregnancies8.000000
0plasma_glucose92.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:

  • initial_split(): specifies how data will be split into a training and testing set

  • training() and testing() functions extract the data in each split

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)
ABCDEFGHIJ0123456789
pregnancies
<dbl>
plasma_glucose
<dbl>
diastolic_blood_pressure
<dbl>
triceps_thickness
<dbl>
0841019
11575611
01025615
1819010
8976524

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)
ABCDEFGHIJ0123456789
diabetic
<fct>
.pred_class
<fct>
00
00
00
00
11
00
00
10
00
00

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)
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
accuracybinary0.7888889

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:

Truth
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)
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
ppvbinary0.7537815
recallbinary0.5772201
accuracybinary0.7888889
f_measbinary0.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 (1P(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)
ABCDEFGHIJ0123456789
diabetic
<fct>
.pred_class
<fct>
.pred_1
<dbl>
.pred_0
<dbl>
000.416662470.5833375
000.098481400.9015186
000.046947840.9530522
000.056061360.9439386
110.580557600.4194424
000.330661160.6693388
000.288265010.7117350
100.269918770.7300812
000.275017920.7249821
000.131275720.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)
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
roc_aucbinary0.8602241

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)
ABCDEFGHIJ0123456789
diabetic
<fct>
.pred_class
<fct>
.pred_1
<dbl>
.pred_0
<dbl>
003.181359e-010.68186413
001.456229e-010.85437708
008.747333e-020.91252667
005.700564e-101.00000000
119.310216e-010.06897839
005.451350e-090.99999999
015.697916e-010.43020837
116.938903e-010.30610969
004.401889e-091.00000000
001.913352e-010.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)
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
ppvbinary0.8199853
recallbinary0.7181467
accuracybinary0.8482222
f_measbinary0.7656947
# Evaluate ROC_AUC metrics
results %>% 
  roc_auc(diabetic, .pred_1)
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
roc_aucbinary0.9248788
# 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)
ABCDEFGHIJ0123456789
diabetic
<fct>
.pred_class
<fct>
.pred_1
<dbl>
.pred_0
<dbl>
000.2846471310.7153529
000.0120752180.9879248
000.0098160020.9901840
000.0107638380.9892362
110.8920126260.1079874
000.1137996060.8862004
000.2298680760.7701319
100.2663187090.7336813
000.0527353190.9472647
000.0384821620.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
ABCDEFGHIJ0123456789
pregnancies
<dbl>
plasma_glucose
<dbl>
diastolic_blood_pressure
<dbl>
triceps_thickness
<dbl>
61759022
184559
# Make predictions
predictions <- new_data %>% 
  bind_cols(loaded_model %>% predict(new_data))

predictions
ABCDEFGHIJ0123456789
pregnancies
<dbl>
plasma_glucose
<dbl>
diastolic_blood_pressure
<dbl>
triceps_thickness
<dbl>
61759022
184559

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.

Meet the data

The Palmer Archipelago penguins. Artwork by @allison_horst.

In this sections, we’ll build a multiclass classifier for classifying penguins!

The palmerpenguins data contains size measurements for three penguin species observed on three islands in the Palmer Archipelago, Antarctica.

These data were collected from 2007 - 2009 by Dr. Kristen Gorman with the Palmer Station Long Term Ecological Research Program, part of the US Long Term Ecological Research Network. The data were imported directly from the Environmental Data Initiative (EDI) Data Portal, and are available for use by CC0 license (“No Rights Reserved”) in accordance with the Palmer Station Data Policy.

In R, the package palmerpenguins by Allison Marie Horst and Alison Presmanes Hill and Kristen B Gorman provides us with data related to these adorable creatures.

The corresponding csv data used in the Python tutorial can be found here.

Care for a pun?

What is a penguin’s favorite movie?

Frozen ❄️👧.

With that, let’s get started exploring some penguins 🐧🐧!

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

skim_variable n_missing complete_rate ordered n_unique top_counts
species 0 1 FALSE 3 Ade: 152, Gen: 124, Chi: 68

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
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))
ABCDEFGHIJ0123456789
bill_length_mm
<dbl>
bill_depth_mm
<dbl>
flipper_length_mm
<int>
body_mass_g
<int>
species
<fct>
NANANANAAdelie
NANANANAGentoo

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)
ABCDEFGHIJ0123456789
species
<fct>
n
<int>
Adelie151
Chinstrap68
Gentoo123

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)
ABCDEFGHIJ0123456789
species
<fct>
.pred_class
<fct>
.pred_Adelie
<dbl>
.pred_Chinstrap
<dbl>
.pred_Gentoo
<dbl>
AdelieAdelie0.99990584.334555e-055.085677e-05
AdelieAdelie0.99962003.584026e-042.157623e-05
AdelieAdelie0.98957378.454760e-031.971491e-03
AdelieAdelie0.99166966.837429e-031.493014e-03
AdelieAdelie0.92369317.613199e-021.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()
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
accuracymulticlass0.9903846
kapmulticlass0.9848507
sensmacro0.9841270
specmacro0.9942529
ppvmacro0.9929078
npvmacro0.9960317
mccmulticlass0.9850012
j_indexmacro0.9783799
bal_accuracymacro0.9891899
detection_prevalencemacro0.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))
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
roc_auchand_till1

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)
ABCDEFGHIJ0123456789
cost
<dbl>
rbf_sigma
<dbl>
9.765625e-041e-10
7.812500e-031e-10
6.250000e-021e-10
5.000000e-011e-10
4.000000e+001e-10
3.200000e+011e-10
9.765625e-041e-08
7.812500e-031e-08
6.250000e-021e-08
5.000000e-011e-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
ABCDEFGHIJ0123456789
splits
<list>
id
<chr>
<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)
ABCDEFGHIJ0123456789
cost
<dbl>
rbf_sigma
<dbl>
.metric
<chr>
.estimator
<chr>
mean
<dbl>
n
<int>
std_err
<dbl>
0.00097656251e-10accuracymulticlass0.4279212100.010790273
0.00097656251e-10roc_auchand_till0.9809805100.003231725
0.00781250001e-10accuracymulticlass0.4279212100.010790273
0.00781250001e-10roc_auchand_till0.9809669100.003253624
0.06250000001e-10accuracymulticlass0.4279212100.010790273
0.06250000001e-10roc_auchand_till0.9803196100.003260246
0.50000000001e-10accuracymulticlass0.4279212100.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")
ABCDEFGHIJ0123456789
cost
<dbl>
rbf_sigma
<dbl>
.metric
<chr>
.estimator
<chr>
mean
<dbl>
n
<int>
std_err
<dbl>
32.01.00accuracymulticlass0.9751752100.005042863
32.00.01accuracymulticlass0.9749459100.004247809
4.01.00accuracymulticlass0.9739020100.005514761
4.00.01accuracymulticlass0.9706938100.004727220
0.51.00accuracymulticlass0.9694269100.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
ABCDEFGHIJ0123456789
cost
<dbl>
rbf_sigma
<dbl>
.config
<chr>
321Preprocessor1_Model36

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()
ABCDEFGHIJ0123456789
.metric
<chr>
.estimator
<chr>
.estimate
<dbl>
.config
<chr>
accuracymulticlass0.9711538Preprocessor1_Model1
roc_auchand_till0.9981021Preprocessor1_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
ABCDEFGHIJ0123456789
bill_length_mm
<dbl>
bill_depth_mm
<dbl>
flipper_length_mm
<dbl>
body_mass_g
<dbl>
.pred_class
<fct>
49.518.41953600Chinstrap
38.220.11903900Adelie

Good job! A working model 🐧🐧!

7. Summary

We need to chill, right? 😅 We hope you had a flippin’ good time!

In this module, you learned how classification can be used to create a machine learning model that predicts categories, or classes. You then used the amazing Tidymodels framework in R to train and evaluate a classification model using different algorithms, do some data preprocessing, tuned some hyperparameters and made better predictions.

While Tidymodels and scikit-learn (Python) are popular framework for writing code to train classification models, you can also create machine learning solutions for classification using the graphical tools in Microsoft Azure Machine Learning. You can learn more about no-code development of classification models using Azure Machine Learning in Create a classification model with Azure Machine Learning designer module.

Challenge: Predict Real Estate Prices

Feel like challenging yourself to train a classification model? Try the challenge in the /challenges/03 - Wine Classification Challenge.ipynb notebook to see if you can classify wines into their grape varietals! Find the data here.

THANK YOU TO:

Allison Horst for creating the amazing illustrations that make R more welcoming and engaging. Find more illustrations at her gallery.

Bethany, Gold Microsoft Learn Student Ambassador, for her valuable feedback and suggestions.

FURTHER READING

Happy leaRning,

Eric (R_ic), Gold Microsoft Learn Student Ambassador.

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==