Build a logistic regression model - Lesson 4

Logistic vs. linear regression infographic
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
Infographic by Dasani Madipalli

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

1. Tidy the data

Now, the fun begins! Let’s start by importing the data, cleaning the data a bit, dropping rows containing missing values and selecting only some of the columns:

# 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)

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:

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…

Wow! Seems that all our columns are all of type character, further alluding that they are all categorical.

Let’s confirm that we will actually be doing a binary classification problem:

# Subset distinct observations in outcome column
pumpkins_select %>% 
  distinct(color)

🥳🥳 That went down well!

2. Explore the data

The goal of data 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.

Given our the data types of our columns, we can encode them and be on our way to making some visualizations. This simply involves translating a column with categorical values for example our columns of type char, into one or more numeric columns that take the place of the original. - Something we did in our last lesson.

For feature encoding there are two main types of encoders:

  1. 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.

  2. 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)

Now, let’s make a categorical plot showing the distribution of the predictors with respect to the outcome color!

# 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()

Amazing🤩! For some of the features, there’s a noticeable difference in the distribution for each color label. For instance, it seems the white pumpkins can be found in smaller packages and in some particular varieties of pumpkins. The item_size category also seems to make a difference in the color distribution. These features may help predict the color of a pumpkin.

Analysing relationships between features and label

# 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)) 

Let’s now focus on a specific relationship: Item Size and Color!

Use a swarm plot

Color is a binary category (Orange or Not), it’s called categorical data. There are other various ways of visualizing categorical data.

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.

3. Build your model

Let’s begin by splitting the data into training and test sets. The training set is used to train a classifier so that it finds a statistical relationship between the features and the label value.

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

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.

Truth
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

For a start, this is not a bad model; its precision, recall, F measure and accuracy are in the 90% range so ideally you could use it to predict the color of a pumpkin given a set of variables. It also seems that our model was not really able to identify the white pumpkins 🧐. Could you guess why? One reason could be because of the high prevalence of ORANGE pumpkins in our training set making our model more inclined to predict the majority class.

Let’s do one more visualization to see the so-called ROC score:

# 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.947. 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).

But for now, congratulations 🎉🎉🎉! You’ve completed these regression lessons!

You R awesome!

Artwork by @allison_horst
Artwork by @allison_horst
LS0tCnRpdGxlOiAnQnVpbGQgYSByZWdyZXNzaW9uIG1vZGVsOiBsb2dpc3RpYyByZWdyZXNzaW9uJwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHllcwotLS0KCiMjIEJ1aWxkIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCAtIExlc3NvbiA0CgohW0xvZ2lzdGljIHZzLiBsaW5lYXIgcmVncmVzc2lvbiBpbmZvZ3JhcGhpY10oaHR0cHM6Ly9naXRodWIuY29tL21pY3Jvc29mdC9NTC1Gb3ItQmVnaW5uZXJzL2Jsb2IvbWFpbi8yLVJlZ3Jlc3Npb24vNC1Mb2dpc3RpYy9pbWFnZXMvbGluZWFyLXZzLWxvZ2lzdGljLnBuZykKCiMjIyMgKipbUHJlLWxlY3R1cmUgcXVpel0oaHR0cHM6Ly9ncmF5LXNhbmQtMDdhMTBmNDAzLjEuYXp1cmVzdGF0aWNhcHBzLm5ldC9xdWl6LzE1LykqKgoKIyMjIyAgSW50cm9kdWN0aW9uCgpJbiB0aGlzIGZpbmFsIGxlc3NvbiBvbiBSZWdyZXNzaW9uLCBvbmUgb2YgdGhlIGJhc2ljICpjbGFzc2ljKiBNTCB0ZWNobmlxdWVzLCB3ZSB3aWxsIHRha2UgYSBsb29rIGF0IExvZ2lzdGljIFJlZ3Jlc3Npb24uIFlvdSB3b3VsZCB1c2UgdGhpcyB0ZWNobmlxdWUgdG8gZGlzY292ZXIgcGF0dGVybnMgdG8gcHJlZGljdCBgYmluYXJ5YCBgY2F0ZWdvcmllc2AuIElzIHRoaXMgY2FuZHkgY2hvY29sYXRlIG9yIG5vdD8gSXMgdGhpcyBkaXNlYXNlIGNvbnRhZ2lvdXMgb3Igbm90PyBXaWxsIHRoaXMgY3VzdG9tZXIgY2hvb3NlIHRoaXMgcHJvZHVjdCBvciBub3Q/CgpJbiB0aGlzIGxlc3NvbiwgeW91IHdpbGwgbGVhcm46CgotICAgVGVjaG5pcXVlcyBmb3IgbG9naXN0aWMgcmVncmVzc2lvbgoK4pyFIERlZXBlbiB5b3VyIHVuZGVyc3RhbmRpbmcgb2Ygd29ya2luZyB3aXRoIHRoaXMgdHlwZSBvZiByZWdyZXNzaW9uIGluIHRoaXMgW0xlYXJuIG1vZHVsZV0oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vbGVhcm4vbW9kdWxlcy90cmFpbi1ldmFsdWF0ZS1jbGFzc2lmaWNhdGlvbi1tb2RlbHM/V1QubWNfaWQ9YWNhZGVtaWMtNzc5NTItbGVlc3RvdHQpCgojIyMjICoqUHJlcmVxdWlzaXRlKioKCkhhdmluZyB3b3JrZWQgd2l0aCB0aGUgcHVtcGtpbiBkYXRhLCB3ZSBhcmUgbm93IGZhbWlsaWFyIGVub3VnaCB3aXRoIGl0IHRvIHJlYWxpemUgdGhhdCB0aGVyZSdzIG9uZSBiaW5hcnkgY2F0ZWdvcnkgdGhhdCB3ZSBjYW4gd29yayB3aXRoOiBgQ29sb3JgLgoKTGV0J3MgYnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgdGhhdCwgZ2l2ZW4gc29tZSB2YXJpYWJsZXMsICp3aGF0IGNvbG9yIGEgZ2l2ZW4gcHVtcGtpbiBpcyBsaWtlbHkgdG8gYmUqIChvcmFuZ2Ug8J+OgyBvciB3aGl0ZSDwn5G7KS4KCj4gV2h5IGFyZSB3ZSB0YWxraW5nIGFib3V0IGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBpbiBhIGxlc3NvbiBncm91cGluZyBhYm91dCByZWdyZXNzaW9uPyBPbmx5IGZvciBsaW5ndWlzdGljIGNvbnZlbmllbmNlLCBhcyBsb2dpc3RpYyByZWdyZXNzaW9uIGlzIFtyZWFsbHkgYSBjbGFzc2lmaWNhdGlvbiBtZXRob2RdKGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9saW5lYXJfbW9kZWwuaHRtbCNsb2dpc3RpYy1yZWdyZXNzaW9uKSwgYWxiZWl0IGEgbGluZWFyLWJhc2VkIG9uZS4gTGVhcm4gYWJvdXQgb3RoZXIgd2F5cyB0byBjbGFzc2lmeSBkYXRhIGluIHRoZSBuZXh0IGxlc3NvbiBncm91cC4KCkZvciB0aGlzIGxlc3Nvbiwgd2UnbGwgcmVxdWlyZSB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOgoKLSAgIGB0aWR5dmVyc2VgOiBUaGUgW3RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pIGlzIGEgW2NvbGxlY3Rpb24gb2YgUiBwYWNrYWdlc10oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy9wYWNrYWdlcykgZGVzaWduZWQgdG8gbWFrZXMgZGF0YSBzY2llbmNlIGZhc3RlciwgZWFzaWVyIGFuZCBtb3JlIGZ1biEKCi0gICBgdGlkeW1vZGVsc2A6IFRoZSBbdGlkeW1vZGVsc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvKSBmcmFtZXdvcmsgaXMgYSBbY29sbGVjdGlvbiBvZiBwYWNrYWdlc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvcGFja2FnZXMvKSBmb3IgbW9kZWxpbmcgYW5kIG1hY2hpbmUgbGVhcm5pbmcuCgotICAgYGphbml0b3JgOiBUaGUgW2phbml0b3IgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3NmaXJrZS9qYW5pdG9yKSBwcm92aWRlcyBzaW1wbGUgbGl0dGxlIHRvb2xzIGZvciBleGFtaW5pbmcgYW5kIGNsZWFuaW5nIGRpcnR5IGRhdGEuCgotICAgYGdnYmVlc3dhcm1gOiBUaGUgW2dnYmVlc3dhcm0gcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2VjbGFya2UvZ2diZWVzd2FybSkgcHJvdmlkZXMgbWV0aG9kcyB0byBjcmVhdGUgYmVlc3dhcm0tc3R5bGUgcGxvdHMgdXNpbmcgZ2dwbG90Mi4KCllvdSBjYW4gaGF2ZSB0aGVtIGluc3RhbGxlZCBhczoKCmBpbnN0YWxsLnBhY2thZ2VzKGMoInRpZHl2ZXJzZSIsICJ0aWR5bW9kZWxzIiwgImphbml0b3IiLCAiZ2diZWVzd2FybSIpKWAKCkFsdGVybmF0ZWx5LCB0aGUgc2NyaXB0IGJlbG93IGNoZWNrcyB3aGV0aGVyIHlvdSBoYXZlIHRoZSBwYWNrYWdlcyByZXF1aXJlZCB0byBjb21wbGV0ZSB0aGlzIG1vZHVsZSBhbmQgaW5zdGFsbHMgdGhlbSBmb3IgeW91IGluIGNhc2UgdGhleSBhcmUgbWlzc2luZy4KCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0Kc3VwcHJlc3NXYXJuaW5ncyhpZiAoIXJlcXVpcmUoInBhY21hbiIpKWluc3RhbGwucGFja2FnZXMoInBhY21hbiIpKQoKcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLCB0aWR5bW9kZWxzLCBqYW5pdG9yLCBnZ2JlZXN3YXJtKQpgYGAKCiMjICoqRGVmaW5lIHRoZSBxdWVzdGlvbioqCgpGb3Igb3VyIHB1cnBvc2VzLCB3ZSB3aWxsIGV4cHJlc3MgdGhpcyBhcyBhIGJpbmFyeTogJ1doaXRlJyBvciAnTm90IFdoaXRlJy4gVGhlcmUgaXMgYWxzbyBhICdzdHJpcGVkJyBjYXRlZ29yeSBpbiBvdXIgZGF0YXNldCBidXQgdGhlcmUgYXJlIGZldyBpbnN0YW5jZXMgb2YgaXQsIHNvIHdlIHdpbGwgbm90IHVzZSBpdC4gSXQgZGlzYXBwZWFycyBvbmNlIHdlIHJlbW92ZSBudWxsIHZhbHVlcyBmcm9tIHRoZSBkYXRhc2V0LCBhbnl3YXkuCgo+IPCfjoMgRnVuIGZhY3QsIHdlIHNvbWV0aW1lcyBjYWxsIHdoaXRlIHB1bXBraW5zICdnaG9zdCcgcHVtcGtpbnMuIFRoZXkgYXJlbid0IHZlcnkgZWFzeSB0byBjYXJ2ZSwgc28gdGhleSBhcmVuJ3QgYXMgcG9wdWxhciBhcyB0aGUgb3JhbmdlIG9uZXMgYnV0IHRoZXkgYXJlIGNvb2wgbG9va2luZyEgU28gd2UgY291bGQgYWxzbyByZWZvcm11bGF0ZSBvdXIgcXVlc3Rpb24gYXM6ICdHaG9zdCcgb3IgJ05vdCBHaG9zdCcuIPCfkbsKCiMjICoqQWJvdXQgbG9naXN0aWMgcmVncmVzc2lvbioqCgpMb2dpc3RpYyByZWdyZXNzaW9uIGRpZmZlcnMgZnJvbSBsaW5lYXIgcmVncmVzc2lvbiwgd2hpY2ggeW91IGxlYXJuZWQgYWJvdXQgcHJldmlvdXNseSwgaW4gYSBmZXcgaW1wb3J0YW50IHdheXMuCgojIyMjICoqQmluYXJ5IGNsYXNzaWZpY2F0aW9uKioKCkxvZ2lzdGljIHJlZ3Jlc3Npb24gZG9lcyBub3Qgb2ZmZXIgdGhlIHNhbWUgZmVhdHVyZXMgYXMgbGluZWFyIHJlZ3Jlc3Npb24uIFRoZSBmb3JtZXIgb2ZmZXJzIGEgcHJlZGljdGlvbiBhYm91dCBhIGBiaW5hcnkgY2F0ZWdvcnlgICgib3JhbmdlIG9yIG5vdCBvcmFuZ2UiKSB3aGVyZWFzIHRoZSBsYXR0ZXIgaXMgY2FwYWJsZSBvZiBwcmVkaWN0aW5nIGBjb250aW51YWwgdmFsdWVzYCwgZm9yIGV4YW1wbGUgZ2l2ZW4gdGhlIG9yaWdpbiBvZiBhIHB1bXBraW4gYW5kIHRoZSB0aW1lIG9mIGhhcnZlc3QsICpob3cgbXVjaCBpdHMgcHJpY2Ugd2lsbCByaXNlKi4KCiFbSW5mb2dyYXBoaWMgYnkgRGFzYW5pIE1hZGlwYWxsaV0oLi4vLi4vaW1hZ2VzL3B1bXBraW4tY2xhc3NpZmllci5wbmcpe3dpZHRoPSI2MDAifQoKIyMjIyAqKlZhcmlhYmxlcyBETyBOT1QgaGF2ZSB0byBjb3JyZWxhdGUqKgoKUmVtZW1iZXIgaG93IGxpbmVhciByZWdyZXNzaW9uIHdvcmtlZCBiZXR0ZXIgd2l0aCBtb3JlIGNvcnJlbGF0ZWQgdmFyaWFibGVzPyBMb2dpc3RpYyByZWdyZXNzaW9uIGlzIHRoZSBvcHBvc2l0ZSAtIHRoZSB2YXJpYWJsZXMgZG9uJ3QgaGF2ZSB0byBhbGlnbi4gVGhhdCB3b3JrcyBmb3IgdGhpcyBkYXRhIHdoaWNoIGhhcyBzb21ld2hhdCB3ZWFrIGNvcnJlbGF0aW9ucy4KCiMjIyMgKipZb3UgbmVlZCBhIGxvdCBvZiBjbGVhbiBkYXRhKioKCkxvZ2lzdGljIHJlZ3Jlc3Npb24gd2lsbCBnaXZlIG1vcmUgYWNjdXJhdGUgcmVzdWx0cyBpZiB5b3UgdXNlIG1vcmUgZGF0YTsgb3VyIHNtYWxsIGRhdGFzZXQgaXMgbm90IG9wdGltYWwgZm9yIHRoaXMgdGFzaywgc28ga2VlcCB0aGF0IGluIG1pbmQuCgrinIUgVGhpbmsgYWJvdXQgdGhlIHR5cGVzIG9mIGRhdGEgdGhhdCB3b3VsZCBsZW5kIHRoZW1zZWx2ZXMgd2VsbCB0byBsb2dpc3RpYyByZWdyZXNzaW9uCgojIyAxLiBUaWR5IHRoZSBkYXRhCgpOb3csIHRoZSBmdW4gYmVnaW5zISBMZXQncyBzdGFydCBieSBpbXBvcnRpbmcgdGhlIGRhdGEsIGNsZWFuaW5nIHRoZSBkYXRhIGEgYml0LCBkcm9wcGluZyByb3dzIGNvbnRhaW5pbmcgbWlzc2luZyB2YWx1ZXMgYW5kIHNlbGVjdGluZyBvbmx5IHNvbWUgb2YgdGhlIGNvbHVtbnM6CgpgYGB7ciwgdGlkeXIsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojIExvYWQgdGhlIGNvcmUgdGlkeXZlcnNlIHBhY2thZ2VzCmxpYnJhcnkodGlkeXZlcnNlKQoKIyBJbXBvcnQgdGhlIGRhdGEgYW5kIGNsZWFuIGNvbHVtbiBuYW1lcwpwdW1wa2lucyA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvTUwtRm9yLUJlZ2lubmVycy9tYWluLzItUmVncmVzc2lvbi9kYXRhL1VTLXB1bXBraW5zLmNzdiIpICU+JSAKICBjbGVhbl9uYW1lcygpCgojIFNlbGVjdCBkZXNpcmVkIGNvbHVtbnMKcHVtcGtpbnNfc2VsZWN0IDwtIHB1bXBraW5zICU+JSAKICBzZWxlY3QoYyhjaXR5X25hbWUsIHBhY2thZ2UsIHZhcmlldHksIG9yaWdpbiwgaXRlbV9zaXplLCBjb2xvcikpIAoKIyBEcm9wIHJvd3MgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcyBhbmQgZW5jb2RlIGNvbG9yIGFzIGZhY3RvciAoY2F0ZWdvcnkpCnB1bXBraW5zX3NlbGVjdCA8LSBwdW1wa2luc19zZWxlY3QgJT4lIAogIGRyb3BfbmEoKSAlPiUgCiAgbXV0YXRlKGNvbG9yID0gZmFjdG9yKGNvbG9yKSkKCiMgVmlldyB0aGUgZmlyc3QgZmV3IHJvd3MKcHVtcGtpbnNfc2VsZWN0ICU+JSAKICBzbGljZV9oZWFkKG4gPSA1KQoKYGBgCgpTb21ldGltZXMsIHdlIG1heSB3YW50IHNvbWUgbGl0dGxlIG1vcmUgaW5mb3JtYXRpb24gb24gb3VyIGRhdGEuIFdlIGNhbiBoYXZlIGEgbG9vayBhdCB0aGUgYGRhdGFgLCBgaXRzIHN0cnVjdHVyZWAgYW5kIHRoZSBgZGF0YSB0eXBlYCBvZiBpdHMgZmVhdHVyZXMgYnkgdXNpbmcgdGhlIFsqZ2xpbXBzZSgpKl0oaHR0cHM6Ly9waWxsYXIuci1saWIub3JnL3JlZmVyZW5jZS9nbGltcHNlLmh0bWwpIGZ1bmN0aW9uIGFzIGJlbG93OgoKYGBge3IgZ2xpbXBzZX0KcHVtcGtpbnNfc2VsZWN0ICU+JSAKICBnbGltcHNlKCkKYGBgCgpXb3chIFNlZW1zIHRoYXQgYWxsIG91ciBjb2x1bW5zIGFyZSBhbGwgb2YgdHlwZSAqY2hhcmFjdGVyKiwgZnVydGhlciBhbGx1ZGluZyB0aGF0IHRoZXkgYXJlIGFsbCBjYXRlZ29yaWNhbC4KCkxldCdzIGNvbmZpcm0gdGhhdCB3ZSB3aWxsIGFjdHVhbGx5IGJlIGRvaW5nIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW06CgpgYGB7ciBkaXN0aW5jdCBjb2xvcn0KIyBTdWJzZXQgZGlzdGluY3Qgb2JzZXJ2YXRpb25zIGluIG91dGNvbWUgY29sdW1uCnB1bXBraW5zX3NlbGVjdCAlPiUgCiAgZGlzdGluY3QoY29sb3IpCgpgYGAKCvCfpbPwn6WzIFRoYXQgd2VudCBkb3duIHdlbGwhCgojIyAyLiBFeHBsb3JlIHRoZSBkYXRhCgpUaGUgZ29hbCBvZiBkYXRhIGV4cGxvcmF0aW9uIGlzIHRvIHRyeSB0byB1bmRlcnN0YW5kIHRoZSBgcmVsYXRpb25zaGlwc2AgYmV0d2VlbiBpdHMgYXR0cmlidXRlczsgaW4gcGFydGljdWxhciwgYW55IGFwcGFyZW50IGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlICpmZWF0dXJlcyogYW5kIHRoZSAqbGFiZWwqIHlvdXIgbW9kZWwgd2lsbCB0cnkgdG8gcHJlZGljdC4gT25lIHdheSBvZiBkb2luZyB0aGlzIGlzIGJ5IHVzaW5nIGRhdGEgdmlzdWFsaXphdGlvbi4KCkdpdmVuIG91ciB0aGUgZGF0YSB0eXBlcyBvZiBvdXIgY29sdW1ucywgd2UgY2FuIGBlbmNvZGVgIHRoZW0gYW5kIGJlIG9uIG91ciB3YXkgdG8gbWFraW5nIHNvbWUgdmlzdWFsaXphdGlvbnMuIFRoaXMgc2ltcGx5IGludm9sdmVzIGB0cmFuc2xhdGluZ2AgYSBjb2x1bW4gd2l0aCBgY2F0ZWdvcmljYWwgdmFsdWVzYCBmb3IgZXhhbXBsZSBvdXIgY29sdW1ucyBvZiB0eXBlICpjaGFyKiwgaW50byBvbmUgb3IgbW9yZSBgbnVtZXJpYyBjb2x1bW5zYCB0aGF0IHRha2UgdGhlIHBsYWNlIG9mIHRoZSBvcmlnaW5hbC4gLSBTb21ldGhpbmcgd2UgZGlkIGluIG91ciBbbGFzdCBsZXNzb25dKGh0dHBzOi8vZ2l0aHViLmNvbS9taWNyb3NvZnQvTUwtRm9yLUJlZ2lubmVycy9ibG9iL21haW4vMi1SZWdyZXNzaW9uLzMtTGluZWFyL3NvbHV0aW9uL2xlc3Nvbl8zLmh0bWwpLgoKRm9yIGZlYXR1cmUgZW5jb2RpbmcgdGhlcmUgYXJlIHR3byBtYWluIHR5cGVzIG9mIGVuY29kZXJzOgoKMS4gT3JkaW5hbCBlbmNvZGVyOiBpdCBzdWl0cyB3ZWxsIGZvciBvcmRpbmFsIHZhcmlhYmxlcywgd2hpY2ggYXJlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB3aGVyZSB0aGVpciBkYXRhIGZvbGxvd3MgYSBsb2dpY2FsIG9yZGVyaW5nLCBsaWtlIHRoZSBgaXRlbV9zaXplYCBjb2x1bW4gaW4gb3VyIGRhdGFzZXQuIEl0IGNyZWF0ZXMgYSBtYXBwaW5nIHN1Y2ggdGhhdCBlYWNoIGNhdGVnb3J5IGlzIHJlcHJlc2VudGVkIGJ5IGEgbnVtYmVyLCB3aGljaCBpcyB0aGUgb3JkZXIgb2YgdGhlIGNhdGVnb3J5IGluIHRoZSBjb2x1bW4uCgoyLiBDYXRlZ29yaWNhbCBlbmNvZGVyOiBpdCBzdWl0cyB3ZWxsIGZvciBub21pbmFsIHZhcmlhYmxlcywgd2hpY2ggYXJlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB3aGVyZSB0aGVpciBkYXRhIGRvZXMgbm90IGZvbGxvdyBhIGxvZ2ljYWwgb3JkZXJpbmcsIGxpa2UgYWxsIHRoZSBmZWF0dXJlcyBkaWZmZXJlbnQgZnJvbSBgaXRlbV9zaXplYCBpbiBvdXIgZGF0YXNldC4gSXQgaXMgYSBvbmUtaG90IGVuY29kaW5nLCB3aGljaCBtZWFucyB0aGF0IGVhY2ggY2F0ZWdvcnkgaXMgcmVwcmVzZW50ZWQgYnkgYSBiaW5hcnkgY29sdW1uOiB0aGUgZW5jb2RlZCB2YXJpYWJsZSBpcyBlcXVhbCB0byAxIGlmIHRoZSBwdW1wa2luIGJlbG9uZ3MgdG8gdGhhdCBWYXJpZXR5IGFuZCAwIG90aGVyd2lzZS4KClRpZHltb2RlbHMgcHJvdmlkZXMgeWV0IGFub3RoZXIgbmVhdCBwYWNrYWdlOiBbcmVjaXBlc10oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnLyktIGEgcGFja2FnZSBmb3IgcHJlcHJvY2Vzc2luZyBkYXRhLiBXZSdsbCBkZWZpbmUgYSBgcmVjaXBlYCB0aGF0IHNwZWNpZmllcyB0aGF0IGFsbCBwcmVkaWN0b3IgY29sdW1ucyBzaG91bGQgYmUgZW5jb2RlZCBpbnRvIGEgc2V0IG9mIGludGVnZXJzICwgYHByZXBgIGl0IHRvIGVzdGltYXRlcyB0aGUgcmVxdWlyZWQgcXVhbnRpdGllcyBhbmQgc3RhdGlzdGljcyBuZWVkZWQgYnkgYW55IG9wZXJhdGlvbnMgYW5kIGZpbmFsbHkgYGJha2VgIHRvIGFwcGx5IHRoZSBjb21wdXRhdGlvbnMgdG8gbmV3IGRhdGEuCgo+IE5vcm1hbGx5LCByZWNpcGVzIGlzIHVzdWFsbHkgdXNlZCBhcyBhIHByZXByb2Nlc3NvciBmb3IgbW9kZWxsaW5nIHdoZXJlIGl0IGRlZmluZXMgd2hhdCBzdGVwcyBzaG91bGQgYmUgYXBwbGllZCB0byBhIGRhdGEgc2V0IGluIG9yZGVyIHRvIGdldCBpdCByZWFkeSBmb3IgbW9kZWxsaW5nLiBJbiB0aGF0IGNhc2UgaXQgaXMgKipoaWdobHkgcmVjb21tZW5kKiogdGhhdCB5b3UgdXNlIGEgYHdvcmtmbG93KClgIGluc3RlYWQgb2YgbWFudWFsbHkgZXN0aW1hdGluZyBhIHJlY2lwZSB1c2luZyBwcmVwIGFuZCBiYWtlLiBXZSdsbCBzZWUgYWxsIHRoaXMgaW4ganVzdCBhIG1vbWVudC4KPgo+IEhvd2V2ZXIgZm9yIG5vdywgd2UgYXJlIHVzaW5nIHJlY2lwZXMgKyBwcmVwICsgYmFrZSB0byBzcGVjaWZ5IHdoYXQgc3RlcHMgc2hvdWxkIGJlIGFwcGxpZWQgdG8gYSBkYXRhIHNldCBpbiBvcmRlciB0byBnZXQgaXQgcmVhZHkgZm9yIGRhdGEgYW5hbHlzaXMgYW5kIHRoZW4gZXh0cmFjdCB0aGUgcHJlcHJvY2Vzc2VkIGRhdGEgd2l0aCB0aGUgc3RlcHMgYXBwbGllZC4KCmBgYHtyIHJlY2lwZV9wcmVwX2Jha2V9CiMgUHJlcHJvY2VzcyBhbmQgZXh0cmFjdCBkYXRhIHRvIGFsbG93IHNvbWUgZGF0YSBhbmFseXNpcwpiYWtlZF9wdW1wa2lucyA8LSByZWNpcGUoY29sb3IgfiAuLCBkYXRhID0gcHVtcGtpbnNfc2VsZWN0KSAlPiUKICAjIERlZmluZSBvcmRlcmluZyBmb3IgaXRlbV9zaXplIGNvbHVtbgogIHN0ZXBfbXV0YXRlKGl0ZW1fc2l6ZSA9IG9yZGVyZWQoaXRlbV9zaXplLCBsZXZlbHMgPSBjKCdzbWwnLCAnbWVkJywgJ21lZC1sZ2UnLCAnbGdlJywgJ3hsZ2UnLCAnamJvJywgJ2V4amJvJykpKSAlPiUKICAjIENvbnZlcnQgZmFjdG9ycyB0byBudW1iZXJzIHVzaW5nIHRoZSBvcmRlciBkZWZpbmVkIGFib3ZlIChPcmRpbmFsIGVuY29kaW5nKQogIHN0ZXBfaW50ZWdlcihpdGVtX3NpemUsIHplcm9fYmFzZWQgPSBGKSAlPiUKICAjIEVuY29kZSBhbGwgb3RoZXIgcHJlZGljdG9ycyB1c2luZyBvbmUgaG90IGVuY29kaW5nCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCksIG9uZV9ob3QgPSBUUlVFKSAlPiUKICBwcmVwKGRhdGEgPSBwdW1wa2luX3NlbGVjdCkgJT4lCiAgYmFrZShuZXdfZGF0YSA9IE5VTEwpCgojIERpc3BsYXkgdGhlIGZpcnN0IGZldyByb3dzIG9mIHByZXByb2Nlc3NlZCBkYXRhCmJha2VkX3B1bXBraW5zICU+JSAKICBzbGljZV9oZWFkKG4gPSA1KQpgYGAKCk5vdywgbGV0J3MgbWFrZSBhIGNhdGVnb3JpY2FsIHBsb3Qgc2hvd2luZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBwcmVkaWN0b3JzIHdpdGggcmVzcGVjdCB0byB0aGUgb3V0Y29tZSBjb2xvciEKCmBgYHtyIGNhdCBwbG90IHB1bXBraW5zLWNvbG9ycy12YXJpZXR5fQojIFNwZWNpZnkgY29sb3JzIGZvciBlYWNoIHZhbHVlIG9mIHRoZSBodWUgdmFyaWFibGUKcGFsZXR0ZSA8LSBjKE9SQU5HRSA9ICJvcmFuZ2UiLCBXSElURSA9ICJ3aGVhdCIpCgojIENyZWF0ZSB0aGUgYmFyIHBsb3QKZ2dwbG90KHB1bXBraW5zX3NlbGVjdCwgYWVzKHkgPSB2YXJpZXR5LCBmaWxsID0gY29sb3IpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZSkgKwogIGxhYnMoeSA9ICJWYXJpZXR5IiwgZmlsbCA9ICJDb2xvciIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpBbWF6aW5n8J+kqSEgRm9yIHNvbWUgb2YgdGhlIGZlYXR1cmVzLCB0aGVyZSdzIGEgbm90aWNlYWJsZSBkaWZmZXJlbmNlIGluIHRoZSBkaXN0cmlidXRpb24gZm9yIGVhY2ggY29sb3IgbGFiZWwuIEZvciBpbnN0YW5jZSwgaXQgc2VlbXMgdGhlIHdoaXRlIHB1bXBraW5zIGNhbiBiZSBmb3VuZCBpbiBzbWFsbGVyIHBhY2thZ2VzIGFuZCBpbiBzb21lIHBhcnRpY3VsYXIgdmFyaWV0aWVzIG9mIHB1bXBraW5zLiBUaGUgKml0ZW1fc2l6ZSogY2F0ZWdvcnkgYWxzbyBzZWVtcyB0byBtYWtlIGEgZGlmZmVyZW5jZSBpbiB0aGUgY29sb3IgZGlzdHJpYnV0aW9uLiBUaGVzZSBmZWF0dXJlcyBtYXkgaGVscCBwcmVkaWN0IHRoZSBjb2xvciBvZiBhIHB1bXBraW4uCgojIyMgKipBbmFseXNpbmcgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGZlYXR1cmVzIGFuZCBsYWJlbCoqCgpgYGB7cn0KCiMgRGVmaW5lIHRoZSBjb2xvciBwYWxldHRlCnBhbGV0dGUgPC0gYyhPUkFOR0UgPSAib3JhbmdlIiwgV0hJVEUgPSAid2hlYXQiKQoKIyBXZSBuZWVkIHRoZSBlbmNvZGVkIEl0ZW0gU2l6ZSBjb2x1bW4gdG8gdXNlIGl0IGFzIHRoZSB4LWF4aXMgdmFsdWVzIGluIHRoZSBwbG90CnB1bXBraW5zX3NlbGVjdF9wbG90PC1wdW1wa2luc19zZWxlY3QKcHVtcGtpbnNfc2VsZWN0X3Bsb3QkaXRlbV9zaXplIDwtIGJha2VkX3B1bXBraW5zJGl0ZW1fc2l6ZQoKIyBDcmVhdGUgdGhlIGdyb3VwZWQgYm94IHBsb3QKZ2dwbG90KHB1bXBraW5zX3NlbGVjdF9wbG90LCBhZXMoeCA9IGBpdGVtX3NpemVgLCB5ID0gY29sb3IsIGZpbGwgPSBjb2xvcikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfZ3JpZCh2YXJpZXR5IH4gLiwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlKSArCiAgbGFicyh4ID0gIkl0ZW0gU2l6ZSIsIHkgPSAiIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJDb2xvciIpKSArCiAgdGhlbWUocGFuZWwuc3BhY2luZyA9IHVuaXQoMC41LCAibGluZXMiKSkrCiAgdGhlbWUoc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA0LCBoanVzdCA9IDApKSAKCmBgYAoKTGV0J3Mgbm93IGZvY3VzIG9uIGEgc3BlY2lmaWMgcmVsYXRpb25zaGlwOiBJdGVtIFNpemUgYW5kIENvbG9yIQoKIyMjIyAqKlVzZSBhIHN3YXJtIHBsb3QqKgoKQ29sb3IgaXMgYSBiaW5hcnkgY2F0ZWdvcnkgKE9yYW5nZSBvciBOb3QpLCBpdCdzIGNhbGxlZCBgY2F0ZWdvcmljYWwgZGF0YWAuIFRoZXJlIGFyZSBvdGhlciB2YXJpb3VzIHdheXMgb2YgW3Zpc3VhbGl6aW5nIGNhdGVnb3JpY2FsIGRhdGFdKGh0dHBzOi8vc2VhYm9ybi5weWRhdGEub3JnL3R1dG9yaWFsL2NhdGVnb3JpY2FsLmh0bWw/aGlnaGxpZ2h0PWJhcikuCgpUcnkgYSBgc3dhcm0gcGxvdGAgdG8gc2hvdyB0aGUgZGlzdHJpYnV0aW9uIG9mIGNvbG9yIHdpdGggcmVzcGVjdCB0byB0aGUgaXRlbV9zaXplLgoKV2UnbGwgdXNlIHRoZSBbZ2diZWVzd2FybSBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vZWNsYXJrZS9nZ2JlZXN3YXJtKSB3aGljaCBwcm92aWRlcyBtZXRob2RzIHRvIGNyZWF0ZSBiZWVzd2FybS1zdHlsZSBwbG90cyB1c2luZyBnZ3Bsb3QyLiBCZWVzd2FybSBwbG90cyBhcmUgYSB3YXkgb2YgcGxvdHRpbmcgcG9pbnRzIHRoYXQgd291bGQgb3JkaW5hcmlseSBvdmVybGFwIHNvIHRoYXQgdGhleSBmYWxsIG5leHQgdG8gZWFjaCBvdGhlciBpbnN0ZWFkLgoKYGBge3IgYmVlX3N3YXJtIHBsb3R9CiMgQ3JlYXRlIGJlZXN3YXJtIHBsb3RzIG9mIGNvbG9yIGFuZCBpdGVtX3NpemUKYmFrZWRfcHVtcGtpbnMgJT4lIAogIG11dGF0ZShjb2xvciA9IGZhY3Rvcihjb2xvcikpICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gY29sb3IsIHkgPSBpdGVtX3NpemUsIGNvbG9yID0gY29sb3IpKSArCiAgZ2VvbV9xdWFzaXJhbmRvbSgpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIGRpcmVjdGlvbiA9IC0xKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKTm93IHRoYXQgd2UgaGF2ZSBhbiBpZGVhIG9mIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgYmluYXJ5IGNhdGVnb3JpZXMgb2YgY29sb3IgYW5kIHRoZSBsYXJnZXIgZ3JvdXAgb2Ygc2l6ZXMsIGxldCdzIGV4cGxvcmUgbG9naXN0aWMgcmVncmVzc2lvbiB0byBkZXRlcm1pbmUgYSBnaXZlbiBwdW1wa2luJ3MgbGlrZWx5IGNvbG9yLgoKIyMgMy4gQnVpbGQgeW91ciBtb2RlbAoKTGV0J3MgYmVnaW4gYnkgc3BsaXR0aW5nIHRoZSBkYXRhIGludG8gYHRyYWluaW5nYCBhbmQgYHRlc3RgIHNldHMuIFRoZSB0cmFpbmluZyBzZXQgaXMgdXNlZCB0byB0cmFpbiBhIGNsYXNzaWZpZXIgc28gdGhhdCBpdCBmaW5kcyBhIHN0YXRpc3RpY2FsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBmZWF0dXJlcyBhbmQgdGhlIGxhYmVsIHZhbHVlLgoKSXQgaXMgYmVzdCBwcmFjdGljZSB0byBob2xkIG91dCBzb21lIG9mIHlvdXIgZGF0YSBmb3IgKip0ZXN0aW5nKiogaW4gb3JkZXIgdG8gZ2V0IGEgYmV0dGVyIGVzdGltYXRlIG9mIGhvdyB5b3VyIG1vZGVscyB3aWxsIHBlcmZvcm0gb24gbmV3IGRhdGEgYnkgY29tcGFyaW5nIHRoZSBwcmVkaWN0ZWQgbGFiZWxzIHdpdGggdGhlIGFscmVhZHkga25vd24gbGFiZWxzIGluIHRoZSB0ZXN0IHNldC4gW3JzYW1wbGVdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy8pLCBhIHBhY2thZ2UgaW4gVGlkeW1vZGVscywgcHJvdmlkZXMgaW5mcmFzdHJ1Y3R1cmUgZm9yIGVmZmljaWVudCBkYXRhIHNwbGl0dGluZyBhbmQgcmVzYW1wbGluZzoKCmBgYHtyIHNwbGl0X2RhdGF9CiMgU3BsaXQgZGF0YSBpbnRvIDgwJSBmb3IgdHJhaW5pbmcgYW5kIDIwJSBmb3IgdGVzdGluZwpzZXQuc2VlZCgyMDU2KQpwdW1wa2luc19zcGxpdCA8LSBwdW1wa2luc19zZWxlY3QgJT4lIAogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuOCkKCiMgRXh0cmFjdCB0aGUgZGF0YSBpbiBlYWNoIHNwbGl0CnB1bXBraW5zX3RyYWluIDwtIHRyYWluaW5nKHB1bXBraW5zX3NwbGl0KQpwdW1wa2luc190ZXN0IDwtIHRlc3RpbmcocHVtcGtpbnNfc3BsaXQpCgojIFByaW50IG91dCB0aGUgZmlyc3QgNSByb3dzIG9mIHRoZSB0cmFpbmluZyBzZXQKcHVtcGtpbnNfdHJhaW4gJT4lIAogIHNsaWNlX2hlYWQobiA9IDUpCmBgYAoK8J+ZjCBXZSBhcmUgbm93IHJlYWR5IHRvIHRyYWluIGEgbW9kZWwgYnkgZml0dGluZyB0aGUgdHJhaW5pbmcgZmVhdHVyZXMgdG8gdGhlIHRyYWluaW5nIGxhYmVsIChjb2xvcikuCgpXZSdsbCBiZWdpbiBieSBjcmVhdGluZyBhIHJlY2lwZSB0aGF0IHNwZWNpZmllcyB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwcyB0aGF0IHNob3VsZCBiZSBjYXJyaWVkIG91dCBvbiBvdXIgZGF0YSB0byBnZXQgaXQgcmVhZHkgZm9yIG1vZGVsbGluZyBpLmU6IGVuY29kaW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbnRvIGEgc2V0IG9mIGludGVnZXJzLiBKdXN0IGxpa2UgYGJha2VkX3B1bXBraW5zYCwgd2UgY3JlYXRlIGEgYHB1bXBraW5zX3JlY2lwZWAgYnV0IGRvIG5vdCBgcHJlcGAgYW5kIGBiYWtlYCBzaW5jZSBpdCB3b3VsZCBiZSBidW5kbGVkIGludG8gYSB3b3JrZmxvdywgd2hpY2ggeW91IHdpbGwgc2VlIGluIGp1c3QgYSBmZXcgc3RlcHMgZnJvbSBub3cuIAoKVGhlcmUgYXJlIHF1aXRlIGEgbnVtYmVyIG9mIHdheXMgdG8gc3BlY2lmeSBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgaW4gVGlkeW1vZGVscy4gU2VlIGA/bG9naXN0aWNfcmVnKClgIEZvciBub3csIHdlJ2xsIHNwZWNpZnkgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHZpYSB0aGUgZGVmYXVsdCBgc3RhdHM6OmdsbSgpYCBlbmdpbmUuCgpgYGB7ciBsb2dfcmVnfQojIENyZWF0ZSBhIHJlY2lwZSB0aGF0IHNwZWNpZmllcyBwcmVwcm9jZXNzaW5nIHN0ZXBzIGZvciBtb2RlbGxpbmcKcHVtcGtpbnNfcmVjaXBlIDwtIHJlY2lwZShjb2xvciB+IC4sIGRhdGEgPSBwdW1wa2luc190cmFpbikgJT4lIAogIHN0ZXBfbXV0YXRlKGl0ZW1fc2l6ZSA9IG9yZGVyZWQoaXRlbV9zaXplLCBsZXZlbHMgPSBjKCdzbWwnLCAnbWVkJywgJ21lZC1sZ2UnLCAnbGdlJywgJ3hsZ2UnLCAnamJvJywgJ2V4amJvJykpKSAlPiUKICBzdGVwX2ludGVnZXIoaXRlbV9zaXplLCB6ZXJvX2Jhc2VkID0gRikgJT4lICAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpCgojIENyZWF0ZSBhIGxvZ2lzdGljIG1vZGVsIHNwZWNpZmljYXRpb24KbG9nX3JlZyA8LSBsb2dpc3RpY19yZWcoKSAlPiUgCiAgc2V0X2VuZ2luZSgiZ2xtIikgJT4lIAogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgYSByZWNpcGUgYW5kIGEgbW9kZWwgc3BlY2lmaWNhdGlvbiwgd2UgbmVlZCB0byBmaW5kIGEgd2F5IG9mIGJ1bmRsaW5nIHRoZW0gdG9nZXRoZXIgaW50byBhbiBvYmplY3QgdGhhdCB3aWxsIGZpcnN0IHByZXByb2Nlc3MgdGhlIGRhdGEgKHByZXArYmFrZSBiZWhpbmQgdGhlIHNjZW5lcyksIGZpdCB0aGUgbW9kZWwgb24gdGhlIHByZXByb2Nlc3NlZCBkYXRhIGFuZCBhbHNvIGFsbG93IGZvciBwb3RlbnRpYWwgcG9zdC1wcm9jZXNzaW5nIGFjdGl2aXRpZXMuCgpJbiBUaWR5bW9kZWxzLCB0aGlzIGNvbnZlbmllbnQgb2JqZWN0IGlzIGNhbGxlZCBhIFtgd29ya2Zsb3dgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy8pIGFuZCBjb252ZW5pZW50bHkgaG9sZHMgeW91ciBtb2RlbGluZyBjb21wb25lbnRzLgoKYGBge3Igd29ya2Zsb3d9CiMgQnVuZGxlIG1vZGVsbGluZyBjb21wb25lbnRzIGluIGEgd29ya2Zsb3cKbG9nX3JlZ193ZiA8LSB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKHB1bXBraW5zX3JlY2lwZSkgJT4lIAogIGFkZF9tb2RlbChsb2dfcmVnKQoKIyBQcmludCBvdXQgdGhlIHdvcmtmbG93CmxvZ19yZWdfd2YKCmBgYAoKQWZ0ZXIgYSB3b3JrZmxvdyBoYXMgYmVlbiAqc3BlY2lmaWVkKiwgYSBtb2RlbCBjYW4gYmUgYHRyYWluZWRgIHVzaW5nIHRoZSBbYGZpdCgpYF0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9wYXJzbmlwL3JlZmVyZW5jZS9maXQuaHRtbCkgZnVuY3Rpb24uIFRoZSB3b3JrZmxvdyB3aWxsIGVzdGltYXRlIGEgcmVjaXBlIGFuZCBwcmVwcm9jZXNzIHRoZSBkYXRhIGJlZm9yZSB0cmFpbmluZywgc28gd2Ugd29uJ3QgaGF2ZSB0byBtYW51YWxseSBkbyB0aGF0IHVzaW5nIHByZXAgYW5kIGJha2UuCgoKYGBge3IgdHJhaW59CiMgVHJhaW4gdGhlIG1vZGVsCndmX2ZpdCA8LSBsb2dfcmVnX3dmICU+JSAKICBmaXQoZGF0YSA9IHB1bXBraW5zX3RyYWluKQoKIyBQcmludCB0aGUgdHJhaW5lZCB3b3JrZmxvdwp3Zl9maXQKCgpgYGAKClRoZSBtb2RlbCBwcmludCBvdXQgc2hvd3MgdGhlIGNvZWZmaWNpZW50cyBsZWFybmVkIGR1cmluZyB0cmFpbmluZy4KCk5vdyB3ZSd2ZSB0cmFpbmVkIHRoZSBtb2RlbCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSwgd2UgY2FuIG1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YSB1c2luZyBbcGFyc25pcDo6cHJlZGljdCgpXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3ByZWRpY3QubW9kZWxfZml0Lmh0bWwpLiBMZXQncyBzdGFydCBieSB1c2luZyB0aGUgbW9kZWwgdG8gcHJlZGljdCBsYWJlbHMgZm9yIG91ciB0ZXN0IHNldCBhbmQgdGhlIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggbGFiZWwuIFdoZW4gdGhlIHByb2JhYmlsaXR5IGlzIG1vcmUgdGhhbiAwLjUsIHRoZSBwcmVkaWN0IGNsYXNzIGlzIGBXSElURWAgZWxzZSBgT1JBTkdFYC4KCmBgYHtyIHRlc3RfcHJlZH0KIyBNYWtlIHByZWRpY3Rpb25zIGZvciBjb2xvciBhbmQgY29ycmVzcG9uZGluZyBwcm9iYWJpbGl0aWVzCnJlc3VsdHMgPC0gcHVtcGtpbnNfdGVzdCAlPiUgc2VsZWN0KGNvbG9yKSAlPiUgCiAgYmluZF9jb2xzKHdmX2ZpdCAlPiUgCiAgICAgICAgICAgICAgcHJlZGljdChuZXdfZGF0YSA9IHB1bXBraW5zX3Rlc3QpKSAlPiUKICBiaW5kX2NvbHMod2ZfZml0ICU+JQogICAgICAgICAgICAgIHByZWRpY3QobmV3X2RhdGEgPSBwdW1wa2luc190ZXN0LCB0eXBlID0gInByb2IiKSkKCiMgQ29tcGFyZSBwcmVkaWN0aW9ucwpyZXN1bHRzICU+JSAKICBzbGljZV9oZWFkKG4gPSAxMCkKCmBgYAoKVmVyeSBuaWNlISBUaGlzIHByb3ZpZGVzIHNvbWUgbW9yZSBpbnNpZ2h0cyBpbnRvIGhvdyBsb2dpc3RpYyByZWdyZXNzaW9uIHdvcmtzLgoKQ29tcGFyaW5nIGVhY2ggcHJlZGljdGlvbiB3aXRoIGl0cyBjb3JyZXNwb25kaW5nICJncm91bmQgdHJ1dGgiIGFjdHVhbCB2YWx1ZSBpc24ndCBhIHZlcnkgZWZmaWNpZW50IHdheSB0byBkZXRlcm1pbmUgaG93IHdlbGwgdGhlIG1vZGVsIGlzIHByZWRpY3RpbmcuIEZvcnR1bmF0ZWx5LCBUaWR5bW9kZWxzIGhhcyBhIGZldyBtb3JlIHRyaWNrcyB1cCBpdHMgc2xlZXZlOiBbYHlhcmRzdGlja2BdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnLykgLSBhIHBhY2thZ2UgdXNlZCB0byBtZWFzdXJlIHRoZSBlZmZlY3RpdmVuZXNzIG9mIG1vZGVscyB1c2luZyBwZXJmb3JtYW5jZSBtZXRyaWNzLgoKT25lIHBlcmZvcm1hbmNlIG1ldHJpYyBhc3NvY2lhdGVkIHdpdGggY2xhc3NpZmljYXRpb24gcHJvYmxlbXMgaXMgdGhlIFtgY29uZnVzaW9uIG1hdHJpeGBdKGh0dHBzOi8vd2lraXBlZGlhLm9yZy93aWtpL0NvbmZ1c2lvbl9tYXRyaXgpLiBBIGNvbmZ1c2lvbiBtYXRyaXggZGVzY3JpYmVzIGhvdyB3ZWxsIGEgY2xhc3NpZmljYXRpb24gbW9kZWwgcGVyZm9ybXMuIEEgY29uZnVzaW9uIG1hdHJpeCB0YWJ1bGF0ZXMgaG93IG1hbnkgZXhhbXBsZXMgaW4gZWFjaCBjbGFzcyB3ZXJlIGNvcnJlY3RseSBjbGFzc2lmaWVkIGJ5IGEgbW9kZWwuIEluIG91ciBjYXNlLCBpdCB3aWxsIHNob3cgeW91IGhvdyBtYW55IG9yYW5nZSBwdW1wa2lucyB3ZXJlIGNsYXNzaWZpZWQgYXMgb3JhbmdlIGFuZCBob3cgbWFueSB3aGl0ZSBwdW1wa2lucyB3ZXJlIGNsYXNzaWZpZWQgYXMgd2hpdGU7IHRoZSBjb25mdXNpb24gbWF0cml4IGFsc28gc2hvd3MgeW91IGhvdyBtYW55IHdlcmUgY2xhc3NpZmllZCBpbnRvIHRoZSAqKndyb25nKiogY2F0ZWdvcmllcy4KClRoZSBbKipgY29uZl9tYXQoKWAqKl0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby95YXJkc3RpY2svcmVmZXJlbmNlL2NvbmZfbWF0Lmh0bWwpIGZ1bmN0aW9uIGZyb20geWFyZHN0aWNrIGNhbGN1bGF0ZXMgdGhpcyBjcm9zcy10YWJ1bGF0aW9uIG9mIG9ic2VydmVkIGFuZCBwcmVkaWN0ZWQgY2xhc3Nlcy4KCmBgYHtyIGNvbmZfbWF0fQojIENvbmZ1c2lvbiBtYXRyaXggZm9yIHByZWRpY3Rpb24gcmVzdWx0cwpjb25mX21hdChkYXRhID0gcmVzdWx0cywgdHJ1dGggPSBjb2xvciwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKYGBgCgpMZXQncyBpbnRlcnByZXQgdGhlIGNvbmZ1c2lvbiBtYXRyaXguIE91ciBtb2RlbCBpcyBhc2tlZCB0byBjbGFzc2lmeSBwdW1wa2lucyBiZXR3ZWVuIHR3byBiaW5hcnkgY2F0ZWdvcmllcywgY2F0ZWdvcnkgYHdoaXRlYCBhbmQgY2F0ZWdvcnkgYG5vdC13aGl0ZWAKCi0gICBJZiB5b3VyIG1vZGVsIHByZWRpY3RzIGEgcHVtcGtpbiBhcyB3aGl0ZSBhbmQgaXQgYmVsb25ncyB0byBjYXRlZ29yeSAnd2hpdGUnIGluIHJlYWxpdHkgd2UgY2FsbCBpdCBhIGB0cnVlIHBvc2l0aXZlYCwgc2hvd24gYnkgdGhlIHRvcCBsZWZ0IG51bWJlci4KCi0gICBJZiB5b3VyIG1vZGVsIHByZWRpY3RzIGEgcHVtcGtpbiBhcyBub3Qgd2hpdGUgYW5kIGl0IGJlbG9uZ3MgdG8gY2F0ZWdvcnkgJ3doaXRlJyBpbiByZWFsaXR5IHdlIGNhbGwgaXQgYSBgZmFsc2UgbmVnYXRpdmVgLCBzaG93biBieSB0aGUgYm90dG9tIGxlZnQgbnVtYmVyLgoKLSAgIElmIHlvdXIgbW9kZWwgcHJlZGljdHMgYSBwdW1wa2luIGFzIHdoaXRlIGFuZCBpdCBiZWxvbmdzIHRvIGNhdGVnb3J5ICdub3Qtd2hpdGUnIGluIHJlYWxpdHkgd2UgY2FsbCBpdCBhIGBmYWxzZSBwb3NpdGl2ZWAsIHNob3duIGJ5IHRoZSB0b3AgcmlnaHQgbnVtYmVyLgoKLSAgIElmIHlvdXIgbW9kZWwgcHJlZGljdHMgYSBwdW1wa2luIGFzIG5vdCB3aGl0ZSBhbmQgaXQgYmVsb25ncyB0byBjYXRlZ29yeSAnbm90LXdoaXRlJyBpbiByZWFsaXR5IHdlIGNhbGwgaXQgYSBgdHJ1ZSBuZWdhdGl2ZWAsIHNob3duIGJ5IHRoZSBib3R0b20gcmlnaHQgbnVtYmVyLgoKfCBUcnV0aCB8Cnw6LS0tLS06fAoKCnwgICAgICAgICAgICAgICB8ICAgICAgICB8ICAgICAgIHwKfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tfAp8ICoqUHJlZGljdGVkKiogfCBXSElURSB8IE9SQU5HRSB8CnwgV0hJVEUgICAgICAgIHwgVFAgICAgIHwgRlAgICAgfAp8IE9SQU5HRSAgICAgICAgIHwgRk4gICAgIHwgVE4gICAgfAoKQXMgeW91IG1pZ2h0IGhhdmUgZ3Vlc3NlZCBpdCdzIHByZWZlcmFibGUgdG8gaGF2ZSBhIGxhcmdlciBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZXMgYW5kIHRydWUgbmVnYXRpdmVzIGFuZCBhIGxvd2VyIG51bWJlciBvZiBmYWxzZSBwb3NpdGl2ZXMgYW5kIGZhbHNlIG5lZ2F0aXZlcywgd2hpY2ggaW1wbGllcyB0aGF0IHRoZSBtb2RlbCBwZXJmb3JtcyBiZXR0ZXIuCgpUaGUgY29uZnVzaW9uIG1hdHJpeCBpcyBoZWxwZnVsIHNpbmNlIGl0IGdpdmVzIHJpc2UgdG8gb3RoZXIgbWV0cmljcyB0aGF0IGNhbiBoZWxwIHVzIGJldHRlciBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbC4gTGV0J3MgZ28gdGhyb3VnaCBzb21lIG9mIHRoZW06Cgrwn46TIFByZWNpc2lvbjogYFRQLyhUUCArIEZQKWAgZGVmaW5lZCBhcyB0aGUgcHJvcG9ydGlvbiBvZiBwcmVkaWN0ZWQgcG9zaXRpdmVzIHRoYXQgYXJlIGFjdHVhbGx5IHBvc2l0aXZlLiBBbHNvIGNhbGxlZCBbcG9zaXRpdmUgcHJlZGljdGl2ZSB2YWx1ZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUG9zaXRpdmVfcHJlZGljdGl2ZV92YWx1ZSAiUG9zaXRpdmUgcHJlZGljdGl2ZSB2YWx1ZSIpCgrwn46TIFJlY2FsbDogYFRQLyhUUCArIEZOKWAgZGVmaW5lZCBhcyB0aGUgcHJvcG9ydGlvbiBvZiBwb3NpdGl2ZSByZXN1bHRzIG91dCBvZiB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgd2hpY2ggd2VyZSBhY3R1YWxseSBwb3NpdGl2ZS4gQWxzbyBrbm93biBhcyBgc2Vuc2l0aXZpdHlgLgoK8J+OkyBTcGVjaWZpY2l0eTogYFROLyhUTiArIEZQKWAgZGVmaW5lZCBhcyB0aGUgcHJvcG9ydGlvbiBvZiBuZWdhdGl2ZSByZXN1bHRzIG91dCBvZiB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgd2hpY2ggd2VyZSBhY3R1YWxseSBuZWdhdGl2ZS4KCvCfjpMgQWNjdXJhY3k6IGBUUCArIFROLyhUUCArIFROICsgRlAgKyBGTilgIFRoZSBwZXJjZW50YWdlIG9mIGxhYmVscyBwcmVkaWN0ZWQgYWNjdXJhdGVseSBmb3IgYSBzYW1wbGUuCgrwn46TIEYgTWVhc3VyZTogQSB3ZWlnaHRlZCBhdmVyYWdlIG9mIHRoZSBwcmVjaXNpb24gYW5kIHJlY2FsbCwgd2l0aCBiZXN0IGJlaW5nIDEgYW5kIHdvcnN0IGJlaW5nIDAuCgpMZXQncyBjYWxjdWxhdGUgdGhlc2UgbWV0cmljcyEKCmBgYHtyIG1ldHJpY19zZXR9CiMgQ29tYmluZSBtZXRyaWMgZnVuY3Rpb25zIGFuZCBjYWxjdWxhdGUgdGhlbSBhbGwgYXQgb25jZQpldmFsX21ldHJpY3MgPC0gbWV0cmljX3NldChwcHYsIHJlY2FsbCwgc3BlYywgZl9tZWFzLCBhY2N1cmFjeSkKZXZhbF9tZXRyaWNzKGRhdGEgPSByZXN1bHRzLCB0cnV0aCA9IGNvbG9yLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKCiMjIyMgKipWaXN1YWxpemUgdGhlIFJPQyBjdXJ2ZSBvZiB0aGlzIG1vZGVsKioKCkZvciBhIHN0YXJ0LCB0aGlzIGlzIG5vdCBhIGJhZCBtb2RlbDsgaXRzIHByZWNpc2lvbiwgcmVjYWxsLCBGIG1lYXN1cmUgYW5kIGFjY3VyYWN5IGFyZSBpbiB0aGUgOTAlIHJhbmdlIHNvIGlkZWFsbHkgeW91IGNvdWxkIHVzZSBpdCB0byBwcmVkaWN0IHRoZSBjb2xvciBvZiBhIHB1bXBraW4gZ2l2ZW4gYSBzZXQgb2YgdmFyaWFibGVzLiBJdCBhbHNvIHNlZW1zIHRoYXQgb3VyIG1vZGVsIHdhcyBub3QgcmVhbGx5IGFibGUgdG8gaWRlbnRpZnkgdGhlIHdoaXRlIHB1bXBraW5zIPCfp5AuIENvdWxkIHlvdSBndWVzcyB3aHk/IE9uZSByZWFzb24gY291bGQgYmUgYmVjYXVzZSBvZiB0aGUgaGlnaCBwcmV2YWxlbmNlIG9mIE9SQU5HRSBwdW1wa2lucyBpbiBvdXIgdHJhaW5pbmcgc2V0IG1ha2luZyBvdXIgbW9kZWwgbW9yZSBpbmNsaW5lZCB0byBwcmVkaWN0IHRoZSBtYWpvcml0eSBjbGFzcy4KCkxldCdzIGRvIG9uZSBtb3JlIHZpc3VhbGl6YXRpb24gdG8gc2VlIHRoZSBzby1jYWxsZWQgW2BST0Mgc2NvcmVgXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SZWNlaXZlcl9vcGVyYXRpbmdfY2hhcmFjdGVyaXN0aWMpOgoKYGBge3Igcm9jX2N1cnZlfQojIE1ha2UgYSByb2NfY3VydmUKcmVzdWx0cyAlPiUgCiAgcm9jX2N1cnZlKGNvbG9yLCAucHJlZF9PUkFOR0UpICU+JSAKICBhdXRvcGxvdCgpCgpgYGAKClJPQyBjdXJ2ZXMgYXJlIG9mdGVuIHVzZWQgdG8gZ2V0IGEgdmlldyBvZiB0aGUgb3V0cHV0IG9mIGEgY2xhc3NpZmllciBpbiB0ZXJtcyBvZiBpdHMgdHJ1ZSB2cy4gZmFsc2UgcG9zaXRpdmVzLiBST0MgY3VydmVzIHR5cGljYWxseSBmZWF0dXJlIGBUcnVlIFBvc2l0aXZlIFJhdGVgL1NlbnNpdGl2aXR5IG9uIHRoZSBZIGF4aXMsIGFuZCBgRmFsc2UgUG9zaXRpdmUgUmF0ZWAvMS1TcGVjaWZpY2l0eSBvbiB0aGUgWCBheGlzLiBUaHVzLCB0aGUgc3RlZXBuZXNzIG9mIHRoZSBjdXJ2ZSBhbmQgdGhlIHNwYWNlIGJldHdlZW4gdGhlIG1pZHBvaW50IGxpbmUgYW5kIHRoZSBjdXJ2ZSBtYXR0ZXI6IHlvdSB3YW50IGEgY3VydmUgdGhhdCBxdWlja2x5IGhlYWRzIHVwIGFuZCBvdmVyIHRoZSBsaW5lLiBJbiBvdXIgY2FzZSwgdGhlcmUgYXJlIGZhbHNlIHBvc2l0aXZlcyB0byBzdGFydCB3aXRoLCBhbmQgdGhlbiB0aGUgbGluZSBoZWFkcyB1cCBhbmQgb3ZlciBwcm9wZXJseS4KCkZpbmFsbHksIGxldCdzIHVzZSBgeWFyZHN0aWNrOjpyb2NfYXVjKClgIHRvIGNhbGN1bGF0ZSB0aGUgYWN0dWFsIEFyZWEgVW5kZXIgdGhlIEN1cnZlLiBPbmUgd2F5IG9mIGludGVycHJldGluZyBBVUMgaXMgYXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIG1vZGVsIHJhbmtzIGEgcmFuZG9tIHBvc2l0aXZlIGV4YW1wbGUgbW9yZSBoaWdobHkgdGhhbiBhIHJhbmRvbSBuZWdhdGl2ZSBleGFtcGxlLgoKYGBge3Igcm9jX2FvY30KIyBDYWxjdWxhdGUgYXJlYSB1bmRlciBjdXJ2ZQpyZXN1bHRzICU+JSAKICByb2NfYXVjKGNvbG9yLCAucHJlZF9PUkFOR0UpCgpgYGAKClRoZSByZXN1bHQgaXMgYXJvdW5kIGAwLjk0N2AuIEdpdmVuIHRoYXQgdGhlIEFVQyByYW5nZXMgZnJvbSAwIHRvIDEsIHlvdSB3YW50IGEgYmlnIHNjb3JlLCBzaW5jZSBhIG1vZGVsIHRoYXQgaXMgMTAwJSBjb3JyZWN0IGluIGl0cyBwcmVkaWN0aW9ucyB3aWxsIGhhdmUgYW4gQVVDIG9mIDE7IGluIHRoaXMgY2FzZSwgdGhlIG1vZGVsIGlzICpwcmV0dHkgZ29vZCouCgpJbiBmdXR1cmUgbGVzc29ucyBvbiBjbGFzc2lmaWNhdGlvbnMsIHlvdSB3aWxsIGxlYXJuIGhvdyB0byBpbXByb3ZlIHlvdXIgbW9kZWwncyBzY29yZXMgKHN1Y2ggYXMgZGVhbGluZyB3aXRoIGltYmFsYW5jZWQgZGF0YSBpbiB0aGlzIGNhc2UpLgoKQnV0IGZvciBub3csIGNvbmdyYXR1bGF0aW9ucyDwn46J8J+OifCfjokhIFlvdSd2ZSBjb21wbGV0ZWQgdGhlc2UgcmVncmVzc2lvbiBsZXNzb25zIQoKWW91IFIgYXdlc29tZSEKCiFbQXJ0d29yayBieSBcQGFsbGlzb25faG9yc3RdKC4uLy4uL2ltYWdlcy9yX2xlYXJuZXJzX3NtLmpwZWcpCg==