Build a logistic regression model - Lesson 4
Logistic vs. linear regression infographic
Introduction
In this final lesson on Regression, one of the basic classic
ML techniques, we will take a look at Logistic Regression. You would use
this technique to discover patterns to predict binary categories. Is
this candy chocolate or not? Is this disease contagious or not? Will
this customer choose this product or not?
In this lesson, you will learn:
- Techniques for logistic regression
✅ Deepen your understanding of working with this type of regression
in this Learn
module
Prerequisite
Having worked with the pumpkin data, we are now familiar enough with
it to realize that there’s one binary category that we can work with:
Color
.
Let’s build a logistic regression model to predict that, given some
variables, what color a given pumpkin is likely to be (orange
🎃 or white 👻).
Why are we talking about binary classification in a lesson grouping
about regression? Only for linguistic convenience, as logistic
regression is really
a classification method, albeit a linear-based one. Learn about
other ways to classify data in the next lesson group.
For this lesson, we’ll require the following packages:
You can have them installed as:
install.packages(c("tidyverse", "tidymodels", "janitor", "ggbeeswarm"))
Alternately, the script below checks whether you have the packages
required to complete this module and installs them for you in case they
are missing.
suppressWarnings(if (!require("pacman"))install.packages("pacman"))
pacman::p_load(tidyverse, tidymodels, janitor, ggbeeswarm)
Define the question
For our purposes, we will express this as a binary: ‘White’ or ‘Not
White’. There is also a ‘striped’ category in our dataset but there are
few instances of it, so we will not use it. It disappears once we remove
null values from the dataset, anyway.
🎃 Fun fact, we sometimes call white pumpkins ‘ghost’ pumpkins. They
aren’t very easy to carve, so they aren’t as popular as the orange ones
but they are cool looking! So we could also reformulate our question as:
‘Ghost’ or ‘Not Ghost’. 👻
About logistic regression
Logistic regression differs from linear regression, which you learned
about previously, in a few important ways.
Binary classification
Logistic regression does not offer the same features as linear
regression. The former offers a prediction about a
binary category
(“orange or not orange”) whereas the latter
is capable of predicting continual values
, for example
given the origin of a pumpkin and the time of harvest, how much its
price will rise.
Infographic by Dasani Madipalli
Other classifications
There are other types of logistic regression, including multinomial
and ordinal:
Multinomial, which involves having more than one
category - “Orange, White, and Striped”.
Ordinal, which involves ordered categories,
useful if we wanted to order our outcomes logically, like our pumpkins
that are ordered by a finite number of sizes
(mini,sm,med,lg,xl,xxl).
Multinomial vs ordinal regression
Variables DO NOT have to correlate
Remember how linear regression worked better with more correlated
variables? Logistic regression is the opposite - the variables don’t
have to align. That works for this data which has somewhat weak
correlations.
You need a lot of clean data
Logistic regression will give more accurate results if you use more
data; our small dataset is not optimal for this task, so keep that in
mind.
✅ Think about the types of data that would lend themselves well to
logistic regression
Exercise - tidy the data
First, clean the data a bit, dropping null values and selecting only
some of the columns:
- Add the following code:
# Load the core tidyverse packages
library(tidyverse)
# Import the data and clean column names
pumpkins <- read_csv(file = "https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/2-Regression/data/US-pumpkins.csv") %>%
clean_names()
# Select desired columns
pumpkins_select <- pumpkins %>%
select(c(city_name, package, variety, origin, item_size, color))
# Drop rows containing missing values and encode color as factor (category)
pumpkins_select <- pumpkins_select %>%
drop_na() %>%
mutate(color = factor(color))
# View the first few rows
pumpkins_select %>%
slice_head(n = 5)
You can always take a peek at your new dataframe, by using the glimpse()
function as below:
pumpkins_select %>%
glimpse()
## Rows: 991
## Columns: 6
## $ city_name <chr> "BALTIMORE", "BALTIMORE", "BALTIMORE", "BALTIMORE", "BALTIMO…
## $ package <chr> "24 inch bins", "24 inch bins", "24 inch bins", "24 inch bin…
## $ variety <chr> "HOWDEN TYPE", "HOWDEN TYPE", "HOWDEN TYPE", "HOWDEN TYPE", …
## $ origin <chr> "DELAWARE", "VIRGINIA", "MARYLAND", "MARYLAND", "MARYLAND", …
## $ item_size <chr> "med", "med", "lge", "lge", "med", "lge", "med", "lge", "med…
## $ color <fct> ORANGE, ORANGE, ORANGE, ORANGE, ORANGE, ORANGE, ORANGE, ORAN…
Let’s confirm that we will actually be doing a binary classification
problem:
# Subset distinct observations in outcome column
pumpkins_select %>%
distinct(color)
Visualization - categorical plot
By now you have loaded up the pumpkin data once again and cleaned it
so as to preserve a dataset containing a few variables, including Color.
Let’s visualize the dataframe in the notebook using ggplot library.
The ggplot library offers some neat ways to visualize your data. For
example, you can compare distributions of the data for each Variety and
Color in a categorical plot.
- Create such a plot by using the geombar function, using our pumpkin
data, and specifying a color mapping for each pumpkin category (orange
or white):
# Specify colors for each value of the hue variable
palette <- c(ORANGE = "orange", WHITE = "wheat")
# Create the bar plot
ggplot(pumpkins_select, aes(y = variety, fill = color)) +
geom_bar(position = "dodge") +
scale_fill_manual(values = palette) +
labs(y = "Variety", fill = "Color") +
theme_minimal()
By observing the data, you can see how the Color data relates to
Variety.
✅ Given this categorical plot, what are some interesting
explorations you can envision?
Data pre-processing: feature encoding
Our pumpkins dataset contains string values for all its columns.
Working with categorical data is intuitive for humans but not for
machines. Machine learning algorithms work well with numbers. That’s why
encoding is a very important step in the data pre-processing phase,
since it enables us to turn categorical data into numerical data,
without losing any information. Good encoding leads to building a good
model.
For feature encoding there are two main types of encoders:
Ordinal encoder: it suits well for ordinal variables, which are
categorical variables where their data follows a logical ordering, like
the item_size
column in our dataset. It creates a mapping
such that each category is represented by a number, which is the order
of the category in the column.
Categorical encoder: it suits well for nominal variables, which
are categorical variables where their data does not follow a logical
ordering, like all the features different from item_size
in
our dataset. It is a one-hot encoding, which means that each category is
represented by a binary column: the encoded variable is equal to 1 if
the pumpkin belongs to that Variety and 0 otherwise.
Tidymodels provides yet another neat package: recipes- a package for
preprocessing data. We’ll define a recipe
that specifies
that all predictor columns should be encoded into a set of integers ,
prep
it to estimates the required quantities and statistics
needed by any operations and finally bake
to apply the
computations to new data.
Normally, recipes is usually used as a preprocessor for modelling
where it defines what steps should be applied to a data set in order to
get it ready for modelling. In that case it is highly
recommend that you use a workflow()
instead of
manually estimating a recipe using prep and bake. We’ll see all this in
just a moment.
However for now, we are using recipes + prep + bake to specify what
steps should be applied to a data set in order to get it ready for data
analysis and then extract the preprocessed data with the steps
applied.
# Preprocess and extract data to allow some data analysis
baked_pumpkins <- recipe(color ~ ., data = pumpkins_select) %>%
# Define ordering for item_size column
step_mutate(item_size = ordered(item_size, levels = c('sml', 'med', 'med-lge', 'lge', 'xlge', 'jbo', 'exjbo'))) %>%
# Convert factors to numbers using the order defined above (Ordinal encoding)
step_integer(item_size, zero_based = F) %>%
# Encode all other predictors using one hot encoding
step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE) %>%
prep(data = pumpkin_select) %>%
bake(new_data = NULL)
# Display the first few rows of preprocessed data
baked_pumpkins %>%
slice_head(n = 5)
✅ What are the advantages of using an ordinal encoder for the Item
Size column?
Analyse relationships between variables
Now that we have pre-processed our data, we can analyse the
relationships between the features and the label to grasp an idea of how
well the model will be able to predict the label given the features. The
best way to perform this kind of analysis is plotting the data. We’ll be
using again the ggplot geom_boxplot_ function, to visualize the
relationships between Item Size, Variety and Color in a categorical
plot. To better plot the data we’ll be using the encoded Item Size
column and the unencoded Variety column.
# Define the color palette
palette <- c(ORANGE = "orange", WHITE = "wheat")
# We need the encoded Item Size column to use it as the x-axis values in the plot
pumpkins_select_plot<-pumpkins_select
pumpkins_select_plot$item_size <- baked_pumpkins$item_size
# Create the grouped box plot
ggplot(pumpkins_select_plot, aes(x = `item_size`, y = color, fill = color)) +
geom_boxplot() +
facet_grid(variety ~ ., scales = "free_x") +
scale_fill_manual(values = palette) +
labs(x = "Item Size", y = "") +
theme_minimal() +
theme(strip.text = element_text(size = 12)) +
theme(axis.text.x = element_text(size = 10)) +
theme(axis.title.x = element_text(size = 12)) +
theme(axis.title.y = element_blank()) +
theme(legend.position = "bottom") +
guides(fill = guide_legend(title = "Color")) +
theme(panel.spacing = unit(0.5, "lines"))+
theme(strip.text.y = element_text(size = 4, hjust = 0))
Use a swarm plot
Since Color is a binary category (White or Not), it needs ‘a specialized
approach to visualization’.
Try a swarm plot
to show the distribution of color with
respect to the item_size.
We’ll use the ggbeeswarm package
which provides methods to create beeswarm-style plots using ggplot2.
Beeswarm plots are a way of plotting points that would ordinarily
overlap so that they fall next to each other instead.
# Create beeswarm plots of color and item_size
baked_pumpkins %>%
mutate(color = factor(color)) %>%
ggplot(mapping = aes(x = color, y = item_size, color = color)) +
geom_quasirandom() +
scale_color_brewer(palette = "Dark2", direction = -1) +
theme(legend.position = "none")
Now that we have an idea of the relationship between the binary
categories of color and the larger group of sizes, let’s explore
logistic regression to determine a given pumpkin’s likely color.
Build your model
Select the variables you want to use in your classification model and
split the data into training and test sets. rsample, a package in
Tidymodels, provides infrastructure for efficient data splitting and
resampling:
# Split data into 80% for training and 20% for testing
set.seed(2056)
pumpkins_split <- pumpkins_select %>%
initial_split(prop = 0.8)
# Extract the data in each split
pumpkins_train <- training(pumpkins_split)
pumpkins_test <- testing(pumpkins_split)
# Print out the first 5 rows of the training set
pumpkins_train %>%
slice_head(n = 5)
🙌 We are now ready to train a model by fitting the training features
to the training label (color).
We’ll begin by creating a recipe that specifies the preprocessing
steps that should be carried out on our data to get it ready for
modelling i.e: encoding categorical variables into a set of integers.
Just like baked_pumpkins
, we create a
pumpkins_recipe
but do not prep
and
bake
since it would be bundled into a workflow, which you
will see in just a few steps from now.
There are quite a number of ways to specify a logistic regression
model in Tidymodels. See ?logistic_reg()
For now, we’ll
specify a logistic regression model via the default
stats::glm()
engine.
# Create a recipe that specifies preprocessing steps for modelling
pumpkins_recipe <- recipe(color ~ ., data = pumpkins_train) %>%
step_mutate(item_size = ordered(item_size, levels = c('sml', 'med', 'med-lge', 'lge', 'xlge', 'jbo', 'exjbo'))) %>%
step_integer(item_size, zero_based = F) %>%
step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE)
# Create a logistic model specification
log_reg <- logistic_reg() %>%
set_engine("glm") %>%
set_mode("classification")
Now that we have a recipe and a model specification, we need to find
a way of bundling them together into an object that will first
preprocess the data (prep+bake behind the scenes), 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.
# Bundle modelling components in a workflow
log_reg_wf <- workflow() %>%
add_recipe(pumpkins_recipe) %>%
add_model(log_reg)
# Print out the workflow
log_reg_wf
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 3 Recipe Steps
##
## • step_mutate()
## • step_integer()
## • 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. The workflow will estimate a recipe and preprocess the data
before training, so we won’t have to manually do that using prep and
bake.
# Train the model
wf_fit <- log_reg_wf %>%
fit(data = pumpkins_train)
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
# Print the trained workflow
wf_fit
## ══ Workflow [trained] ══════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 3 Recipe Steps
##
## • step_mutate()
## • step_integer()
## • step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
##
## Call: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)
##
## Coefficients:
## (Intercept) item_size
## -7.596e+15 5.343e+13
## city_name_ATLANTA city_name_BALTIMORE
## 4.227e+15 3.812e+15
## city_name_BOSTON city_name_CHICAGO
## 3.432e+15 1.745e+15
## city_name_COLUMBIA city_name_DALLAS
## 2.306e+15 1.914e+15
## city_name_DETROIT city_name_LOS.ANGELES
## 2.137e+14 2.958e+15
## city_name_MIAMI city_name_NEW.YORK
## 1.892e+15 1.999e+15
## city_name_PHILADELPHIA city_name_SAN.FRANCISCO
## 1.698e+15 1.399e+15
## city_name_ST..LOUIS package_X1.1.9.bushel.cartons
## NA 2.129e+15
## package_X1.1.9.bushel.crates package_X1.2.bushel.cartons
## 5.432e+15 6.396e+13
## package_X24.inch.bins package_X36.inch.bins
## 3.219e+15 2.116e+15
## package_bins package_bushel.cartons
## 1.227e+15 NA
## variety_BIG.MACK.TYPE variety_BLUE.TYPE
## 4.932e+14 1.999e+15
## variety_CINDERELLA variety_FAIRYTALE
## 4.701e+15 3.003e+15
## variety_HOWDEN.TYPE variety_HOWDEN.WHITE.TYPE
## -1.199e+15 4.840e+15
## variety_KNUCKLE.HEAD variety_MINIATURE
## 1.999e+15 5.946e+15
## variety_PIE.TYPE origin_ALABAMA
## NA 1.242e+15
## origin_CALIFORNIA origin_CANADA
## NA -1.750e+15
## origin_DELAWARE origin_ILLINOIS
## -1.701e+15 1.979e+15
## origin_MARYLAND origin_MASSACHUSETTS
## 3.171e+14 -1.321e+15
## origin_MEXICO origin_MICHIGAN
## 7.961e+13 1.630e+13
## origin_NEW.JERSEY origin_NEW.YORK
## -2.679e+14 1.416e+14
## origin_NORTH.CAROLINA origin_OHIO
## -9.301e+14 4.669e+14
## origin_PENNSYLVANIA origin_TENNESSEE
## 9.575e+14 2.239e+15
##
## ...
## and 8 more lines.
The model print out shows the coefficients learned during
training.
Now we’ve trained the model using the training data, we can make
predictions on the test data using parsnip::predict().
Let’s start by using the model to predict labels for our test set and
the probabilities for each label. When the probability is more than 0.5,
the predict class is WHITE
else ORANGE
.
# Make predictions for color and corresponding probabilities
results <- pumpkins_test %>% select(color) %>%
bind_cols(wf_fit %>%
predict(new_data = pumpkins_test)) %>%
bind_cols(wf_fit %>%
predict(new_data = pumpkins_test, type = "prob"))
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type == :
## prediction from rank-deficient fit; attr(*, "non-estim") has doubtful cases
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type == :
## prediction from rank-deficient fit; attr(*, "non-estim") has doubtful cases
# Compare predictions
results %>%
slice_head(n = 10)
Very nice! This provides some more insights into how logistic
regression works.
Better comprehension via a confusion matrix
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.
One performance metric associated with classification problems is the
confusion matrix
.
A confusion matrix describes how well a classification model performs. A
confusion matrix tabulates how many examples in each class were
correctly classified by a model. In our case, it will show you how many
orange pumpkins were classified as orange and how many white pumpkins
were classified as white; 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 = color, estimate = .pred_class)
## Truth
## Prediction ORANGE WHITE
## ORANGE 153 0
## WHITE 18 28
Let’s interpret the confusion matrix. Our model is asked to classify
pumpkins between two binary categories, category white
and
category not-white
If your model predicts a pumpkin as white and it belongs to
category ‘white’ in reality we call it a true positive
,
shown by the top left number.
If your model predicts a pumpkin as not white and it belongs to
category ‘white’ in reality we call it a false negative
,
shown by the bottom left number.
If your model predicts a pumpkin as white and it belongs to
category ‘not-white’ in reality we call it a
false positive
, shown by the top right number.
If your model predicts a pumpkin as not white and it belongs to
category ‘not-white’ in reality we call it a true negative
,
shown by the bottom right number.
Predicted |
WHITE |
ORANGE |
WHITE |
TP |
FP |
ORANGE |
FN |
TN |
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.
Let’s calculate these metrics!
# Combine metric functions and calculate them all at once
eval_metrics <- metric_set(ppv, recall, spec, f_meas, accuracy)
eval_metrics(data = results, truth = color, estimate = .pred_class)
Visualize the ROC curve of this model
Let’s do one more visualization to see the so-called ROC curve
:
# Make a roc_curve
results %>%
roc_curve(color, .pred_ORANGE) %>%
autoplot()
ROC curves are often used to get a view of the output of a classifier
in terms of its true vs. false positives. ROC curves typically feature
True Positive Rate
/Sensitivity on the Y axis, and
False Positive Rate
/1-Specificity on the X axis. Thus, the
steepness of the curve and the space between the midpoint line and the
curve matter: you want a curve that quickly heads up and over the line.
In our case, there are false positives to start with, and then the line
heads up and over properly.
Finally, let’s use yardstick::roc_auc()
to calculate the
actual Area Under the Curve. One way of interpreting AUC is as the
probability that the model ranks a random positive example more highly
than a random negative example.
# Calculate area under curve
results %>%
roc_auc(color, .pred_ORANGE)
The result is around 0.975
. Given that the AUC ranges
from 0 to 1, you want a big score, since a model that is 100% correct in
its predictions will have an AUC of 1; in this case, the model is
pretty good.
In future lessons on classifications, you will learn how to improve
your model’s scores (such as dealing with imbalanced data in this
case).
🚀Challenge
There’s a lot more to unpack regarding logistic regression! But the
best way to learn is to experiment. Find a dataset that lends itself to
this type of analysis and build a model with it. What do you learn? tip:
try Kaggle
for interesting datasets.
Review & Self Study
Read the first few pages of this paper from
Stanford on some practical uses for logistic regression. Think about
tasks that are better suited for one or the other type of regression
tasks that we have studied up to this point. What would work best?
LS0tCnRpdGxlOiAnQnVpbGQgYSByZWdyZXNzaW9uIG1vZGVsOiBsb2dpc3RpYyByZWdyZXNzaW9uJwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHllcwotLS0KCiMjIEJ1aWxkIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCAtIExlc3NvbiA0CgohW0xvZ2lzdGljIHZzLiBsaW5lYXIgcmVncmVzc2lvbiBpbmZvZ3JhcGhpY10oLi4vLi4vaW1hZ2VzL2xpbmVhci12cy1sb2dpc3RpYy5wbmcpCgojIyMjICoqW1ByZS1sZWN0dXJlIHF1aXpdKGh0dHBzOi8vZ3JheS1zYW5kLTA3YTEwZjQwMy4xLmF6dXJlc3RhdGljYXBwcy5uZXQvcXVpei8xNS8pKioKCiMjIyMgIEludHJvZHVjdGlvbgoKSW4gdGhpcyBmaW5hbCBsZXNzb24gb24gUmVncmVzc2lvbiwgb25lIG9mIHRoZSBiYXNpYyAqY2xhc3NpYyogTUwgdGVjaG5pcXVlcywgd2Ugd2lsbCB0YWtlIGEgbG9vayBhdCBMb2dpc3RpYyBSZWdyZXNzaW9uLiBZb3Ugd291bGQgdXNlIHRoaXMgdGVjaG5pcXVlIHRvIGRpc2NvdmVyIHBhdHRlcm5zIHRvIHByZWRpY3QgYmluYXJ5IGNhdGVnb3JpZXMuIElzIHRoaXMgY2FuZHkgY2hvY29sYXRlIG9yIG5vdD8gSXMgdGhpcyBkaXNlYXNlIGNvbnRhZ2lvdXMgb3Igbm90PyBXaWxsIHRoaXMgY3VzdG9tZXIgY2hvb3NlIHRoaXMgcHJvZHVjdCBvciBub3Q/CgpJbiB0aGlzIGxlc3NvbiwgeW91IHdpbGwgbGVhcm46CgotICAgVGVjaG5pcXVlcyBmb3IgbG9naXN0aWMgcmVncmVzc2lvbgoK4pyFIERlZXBlbiB5b3VyIHVuZGVyc3RhbmRpbmcgb2Ygd29ya2luZyB3aXRoIHRoaXMgdHlwZSBvZiByZWdyZXNzaW9uIGluIHRoaXMgW0xlYXJuIG1vZHVsZV0oaHR0cHM6Ly9sZWFybi5taWNyb3NvZnQuY29tL3RyYWluaW5nL21vZHVsZXMvaW50cm9kdWN0aW9uLWNsYXNzaWZpY2F0aW9uLW1vZGVscy8/V1QubWNfaWQ9YWNhZGVtaWMtNzc5NTItbGVlc3RvdHQpCgojIyBQcmVyZXF1aXNpdGUKCkhhdmluZyB3b3JrZWQgd2l0aCB0aGUgcHVtcGtpbiBkYXRhLCB3ZSBhcmUgbm93IGZhbWlsaWFyIGVub3VnaCB3aXRoIGl0IHRvIHJlYWxpemUgdGhhdCB0aGVyZSdzIG9uZSBiaW5hcnkgY2F0ZWdvcnkgdGhhdCB3ZSBjYW4gd29yayB3aXRoOiBgQ29sb3JgLgoKTGV0J3MgYnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgdGhhdCwgZ2l2ZW4gc29tZSB2YXJpYWJsZXMsICp3aGF0IGNvbG9yIGEgZ2l2ZW4gcHVtcGtpbiBpcyBsaWtlbHkgdG8gYmUqIChvcmFuZ2Ug8J+OgyBvciB3aGl0ZSDwn5G7KS4KCj4gV2h5IGFyZSB3ZSB0YWxraW5nIGFib3V0IGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBpbiBhIGxlc3NvbiBncm91cGluZyBhYm91dCByZWdyZXNzaW9uPyBPbmx5IGZvciBsaW5ndWlzdGljIGNvbnZlbmllbmNlLCBhcyBsb2dpc3RpYyByZWdyZXNzaW9uIGlzIFtyZWFsbHkgYSBjbGFzc2lmaWNhdGlvbiBtZXRob2RdKGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9saW5lYXJfbW9kZWwuaHRtbCNsb2dpc3RpYy1yZWdyZXNzaW9uKSwgYWxiZWl0IGEgbGluZWFyLWJhc2VkIG9uZS4gTGVhcm4gYWJvdXQgb3RoZXIgd2F5cyB0byBjbGFzc2lmeSBkYXRhIGluIHRoZSBuZXh0IGxlc3NvbiBncm91cC4KCkZvciB0aGlzIGxlc3Nvbiwgd2UnbGwgcmVxdWlyZSB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOgoKLSAgIGB0aWR5dmVyc2VgOiBUaGUgW3RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pIGlzIGEgW2NvbGxlY3Rpb24gb2YgUiBwYWNrYWdlc10oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy9wYWNrYWdlcykgZGVzaWduZWQgdG8gbWFrZXMgZGF0YSBzY2llbmNlIGZhc3RlciwgZWFzaWVyIGFuZCBtb3JlIGZ1biEKCi0gICBgdGlkeW1vZGVsc2A6IFRoZSBbdGlkeW1vZGVsc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvKSBmcmFtZXdvcmsgaXMgYSBbY29sbGVjdGlvbiBvZiBwYWNrYWdlc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvcGFja2FnZXMvKSBmb3IgbW9kZWxpbmcgYW5kIG1hY2hpbmUgbGVhcm5pbmcuCgotICAgYGphbml0b3JgOiBUaGUgW2phbml0b3IgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3NmaXJrZS9qYW5pdG9yKSBwcm92aWRlcyBzaW1wbGUgbGl0dGxlIHRvb2xzIGZvciBleGFtaW5pbmcgYW5kIGNsZWFuaW5nIGRpcnR5IGRhdGEuCgotICAgYGdnYmVlc3dhcm1gOiBUaGUgW2dnYmVlc3dhcm0gcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2VjbGFya2UvZ2diZWVzd2FybSkgcHJvdmlkZXMgbWV0aG9kcyB0byBjcmVhdGUgYmVlc3dhcm0tc3R5bGUgcGxvdHMgdXNpbmcgZ2dwbG90Mi4KCllvdSBjYW4gaGF2ZSB0aGVtIGluc3RhbGxlZCBhczoKCmBpbnN0YWxsLnBhY2thZ2VzKGMoInRpZHl2ZXJzZSIsICJ0aWR5bW9kZWxzIiwgImphbml0b3IiLCAiZ2diZWVzd2FybSIpKWAKCkFsdGVybmF0ZWx5LCB0aGUgc2NyaXB0IGJlbG93IGNoZWNrcyB3aGV0aGVyIHlvdSBoYXZlIHRoZSBwYWNrYWdlcyByZXF1aXJlZCB0byBjb21wbGV0ZSB0aGlzIG1vZHVsZSBhbmQgaW5zdGFsbHMgdGhlbSBmb3IgeW91IGluIGNhc2UgdGhleSBhcmUgbWlzc2luZy4KCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0Kc3VwcHJlc3NXYXJuaW5ncyhpZiAoIXJlcXVpcmUoInBhY21hbiIpKWluc3RhbGwucGFja2FnZXMoInBhY21hbiIpKQoKcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLCB0aWR5bW9kZWxzLCBqYW5pdG9yLCBnZ2JlZXN3YXJtKQpgYGAKCiMjICoqRGVmaW5lIHRoZSBxdWVzdGlvbioqCgpGb3Igb3VyIHB1cnBvc2VzLCB3ZSB3aWxsIGV4cHJlc3MgdGhpcyBhcyBhIGJpbmFyeTogJ1doaXRlJyBvciAnTm90IFdoaXRlJy4gVGhlcmUgaXMgYWxzbyBhICdzdHJpcGVkJyBjYXRlZ29yeSBpbiBvdXIgZGF0YXNldCBidXQgdGhlcmUgYXJlIGZldyBpbnN0YW5jZXMgb2YgaXQsIHNvIHdlIHdpbGwgbm90IHVzZSBpdC4gSXQgZGlzYXBwZWFycyBvbmNlIHdlIHJlbW92ZSBudWxsIHZhbHVlcyBmcm9tIHRoZSBkYXRhc2V0LCBhbnl3YXkuCgo+IPCfjoMgRnVuIGZhY3QsIHdlIHNvbWV0aW1lcyBjYWxsIHdoaXRlIHB1bXBraW5zICdnaG9zdCcgcHVtcGtpbnMuIFRoZXkgYXJlbid0IHZlcnkgZWFzeSB0byBjYXJ2ZSwgc28gdGhleSBhcmVuJ3QgYXMgcG9wdWxhciBhcyB0aGUgb3JhbmdlIG9uZXMgYnV0IHRoZXkgYXJlIGNvb2wgbG9va2luZyEgU28gd2UgY291bGQgYWxzbyByZWZvcm11bGF0ZSBvdXIgcXVlc3Rpb24gYXM6ICdHaG9zdCcgb3IgJ05vdCBHaG9zdCcuIPCfkbsKCiMjICoqQWJvdXQgbG9naXN0aWMgcmVncmVzc2lvbioqCgpMb2dpc3RpYyByZWdyZXNzaW9uIGRpZmZlcnMgZnJvbSBsaW5lYXIgcmVncmVzc2lvbiwgd2hpY2ggeW91IGxlYXJuZWQgYWJvdXQgcHJldmlvdXNseSwgaW4gYSBmZXcgaW1wb3J0YW50IHdheXMuCgojIyMjICoqQmluYXJ5IGNsYXNzaWZpY2F0aW9uKioKCkxvZ2lzdGljIHJlZ3Jlc3Npb24gZG9lcyBub3Qgb2ZmZXIgdGhlIHNhbWUgZmVhdHVyZXMgYXMgbGluZWFyIHJlZ3Jlc3Npb24uIFRoZSBmb3JtZXIgb2ZmZXJzIGEgcHJlZGljdGlvbiBhYm91dCBhIGBiaW5hcnkgY2F0ZWdvcnlgICgib3JhbmdlIG9yIG5vdCBvcmFuZ2UiKSB3aGVyZWFzIHRoZSBsYXR0ZXIgaXMgY2FwYWJsZSBvZiBwcmVkaWN0aW5nIGBjb250aW51YWwgdmFsdWVzYCwgZm9yIGV4YW1wbGUgZ2l2ZW4gdGhlIG9yaWdpbiBvZiBhIHB1bXBraW4gYW5kIHRoZSB0aW1lIG9mIGhhcnZlc3QsICpob3cgbXVjaCBpdHMgcHJpY2Ugd2lsbCByaXNlKi4KCiFbSW5mb2dyYXBoaWMgYnkgRGFzYW5pIE1hZGlwYWxsaV0oLi4vLi4vaW1hZ2VzL3B1bXBraW4tY2xhc3NpZmllci5wbmcpCgojIyMgT3RoZXIgY2xhc3NpZmljYXRpb25zCgpUaGVyZSBhcmUgb3RoZXIgdHlwZXMgb2YgbG9naXN0aWMgcmVncmVzc2lvbiwgaW5jbHVkaW5nIG11bHRpbm9taWFsIGFuZCBvcmRpbmFsOgoKLSAqKk11bHRpbm9taWFsKiosIHdoaWNoIGludm9sdmVzIGhhdmluZyBtb3JlIHRoYW4gb25lIGNhdGVnb3J5IC0gIk9yYW5nZSwgV2hpdGUsIGFuZCBTdHJpcGVkIi4KCi0gKipPcmRpbmFsKiosIHdoaWNoIGludm9sdmVzIG9yZGVyZWQgY2F0ZWdvcmllcywgdXNlZnVsIGlmIHdlIHdhbnRlZCB0byBvcmRlciBvdXIgb3V0Y29tZXMgbG9naWNhbGx5LCBsaWtlIG91ciBwdW1wa2lucyB0aGF0IGFyZSBvcmRlcmVkIGJ5IGEgZmluaXRlIG51bWJlciBvZiBzaXplcyAobWluaSxzbSxtZWQsbGcseGwseHhsKS4KCiFbTXVsdGlub21pYWwgdnMgb3JkaW5hbCByZWdyZXNzaW9uXSguLi8uLi9pbWFnZXMvbXVsdGlub21pYWwtdnMtb3JkaW5hbC5wbmcpCgojIyMjICoqVmFyaWFibGVzIERPIE5PVCBoYXZlIHRvIGNvcnJlbGF0ZSoqCgpSZW1lbWJlciBob3cgbGluZWFyIHJlZ3Jlc3Npb24gd29ya2VkIGJldHRlciB3aXRoIG1vcmUgY29ycmVsYXRlZCB2YXJpYWJsZXM/IExvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdGhlIG9wcG9zaXRlIC0gdGhlIHZhcmlhYmxlcyBkb24ndCBoYXZlIHRvIGFsaWduLiBUaGF0IHdvcmtzIGZvciB0aGlzIGRhdGEgd2hpY2ggaGFzIHNvbWV3aGF0IHdlYWsgY29ycmVsYXRpb25zLgoKIyMjIyAqKllvdSBuZWVkIGEgbG90IG9mIGNsZWFuIGRhdGEqKgoKTG9naXN0aWMgcmVncmVzc2lvbiB3aWxsIGdpdmUgbW9yZSBhY2N1cmF0ZSByZXN1bHRzIGlmIHlvdSB1c2UgbW9yZSBkYXRhOyBvdXIgc21hbGwgZGF0YXNldCBpcyBub3Qgb3B0aW1hbCBmb3IgdGhpcyB0YXNrLCBzbyBrZWVwIHRoYXQgaW4gbWluZC4KCuKchSBUaGluayBhYm91dCB0aGUgdHlwZXMgb2YgZGF0YSB0aGF0IHdvdWxkIGxlbmQgdGhlbXNlbHZlcyB3ZWxsIHRvIGxvZ2lzdGljIHJlZ3Jlc3Npb24KCiMjIEV4ZXJjaXNlIC0gdGlkeSB0aGUgZGF0YQoKRmlyc3QsIGNsZWFuIHRoZSBkYXRhIGEgYml0LCBkcm9wcGluZyBudWxsIHZhbHVlcyBhbmQgc2VsZWN0aW5nIG9ubHkgc29tZSBvZiB0aGUgY29sdW1uczoKCjEuIEFkZCB0aGUgZm9sbG93aW5nIGNvZGU6CgpgYGB7ciwgdGlkeXIsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojIExvYWQgdGhlIGNvcmUgdGlkeXZlcnNlIHBhY2thZ2VzCmxpYnJhcnkodGlkeXZlcnNlKQoKIyBJbXBvcnQgdGhlIGRhdGEgYW5kIGNsZWFuIGNvbHVtbiBuYW1lcwpwdW1wa2lucyA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvTUwtRm9yLUJlZ2lubmVycy9tYWluLzItUmVncmVzc2lvbi9kYXRhL1VTLXB1bXBraW5zLmNzdiIpICU+JSAKICBjbGVhbl9uYW1lcygpCgojIFNlbGVjdCBkZXNpcmVkIGNvbHVtbnMKcHVtcGtpbnNfc2VsZWN0IDwtIHB1bXBraW5zICU+JSAKICBzZWxlY3QoYyhjaXR5X25hbWUsIHBhY2thZ2UsIHZhcmlldHksIG9yaWdpbiwgaXRlbV9zaXplLCBjb2xvcikpIAoKIyBEcm9wIHJvd3MgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcyBhbmQgZW5jb2RlIGNvbG9yIGFzIGZhY3RvciAoY2F0ZWdvcnkpCnB1bXBraW5zX3NlbGVjdCA8LSBwdW1wa2luc19zZWxlY3QgJT4lIAogIGRyb3BfbmEoKSAlPiUgCiAgbXV0YXRlKGNvbG9yID0gZmFjdG9yKGNvbG9yKSkKCiMgVmlldyB0aGUgZmlyc3QgZmV3IHJvd3MKcHVtcGtpbnNfc2VsZWN0ICU+JSAKICBzbGljZV9oZWFkKG4gPSA1KQpgYGAKCllvdSBjYW4gYWx3YXlzIHRha2UgYSBwZWVrIGF0IHlvdXIgbmV3IGRhdGFmcmFtZSwgYnkgdXNpbmcgdGhlIFsqZ2xpbXBzZSgpKl0oaHR0cHM6Ly9waWxsYXIuci1saWIub3JnL3JlZmVyZW5jZS9nbGltcHNlLmh0bWwpIGZ1bmN0aW9uIGFzIGJlbG93OgoKCmBgYHtyIGdsaW1wc2V9CnB1bXBraW5zX3NlbGVjdCAlPiUgCiAgZ2xpbXBzZSgpCmBgYAoKTGV0J3MgY29uZmlybSB0aGF0IHdlIHdpbGwgYWN0dWFsbHkgYmUgZG9pbmcgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbToKCmBgYHtyIGRpc3RpbmN0IGNvbG9yfQojIFN1YnNldCBkaXN0aW5jdCBvYnNlcnZhdGlvbnMgaW4gb3V0Y29tZSBjb2x1bW4KcHVtcGtpbnNfc2VsZWN0ICU+JSAKICBkaXN0aW5jdChjb2xvcikKYGBgCgojIyMgVmlzdWFsaXphdGlvbiAtIGNhdGVnb3JpY2FsIHBsb3QKQnkgbm93IHlvdSBoYXZlIGxvYWRlZCB1cCB0aGUgcHVtcGtpbiBkYXRhIG9uY2UgYWdhaW4gYW5kIGNsZWFuZWQgaXQgc28gYXMgdG8gcHJlc2VydmUgYSBkYXRhc2V0IGNvbnRhaW5pbmcgYSBmZXcgdmFyaWFibGVzLCBpbmNsdWRpbmcgQ29sb3IuIExldCdzIHZpc3VhbGl6ZSB0aGUgZGF0YWZyYW1lIGluIHRoZSBub3RlYm9vayB1c2luZyBnZ3Bsb3QgbGlicmFyeS4KClRoZSBnZ3Bsb3QgbGlicmFyeSBvZmZlcnMgc29tZSBuZWF0IHdheXMgdG8gdmlzdWFsaXplIHlvdXIgZGF0YS4gRm9yIGV4YW1wbGUsIHlvdSBjYW4gY29tcGFyZSBkaXN0cmlidXRpb25zIG9mIHRoZSBkYXRhIGZvciBlYWNoIFZhcmlldHkgYW5kIENvbG9yIGluIGEgY2F0ZWdvcmljYWwgcGxvdC4KCjEuIENyZWF0ZSBzdWNoIGEgcGxvdCBieSB1c2luZyB0aGUgZ2VvbWJhciBmdW5jdGlvbiwgdXNpbmcgb3VyIHB1bXBraW4gZGF0YSwgYW5kIHNwZWNpZnlpbmcgYSBjb2xvciBtYXBwaW5nIGZvciBlYWNoIHB1bXBraW4gY2F0ZWdvcnkgKG9yYW5nZSBvciB3aGl0ZSk6CgpgYGB7cn0KIyBTcGVjaWZ5IGNvbG9ycyBmb3IgZWFjaCB2YWx1ZSBvZiB0aGUgaHVlIHZhcmlhYmxlCnBhbGV0dGUgPC0gYyhPUkFOR0UgPSAib3JhbmdlIiwgV0hJVEUgPSAid2hlYXQiKQoKIyBDcmVhdGUgdGhlIGJhciBwbG90CmdncGxvdChwdW1wa2luc19zZWxlY3QsIGFlcyh5ID0gdmFyaWV0eSwgZmlsbCA9IGNvbG9yKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGUpICsKICBsYWJzKHkgPSAiVmFyaWV0eSIsIGZpbGwgPSAiQ29sb3IiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKQnkgb2JzZXJ2aW5nIHRoZSBkYXRhLCB5b3UgY2FuIHNlZSBob3cgdGhlIENvbG9yIGRhdGEgcmVsYXRlcyB0byBWYXJpZXR5LgoK4pyFIEdpdmVuIHRoaXMgY2F0ZWdvcmljYWwgcGxvdCwgd2hhdCBhcmUgc29tZSBpbnRlcmVzdGluZyBleHBsb3JhdGlvbnMgeW91IGNhbiBlbnZpc2lvbj8KCiMjIyBEYXRhIHByZS1wcm9jZXNzaW5nOiBmZWF0dXJlIGVuY29kaW5nCgpPdXIgcHVtcGtpbnMgZGF0YXNldCBjb250YWlucyBzdHJpbmcgdmFsdWVzIGZvciBhbGwgaXRzIGNvbHVtbnMuIFdvcmtpbmcgd2l0aCBjYXRlZ29yaWNhbCBkYXRhIGlzIGludHVpdGl2ZSBmb3IgaHVtYW5zIGJ1dCBub3QgZm9yIG1hY2hpbmVzLiBNYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgd29yayB3ZWxsIHdpdGggbnVtYmVycy4gVGhhdCdzIHdoeSBlbmNvZGluZyBpcyBhIHZlcnkgaW1wb3J0YW50IHN0ZXAgaW4gdGhlIGRhdGEgcHJlLXByb2Nlc3NpbmcgcGhhc2UsIHNpbmNlIGl0IGVuYWJsZXMgdXMgdG8gdHVybiBjYXRlZ29yaWNhbCBkYXRhIGludG8gbnVtZXJpY2FsIGRhdGEsIHdpdGhvdXQgbG9zaW5nIGFueSBpbmZvcm1hdGlvbi4gR29vZCBlbmNvZGluZyBsZWFkcyB0byBidWlsZGluZyBhIGdvb2QgbW9kZWwuCgpGb3IgZmVhdHVyZSBlbmNvZGluZyB0aGVyZSBhcmUgdHdvIG1haW4gdHlwZXMgb2YgZW5jb2RlcnM6CgoxLiBPcmRpbmFsIGVuY29kZXI6IGl0IHN1aXRzIHdlbGwgZm9yIG9yZGluYWwgdmFyaWFibGVzLCB3aGljaCBhcmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdoZXJlIHRoZWlyIGRhdGEgZm9sbG93cyBhIGxvZ2ljYWwgb3JkZXJpbmcsIGxpa2UgdGhlIGBpdGVtX3NpemVgIGNvbHVtbiBpbiBvdXIgZGF0YXNldC4gSXQgY3JlYXRlcyBhIG1hcHBpbmcgc3VjaCB0aGF0IGVhY2ggY2F0ZWdvcnkgaXMgcmVwcmVzZW50ZWQgYnkgYSBudW1iZXIsIHdoaWNoIGlzIHRoZSBvcmRlciBvZiB0aGUgY2F0ZWdvcnkgaW4gdGhlIGNvbHVtbi4KCjIuIENhdGVnb3JpY2FsIGVuY29kZXI6IGl0IHN1aXRzIHdlbGwgZm9yIG5vbWluYWwgdmFyaWFibGVzLCB3aGljaCBhcmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdoZXJlIHRoZWlyIGRhdGEgZG9lcyBub3QgZm9sbG93IGEgbG9naWNhbCBvcmRlcmluZywgbGlrZSBhbGwgdGhlIGZlYXR1cmVzIGRpZmZlcmVudCBmcm9tIGBpdGVtX3NpemVgIGluIG91ciBkYXRhc2V0LiBJdCBpcyBhIG9uZS1ob3QgZW5jb2RpbmcsIHdoaWNoIG1lYW5zIHRoYXQgZWFjaCBjYXRlZ29yeSBpcyByZXByZXNlbnRlZCBieSBhIGJpbmFyeSBjb2x1bW46IHRoZSBlbmNvZGVkIHZhcmlhYmxlIGlzIGVxdWFsIHRvIDEgaWYgdGhlIHB1bXBraW4gYmVsb25ncyB0byB0aGF0IFZhcmlldHkgYW5kIDAgb3RoZXJ3aXNlLgoKVGlkeW1vZGVscyBwcm92aWRlcyB5ZXQgYW5vdGhlciBuZWF0IHBhY2thZ2U6IFtyZWNpcGVzXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvKS0gYSBwYWNrYWdlIGZvciBwcmVwcm9jZXNzaW5nIGRhdGEuIFdlJ2xsIGRlZmluZSBhIGByZWNpcGVgIHRoYXQgc3BlY2lmaWVzIHRoYXQgYWxsIHByZWRpY3RvciBjb2x1bW5zIHNob3VsZCBiZSBlbmNvZGVkIGludG8gYSBzZXQgb2YgaW50ZWdlcnMgLCBgcHJlcGAgaXQgdG8gZXN0aW1hdGVzIHRoZSByZXF1aXJlZCBxdWFudGl0aWVzIGFuZCBzdGF0aXN0aWNzIG5lZWRlZCBieSBhbnkgb3BlcmF0aW9ucyBhbmQgZmluYWxseSBgYmFrZWAgdG8gYXBwbHkgdGhlIGNvbXB1dGF0aW9ucyB0byBuZXcgZGF0YS4KCj4gTm9ybWFsbHksIHJlY2lwZXMgaXMgdXN1YWxseSB1c2VkIGFzIGEgcHJlcHJvY2Vzc29yIGZvciBtb2RlbGxpbmcgd2hlcmUgaXQgZGVmaW5lcyB3aGF0IHN0ZXBzIHNob3VsZCBiZSBhcHBsaWVkIHRvIGEgZGF0YSBzZXQgaW4gb3JkZXIgdG8gZ2V0IGl0IHJlYWR5IGZvciBtb2RlbGxpbmcuIEluIHRoYXQgY2FzZSBpdCBpcyAqKmhpZ2hseSByZWNvbW1lbmQqKiB0aGF0IHlvdSB1c2UgYSBgd29ya2Zsb3coKWAgaW5zdGVhZCBvZiBtYW51YWxseSBlc3RpbWF0aW5nIGEgcmVjaXBlIHVzaW5nIHByZXAgYW5kIGJha2UuIFdlJ2xsIHNlZSBhbGwgdGhpcyBpbiBqdXN0IGEgbW9tZW50Lgo+Cj4gSG93ZXZlciBmb3Igbm93LCB3ZSBhcmUgdXNpbmcgcmVjaXBlcyArIHByZXAgKyBiYWtlIHRvIHNwZWNpZnkgd2hhdCBzdGVwcyBzaG91bGQgYmUgYXBwbGllZCB0byBhIGRhdGEgc2V0IGluIG9yZGVyIHRvIGdldCBpdCByZWFkeSBmb3IgZGF0YSBhbmFseXNpcyBhbmQgdGhlbiBleHRyYWN0IHRoZSBwcmVwcm9jZXNzZWQgZGF0YSB3aXRoIHRoZSBzdGVwcyBhcHBsaWVkLgoKYGBge3IgcmVjaXBlX3ByZXBfYmFrZX0KIyBQcmVwcm9jZXNzIGFuZCBleHRyYWN0IGRhdGEgdG8gYWxsb3cgc29tZSBkYXRhIGFuYWx5c2lzCmJha2VkX3B1bXBraW5zIDwtIHJlY2lwZShjb2xvciB+IC4sIGRhdGEgPSBwdW1wa2luc19zZWxlY3QpICU+JQogICMgRGVmaW5lIG9yZGVyaW5nIGZvciBpdGVtX3NpemUgY29sdW1uCiAgc3RlcF9tdXRhdGUoaXRlbV9zaXplID0gb3JkZXJlZChpdGVtX3NpemUsIGxldmVscyA9IGMoJ3NtbCcsICdtZWQnLCAnbWVkLWxnZScsICdsZ2UnLCAneGxnZScsICdqYm8nLCAnZXhqYm8nKSkpICU+JQogICMgQ29udmVydCBmYWN0b3JzIHRvIG51bWJlcnMgdXNpbmcgdGhlIG9yZGVyIGRlZmluZWQgYWJvdmUgKE9yZGluYWwgZW5jb2RpbmcpCiAgc3RlcF9pbnRlZ2VyKGl0ZW1fc2l6ZSwgemVyb19iYXNlZCA9IEYpICU+JQogICMgRW5jb2RlIGFsbCBvdGhlciBwcmVkaWN0b3JzIHVzaW5nIG9uZSBob3QgZW5jb2RpbmcKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpICU+JQogIHByZXAoZGF0YSA9IHB1bXBraW5fc2VsZWN0KSAlPiUKICBiYWtlKG5ld19kYXRhID0gTlVMTCkKCiMgRGlzcGxheSB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgcHJlcHJvY2Vzc2VkIGRhdGEKYmFrZWRfcHVtcGtpbnMgJT4lIAogIHNsaWNlX2hlYWQobiA9IDUpCmBgYAoK4pyFIFdoYXQgYXJlIHRoZSBhZHZhbnRhZ2VzIG9mIHVzaW5nIGFuIG9yZGluYWwgZW5jb2RlciBmb3IgdGhlIEl0ZW0gU2l6ZSBjb2x1bW4/CgojIyMgQW5hbHlzZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdmFyaWFibGVzCgpOb3cgdGhhdCB3ZSBoYXZlIHByZS1wcm9jZXNzZWQgb3VyIGRhdGEsIHdlIGNhbiBhbmFseXNlIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIGZlYXR1cmVzIGFuZCB0aGUgbGFiZWwgdG8gZ3Jhc3AgYW4gaWRlYSBvZiBob3cgd2VsbCB0aGUgbW9kZWwgd2lsbCBiZSBhYmxlIHRvIHByZWRpY3QgdGhlIGxhYmVsIGdpdmVuIHRoZSBmZWF0dXJlcy4gVGhlIGJlc3Qgd2F5IHRvIHBlcmZvcm0gdGhpcyBraW5kIG9mIGFuYWx5c2lzIGlzIHBsb3R0aW5nIHRoZSBkYXRhLiAKV2UnbGwgYmUgdXNpbmcgYWdhaW4gdGhlIGdncGxvdCBnZW9tX2JveHBsb3RfIGZ1bmN0aW9uLCB0byB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBJdGVtIFNpemUsIFZhcmlldHkgYW5kIENvbG9yIGluIGEgY2F0ZWdvcmljYWwgcGxvdC4gVG8gYmV0dGVyIHBsb3QgdGhlIGRhdGEgd2UnbGwgYmUgdXNpbmcgdGhlIGVuY29kZWQgSXRlbSBTaXplIGNvbHVtbiBhbmQgdGhlIHVuZW5jb2RlZCBWYXJpZXR5IGNvbHVtbi4KCmBgYHtyIGJveHBsb3R9CiMgRGVmaW5lIHRoZSBjb2xvciBwYWxldHRlCnBhbGV0dGUgPC0gYyhPUkFOR0UgPSAib3JhbmdlIiwgV0hJVEUgPSAid2hlYXQiKQoKIyBXZSBuZWVkIHRoZSBlbmNvZGVkIEl0ZW0gU2l6ZSBjb2x1bW4gdG8gdXNlIGl0IGFzIHRoZSB4LWF4aXMgdmFsdWVzIGluIHRoZSBwbG90CnB1bXBraW5zX3NlbGVjdF9wbG90PC1wdW1wa2luc19zZWxlY3QKcHVtcGtpbnNfc2VsZWN0X3Bsb3QkaXRlbV9zaXplIDwtIGJha2VkX3B1bXBraW5zJGl0ZW1fc2l6ZQoKIyBDcmVhdGUgdGhlIGdyb3VwZWQgYm94IHBsb3QKZ2dwbG90KHB1bXBraW5zX3NlbGVjdF9wbG90LCBhZXMoeCA9IGBpdGVtX3NpemVgLCB5ID0gY29sb3IsIGZpbGwgPSBjb2xvcikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfZ3JpZCh2YXJpZXR5IH4gLiwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlKSArCiAgbGFicyh4ID0gIkl0ZW0gU2l6ZSIsIHkgPSAiIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJDb2xvciIpKSArCiAgdGhlbWUocGFuZWwuc3BhY2luZyA9IHVuaXQoMC41LCAibGluZXMiKSkrCiAgdGhlbWUoc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA0LCBoanVzdCA9IDApKSAKYGBgCgojIyMjIFVzZSBhIHN3YXJtIHBsb3QKClNpbmNlIENvbG9yIGlzIGEgYmluYXJ5IGNhdGVnb3J5IChXaGl0ZSBvciBOb3QpLCBpdCBuZWVkcyAnYSBbc3BlY2lhbGl6ZWQgYXBwcm9hY2hdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL2Jsb2IvbWFpbi9kYXRhLXZpc3VhbGl6YXRpb24ucGRmKSB0byB2aXN1YWxpemF0aW9uJy4KClRyeSBhIGBzd2FybSBwbG90YCB0byBzaG93IHRoZSBkaXN0cmlidXRpb24gb2YgY29sb3Igd2l0aCByZXNwZWN0IHRvIHRoZSBpdGVtX3NpemUuCgpXZSdsbCB1c2UgdGhlIFtnZ2JlZXN3YXJtIHBhY2thZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9lY2xhcmtlL2dnYmVlc3dhcm0pIHdoaWNoIHByb3ZpZGVzIG1ldGhvZHMgdG8gY3JlYXRlIGJlZXN3YXJtLXN0eWxlIHBsb3RzIHVzaW5nIGdncGxvdDIuIEJlZXN3YXJtIHBsb3RzIGFyZSBhIHdheSBvZiBwbG90dGluZyBwb2ludHMgdGhhdCB3b3VsZCBvcmRpbmFyaWx5IG92ZXJsYXAgc28gdGhhdCB0aGV5IGZhbGwgbmV4dCB0byBlYWNoIG90aGVyIGluc3RlYWQuCgpgYGB7ciBiZWVfc3dhcm0gcGxvdH0KIyBDcmVhdGUgYmVlc3dhcm0gcGxvdHMgb2YgY29sb3IgYW5kIGl0ZW1fc2l6ZQpiYWtlZF9wdW1wa2lucyAlPiUgCiAgbXV0YXRlKGNvbG9yID0gZmFjdG9yKGNvbG9yKSkgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBjb2xvciwgeSA9IGl0ZW1fc2l6ZSwgY29sb3IgPSBjb2xvcikpICsKICBnZW9tX3F1YXNpcmFuZG9tKCkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgZGlyZWN0aW9uID0gLTEpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKTm93IHRoYXQgd2UgaGF2ZSBhbiBpZGVhIG9mIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgYmluYXJ5IGNhdGVnb3JpZXMgb2YgY29sb3IgYW5kIHRoZSBsYXJnZXIgZ3JvdXAgb2Ygc2l6ZXMsIGxldCdzIGV4cGxvcmUgbG9naXN0aWMgcmVncmVzc2lvbiB0byBkZXRlcm1pbmUgYSBnaXZlbiBwdW1wa2luJ3MgbGlrZWx5IGNvbG9yLgoKIyMgQnVpbGQgeW91ciBtb2RlbAoKU2VsZWN0IHRoZSB2YXJpYWJsZXMgeW91IHdhbnQgdG8gdXNlIGluIHlvdXIgY2xhc3NpZmljYXRpb24gbW9kZWwgYW5kIHNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cy4gW3JzYW1wbGVdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy8pLCBhIHBhY2thZ2UgaW4gVGlkeW1vZGVscywgcHJvdmlkZXMgaW5mcmFzdHJ1Y3R1cmUgZm9yIGVmZmljaWVudCBkYXRhIHNwbGl0dGluZyBhbmQgcmVzYW1wbGluZzoKCmBgYHtyIHNwbGl0X2RhdGF9CiMgU3BsaXQgZGF0YSBpbnRvIDgwJSBmb3IgdHJhaW5pbmcgYW5kIDIwJSBmb3IgdGVzdGluZwpzZXQuc2VlZCgyMDU2KQpwdW1wa2luc19zcGxpdCA8LSBwdW1wa2luc19zZWxlY3QgJT4lIAogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuOCkKCiMgRXh0cmFjdCB0aGUgZGF0YSBpbiBlYWNoIHNwbGl0CnB1bXBraW5zX3RyYWluIDwtIHRyYWluaW5nKHB1bXBraW5zX3NwbGl0KQpwdW1wa2luc190ZXN0IDwtIHRlc3RpbmcocHVtcGtpbnNfc3BsaXQpCgojIFByaW50IG91dCB0aGUgZmlyc3QgNSByb3dzIG9mIHRoZSB0cmFpbmluZyBzZXQKcHVtcGtpbnNfdHJhaW4gJT4lIAogIHNsaWNlX2hlYWQobiA9IDUpCmBgYAoK8J+ZjCBXZSBhcmUgbm93IHJlYWR5IHRvIHRyYWluIGEgbW9kZWwgYnkgZml0dGluZyB0aGUgdHJhaW5pbmcgZmVhdHVyZXMgdG8gdGhlIHRyYWluaW5nIGxhYmVsIChjb2xvcikuCgpXZSdsbCBiZWdpbiBieSBjcmVhdGluZyBhIHJlY2lwZSB0aGF0IHNwZWNpZmllcyB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwcyB0aGF0IHNob3VsZCBiZSBjYXJyaWVkIG91dCBvbiBvdXIgZGF0YSB0byBnZXQgaXQgcmVhZHkgZm9yIG1vZGVsbGluZyBpLmU6IGVuY29kaW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbnRvIGEgc2V0IG9mIGludGVnZXJzLiBKdXN0IGxpa2UgYGJha2VkX3B1bXBraW5zYCwgd2UgY3JlYXRlIGEgYHB1bXBraW5zX3JlY2lwZWAgYnV0IGRvIG5vdCBgcHJlcGAgYW5kIGBiYWtlYCBzaW5jZSBpdCB3b3VsZCBiZSBidW5kbGVkIGludG8gYSB3b3JrZmxvdywgd2hpY2ggeW91IHdpbGwgc2VlIGluIGp1c3QgYSBmZXcgc3RlcHMgZnJvbSBub3cuIAoKVGhlcmUgYXJlIHF1aXRlIGEgbnVtYmVyIG9mIHdheXMgdG8gc3BlY2lmeSBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgaW4gVGlkeW1vZGVscy4gU2VlIGA/bG9naXN0aWNfcmVnKClgIEZvciBub3csIHdlJ2xsIHNwZWNpZnkgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHZpYSB0aGUgZGVmYXVsdCBgc3RhdHM6OmdsbSgpYCBlbmdpbmUuCgpgYGB7ciBsb2dfcmVnfQojIENyZWF0ZSBhIHJlY2lwZSB0aGF0IHNwZWNpZmllcyBwcmVwcm9jZXNzaW5nIHN0ZXBzIGZvciBtb2RlbGxpbmcKcHVtcGtpbnNfcmVjaXBlIDwtIHJlY2lwZShjb2xvciB+IC4sIGRhdGEgPSBwdW1wa2luc190cmFpbikgJT4lIAogIHN0ZXBfbXV0YXRlKGl0ZW1fc2l6ZSA9IG9yZGVyZWQoaXRlbV9zaXplLCBsZXZlbHMgPSBjKCdzbWwnLCAnbWVkJywgJ21lZC1sZ2UnLCAnbGdlJywgJ3hsZ2UnLCAnamJvJywgJ2V4amJvJykpKSAlPiUKICBzdGVwX2ludGVnZXIoaXRlbV9zaXplLCB6ZXJvX2Jhc2VkID0gRikgJT4lICAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpCgojIENyZWF0ZSBhIGxvZ2lzdGljIG1vZGVsIHNwZWNpZmljYXRpb24KbG9nX3JlZyA8LSBsb2dpc3RpY19yZWcoKSAlPiUgCiAgc2V0X2VuZ2luZSgiZ2xtIikgJT4lIAogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCmBgYAoKTm93IHRoYXQgd2UgaGF2ZSBhIHJlY2lwZSBhbmQgYSBtb2RlbCBzcGVjaWZpY2F0aW9uLCB3ZSBuZWVkIHRvIGZpbmQgYSB3YXkgb2YgYnVuZGxpbmcgdGhlbSB0b2dldGhlciBpbnRvIGFuIG9iamVjdCB0aGF0IHdpbGwgZmlyc3QgcHJlcHJvY2VzcyB0aGUgZGF0YSAocHJlcCtiYWtlIGJlaGluZCB0aGUgc2NlbmVzKSwgZml0IHRoZSBtb2RlbCBvbiB0aGUgcHJlcHJvY2Vzc2VkIGRhdGEgYW5kIGFsc28gYWxsb3cgZm9yIHBvdGVudGlhbCBwb3N0LXByb2Nlc3NpbmcgYWN0aXZpdGllcy4KCkluIFRpZHltb2RlbHMsIHRoaXMgY29udmVuaWVudCBvYmplY3QgaXMgY2FsbGVkIGEgW2B3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnLykgYW5kIGNvbnZlbmllbnRseSBob2xkcyB5b3VyIG1vZGVsaW5nIGNvbXBvbmVudHMuCgpgYGB7ciB3b3JrZmxvd30KIyBCdW5kbGUgbW9kZWxsaW5nIGNvbXBvbmVudHMgaW4gYSB3b3JrZmxvdwpsb2dfcmVnX3dmIDwtIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUocHVtcGtpbnNfcmVjaXBlKSAlPiUgCiAgYWRkX21vZGVsKGxvZ19yZWcpCgojIFByaW50IG91dCB0aGUgd29ya2Zsb3cKbG9nX3JlZ193ZgpgYGAKCkFmdGVyIGEgd29ya2Zsb3cgaGFzIGJlZW4gKnNwZWNpZmllZCosIGEgbW9kZWwgY2FuIGJlIGB0cmFpbmVkYCB1c2luZyB0aGUgW2BmaXQoKWBdKGh0dHBzOi8vdGlkeW1vZGVscy5naXRodWIuaW8vcGFyc25pcC9yZWZlcmVuY2UvZml0Lmh0bWwpIGZ1bmN0aW9uLiBUaGUgd29ya2Zsb3cgd2lsbCBlc3RpbWF0ZSBhIHJlY2lwZSBhbmQgcHJlcHJvY2VzcyB0aGUgZGF0YSBiZWZvcmUgdHJhaW5pbmcsIHNvIHdlIHdvbid0IGhhdmUgdG8gbWFudWFsbHkgZG8gdGhhdCB1c2luZyBwcmVwIGFuZCBiYWtlLgoKCmBgYHtyIHRyYWlufQojIFRyYWluIHRoZSBtb2RlbAp3Zl9maXQgPC0gbG9nX3JlZ193ZiAlPiUgCiAgZml0KGRhdGEgPSBwdW1wa2luc190cmFpbikKCiMgUHJpbnQgdGhlIHRyYWluZWQgd29ya2Zsb3cKd2ZfZml0CmBgYAoKVGhlIG1vZGVsIHByaW50IG91dCBzaG93cyB0aGUgY29lZmZpY2llbnRzIGxlYXJuZWQgZHVyaW5nIHRyYWluaW5nLgoKTm93IHdlJ3ZlIHRyYWluZWQgdGhlIG1vZGVsIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhLCB3ZSBjYW4gbWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhIHVzaW5nIFtwYXJzbmlwOjpwcmVkaWN0KCldKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcHJlZGljdC5tb2RlbF9maXQuaHRtbCkuIExldCdzIHN0YXJ0IGJ5IHVzaW5nIHRoZSBtb2RlbCB0byBwcmVkaWN0IGxhYmVscyBmb3Igb3VyIHRlc3Qgc2V0IGFuZCB0aGUgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBsYWJlbC4gV2hlbiB0aGUgcHJvYmFiaWxpdHkgaXMgbW9yZSB0aGFuIDAuNSwgdGhlIHByZWRpY3QgY2xhc3MgaXMgYFdISVRFYCBlbHNlIGBPUkFOR0VgLgoKYGBge3IgdGVzdF9wcmVkfQojIE1ha2UgcHJlZGljdGlvbnMgZm9yIGNvbG9yIGFuZCBjb3JyZXNwb25kaW5nIHByb2JhYmlsaXRpZXMKcmVzdWx0cyA8LSBwdW1wa2luc190ZXN0ICU+JSBzZWxlY3QoY29sb3IpICU+JSAKICBiaW5kX2NvbHMod2ZfZml0ICU+JSAKICAgICAgICAgICAgICBwcmVkaWN0KG5ld19kYXRhID0gcHVtcGtpbnNfdGVzdCkpICU+JQogIGJpbmRfY29scyh3Zl9maXQgJT4lCiAgICAgICAgICAgICAgcHJlZGljdChuZXdfZGF0YSA9IHB1bXBraW5zX3Rlc3QsIHR5cGUgPSAicHJvYiIpKQoKIyBDb21wYXJlIHByZWRpY3Rpb25zCnJlc3VsdHMgJT4lIAogIHNsaWNlX2hlYWQobiA9IDEwKQpgYGAKClZlcnkgbmljZSEgVGhpcyBwcm92aWRlcyBzb21lIG1vcmUgaW5zaWdodHMgaW50byBob3cgbG9naXN0aWMgcmVncmVzc2lvbiB3b3Jrcy4KCiMjIyBCZXR0ZXIgY29tcHJlaGVuc2lvbiB2aWEgYSBjb25mdXNpb24gbWF0cml4CgpDb21wYXJpbmcgZWFjaCBwcmVkaWN0aW9uIHdpdGggaXRzIGNvcnJlc3BvbmRpbmcgImdyb3VuZCB0cnV0aCIgYWN0dWFsIHZhbHVlIGlzbid0IGEgdmVyeSBlZmZpY2llbnQgd2F5IHRvIGRldGVybWluZSBob3cgd2VsbCB0aGUgbW9kZWwgaXMgcHJlZGljdGluZy4gRm9ydHVuYXRlbHksIFRpZHltb2RlbHMgaGFzIGEgZmV3IG1vcmUgdHJpY2tzIHVwIGl0cyBzbGVldmU6IFtgeWFyZHN0aWNrYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvKSAtIGEgcGFja2FnZSB1c2VkIHRvIG1lYXN1cmUgdGhlIGVmZmVjdGl2ZW5lc3Mgb2YgbW9kZWxzIHVzaW5nIHBlcmZvcm1hbmNlIG1ldHJpY3MuCgpPbmUgcGVyZm9ybWFuY2UgbWV0cmljIGFzc29jaWF0ZWQgd2l0aCBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyBpcyB0aGUgW2Bjb25mdXNpb24gbWF0cml4YF0oaHR0cHM6Ly93aWtpcGVkaWEub3JnL3dpa2kvQ29uZnVzaW9uX21hdHJpeCkuIEEgY29uZnVzaW9uIG1hdHJpeCBkZXNjcmliZXMgaG93IHdlbGwgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBwZXJmb3Jtcy4gQSBjb25mdXNpb24gbWF0cml4IHRhYnVsYXRlcyBob3cgbWFueSBleGFtcGxlcyBpbiBlYWNoIGNsYXNzIHdlcmUgY29ycmVjdGx5IGNsYXNzaWZpZWQgYnkgYSBtb2RlbC4gSW4gb3VyIGNhc2UsIGl0IHdpbGwgc2hvdyB5b3UgaG93IG1hbnkgb3JhbmdlIHB1bXBraW5zIHdlcmUgY2xhc3NpZmllZCBhcyBvcmFuZ2UgYW5kIGhvdyBtYW55IHdoaXRlIHB1bXBraW5zIHdlcmUgY2xhc3NpZmllZCBhcyB3aGl0ZTsgdGhlIGNvbmZ1c2lvbiBtYXRyaXggYWxzbyBzaG93cyB5b3UgaG93IG1hbnkgd2VyZSBjbGFzc2lmaWVkIGludG8gdGhlICoqd3JvbmcqKiBjYXRlZ29yaWVzLgoKVGhlIFsqKmBjb25mX21hdCgpYCoqXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3lhcmRzdGljay9yZWZlcmVuY2UvY29uZl9tYXQuaHRtbCkgZnVuY3Rpb24gZnJvbSB5YXJkc3RpY2sgY2FsY3VsYXRlcyB0aGlzIGNyb3NzLXRhYnVsYXRpb24gb2Ygb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBjbGFzc2VzLgoKYGBge3IgY29uZl9tYXR9CiMgQ29uZnVzaW9uIG1hdHJpeCBmb3IgcHJlZGljdGlvbiByZXN1bHRzCmNvbmZfbWF0KGRhdGEgPSByZXN1bHRzLCB0cnV0aCA9IGNvbG9yLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKCkxldCdzIGludGVycHJldCB0aGUgY29uZnVzaW9uIG1hdHJpeC4gT3VyIG1vZGVsIGlzIGFza2VkIHRvIGNsYXNzaWZ5IHB1bXBraW5zIGJldHdlZW4gdHdvIGJpbmFyeSBjYXRlZ29yaWVzLCBjYXRlZ29yeSBgd2hpdGVgIGFuZCBjYXRlZ29yeSBgbm90LXdoaXRlYAoKLSAgIElmIHlvdXIgbW9kZWwgcHJlZGljdHMgYSBwdW1wa2luIGFzIHdoaXRlIGFuZCBpdCBiZWxvbmdzIHRvIGNhdGVnb3J5ICd3aGl0ZScgaW4gcmVhbGl0eSB3ZSBjYWxsIGl0IGEgYHRydWUgcG9zaXRpdmVgLCBzaG93biBieSB0aGUgdG9wIGxlZnQgbnVtYmVyLgoKLSAgIElmIHlvdXIgbW9kZWwgcHJlZGljdHMgYSBwdW1wa2luIGFzIG5vdCB3aGl0ZSBhbmQgaXQgYmVsb25ncyB0byBjYXRlZ29yeSAnd2hpdGUnIGluIHJlYWxpdHkgd2UgY2FsbCBpdCBhIGBmYWxzZSBuZWdhdGl2ZWAsIHNob3duIGJ5IHRoZSBib3R0b20gbGVmdCBudW1iZXIuCgotICAgSWYgeW91ciBtb2RlbCBwcmVkaWN0cyBhIHB1bXBraW4gYXMgd2hpdGUgYW5kIGl0IGJlbG9uZ3MgdG8gY2F0ZWdvcnkgJ25vdC13aGl0ZScgaW4gcmVhbGl0eSB3ZSBjYWxsIGl0IGEgYGZhbHNlIHBvc2l0aXZlYCwgc2hvd24gYnkgdGhlIHRvcCByaWdodCBudW1iZXIuCgotICAgSWYgeW91ciBtb2RlbCBwcmVkaWN0cyBhIHB1bXBraW4gYXMgbm90IHdoaXRlIGFuZCBpdCBiZWxvbmdzIHRvIGNhdGVnb3J5ICdub3Qtd2hpdGUnIGluIHJlYWxpdHkgd2UgY2FsbCBpdCBhIGB0cnVlIG5lZ2F0aXZlYCwgc2hvd24gYnkgdGhlIGJvdHRvbSByaWdodCBudW1iZXIuCgp8IFRydXRoIHwKfDotLS0tLTp8CgoKfCAgICAgICAgICAgICAgIHwgICAgICAgIHwgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tfC0tLS0tLS18CnwgKipQcmVkaWN0ZWQqKiB8IFdISVRFIHwgT1JBTkdFIHwKfCBXSElURSAgICAgICAgfCBUUCAgICAgfCBGUCAgICB8CnwgT1JBTkdFICAgICAgICAgfCBGTiAgICAgfCBUTiAgICB8CgpBcyB5b3UgbWlnaHQgaGF2ZSBndWVzc2VkIGl0J3MgcHJlZmVyYWJsZSB0byBoYXZlIGEgbGFyZ2VyIG51bWJlciBvZiB0cnVlIHBvc2l0aXZlcyBhbmQgdHJ1ZSBuZWdhdGl2ZXMgYW5kIGEgbG93ZXIgbnVtYmVyIG9mIGZhbHNlIHBvc2l0aXZlcyBhbmQgZmFsc2UgbmVnYXRpdmVzLCB3aGljaCBpbXBsaWVzIHRoYXQgdGhlIG1vZGVsIHBlcmZvcm1zIGJldHRlci4KClRoZSBjb25mdXNpb24gbWF0cml4IGlzIGhlbHBmdWwgc2luY2UgaXQgZ2l2ZXMgcmlzZSB0byBvdGhlciBtZXRyaWNzIHRoYXQgY2FuIGhlbHAgdXMgYmV0dGVyIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBvZiBhIGNsYXNzaWZpY2F0aW9uIG1vZGVsLiBMZXQncyBnbyB0aHJvdWdoIHNvbWUgb2YgdGhlbToKCvCfjpMgUHJlY2lzaW9uOiBgVFAvKFRQICsgRlApYCBkZWZpbmVkIGFzIHRoZSBwcm9wb3J0aW9uIG9mIHByZWRpY3RlZCBwb3NpdGl2ZXMgdGhhdCBhcmUgYWN0dWFsbHkgcG9zaXRpdmUuIEFsc28gY2FsbGVkIFtwb3NpdGl2ZSBwcmVkaWN0aXZlIHZhbHVlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qb3NpdGl2ZV9wcmVkaWN0aXZlX3ZhbHVlICJQb3NpdGl2ZSBwcmVkaWN0aXZlIHZhbHVlIikKCvCfjpMgUmVjYWxsOiBgVFAvKFRQICsgRk4pYCBkZWZpbmVkIGFzIHRoZSBwcm9wb3J0aW9uIG9mIHBvc2l0aXZlIHJlc3VsdHMgb3V0IG9mIHRoZSBudW1iZXIgb2Ygc2FtcGxlcyB3aGljaCB3ZXJlIGFjdHVhbGx5IHBvc2l0aXZlLiBBbHNvIGtub3duIGFzIGBzZW5zaXRpdml0eWAuCgrwn46TIFNwZWNpZmljaXR5OiBgVE4vKFROICsgRlApYCBkZWZpbmVkIGFzIHRoZSBwcm9wb3J0aW9uIG9mIG5lZ2F0aXZlIHJlc3VsdHMgb3V0IG9mIHRoZSBudW1iZXIgb2Ygc2FtcGxlcyB3aGljaCB3ZXJlIGFjdHVhbGx5IG5lZ2F0aXZlLgoK8J+OkyBBY2N1cmFjeTogYFRQICsgVE4vKFRQICsgVE4gKyBGUCArIEZOKWAgVGhlIHBlcmNlbnRhZ2Ugb2YgbGFiZWxzIHByZWRpY3RlZCBhY2N1cmF0ZWx5IGZvciBhIHNhbXBsZS4KCvCfjpMgRiBNZWFzdXJlOiBBIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgdGhlIHByZWNpc2lvbiBhbmQgcmVjYWxsLCB3aXRoIGJlc3QgYmVpbmcgMSBhbmQgd29yc3QgYmVpbmcgMC4KCkxldCdzIGNhbGN1bGF0ZSB0aGVzZSBtZXRyaWNzIQoKYGBge3IgbWV0cmljX3NldH0KIyBDb21iaW5lIG1ldHJpYyBmdW5jdGlvbnMgYW5kIGNhbGN1bGF0ZSB0aGVtIGFsbCBhdCBvbmNlCmV2YWxfbWV0cmljcyA8LSBtZXRyaWNfc2V0KHBwdiwgcmVjYWxsLCBzcGVjLCBmX21lYXMsIGFjY3VyYWN5KQpldmFsX21ldHJpY3MoZGF0YSA9IHJlc3VsdHMsIHRydXRoID0gY29sb3IsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCmBgYAoKIyMgVmlzdWFsaXplIHRoZSBST0MgY3VydmUgb2YgdGhpcyBtb2RlbAoKTGV0J3MgZG8gb25lIG1vcmUgdmlzdWFsaXphdGlvbiB0byBzZWUgdGhlIHNvLWNhbGxlZCBbYFJPQyBjdXJ2ZWBdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1JlY2VpdmVyX29wZXJhdGluZ19jaGFyYWN0ZXJpc3RpYyk6CgpgYGB7ciByb2NfY3VydmV9CiMgTWFrZSBhIHJvY19jdXJ2ZQpyZXN1bHRzICU+JSAKICByb2NfY3VydmUoY29sb3IsIC5wcmVkX09SQU5HRSkgJT4lIAogIGF1dG9wbG90KCkKYGBgCgpST0MgY3VydmVzIGFyZSBvZnRlbiB1c2VkIHRvIGdldCBhIHZpZXcgb2YgdGhlIG91dHB1dCBvZiBhIGNsYXNzaWZpZXIgaW4gdGVybXMgb2YgaXRzIHRydWUgdnMuIGZhbHNlIHBvc2l0aXZlcy4gUk9DIGN1cnZlcyB0eXBpY2FsbHkgZmVhdHVyZSBgVHJ1ZSBQb3NpdGl2ZSBSYXRlYC9TZW5zaXRpdml0eSBvbiB0aGUgWSBheGlzLCBhbmQgYEZhbHNlIFBvc2l0aXZlIFJhdGVgLzEtU3BlY2lmaWNpdHkgb24gdGhlIFggYXhpcy4gVGh1cywgdGhlIHN0ZWVwbmVzcyBvZiB0aGUgY3VydmUgYW5kIHRoZSBzcGFjZSBiZXR3ZWVuIHRoZSBtaWRwb2ludCBsaW5lIGFuZCB0aGUgY3VydmUgbWF0dGVyOiB5b3Ugd2FudCBhIGN1cnZlIHRoYXQgcXVpY2tseSBoZWFkcyB1cCBhbmQgb3ZlciB0aGUgbGluZS4gSW4gb3VyIGNhc2UsIHRoZXJlIGFyZSBmYWxzZSBwb3NpdGl2ZXMgdG8gc3RhcnQgd2l0aCwgYW5kIHRoZW4gdGhlIGxpbmUgaGVhZHMgdXAgYW5kIG92ZXIgcHJvcGVybHkuCgpGaW5hbGx5LCBsZXQncyB1c2UgYHlhcmRzdGljazo6cm9jX2F1YygpYCB0byBjYWxjdWxhdGUgdGhlIGFjdHVhbCBBcmVhIFVuZGVyIHRoZSBDdXJ2ZS4gT25lIHdheSBvZiBpbnRlcnByZXRpbmcgQVVDIGlzIGFzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBtb2RlbCByYW5rcyBhIHJhbmRvbSBwb3NpdGl2ZSBleGFtcGxlIG1vcmUgaGlnaGx5IHRoYW4gYSByYW5kb20gbmVnYXRpdmUgZXhhbXBsZS4KCmBgYHtyIHJvY19hb2N9CiMgQ2FsY3VsYXRlIGFyZWEgdW5kZXIgY3VydmUKcmVzdWx0cyAlPiUgCiAgcm9jX2F1Yyhjb2xvciwgLnByZWRfT1JBTkdFKQpgYGAKClRoZSByZXN1bHQgaXMgYXJvdW5kIGAwLjk3NWAuIEdpdmVuIHRoYXQgdGhlIEFVQyByYW5nZXMgZnJvbSAwIHRvIDEsIHlvdSB3YW50IGEgYmlnIHNjb3JlLCBzaW5jZSBhIG1vZGVsIHRoYXQgaXMgMTAwJSBjb3JyZWN0IGluIGl0cyBwcmVkaWN0aW9ucyB3aWxsIGhhdmUgYW4gQVVDIG9mIDE7IGluIHRoaXMgY2FzZSwgdGhlIG1vZGVsIGlzICpwcmV0dHkgZ29vZCouCgpJbiBmdXR1cmUgbGVzc29ucyBvbiBjbGFzc2lmaWNhdGlvbnMsIHlvdSB3aWxsIGxlYXJuIGhvdyB0byBpbXByb3ZlIHlvdXIgbW9kZWwncyBzY29yZXMgKHN1Y2ggYXMgZGVhbGluZyB3aXRoIGltYmFsYW5jZWQgZGF0YSBpbiB0aGlzIGNhc2UpLgoKIyMg8J+agENoYWxsZW5nZQoKVGhlcmUncyBhIGxvdCBtb3JlIHRvIHVucGFjayByZWdhcmRpbmcgbG9naXN0aWMgcmVncmVzc2lvbiEgQnV0IHRoZSBiZXN0IHdheSB0byBsZWFybiBpcyB0byBleHBlcmltZW50LiBGaW5kIGEgZGF0YXNldCB0aGF0IGxlbmRzIGl0c2VsZiB0byB0aGlzIHR5cGUgb2YgYW5hbHlzaXMgYW5kIGJ1aWxkIGEgbW9kZWwgd2l0aCBpdC4gV2hhdCBkbyB5b3UgbGVhcm4/IHRpcDogdHJ5IFtLYWdnbGVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vc2VhcmNoP3E9bG9naXN0aWMrcmVncmVzc2lvbitkYXRhc2V0cykgZm9yIGludGVyZXN0aW5nIGRhdGFzZXRzLgoKIyMgUmV2aWV3ICYgU2VsZiBTdHVkeQoKUmVhZCB0aGUgZmlyc3QgZmV3IHBhZ2VzIG9mIFt0aGlzIHBhcGVyIGZyb20gU3RhbmZvcmRdKGh0dHBzOi8vd2ViLnN0YW5mb3JkLmVkdS9+anVyYWZza3kvc2xwMy81LnBkZikgb24gc29tZSBwcmFjdGljYWwgdXNlcyBmb3IgbG9naXN0aWMgcmVncmVzc2lvbi4gVGhpbmsgYWJvdXQgdGFza3MgdGhhdCBhcmUgYmV0dGVyIHN1aXRlZCBmb3Igb25lIG9yIHRoZSBvdGhlciB0eXBlIG9mIHJlZ3Jlc3Npb24gdGFza3MgdGhhdCB3ZSBoYXZlIHN0dWRpZWQgdXAgdG8gdGhpcyBwb2ludC4gV2hhdCB3b3VsZCB3b3JrIGJlc3Q/Cgo=