Buckle up 👨🚀
In this learning path, we’ll learn how to create Machine learning models using R
😊. Machine learning is the foundation for predictive modeling and artificial intelligence. We’ll learn the core principles of machine learning and how to use common tools and frameworks to train, evaluate, and use machine learning models.
Modules that will be covered in this learning path include:
Explore and analyze data with R
Train and evaluate regression models
Train and evaluate classification models (under development)
Train and evaluate clustering models (under development)
Train and evaluate deep learning models (under development)
Prerequisites
This learning path assumes knowledge of basic mathematical concepts. Some experience with R and the tidyverse
is also beneficial though we’ll try as much as possible to skim through the core concepts. To get started with R and the tidyverse, the best place would be R for Data Science an O’Reilly book written by Hadley Wickham and Garrett Grolemund. It’s designed to take you from knowing nothing about R or the tidyverse to having all the basic tools of data science at your fingertips.
This tutorial will help you set up your computer to use R. It is for you if you need to:
You can skip this tutorial if you’ve already done these things.
We’ll require some packages to knock-off this module. You can have them installed as:
install.packages(c('tidyverse', 'tidymodels', 'glmnet', 'randomForest', 'xgboost', 'patchwork', 'paletteer', 'statip', 'summarytools'))
Alternatively, feel free to follow along this interactive tutorial by running the code chunks right from your browser.
The Python
edition of the learning path can be found at this learning path.
Why R?
R is open-source and free of charge. It is a powerful programming language that can be used for many different purposes but specializes in data analysis, modeling, visualization, and machine learning. R is easily extensible; it has a vast ecosystem of packages, mostly user-contributed modules that focus on a specific theme, such as modeling, visualization, and so on. - Tidy Modeling with R, MAX KUHN AND JULIA SILGE
Now, let’s get started!
An introduction to regression 👨🏫✍️👩🏫
Very simply put, supervised learning is the machine learning task of learning a function
that maps
an input (feature
) to an output (label
) based on example input-output
pairs. It infers a function from labeled training data that can be applied to new features for which the labels are unknown, and predict them.
You can think of this function like this:
\[
y = f(x)
\]
in which \(y\) represents the label we want to predict and \(x\) represents the features the model uses to predict it.
In most cases, \(x\) is actually a vector that consists of multiple feature values, so to be a little more precise, the function could be expressed like this:
\[
y = f([x_1,x_2,x_3...])
\]
The goal of training
the model is to find a function
that performs some kind of calculation to the \(x\) values that produces the result \(y\). We do this by applying a machine learning algorithm that tries to fit
the \(x\) values to a calculation that produces \(y\) reasonably accurately for all of the cases in the training dataset.
There are lots of machine learning algorithms for supervised learning, and they can be broadly divided into two types:
- Classification algorithms: Algorithms that predict to which category, or class, an observation belongs.
In this module, we’ll focus on regression
.
Regression is a form of machine learning in which the goal is to create a model that can predict a numeric
, quantifiable value
; such as a price, amount, size, or other scalar number.
For example, a company that rents bicycles might want to predict the expected number of rentals in a given day, based on the season, day of the week, weather conditions, and so on.

Training and evaluating a regression model ⚙️
Regression works by establishing a relationship between variables in the data that represent characteristics (known as the features
) of the thing being observed, and the variable we’re trying to predict (known as the label
).
In this case, we’re observing information about days, so the features include things like the day of the week, month, temperature, rainfall, and so on; and the label is the number of bicycle rentals.
To train the model, we start with a data sample containing the features as well as known values for the label - so in this case we need historic data that includes dates, weather conditions, and the number of bicycle rentals. We’ll then split this data sample into two subsets:
A training
dataset
to which we’ll apply an algorithm that determines a function encapsulating the relationship between the feature values and the known label values.
A validation
or test
dataset
that we can use to evaluate the model by using it to generate predictions for the label and comparing them to the actual known label values.
The use of historic data with known label values to train a model makes regression an example of supervised
machine learning
.
A simple example 🚴⛅
Let’s take a simple example to see how the training and evaluation process works in principle. Suppose we simplify the scenario so that we use a single feature, average daily temperature, to predict the bicycle rentals label.
We start with some data that includes known values for the average daily temperature feature and the bicycle rentals label.
Temperature
|
Rentals
|
56
|
115
|
61
|
126
|
67
|
137
|
72
|
140
|
76
|
152
|
82
|
156
|
54
|
114
|
62
|
129
|
Now we’ll take the first five of these observations and use them to train a regression model.
In reality, you’d randomly
split the data into training and validation sets - it’s important for the split to be random to ensure that each subset is statistically similar
Our goal in training
the model is to find a function
(let’s call it \(f\)) that we can apply to the temperature feature
(which we’ll call \(x\)) to calculate the rentals label
(which we’ll call \(y\)). In other words, we need to define the following function:
\[
f\ (x)\ =\ y
\]
Our training dataset looks like this:
x
|
y
|
56
|
115
|
61
|
126
|
67
|
137
|
72
|
140
|
76
|
152
|
Anytime you are planning to implement modelling, it’s always a good idea to explore your dataset. Let’s see this graphically
. The plots are interactive, so feel free to hover around 😄.
Now we need to fit these values to a function, allowing for some random variation. You can probably see that the plotted points form an almost straight diagonal line - in other words, there’s an apparent linear
relationship
between \(x\) and \(y\), so we need to find a linear function that’s the best fit for the data sample.
There are various algorithms we can use to determine this function, which will ultimately find a straight line with minimal overall variance from the plotted points; like this:
The line represents a linear function that can be used with any value of \(x\) to apply the slope of the line and its intercept (where the line crosses the y axis when \(x\) is 0) to calculate \(y\). In this case, if we extended the line to the left we’d find that when \(x\) is 0, \(y\) is around 20, and the slope of the line is such that for each unit of \(x\) you move along to the right, \(y\) increases by around 1.7. Our \(f\) function therefore can be calculated as:
\[
f(x) \ = \ 20\ +\ 1.7x
\]
Now that we’ve defined ourpredictive function
, we can use it to predict labels
for the validation data
we held back and compare the predicted values (which we typically indicate with the symbol \(\hat y\), or “y-hat”) with the actual known \(y\) values (Rentals).
Temperature
|
Rentals
|
\(\hat{y}\)
|
82
|
156
|
159.4
|
54
|
114
|
111.8
|
62
|
129
|
125.4
|
Let’s see how the\(\ y\) and \(\hat y\) values compare in a plot:
The plotted points that are on the function line are the predicted
\(\hat y\) values calculated by the function, and the other plotted points are the actual
\(y\) values.
There are various ways we can measure the variance between the predicted and actual values, and we can use these metrics to evaluate how well the model predicts.
Machine learning is based on statistics and math, and it’s important to be aware of specific terms that statisticians and mathematicians (and therefore data scientists) use. You can think of the difference between a predicted label value and the actual label value as a measure of error
. However, in practice, the “actual” values are based on sample observations (which themselves may be subject to some random variance). To make it clear that we’re comparing a predicted value (\(\hat y\)) with an observed value (\(y\)) we refer to the difference between them as the residuals
. We can summarize the residuals for all of the validation data predictions to calculate the overall loss in the model as a measure of its predictive performance.
One of the most common ways to measure the loss is to square the individual residuals, sum the squares, and calculate the mean. Squaring the residuals has the effect of basing the calculation on absolute values (ignoring whether the difference is negative or positive) and giving more weight to larger differences. This metric is called the Mean Squared Error
.
\[MSE= \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat y_i)^2\] In other words, the MSE is the mean \(\frac{1}{n}\sum_{i=1}^{n}\) of the squares of the errors \((y_i - \hat y_i)^2\)
For our validation data, the calculation looks like this:
\(y\)
|
\(\hat{y}\)
|
\(y - \hat{y}\)
|
\((y - \hat{y})^2\)
|
156
|
159.4
|
-3.4
|
11.56
|
114
|
111.8
|
2.2
|
4.84
|
129
|
125.4
|
3.6
|
12.96
|
Great! Let’s go ahead and do some summary statistics on column \((y - \hat y)^2\) to obtain the total summation and the mean:
\(\sum(y - \hat{y})^2\)
|
\(MSE\)
|
29.36
|
9.79
|
So the loss for our model based on the MSE
metric is 9.79
.
So is that any good? 🤷 It’s difficult to tell because MSE value is not expressed in a meaningful unit of measurement.
However, we do know that the lower
the value is, the less loss
there is in the model; and therefore, the better it is predicting. This makes it a useful metric to compare two models and find the one that performs best.
Sometimes, it’s more useful to express the loss in the same unit of measurement as the predicted label value itself
- in this case, the number of rentals. It’s possible to do this by calculating the square root of the MSE
, which produces a metric known, unsurprisingly, as the Root Mean Squared Error
(RMSE).
\[RMSE = \sqrt{9.79} = 3.13 \]
So our model’s RMSE indicates that the loss is just over 3, which you can interpret loosely as meaning that on average, incorrect predictions are wrong by around 3 rentals.
There are many other metrics that can be used to measure loss in a regression. For example, \(R^2\) (R-Squared)
(sometimes known as coefficient of determination) is the correlation between \(x\) and \(y\) squared. This produces a value between 0 and 1 that measures the amount of variance that can be explained by the model
.
An interior value such as \(R^2 = 0.7\) may be loosely interpreted as follows: “Seventy percent of the variance in the predicted label can be explained by the predicting features. The remaining thirty percent can be attributed to unknown, lurking variables or inherent variability.” Generally, the closer the \(R^2\) value is to 1, the better the model predicts.
Exercise - Train and evaluate a regression model:
Calculating a regression line for a simple binomial (two-variable) function from first principles is possible, but involves some mathematical effort. When you consider a real-world dataset
in which \(x\) is not a single feature value such as temperature, but a vector of multiple variables
such as temperature, day of week, month, rainfall, and so on; the calculations become more complex.
For this reason, data scientists generally use specialized machine learning frameworks to perform model training and evaluation. Such frameworks encapsulate common algorithms and provide useful functions for preparing data, fitting data to a model, and calculating model evaluation metrics.
One commonly used machine learning framework in R, is the tidymodels
, a collection of packages for modeling and machine learning using tidyverse principles.
From this section forward, we’ll explore a hands-on exercise where we’ll use tidymodels to train and evaluate regression models using an example based on a real study in which data for a bicycle sharing scheme was collected and used to predict the number of rentals based on seasonality and weather conditions. We’ll use a simplified version of the dataset from that study.
Citation: The data used in this exercise is derived from Capital Bikeshare and is used in accordance with the published license agreement.
Let’s get right into it 🚀 🌌!
1. Explore the Data 🕵️️
The first step in any machine learning project is to explore the data
that you will use to train a model. The goal of this exploration is to try to understand the relationships
between its attributes; in particular, any apparent correlation between the features and the label your model will try to predict.
This may require some work to detect and fix issues in the data
(such as dealing with missing values, errors, or outlier values), deriving new feature columns
by transforming or combining existing features (a process known as feature engineering), normalizing
numeric features (values you can measure or count) so they’re on a similar scale, and encoding categorical features
(values that represent discrete categories) as numeric indicators.
To achieve this important step in our adventure, we’ll need some packages in the tidyverse! The tidyverse is an opinionated collection of R packages designed for data science.
We’ll begin by loading the data frame from the original repo of this course and then …
the real fun begins!
Sometimes, we may want some little more information on our data. We can have a look at the data
andits structure
by using the glimpse() function.
## Rows: 731
## Columns: 14
## $ instant <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ~
## $ dteday <chr> "1/1/2011", "1/2/2011", "1/3/2011", "1/4/2011", "1/5/2011",~
## $ season <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
## $ yr <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
## $ mnth <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
## $ holiday <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,~
## $ weekday <dbl> 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,~
## $ workingday <dbl> 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1,~
## $ weathersit <dbl> 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 2,~
## $ temp <dbl> 0.3441670, 0.3634780, 0.1963640, 0.2000000, 0.2269570, 0.20~
## $ atemp <dbl> 0.3636250, 0.3537390, 0.1894050, 0.2121220, 0.2292700, 0.23~
## $ hum <dbl> 0.805833, 0.696087, 0.437273, 0.590435, 0.436957, 0.518261,~
## $ windspeed <dbl> 0.1604460, 0.2485390, 0.2483090, 0.1602960, 0.1869000, 0.08~
## $ rentals <dbl> 331, 131, 120, 108, 82, 88, 148, 68, 54, 41, 43, 25, 38, 54~
Good job!💪
We can observe that glimpse()
will give you the total number of rows (observations) and columns (variables), then, the first few entries of each variable in a row after the variable name. In addition, the data type of the variable is given immediately after each variable’s name inside < >
.
The data consists of 731 rows the following 14 columns:
instant: A unique row identifier
dteday: The date on which the data was observed - in this case, the data was collected daily; so there’s one row per date.
season: A numerically encoded value indicating the season (1-spring, 2-summer, 3-fall, 4-winter)
yr: The year of the study in which the observation was made (the study took place over two years (year 0 represents 2011, and year 1 represents 2012)
mnth: The calendar month in which the observation was made (1-January … 12-December)
holiday: A binary value indicating whether or not the observation was made on a public holiday)
weekday: The day of the week on which the observation was made (0-Sunday … 6-Saturday)
workingday: A binary value indicating whether or not the day is a working day (not a weekend or holiday)
weathersit: A categorical value indicating the weather situation (1-clear, 2-mist/cloud, 3-light rain/snow, 4-heavy rain/hail/snow/fog)
temp: The temperature in celsius (normalized)
atemp: The apparent (“feels-like”) temperature in celsius (normalized)
hum: The humidity level (normalized)
windspeed: The windspeed (normalized)
rentals: The number of bicycle rentals recorded.
In this dataset, rentals
represents the label
(the \(y\) value) our model must be trained to predict. The other columns are potential features (\(x\) values).
As mentioned previously, you can perform some feature engineering to combine or derive new features. For example, let’s add a new column named day to the data frame by extracting the day component from the existing dteday column. The new column represents the day of the month from 1 to 31.
From the output of glimpse(), you’ll realize that the dteday column is stored as a "character"
vector. So, we’ll first need to transform this to a date object.
Lubridate, a package in the tidyverse, provides tools that make it easier to parse and manipulate dates.
If you are new to lubridate, the best place to start is the date and times chapter in R for data science.
Such a beauty!🤩
OK, let’s start our analysis of the data by examining a few key descriptive statistics. We can use the summarytools::descr()
function to neatly and quickly summarize the numeric features as well as the rentals label column.
# load package into the R session
library(summarytools)
# Obtain summary stats for feature and label columns
bike_data %>%
# Select features and label
select(c(temp, atemp, hum, windspeed, rentals)) %>%
# Summary stats
descr(order = "preserve",
stats = c('mean', 'sd', 'min', 'q1', 'med', 'q3', 'max'),
round.digits = 6)
## Descriptive Statistics
## bike_data
## N: 731
##
## temp atemp hum windspeed rentals
## ------------- ---------- ---------- ---------- ----------- -------------
## Mean 0.495385 0.474354 0.627894 0.190486 848.176471
## Std.Dev 0.183051 0.162961 0.142429 0.077498 686.622488
## Min 0.059130 0.079070 0.000000 0.022392 2.000000
## Q1 0.336667 0.337746 0.520000 0.134950 315.000000
## Median 0.498333 0.486733 0.626667 0.180975 713.000000
## Q3 0.655833 0.609229 0.730417 0.233221 1097.000000
## Max 0.861667 0.840896 0.972500 0.507463 3410.000000
The statistics reveal some information about the distribution of the data in each of the numeric fields, including the number of observations (there are 731 records), the mean, standard deviation, minimum and maximum values, and the quartile values (the threshold values for 25%, 50% - which is also the median, and 75% of the data).
From this, we can see that the mean number of daily rentals is around 848; but there’s a comparatively large standard deviation
, indicating a lot of variance
in the number of rentals per day.
We might get a clearer idea of the distribution of rentals values by visualizing the data. Common plot types for visualizing numeric data distributions are histograms and box plots, so let’s get our ggplot2
on and create one of each of these for the rentals column. patchwork extends ggplot
API by providing mathematical operators (such as +
or /
) for combining multiple plots. Yes, as easy as that! 📈📉
In case you are new to R, R has several systems for making graphs, but ggplot2
is one of the most elegant and most versatile. The best place to start in creating masterpiece visualisations is the Data Visualisation chapter in R for data science
library(patchwork)
# Plot a histogram
theme_set(theme_light())
hist_plt <- bike_data %>%
ggplot(mapping = aes(x = rentals)) +
geom_histogram(bins = 100, fill = "midnightblue", alpha = 0.7) +
# Add lines for mean and median
geom_vline(aes(xintercept = mean(rentals), color = 'Mean'), linetype = "dashed", size = 1.3) +
geom_vline(aes(xintercept = median(rentals), color = 'Median'), linetype = "dashed", size = 1.3 ) +
xlab("") +
ylab("Frequency") +
scale_color_manual(name = "", values = c(Mean = "red", Median = "yellow")) +
theme(legend.position = c(0.9, 0.9), legend.background = element_blank())
# Plot a box plot
box_plt <- bike_data %>%
ggplot(aes(x = rentals, y = 1)) +
geom_boxplot(fill = "#E69F00", color = "gray23", alpha = 0.7) +
# Add titles and labels
xlab("Rentals")+
ylab("")
# Combine plots
(hist_plt / box_plt) +
plot_annotation(title = 'Rental Distribution',
theme = theme(
plot.title = element_text(hjust = 0.5)))

The plots show that the number of daily rentals ranges from 0 to just over 3,400. However, the mean (and median) number of daily rentals is closer to the low end of that range, with most of the data between 0 and around 2,200 rentals. The few values above this are shown in the box plot as small circles, indicating that they are outliers - in other words, unusually high or low values beyond the typical range of most of the data.
We can do the same kind of visual exploration of the numeric features. One way to do this would be to use a for loop
but ggplot2 provides a way of avoiding this entirely using facets
💁. Facets allow us to create subplots that each display one subset of the data.
This will require us to transform our data into a long format using tidyr::pivot_longer, calculate some statistical summaries and then whip up a histogram for each feature.
# Create a data frame of numeric features & label
numeric_features <- bike_data %>%
select(c(temp, atemp, hum, windspeed, rentals))
# Pivot data to a long format
numeric_features <- numeric_features %>%
pivot_longer(!rentals, names_to = "features", values_to = "values") %>%
group_by(features) %>%
mutate(Mean = mean(values),
Median = median(values))
# Plot a histogram for each feature
numeric_features %>%
ggplot() +
geom_histogram(aes(x = values, fill = features), bins = 100, alpha = 0.7, show.legend = F) +
facet_wrap(~ features, scales = 'free')+
paletteer::scale_fill_paletteer_d("ggthemes::excel_Parallax") +
# Add lines for mean and median
geom_vline(aes(xintercept = Mean, color = "Mean"), linetype = "dashed", size = 1.3 ) +
geom_vline(aes(xintercept = Median, color = "Median"), linetype = "dashed", size = 1.3 ) +
scale_color_manual(name = "", values = c(Mean = "red", Median = "yellow"))

WooHoo! 🙌
The numeric features seem to be more normally distributed, with the mean and median nearer the middle of the range of values, coinciding with where the most commonly occurring values are.
Note: The distributions are not truly normal in the statistical sense, which would result in a smooth, symmetric “bell-curve” histogram with the mean and mode (the most common value) in the center; but they do generally indicate that most of the observations have a value somewhere near the middle.
We’ve explored the distribution of the numeric
values in the dataset, but what about the categorical
features? These aren’t continuous numbers on a scale, so we can’t use histograms; but we can plot a bar chart showing the count of each discrete value for each category.
We’ll follow the same procedure we used for the numeric feature.
# Create a data frame of categorical features & label
categorical_features <- bike_data %>%
select(c(season, mnth, holiday, weekday, workingday, weathersit, day, rentals))
# Pivot data to a long format
categorical_features <- categorical_features %>%
pivot_longer(!rentals, names_to = "features", values_to = "values") %>%
group_by(features) %>%
mutate(values = factor(values))
# Plot a bar plot for each feature
categorical_features %>%
ggplot() +
geom_bar(aes(x = values, fill = features), alpha = 0.7, show.legend = F) +
facet_wrap(~ features, scales = 'free') +
paletteer::scale_fill_paletteer_d("ggthemr::solarized") +
theme(
panel.grid = element_blank(),
axis.text.x = element_text(angle = 90))

Many of the categorical features show a more or less uniform distribution (meaning there’s roughly the same number of rows for each category). Exceptions to this include:
holiday: There are many fewer days that are holidays than days that aren’t.
workingday: There are more working days than non-working days.
weathersit: Most days are category 1 (clear), with category 2 (mist and cloud) the next most common. There are comparatively few category 3 (light rain or snow) days, and no category 4 (heavy rain, hail, or fog) days at all.
Now that we know something about the distribution of the data in our columns, we can start to look for relationships between the features and the rentals label we want to be able to predict.
For the numeric features, we can create scatter plots that show the intersection of feature and label values.

The correlation statistic, r, quantifies the apparent relationship. The correlation statistic is a value between -1 and 1 that indicates the strength of a linear relationship.
The results aren’t conclusive, but if you look closely at the scatter plots for temp
and atemp
, you can see a vague diagonal trend
showing that higher rental counts tend to coincide with higher temperatures; and a correlation value of just over 0.5 for both of these features supports this observation. Conversely, the plots for hum
and windspeed
show a slightly negative correlation
, indicating that there are fewer rentals on days with high humidity or windspeed.
Now let’s compare the categorical features to the label. We’ll do this by creating box plots that show the distribution of rental counts for each category.

The plots show some variance in the relationship between some category values and rentals. For example, there’s a clear difference
in the distribution of rentals on weekends (weekday 0 or 6) and those during the working week (weekday 1 to 5). Similarly, there are notable differences for holiday
and workingday
categories. There’s a noticeable trend that shows different rental distributions in summer and fall months compared to spring and winter months. The weathersit
category also seems to make a difference in rental distribution. The day feature we created for the day of the month shows little variation, indicating that it’s probably not predictive of the number of rentals.
Amazing 🤜🤛! We have just gone through the phase of understanding the data, often referred to as exploratory data analysis (EDA
). EDA brings to light how the different variables are related to one another, their distributions, typical ranges, and other attributes. With these insights in mind, it’s time to train some regression models!
2. Train a Regression Model using Tidymodels 
Now that we’ve explored the data, it’s time to use it to train a regression model that uses the features we’ve identified as potentially predictive
to predict the rentals label. The first thing we need to do is create a data frame that contains the predictive features and the label. Also, we’ll need to specify the roles of the predictors. Are they quantitative (integers/doubles) or are they nominal (characters/factors)?
dplyr::select()
, dplyr::mutate() and dplyr::across() (for applying a function across multiple columns) will come in handy for what lies ahead!
# Select desired features and labels
bike_select <- bike_data %>%
select(c(season, mnth, holiday, weekday, workingday, weathersit,
temp, atemp, hum, windspeed, rentals)) %>%
mutate(across(1:6, factor))
# Get a glimpse of your data
glimpse(bike_select)
## Rows: 731
## Columns: 11
## $ season <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
## $ mnth <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
## $ holiday <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,~
## $ weekday <fct> 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,~
## $ workingday <fct> 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1,~
## $ weathersit <fct> 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 2,~
## $ temp <dbl> 0.3441670, 0.3634780, 0.1963640, 0.2000000, 0.2269570, 0.20~
## $ atemp <dbl> 0.3636250, 0.3537390, 0.1894050, 0.2121220, 0.2292700, 0.23~
## $ hum <dbl> 0.805833, 0.696087, 0.437273, 0.590435, 0.436957, 0.518261,~
## $ windspeed <dbl> 0.1604460, 0.2485390, 0.2483090, 0.1602960, 0.1869000, 0.08~
## $ rentals <dbl> 331, 131, 120, 108, 82, 88, 148, 68, 54, 41, 43, 25, 38, 54~
Alternatively 🤔, it would have been easier to just deselect the unwanted columns using select(-c(…))
but we’ll leave that for next time.
We could train a model using all of the data; but it’s common practice in supervised learning to split the data into two subsets; a (typically larger) set with which to train the model, and a smaller “hold-back” set with which to validate the trained model. This enables us to evaluate how well the model performs in order to get a better estimate of how your models will perform
on new data
. It’s important to split the data randomly (rather than say, taking the first 70% of the data for training and keeping the rest for validation). This helps ensure that the two subsets of data are statistically comparable
(so we validate the model with data that has a similar statistical distribution to the data on which it was trained).
To randomly split the data, we’ll use rsample::initial_split()
. rsample is one of the many packages in the tidymodels.
The tidy modeling “verse” is a collection of packages for modeling and statistical analysis that share the underlying design philosophy, grammar, and data structures of the tidyverse. Tidy Modeling with R would be a good place to get up to speed with this amazing framework!
## Training Set: 511 rows
## Test Set: 220 rows
This results into the following two datasets:
Now ⏲, we’re ready to train a model by fitting a suitable regression algorithm to the training data.
Before embarking on more complex machine learning models, it’s a good idea to build the simplest possible model to get an idea of what is going on. We’ll use a linear regression
algorithm, a common starting point for regression that works by trying to find a linear relationship between the \(x\) values and the \(y\) label. The resulting model is a function that conceptually defines a line where every possible \(x\) and \(y\) value combination intersect.
In Tidymodels, you specify models using parsnip()
. The goal of parsnip is to provide a tidy, unified interface to models that can be used to try a range of models by specifying three concepts:
Model type differentiates models such as logistic regression, decision tree models, and so forth.
Model mode includes common options like regression and classification; some model types support either of these while some only have one mode.
Model engine is the computational tool which will be used to fit the model. Often these are R packages, such as "lm"
or "ranger"
In tidymodels, we capture that modeling information in a model specification, so setting up your model specification can be a good place to start.
After a model has been specified, the model can be estimated
or trained
using the fit()
function, typically using a symbolic description of the model (a formula) and some data.
rentals ~ .
means we’ll fit rentals
as the predicted quantity, explained by all the predictors/features ie, .
## parsnip model object
##
## Fit time: 11ms
##
## Call:
## stats::lm(formula = rentals ~ ., data = data)
##
## Coefficients:
## (Intercept) season2 season3 season4 mnth2 mnth3
## 890.95 302.62 204.77 74.71 -81.60 137.13
## mnth4 mnth5 mnth6 mnth7 mnth8 mnth9
## 46.33 -44.03 -258.90 -213.47 -200.88 39.64
## mnth10 mnth11 mnth12 holiday1 weekday1 weekday2
## 202.04 116.12 -53.85 523.43 -700.04 -769.93
## weekday3 weekday4 weekday5 weekday6 workingday1 weathersit2
## -778.10 -765.64 -600.51 223.64 NA -23.31
## weathersit3 temp atemp hum windspeed
## -447.03 2516.10 -462.18 -737.63 -1265.53
So, these are the coefficients that the model learned during training.
Evaluate the Trained Model
It’s time to see how the model performed 📏!
How do we do this? Simple! Now that we’ve trained the model, we can use it to predict rental counts for the features we held back in our validation dataset using parsnip::predict(). Then we can compare these predictions to the actual label values to evaluate how well (or not!) the model is working.
Comparing each prediction with its corresponding “ground truth” actual value isn’t a very efficient way to determine how well the model is predicting. Let’s see if we can get a better indication by visualizing a scatter plot that compares the predictions to the actual labels. We’ll also overlay a trend line to get a general sense for how well the predicted labels align with the true labels.

🕵 📈There’s a definite diagonal trend, and the intersections of the predicted and actual values are generally following the path of the trend line; but there’s a fair amount of difference between the ideal function represented by the line and the results. This variance represents the residuals of the model - in other words, the difference between the label predicted when the model applies the coefficients it learned during training to the validation data, and the actual value of the validation label. These residuals when evaluated from the validation data indicate the expected level of error when the model is used with new data for which the label is unknown.
You can quantify the residuals by calculating a number of commonly used evaluation metrics. We’ll focus on the following three:
Mean Square Error (MSE)
: The mean of the squared differences between predicted and actual values. This yields a relative metric in which the smaller the value, the better the fit of the model
Root Mean Square Error (RMSE)
: The square root of the MSE. This yields an absolute metric in the same unit as the label (in this case, numbers of rentals). The smaller the value, the better the model (in a simplistic sense, it represents the average number of rentals by which the predictions are wrong!)
Coefficient of Determination (usually known as R-squared or R2)
: A relative metric in which the higher the value, the better the fit of the model. In essence, this metric represents how much of the variance between predicted and actual label values the model is able to explain.
yardstick
is a package in the Tidymodels, used to estimate how well models are working based on the predictions it made for the validation data. You can find out more about these and other metrics for evaluating regression models in the Metric types documentation.
Great job 🙌! So now we’ve quantified the ability of our model to predict the number of rentals. It definitely has some predictive power, but we can probably do better!
3. Summoning more powerful regression algorithms 
The linear regression algorithm we used to train the model has some predictive capability, but there are many kinds of regression algorithm we could try, including:
Linear algorithms: Not just the Linear Regression algorithm we used above (which is technically an Ordinary Least Squares algorithm), but other variants such as Lasso and Ridge.
Tree-based algorithms: Algorithms that build a decision tree to reach a prediction.
Ensemble algorithms: Algorithms that combine the outputs of multiple base algorithms to improve generalizability.
Note: See a full list of parsnip model types and engines and explore model arguments.
Machine Learning for Social Scientists provides a very good explanation and introduction to the what we just discussed above. I would highly recommend it!
Try Another Linear Algorithm
Let’s try training our regression model by using a Lasso
(least absolute shrinkage and selection operator) algorithm. In Tidymodels, we can do this easily by just changing the model specification then the rest is a breeze😎!
Here we’ll set up one model specification for lasso regression; I picked a value for penalty
(sort of randomly) and I set mixture = 1
for lasso (When mixture = 1, it is a pure lasso model).
For convenience, we’ll rewrite the whole code from splitting data to evaluating model performance.
# Split 70% of the data for training and the rest for tesing
set.seed(2056)
bike_split <- bike_select %>%
initial_split(prop = 0.7, strata = holiday)
# Extract the data in each split
bike_train <- training(bike_split)
bike_test <- testing(bike_split)
# Build a lasso model specification
lasso_spec <-
# Type
linear_reg(penalty = 1, mixture = 1) %>%
# Engine
set_engine('glmnet') %>%
# Mode
set_mode('regression')
# Train a lasso regression model
lasso_mod <- lasso_spec %>%
fit(rentals ~ ., data = bike_train)
# Make predictions for test data
results <- bike_test %>%
bind_cols(lasso_mod %>% predict(new_data = bike_test) %>%
rename(predictions = .pred))
# Evaluate the model
lasso_metrics <- eval_metrics(data = results,
truth = rentals,
estimate = predictions) %>%
select(-2)
# Plot predicted vs actual
lasso_plt <- results %>%
ggplot(mapping = aes(x = rentals, y = predictions)) +
geom_point(size = 1.6, color = 'darkorchid') +
# overlay regression line
geom_smooth(method = 'lm', color = 'black', se = F) +
ggtitle("Daily Bike Share Predictions") +
xlab("Actual Labels") +
ylab("Predicted Labels") +
theme(plot.title = element_text(hjust = 0.5))
# Return evaluations
list(lasso_metrics, lasso_plt)
## [[1]]
## # A tibble: 2 x 2
## .metric .estimate
## <chr> <dbl>
## 1 rmse 393.
## 2 rsq 0.683
##
## [[2]]

🤔 Hmmmm… Not much of an improvement. We could improve the performance metrics by estimating the right regularization hyperparameter penalty
. This can be figured out by using resampling
and tuning
the model which we’ll discuss in just a few.
Try a Decision Tree Algorithm
As an alternative to a linear model, there’s a category of algorithms for machine learning that uses a tree-based
approach in which the features in the dataset are examined in a series of evaluations, each of which results in a branch in a decision tree based on the feature value. At the end of each series of branches are leaf-nodes with the predicted label value based on the feature values.
The Decision Trees Chapter in Hands-on Machine Learning with R will provide you with a strong foundation in decision trees.
It’s easiest to see how this works with an example. Let’s train a Decision Tree regression model using the bike rental data. After training the model, the code below will print the model definition and a text representation of the tree it uses to predict label values.
## parsnip model object
##
## Fit time: 101ms
## n= 511
##
## node), split, n, deviance, yval
## * denotes terminal node
##
## 1) root 511 237145200.0 851.7730
## 2) atemp< 0.417285 197 27596650.0 391.9391
## 4) workingday=1 129 7253526.0 264.3566 *
## 5) workingday=0 68 14259970.0 633.9706
## 10) atemp< 0.315586 35 1398290.0 336.3714 *
## 11) atemp>=0.315586 33 6474244.0 949.6061 *
## 3) atemp>=0.417285 314 141759500.0 1140.2680
## 6) workingday=1 221 22239830.0 815.6652
## 12) hum>=0.7377085 51 3782410.0 546.8039 *
## 13) hum< 0.7377085 170 13664830.0 896.3235 *
## 7) workingday=0 93 40897980.0 1911.6340
## 14) hum>=0.81625 12 3079971.0 1201.5000 *
## 15) hum< 0.81625 81 30870000.0 2016.8400
## 30) weekday=0,1,5 43 16898760.0 1823.9300
## 60) atemp< 0.491481 10 3299861.0 1351.9000 *
## 61) atemp>=0.491481 33 10695590.0 1966.9700
## 122) temp>=0.73 8 616967.9 1486.3750 *
## 123) temp< 0.73 25 7639563.0 2120.7600
## 246) mnth=4,6,8,10 12 2090378.0 1742.7500 *
## 247) mnth=5,7,9 13 2251687.0 2469.6920 *
## 31) weekday=3,6 38 10560280.0 2235.1320 *
So now we have a tree-based model; but is it any good? Let’s evaluate it with the test data.
## [[1]]
## # A tibble: 2 x 2
## .metric .estimate
## <chr> <dbl>
## 1 rmse 387.
## 2 rsq 0.696
##
## [[2]]

Our decision tree really failed to generalize 🤦. We can see that it’s predicting constant values for a given range of predictors. We could probably improve this by tuning the model’s hyperparameters
. We’ll see this in just a bit.
Try an Ensemble Algorithm
Ensemble algorithms work by combining multiple base estimators to produce an optimal model, either by applying an aggregate function to a collection of base models (sometimes referred to a bagging
) or by building a sequence of models that build on one another to improve predictive performance (referred to as boosting
).
For example, let’s try a Random Forest model, which applies an averaging function to multiple Decision Tree models for a better overall model.
## parsnip model object
##
## Fit time: 1.1s
##
## Call:
## randomForest(x = maybe_data_frame(x), y = y)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 111555.2
## % Var explained: 75.96
So now we have a random forest model; but is it any good? Let’s evaluate it with the test data.
## [[1]]
## # A tibble: 2 x 2
## .metric .estimate
## <chr> <dbl>
## 1 rmse 280.
## 2 rsq 0.843
##
## [[2]]

🤩 Whoa! That’s a step in the right direction.
Let’s also try a boosting ensemble algorithm. We’ll use a Gradient Boosting
estimator, which like a Random Forest algorithm builds multiple trees, but instead of building them all independently and taking the average result, each tree is built
on the outputs
of the previous one
in an attempt to incrementally reduce the loss (error) in the model.
The Gradient Boosting chapter in Hands-on Machine Learning with R, will provide you with the fundamentals to understanding Gradient Boosting Machines.
In this tutorial, we’ll demonstrate how to implement Gradient Boosting Machines using the xgboost engine.
## parsnip model object
##
## Fit time: 310ms
## ##### xgb.Booster
## raw: 80.2 Kb
## call:
## xgboost::xgb.train(params = list(eta = 0.3, max_depth = 6, gamma = 0,
## colsample_bytree = 1, min_child_weight = 1, subsample = 1,
## objective = "reg:squarederror"), data = x$data, nrounds = 15,
## watchlist = x$watchlist, verbose = 0, nthread = 1)
## params (as set within xgb.train):
## eta = "0.3", max_depth = "6", gamma = "0", colsample_bytree = "1", min_child_weight = "1", subsample = "1", objective = "reg:squarederror", nthread = "1", validate_parameters = "TRUE"
## xgb.attributes:
## niter
## callbacks:
## cb.evaluation.log()
## # of features: 34
## niter: 15
## nfeatures : 34
## evaluation_log:
## iter training_rmse
## 1 799.0744
## 2 596.1309
## ---
## 14 108.1180
## 15 103.3744
From the output, we actually see that Gradient Boosting Machines combine a series of base models, each of which is created sequentially and depends on the previous models, in an attempt to incrementally reduce the error in the model.
So now we have an XGboost model; but is it any good🤷? Again, let’s evaluate it with the test data.
## [[1]]
## # A tibble: 2 x 2
## .metric .estimate
## <chr> <dbl>
## 1 rmse 289.
## 2 rsq 0.829
##
## [[2]]

Okay not bad👌! We are definitely getting somewhere. Can we do better? Let’s take a look at Data Preprocessing
and model hyperparameters
.
4. Recipes and Workflows
Preprocess the Data using recipes 
We trained a model with data that was loaded straight from a source file, with only moderately successful results. In practice, it’s common to perform some preprocessing of the data to make it easier for the algorithm to fit a model to it.
In this section, we’ll explore another tidymodels package, recipes, which is designed to help you preprocess your data before training your model. A recipe is an object that defines a series of steps for data processing.
There’s a huge range of preprocessing transformations you can perform to get your data ready for modeling, but we’ll limit ourselves to a few common techniques:
Scaling numeric features
Normalizing numeric features so they’re on the same scale prevents features with large values from producing coefficients that disproportionately affect the predictions. For example, suppose your data includes the following numeric features:
Normalizing these features to the same scale may result in the following values (assuming A contains values from 0 to 10, B contains values from 0 to 1000, and C contains values from 0 to 100):
There are multiple ways you can scale numeric data, such as calculating the minimum and maximum values for each column and assigning a proportional value between 0 and 1, or by using the mean and standard deviation of a normally distributed variable to maintain the same spread of values on a different scale.
Encoding categorical variables
This involves translating a column with categorical values into one or more numeric columns that take the place of the original.
Machine learning models work best with numeric features rather than text values, so you generally need to convert categorical features into numeric representations. For example, suppose your data includes the following categorical feature.
You can apply ordinal encoding to substitute a unique integer value for each category, like this:
Another common technique is to create “dummy” or indicator variables which replace the original categorical feature with numeric columns whose values are either 1 or 0. This can be shown as:
In R, the convention is to exclude a column for the first factor level (S
, in this case). The reasons for this include simplicity
and reducing linear dependencies
. The full set of encodings can be used for some models. This is traditionally called the “one-hot” encoding and can be achieved using the one_hot
argument of step_dummy()
.
The chapter Feature engineering with recipes in Tidy Modeling with R
contains more information on how to transform and encode data for modelling.
Now, let’s bike forth and create some recipes 🔪🚴!
## Data Recipe
##
## Inputs:
##
## role #variables
## outcome 1
## predictor 10
##
## Operations:
##
## Centering and scaling for all_numeric_predictors()
## Dummy variables from all_nominal_predictors()
We just created our first recipe containing an outcome and its corresponding predictors, with the numeric predictors normalized and the nominal predictors converted to a quantitative format 🙌! Let’s quickly break it down:
The call to recipe()
with a formula tells the recipe the roles of the variables (e.g., predictor, outcome) using bike_train
data as the reference. This can be seen from the results of summary(bike_recipe)
step_normalize(all_numeric_predictors())
specifies that all numeric predictors should be normalized.
step_dummy(all_nominal_predictors())
specifies that all predictors that are currently factor or charactor should be converted to a quantitative format (1s/0s).
Great! Now that we have a recipe, the next step would be to create a model specification (which we already did). In this case, let’s recreate an xgboost
model specification. Tree based models created using the xgboost engine typically require one to create dummy variables.
The chapter Recommended preprocessing in Tidy Modelling with R provides preprocessing recommendations that are needed for various modelling functions.
“Wait!”, you’ll say, “How do we combine this model specification with the data preprocessing we need to do from our recipe? 🤔”
Well, welcome to modelling workflows
😊. This is what we’d call pipelines in Python.
Bundling it all together using a workflow
.
The workflows package allows the user to bind modeling and preprocessing objects together. You can then fit the entire workflow to the data, so that the model encapsulates all of the preprocessing steps as well as the algorithm.
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: boost_tree()
##
## -- Preprocessor ----------------------------------------------------------------
## 2 Recipe Steps
##
## * step_normalize()
## * step_dummy()
##
## -- Model -----------------------------------------------------------------------
## Boosted Tree Model Specification (regression)
##
## Computational engine: xgboost
The workflow
object provides quite an informative summary of the preprocessing steps that will be done by the recipe and also the model specification 👌👌. Into the bargain, a workflow()
can be fit in much the same way a model can.
Now that we have our fitted workflow, how do we make some predictions🔮? predict()
can be used on a workflow in the same way as on a model!
Now, let’s make some predictions on the first 6 observations of our test set.
How convenient workflows are!💁
So probably you may be wondering why we haven’t made predictions on the whole test set, evaluated performance and created some pretty graphs. We’ll get right into that, but first, let’s address a more pressing issue; a boosted tree’s specification:
## function (mode = "unknown", mtry = NULL, trees = NULL, min_n = NULL,
## tree_depth = NULL, learn_rate = NULL, loss_reduction = NULL,
## sample_size = NULL, stop_iter = NULL)
## NULL
Those are a lot of model arguments: mtry
, trees
, min_n
, tree_depth
, learn_rate
, loss_reduction
, sample_size
, stop_iter
🤯🤯!
Now, this begs the question:
how do we know what values we should use?🤔
This brings us to model tuning
.
5. Tune model hyperparameters 
Models have parameters with unknown values that must be estimated in order to use the model for predicting. Some model parameters cannot be learned directly from a dataset during model training; these kinds of parameters are called hyperparameters or tuning parameters.
Instead of learning these kinds of hyperparameters during model training, we can estimate the best values for these by training many models on a simulated data set
and measuring how well all these models perform. This process is called tuning.
Simple models with small datasets can often be fit in a single step, while larger datasets and more complex models tend to achieve better results by fitting repeatedly using simulated data. If the prediction is accurate enough, we consider the model trained. If not, we adjust the model slightly and loop again.
We won’t go into the details of each hyperparameter, but they work together to affect the way the algorithm trains a model. For instance in boosted trees,
min_n
forces the tree to discard any node that has a number of observations below your specified minimum.
tuning the value of mtry
controls the number of variables that will be used at each split of a decision tree.
tuning tree_depth
, on the other hand, helps by stopping our tree from growing after it reaches a certain depth - Tune model parameters, Tidymodels Get Started.
Learning rate
, sets how much a model is adjusted during each training cycle. A high learning rate means a model can be trained faster, but if it’s too high the adjustments can be so large that the model is never ‘finely tuned’ and not optimal - Microsoft Learn, Build Machine Learning Models.
In many cases, the default values provided by Tidymodels will work well (see the defaults by typing help("boost_tree")
on your console); but there may be some advantage in modifying hyperparameters to get better predictive performance or reduce training time.
So how do you know what hyperparameter values you should use? Well, in the absence of a deep understanding of how the underlying algorithm works, you’ll need to experiment. Fortunately, Tidymodels provides a way to tune hyperparameters by trying multiple combinations and finding the best result for a given performance metric.
Machine Learning for Social Scientists provides a very good explanation and introduction to Tree based models e.g Decision trees, Random Forests, Boosted trees etc. I would highly recommend it!
Identify tuning parameters.
How can we signal to tidymodels functions which arguments (in our case cost_complexity
, tree_depth
, min_n
) should be optimized? Parameters are marked for tuning by assigning them a value of tune()
.
Next let’s build our model specification with some tuning and then put our recipe and model specification together in a workflow()
, for ease of use.
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: boost_tree()
##
## -- Preprocessor ----------------------------------------------------------------
## 2 Recipe Steps
##
## * step_normalize()
## * step_dummy()
##
## -- Model -----------------------------------------------------------------------
## Boosted Tree Model Specification (regression)
##
## Main Arguments:
## trees = 50
## tree_depth = tune()
## learn_rate = tune()
##
## Computational engine: xgboost
Create a tuning grid.
Good job! Now that we have specified what parameter to tune, we’ll need to figure out a set of possible values to try out then choose the best.
To do this, we’ll create a grid! To tune our hyperparameters, we need a set of possible values for each parameter to try. In this case study, we’ll work through a regular grid of hyperparameter values.
The function grid_regular()
is from the dials package. It chooses sensible values to try for each hyperparameter; here, we asked for 5 of each. Since we have two to tune, grid_regular()
returns 5×5 = 25 different possible tuning combinations to try in a tidy tibble format.
Let’s sample our data.
As we pointed out earlier, hyperparameters cannot be learned directly from the training set. Instead, they are estimated using simulated data sets created from a process called resampling. One resampling approach is cross-validation
.
Cross-validation involves taking your training set and randomly dividing it up evenly into V
subsets/folds. You then use one of the folds for validation and the rest for training, then you repeat these steps with all the subsets and combine the results, usually by taking the mean. This is just one round of cross-validation. Sometimes practictioners do this more than once, perhaps 5 times.
Time to tune
Now, it’s time to tune the grid to find out which penalty results in the best performance!
Visualize tuning results
Now that we have trained models for many possible penalty parameter, let’s explore the results.
As a first step, we’ll use the function collect_metrics()
to extract the performance metrics from the tuning results.
Once we have our tuning results, we can both explore them through visualization and then select the best result.

We can see that our “stubbiest” tree, with a depth of 1, is the worst model according to both metrics (rmse, rsq) and across all candidate values of learn_rate
. A tree depth of 4 and a learn_rate of 0.1 seems to do the trick! Let’s investigate these tuning parameters further. We can use show_best()
to display the top sub-models and their performance estimates.
We can then use select_best()
to find the tuning parameter combination with the best performance values.
6. Finalizing our model
Now that we have the best performance values, we can use tune::finalize_workflow()
to update (or “finalize”) our workflow object with the best estimate values for tree_depth and learn_rate.
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: boost_tree()
##
## -- Preprocessor ----------------------------------------------------------------
## 2 Recipe Steps
##
## * step_normalize()
## * step_dummy()
##
## -- Model -----------------------------------------------------------------------
## Boosted Tree Model Specification (regression)
##
## Main Arguments:
## trees = 50
## tree_depth = 4
## learn_rate = 0.155
##
## Computational engine: xgboost
Our tuning is done! 🥳 We have updated our workflow with the best estimated hyperparameter values!
The last fit: back to our test set.
Finally, let’s return to our test data and estimate the model performance we expect to see with new data. We can use the function last_fit()
with our finalized model; this function fits the finalized model on the full training data set and evaluates the finalized model on the testing data.
How’s that for a tune 🎶 💃🕺! Also, there seems to be some improvement in the evaluation metrics compared to using the default values for learn_rate and tree_depth hyperparameters. Now, we leave it to you to explore how tuning the other hyperparameters affects the model performance.
We’ve now seen a number of common techniques used to train predictive models for regression. In a real project, you’d likely try a few more algorithms, hyperparameters, and preprocessing transformations; but by now you should have got the general idea of the procedure to follow. You can explore the reference docs, or use the args()
function to see which parsnip object arguments are available.
Use this Tidymodels reference page to explore model types and engines and to explore model arguments.
Let’s now explore how you can use the trained model with new data.
Use the Trained Model
We’ll begin by saving our model but first, let’s extract the trained workflow object from final_fit
object.
Now, we can save this model to be used later.
Now, we can load it whenever we need it, and use it to predict labels for new data. This is often called scoring
or inferencing
.
For example, lets try and predict some values from our test set using the saved model.
PeRfect!🐱 All is well that ends with a working model, time to end the cycle 🚴!
7. Summary
We need a bRake, don’t we? 😅 We hope you had a wheelie good time!
In this module, we learnt how regression can be used to create a machine learning model that predicts numeric values. We cycled off by doing some Exploratory Data Analysis using the Tidyverse
then we used the Tidymodels
framework in R
to train and evaluate a regression model using different algorithms, do some data preprocessing, tuned some hyperparameters and made better predictions.
While Tidymodels
and scikit-learn
(Python) are popular framework for writing code to train regression models, you can also create machine learning solutions for regression using the graphical tools in Microsoft Azure Machine Learning. You can learn more about no-code development of regression models using Azure Machine Learning in the Create a Regression Model with Azure Machine Learning designer module.
THANK YOU TO:
Allison Horst
for creating the amazing illustrations that make R more welcoming and engaging. Find more illustrations at her gallery.
Bethany
, Gold Microsoft Learn Student Ambassador, for the valuable feedback and suggestions.
FURTHER READING
Max Kuhn and Julia Silge, Tidy Modeling with R.
Kuhn, M, and K Johnson. 2013. Applied Predictive Modeling. Springer.
Bradley Boehmke & Brandon Greenwell, Hands-On Machine Learning with R.
Jorge Cimentada, Machine Learning for Social Scientists.
Tidy models reference website.
H. Wickham and G. Grolemund, R for Data Science: Visualize, Model, Transform, Tidy, and Import Data.
Be sure to join us in the next R module where we’ll slice and dice some classification algorithms.
Till then …
Happy learning,
Eric (R_ic), Gold Microsoft Learn Student Ambassador.
LS0tDQp0aXRsZTogIlRyYWluIGFuZCBFdmFsdWF0ZSBSZWdyZXNzaW9uIE1vZGVscyB1c2luZyBUaWR5bW9kZWxzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNzczogc3R5bGVfNi5jc3MNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogZmxhdGx5DQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KGdsdWUpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkocGF0Y2h3b3JrKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHN1bW1hcnl0b29scykNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShnbG1uZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoeGdib29zdCkNCmxpYnJhcnkoY29uZmxpY3RlZCkNCnNsaWNlIDwtIGRwbHlyOjpzbGljZQ0KZXZhbF9tZXRyaWNzIDwtIG1ldHJpY19zZXQocm1zZSwgcnNxKQ0KYGBgDQoNCiMjIEJ1Y2tsZSB1cCDwn5Go4oCN8J+agA0KDQpJbiB0aGlzIGxlYXJuaW5nIHBhdGgsIHdlJ2xsIGxlYXJuIGhvdyB0byBjcmVhdGUgTWFjaGluZSBsZWFybmluZyBtb2RlbHMgdXNpbmcgYFJgIPCfmIouIE1hY2hpbmUgbGVhcm5pbmcgaXMgdGhlIGZvdW5kYXRpb24gZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcgYW5kIGFydGlmaWNpYWwgaW50ZWxsaWdlbmNlLiBXZSdsbCBsZWFybiB0aGUgY29yZSBwcmluY2lwbGVzIG9mIG1hY2hpbmUgbGVhcm5pbmcgYW5kIGhvdyB0byB1c2UgY29tbW9uIHRvb2xzIGFuZCBmcmFtZXdvcmtzIHRvIHRyYWluLCBldmFsdWF0ZSwgYW5kIHVzZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4NCg0KTW9kdWxlcyB0aGF0IHdpbGwgYmUgY292ZXJlZCBpbiB0aGlzIGxlYXJuaW5nIHBhdGggaW5jbHVkZToNCg0KLSAgIFsqRXhwbG9yZSBhbmQgYW5hbHl6ZSBkYXRhIHdpdGggUipdKGh0dHBzOi8vcnB1YnMuY29tL2VSX2ljL2V4cGxvUmUpDQoNCi0gICAqVHJhaW4gYW5kIGV2YWx1YXRlIHJlZ3Jlc3Npb24gbW9kZWxzKg0KDQotICAgKlRyYWluIGFuZCBldmFsdWF0ZSBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgKHVuZGVyIGRldmVsb3BtZW50KSoNCg0KLSAgICpUcmFpbiBhbmQgZXZhbHVhdGUgY2x1c3RlcmluZyBtb2RlbHMgKHVuZGVyIGRldmVsb3BtZW50KSoNCg0KLSAgICpUcmFpbiBhbmQgZXZhbHVhdGUgZGVlcCBsZWFybmluZyBtb2RlbHMgKHVuZGVyIGRldmVsb3BtZW50KSoNCg0KIyMjICoqUHJlcmVxdWlzaXRlcyoqDQoNClRoaXMgbGVhcm5pbmcgcGF0aCBhc3N1bWVzIGtub3dsZWRnZSBvZiBiYXNpYyBtYXRoZW1hdGljYWwgY29uY2VwdHMuIFNvbWUgZXhwZXJpZW5jZSB3aXRoIGBSIGFuZCB0aGUgdGlkeXZlcnNlYCBpcyBhbHNvIGJlbmVmaWNpYWwgdGhvdWdoIHdlJ2xsIHRyeSBhcyBtdWNoIGFzIHBvc3NpYmxlIHRvIHNraW0gdGhyb3VnaCB0aGUgY29yZSBjb25jZXB0cy4gVG8gZ2V0IHN0YXJ0ZWQgd2l0aCBSIGFuZCB0aGUgdGlkeXZlcnNlLCB0aGUgYmVzdCBwbGFjZSB3b3VsZCBiZSBbUiBmb3IgRGF0YSBTY2llbmNlXShodHRwOi8vcjRkcy5oYWQuY28ubnovKSBhbiBPJ1JlaWxseSBib29rIHdyaXR0ZW4gYnkgSGFkbGV5IFdpY2toYW0gYW5kIEdhcnJldHQgR3JvbGVtdW5kLiBJdCdzIGRlc2lnbmVkIHRvIHRha2UgeW91IGZyb20ga25vd2luZyBub3RoaW5nIGFib3V0IFIgb3IgdGhlIHRpZHl2ZXJzZSB0byBoYXZpbmcgYWxsIHRoZSBiYXNpYyB0b29scyBvZiBkYXRhIHNjaWVuY2UgYXQgeW91ciBmaW5nZXJ0aXBzLg0KDQpUaGlzIFt0dXRvcmlhbF0oaHR0cHM6Ly9sZWFybnItZXhhbXBsZXMuc2hpbnlhcHBzLmlvL2V4LXNldHVwLXIvI3NlY3Rpb24td2VsY29tZSkgd2lsbCBoZWxwIHlvdSBzZXQgdXAgeW91ciBjb21wdXRlciB0byB1c2UgUi4gSXQgaXMgZm9yIHlvdSBpZiB5b3UgbmVlZCB0bzoNCg0KLSAgIEluc3RhbGwgUiBvbiB5b3VyIGNvbXB1dGVyDQoNCi0gICBJbnN0YWxsIHRoZSBSU3R1ZGlvIElERQ0KDQotICAgSW5zdGFsbCB0aGUgYHRpZHl2ZXJzZWAgUiBwYWNrYWdlDQoNCllvdSBjYW4gc2tpcCB0aGlzIHR1dG9yaWFsIGlmIHlvdSd2ZSBhbHJlYWR5IGRvbmUgdGhlc2UgdGhpbmdzLg0KDQpXZSdsbCByZXF1aXJlIHNvbWUgcGFja2FnZXMgdG8ga25vY2stb2ZmIHRoaXMgbW9kdWxlLiBZb3UgY2FuIGhhdmUgdGhlbSBpbnN0YWxsZWQgYXM6DQoNCmBpbnN0YWxsLnBhY2thZ2VzKGMoJ3RpZHl2ZXJzZScsICd0aWR5bW9kZWxzJywgJ2dsbW5ldCcsICdyYW5kb21Gb3Jlc3QnLCAneGdib29zdCcsICdwYXRjaHdvcmsnLCAncGFsZXR0ZWVyJywgJ3N0YXRpcCcsICdzdW1tYXJ5dG9vbHMnKSlgDQoNCkFsdGVybmF0aXZlbHksIGZlZWwgZnJlZSB0byBmb2xsb3cgYWxvbmcgdGhpcyBpbnRlcmFjdGl2ZSB0dXRvcmlhbCBieSBydW5uaW5nIHRoZSBjb2RlIGNodW5rcyByaWdodCBmcm9tIHlvdXIgYnJvd3Nlci4NCg0KVGhlIGBQeXRob25gIGVkaXRpb24gb2YgdGhlIGxlYXJuaW5nIHBhdGggY2FuIGJlIGZvdW5kIGF0IFt0aGlzIGxlYXJuaW5nIHBhdGhdKGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2xlYXJuL3BhdGhzL2NyZWF0ZS1tYWNoaW5lLWxlYXJuLW1vZGVscy8pLg0KDQoqKldoeSBSPyoqDQoNCj4gUiBpcyBvcGVuLXNvdXJjZSBhbmQgZnJlZSBvZiBjaGFyZ2UuIEl0IGlzIGEgcG93ZXJmdWwgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgdGhhdCBjYW4gYmUgdXNlZCBmb3IgbWFueSBkaWZmZXJlbnQgcHVycG9zZXMgYnV0IHNwZWNpYWxpemVzIGluIGRhdGEgYW5hbHlzaXMsIG1vZGVsaW5nLCB2aXN1YWxpemF0aW9uLCBhbmQgbWFjaGluZSBsZWFybmluZy4gUiBpcyBlYXNpbHkgKmV4dGVuc2libGUqOyBpdCBoYXMgYSB2YXN0IGVjb3N5c3RlbSBvZiBwYWNrYWdlcywgbW9zdGx5IHVzZXItY29udHJpYnV0ZWQgbW9kdWxlcyB0aGF0IGZvY3VzIG9uIGEgc3BlY2lmaWMgdGhlbWUsIHN1Y2ggYXMgbW9kZWxpbmcsIHZpc3VhbGl6YXRpb24sIGFuZCBzbyBvbi4gLSBbKmBUaWR5IE1vZGVsaW5nIHdpdGggUiwgTUFYIEtVSE4gQU5EIEpVTElBIFNJTEdFYCpdKGh0dHBzOi8vd3d3LnRtd3Iub3JnLykNCg0KTm93LCBsZXQncyBnZXQgc3RhcnRlZCENCg0KIVtBcnR3b3JrIGJ5IFxAYWxsaXNvbl9ob3JzdF0oaW1hZ2VzL2VuY291UmFnZS5qcGcpe3dpZHRoPSI2MzAifQ0KDQojIyBBbiBpbnRyb2R1Y3Rpb24gdG8gcmVncmVzc2lvbiDigIvwn5Go4oCN8J+Pq+KAi+Kcje+4j+KAi/CfkanigI3wn4+r4oCLDQoNClZlcnkgc2ltcGx5IHB1dCwgKipzdXBlcnZpc2VkIGxlYXJuaW5nKiogaXMgdGhlIG1hY2hpbmUgbGVhcm5pbmcgdGFzayBvZiBgbGVhcm5pbmcgYSBmdW5jdGlvbmAgdGhhdCBgbWFwc2AgYW4gaW5wdXQgKGBmZWF0dXJlYCkgdG8gYW4gb3V0cHV0IChgbGFiZWxgKSBiYXNlZCBvbiBgZXhhbXBsZSBpbnB1dC1vdXRwdXRgcGFpcnMuIEl0IGluZmVycyBhIGZ1bmN0aW9uIGZyb20gbGFiZWxlZCB0cmFpbmluZyBkYXRhIHRoYXQgY2FuIGJlIGFwcGxpZWQgdG8gbmV3IGZlYXR1cmVzIGZvciB3aGljaCB0aGUgbGFiZWxzIGFyZSB1bmtub3duLCBhbmQgcHJlZGljdCB0aGVtLg0KDQpZb3UgY2FuIHRoaW5rIG9mIHRoaXMgZnVuY3Rpb24gbGlrZSB0aGlzOg0KDQokJA0KeSA9IGYoeCkNCiQkDQoNCmluIHdoaWNoICR5JCByZXByZXNlbnRzIHRoZSBsYWJlbCB3ZSB3YW50IHRvIHByZWRpY3QgYW5kICR4JCByZXByZXNlbnRzIHRoZSBmZWF0dXJlcyB0aGUgbW9kZWwgdXNlcyB0byBwcmVkaWN0IGl0Lg0KDQpJbiBtb3N0IGNhc2VzLCAkeCQgaXMgYWN0dWFsbHkgYSAqdmVjdG9yKiB0aGF0IGNvbnNpc3RzIG9mIG11bHRpcGxlIGZlYXR1cmUgdmFsdWVzLCBzbyB0byBiZSBhIGxpdHRsZSBtb3JlIHByZWNpc2UsIHRoZSBmdW5jdGlvbiBjb3VsZCBiZSBleHByZXNzZWQgbGlrZSB0aGlzOg0KDQokJA0KeSA9IGYoW3hfMSx4XzIseF8zLi4uXSkNCiQkDQoNClRoZSBnb2FsIG9mIGB0cmFpbmluZ2AgdGhlIG1vZGVsIGlzIHRvIGZpbmQgYSBgZnVuY3Rpb25gIHRoYXQgcGVyZm9ybXMgc29tZSBraW5kIG9mIGNhbGN1bGF0aW9uIHRvIHRoZSAkeCQgdmFsdWVzIHRoYXQgcHJvZHVjZXMgdGhlIHJlc3VsdCAkeSQuIFdlIGRvIHRoaXMgYnkgYXBwbHlpbmcgYSBtYWNoaW5lIGxlYXJuaW5nICphbGdvcml0aG0qIHRoYXQgdHJpZXMgdG8gYGZpdGAgdGhlICR4JCB2YWx1ZXMgdG8gYSBjYWxjdWxhdGlvbiB0aGF0IHByb2R1Y2VzICR5JCByZWFzb25hYmx5IGFjY3VyYXRlbHkgZm9yIGFsbCBvZiB0aGUgY2FzZXMgaW4gdGhlIHRyYWluaW5nIGRhdGFzZXQuDQoNClRoZXJlIGFyZSBsb3RzIG9mIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBmb3Igc3VwZXJ2aXNlZCBsZWFybmluZywgYW5kIHRoZXkgY2FuIGJlIGJyb2FkbHkgZGl2aWRlZCBpbnRvIHR3byB0eXBlczoNCg0KLSAgICoqQ2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcyoqOiBBbGdvcml0aG1zIHRoYXQgcHJlZGljdCB0byB3aGljaCBjYXRlZ29yeSwgb3IgKmNsYXNzKiwgYW4gb2JzZXJ2YXRpb24gYmVsb25ncy4NCg0KPCEtLSAtLT4NCg0KLSAgICoqUmVncmVzc2lvbiBhbGdvcml0aG1zKioNCg0KSW4gdGhpcyBtb2R1bGUsIHdlJ2xsIGZvY3VzIG9uICoqKmByZWdyZXNzaW9uYCoqLioNCg0KKlJlZ3Jlc3Npb24qIGlzIGEgZm9ybSBvZiBtYWNoaW5lIGxlYXJuaW5nIGluIHdoaWNoIHRoZSBnb2FsIGlzIHRvIGNyZWF0ZSBhIG1vZGVsIHRoYXQgY2FuIHByZWRpY3QgYSBgbnVtZXJpY2AsIGBxdWFudGlmaWFibGUgdmFsdWVgOyBzdWNoIGFzIGEgcHJpY2UsIGFtb3VudCwgc2l6ZSwgb3Igb3RoZXIgc2NhbGFyIG51bWJlci4NCg0KRm9yIGV4YW1wbGUsIGEgY29tcGFueSB0aGF0IHJlbnRzIGJpY3ljbGVzIG1pZ2h0IHdhbnQgdG8gcHJlZGljdCB0aGUgZXhwZWN0ZWQgbnVtYmVyIG9mIHJlbnRhbHMgaW4gYSBnaXZlbiBkYXksIGJhc2VkIG9uIHRoZSBzZWFzb24sIGRheSBvZiB0aGUgd2Vlaywgd2VhdGhlciBjb25kaXRpb25zLCBhbmQgc28gb24uDQoNCiFbXShpbWFnZXMvY3ljbGUtcmVudGFscy5wbmcpe3dpZHRoPSI1MDAifQ0KDQo8YnI+ICoqVHJhaW5pbmcgYW5kIGV2YWx1YXRpbmcgYSByZWdyZXNzaW9uIG1vZGVsIOKame+4jyoqDQoNClJlZ3Jlc3Npb24gd29ya3MgYnkgZXN0YWJsaXNoaW5nIGEgcmVsYXRpb25zaGlwIGJldHdlZW4gdmFyaWFibGVzIGluIHRoZSBkYXRhIHRoYXQgcmVwcmVzZW50IGNoYXJhY3RlcmlzdGljcyAoa25vd24gYXMgdGhlICpgZmVhdHVyZXNgKikgb2YgdGhlIHRoaW5nIGJlaW5nIG9ic2VydmVkLCBhbmQgdGhlIHZhcmlhYmxlIHdlJ3JlIHRyeWluZyB0byBwcmVkaWN0IChrbm93biBhcyB0aGUgKmBsYWJlbGAqKS4NCg0KSW4gdGhpcyBjYXNlLCB3ZSdyZSBvYnNlcnZpbmcgaW5mb3JtYXRpb24gYWJvdXQgZGF5cywgc28gdGhlIGZlYXR1cmVzIGluY2x1ZGUgdGhpbmdzIGxpa2UgdGhlIGRheSBvZiB0aGUgd2VlaywgbW9udGgsIHRlbXBlcmF0dXJlLCByYWluZmFsbCwgYW5kIHNvIG9uOyBhbmQgdGhlIGxhYmVsIGlzIHRoZSBudW1iZXIgb2YgYmljeWNsZSByZW50YWxzLg0KDQpUbyB0cmFpbiB0aGUgbW9kZWwsIHdlIHN0YXJ0IHdpdGggYSBkYXRhIHNhbXBsZSBjb250YWluaW5nIHRoZSBmZWF0dXJlcyBhcyB3ZWxsIGFzIGtub3duIHZhbHVlcyBmb3IgdGhlIGxhYmVsIC0gc28gaW4gdGhpcyBjYXNlIHdlIG5lZWQgaGlzdG9yaWMgZGF0YSB0aGF0IGluY2x1ZGVzIGRhdGVzLCB3ZWF0aGVyIGNvbmRpdGlvbnMsIGFuZCB0aGUgbnVtYmVyIG9mIGJpY3ljbGUgcmVudGFscy4gV2UnbGwgdGhlbiBzcGxpdCB0aGlzIGRhdGEgc2FtcGxlIGludG8gdHdvIHN1YnNldHM6DQoNCi0gICBBICpgdHJhaW5pbmdgKmBkYXRhc2V0YCB0byB3aGljaCB3ZSdsbCBhcHBseSBhbiBhbGdvcml0aG0gdGhhdCBkZXRlcm1pbmVzIGEgZnVuY3Rpb24gZW5jYXBzdWxhdGluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGZlYXR1cmUgdmFsdWVzIGFuZCB0aGUga25vd24gbGFiZWwgdmFsdWVzLg0KDQotICAgQSAqYHZhbGlkYXRpb25gKiBvciAqYHRlc3RgKmBkYXRhc2V0YCB0aGF0IHdlIGNhbiB1c2UgdG8gZXZhbHVhdGUgdGhlIG1vZGVsIGJ5IHVzaW5nIGl0IHRvIGdlbmVyYXRlIHByZWRpY3Rpb25zIGZvciB0aGUgbGFiZWwgYW5kIGNvbXBhcmluZyB0aGVtIHRvIHRoZSBhY3R1YWwga25vd24gbGFiZWwgdmFsdWVzLg0KDQpUaGUgdXNlIG9mIGhpc3RvcmljIGRhdGEgd2l0aCBrbm93biBsYWJlbCB2YWx1ZXMgdG8gdHJhaW4gYSBtb2RlbCBtYWtlcyByZWdyZXNzaW9uIGFuIGV4YW1wbGUgb2YgKmBzdXBlcnZpc2VkYCpgbWFjaGluZSBsZWFybmluZ2AuDQoNCjxicj4gKipBIHNpbXBsZSBleGFtcGxlIPCfmrTim4UqKg0KDQpMZXQncyB0YWtlIGEgc2ltcGxlIGV4YW1wbGUgdG8gc2VlIGhvdyB0aGUgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb24gcHJvY2VzcyB3b3JrcyBpbiBwcmluY2lwbGUuIFN1cHBvc2Ugd2Ugc2ltcGxpZnkgdGhlIHNjZW5hcmlvIHNvIHRoYXQgd2UgdXNlIGEgc2luZ2xlIGZlYXR1cmUsIGF2ZXJhZ2UgZGFpbHkgdGVtcGVyYXR1cmUsIHRvIHByZWRpY3QgdGhlIGJpY3ljbGUgcmVudGFscyBsYWJlbC4NCg0KV2Ugc3RhcnQgd2l0aCBzb21lIGRhdGEgdGhhdCBpbmNsdWRlcyBrbm93biB2YWx1ZXMgZm9yIHRoZSBhdmVyYWdlIGRhaWx5IHRlbXBlcmF0dXJlIGZlYXR1cmUgYW5kIHRoZSBiaWN5Y2xlIHJlbnRhbHMgbGFiZWwuDQoNCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIER1bW15IHNldCBjb250YWluaW5nIGEgZmVhdHVyZSBhbmQgbGFiZWwgY29sdW1uDQpkdW1teV9zZXQgPC0gdGliYmxlKA0KICBUZW1wZXJhdHVyZSA9IGMoNTYsIDYxLCA2NywgNzIsIDc2LCA4MiwgNTQsIDYyKSwNCiAgUmVudGFscyA9IGMoMTE1LCAxMjYsIDEzNywgMTQwLCAxNTIsIDE1NiwgMTE0LCAxMjkpDQopDQoNCiMgRGlzcGxheSB0aGUgZGF0YSBzZXQNCmR1bW15X3NldCAlPiUgDQogIGtibChhbGlnbiA9ICdsJykgJT4lIA0KICBrYWJsZV9tYXRlcmlhbF9kYXJrKCkNCmBgYA0KDQo8YnI+IE5vdyB3ZSdsbCB0YWtlIHRoZSBmaXJzdCBmaXZlIG9mIHRoZXNlIG9ic2VydmF0aW9ucyBhbmQgdXNlIHRoZW0gdG8gdHJhaW4gYSByZWdyZXNzaW9uIG1vZGVsLg0KDQo+IEluIHJlYWxpdHksIHlvdSdkICpgcmFuZG9tbHlgKiBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMgLSBpdCdzIGltcG9ydGFudCBmb3IgdGhlIHNwbGl0IHRvIGJlIHJhbmRvbSB0byBlbnN1cmUgdGhhdCBlYWNoIHN1YnNldCBpcyBgc3RhdGlzdGljYWxseSBzaW1pbGFyYA0KDQpPdXIgZ29hbCBpbiBgdHJhaW5pbmdgIHRoZSBtb2RlbCBpcyB0byBmaW5kIGEgYGZ1bmN0aW9uYCAobGV0J3MgY2FsbCBpdCAkZiQpIHRoYXQgd2UgY2FuICphcHBseSogdG8gdGhlIHRlbXBlcmF0dXJlICpgZmVhdHVyZWAqICh3aGljaCB3ZSdsbCBjYWxsICR4JCkgdG8gKmNhbGN1bGF0ZSogdGhlIHJlbnRhbHMgKmBsYWJlbGAqICh3aGljaCB3ZSdsbCBjYWxsICR5JCkuIEluIG90aGVyIHdvcmRzLCB3ZSBuZWVkIHRvIGRlZmluZSB0aGUgZm9sbG93aW5nIGZ1bmN0aW9uOg0KDQokJA0KZlwgKHgpXCA9XCB5DQokJA0KDQpPdXIgdHJhaW5pbmcgZGF0YXNldCBsb29rcyBsaWtlIHRoaXM6DQoNCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIFNwbGl0IGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzDQpkdW1teV90cmFpbl9zZXQgPC0gZHVtbXlfc2V0ICU+JSANCiAgZHBseXI6OnNsaWNlKDE6NSkNCg0KZHVtbXlfdGVzdF9zZXQgPC0gZHVtbXlfc2V0ICU+JSANCiAgZHBseXI6OnNsaWNlKDY6OCkNCg0KIyBEaXNwbGF5IHRoZSB0cmFpbmluZyBzZXQNCmR1bW15X3RyYWluX3NldCAlPiUgDQogIHJlbmFtZShjKHggPSBUZW1wZXJhdHVyZSwgeSA9IFJlbnRhbHMpKSAlPiUgDQogIGtibChhbGlnbiA9ICdsJykgJT4lIA0KICBrYWJsZV9tYXRlcmlhbF9kYXJrKCkNCmBgYA0KDQo8YnI+IEFueXRpbWUgeW91IGFyZSBwbGFubmluZyB0byBpbXBsZW1lbnQgbW9kZWxsaW5nLCBpdCdzIGFsd2F5cyBhIGdvb2QgaWRlYSB0byBleHBsb3JlIHlvdXIgZGF0YXNldC4gTGV0J3Mgc2VlIHRoaXMgZ3JhcGhpY2FsbHkgIVtdKGltYWdlcy9nZ3Bsb3QyLnBuZyl7d2lkdGg9IjIwIn0gLiBUaGUgcGxvdHMgYXJlIGludGVyYWN0aXZlLCBzbyBmZWVsIGZyZWUgdG8gaG92ZXIgYXJvdW5kIPCfmIQuDQoNCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIERpc3BsYXkgdGhlIHRyYWluaW5nIHNldA0KdGhlbWVfc2V0KHRoZW1lX2xpZ2h0KCkpDQp0cmFpbl9wbG90IDwtIGR1bW15X3NldCAlPiUNCiAgZHBseXI6OnNsaWNlKDE6NSkgJT4lDQogIHJlbmFtZShjKHggPSBUZW1wZXJhdHVyZSwgeSA9IFJlbnRhbHMpKSAlPiUNCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHgsIHkgPSB5KSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAxLjYsIGNvbG9yID0gImJsdWUiKSsNCiAgeGxpbSg1MCwgODApKw0KICB5bGltKDgwLCAxNjApKw0KICBsYWJzKHggPSAneCAoVGVtcGVyYXR1cmUpJywgeSA9ICd5IChSZW50YWxzKScpDQoNCmdncGxvdGx5KHRyYWluX3Bsb3QpDQpgYGANCg0KPGJyPiBOb3cgd2UgbmVlZCB0byBmaXQgdGhlc2UgdmFsdWVzIHRvIGEgZnVuY3Rpb24sIGFsbG93aW5nIGZvciBzb21lIHJhbmRvbSB2YXJpYXRpb24uIFlvdSBjYW4gcHJvYmFibHkgc2VlIHRoYXQgdGhlIHBsb3R0ZWQgcG9pbnRzIGZvcm0gYW4gYWxtb3N0IHN0cmFpZ2h0IGRpYWdvbmFsIGxpbmUgLSBpbiBvdGhlciB3b3JkcywgdGhlcmUncyBhbiBhcHBhcmVudCAqYGxpbmVhcmAqYHJlbGF0aW9uc2hpcGAgYmV0d2VlbiAkeCQgYW5kICR5JCwgc28gd2UgbmVlZCB0byBmaW5kIGEgbGluZWFyIGZ1bmN0aW9uIHRoYXQncyB0aGUgYmVzdCBmaXQgZm9yIHRoZSBkYXRhIHNhbXBsZS4NCg0KVGhlcmUgYXJlIHZhcmlvdXMgYWxnb3JpdGhtcyB3ZSBjYW4gdXNlIHRvIGRldGVybWluZSB0aGlzIGZ1bmN0aW9uLCB3aGljaCB3aWxsIHVsdGltYXRlbHkgZmluZCBhIHN0cmFpZ2h0IGxpbmUgd2l0aCAqbWluaW1hbCBvdmVyYWxsIHZhcmlhbmNlKiBmcm9tIHRoZSBwbG90dGVkIHBvaW50czsgbGlrZSB0aGlzOg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBEaXNwbGF5IHRoZSB0cmFpbmluZyBzZXQNCnRoZW1lX3NldCh0aGVtZV9saWdodCgpKQ0KdHJhaW5fcGxvdCA8LSBkdW1teV9zZXQgJT4lDQogIGRwbHlyOjpzbGljZSgxOjUpICU+JQ0KICByZW5hbWUoYyh4ID0gVGVtcGVyYXR1cmUsIHkgPSBSZW50YWxzKSkgJT4lDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSB4LCB5ID0geSkpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMS42LCBjb2xvciA9ICJibHVlIikgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBzZSA9IEYsIGZ1bGxyYW5nZSA9IFRSVUUsIGxpbmV0eXBlID0gMywgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuMikgKw0KICB4bGltKDUwLCA4MCkrDQogIHlsaW0oODAsIDE2MCkrDQogIGxhYnMoeCA9ICd4IChUZW1wZXJhdHVyZSknLCB5ID0gJ3kgKFJlbnRhbHMpJykNCg0KZ2dwbG90bHkodHJhaW5fcGxvdCkNCmBgYA0KDQo8YnI+IFRoZSBsaW5lIHJlcHJlc2VudHMgYSBsaW5lYXIgZnVuY3Rpb24gdGhhdCBjYW4gYmUgdXNlZCB3aXRoIGFueSB2YWx1ZSBvZiAkeCQgdG8gYXBwbHkgdGhlICpzbG9wZSogb2YgdGhlIGxpbmUgYW5kIGl0cyAqaW50ZXJjZXB0KiAod2hlcmUgdGhlIGxpbmUgY3Jvc3NlcyB0aGUgeSBheGlzIHdoZW4gJHgkIGlzIDApIHRvIGNhbGN1bGF0ZSAkeSQuIEluIHRoaXMgY2FzZSwgaWYgd2UgZXh0ZW5kZWQgdGhlIGxpbmUgdG8gdGhlIGxlZnQgd2UnZCBmaW5kIHRoYXQgd2hlbiAkeCQgaXMgMCwgJHkkIGlzIGFyb3VuZCAyMCwgYW5kIHRoZSBzbG9wZSBvZiB0aGUgbGluZSBpcyBzdWNoIHRoYXQgZm9yIGVhY2ggdW5pdCBvZiAkeCQgeW91IG1vdmUgYWxvbmcgdG8gdGhlIHJpZ2h0LCAkeSQgaW5jcmVhc2VzIGJ5IGFyb3VuZCAxLjcuIE91ciAkZiQgZnVuY3Rpb24gdGhlcmVmb3JlIGNhbiBiZSBjYWxjdWxhdGVkIGFzOg0KDQokJA0KZih4KSBcID0gXCAyMFwgK1wgMS43eA0KJCQNCg0KTm93IHRoYXQgd2UndmUgZGVmaW5lZCBvdXJgcHJlZGljdGl2ZSBmdW5jdGlvbmAsIHdlIGNhbiB1c2UgaXQgdG8gYHByZWRpY3QgbGFiZWxzYCBmb3IgdGhlIGB2YWxpZGF0aW9uIGRhdGFgIHdlIGhlbGQgYmFjayBhbmQgY29tcGFyZSB0aGUgcHJlZGljdGVkIHZhbHVlcyAod2hpY2ggd2UgdHlwaWNhbGx5IGluZGljYXRlIHdpdGggdGhlIHN5bWJvbCAkXGhhdCB5JCwgb3IgInktaGF0Iikgd2l0aCB0aGUgYWN0dWFsIGtub3duICR5JCB2YWx1ZXMgKFJlbnRhbHMpLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBEaXNwbGF5IHRoZSB0ZXN0IHNldA0KZHVtbXlfdGVzdF9zZXQgJT4lIA0KICBtdXRhdGUoIiRcXGhhdHt5fSQiID0oMjArMS43KlRlbXBlcmF0dXJlKSkgJT4lDQogIGtibChhbGlnbiA9ICdsJykgJT4lIA0KICBrYWJsZV9tYXRlcmlhbF9kYXJrKCkNCmBgYA0KDQo8YnI+IExldCdzIHNlZSBob3cgdGhlJFwgeSQgYW5kICRcaGF0IHkkIHZhbHVlcyBjb21wYXJlIGluIGEgcGxvdDoNCg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgTWFrZSBwcmVkaWN0aW9ucyB1c2luZyB0aGUgbGluZWFyIG1vZGVsDQpkdW1teV9wcmVkIDwtIGR1bW15X3Rlc3Rfc2V0ICU+JSANCiAgbXV0YXRlKCIkXFxoYXR7eX0kIiA9KDIwKzEuNypUZW1wZXJhdHVyZSkpDQpjb2xvcnMgPC0gYygiQWN0dWFsIFJlbnRhbHMiID0gImJsdWUiLCAiUHJlZGljdGVkIFJlbnRhbHMiID0gInJlZCIpDQojIENvbXBhcmlzb24gcGxvdA0KZHVtbXlfcHJlZF9wbG90IDwtIGR1bW15X3ByZWQgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSBUZW1wZXJhdHVyZSwgeSA9IFJlbnRhbHMsIGNvbG9yID0gIkFjdHVhbCBSZW50YWxzIikpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gVGVtcGVyYXR1cmUsIHkgPSBgJFxcaGF0e3l9JGAsIGNvbG9yID0gIlByZWRpY3RlZCBSZW50YWxzIikpICsNCiAgZ2VvbV9zbW9vdGgoYWVzKHggPSBUZW1wZXJhdHVyZSwgeSA9IGAkXFxoYXR7eX0kYCksIG1ldGhvZCA9ICdsbScsc2UgPSBGLCBmdWxscmFuZ2UgPSBUUlVFLCBsaW5ldHlwZSA9IDMsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjIpICsNCiAgeGxpbSg1MCwgOTApKw0KICB5bGltKDgwLCAxNjApKw0KICBsYWJzKHggPSAneCAoVGVtcGVyYXR1cmUpJywgeSA9ICdSZW50YWxzJyApKw0KICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICIiLHZhbHVlcyA9IGNvbG9ycykNCg0KZ2dwbG90bHkoZHVtbXlfcHJlZF9wbG90KQ0KICANCmBgYA0KDQo8YnI+IFRoZSBwbG90dGVkIHBvaW50cyB0aGF0IGFyZSBvbiB0aGUgZnVuY3Rpb24gbGluZSBhcmUgdGhlIGBwcmVkaWN0ZWRgICRcaGF0IHkkIHZhbHVlcyBjYWxjdWxhdGVkIGJ5IHRoZSBmdW5jdGlvbiwgYW5kIHRoZSBvdGhlciBwbG90dGVkIHBvaW50cyBhcmUgdGhlIGBhY3R1YWxgICR5JCB2YWx1ZXMuDQoNClRoZXJlIGFyZSB2YXJpb3VzIHdheXMgd2UgY2FuIG1lYXN1cmUgdGhlIHZhcmlhbmNlIGJldHdlZW4gdGhlIHByZWRpY3RlZCBhbmQgYWN0dWFsIHZhbHVlcywgYW5kIHdlIGNhbiB1c2UgdGhlc2UgbWV0cmljcyB0byBldmFsdWF0ZSBob3cgd2VsbCB0aGUgbW9kZWwgcHJlZGljdHMuDQoNCj4gKk1hY2hpbmUgbGVhcm5pbmcgaXMgYmFzZWQgb24gc3RhdGlzdGljcyBhbmQgbWF0aCwgYW5kIGl0J3MgaW1wb3J0YW50IHRvIGJlIGF3YXJlIG9mIHNwZWNpZmljIHRlcm1zIHRoYXQgc3RhdGlzdGljaWFucyBhbmQgbWF0aGVtYXRpY2lhbnMgKGFuZCB0aGVyZWZvcmUgZGF0YSBzY2llbnRpc3RzKSB1c2UuIFlvdSBjYW4gdGhpbmsgb2YgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBhIHByZWRpY3RlZCBsYWJlbCB2YWx1ZSBhbmQgdGhlIGFjdHVhbCBsYWJlbCB2YWx1ZSBhcyBhIG1lYXN1cmUgb2YgKipgZXJyb3JgKiouIEhvd2V2ZXIsIGluIHByYWN0aWNlLCB0aGUgImFjdHVhbCIgdmFsdWVzIGFyZSBiYXNlZCBvbiBzYW1wbGUgb2JzZXJ2YXRpb25zICh3aGljaCB0aGVtc2VsdmVzIG1heSBiZSBzdWJqZWN0IHRvIHNvbWUgcmFuZG9tIHZhcmlhbmNlKS4gVG8gbWFrZSBpdCBjbGVhciB0aGF0IHdlJ3JlIGNvbXBhcmluZyBhIHByZWRpY3RlZCB2YWx1ZSAoKiRcaGF0IHkkKikgd2l0aCBhbiBvYnNlcnZlZCB2YWx1ZSAoKiR5JCopIHdlIHJlZmVyIHRvIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlbSBhcyB0aGUgKipgcmVzaWR1YWxzYCoqLiBXZSBjYW4gc3VtbWFyaXplIHRoZSByZXNpZHVhbHMgZm9yIGFsbCBvZiB0aGUgdmFsaWRhdGlvbiBkYXRhIHByZWRpY3Rpb25zIHRvIGNhbGN1bGF0ZSB0aGUgb3ZlcmFsbCAqKmxvc3MqKiBpbiB0aGUgbW9kZWwgYXMgYSBtZWFzdXJlIG9mIGl0cyBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlLioNCg0KT25lIG9mIHRoZSBtb3N0IGNvbW1vbiB3YXlzIHRvIG1lYXN1cmUgdGhlIGxvc3MgaXMgdG8gc3F1YXJlIHRoZSBpbmRpdmlkdWFsIHJlc2lkdWFscywgc3VtIHRoZSBzcXVhcmVzLCBhbmQgY2FsY3VsYXRlIHRoZSBtZWFuLiBTcXVhcmluZyB0aGUgcmVzaWR1YWxzIGhhcyB0aGUgZWZmZWN0IG9mIGJhc2luZyB0aGUgY2FsY3VsYXRpb24gb24gKmFic29sdXRlKiB2YWx1ZXMgKGlnbm9yaW5nIHdoZXRoZXIgdGhlIGRpZmZlcmVuY2UgaXMgbmVnYXRpdmUgb3IgcG9zaXRpdmUpIGFuZCBnaXZpbmcgbW9yZSB3ZWlnaHQgdG8gbGFyZ2VyIGRpZmZlcmVuY2VzLiBUaGlzIG1ldHJpYyBpcyBjYWxsZWQgdGhlICoqKmBNZWFuIFNxdWFyZWQgRXJyb3JgKioqLg0KDQokJE1TRT0gXGZyYWN7MX17bn0gIFxzdW1fe2k9MX1ee259ICh5X2kgLSBcaGF0IHlfaSleMiQkIEluIG90aGVyIHdvcmRzLCB0aGUgTVNFIGlzIHRoZSAqbWVhbiogJFxmcmFjezF9e259XHN1bV97aT0xfV57bn0kIG9mIHRoZSAqc3F1YXJlcyBvZiB0aGUgZXJyb3JzKiAkKHlfaSAtIFxoYXQgeV9pKV4yJA0KDQpGb3Igb3VyIHZhbGlkYXRpb24gZGF0YSwgdGhlIGNhbGN1bGF0aW9uIGxvb2tzIGxpa2UgdGhpczoNCg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgQ2FsY3VsYXRlIHJlc2lkdWFscyBhbmQgc3F1YXJlIG9mIHJlc2lkdWFscw0KZHVtbXlfbXNlIDwtIGR1bW15X3Rlc3Rfc2V0ICU+JSANCiAgbXV0YXRlKCIkXFxoYXR7eX0kIiA9KDIwKzEuNypUZW1wZXJhdHVyZSkpICU+JSANCiAgc2VsZWN0KC0xKSAlPiUgDQogIG11dGF0ZSgiJHkgLSBcXGhhdHt5fSQgIiA9ICguWzFdIC0gLlsyXSkpICU+JSANCiAgbXV0YXRlKCIkKHkgLSBcXGhhdHt5fSleMiQgIiA9ICguWzNdXjIpKSAlPiUgDQogIHJlbmFtZSgiJHkkIiA9IDEpDQoNCmR1bW15X21zZSAlPiUgDQogIGtibChhbGlnbiA9ICdsJykgJT4lIA0KICBrYWJsZV9tYXRlcmlhbF9kYXJrKCkNCmBgYA0KDQo8YnI+IEdyZWF0ISBMZXQncyBnbyBhaGVhZCBhbmQgZG8gc29tZSBzdW1tYXJ5IHN0YXRpc3RpY3Mgb24gY29sdW1uICQoeSAtIFxoYXQgeSleMiQgdG8gb2J0YWluIHRoZSB0b3RhbCBzdW1tYXRpb24gYW5kIHRoZSBtZWFuOg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDYWxjdWxhdGUgbXNlDQpkdW1teV9tc2UgJT4lDQogIHNlbGVjdCg0KSAlPiUgDQogIHN1bW1hcmlzZSgiJFxcc3VtKHkgLSBcXGhhdHt5fSleMiQgIiA9IGNvbFN1bXMoLiksICIkTVNFJCIgPSByb3VuZChjb2xNZWFucyguKSwgMikpICU+JSANCiAga2JsKGFsaWduID0gJ2wnKSAlPiUgDQogIGthYmxlX21hdGVyaWFsX2RhcmsoKQ0KYGBgDQoNCjxicj4gU28gdGhlIGxvc3MgZm9yIG91ciBtb2RlbCBiYXNlZCBvbiB0aGUgYE1TRWAgbWV0cmljIGlzIGA5Ljc5YC4NCg0KU28gaXMgdGhhdCBhbnkgZ29vZD8g8J+ktyBJdCdzIGRpZmZpY3VsdCB0byB0ZWxsIGJlY2F1c2UgTVNFIHZhbHVlIGlzIG5vdCBleHByZXNzZWQgaW4gYSBtZWFuaW5nZnVsIHVuaXQgb2YgbWVhc3VyZW1lbnQuDQoNCkhvd2V2ZXIsIHdlIGRvIGtub3cgdGhhdCB0aGUgYGxvd2VyYCB0aGUgdmFsdWUgaXMsIHRoZSBgbGVzcyBsb3NzYCB0aGVyZSBpcyBpbiB0aGUgbW9kZWw7IGFuZCB0aGVyZWZvcmUsIHRoZSBiZXR0ZXIgaXQgaXMgcHJlZGljdGluZy4gVGhpcyBtYWtlcyBpdCBhIHVzZWZ1bCBtZXRyaWMgdG8gY29tcGFyZSB0d28gbW9kZWxzIGFuZCBmaW5kIHRoZSBvbmUgdGhhdCBwZXJmb3JtcyBiZXN0Lg0KDQpTb21ldGltZXMsIGl0J3MgbW9yZSB1c2VmdWwgdG8gZXhwcmVzcyB0aGUgbG9zcyBpbiB0aGUgYHNhbWUgdW5pdCBvZiBtZWFzdXJlbWVudCBhcyB0aGUgcHJlZGljdGVkIGxhYmVsIHZhbHVlIGl0c2VsZmAgLSBpbiB0aGlzIGNhc2UsIHRoZSBudW1iZXIgb2YgcmVudGFscy4gSXQncyBwb3NzaWJsZSB0byBkbyB0aGlzIGJ5IGNhbGN1bGF0aW5nIHRoZSBgc3F1YXJlIHJvb3Qgb2YgdGhlIE1TRWAsIHdoaWNoIHByb2R1Y2VzIGEgbWV0cmljIGtub3duLCB1bnN1cnByaXNpbmdseSwgYXMgdGhlICoqKmBSb290IE1lYW4gU3F1YXJlZCBFcnJvcmAqKiogKFJNU0UpLg0KDQokJFJNU0UgPSBcc3FydHs5Ljc5fSA9IDMuMTMgJCQNCg0KU28gb3VyIG1vZGVsJ3MgUk1TRSBpbmRpY2F0ZXMgdGhhdCB0aGUgbG9zcyBpcyBqdXN0IG92ZXIgMywgd2hpY2ggeW91IGNhbiBpbnRlcnByZXQgbG9vc2VseSBhcyBtZWFuaW5nIHRoYXQgb24gYXZlcmFnZSwgaW5jb3JyZWN0IHByZWRpY3Rpb25zIGFyZSB3cm9uZyBieSBhcm91bmQgMyByZW50YWxzLg0KDQpUaGVyZSBhcmUgbWFueSBvdGhlciBtZXRyaWNzIHRoYXQgY2FuIGJlIHVzZWQgdG8gbWVhc3VyZSBsb3NzIGluIGEgcmVncmVzc2lvbi4gRm9yIGV4YW1wbGUsICRSXjIkICoqYChSLVNxdWFyZWQpYCoqIChzb21ldGltZXMga25vd24gYXMgKmNvZWZmaWNpZW50IG9mIGRldGVybWluYXRpb24qKSBpcyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiAkeCQgYW5kICR5JCBzcXVhcmVkLiBUaGlzIHByb2R1Y2VzIGEgdmFsdWUgYmV0d2VlbiAwIGFuZCAxIHRoYXQgbWVhc3VyZXMgdGhlIGFtb3VudCBvZiBgdmFyaWFuY2UgdGhhdCBjYW4gYmUgZXhwbGFpbmVkIGJ5IHRoZSBtb2RlbGAuDQoNCkFuIGludGVyaW9yIHZhbHVlIHN1Y2ggYXMgJFJeMiA9IDAuNyQgbWF5IGJlIGxvb3NlbHkgaW50ZXJwcmV0ZWQgYXMgZm9sbG93czogIlNldmVudHkgcGVyY2VudCBvZiB0aGUgdmFyaWFuY2UgaW4gdGhlIHByZWRpY3RlZCBsYWJlbCBjYW4gYmUgZXhwbGFpbmVkIGJ5IHRoZSBwcmVkaWN0aW5nIGZlYXR1cmVzLiBUaGUgcmVtYWluaW5nIHRoaXJ0eSBwZXJjZW50IGNhbiBiZSBhdHRyaWJ1dGVkIHRvIHVua25vd24sIFtsdXJraW5nIHZhcmlhYmxlc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTHVya2luZ192YXJpYWJsZSAiTHVya2luZyB2YXJpYWJsZSIpIG9yIGluaGVyZW50IHZhcmlhYmlsaXR5LiIgR2VuZXJhbGx5LCB0aGUgY2xvc2VyIHRoZSAkUl4yJCB2YWx1ZSBpcyB0byAxLCB0aGUgYmV0dGVyIHRoZSBtb2RlbCBwcmVkaWN0cy4NCg0KIyMgKipFeGVyY2lzZSAtIFRyYWluIGFuZCBldmFsdWF0ZSBhIHJlZ3Jlc3Npb24gbW9kZWw6KioNCg0KQ2FsY3VsYXRpbmcgYSByZWdyZXNzaW9uIGxpbmUgZm9yIGEgc2ltcGxlIGJpbm9taWFsICh0d28tdmFyaWFibGUpIGZ1bmN0aW9uIGZyb20gZmlyc3QgcHJpbmNpcGxlcyBpcyBwb3NzaWJsZSwgYnV0IGludm9sdmVzIHNvbWUgbWF0aGVtYXRpY2FsIGVmZm9ydC4gV2hlbiB5b3UgY29uc2lkZXIgYSBgcmVhbC13b3JsZCBkYXRhc2V0YCBpbiB3aGljaCAkeCQgaXMgbm90IGEgc2luZ2xlIGZlYXR1cmUgdmFsdWUgc3VjaCBhcyB0ZW1wZXJhdHVyZSwgYnV0IGEgdmVjdG9yIG9mIGBtdWx0aXBsZSB2YXJpYWJsZXNgIHN1Y2ggYXMgdGVtcGVyYXR1cmUsIGRheSBvZiB3ZWVrLCBtb250aCwgcmFpbmZhbGwsIGFuZCBzbyBvbjsgdGhlIGNhbGN1bGF0aW9ucyBiZWNvbWUgbW9yZSBjb21wbGV4Lg0KDQpGb3IgdGhpcyByZWFzb24sIGRhdGEgc2NpZW50aXN0cyBnZW5lcmFsbHkgdXNlIHNwZWNpYWxpemVkIG1hY2hpbmUgbGVhcm5pbmcgZnJhbWV3b3JrcyB0byBwZXJmb3JtIG1vZGVsIHRyYWluaW5nIGFuZCBldmFsdWF0aW9uLiBTdWNoIGZyYW1ld29ya3MgZW5jYXBzdWxhdGUgY29tbW9uIGFsZ29yaXRobXMgYW5kIHByb3ZpZGUgdXNlZnVsIGZ1bmN0aW9ucyBmb3IgcHJlcGFyaW5nIGRhdGEsIGZpdHRpbmcgZGF0YSB0byBhIG1vZGVsLCBhbmQgY2FsY3VsYXRpbmcgbW9kZWwgZXZhbHVhdGlvbiBtZXRyaWNzLg0KDQpPbmUgY29tbW9ubHkgdXNlZCBtYWNoaW5lIGxlYXJuaW5nIGZyYW1ld29yayBpbiBSLCBpcyB0aGUgWyoqYHRpZHltb2RlbHNgKipdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnLyksIGEgY29sbGVjdGlvbiBvZiBwYWNrYWdlcyBmb3IgbW9kZWxpbmcgYW5kIG1hY2hpbmUgbGVhcm5pbmcgdXNpbmcgWyoqdGlkeXZlcnNlKipdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKSBwcmluY2lwbGVzLg0KDQotICAgSW5zdGFsbCBtYW55IG9mIHRoZSBwYWNrYWdlcyBpbiB0aGUgdGlkeW1vZGVscyBlY29zeXN0ZW0gYnkgcnVubmluZyBgaW5zdGFsbC5wYWNrYWdlcygidGlkeW1vZGVscyIpYC4NCg0KLSAgIFJ1biBgbGlicmFyeSh0aWR5bW9kZWxzKWAgdG8gbG9hZCB0aGUgY29yZSBwYWNrYWdlcyBhbmQgbWFrZSB0aGVtIGF2YWlsYWJsZSBpbiB5b3VyIGN1cnJlbnQgUiBzZXNzaW9uLg0KDQpGcm9tIHRoaXMgc2VjdGlvbiBmb3J3YXJkLCB3ZSdsbCBleHBsb3JlIGEgaGFuZHMtb24gZXhlcmNpc2Ugd2hlcmUgd2UnbGwgdXNlIHRpZHltb2RlbHMgdG8gdHJhaW4gYW5kIGV2YWx1YXRlIHJlZ3Jlc3Npb24gbW9kZWxzIHVzaW5nIGFuIGV4YW1wbGUgYmFzZWQgb24gYSByZWFsIHN0dWR5IGluIHdoaWNoIGRhdGEgZm9yIGEgYmljeWNsZSBzaGFyaW5nIHNjaGVtZSB3YXMgY29sbGVjdGVkIGFuZCB1c2VkIHRvIHByZWRpY3QgdGhlIG51bWJlciBvZiByZW50YWxzIGJhc2VkIG9uIHNlYXNvbmFsaXR5IGFuZCB3ZWF0aGVyIGNvbmRpdGlvbnMuIFdlJ2xsIHVzZSBhIHNpbXBsaWZpZWQgdmVyc2lvbiBvZiB0aGUgZGF0YXNldCBmcm9tIHRoYXQgc3R1ZHkuDQoNCj4gKipDaXRhdGlvbioqOiBUaGUgZGF0YSB1c2VkIGluIHRoaXMgZXhlcmNpc2UgaXMgZGVyaXZlZCBmcm9tIFtDYXBpdGFsIEJpa2VzaGFyZV0oaHR0cHM6Ly93d3cuY2FwaXRhbGJpa2VzaGFyZS5jb20vc3lzdGVtLWRhdGEpIGFuZCBpcyB1c2VkIGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgcHVibGlzaGVkIFtsaWNlbnNlIGFncmVlbWVudF0oaHR0cHM6Ly93d3cuY2FwaXRhbGJpa2VzaGFyZS5jb20vZGF0YS1saWNlbnNlLWFncmVlbWVudCkuDQoNCkxldCdzIGdldCByaWdodCBpbnRvIGl0IPCfmoAg4oCL8J+MjOKAi+KAiyENCg0KIyMgMS4gRXhwbG9yZSB0aGUgRGF0YSDwn5W177iP4oCN77iPDQoNClRoZSBmaXJzdCBzdGVwIGluIGFueSBtYWNoaW5lIGxlYXJuaW5nIHByb2plY3QgaXMgdG8gYGV4cGxvcmUgdGhlIGRhdGFgIHRoYXQgeW91IHdpbGwgdXNlIHRvIHRyYWluIGEgbW9kZWwuIFRoZSBnb2FsIG9mIHRoaXMgZXhwbG9yYXRpb24gaXMgdG8gdHJ5IHRvIHVuZGVyc3RhbmQgdGhlIGByZWxhdGlvbnNoaXBzYCBiZXR3ZWVuIGl0cyBhdHRyaWJ1dGVzOyBpbiBwYXJ0aWN1bGFyLCBhbnkgYXBwYXJlbnQgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgKmZlYXR1cmVzKiBhbmQgdGhlICpsYWJlbCogeW91ciBtb2RlbCB3aWxsIHRyeSB0byBwcmVkaWN0Lg0KDQpUaGlzIG1heSByZXF1aXJlIHNvbWUgd29yayB0byBkZXRlY3QgYW5kIGBmaXggaXNzdWVzIGluIHRoZSBkYXRhYCAoc3VjaCBhcyBkZWFsaW5nIHdpdGggbWlzc2luZyB2YWx1ZXMsIGVycm9ycywgb3Igb3V0bGllciB2YWx1ZXMpLCBgZGVyaXZpbmcgbmV3IGZlYXR1cmUgY29sdW1uc2AgYnkgdHJhbnNmb3JtaW5nIG9yIGNvbWJpbmluZyBleGlzdGluZyBmZWF0dXJlcyAoYSBwcm9jZXNzIGtub3duIGFzICpmZWF0dXJlIGVuZ2luZWVyaW5nKiksIGBub3JtYWxpemluZ2AgbnVtZXJpYyBmZWF0dXJlcyAodmFsdWVzIHlvdSBjYW4gbWVhc3VyZSBvciBjb3VudCkgc28gdGhleSdyZSBvbiBhIHNpbWlsYXIgc2NhbGUsIGFuZCBgZW5jb2RpbmcgY2F0ZWdvcmljYWwgZmVhdHVyZXNgICh2YWx1ZXMgdGhhdCByZXByZXNlbnQgZGlzY3JldGUgY2F0ZWdvcmllcykgYXMgbnVtZXJpYyBpbmRpY2F0b3JzLg0KDQpUbyBhY2hpZXZlIHRoaXMgaW1wb3J0YW50IHN0ZXAgaW4gb3VyIGFkdmVudHVyZSwgd2UnbGwgbmVlZCBzb21lIHBhY2thZ2VzIGluIHRoZSB0aWR5dmVyc2UhIFRoZSB0aWR5dmVyc2UgaXMgYW4gb3BpbmlvbmF0ZWQgWyoqY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzKipdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvcGFja2FnZXMpIGRlc2lnbmVkIGZvciBkYXRhIHNjaWVuY2UuDQoNCldlJ2xsIGJlZ2luIGJ5IGxvYWRpbmcgdGhlIGRhdGEgZnJhbWUgZnJvbSB0aGUgW29yaWdpbmFsIHJlcG9dKGh0dHBzOi8vZ2l0aHViLmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcykgb2YgdGhpcyBjb3Vyc2UgYW5kIHRoZW4gYOKApmAgdGhlIHJlYWwgZnVuIGJlZ2lucyENCg0KYGBge3IgcmVhZF91cmwsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBleGVyY2lzZS5zZXR1cCA9ICJzZXR1cEEifQ0KIyBMb2FkIHRoZSBjb3JlIHRpZHl2ZXJzZSBhbmQgbWFrZSBpdCBhdmFpbGFibGUgaW4geW91ciBjdXJyZW50IFIgc2Vzc2lvbi4NCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQojIEdldCB0aGUgdXJsIG9mIHRoZSByYXcgdmVyc2lvbiBvZiB0aGUgZGFpbHktYmlrZS1zaGFyZS5jc3YgZGF0YQ0KdXJsX2RhdGEgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcy9tYXN0ZXIvZGF0YS9kYWlseS1iaWtlLXNoYXJlLmNzdiINCg0KIyBMb2FkIHRoZSBkYXRhIGludG8gdGhlIFIgc2Vzc2lvbg0KYmlrZV9kYXRhIDwtIHJlYWRfY3N2KHVybF9kYXRhKSAlPiUgDQogICMgdHVybiBleGlzdGluZyBvYmplY3QgaW50byBhIHRpYmJsZQ0KICBhc190aWJibGUoKQ0KDQojIFZpZXcgZmlyc3QgZmV3IHJvd3MNCmJpa2VfZGF0YSAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDcpDQoNCmBgYA0KDQpTb21ldGltZXMsIHdlIG1heSB3YW50IHNvbWUgbGl0dGxlIG1vcmUgaW5mb3JtYXRpb24gb24gb3VyIGRhdGEuIFdlIGNhbiBoYXZlIGEgbG9vayBhdCB0aGUgYGRhdGFgIGFuZGBpdHMgc3RydWN0dXJlYCBieSB1c2luZyB0aGUgWypnbGltcHNlKCkqXShodHRwczovL3BpbGxhci5yLWxpYi5vcmcvcmVmZXJlbmNlL2dsaW1wc2UuaHRtbCkgZnVuY3Rpb24uDQoNCmBgYHtyIGdsaW1wc2UsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBUYWtlIGEgcXVpY2sgZ2xhbmNlIGF0IHRoZSBkYXRhDQpnbGltcHNlKGJpa2VfZGF0YSkNCg0KYGBgDQoNCkdvb2Qgam9iIfCfkqoNCg0KV2UgY2FuIG9ic2VydmUgdGhhdCBgZ2xpbXBzZSgpYCB3aWxsIGdpdmUgeW91IHRoZSB0b3RhbCBudW1iZXIgb2Ygcm93cyAob2JzZXJ2YXRpb25zKSBhbmQgY29sdW1ucyAodmFyaWFibGVzKSwgdGhlbiwgdGhlIGZpcnN0IGZldyBlbnRyaWVzIG9mIGVhY2ggdmFyaWFibGUgaW4gYSByb3cgYWZ0ZXIgdGhlIHZhcmlhYmxlIG5hbWUuIEluIGFkZGl0aW9uLCB0aGUgKmRhdGEgdHlwZSogb2YgdGhlIHZhcmlhYmxlIGlzIGdpdmVuIGltbWVkaWF0ZWx5IGFmdGVyIGVhY2ggdmFyaWFibGUncyBuYW1lIGluc2lkZSBgPCA+YC4NCg0KVGhlIGRhdGEgY29uc2lzdHMgb2YgKjczMSByb3dzKiB0aGUgZm9sbG93aW5nICoxNCBjb2x1bW5zKjoNCg0KLSAgICoqaW5zdGFudCoqOiBBIHVuaXF1ZSByb3cgaWRlbnRpZmllcg0KDQotICAgKipkdGVkYXkqKjogVGhlIGRhdGUgb24gd2hpY2ggdGhlIGRhdGEgd2FzIG9ic2VydmVkIC0gaW4gdGhpcyBjYXNlLCB0aGUgZGF0YSB3YXMgY29sbGVjdGVkIGRhaWx5OyBzbyB0aGVyZSdzIG9uZSByb3cgcGVyIGRhdGUuDQoNCi0gICAqKnNlYXNvbioqOiBBIG51bWVyaWNhbGx5IGVuY29kZWQgdmFsdWUgaW5kaWNhdGluZyB0aGUgc2Vhc29uICgxLXNwcmluZywgMi1zdW1tZXIsIDMtZmFsbCwgNC13aW50ZXIpDQoNCi0gICAqKnlyKio6IFRoZSB5ZWFyIG9mIHRoZSBzdHVkeSBpbiB3aGljaCB0aGUgb2JzZXJ2YXRpb24gd2FzIG1hZGUgKHRoZSBzdHVkeSB0b29rIHBsYWNlIG92ZXIgdHdvIHllYXJzICh5ZWFyIDAgcmVwcmVzZW50cyAyMDExLCBhbmQgeWVhciAxIHJlcHJlc2VudHMgMjAxMikNCg0KLSAgICoqbW50aCoqOiBUaGUgY2FsZW5kYXIgbW9udGggaW4gd2hpY2ggdGhlIG9ic2VydmF0aW9uIHdhcyBtYWRlICgxLUphbnVhcnkgLi4uIDEyLURlY2VtYmVyKQ0KDQotICAgKipob2xpZGF5Kio6IEEgYmluYXJ5IHZhbHVlIGluZGljYXRpbmcgd2hldGhlciBvciBub3QgdGhlIG9ic2VydmF0aW9uIHdhcyBtYWRlIG9uIGEgcHVibGljIGhvbGlkYXkpDQoNCi0gICAqKndlZWtkYXkqKjogVGhlIGRheSBvZiB0aGUgd2VlayBvbiB3aGljaCB0aGUgb2JzZXJ2YXRpb24gd2FzIG1hZGUgKDAtU3VuZGF5IC4uLiA2LVNhdHVyZGF5KQ0KDQotICAgKip3b3JraW5nZGF5Kio6IEEgYmluYXJ5IHZhbHVlIGluZGljYXRpbmcgd2hldGhlciBvciBub3QgdGhlIGRheSBpcyBhIHdvcmtpbmcgZGF5IChub3QgYSB3ZWVrZW5kIG9yIGhvbGlkYXkpDQoNCi0gICAqKndlYXRoZXJzaXQqKjogQSBjYXRlZ29yaWNhbCB2YWx1ZSBpbmRpY2F0aW5nIHRoZSB3ZWF0aGVyIHNpdHVhdGlvbiAoMS1jbGVhciwgMi1taXN0L2Nsb3VkLCAzLWxpZ2h0IHJhaW4vc25vdywgNC1oZWF2eSByYWluL2hhaWwvc25vdy9mb2cpDQoNCi0gICAqKnRlbXAqKjogVGhlIHRlbXBlcmF0dXJlIGluIGNlbHNpdXMgKG5vcm1hbGl6ZWQpDQoNCi0gICAqKmF0ZW1wKio6IFRoZSBhcHBhcmVudCAoImZlZWxzLWxpa2UiKSB0ZW1wZXJhdHVyZSBpbiBjZWxzaXVzIChub3JtYWxpemVkKQ0KDQotICAgKipodW0qKjogVGhlIGh1bWlkaXR5IGxldmVsIChub3JtYWxpemVkKQ0KDQotICAgKip3aW5kc3BlZWQqKjogVGhlIHdpbmRzcGVlZCAobm9ybWFsaXplZCkNCg0KLSAgICoqcmVudGFscyoqOiBUaGUgbnVtYmVyIG9mIGJpY3ljbGUgcmVudGFscyByZWNvcmRlZC4NCg0KSW4gdGhpcyBkYXRhc2V0LCBgcmVudGFsc2AgcmVwcmVzZW50cyB0aGUgYGxhYmVsYCAodGhlICR5JCB2YWx1ZSkgb3VyIG1vZGVsIG11c3QgYmUgdHJhaW5lZCB0byBwcmVkaWN0LiBUaGUgb3RoZXIgY29sdW1ucyBhcmUgcG90ZW50aWFsIGZlYXR1cmVzICgkeCQgdmFsdWVzKS4NCg0KQXMgbWVudGlvbmVkIHByZXZpb3VzbHksIHlvdSBjYW4gcGVyZm9ybSBzb21lICpmZWF0dXJlIGVuZ2luZWVyaW5nKiB0byBjb21iaW5lIG9yIGRlcml2ZSBuZXcgZmVhdHVyZXMuIEZvciBleGFtcGxlLCBsZXQncyBhZGQgYSBuZXcgY29sdW1uIG5hbWVkICoqZGF5KiogdG8gdGhlIGRhdGEgZnJhbWUgYnkgZXh0cmFjdGluZyB0aGUgZGF5IGNvbXBvbmVudCBmcm9tIHRoZSBleGlzdGluZyAqKmR0ZWRheSoqIGNvbHVtbi4gVGhlIG5ldyBjb2x1bW4gcmVwcmVzZW50cyB0aGUgZGF5IG9mIHRoZSBtb250aCBmcm9tIDEgdG8gMzEuDQoNCkZyb20gdGhlIG91dHB1dCBvZiAqZ2xpbXBzZSgpLCogeW91J2xsIHJlYWxpemUgdGhhdCB0aGUgKipkdGVkYXkqKiBjb2x1bW4gaXMgc3RvcmVkIGFzIGEgYCJjaGFyYWN0ZXIiYCB2ZWN0b3IuIFNvLCB3ZSdsbCBmaXJzdCBuZWVkIHRvIHRyYW5zZm9ybSB0aGlzIHRvIGEgZGF0ZSBvYmplY3QuDQoNCltMdWJyaWRhdGVdKGh0dHBzOi8vbHVicmlkYXRlLnRpZHl2ZXJzZS5vcmcvKSwgYSBwYWNrYWdlIGluIHRoZSB0aWR5dmVyc2UsIHByb3ZpZGVzIHRvb2xzIHRoYXQgbWFrZSBpdCBlYXNpZXIgdG8gcGFyc2UgYW5kIG1hbmlwdWxhdGUgZGF0ZXMuDQoNCj4gKklmIHlvdSBhcmUgbmV3IHRvIGx1YnJpZGF0ZSwgdGhlIGJlc3QgcGxhY2UgdG8gc3RhcnQgaXMgdGhlIFtkYXRlIGFuZCB0aW1lcyBjaGFwdGVyXShodHRwOi8vcjRkcy5oYWQuY28ubnovZGF0ZXMtYW5kLXRpbWVzLmh0bWwpIGluIFIgZm9yIGRhdGEgc2NpZW5jZS4qDQoNCiFbQXJ0d29yayBieSBcQGFsbGlzb25faG9yc3RdKGltYWdlcy9sdWJyaWRhdGUuanBnKXt3aWR0aD0iNDUwIn0NCg0KYGBge3IgZGF0ZXNfZGF5cywgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIGxvYWQgbHVicmlkYXRlIGludG8gdGhlIFIgc2Vzc2lvbg0KbGlicmFyeShsdWJyaWRhdGUpDQoNCiMgUGFyc2UgZGF0ZXMgdGhlbiBleHRyYWN0IGRheXMNCmJpa2VfZGF0YSA8LSBiaWtlX2RhdGEgJT4lDQogICMgUGFyc2UgZGF0ZXMNCiAgbXV0YXRlKGR0ZWRheSA9IG1keShkdGVkYXkpKSAlPiUgDQogICNHZXQgZGF5DQogIG11dGF0ZShkYXkgPSBkYXkoZHRlZGF5KSkNCg0KIyBleHRyYWN0IHRoZSBmaXJzdCAxMCByb3dzDQpiaWtlX2RhdGEgJT4lIA0KICBzbGljZV9oZWFkKG4gPSAxMCkNCiAgDQpgYGANCg0KPGJyPiBTdWNoIGEgYmVhdXR5IfCfpKkNCg0KT0ssIGxldCdzIHN0YXJ0IG91ciBhbmFseXNpcyBvZiB0aGUgZGF0YSBieSBleGFtaW5pbmcgYSBmZXcga2V5IGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MuIFdlIGNhbiB1c2UgdGhlIGBzdW1tYXJ5dG9vbHM6OmRlc2NyKClgIGZ1bmN0aW9uIHRvIG5lYXRseSBhbmQgcXVpY2tseSBzdW1tYXJpemUgdGhlIG51bWVyaWMgZmVhdHVyZXMgYXMgd2VsbCBhcyB0aGUgKipyZW50YWxzKiogbGFiZWwgY29sdW1uLg0KDQpgYGB7ciBzdW1tYXJ5LCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgbG9hZCBwYWNrYWdlIGludG8gdGhlIFIgc2Vzc2lvbg0KbGlicmFyeShzdW1tYXJ5dG9vbHMpDQoNCiMgT2J0YWluIHN1bW1hcnkgc3RhdHMgZm9yIGZlYXR1cmUgYW5kIGxhYmVsIGNvbHVtbnMNCmJpa2VfZGF0YSAlPiUgDQogICMgU2VsZWN0IGZlYXR1cmVzIGFuZCBsYWJlbA0KICBzZWxlY3QoYyh0ZW1wLCBhdGVtcCwgaHVtLCB3aW5kc3BlZWQsIHJlbnRhbHMpKSAlPiUgDQogICMgU3VtbWFyeSBzdGF0cw0KICBkZXNjcihvcmRlciA9ICJwcmVzZXJ2ZSIsDQogICAgICAgIHN0YXRzID0gYygnbWVhbicsICdzZCcsICdtaW4nLCAncTEnLCAnbWVkJywgJ3EzJywgJ21heCcpLA0KICAgICAgICByb3VuZC5kaWdpdHMgPSA2KQ0KDQpgYGANCg0KVGhlIHN0YXRpc3RpY3MgcmV2ZWFsIHNvbWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGF0YSBpbiBlYWNoIG9mIHRoZSBudW1lcmljIGZpZWxkcywgaW5jbHVkaW5nIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zICh0aGVyZSBhcmUgNzMxIHJlY29yZHMpLCB0aGUgbWVhbiwgc3RhbmRhcmQgZGV2aWF0aW9uLCBtaW5pbXVtIGFuZCBtYXhpbXVtIHZhbHVlcywgYW5kIHRoZSBxdWFydGlsZSB2YWx1ZXMgKHRoZSB0aHJlc2hvbGQgdmFsdWVzIGZvciAyNSUsIDUwJSAtIHdoaWNoIGlzIGFsc28gdGhlIG1lZGlhbiwgYW5kIDc1JSBvZiB0aGUgZGF0YSkuDQoNCkZyb20gdGhpcywgd2UgY2FuIHNlZSB0aGF0IHRoZSBtZWFuIG51bWJlciBvZiBkYWlseSByZW50YWxzIGlzIGFyb3VuZCAqODQ4KjsgYnV0IHRoZXJlJ3MgYSBjb21wYXJhdGl2ZWx5IGBsYXJnZSBzdGFuZGFyZCBkZXZpYXRpb25gLCBpbmRpY2F0aW5nIGBhIGxvdCBvZiB2YXJpYW5jZWAgaW4gdGhlIG51bWJlciBvZiByZW50YWxzIHBlciBkYXkuDQoNCldlIG1pZ2h0IGdldCBhIGNsZWFyZXIgaWRlYSBvZiB0aGUgZGlzdHJpYnV0aW9uIG9mIHJlbnRhbHMgdmFsdWVzIGJ5IHZpc3VhbGl6aW5nIHRoZSBkYXRhLiBDb21tb24gcGxvdCB0eXBlcyBmb3IgdmlzdWFsaXppbmcgbnVtZXJpYyBkYXRhIGRpc3RyaWJ1dGlvbnMgYXJlICpoaXN0b2dyYW1zKiBhbmQgKmJveCBwbG90cyosIHNvIGxldCdzIGdldCBvdXIgW2BnZ3Bsb3QyYF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSBvbiBhbmQgY3JlYXRlIG9uZSBvZiBlYWNoIG9mIHRoZXNlIGZvciB0aGUgKipyZW50YWxzKiogY29sdW1uLiBbcGF0Y2h3b3JrXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKSBleHRlbmRzIGBnZ3Bsb3RgIEFQSSBieSBwcm92aWRpbmcgbWF0aGVtYXRpY2FsIG9wZXJhdG9ycyAoc3VjaCBhcyBgK2Agb3IgYC9gKSBmb3IgY29tYmluaW5nIG11bHRpcGxlIHBsb3RzLiBZZXMsIGFzIGVhc3kgYXMgdGhhdCEg8J+TiPCfk4kNCg0KPiAqSW4gY2FzZSB5b3UgYXJlIG5ldyB0byBSLCBSIGhhcyBzZXZlcmFsIHN5c3RlbXMgZm9yIG1ha2luZyBncmFwaHMsIGJ1dCBgZ2dwbG90MmAgaXMgb25lIG9mIHRoZSBtb3N0IGVsZWdhbnQgYW5kIG1vc3QgdmVyc2F0aWxlLiBUaGUgYmVzdCBwbGFjZSB0byBzdGFydCBpbiBjcmVhdGluZyBtYXN0ZXJwaWVjZSB2aXN1YWxpc2F0aW9ucyBpcyB0aGUgW0RhdGEgVmlzdWFsaXNhdGlvbiBjaGFwdGVyXShodHRwczovL3I0ZHMuaGFkLmNvLm56L2RhdGEtdmlzdWFsaXNhdGlvbi5odG1sKSBpbiBSIGZvciBkYXRhIHNjaWVuY2UqDQoNCmBgYHtyIHBsdF9yZW50YWxzLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCmxpYnJhcnkocGF0Y2h3b3JrKQ0KIyBQbG90IGEgaGlzdG9ncmFtDQp0aGVtZV9zZXQodGhlbWVfbGlnaHQoKSkNCmhpc3RfcGx0IDwtIGJpa2VfZGF0YSAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSByZW50YWxzKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCwgZmlsbCA9ICJtaWRuaWdodGJsdWUiLCBhbHBoYSA9IDAuNykgKw0KICANCiAgIyBBZGQgbGluZXMgZm9yIG1lYW4gYW5kIG1lZGlhbg0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbihyZW50YWxzKSwgY29sb3IgPSAnTWVhbicpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMS4zKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWRpYW4ocmVudGFscyksIGNvbG9yID0gJ01lZGlhbicpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMS4zICkgKw0KICB4bGFiKCIiKSArDQogIHlsYWIoIkZyZXF1ZW5jeSIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gYyhNZWFuID0gInJlZCIsIE1lZGlhbiA9ICJ5ZWxsb3ciKSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC45KSwgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpDQoNCiMgUGxvdCBhIGJveCBwbG90DQpib3hfcGx0IDwtIGJpa2VfZGF0YSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJlbnRhbHMsIHkgPSAxKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjRTY5RjAwIiwgY29sb3IgPSAiZ3JheTIzIiwgYWxwaGEgPSAwLjcpICsNCiAgICAjIEFkZCB0aXRsZXMgYW5kIGxhYmVscw0KICB4bGFiKCJSZW50YWxzIikrDQogIHlsYWIoIiIpDQoNCg0KIyBDb21iaW5lIHBsb3RzDQooaGlzdF9wbHQgLyBib3hfcGx0KSArDQogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICdSZW50YWwgRGlzdHJpYnV0aW9uJywNCiAgICAgICAgICAgICAgICAgIHRoZW1lID0gdGhlbWUoDQogICAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSkNCiAgDQpgYGANCg0KPGJyPiBUaGUgcGxvdHMgc2hvdyB0aGF0IHRoZSBudW1iZXIgb2YgZGFpbHkgcmVudGFscyByYW5nZXMgZnJvbSAwIHRvIGp1c3Qgb3ZlciAzLDQwMC4gSG93ZXZlciwgdGhlIG1lYW4gKGFuZCBtZWRpYW4pIG51bWJlciBvZiBkYWlseSByZW50YWxzIGlzIGNsb3NlciB0byB0aGUgbG93IGVuZCBvZiB0aGF0IHJhbmdlLCB3aXRoIG1vc3Qgb2YgdGhlIGRhdGEgYmV0d2VlbiAwIGFuZCBhcm91bmQgMiwyMDAgcmVudGFscy4gVGhlIGZldyB2YWx1ZXMgYWJvdmUgdGhpcyBhcmUgc2hvd24gaW4gdGhlIGJveCBwbG90IGFzIHNtYWxsIGNpcmNsZXMsIGluZGljYXRpbmcgdGhhdCB0aGV5IGFyZSAqb3V0bGllcnMqIC0gaW4gb3RoZXIgd29yZHMsIHVudXN1YWxseSBoaWdoIG9yIGxvdyB2YWx1ZXMgYmV5b25kIHRoZSB0eXBpY2FsIHJhbmdlIG9mIG1vc3Qgb2YgdGhlIGRhdGEuDQoNCldlIGNhbiBkbyB0aGUgc2FtZSBraW5kIG9mIHZpc3VhbCBleHBsb3JhdGlvbiBvZiB0aGUgbnVtZXJpYyBmZWF0dXJlcy4gT25lIHdheSB0byBkbyB0aGlzIHdvdWxkIGJlIHRvIHVzZSBhIGBmb3IgbG9vcGAgYnV0IGdncGxvdDIgcHJvdmlkZXMgYSB3YXkgb2YgYXZvaWRpbmcgdGhpcyBlbnRpcmVseSB1c2luZyBgZmFjZXRzYCDwn5KBLiBGYWNldHMgYWxsb3cgdXMgdG8gY3JlYXRlIHN1YnBsb3RzIHRoYXQgZWFjaCBkaXNwbGF5IG9uZSBzdWJzZXQgb2YgdGhlIGRhdGEuDQoNClRoaXMgd2lsbCByZXF1aXJlIHVzIHRvIHRyYW5zZm9ybSBvdXIgZGF0YSBpbnRvIGEgKmxvbmcqICpmb3JtYXQqIHVzaW5nIFt0aWR5cjo6cGl2b3RfbG9uZ2VyXShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3Bpdm90X2xvbmdlci5odG1sKSwgY2FsY3VsYXRlIHNvbWUgc3RhdGlzdGljYWwgc3VtbWFyaWVzIGFuZCB0aGVuIHdoaXAgdXAgYSBoaXN0b2dyYW0gZm9yIGVhY2ggZmVhdHVyZS4NCg0KYGBge3IgcGx0X2ZlYXR1cmVzLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBvZiBudW1lcmljIGZlYXR1cmVzICYgbGFiZWwNCm51bWVyaWNfZmVhdHVyZXMgPC0gYmlrZV9kYXRhICU+JSANCiAgc2VsZWN0KGModGVtcCwgYXRlbXAsIGh1bSwgd2luZHNwZWVkLCByZW50YWxzKSkNCg0KIyBQaXZvdCBkYXRhIHRvIGEgbG9uZyBmb3JtYXQNCm51bWVyaWNfZmVhdHVyZXMgPC0gbnVtZXJpY19mZWF0dXJlcyAlPiUgDQogIHBpdm90X2xvbmdlcighcmVudGFscywgbmFtZXNfdG8gPSAiZmVhdHVyZXMiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikgJT4lDQogIGdyb3VwX2J5KGZlYXR1cmVzKSAlPiUgDQogIG11dGF0ZShNZWFuID0gbWVhbih2YWx1ZXMpLA0KICAgICAgICAgTWVkaWFuID0gbWVkaWFuKHZhbHVlcykpDQoNCg0KIyBQbG90IGEgaGlzdG9ncmFtIGZvciBlYWNoIGZlYXR1cmUNCm51bWVyaWNfZmVhdHVyZXMgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSB2YWx1ZXMsIGZpbGwgPSBmZWF0dXJlcyksIGJpbnMgPSAxMDAsIGFscGhhID0gMC43LCBzaG93LmxlZ2VuZCA9IEYpICsNCiAgZmFjZXRfd3JhcCh+IGZlYXR1cmVzLCBzY2FsZXMgPSAnZnJlZScpKw0KICBwYWxldHRlZXI6OnNjYWxlX2ZpbGxfcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpleGNlbF9QYXJhbGxheCIpICsNCiAgDQogICMgQWRkIGxpbmVzIGZvciBtZWFuIGFuZCBtZWRpYW4NCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IE1lYW4sIGNvbG9yID0gIk1lYW4iKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDEuMyApICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IE1lZGlhbiwgY29sb3IgPSAiTWVkaWFuIiksIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxLjMgKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gIiIsIHZhbHVlcyA9IGMoTWVhbiA9ICJyZWQiLCBNZWRpYW4gPSAieWVsbG93IikpIA0KICANCmBgYA0KDQo8YnI+IFdvb0hvbyEg8J+ZjA0KDQpUaGUgbnVtZXJpYyBmZWF0dXJlcyBzZWVtIHRvIGJlIG1vcmUgKm5vcm1hbGx5KiBkaXN0cmlidXRlZCwgd2l0aCB0aGUgbWVhbiBhbmQgbWVkaWFuIG5lYXJlciB0aGUgbWlkZGxlIG9mIHRoZSByYW5nZSBvZiB2YWx1ZXMsIGNvaW5jaWRpbmcgd2l0aCB3aGVyZSB0aGUgbW9zdCBjb21tb25seSBvY2N1cnJpbmcgdmFsdWVzIGFyZS4NCg0KPiAqKk5vdGUqKjogVGhlIGRpc3RyaWJ1dGlvbnMgYXJlIG5vdCAqdHJ1bHkqICpub3JtYWwqIGluIHRoZSBzdGF0aXN0aWNhbCBzZW5zZSwgd2hpY2ggd291bGQgcmVzdWx0IGluIGEgc21vb3RoLCBzeW1tZXRyaWMgImJlbGwtY3VydmUiIGhpc3RvZ3JhbSB3aXRoIHRoZSBtZWFuIGFuZCBtb2RlICh0aGUgbW9zdCBjb21tb24gdmFsdWUpIGluIHRoZSBjZW50ZXI7IGJ1dCB0aGV5IGRvIGdlbmVyYWxseSBpbmRpY2F0ZSB0aGF0IG1vc3Qgb2YgdGhlIG9ic2VydmF0aW9ucyBoYXZlIGEgdmFsdWUgc29tZXdoZXJlIG5lYXIgdGhlIG1pZGRsZS4NCg0KV2UndmUgZXhwbG9yZWQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgYG51bWVyaWNgIHZhbHVlcyBpbiB0aGUgZGF0YXNldCwgYnV0IHdoYXQgYWJvdXQgdGhlIGBjYXRlZ29yaWNhbGAgZmVhdHVyZXM/IFRoZXNlIGFyZW4ndCBjb250aW51b3VzIG51bWJlcnMgb24gYSBzY2FsZSwgc28gd2UgY2FuJ3QgdXNlIGhpc3RvZ3JhbXM7IGJ1dCB3ZSBjYW4gcGxvdCBhIGJhciBjaGFydCBzaG93aW5nIHRoZSBjb3VudCBvZiBlYWNoIGRpc2NyZXRlIHZhbHVlIGZvciBlYWNoIGNhdGVnb3J5Lg0KDQpXZSdsbCBmb2xsb3cgdGhlIHNhbWUgcHJvY2VkdXJlIHdlIHVzZWQgZm9yIHRoZSBudW1lcmljIGZlYXR1cmUuDQoNCmBgYHtyIHBsdF9jYXRfZmVhdHVyZXMsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGNhdGVnb3JpY2FsIGZlYXR1cmVzICYgbGFiZWwNCmNhdGVnb3JpY2FsX2ZlYXR1cmVzIDwtIGJpa2VfZGF0YSAlPiUgDQogIHNlbGVjdChjKHNlYXNvbiwgbW50aCwgaG9saWRheSwgd2Vla2RheSwgd29ya2luZ2RheSwgd2VhdGhlcnNpdCwgZGF5LCByZW50YWxzKSkNCg0KIyBQaXZvdCBkYXRhIHRvIGEgbG9uZyBmb3JtYXQNCmNhdGVnb3JpY2FsX2ZlYXR1cmVzIDwtIGNhdGVnb3JpY2FsX2ZlYXR1cmVzICU+JSANCiAgcGl2b3RfbG9uZ2VyKCFyZW50YWxzLCBuYW1lc190byA9ICJmZWF0dXJlcyIsIHZhbHVlc190byA9ICJ2YWx1ZXMiKSAlPiUNCiAgZ3JvdXBfYnkoZmVhdHVyZXMpICU+JSANCiAgbXV0YXRlKHZhbHVlcyA9IGZhY3Rvcih2YWx1ZXMpKQ0KDQoNCiMgUGxvdCBhIGJhciBwbG90IGZvciBlYWNoIGZlYXR1cmUNCmNhdGVnb3JpY2FsX2ZlYXR1cmVzICU+JQ0KICBnZ3Bsb3QoKSArDQogIGdlb21fYmFyKGFlcyh4ID0gdmFsdWVzLCBmaWxsID0gZmVhdHVyZXMpLCBhbHBoYSA9IDAuNywgc2hvdy5sZWdlbmQgPSBGKSArDQogIGZhY2V0X3dyYXAofiBmZWF0dXJlcywgc2NhbGVzID0gJ2ZyZWUnKSArDQogIHBhbGV0dGVlcjo6c2NhbGVfZmlsbF9wYWxldHRlZXJfZCgiZ2d0aGVtcjo6c29sYXJpemVkIikgKw0KICB0aGVtZSgNCiAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KICANCg0KYGBgDQoNCjxicj4gTWFueSBvZiB0aGUgY2F0ZWdvcmljYWwgZmVhdHVyZXMgc2hvdyBhIG1vcmUgb3IgbGVzcyAqdW5pZm9ybSogZGlzdHJpYnV0aW9uIChtZWFuaW5nIHRoZXJlJ3Mgcm91Z2hseSB0aGUgc2FtZSBudW1iZXIgb2Ygcm93cyBmb3IgZWFjaCBjYXRlZ29yeSkuIEV4Y2VwdGlvbnMgdG8gdGhpcyBpbmNsdWRlOg0KDQotICAgKipob2xpZGF5Kio6IFRoZXJlIGFyZSBtYW55IGZld2VyIGRheXMgdGhhdCBhcmUgaG9saWRheXMgdGhhbiBkYXlzIHRoYXQgYXJlbid0Lg0KDQotICAgKip3b3JraW5nZGF5Kio6IFRoZXJlIGFyZSBtb3JlIHdvcmtpbmcgZGF5cyB0aGFuIG5vbi13b3JraW5nIGRheXMuDQoNCi0gICAqKndlYXRoZXJzaXQqKjogTW9zdCBkYXlzIGFyZSBjYXRlZ29yeSAqMSogKGNsZWFyKSwgd2l0aCBjYXRlZ29yeSAqMiogKG1pc3QgYW5kIGNsb3VkKSB0aGUgbmV4dCBtb3N0IGNvbW1vbi4gVGhlcmUgYXJlIGNvbXBhcmF0aXZlbHkgZmV3IGNhdGVnb3J5ICozKiAobGlnaHQgcmFpbiBvciBzbm93KSBkYXlzLCBhbmQgbm8gY2F0ZWdvcnkgKjQqIChoZWF2eSByYWluLCBoYWlsLCBvciBmb2cpIGRheXMgYXQgYWxsLg0KDQpOb3cgdGhhdCB3ZSBrbm93IHNvbWV0aGluZyBhYm91dCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhIGluIG91ciBjb2x1bW5zLCB3ZSBjYW4gc3RhcnQgdG8gbG9vayBmb3IgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRoZSAqZmVhdHVyZXMqIGFuZCB0aGUgKnJlbnRhbHMgbGFiZWwqIHdlIHdhbnQgdG8gYmUgYWJsZSB0byBwcmVkaWN0Lg0KDQpGb3IgdGhlIG51bWVyaWMgZmVhdHVyZXMsIHdlIGNhbiBjcmVhdGUgc2NhdHRlciBwbG90cyB0aGF0IHNob3cgdGhlIGludGVyc2VjdGlvbiBvZiBmZWF0dXJlIGFuZCBsYWJlbCB2YWx1ZXMuDQoNCmBgYHtyIHBsdF9mZWF0dXJlc19sYWJlbCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQoNCiMgUGxvdCBhIHNjYXR0ZXIgcGxvdCBmb3IgZWFjaCBmZWF0dXJlDQpudW1lcmljX2ZlYXR1cmVzICU+JSANCiAgbXV0YXRlKGNvcnJfY29lZiA9IGNvcih2YWx1ZXMsIHJlbnRhbHMpKSAlPiUNCiAgbXV0YXRlKGZlYXR1cmVzID0gcGFzdGUoZmVhdHVyZXMsICcgdnMgcmVudGFscywgciA9ICcsIGNvcnJfY29lZiwgc2VwID0gJycpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHZhbHVlcywgeSA9IHJlbnRhbHMsIGNvbG9yID0gZmVhdHVyZXMpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjcsIHNob3cubGVnZW5kID0gRikgKw0KICBmYWNldF93cmFwKH4gZmVhdHVyZXMsIHNjYWxlcyA9ICdmcmVlJykrDQogIHBhbGV0dGVlcjo6c2NhbGVfY29sb3JfcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpleGNlbF9QYXJhbGxheCIpDQoNCmBgYA0KDQo8YnI+IFRoZSAqY29ycmVsYXRpb24qIHN0YXRpc3RpYywgKnIqLCBxdWFudGlmaWVzIHRoZSBhcHBhcmVudCByZWxhdGlvbnNoaXAuIFRoZSBjb3JyZWxhdGlvbiBzdGF0aXN0aWMgaXMgYSB2YWx1ZSBiZXR3ZWVuIC0xIGFuZCAxIHRoYXQgaW5kaWNhdGVzIHRoZSBzdHJlbmd0aCBvZiBhIGxpbmVhciByZWxhdGlvbnNoaXAuDQoNCmBgYHtyIGNvcnJfZmVhdHVyZXNfbGFiZWwsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBDYWxjdWxhdGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQNCm51bWVyaWNfZmVhdHVyZXMgJT4lIA0KICBzdW1tYXJpc2UoY29ycl9jb2VmID0gY29yKHZhbHVlcywgcmVudGFscykpDQoNCmBgYA0KDQo8YnI+IFRoZSByZXN1bHRzIGFyZW4ndCBjb25jbHVzaXZlLCBidXQgaWYgeW91IGxvb2sgY2xvc2VseSBhdCB0aGUgc2NhdHRlciBwbG90cyBmb3IgYHRlbXBgIGFuZCBgYXRlbXBgLCB5b3UgY2FuIHNlZSBhIGB2YWd1ZSBkaWFnb25hbCB0cmVuZGAgc2hvd2luZyB0aGF0IGhpZ2hlciByZW50YWwgY291bnRzIHRlbmQgdG8gY29pbmNpZGUgd2l0aCBoaWdoZXIgdGVtcGVyYXR1cmVzOyBhbmQgYSBjb3JyZWxhdGlvbiB2YWx1ZSBvZiBqdXN0IG92ZXIgMC41IGZvciBib3RoIG9mIHRoZXNlIGZlYXR1cmVzIHN1cHBvcnRzIHRoaXMgb2JzZXJ2YXRpb24uIENvbnZlcnNlbHksIHRoZSBwbG90cyBmb3IgYGh1bWAgYW5kIGB3aW5kc3BlZWRgIHNob3cgYSBgc2xpZ2h0bHkgbmVnYXRpdmUgY29ycmVsYXRpb25gLCBpbmRpY2F0aW5nIHRoYXQgdGhlcmUgYXJlIGZld2VyIHJlbnRhbHMgb24gZGF5cyB3aXRoIGhpZ2ggaHVtaWRpdHkgb3Igd2luZHNwZWVkLg0KDQpOb3cgbGV0J3MgY29tcGFyZSB0aGUgY2F0ZWdvcmljYWwgZmVhdHVyZXMgdG8gdGhlIGxhYmVsLiBXZSdsbCBkbyB0aGlzIGJ5IGNyZWF0aW5nIGJveCBwbG90cyB0aGF0IHNob3cgdGhlIGRpc3RyaWJ1dGlvbiBvZiByZW50YWwgY291bnRzIGZvciBlYWNoIGNhdGVnb3J5Lg0KDQpgYGB7ciBjYXRfZmVhdHVyZXNfbGFiZWwsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBQbG90IGEgYm94IHBsb3QgZm9yIGVhY2ggZmVhdHVyZQ0KY2F0ZWdvcmljYWxfZmVhdHVyZXMgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gdmFsdWVzLCB5ID0gcmVudGFscywgZmlsbCA9IGZlYXR1cmVzKSwgYWxwaGEgPSAwLjksIHNob3cubGVnZW5kID0gRikgKw0KICBmYWNldF93cmFwKH4gZmVhdHVyZXMsIHNjYWxlcyA9ICdmcmVlJykgKw0KICBwYWxldHRlZXI6OnNjYWxlX2ZpbGxfcGFsZXR0ZWVyX2QoInR2dGhlbWVzOjpzaW1wc29ucyIpKw0KICB0aGVtZSgNCiAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KYGBgDQoNCjxicj4gVGhlIHBsb3RzIHNob3cgc29tZSB2YXJpYW5jZSBpbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gc29tZSBjYXRlZ29yeSB2YWx1ZXMgYW5kIHJlbnRhbHMuIEZvciBleGFtcGxlLCB0aGVyZSdzIGEgYGNsZWFyIGRpZmZlcmVuY2VgIGluIHRoZSBkaXN0cmlidXRpb24gb2YgcmVudGFscyBvbiB3ZWVrZW5kcyAoKndlZWtkYXkgMCBvciA2KikgYW5kIHRob3NlIGR1cmluZyB0aGUgd29ya2luZyB3ZWVrICgqd2Vla2RheSAxIHRvIDUqKS4gU2ltaWxhcmx5LCB0aGVyZSBhcmUgbm90YWJsZSBkaWZmZXJlbmNlcyBmb3IgYGhvbGlkYXlgIGFuZCBgd29ya2luZ2RheWAgY2F0ZWdvcmllcy4gVGhlcmUncyBhIG5vdGljZWFibGUgdHJlbmQgdGhhdCBzaG93cyBkaWZmZXJlbnQgcmVudGFsIGRpc3RyaWJ1dGlvbnMgaW4gc3VtbWVyIGFuZCBmYWxsIG1vbnRocyBjb21wYXJlZCB0byBzcHJpbmcgYW5kIHdpbnRlciBtb250aHMuIFRoZSBgd2VhdGhlcnNpdGAgY2F0ZWdvcnkgYWxzbyBzZWVtcyB0byBtYWtlIGEgZGlmZmVyZW5jZSBpbiByZW50YWwgZGlzdHJpYnV0aW9uLiBUaGUgKipkYXkqKiBmZWF0dXJlIHdlIGNyZWF0ZWQgZm9yIHRoZSBkYXkgb2YgdGhlIG1vbnRoIHNob3dzIGxpdHRsZSB2YXJpYXRpb24sIGluZGljYXRpbmcgdGhhdCBpdCdzIHByb2JhYmx5IG5vdCBwcmVkaWN0aXZlIG9mIHRoZSBudW1iZXIgb2YgcmVudGFscy4NCg0KQW1hemluZyDwn6Sc8J+kmyEgV2UgaGF2ZSBqdXN0IGdvbmUgdGhyb3VnaCB0aGUgcGhhc2Ugb2YgKip1bmRlcnN0YW5kaW5nIHRoZSBkYXRhKiosIG9mdGVuIHJlZmVycmVkIHRvIGFzIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgKGBFREFgKS4gRURBIGJyaW5ncyB0byBsaWdodCBob3cgdGhlIGRpZmZlcmVudCB2YXJpYWJsZXMgYXJlIHJlbGF0ZWQgdG8gb25lIGFub3RoZXIsIHRoZWlyIGRpc3RyaWJ1dGlvbnMsIHR5cGljYWwgcmFuZ2VzLCBhbmQgb3RoZXIgYXR0cmlidXRlcy4gV2l0aCB0aGVzZSBpbnNpZ2h0cyBpbiBtaW5kLCBpdCdzIHRpbWUgdG8gdHJhaW4gc29tZSByZWdyZXNzaW9uIG1vZGVscyENCg0KIyMgMi4gVHJhaW4gYSBSZWdyZXNzaW9uIE1vZGVsIHVzaW5nIFRpZHltb2RlbHMgIVtdKGltYWdlcy90ZHltLnBuZyl7d2lkdGg9IjI1In0NCg0KIVtBcnR3b3JrIGJ5IFxAYWxsaXNvbl9ob3JzdF0oaW1hZ2VzL3BhcnNuaXAuanBnKXt3aWR0aD0iNTAwIn0NCg0KTm93IHRoYXQgd2UndmUgZXhwbG9yZWQgdGhlIGRhdGEsIGl0J3MgdGltZSB0byB1c2UgaXQgdG8gdHJhaW4gYSByZWdyZXNzaW9uIG1vZGVsIHRoYXQgdXNlcyB0aGUgZmVhdHVyZXMgd2UndmUgaWRlbnRpZmllZCBhcyBgcG90ZW50aWFsbHkgcHJlZGljdGl2ZWAgdG8gcHJlZGljdCB0aGUgKipyZW50YWxzKiogbGFiZWwuIFRoZSBmaXJzdCB0aGluZyB3ZSBuZWVkIHRvIGRvIGlzIGNyZWF0ZSBhIGRhdGEgZnJhbWUgdGhhdCBjb250YWlucyB0aGUgcHJlZGljdGl2ZSBmZWF0dXJlcyBhbmQgdGhlIGxhYmVsLiBBbHNvLCB3ZSdsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIHJvbGVzIG9mIHRoZSBwcmVkaWN0b3JzLiBBcmUgdGhleSBxdWFudGl0YXRpdmUgKGludGVnZXJzL2RvdWJsZXMpIG9yIGFyZSB0aGV5IG5vbWluYWwgKGNoYXJhY3RlcnMvZmFjdG9ycyk/DQoNCltgZHBseXI6OnNlbGVjdCgpYF0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9zZWxlY3QuaHRtbCksIFtkcGx5cjo6bXV0YXRlKCldKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbXV0YXRlLmh0bWwpIGFuZCBbZHBseXI6OmFjcm9zcygpXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2Fjcm9zcy5odG1sKSAoZm9yIGFwcGx5aW5nIGEgZnVuY3Rpb24gYWNyb3NzIG11bHRpcGxlIGNvbHVtbnMpIHdpbGwgY29tZSBpbiBoYW5keSBmb3Igd2hhdCBsaWVzIGFoZWFkIQ0KDQpgYGB7ciBkZl9mZWF0dXJlc19sYWJlbCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIFNlbGVjdCBkZXNpcmVkIGZlYXR1cmVzIGFuZCBsYWJlbHMNCmJpa2Vfc2VsZWN0IDwtIGJpa2VfZGF0YSAlPiUgDQogIHNlbGVjdChjKHNlYXNvbiwgbW50aCwgaG9saWRheSwgd2Vla2RheSwgd29ya2luZ2RheSwgd2VhdGhlcnNpdCwNCiAgICAgICAgICAgdGVtcCwgYXRlbXAsIGh1bSwgd2luZHNwZWVkLCByZW50YWxzKSkgJT4lIA0KICBtdXRhdGUoYWNyb3NzKDE6NiwgZmFjdG9yKSkNCg0KIyBHZXQgYSBnbGltcHNlIG9mIHlvdXIgZGF0YQ0KZ2xpbXBzZShiaWtlX3NlbGVjdCkNCmBgYA0KDQpBbHRlcm5hdGl2ZWx5IPCfpJQsIGl0IHdvdWxkIGhhdmUgYmVlbiBlYXNpZXIgdG8ganVzdCBkZXNlbGVjdCB0aGUgdW53YW50ZWQgY29sdW1ucyB1c2luZyBgc2VsZWN0KC1jKOKApikpYCBidXQgd2UnbGwgbGVhdmUgdGhhdCBmb3IgbmV4dCB0aW1lLg0KDQpXZSAqY291bGQqIHRyYWluIGEgbW9kZWwgdXNpbmcgYWxsIG9mIHRoZSBkYXRhOyBidXQgaXQncyBjb21tb24gcHJhY3RpY2UgaW4gc3VwZXJ2aXNlZCBsZWFybmluZyB0byAqc3BsaXQqIHRoZSBkYXRhIGludG8gdHdvIHN1YnNldHM7IGEgKHR5cGljYWxseSBsYXJnZXIpIHNldCB3aXRoIHdoaWNoIHRvIHRyYWluIHRoZSBtb2RlbCwgYW5kIGEgc21hbGxlciAiaG9sZC1iYWNrIiBzZXQgd2l0aCB3aGljaCB0byB2YWxpZGF0ZSB0aGUgdHJhaW5lZCBtb2RlbC4gVGhpcyBlbmFibGVzIHVzIHRvIGV2YWx1YXRlIGhvdyB3ZWxsIHRoZSBtb2RlbCBwZXJmb3JtcyBpbiBvcmRlciB0byBnZXQgYSBiZXR0ZXIgZXN0aW1hdGUgb2YgaG93IHlvdXIgbW9kZWxzIHdpbGwgYHBlcmZvcm1gIG9uIGBuZXcgZGF0YWAuIEl0J3MgaW1wb3J0YW50IHRvIHNwbGl0IHRoZSBkYXRhICpyYW5kb21seSogKHJhdGhlciB0aGFuIHNheSwgdGFraW5nIHRoZSBmaXJzdCA3MCUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCBrZWVwaW5nIHRoZSByZXN0IGZvciB2YWxpZGF0aW9uKS4gVGhpcyBoZWxwcyBlbnN1cmUgdGhhdCB0aGUgdHdvIHN1YnNldHMgb2YgZGF0YSBhcmUgYHN0YXRpc3RpY2FsbHkgY29tcGFyYWJsZWAgKHNvIHdlIHZhbGlkYXRlIHRoZSBtb2RlbCB3aXRoIGRhdGEgdGhhdCBoYXMgYSBzaW1pbGFyIHN0YXRpc3RpY2FsIGRpc3RyaWJ1dGlvbiB0byB0aGUgZGF0YSBvbiB3aGljaCBpdCB3YXMgdHJhaW5lZCkuDQoNClRvIHJhbmRvbWx5IHNwbGl0IHRoZSBkYXRhLCB3ZSdsbCB1c2UgW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0KClgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkgLiByc2FtcGxlIGlzIG9uZSBvZiB0aGUgbWFueSBwYWNrYWdlcyBpbiB0aGUgdGlkeW1vZGVscy4NCg0KPiBUaGUgdGlkeSBtb2RlbGluZyAidmVyc2UiIGlzIGEgY29sbGVjdGlvbiBvZiBwYWNrYWdlcyBmb3IgbW9kZWxpbmcgYW5kIHN0YXRpc3RpY2FsIGFuYWx5c2lzIHRoYXQgc2hhcmUgdGhlIHVuZGVybHlpbmcgZGVzaWduIHBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMgb2YgdGhlIHRpZHl2ZXJzZS4gW1RpZHkgTW9kZWxpbmcgd2l0aCBSXShodHRwczovL3d3dy50bXdyLm9yZy8pIHdvdWxkIGJlIGEgZ29vZCBwbGFjZSB0byBnZXQgdXAgdG8gc3BlZWQgd2l0aCB0aGlzIGFtYXppbmcgZnJhbWV3b3JrIQ0KDQpgYGB7ciBpbml0aWFsX3NwbGl0LG1lc3NhZ2UgPSBGLCB3YXJuaW5nPUZ9DQojIExvYWQgdGhlIFRpZHltb2RlbHMgcGFja2FnZXMNCmxpYnJhcnkodGlkeW1vZGVscykNCg0KIyBTcGxpdCA3MCUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCB0aGUgcmVzdCBmb3IgdGVzaW5nDQpzZXQuc2VlZCgyMDU2KQ0KYmlrZV9zcGxpdCA8LSBiaWtlX3NlbGVjdCAlPiUgDQogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuNywNCiAgIyBzcGxpdHRpbmcgZGF0YSBldmVubHkgb24gdGhlIGhvbGlkYXkgdmFyaWFibGUNCiAgICAgICAgICAgICAgICBzdHJhdGEgPSBob2xpZGF5KQ0KDQojIEV4dHJhY3QgdGhlIGRhdGEgaW4gZWFjaCBzcGxpdA0KYmlrZV90cmFpbiA8LSB0cmFpbmluZyhiaWtlX3NwbGl0KQ0KYmlrZV90ZXN0IDwtIHRlc3RpbmcoYmlrZV9zcGxpdCkNCg0KZ2x1ZTo6Z2x1ZSgNCiAgJ1RyYWluaW5nIFNldDoge25yb3coYmlrZV90cmFpbil9IHJvd3MNCiAgVGVzdCBTZXQ6IHtucm93KGJpa2VfdGVzdCl9IHJvd3MnKQ0KYGBgDQoNClRoaXMgcmVzdWx0cyBpbnRvIHRoZSBmb2xsb3dpbmcgdHdvIGRhdGFzZXRzOg0KDQotICAgKmJpa2VfdHJhaW4qOiBzdWJzZXQgb2YgdGhlIGRhdGFzZXQgdXNlZCB0byB0cmFpbiB0aGUgbW9kZWwuDQoNCi0gICAqYmlrZV90ZXN0Kjogc3Vic2V0IG9mIHRoZSBkYXRhc2V0IHVzZWQgdG8gdmFsaWRhdGUgdGhlIG1vZGVsLg0KDQpOb3cg4o+yLCB3ZSdyZSByZWFkeSB0byB0cmFpbiBhIG1vZGVsIGJ5IGZpdHRpbmcgYSBzdWl0YWJsZSByZWdyZXNzaW9uIGFsZ29yaXRobSB0byB0aGUgdHJhaW5pbmcgZGF0YS4NCg0KQmVmb3JlIGVtYmFya2luZyBvbiBtb3JlIGNvbXBsZXggbWFjaGluZSBsZWFybmluZyBtb2RlbHMsIGl0J3MgYSBnb29kIGlkZWEgdG8gYnVpbGQgdGhlIHNpbXBsZXN0IHBvc3NpYmxlIG1vZGVsIHRvIGdldCBhbiBpZGVhIG9mIHdoYXQgaXMgZ29pbmcgb24uIFdlJ2xsIHVzZSBhIGBsaW5lYXIgcmVncmVzc2lvbmAgYWxnb3JpdGhtLCBhIGNvbW1vbiBzdGFydGluZyBwb2ludCBmb3IgcmVncmVzc2lvbiB0aGF0IHdvcmtzIGJ5IHRyeWluZyB0byBmaW5kIGEgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSAkeCQgdmFsdWVzIGFuZCB0aGUgJHkkIGxhYmVsLiBUaGUgcmVzdWx0aW5nIG1vZGVsIGlzIGEgZnVuY3Rpb24gdGhhdCBjb25jZXB0dWFsbHkgZGVmaW5lcyBhIGxpbmUgd2hlcmUgZXZlcnkgcG9zc2libGUgJHgkIGFuZCAkeSQgdmFsdWUgY29tYmluYXRpb24gaW50ZXJzZWN0Lg0KDQpJbiBUaWR5bW9kZWxzLCB5b3Ugc3BlY2lmeSBtb2RlbHMgdXNpbmcgYHBhcnNuaXAoKWAuIFRoZSBnb2FsIG9mIFtwYXJzbmlwXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvKSBpcyB0byBwcm92aWRlIGEgdGlkeSwgdW5pZmllZCBpbnRlcmZhY2UgdG8gbW9kZWxzIHRoYXQgY2FuIGJlIHVzZWQgdG8gdHJ5IGEgcmFuZ2Ugb2YgbW9kZWxzIGJ5IHNwZWNpZnlpbmcgdGhyZWUgY29uY2VwdHM6DQoNCi0gICBNb2RlbCAqKnR5cGUqKiBkaWZmZXJlbnRpYXRlcyBtb2RlbHMgc3VjaCBhcyBsb2dpc3RpYyByZWdyZXNzaW9uLCBkZWNpc2lvbiB0cmVlIG1vZGVscywgYW5kIHNvIGZvcnRoLg0KDQotICAgTW9kZWwgKiptb2RlKiogaW5jbHVkZXMgY29tbW9uIG9wdGlvbnMgbGlrZSByZWdyZXNzaW9uIGFuZCBjbGFzc2lmaWNhdGlvbjsgc29tZSBtb2RlbCB0eXBlcyBzdXBwb3J0IGVpdGhlciBvZiB0aGVzZSB3aGlsZSBzb21lIG9ubHkgaGF2ZSBvbmUgbW9kZS4NCg0KLSAgIE1vZGVsICoqZW5naW5lKiogaXMgdGhlIGNvbXB1dGF0aW9uYWwgdG9vbCB3aGljaCB3aWxsIGJlIHVzZWQgdG8gZml0IHRoZSBtb2RlbC4gT2Z0ZW4gdGhlc2UgYXJlIFIgcGFja2FnZXMsIHN1Y2ggYXMgKipgImxtImAqKiBvciAqKmAicmFuZ2VyImAqKg0KDQpJbiB0aWR5bW9kZWxzLCB3ZSBjYXB0dXJlIHRoYXQgbW9kZWxpbmcgaW5mb3JtYXRpb24gaW4gYSBtb2RlbCBzcGVjaWZpY2F0aW9uLCBzbyBzZXR0aW5nIHVwIHlvdXIgbW9kZWwgc3BlY2lmaWNhdGlvbiBjYW4gYmUgYSBnb29kIHBsYWNlIHRvIHN0YXJ0Lg0KDQpgYGB7ciBtb2RlbF9zcGVjLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIEJ1aWxkIGEgbGluZWFyIG1vZGVsIHNwZWNpZmljYXRpb24NCmxtX3NwZWMgPC0gDQogICMgVHlwZQ0KICBsaW5lYXJfcmVnKCkgJT4lIA0KICAjIEVuZ2luZQ0KICBzZXRfZW5naW5lKCJsbSIpICU+JSANCiAgIyBNb2RlDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KYGBgDQoNCkFmdGVyIGEgbW9kZWwgaGFzIGJlZW4gKnNwZWNpZmllZCosIHRoZSBtb2RlbCBjYW4gYmUgYGVzdGltYXRlZGAgb3IgYHRyYWluZWRgIHVzaW5nIHRoZSBbYGZpdCgpYF0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9wYXJzbmlwL3JlZmVyZW5jZS9maXQuaHRtbCkgZnVuY3Rpb24sIHR5cGljYWxseSB1c2luZyBhIHN5bWJvbGljIGRlc2NyaXB0aW9uIG9mIHRoZSBtb2RlbCAoYSBmb3JtdWxhKSBhbmQgc29tZSBkYXRhLg0KDQo+IGByZW50YWxzIH4gLmAgbWVhbnMgd2UnbGwgZml0IGByZW50YWxzYCBhcyB0aGUgcHJlZGljdGVkIHF1YW50aXR5LCBleHBsYWluZWQgYnkgYWxsIHRoZSBwcmVkaWN0b3JzL2ZlYXR1cmVzIGllLCBgLmANCg0KYGBge3IgbW9kZWxfZml0LG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIFRyYWluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwNCmxtX21vZCA8LSBsbV9zcGVjICU+JSANCiAgZml0KHJlbnRhbHMgfiAuLCBkYXRhID0gYmlrZV90cmFpbikNCg0KIyBQcmludCB0aGUgbW9kZWwgb2JqZWN0DQpsbV9tb2QNCmBgYA0KDQpTbywgdGhlc2UgYXJlIHRoZSBjb2VmZmljaWVudHMgdGhhdCB0aGUgbW9kZWwgbGVhcm5lZCBkdXJpbmcgdHJhaW5pbmcuDQoNCiMjIyBFdmFsdWF0ZSB0aGUgVHJhaW5lZCBNb2RlbA0KDQpJdCdzIHRpbWUgdG8gc2VlIGhvdyB0aGUgbW9kZWwgcGVyZm9ybWVkIPCfk48hDQoNCkhvdyBkbyB3ZSBkbyB0aGlzPyBTaW1wbGUhIE5vdyB0aGF0IHdlJ3ZlIHRyYWluZWQgdGhlIG1vZGVsLCB3ZSBjYW4gdXNlIGl0IHRvIHByZWRpY3QgcmVudGFsIGNvdW50cyBmb3IgdGhlIGZlYXR1cmVzIHdlIGhlbGQgYmFjayBpbiBvdXIgdmFsaWRhdGlvbiBkYXRhc2V0IHVzaW5nIFtwYXJzbmlwOjpwcmVkaWN0KCldKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcHJlZGljdC5tb2RlbF9maXQuaHRtbCkuIFRoZW4gd2UgY2FuIGNvbXBhcmUgdGhlc2UgcHJlZGljdGlvbnMgdG8gdGhlIGFjdHVhbCBsYWJlbCB2YWx1ZXMgdG8gZXZhbHVhdGUgaG93IHdlbGwgKG9yIG5vdCEpIHRoZSBtb2RlbCBpcyB3b3JraW5nLg0KDQpgYGB7ciBtb2RlbF9ldmFsLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIFByZWRpY3QgcmVudGFscyBmb3IgdGhlIHRlc3Qgc2V0IGFuZCBiaW5kIGl0IHRvIHRoZSB0ZXN0X3NldA0KcmVzdWx0cyA8LSBiaWtlX3Rlc3QgJT4lIA0KICBiaW5kX2NvbHMobG1fbW9kICU+JSANCiAgICAjIFByZWRpY3QgcmVudGFscw0KICAgIHByZWRpY3QobmV3X2RhdGEgPSBiaWtlX3Rlc3QpICU+JSANCiAgICAgIHJlbmFtZShwcmVkaWN0aW9ucyA9IC5wcmVkKSkNCg0KIyBDb21wYXJlIHByZWRpY3Rpb25zDQpyZXN1bHRzICU+JSANCiAgc2VsZWN0KGMocmVudGFscywgcHJlZGljdGlvbnMpKSAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDEwKQ0KYGBgDQoNCkNvbXBhcmluZyBlYWNoIHByZWRpY3Rpb24gd2l0aCBpdHMgY29ycmVzcG9uZGluZyAiZ3JvdW5kIHRydXRoIiBhY3R1YWwgdmFsdWUgaXNuJ3QgYSB2ZXJ5IGVmZmljaWVudCB3YXkgdG8gZGV0ZXJtaW5lIGhvdyB3ZWxsIHRoZSBtb2RlbCBpcyBwcmVkaWN0aW5nLiBMZXQncyBzZWUgaWYgd2UgY2FuIGdldCBhIGJldHRlciBpbmRpY2F0aW9uIGJ5IHZpc3VhbGl6aW5nIGEgc2NhdHRlciBwbG90IHRoYXQgY29tcGFyZXMgdGhlIHByZWRpY3Rpb25zIHRvIHRoZSBhY3R1YWwgbGFiZWxzLiBXZSdsbCBhbHNvIG92ZXJsYXkgYSB0cmVuZCBsaW5lIHRvIGdldCBhIGdlbmVyYWwgc2Vuc2UgZm9yIGhvdyB3ZWxsIHRoZSBwcmVkaWN0ZWQgbGFiZWxzIGFsaWduIHdpdGggdGhlIHRydWUgbGFiZWxzLg0KDQpgYGB7ciBtb2RlbF9ldmFsX3BsdCxtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBWaXN1YWxpc2UgdGhlIHJlc3VsdHMNCnJlc3VsdHMgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gcmVudGFscywgeSA9IHByZWRpY3Rpb25zKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAxLjYsIGNvbG9yID0gInN0ZWVsYmx1ZSIpICsNCiAgIyBPdmVybGF5IGEgcmVncmVzc2lvbiBsaW5lDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRiwgY29sb3IgPSAnbWFnZW50YScpICsNCiAgZ2d0aXRsZSgiRGFpbHkgQmlrZSBTaGFyZSBQcmVkaWN0aW9ucyIpICsNCiAgeGxhYigiQWN0dWFsIExhYmVscyIpICsNCiAgeWxhYigiUHJlZGljdGVkIExhYmVscyIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQpgYGANCg0KPGJyPiDwn5W1IPCfk4hUaGVyZSdzIGEgZGVmaW5pdGUgKmRpYWdvbmFsIHRyZW5kKiwgYW5kIHRoZSBpbnRlcnNlY3Rpb25zIG9mIHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCB2YWx1ZXMgYXJlIGdlbmVyYWxseSBmb2xsb3dpbmcgdGhlIHBhdGggb2YgdGhlIHRyZW5kIGxpbmU7IGJ1dCB0aGVyZSdzIGEgZmFpciBhbW91bnQgb2YgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBpZGVhbCBmdW5jdGlvbiByZXByZXNlbnRlZCBieSB0aGUgbGluZSBhbmQgdGhlIHJlc3VsdHMuIFRoaXMgdmFyaWFuY2UgcmVwcmVzZW50cyB0aGUgKnJlc2lkdWFscyogb2YgdGhlIG1vZGVsIC0gaW4gb3RoZXIgd29yZHMsIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGxhYmVsIHByZWRpY3RlZCB3aGVuIHRoZSBtb2RlbCBhcHBsaWVzIHRoZSBjb2VmZmljaWVudHMgaXQgbGVhcm5lZCBkdXJpbmcgdHJhaW5pbmcgdG8gdGhlIHZhbGlkYXRpb24gZGF0YSwgYW5kIHRoZSBhY3R1YWwgdmFsdWUgb2YgdGhlIHZhbGlkYXRpb24gbGFiZWwuIFRoZXNlIHJlc2lkdWFscyB3aGVuIGV2YWx1YXRlZCBmcm9tIHRoZSB2YWxpZGF0aW9uIGRhdGEgaW5kaWNhdGUgdGhlIGV4cGVjdGVkIGxldmVsIG9mICplcnJvciogd2hlbiB0aGUgbW9kZWwgaXMgdXNlZCB3aXRoIG5ldyBkYXRhIGZvciB3aGljaCB0aGUgbGFiZWwgaXMgdW5rbm93bi4NCg0KWW91IGNhbiBxdWFudGlmeSB0aGUgcmVzaWR1YWxzIGJ5IGNhbGN1bGF0aW5nIGEgbnVtYmVyIG9mIGNvbW1vbmx5IHVzZWQgZXZhbHVhdGlvbiBtZXRyaWNzLiBXZSdsbCBmb2N1cyBvbiB0aGUgZm9sbG93aW5nIHRocmVlOg0KDQotICAgYE1lYW4gU3F1YXJlIEVycm9yIChNU0UpYDogVGhlIG1lYW4gb2YgdGhlIHNxdWFyZWQgZGlmZmVyZW5jZXMgYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIGFjdHVhbCB2YWx1ZXMuIFRoaXMgeWllbGRzIGEgcmVsYXRpdmUgbWV0cmljIGluIHdoaWNoIHRoZSBzbWFsbGVyIHRoZSB2YWx1ZSwgdGhlIGJldHRlciB0aGUgZml0IG9mIHRoZSBtb2RlbA0KDQotICAgYFJvb3QgTWVhbiBTcXVhcmUgRXJyb3IgKFJNU0UpYDogVGhlIHNxdWFyZSByb290IG9mIHRoZSBNU0UuIFRoaXMgeWllbGRzIGFuIGFic29sdXRlIG1ldHJpYyBpbiB0aGUgc2FtZSB1bml0IGFzIHRoZSBsYWJlbCAoaW4gdGhpcyBjYXNlLCBudW1iZXJzIG9mIHJlbnRhbHMpLiBUaGUgc21hbGxlciB0aGUgdmFsdWUsIHRoZSBiZXR0ZXIgdGhlIG1vZGVsIChpbiBhIHNpbXBsaXN0aWMgc2Vuc2UsIGl0IHJlcHJlc2VudHMgdGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHJlbnRhbHMgYnkgd2hpY2ggdGhlIHByZWRpY3Rpb25zIGFyZSB3cm9uZyEpDQoNCi0gICBgQ29lZmZpY2llbnQgb2YgRGV0ZXJtaW5hdGlvbiAodXN1YWxseSBrbm93biBhcyBSLXNxdWFyZWQgb3IgUjIpYDogQSByZWxhdGl2ZSBtZXRyaWMgaW4gd2hpY2ggdGhlIGhpZ2hlciB0aGUgdmFsdWUsIHRoZSBiZXR0ZXIgdGhlIGZpdCBvZiB0aGUgbW9kZWwuIEluIGVzc2VuY2UsIHRoaXMgbWV0cmljIHJlcHJlc2VudHMgaG93IG11Y2ggb2YgdGhlIHZhcmlhbmNlIGJldHdlZW4gcHJlZGljdGVkIGFuZCBhY3R1YWwgbGFiZWwgdmFsdWVzIHRoZSBtb2RlbCBpcyBhYmxlIHRvIGV4cGxhaW4uDQoNCj4gW2B5YXJkc3RpY2tgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy8pIGlzIGEgcGFja2FnZSBpbiB0aGUgVGlkeW1vZGVscywgdXNlZCB0byBlc3RpbWF0ZSBob3cgd2VsbCBtb2RlbHMgYXJlIHdvcmtpbmcgYmFzZWQgb24gdGhlIHByZWRpY3Rpb25zIGl0IG1hZGUgZm9yIHRoZSB2YWxpZGF0aW9uIGRhdGEuIFlvdSBjYW4gZmluZCBvdXQgbW9yZSBhYm91dCB0aGVzZSBhbmQgb3RoZXIgbWV0cmljcyBmb3IgZXZhbHVhdGluZyByZWdyZXNzaW9uIG1vZGVscyBpbiB0aGUgW01ldHJpYyB0eXBlcyBkb2N1bWVudGF0aW9uXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9hcnRpY2xlcy9tZXRyaWMtdHlwZXMuaHRtbCkuDQoNCmBgYHtyIG1vZGVsX2V2YWxfbWV0LG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIE11bHRpcGxlIHJlZ3Jlc3Npb24gbWV0cmljcw0KZXZhbF9tZXRyaWNzIDwtIG1ldHJpY19zZXQocm1zZSwgcnNxKQ0KDQojIEV2YWx1YXRlIFJNU0UsIFIyIGJhc2VkIG9uIHRoZSByZXN1bHRzDQpldmFsX21ldHJpY3MoZGF0YSA9IHJlc3VsdHMsDQogICAgICAgICAgICAgdHJ1dGggPSByZW50YWxzLA0KICAgICAgICAgICAgIGVzdGltYXRlID0gcHJlZGljdGlvbnMpICU+JSANCiAgc2VsZWN0KC0yKQ0KDQoNCg0KYGBgDQoNCkdyZWF0IGpvYiDwn5mMISBTbyBub3cgd2UndmUgcXVhbnRpZmllZCB0aGUgYWJpbGl0eSBvZiBvdXIgbW9kZWwgdG8gcHJlZGljdCB0aGUgbnVtYmVyIG9mIHJlbnRhbHMuIEl0IGRlZmluaXRlbHkgaGFzICpzb21lKiBwcmVkaWN0aXZlIHBvd2VyLCBidXQgd2UgY2FuIHByb2JhYmx5IGRvIGJldHRlciENCg0KIyMgMy4gU3VtbW9uaW5nIG1vcmUgcG93ZXJmdWwgcmVncmVzc2lvbiBhbGdvcml0aG1zICFbXShpbWFnZXMvcGFyc25pcF9sLnBuZyl7d2lkdGg9IjI1In0NCg0KVGhlIGxpbmVhciByZWdyZXNzaW9uIGFsZ29yaXRobSB3ZSB1c2VkIHRvIHRyYWluIHRoZSBtb2RlbCBoYXMgc29tZSBwcmVkaWN0aXZlIGNhcGFiaWxpdHksIGJ1dCB0aGVyZSBhcmUgbWFueSBraW5kcyBvZiByZWdyZXNzaW9uIGFsZ29yaXRobSB3ZSBjb3VsZCB0cnksIGluY2x1ZGluZzoNCg0KLSAgICoqTGluZWFyIGFsZ29yaXRobXMqKjogTm90IGp1c3QgdGhlIExpbmVhciBSZWdyZXNzaW9uIGFsZ29yaXRobSB3ZSB1c2VkIGFib3ZlICh3aGljaCBpcyB0ZWNobmljYWxseSBhbiAqT3JkaW5hcnkgTGVhc3QgU3F1YXJlcyogYWxnb3JpdGhtKSwgYnV0IG90aGVyIHZhcmlhbnRzIHN1Y2ggYXMgKkxhc3NvKiBhbmQgKlJpZGdlKi4NCg0KLSAgICoqVHJlZS1iYXNlZCBhbGdvcml0aG1zKio6IEFsZ29yaXRobXMgdGhhdCBidWlsZCBhIGRlY2lzaW9uIHRyZWUgdG8gcmVhY2ggYSBwcmVkaWN0aW9uLg0KDQotICAgKipFbnNlbWJsZSBhbGdvcml0aG1zKio6IEFsZ29yaXRobXMgdGhhdCBjb21iaW5lIHRoZSBvdXRwdXRzIG9mIG11bHRpcGxlIGJhc2UgYWxnb3JpdGhtcyB0byBpbXByb3ZlIGdlbmVyYWxpemFiaWxpdHkuDQoNCj4gKipOb3RlKio6IFNlZSBhIGZ1bGwgbGlzdCBvZiBwYXJzbmlwIFttb2RlbCB0eXBlcyBhbmQgZW5naW5lc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvZmluZC9wYXJzbmlwLyNtb2RlbHMpIGFuZCBleHBsb3JlIFttb2RlbCBhcmd1bWVudHNdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnL2ZpbmQvcGFyc25pcC8jbW9kZWwtYXJncykuDQo+DQo+IFtNYWNoaW5lIExlYXJuaW5nIGZvciBTb2NpYWwgU2NpZW50aXN0c10oaHR0cHM6Ly9jaW1lbnRhZGFqLmdpdGh1Yi5pby9tbF9zb2NzY2kvdHJlZS1iYXNlZC1tZXRob2RzLmh0bWwjYm9vc3RpbmcpIHByb3ZpZGVzIGEgdmVyeSBnb29kIGV4cGxhbmF0aW9uIGFuZCBpbnRyb2R1Y3Rpb24gdG8gdGhlIHdoYXQgd2UganVzdCBkaXNjdXNzZWQgYWJvdmUuIEkgd291bGQgaGlnaGx5IHJlY29tbWVuZCBpdCENCg0KIyMjIyBUcnkgQW5vdGhlciBMaW5lYXIgQWxnb3JpdGhtDQoNCkxldCdzIHRyeSB0cmFpbmluZyBvdXIgcmVncmVzc2lvbiBtb2RlbCBieSB1c2luZyBhIGBMYXNzb2AgKCoqbGVhc3QgYWJzb2x1dGUgc2hyaW5rYWdlIGFuZCBzZWxlY3Rpb24gb3BlcmF0b3IqKikgYWxnb3JpdGhtLiBJbiBUaWR5bW9kZWxzLCB3ZSBjYW4gZG8gdGhpcyBlYXNpbHkgYnkganVzdCBjaGFuZ2luZyB0aGUgbW9kZWwgc3BlY2lmaWNhdGlvbiB0aGVuIHRoZSByZXN0IGlzIGEgYnJlZXpl8J+YjiENCg0KSGVyZSB3ZSdsbCBzZXQgdXAgb25lIG1vZGVsIHNwZWNpZmljYXRpb24gZm9yIGxhc3NvIHJlZ3Jlc3Npb247IEkgcGlja2VkIGEgdmFsdWUgZm9yIGBwZW5hbHR5YCAoc29ydCBvZiByYW5kb21seSkgYW5kIEkgc2V0IGBtaXh0dXJlID0gMWAgZm9yIGxhc3NvIChXaGVuIG1peHR1cmUgPSAxLCBpdCBpcyBhIHB1cmUgbGFzc28gbW9kZWwpLg0KDQpGb3IgY29udmVuaWVuY2UsIHdlJ2xsIHJld3JpdGUgdGhlIHdob2xlIGNvZGUgZnJvbSBzcGxpdHRpbmcgZGF0YSB0byBldmFsdWF0aW5nIG1vZGVsIHBlcmZvcm1hbmNlLg0KDQpgYGB7ciBtb2RlbF9sYXNzbyxtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBTcGxpdCA3MCUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCB0aGUgcmVzdCBmb3IgdGVzaW5nDQpzZXQuc2VlZCgyMDU2KQ0KYmlrZV9zcGxpdCA8LSBiaWtlX3NlbGVjdCAlPiUgDQogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuNywgc3RyYXRhID0gaG9saWRheSkNCg0KIyBFeHRyYWN0IHRoZSBkYXRhIGluIGVhY2ggc3BsaXQNCmJpa2VfdHJhaW4gPC0gdHJhaW5pbmcoYmlrZV9zcGxpdCkNCmJpa2VfdGVzdCA8LSB0ZXN0aW5nKGJpa2Vfc3BsaXQpDQoNCiMgQnVpbGQgYSBsYXNzbyBtb2RlbCBzcGVjaWZpY2F0aW9uDQpsYXNzb19zcGVjIDwtDQogICMgVHlwZQ0KICBsaW5lYXJfcmVnKHBlbmFsdHkgPSAxLCBtaXh0dXJlID0gMSkgJT4lIA0KICAjIEVuZ2luZQ0KICBzZXRfZW5naW5lKCdnbG1uZXQnKSAlPiUgDQogICMgTW9kZQ0KICBzZXRfbW9kZSgncmVncmVzc2lvbicpDQoNCiMgVHJhaW4gYSBsYXNzbyByZWdyZXNzaW9uIG1vZGVsDQpsYXNzb19tb2QgPC0gbGFzc29fc3BlYyAlPiUgDQogIGZpdChyZW50YWxzIH4gLiwgZGF0YSA9IGJpa2VfdHJhaW4pDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBmb3IgdGVzdCBkYXRhDQpyZXN1bHRzIDwtIGJpa2VfdGVzdCAlPiUgDQogIGJpbmRfY29scyhsYXNzb19tb2QgJT4lIHByZWRpY3QobmV3X2RhdGEgPSBiaWtlX3Rlc3QpICU+JSANCiAgICAgICAgICAgICAgcmVuYW1lKHByZWRpY3Rpb25zID0gLnByZWQpKQ0KDQojIEV2YWx1YXRlIHRoZSBtb2RlbA0KbGFzc29fbWV0cmljcyA8LSBldmFsX21ldHJpY3MoZGF0YSA9IHJlc3VsdHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnV0aCA9IHJlbnRhbHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZSA9IHByZWRpY3Rpb25zKSAlPiUNCiAgc2VsZWN0KC0yKQ0KDQoNCiMgUGxvdCBwcmVkaWN0ZWQgdnMgYWN0dWFsDQpsYXNzb19wbHQgPC0gcmVzdWx0cyAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSByZW50YWxzLCB5ID0gcHJlZGljdGlvbnMpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNiwgY29sb3IgPSAnZGFya29yY2hpZCcpICsNCiAgIyBvdmVybGF5IHJlZ3Jlc3Npb24gbGluZQ0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBjb2xvciA9ICdibGFjaycsIHNlID0gRikgKw0KICBnZ3RpdGxlKCJEYWlseSBCaWtlIFNoYXJlIFByZWRpY3Rpb25zIikgKw0KICB4bGFiKCJBY3R1YWwgTGFiZWxzIikgKw0KICB5bGFiKCJQcmVkaWN0ZWQgTGFiZWxzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkNCg0KIyBSZXR1cm4gZXZhbHVhdGlvbnMNCmxpc3QobGFzc29fbWV0cmljcywgbGFzc29fcGx0KQ0KICANCiAgDQpgYGANCg0K8J+klCBIbW1tbS4uLiBOb3QgbXVjaCBvZiBhbiBpbXByb3ZlbWVudC4gV2UgY291bGQgaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcyBieSBlc3RpbWF0aW5nIHRoZSByaWdodCByZWd1bGFyaXphdGlvbiBoeXBlcnBhcmFtZXRlciBgcGVuYWx0eWAuIFRoaXMgY2FuIGJlIGZpZ3VyZWQgb3V0IGJ5IHVzaW5nIGByZXNhbXBsaW5nYCBhbmQgYHR1bmluZ2AgdGhlIG1vZGVsIHdoaWNoIHdlJ2xsIGRpc2N1c3MgaW4ganVzdCBhIGZldy4NCg0KIyMjIyBUcnkgYSBEZWNpc2lvbiBUcmVlIEFsZ29yaXRobQ0KDQpBcyBhbiBhbHRlcm5hdGl2ZSB0byBhIGxpbmVhciBtb2RlbCwgdGhlcmUncyBhIGNhdGVnb3J5IG9mIGFsZ29yaXRobXMgZm9yIG1hY2hpbmUgbGVhcm5pbmcgdGhhdCB1c2VzIGEgYHRyZWUtYmFzZWRgIGFwcHJvYWNoIGluIHdoaWNoIHRoZSBmZWF0dXJlcyBpbiB0aGUgZGF0YXNldCBhcmUgZXhhbWluZWQgaW4gYSBzZXJpZXMgb2YgZXZhbHVhdGlvbnMsIGVhY2ggb2Ygd2hpY2ggcmVzdWx0cyBpbiBhICpicmFuY2gqIGluIGEgKmRlY2lzaW9uIHRyZWUqIGJhc2VkIG9uIHRoZSBmZWF0dXJlIHZhbHVlLiBBdCB0aGUgZW5kIG9mIGVhY2ggc2VyaWVzIG9mIGJyYW5jaGVzIGFyZSBsZWFmLW5vZGVzIHdpdGggdGhlIHByZWRpY3RlZCBsYWJlbCB2YWx1ZSBiYXNlZCBvbiB0aGUgZmVhdHVyZSB2YWx1ZXMuXA0KDQo+ICpUaGUgW0RlY2lzaW9uIFRyZWVzIENoYXB0ZXJdKGh0dHBzOi8vYnJhZGxleWJvZWhta2UuZ2l0aHViLmlvL0hPTUwvRFQuaHRtbCkgaW4gSGFuZHMtb24gTWFjaGluZSBMZWFybmluZyB3aXRoIFIgd2lsbCBwcm92aWRlIHlvdSB3aXRoIGEgc3Ryb25nIGZvdW5kYXRpb24gaW4gZGVjaXNpb24gdHJlZXMuKg0KDQpcDQpJdCdzIGVhc2llc3QgdG8gc2VlIGhvdyB0aGlzIHdvcmtzIHdpdGggYW4gZXhhbXBsZS4gTGV0J3MgdHJhaW4gYSBEZWNpc2lvbiBUcmVlIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgdGhlIGJpa2UgcmVudGFsIGRhdGEuIEFmdGVyIHRyYWluaW5nIHRoZSBtb2RlbCwgdGhlIGNvZGUgYmVsb3cgd2lsbCBwcmludCB0aGUgbW9kZWwgZGVmaW5pdGlvbiBhbmQgYSB0ZXh0IHJlcHJlc2VudGF0aW9uIG9mIHRoZSB0cmVlIGl0IHVzZXMgdG8gcHJlZGljdCBsYWJlbCB2YWx1ZXMuDQoNCmBgYHtyIGRlY2lzaW9uX3RyZWUsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCiMgQnVpbGQgYSBkZWNpc2lvbiB0cmVlIHNwZWNpZmljYXRpb24NCnRyZWVfc3BlYyA8LSBkZWNpc2lvbl90cmVlKCkgJT4lIA0KICBzZXRfZW5naW5lKCdycGFydCcpICU+JSANCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQ0KDQojIFRyYWluIGEgZGVjaXNpb24gdHJlZSBtb2RlbCANCnRyZWVfbW9kIDwtIHRyZWVfc3BlYyAlPiUgDQogIGZpdChyZW50YWxzIH4gLiwgZGF0YSA9IGJpa2VfdHJhaW4pDQoNCiMgUHJpbnQgbW9kZWwNCnRyZWVfbW9kDQoNCg0KYGBgDQoNClwNClNvIG5vdyB3ZSBoYXZlIGEgdHJlZS1iYXNlZCBtb2RlbDsgYnV0IGlzIGl0IGFueSBnb29kPyBMZXQncyBldmFsdWF0ZSBpdCB3aXRoIHRoZSB0ZXN0IGRhdGEuDQoNCmBgYHtyIGRlY2lzaW9uX3RyZWVfZXZhbCxtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBNYWtlIHByZWRpY3Rpb25zIGZvciB0ZXN0IGRhdGENCnJlc3VsdHMgPC0gYmlrZV90ZXN0ICU+JSANCiAgYmluZF9jb2xzKHRyZWVfbW9kICU+JSBwcmVkaWN0KG5ld19kYXRhID0gYmlrZV90ZXN0KSAlPiUgDQogICAgICAgICAgICAgIHJlbmFtZShwcmVkaWN0aW9ucyA9IC5wcmVkKSkNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwNCnRyZWVfbWV0cmljcyA8LSBldmFsX21ldHJpY3MoZGF0YSA9IHJlc3VsdHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1dGggPSByZW50YWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVzdGltYXRlID0gcHJlZGljdGlvbnMpICU+JSANCiAgc2VsZWN0KC0yKQ0KDQoNCiMgUGxvdCBwcmVkaWN0ZWQgdnMgYWN0dWFsDQp0cmVlX3BsdCA8LSByZXN1bHRzICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHJlbnRhbHMsIHkgPSBwcmVkaWN0aW9ucykpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICd0b21hdG8nKSArDQogICMgb3ZlcmxheSByZWdyZXNzaW9uIGxpbmUNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgY29sb3IgPSAnc3RlZWxibHVlJywgc2UgPSBGKSArDQogIGdndGl0bGUoIkRhaWx5IEJpa2UgU2hhcmUgUHJlZGljdGlvbnMiKSArDQogIHhsYWIoIkFjdHVhbCBMYWJlbHMiKSArDQogIHlsYWIoIlByZWRpY3RlZCBMYWJlbHMiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQ0KDQojIFJldHVybiBldmFsdWF0aW9ucw0KbGlzdCh0cmVlX21ldHJpY3MsIHRyZWVfcGx0KQ0KDQpgYGANCg0KT3VyIGRlY2lzaW9uIHRyZWUgcmVhbGx5IGZhaWxlZCB0byBnZW5lcmFsaXplIPCfpKYuIFdlIGNhbiBzZWUgdGhhdCBpdCdzIHByZWRpY3RpbmcgY29uc3RhbnQgdmFsdWVzIGZvciBhIGdpdmVuIHJhbmdlIG9mIHByZWRpY3RvcnMuIFdlIGNvdWxkIHByb2JhYmx5IGltcHJvdmUgdGhpcyBieSB0dW5pbmcgdGhlIG1vZGVsJ3MgYGh5cGVycGFyYW1ldGVyc2AuIFdlJ2xsIHNlZSB0aGlzIGluIGp1c3QgYSBiaXQuDQoNCiMjIyMgVHJ5IGFuIEVuc2VtYmxlIEFsZ29yaXRobQ0KDQpFbnNlbWJsZSBhbGdvcml0aG1zIHdvcmsgYnkgY29tYmluaW5nIG11bHRpcGxlIGJhc2UgZXN0aW1hdG9ycyB0byBwcm9kdWNlIGFuIG9wdGltYWwgbW9kZWwsIGVpdGhlciBieSBhcHBseWluZyBhbiAqYWdncmVnYXRlIGZ1bmN0aW9uKiB0byBhIGNvbGxlY3Rpb24gb2YgYmFzZSBtb2RlbHMgKHNvbWV0aW1lcyByZWZlcnJlZCB0byBhIGBiYWdnaW5nYCkgb3IgYnkgYnVpbGRpbmcgYSBzZXF1ZW5jZSBvZiBtb2RlbHMgdGhhdCBidWlsZCBvbiBvbmUgYW5vdGhlciB0byBpbXByb3ZlIHByZWRpY3RpdmUgcGVyZm9ybWFuY2UgKHJlZmVycmVkIHRvIGFzIGBib29zdGluZ2ApLg0KDQpGb3IgZXhhbXBsZSwgbGV0J3MgdHJ5IGEgUmFuZG9tIEZvcmVzdCBtb2RlbCwgd2hpY2ggYXBwbGllcyBhbiBhdmVyYWdpbmcgZnVuY3Rpb24gdG8gbXVsdGlwbGUgRGVjaXNpb24gVHJlZSBtb2RlbHMgZm9yIGEgYmV0dGVyIG92ZXJhbGwgbW9kZWwuDQoNCmBgYHtyIHJhbmRvbV9mb3Jlc3QsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCiMgQnVpbGQgYSByYW5kb20gZm9yZXN0IG1vZGVsIHNwZWNpZmljYXRpb24NCnJmX3NwZWMgPC0gcmFuZF9mb3Jlc3QoKSAlPiUgDQogIHNldF9lbmdpbmUoJ3JhbmRvbUZvcmVzdCcpICU+JSANCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQ0KDQojIFRyYWluIGEgcmFuZG9tIGZvcmVzdCBtb2RlbCANCnJmX21vZCA8LSByZl9zcGVjICU+JSANCiAgZml0KHJlbnRhbHMgfiAuLCBkYXRhID0gYmlrZV90cmFpbikNCg0KDQojIFByaW50IG1vZGVsDQpyZl9tb2QNCg0KYGBgDQoNClwNClNvIG5vdyB3ZSBoYXZlIGEgcmFuZG9tIGZvcmVzdCBtb2RlbDsgYnV0IGlzIGl0IGFueSBnb29kPyBMZXQncyBldmFsdWF0ZSBpdCB3aXRoIHRoZSB0ZXN0IGRhdGEuDQoNCmBgYHtyIHJhbmRvbV9mb3Jlc3RfZXZhbCxtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBNYWtlIHByZWRpY3Rpb25zIGZvciB0ZXN0IGRhdGENCnJlc3VsdHMgPC0gYmlrZV90ZXN0ICU+JSANCiAgYmluZF9jb2xzKHJmX21vZCAlPiUgcHJlZGljdChuZXdfZGF0YSA9IGJpa2VfdGVzdCkgJT4lIA0KICAgICAgICAgICAgICByZW5hbWUocHJlZGljdGlvbnMgPSAucHJlZCkpDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsDQpyZl9tZXRyaWNzIDwtIGV2YWxfbWV0cmljcyhkYXRhID0gcmVzdWx0cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnV0aCA9IHJlbnRhbHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXN0aW1hdGUgPSBwcmVkaWN0aW9ucykgJT4lIA0KICBzZWxlY3QoLTIpDQoNCg0KIyBQbG90IHByZWRpY3RlZCB2cyBhY3R1YWwNCnJmX3BsdCA8LSByZXN1bHRzICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHJlbnRhbHMsIHkgPSBwcmVkaWN0aW9ucykpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICcjNkNCRTUwRkYnKSArDQogICMgb3ZlcmxheSByZWdyZXNzaW9uIGxpbmUNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgY29sb3IgPSAnIzJCN0ZGOUZGJywgc2UgPSBGKSArDQogIGdndGl0bGUoIkRhaWx5IEJpa2UgU2hhcmUgUHJlZGljdGlvbnMiKSArDQogIHhsYWIoIkFjdHVhbCBMYWJlbHMiKSArDQogIHlsYWIoIlByZWRpY3RlZCBMYWJlbHMiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQ0KDQojIFJldHVybiBldmFsdWF0aW9ucw0KbGlzdChyZl9tZXRyaWNzLCByZl9wbHQpDQoNCmBgYA0KDQrwn6SpIFdob2EhIFRoYXQncyBhIHN0ZXAgaW4gdGhlIHJpZ2h0IGRpcmVjdGlvbi4NCg0KTGV0J3MgYWxzbyB0cnkgYSAqYm9vc3RpbmcqIGVuc2VtYmxlIGFsZ29yaXRobS4gV2UnbGwgdXNlIGEgYEdyYWRpZW50IEJvb3N0aW5nYCBlc3RpbWF0b3IsIHdoaWNoIGxpa2UgYSBSYW5kb20gRm9yZXN0IGFsZ29yaXRobSBidWlsZHMgbXVsdGlwbGUgdHJlZXMsIGJ1dCBpbnN0ZWFkIG9mIGJ1aWxkaW5nIHRoZW0gYWxsIGluZGVwZW5kZW50bHkgYW5kIHRha2luZyB0aGUgYXZlcmFnZSByZXN1bHQsIGVhY2ggdHJlZSBpcyBgYnVpbHRgIG9uIHRoZSBgb3V0cHV0c2Agb2YgdGhlIGBwcmV2aW91cyBvbmVgIGluIGFuIGF0dGVtcHQgdG8gaW5jcmVtZW50YWxseSByZWR1Y2UgdGhlICpsb3NzKiAoZXJyb3IpIGluIHRoZSBtb2RlbC4NCg0KPiBcDQo+ICpUaGUgW0dyYWRpZW50IEJvb3N0aW5nXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01ML2dibS5odG1sKSBjaGFwdGVyIGluIEhhbmRzLW9uIE1hY2hpbmUgTGVhcm5pbmcgd2l0aCBSLCB3aWxsIHByb3ZpZGUgeW91IHdpdGggdGhlIGZ1bmRhbWVudGFscyB0byB1bmRlcnN0YW5kaW5nIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmVzLioNCg0KSW4gdGhpcyB0dXRvcmlhbCwgd2UnbGwgZGVtb25zdHJhdGUgaG93IHRvIGltcGxlbWVudCAqR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZXMqIHVzaW5nIHRoZSAqKnhnYm9vc3QqKiBlbmdpbmUuDQoNCmBgYHtyIHhnYm9vc3QsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCiMgQnVpbGQgYW4geGdib29zdCBtb2RlbCBzcGVjaWZpY2F0aW9uDQpib29zdF9zcGVjIDwtIGJvb3N0X3RyZWUoKSAlPiUgDQogIHNldF9lbmdpbmUoJ3hnYm9vc3QnKSAlPiUgDQogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykNCg0KIyBUcmFpbiBhbiB4Z2Jvb3N0IG1vZGVsIA0KYm9vc3RfbW9kIDwtIGJvb3N0X3NwZWMgJT4lIA0KICBmaXQocmVudGFscyB+IC4sIGRhdGEgPSBiaWtlX3RyYWluKQ0KDQoNCiMgUHJpbnQgbW9kZWwNCmJvb3N0X21vZA0KDQpgYGANCg0KRnJvbSB0aGUgb3V0cHV0LCB3ZSBhY3R1YWxseSBzZWUgdGhhdCBHcmFkaWVudCBCb29zdGluZyBNYWNoaW5lcyBjb21iaW5lIGEgc2VyaWVzIG9mIGJhc2UgbW9kZWxzLCBlYWNoIG9mIHdoaWNoIGlzIGNyZWF0ZWQgc2VxdWVudGlhbGx5IGFuZCBkZXBlbmRzIG9uIHRoZSBwcmV2aW91cyBtb2RlbHMsIGluIGFuIGF0dGVtcHQgdG8gaW5jcmVtZW50YWxseSByZWR1Y2UgdGhlIGVycm9yIGluIHRoZSBtb2RlbC4NCg0KU28gbm93IHdlIGhhdmUgYW4gWEdib29zdCBtb2RlbDsgYnV0IGlzIGl0IGFueSBnb29k8J+ktz8gQWdhaW4sIGxldCdzIGV2YWx1YXRlIGl0IHdpdGggdGhlIHRlc3QgZGF0YS4NCg0KYGBge3IgeGdib29zdF9ldmFsLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIE1ha2UgcHJlZGljdGlvbnMgZm9yIHRlc3QgZGF0YQ0KcmVzdWx0cyA8LSBiaWtlX3Rlc3QgJT4lIA0KICBiaW5kX2NvbHMoYm9vc3RfbW9kICU+JSBwcmVkaWN0KG5ld19kYXRhID0gYmlrZV90ZXN0KSAlPiUgDQogICAgICAgICAgICAgIHJlbmFtZShwcmVkaWN0aW9ucyA9IC5wcmVkKSkNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwNCmJvb3N0X21ldHJpY3MgPC0gZXZhbF9tZXRyaWNzKGRhdGEgPSByZXN1bHRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydXRoID0gcmVudGFscywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZSA9IHByZWRpY3Rpb25zKSAlPiUgDQogIHNlbGVjdCgtMikNCg0KDQojIFBsb3QgcHJlZGljdGVkIHZzIGFjdHVhbA0KYm9vc3RfcGx0IDwtIHJlc3VsdHMgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gcmVudGFscywgeSA9IHByZWRpY3Rpb25zKSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gJyM0RDMxNjFGRicpICsNCiAgIyBvdmVybGF5IHJlZ3Jlc3Npb24gbGluZQ0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBjb2xvciA9ICdibGFjaycsIHNlID0gRikgKw0KICBnZ3RpdGxlKCJEYWlseSBCaWtlIFNoYXJlIFByZWRpY3Rpb25zIikgKw0KICB4bGFiKCJBY3R1YWwgTGFiZWxzIikgKw0KICB5bGFiKCJQcmVkaWN0ZWQgTGFiZWxzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkNCg0KIyBSZXR1cm4gZXZhbHVhdGlvbnMNCmxpc3QoYm9vc3RfbWV0cmljcywgYm9vc3RfcGx0KQ0KYGBgDQoNCk9rYXkgbm90IGJhZPCfkYwhIFdlIGFyZSBkZWZpbml0ZWx5IGdldHRpbmcgc29tZXdoZXJlLiBDYW4gd2UgZG8gYmV0dGVyPyBMZXQncyB0YWtlIGEgbG9vayBhdCBgRGF0YSBQcmVwcm9jZXNzaW5nYCBhbmQgYG1vZGVsIGh5cGVycGFyYW1ldGVyc2AuDQoNCiMjIDQuIFJlY2lwZXMgYW5kIFdvcmtmbG93cw0KDQojIyMgUHJlcHJvY2VzcyB0aGUgRGF0YSB1c2luZyByZWNpcGVzICFbXShpbWFnZXMvbG9nb19yZWNpcGUucG5nKXt3aWR0aD0iMzAifQ0KDQpXZSB0cmFpbmVkIGEgbW9kZWwgd2l0aCBkYXRhIHRoYXQgd2FzIGxvYWRlZCBzdHJhaWdodCBmcm9tIGEgc291cmNlIGZpbGUsIHdpdGggb25seSBtb2RlcmF0ZWx5IHN1Y2Nlc3NmdWwgcmVzdWx0cy4gSW4gcHJhY3RpY2UsIGl0J3MgY29tbW9uIHRvIHBlcmZvcm0gc29tZSBwcmVwcm9jZXNzaW5nIG9mIHRoZSBkYXRhIHRvIG1ha2UgaXQgZWFzaWVyIGZvciB0aGUgYWxnb3JpdGhtIHRvIGZpdCBhIG1vZGVsIHRvIGl0Lg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIGV4cGxvcmUgYW5vdGhlciB0aWR5bW9kZWxzIHBhY2thZ2UsIFtyZWNpcGVzXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3JlY2lwZXMvKSwgd2hpY2ggaXMgZGVzaWduZWQgdG8gaGVscCB5b3UgcHJlcHJvY2VzcyB5b3VyIGRhdGEgKmJlZm9yZSogdHJhaW5pbmcgeW91ciBtb2RlbC4gQSByZWNpcGUgaXMgYW4gb2JqZWN0IHRoYXQgZGVmaW5lcyBhIHNlcmllcyBvZiBzdGVwcyBmb3IgZGF0YSBwcm9jZXNzaW5nLg0KDQpUaGVyZSdzIGEgaHVnZSByYW5nZSBvZiBwcmVwcm9jZXNzaW5nIHRyYW5zZm9ybWF0aW9ucyB5b3UgY2FuIHBlcmZvcm0gdG8gZ2V0IHlvdXIgZGF0YSByZWFkeSBmb3IgbW9kZWxpbmcsIGJ1dCB3ZSdsbCBsaW1pdCBvdXJzZWx2ZXMgdG8gYSBmZXcgY29tbW9uIHRlY2huaXF1ZXM6DQoNCiMjIyMgU2NhbGluZyBudW1lcmljIGZlYXR1cmVzDQoNCk5vcm1hbGl6aW5nIG51bWVyaWMgZmVhdHVyZXMgc28gdGhleSdyZSBvbiB0aGUgc2FtZSBzY2FsZSBwcmV2ZW50cyBmZWF0dXJlcyB3aXRoIGxhcmdlIHZhbHVlcyBmcm9tIHByb2R1Y2luZyBjb2VmZmljaWVudHMgdGhhdCBkaXNwcm9wb3J0aW9uYXRlbHkgYWZmZWN0IHRoZSBwcmVkaWN0aW9ucy4gRm9yIGV4YW1wbGUsIHN1cHBvc2UgeW91ciBkYXRhIGluY2x1ZGVzIHRoZSBmb2xsb3dpbmcgbnVtZXJpYyBmZWF0dXJlczoNCg0KfCAgQSAgfCAgQiAgfCAgQyAgfA0KfDotLS06fDotLS06fDotLS06fA0KfCAgMyAgfCA0ODAgfCA2NSAgfA0KDQpOb3JtYWxpemluZyB0aGVzZSBmZWF0dXJlcyB0byB0aGUgc2FtZSBzY2FsZSBtYXkgcmVzdWx0IGluIHRoZSBmb2xsb3dpbmcgdmFsdWVzIChhc3N1bWluZyBBIGNvbnRhaW5zIHZhbHVlcyBmcm9tIDAgdG8gMTAsIEIgY29udGFpbnMgdmFsdWVzIGZyb20gMCB0byAxMDAwLCBhbmQgQyBjb250YWlucyB2YWx1ZXMgZnJvbSAwIHRvIDEwMCk6DQoNCnwgIEEgIHwgIEIgICB8ICBDICAgfA0KfDotLS06fDotLS0tOnw6LS0tLTp8DQp8IDAuMyB8IDAuNDggfCAwLjY1IHwNCg0KVGhlcmUgYXJlIG11bHRpcGxlIHdheXMgeW91IGNhbiBzY2FsZSBudW1lcmljIGRhdGEsIHN1Y2ggYXMgY2FsY3VsYXRpbmcgdGhlIG1pbmltdW0gYW5kIG1heGltdW0gdmFsdWVzIGZvciBlYWNoIGNvbHVtbiBhbmQgYXNzaWduaW5nIGEgcHJvcG9ydGlvbmFsIHZhbHVlIGJldHdlZW4gMCBhbmQgMSwgb3IgYnkgdXNpbmcgdGhlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBhIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHZhcmlhYmxlIHRvIG1haW50YWluIHRoZSBzYW1lICpzcHJlYWQqIG9mIHZhbHVlcyBvbiBhIGRpZmZlcmVudCBzY2FsZS4NCg0KIyMjIyBFbmNvZGluZyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMNCg0KVGhpcyBpbnZvbHZlcyB0cmFuc2xhdGluZyBhIGNvbHVtbiB3aXRoIGNhdGVnb3JpY2FsIHZhbHVlcyBpbnRvIG9uZSBvciBtb3JlIG51bWVyaWMgY29sdW1ucyB0aGF0IHRha2UgdGhlIHBsYWNlIG9mIHRoZSBvcmlnaW5hbC4NCg0KTWFjaGluZSBsZWFybmluZyBtb2RlbHMgd29yayBiZXN0IHdpdGggbnVtZXJpYyBmZWF0dXJlcyByYXRoZXIgdGhhbiB0ZXh0IHZhbHVlcywgc28geW91IGdlbmVyYWxseSBuZWVkIHRvIGNvbnZlcnQgY2F0ZWdvcmljYWwgZmVhdHVyZXMgaW50byBudW1lcmljIHJlcHJlc2VudGF0aW9ucy4gRm9yIGV4YW1wbGUsIHN1cHBvc2UgeW91ciBkYXRhIGluY2x1ZGVzIHRoZSBmb2xsb3dpbmcgY2F0ZWdvcmljYWwgZmVhdHVyZS4NCg0KfCBTaXplIHwNCnw6LS0tLTp8DQp8ICBTICAgfA0KfCAgTSAgIHwNCnwgIEwgICB8DQoNCllvdSBjYW4gYXBwbHkgKm9yZGluYWwgZW5jb2RpbmcqIHRvIHN1YnN0aXR1dGUgYSB1bmlxdWUgaW50ZWdlciB2YWx1ZSBmb3IgZWFjaCBjYXRlZ29yeSwgbGlrZSB0aGlzOg0KDQp8IFNpemUgfA0KfDotLS0tOnwNCnwgIDAgICB8DQp8ICAxICAgfA0KfCAgMiAgIHwNCg0KQW5vdGhlciBjb21tb24gdGVjaG5pcXVlIGlzIHRvIGNyZWF0ZSAiKmR1bW15KiIgb3IgKmluZGljYXRvciB2YXJpYWJsZXMqIHdoaWNoIHJlcGxhY2UgdGhlIG9yaWdpbmFsIGNhdGVnb3JpY2FsIGZlYXR1cmUgd2l0aCBudW1lcmljIGNvbHVtbnMgd2hvc2UgdmFsdWVzIGFyZSBlaXRoZXIgMSBvciAwLiBUaGlzIGNhbiBiZSBzaG93biBhczoNCg0KfCBSYXcgRGF0YSB8ICBNICB8ICBMICB8DQp8Oi0tLS0tLS0tOnw6LS0tOnw6LS0tOnwNCnwgICAgUyAgICAgfCAgMCAgfCAgMCAgfA0KfCAgICBNICAgICB8ICAxICB8ICAwICB8DQp8ICAgIEwgICAgIHwgIDAgIHwgIDEgIHwNCg0KSW4gUiwgdGhlIGNvbnZlbnRpb24gaXMgdG8gKmV4Y2x1ZGUqIGEgY29sdW1uIGZvciB0aGUgZmlyc3QgZmFjdG9yIGxldmVsIChgU2AsIGluIHRoaXMgY2FzZSkuIFRoZSByZWFzb25zIGZvciB0aGlzIGluY2x1ZGUgYHNpbXBsaWNpdHlgIGFuZCByZWR1Y2luZyBgbGluZWFyIGRlcGVuZGVuY2llc2AuIFRoZSBmdWxsIHNldCBvZiBlbmNvZGluZ3MgY2FuIGJlIHVzZWQgZm9yIHNvbWUgbW9kZWxzLiBUaGlzIGlzIHRyYWRpdGlvbmFsbHkgY2FsbGVkIHRoZSAib25lLWhvdCIgZW5jb2RpbmcgYW5kIGNhbiBiZSBhY2hpZXZlZCB1c2luZyB0aGUgYG9uZV9ob3RgIGFyZ3VtZW50IG9mIGBzdGVwX2R1bW15KClgLg0KDQo+IFRoZSBjaGFwdGVyIFtGZWF0dXJlIGVuZ2luZWVyaW5nIHdpdGggcmVjaXBlc10oaHR0cHM6Ly93d3cudG13ci5vcmcvcmVjaXBlcy5odG1sKSBpbiAqYFRpZHkgTW9kZWxpbmcgd2l0aCBSYCogY29udGFpbnMgbW9yZSBpbmZvcm1hdGlvbiBvbiBob3cgdG8gdHJhbnNmb3JtIGFuZCBlbmNvZGUgZGF0YSBmb3IgbW9kZWxsaW5nLg0KDQpOb3csIGxldCdzIGJpa2UgZm9ydGggYW5kIGNyZWF0ZSBzb21lIHJlY2lwZXMg4oCL8J+UquKAiyoq8J+atCEqKg0KDQpgYGB7ciByZWNpcGVzLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIFNwZWNpZnkgYSByZWNpcGUNCmJpa2VfcmVjaXBlIDwtIHJlY2lwZShyZW50YWxzIH4gLiwgZGF0YSA9IGJpa2VfdHJhaW4pICU+JSANCiAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSANCg0KYmlrZV9yZWNpcGUNCg0KDQojIFN1bW1hcnkgb2YgdGhlIHJlY2lwZQ0Kc3VtbWFyeShiaWtlX3JlY2lwZSkNCmBgYA0KDQpXZSBqdXN0IGNyZWF0ZWQgb3VyIGZpcnN0IHJlY2lwZSBjb250YWluaW5nIGFuIG91dGNvbWUgYW5kIGl0cyBjb3JyZXNwb25kaW5nIHByZWRpY3RvcnMsIHdpdGggdGhlIG51bWVyaWMgcHJlZGljdG9ycyBub3JtYWxpemVkIGFuZCB0aGUgbm9taW5hbCBwcmVkaWN0b3JzIGNvbnZlcnRlZCB0byBhIHF1YW50aXRhdGl2ZSBmb3JtYXQg8J+ZjCEgTGV0J3MgcXVpY2tseSBicmVhayBpdCBkb3duOg0KDQotICAgVGhlIGNhbGwgdG8gYHJlY2lwZSgpYCB3aXRoIGEgZm9ybXVsYSB0ZWxscyB0aGUgcmVjaXBlIHRoZSAqcm9sZXMqIG9mIHRoZSB2YXJpYWJsZXMgKGUuZy4sIHByZWRpY3Rvciwgb3V0Y29tZSkgdXNpbmcgYGJpa2VfdHJhaW5gIGRhdGEgYXMgdGhlIHJlZmVyZW5jZS4gVGhpcyBjYW4gYmUgc2VlbiBmcm9tIHRoZSByZXN1bHRzIG9mIGBzdW1tYXJ5KGJpa2VfcmVjaXBlKWANCg0KLSAgIGBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpYCBzcGVjaWZpZXMgdGhhdCBhbGwgbnVtZXJpYyBwcmVkaWN0b3JzIHNob3VsZCBiZSBub3JtYWxpemVkLg0KDQotICAgYHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKWAgc3BlY2lmaWVzIHRoYXQgYWxsIHByZWRpY3RvcnMgdGhhdCBhcmUgY3VycmVudGx5IGZhY3RvciBvciBjaGFyYWN0b3Igc2hvdWxkIGJlIGNvbnZlcnRlZCB0byBhIHF1YW50aXRhdGl2ZSBmb3JtYXQgKDFzLzBzKS4NCg0KR3JlYXQhIE5vdyB0aGF0IHdlIGhhdmUgYSByZWNpcGUsIHRoZSBuZXh0IHN0ZXAgd291bGQgYmUgdG8gY3JlYXRlIGEgbW9kZWwgc3BlY2lmaWNhdGlvbiAod2hpY2ggd2UgYWxyZWFkeSBkaWQpLiBJbiB0aGlzIGNhc2UsIGxldCdzIHJlY3JlYXRlIGFuIGB4Z2Jvb3N0YCBtb2RlbCBzcGVjaWZpY2F0aW9uLiBUcmVlIGJhc2VkIG1vZGVscyBjcmVhdGVkIHVzaW5nIHRoZSB4Z2Jvb3N0IGVuZ2luZSB0eXBpY2FsbHkgcmVxdWlyZSBvbmUgdG8gY3JlYXRlIGR1bW15IHZhcmlhYmxlcy4NCg0KPiBUaGUgY2hhcHRlciBbUmVjb21tZW5kZWQgcHJlcHJvY2Vzc2luZ10oaHR0cHM6Ly93d3cudG13ci5vcmcvcHJlLXByb2MtdGFibGUuaHRtbCkgaW4gVGlkeSBNb2RlbGxpbmcgd2l0aCBSIHByb3ZpZGVzIHByZXByb2Nlc3NpbmcgcmVjb21tZW5kYXRpb25zIHRoYXQgYXJlIG5lZWRlZCBmb3IgdmFyaW91cyBtb2RlbGxpbmcgZnVuY3Rpb25zLg0KDQpgYGB7ciB4Z2Jvb3N0X3NwZWMyLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQpib29zdF9zcGVjIDwtIGJvb3N0X3RyZWUoKSAlPiUgDQogIHNldF9lbmdpbmUoJ3hnYm9vc3QnKSAlPiUgDQogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykNCmBgYA0KDQoiV2FpdCEiLCB5b3UnbGwgc2F5LCAiSG93IGRvIHdlIGNvbWJpbmUgdGhpcyBtb2RlbCBzcGVjaWZpY2F0aW9uIHdpdGggdGhlIGRhdGEgcHJlcHJvY2Vzc2luZyB3ZSBuZWVkIHRvIGRvIGZyb20gb3VyIHJlY2lwZT8g8J+klCINCg0KV2VsbCwgd2VsY29tZSB0byBtb2RlbGxpbmcgYHdvcmtmbG93c2Ag8J+Yii4gVGhpcyBpcyB3aGF0IHdlJ2QgY2FsbCAqcGlwZWxpbmVzKiBpbiAqUHl0aG9uKi4NCg0KIyMjIEJ1bmRsaW5nIGl0IGFsbCB0b2dldGhlciB1c2luZyBhIHdvcmtmbG93ICFbXShpbWFnZXMvd29ya2Zsb3dfbG9nby5wbmcpe3dpZHRoPSIyOCIgaGVpZ2h0PSIzMiJ9Lg0KDQpUaGUgWyoqd29ya2Zsb3dzKipdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnLykgcGFja2FnZSBhbGxvd3MgdGhlIHVzZXIgdG8gYmluZCBtb2RlbGluZyBhbmQgcHJlcHJvY2Vzc2luZyBvYmplY3RzIHRvZ2V0aGVyLiBZb3UgY2FuIHRoZW4gZml0IHRoZSBlbnRpcmUgd29ya2Zsb3cgdG8gdGhlIGRhdGEsIHNvIHRoYXQgdGhlIG1vZGVsIGVuY2Fwc3VsYXRlcyBhbGwgb2YgdGhlIHByZXByb2Nlc3Npbmcgc3RlcHMgYXMgd2VsbCBhcyB0aGUgYWxnb3JpdGhtLg0KDQpgYGB7ciBib29zdF93b3JrZmxvdyxtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBDcmVhdGUgdGhlIHdvcmtmbG93DQpib29zdF93b3JrZmxvdyA8LSB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShiaWtlX3JlY2lwZSkgJT4lIA0KICBhZGRfbW9kZWwoYm9vc3Rfc3BlYykNCg0KYm9vc3Rfd29ya2Zsb3cNCg0KYGBgDQoNClRoZSBgd29ya2Zsb3dgIG9iamVjdCBwcm92aWRlcyBxdWl0ZSBhbiBpbmZvcm1hdGl2ZSBzdW1tYXJ5IG9mIHRoZSBwcmVwcm9jZXNzaW5nIHN0ZXBzIHRoYXQgd2lsbCBiZSBkb25lIGJ5IHRoZSByZWNpcGUgYW5kIGFsc28gdGhlIG1vZGVsIHNwZWNpZmljYXRpb24g8J+RjPCfkYwuIEludG8gdGhlIGJhcmdhaW4sIGEgKipgd29ya2Zsb3coKWAqKiBjYW4gYmUgZml0IGluIG11Y2ggdGhlIHNhbWUgd2F5IGEgbW9kZWwgY2FuLg0KDQpgYGB7ciBib29zdF93b3JrZmxvd19maXQsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCiMgVHJhaW4gdGhlIG1vZGVsDQpib29zdF93b3JrZmxvdyA8LSBib29zdF93b3JrZmxvdyAlPiUgDQogIGZpdChkYXRhID0gYmlrZV90cmFpbikNCg0KI2Jvb3N0X3dvcmtmbG93DQpgYGANCg0KTm93IHRoYXQgd2UgaGF2ZSBvdXIgKmZpdHRlZCB3b3JrZmxvdyosIGhvdyBkbyB3ZSBtYWtlIHNvbWUgcHJlZGljdGlvbnPwn5SuPyBgcHJlZGljdCgpYCBjYW4gYmUgdXNlZCBvbiBhIHdvcmtmbG93IGluIHRoZSBzYW1lIHdheSBhcyBvbiBhIG1vZGVsIQ0KDQpOb3csIGxldCdzIG1ha2Ugc29tZSBwcmVkaWN0aW9ucyBvbiB0aGUgZmlyc3QgNiBvYnNlcnZhdGlvbnMgb2Ygb3VyIHRlc3Qgc2V0Lg0KDQpgYGB7ciBib29zdF93b3JrZmxvd19wcmVkLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQpib29zdF93b3JrZmxvdyAlPiUgDQogIHByZWRpY3QobmV3X2RhdGEgPSBiaWtlX3Rlc3QgJT4lIGRwbHlyOjpzbGljZSgxOjYpKQ0KYGBgDQoNCkhvdyBjb252ZW5pZW50IHdvcmtmbG93cyBhcmUh8J+SgQ0KDQpTbyBwcm9iYWJseSB5b3UgbWF5IGJlIHdvbmRlcmluZyB3aHkgd2UgaGF2ZW4ndCBtYWRlIHByZWRpY3Rpb25zIG9uIHRoZSB3aG9sZSB0ZXN0IHNldCwgZXZhbHVhdGVkIHBlcmZvcm1hbmNlIGFuZCBjcmVhdGVkIHNvbWUgcHJldHR5IGdyYXBocy4gV2UnbGwgZ2V0IHJpZ2h0IGludG8gdGhhdCwgYnV0IGZpcnN0LCBsZXQncyBhZGRyZXNzIGEgbW9yZSBwcmVzc2luZyBpc3N1ZTsgYSBib29zdGVkIHRyZWUncyBzcGVjaWZpY2F0aW9uOg0KDQpgYGB7cn0NCmFyZ3MoYm9vc3RfdHJlZSkNCmBgYA0KDQpUaG9zZSBhcmUgYSBsb3Qgb2YgbW9kZWwgYXJndW1lbnRzOiBgbXRyeWAsIGB0cmVlc2AsIGBtaW5fbmAsIGB0cmVlX2RlcHRoYCwgYGxlYXJuX3JhdGVgLCBgbG9zc19yZWR1Y3Rpb25gLCBgc2FtcGxlX3NpemVgLCBgc3RvcF9pdGVyYCDwn6Sv8J+kryENCg0KTm93LCB0aGlzIGJlZ3MgdGhlIHF1ZXN0aW9uOg0KDQpob3cgZG8gd2Uga25vdyB3aGF0IHZhbHVlcyB3ZSBzaG91bGQgdXNlP/CfpJQNCg0KVGhpcyBicmluZ3MgdXMgdG8gYG1vZGVsIHR1bmluZ2AuDQoNCiMjIDUuIFR1bmUgbW9kZWwgaHlwZXJwYXJhbWV0ZXJzICFbXShpbWFnZXMvdHVuZV9sb2dvLnBuZyl7d2lkdGg9IjI1IiBoZWlnaHQ9IjI5In0NCg0KTW9kZWxzIGhhdmUgcGFyYW1ldGVycyB3aXRoIHVua25vd24gdmFsdWVzIHRoYXQgbXVzdCBiZSBlc3RpbWF0ZWQgaW4gb3JkZXIgdG8gdXNlIHRoZSBtb2RlbCBmb3IgcHJlZGljdGluZy4gU29tZSBtb2RlbCBwYXJhbWV0ZXJzIGNhbm5vdCBiZSBsZWFybmVkIGRpcmVjdGx5IGZyb20gYSBkYXRhc2V0IGR1cmluZyBtb2RlbCB0cmFpbmluZzsgdGhlc2Uga2luZHMgb2YgcGFyYW1ldGVycyBhcmUgY2FsbGVkICoqaHlwZXJwYXJhbWV0ZXJzKiogb3IgKip0dW5pbmcgcGFyYW1ldGVycyoqLg0KDQpJbnN0ZWFkIG9mIGxlYXJuaW5nIHRoZXNlIGtpbmRzIG9mIGh5cGVycGFyYW1ldGVycyBkdXJpbmcgbW9kZWwgdHJhaW5pbmcsIHdlIGNhbiAqZXN0aW1hdGUqIHRoZSAqYmVzdCB2YWx1ZXMqIGZvciB0aGVzZSBieSB0cmFpbmluZyBtYW55IG1vZGVscyBvbiBhIGBzaW11bGF0ZWQgZGF0YSBzZXRgIGFuZCBtZWFzdXJpbmcgaG93IHdlbGwgYWxsIHRoZXNlIG1vZGVscyBwZXJmb3JtLiBUaGlzIHByb2Nlc3MgaXMgY2FsbGVkICoqdHVuaW5nKiouDQoNCj4gU2ltcGxlIG1vZGVscyB3aXRoIHNtYWxsIGRhdGFzZXRzIGNhbiBvZnRlbiBiZSBmaXQgaW4gYSBzaW5nbGUgc3RlcCwgd2hpbGUgbGFyZ2VyIGRhdGFzZXRzIGFuZCBtb3JlIGNvbXBsZXggbW9kZWxzIHRlbmQgdG8gYWNoaWV2ZSBiZXR0ZXIgcmVzdWx0cyBieSBmaXR0aW5nIHJlcGVhdGVkbHkgdXNpbmcgc2ltdWxhdGVkIGRhdGEuIElmIHRoZSBwcmVkaWN0aW9uIGlzIGFjY3VyYXRlIGVub3VnaCwgd2UgY29uc2lkZXIgdGhlIG1vZGVsIHRyYWluZWQuIElmIG5vdCwgd2UgYWRqdXN0IHRoZSBtb2RlbCBzbGlnaHRseSBhbmQgbG9vcCBhZ2Fpbi4NCg0KV2Ugd29uJ3QgZ28gaW50byB0aGUgZGV0YWlscyBvZiBlYWNoIGh5cGVycGFyYW1ldGVyLCBidXQgdGhleSB3b3JrIHRvZ2V0aGVyIHRvIGFmZmVjdCB0aGUgd2F5IHRoZSBhbGdvcml0aG0gdHJhaW5zIGEgbW9kZWwuIEZvciBpbnN0YW5jZSBpbiBib29zdGVkIHRyZWVzLA0KDQotICAgYG1pbl9uYCBmb3JjZXMgdGhlIHRyZWUgdG8gZGlzY2FyZCBhbnkgbm9kZSB0aGF0IGhhcyBhIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgYmVsb3cgeW91ciBzcGVjaWZpZWQgbWluaW11bS4NCg0KLSAgIHR1bmluZyB0aGUgdmFsdWUgb2YgYG10cnlgIGNvbnRyb2xzIHRoZSBudW1iZXIgb2YgdmFyaWFibGVzIHRoYXQgd2lsbCBiZSB1c2VkIGF0IGVhY2ggc3BsaXQgb2YgYSBkZWNpc2lvbiB0cmVlLg0KDQotICAgdHVuaW5nIGB0cmVlX2RlcHRoYCwgb24gdGhlIG90aGVyIGhhbmQsIGhlbHBzIGJ5IFtzdG9wcGluZ10oaHR0cHM6Ly9icmFkbGV5Ym9laG1rZS5naXRodWIuaW8vSE9NTC9EVC5odG1sI2Vhcmx5LXN0b3BwaW5nKSBvdXIgdHJlZSBmcm9tIGdyb3dpbmcgYWZ0ZXIgaXQgcmVhY2hlcyBhIGNlcnRhaW4gZGVwdGggLSBbVHVuZSBtb2RlbCBwYXJhbWV0ZXJzXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy9zdGFydC90dW5pbmcvKSwgVGlkeW1vZGVscyBHZXQgU3RhcnRlZC4NCg0KLSAgIGBMZWFybmluZyByYXRlYCwgc2V0cyBob3cgbXVjaCBhIG1vZGVsIGlzIGFkanVzdGVkIGR1cmluZyBlYWNoIHRyYWluaW5nIGN5Y2xlLiBBIGhpZ2ggbGVhcm5pbmcgcmF0ZSBtZWFucyBhIG1vZGVsIGNhbiBiZSB0cmFpbmVkIGZhc3RlciwgYnV0IGlmIGl0J3MgdG9vIGhpZ2ggdGhlIGFkanVzdG1lbnRzIGNhbiBiZSBzbyBsYXJnZSB0aGF0IHRoZSBtb2RlbCBpcyBuZXZlciAnZmluZWx5IHR1bmVkJyBhbmQgbm90IG9wdGltYWwgLSBbTWljcm9zb2Z0IExlYXJuXShodHRwczovL2RvY3MubWljcm9zb2Z0LmNvbS9lbi11cy9sZWFybi9tb2R1bGVzL3RyYWluLWV2YWx1YXRlLXJlZ3Jlc3Npb24tbW9kZWxzLzYtaW1wcm92ZS1tb2RlbHMpLCBCdWlsZCBNYWNoaW5lIExlYXJuaW5nIE1vZGVscy4NCg0KSW4gbWFueSBjYXNlcywgdGhlIGRlZmF1bHQgdmFsdWVzIHByb3ZpZGVkIGJ5IFRpZHltb2RlbHMgd2lsbCB3b3JrIHdlbGwgKHNlZSB0aGUgZGVmYXVsdHMgYnkgdHlwaW5nIGBoZWxwKCJib29zdF90cmVlIilgIG9uIHlvdXIgY29uc29sZSk7IGJ1dCB0aGVyZSBtYXkgYmUgc29tZSBhZHZhbnRhZ2UgaW4gbW9kaWZ5aW5nIGh5cGVycGFyYW1ldGVycyB0byBnZXQgYmV0dGVyIHByZWRpY3RpdmUgcGVyZm9ybWFuY2Ugb3IgcmVkdWNlIHRyYWluaW5nIHRpbWUuDQoNClNvIGhvdyBkbyB5b3Uga25vdyB3aGF0IGh5cGVycGFyYW1ldGVyIHZhbHVlcyB5b3Ugc2hvdWxkIHVzZT8gV2VsbCwgaW4gdGhlIGFic2VuY2Ugb2YgYSBkZWVwIHVuZGVyc3RhbmRpbmcgb2YgaG93IHRoZSB1bmRlcmx5aW5nIGFsZ29yaXRobSB3b3JrcywgeW91J2xsIG5lZWQgdG8gZXhwZXJpbWVudC4gRm9ydHVuYXRlbHksIFRpZHltb2RlbHMgcHJvdmlkZXMgYSB3YXkgdG8gKnR1bmUqIGh5cGVycGFyYW1ldGVycyBieSB0cnlpbmcgbXVsdGlwbGUgY29tYmluYXRpb25zIGFuZCBmaW5kaW5nIHRoZSBiZXN0IHJlc3VsdCBmb3IgYSBnaXZlbiBwZXJmb3JtYW5jZSBtZXRyaWMuDQoNCj4gW01hY2hpbmUgTGVhcm5pbmcgZm9yIFNvY2lhbCBTY2llbnRpc3RzXShodHRwczovL2NpbWVudGFkYWouZ2l0aHViLmlvL21sX3NvY3NjaS90cmVlLWJhc2VkLW1ldGhvZHMuaHRtbCNib29zdGluZykgcHJvdmlkZXMgYSB2ZXJ5IGdvb2QgZXhwbGFuYXRpb24gYW5kIGludHJvZHVjdGlvbiB0byBUcmVlIGJhc2VkIG1vZGVscyBlLmcgRGVjaXNpb24gdHJlZXMsIFJhbmRvbSBGb3Jlc3RzLCBCb29zdGVkIHRyZWVzIGV0Yy4gSSB3b3VsZCBoaWdobHkgcmVjb21tZW5kIGl0IQ0KDQojIyMjIElkZW50aWZ5IHR1bmluZyBwYXJhbWV0ZXJzLg0KDQpIb3cgY2FuIHdlIHNpZ25hbCB0byB0aWR5bW9kZWxzIGZ1bmN0aW9ucyB3aGljaCBhcmd1bWVudHMgKGluIG91ciBjYXNlIGBjb3N0X2NvbXBsZXhpdHlgLCBgdHJlZV9kZXB0aGAsIGBtaW5fbmApIHNob3VsZCBiZSBvcHRpbWl6ZWQ/IFBhcmFtZXRlcnMgYXJlIG1hcmtlZCBmb3IgdHVuaW5nIGJ5IGFzc2lnbmluZyB0aGVtIGEgdmFsdWUgb2YgYHR1bmUoKWAuDQoNCk5leHQgbGV0J3MgYnVpbGQgb3VyIG1vZGVsIHNwZWNpZmljYXRpb24gd2l0aCBzb21lIHR1bmluZyBhbmQgdGhlbiBwdXQgb3VyIHJlY2lwZSBhbmQgbW9kZWwgc3BlY2lmaWNhdGlvbiB0b2dldGhlciBpbiBhICoqYHdvcmtmbG93KClgKiosIGZvciBlYXNlIG9mIHVzZS4NCg0KYGBge3IgYm9vc3Rfc3BlY190dW5lLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIFNwZWNpZnkgYSByZWNpcGUNCmJpa2VfcmVjaXBlIDwtIHJlY2lwZShyZW50YWxzIH4gLiwgZGF0YSA9IGJpa2VfdHJhaW4pICU+JSANCiAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSANCg0KDQojIE1ha2UgYSB0dW5hYmxlIG1vZGVsIHNwZWNpZmljYXRpb24NCmJvb3N0X3NwZWMgPC0gYm9vc3RfdHJlZSh0cmVlcyA9IDUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVfZGVwdGggPSB0dW5lKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgbGVhcm5fcmF0ZSA9IHR1bmUoKSkgJT4lIA0KICBzZXRfZW5naW5lKCd4Z2Jvb3N0JykgJT4lIA0KICBzZXRfbW9kZSgncmVncmVzc2lvbicpDQoNCg0KIyBCdW5kbGUgYSByZWNpcGUgYW5kIG1vZGVsIHNwZWMgdXNpbmcgYSB3b3JrZmxvdw0KYm9vc3Rfd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUgDQogIGFkZF9yZWNpcGUoYmlrZV9yZWNpcGUpICU+JSANCiAgYWRkX21vZGVsKGJvb3N0X3NwZWMpDQoNCmJvb3N0X3dvcmtmbG93DQpgYGANCg0KIyMjIyBDcmVhdGUgYSB0dW5pbmcgZ3JpZC4NCg0KR29vZCBqb2IhIE5vdyB0aGF0IHdlIGhhdmUgc3BlY2lmaWVkIHdoYXQgcGFyYW1ldGVyIHRvIHR1bmUsIHdlJ2xsIG5lZWQgdG8gZmlndXJlIG91dCBhIHNldCBvZiBwb3NzaWJsZSB2YWx1ZXMgdG8gdHJ5IG91dCB0aGVuIGNob29zZSB0aGUgYmVzdC4NCg0KVG8gZG8gdGhpcywgd2UnbGwgY3JlYXRlIGEgZ3JpZCEgVG8gdHVuZSBvdXIgaHlwZXJwYXJhbWV0ZXJzLCB3ZSBuZWVkIGEgc2V0IG9mIHBvc3NpYmxlIHZhbHVlcyBmb3IgZWFjaCBwYXJhbWV0ZXIgdG8gdHJ5LiBJbiB0aGlzIGNhc2Ugc3R1ZHksIHdlJ2xsIHdvcmsgdGhyb3VnaCBhIHJlZ3VsYXIgZ3JpZCBvZiBoeXBlcnBhcmFtZXRlciB2YWx1ZXMuDQoNCmBgYHtyIGJvb3N0X3NwZWNfZ3JpZCwgZXhlcmNpc2UgPSBGLCBleGVyY2lzZS5ldmFsID0gRixtZXNzYWdlPUYsd2FybmluZz1UfQ0KDQojIENyZWF0ZSBhIGdyaWQgb2YgdHVuaW5nIHBhcmFtZXRlcnMNCnRyZWVfZ3JpZCA8LSBncmlkX3JlZ3VsYXIodHJlZV9kZXB0aCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAjIFVzZSBkZWZhdWx0IHJhbmdlcyBpZiB5b3UgYXJlIG5vdCBzdXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxlYXJuX3JhdGUocmFuZ2UgPSBjKDAuMDEsIDAuMyksdHJhbnMgPSBOVUxMKSwgbGV2ZWxzID0gNSkNCg0KIyBEaXNwbGF5IHNvbWUgb2YgdGhlIHBlbmFsdHkgdmFsdWVzIHRoYXQgd2lsbCBiZSB1c2VkIGZvciB0dW5pbmcgDQp0cmVlX2dyaWQNCg0KYGBgDQoNClRoZSBmdW5jdGlvbiBbYGdyaWRfcmVndWxhcigpYF0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9kaWFscy9yZWZlcmVuY2UvZ3JpZF9yZWd1bGFyLmh0bWwpIGlzIGZyb20gdGhlIFtkaWFsc10oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9kaWFscy8pIHBhY2thZ2UuIEl0IGNob29zZXMgc2Vuc2libGUgdmFsdWVzIHRvIHRyeSBmb3IgZWFjaCBoeXBlcnBhcmFtZXRlcjsgaGVyZSwgd2UgYXNrZWQgZm9yIDUgb2YgZWFjaC4gU2luY2Ugd2UgaGF2ZSB0d28gdG8gdHVuZSwgYGdyaWRfcmVndWxhcigpYCByZXR1cm5zIDXDlzUgPSAyNSBkaWZmZXJlbnQgcG9zc2libGUgdHVuaW5nIGNvbWJpbmF0aW9ucyB0byB0cnkgaW4gYSB0aWR5IHRpYmJsZSBmb3JtYXQuDQoNCiMjIyMgTGV0J3Mgc2FtcGxlIG91ciBkYXRhLg0KDQpBcyB3ZSBwb2ludGVkIG91dCBlYXJsaWVyLCBoeXBlcnBhcmFtZXRlcnMgY2Fubm90IGJlIGxlYXJuZWQgZGlyZWN0bHkgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LiBJbnN0ZWFkLCB0aGV5IGFyZSBlc3RpbWF0ZWQgdXNpbmcgc2ltdWxhdGVkIGRhdGEgc2V0cyBjcmVhdGVkIGZyb20gYSBwcm9jZXNzIGNhbGxlZCByZXNhbXBsaW5nLiBPbmUgcmVzYW1wbGluZyBhcHByb2FjaCBpcyBgY3Jvc3MtdmFsaWRhdGlvbmAuDQoNCkNyb3NzLXZhbGlkYXRpb24gaW52b2x2ZXMgdGFraW5nIHlvdXIgdHJhaW5pbmcgc2V0IGFuZCByYW5kb21seSBkaXZpZGluZyBpdCB1cCBldmVubHkgaW50byBgVmAgc3Vic2V0cy9mb2xkcy4gWW91IHRoZW4gdXNlIG9uZSBvZiB0aGUgZm9sZHMgZm9yIHZhbGlkYXRpb24gYW5kIHRoZSByZXN0IGZvciB0cmFpbmluZywgdGhlbiB5b3UgcmVwZWF0IHRoZXNlIHN0ZXBzIHdpdGggYWxsIHRoZSBzdWJzZXRzIGFuZCBjb21iaW5lIHRoZSByZXN1bHRzLCB1c3VhbGx5IGJ5IHRha2luZyB0aGUgbWVhbi4gVGhpcyBpcyBqdXN0IG9uZSByb3VuZCBvZiBjcm9zcy12YWxpZGF0aW9uLiBTb21ldGltZXMgcHJhY3RpY3Rpb25lcnMgZG8gdGhpcyBtb3JlIHRoYW4gb25jZSwgcGVyaGFwcyA1IHRpbWVzLg0KDQpgYGB7ciBiaWtlX2ZvbGRzLCBleGVyY2lzZSA9IEYsIGV4ZXJjaXNlLmV2YWwgPSBGLG1lc3NhZ2U9Rix3YXJuaW5nPVR9DQpzZXQuc2VlZCgxMDIwKQ0KIyA1IGZvbGQgQ1YgcmVwZWF0ZWQgb25jZQ0KYmlrZV9mb2xkcyA8LSB2Zm9sZF9jdihkYXRhID0gYmlrZV90cmFpbiwgdiA9IDUsIHJlcGVhdHMgPSAxKQ0KDQpiaWtlX2ZvbGRzDQpgYGANCg0KIyMjIyBUaW1lIHRvIHR1bmUNCg0KTm93LCBpdCdzIHRpbWUgdG8gdHVuZSB0aGUgZ3JpZCB0byBmaW5kIG91dCB3aGljaCBwZW5hbHR5IHJlc3VsdHMgaW4gdGhlIGJlc3QgcGVyZm9ybWFuY2UhDQoNCmBgYHtyIHR1bmVfZ3JpZCwgZXhlcmNpc2UgPSBGLCBleGVyY2lzZS5ldmFsID0gRixtZXNzYWdlPUYsd2FybmluZz1GfQ0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkNCg0KIyBNb2RlbCB0dW5pbmcgdmlhIGdyaWQgc2VhcmNoDQpzZXQuc2VlZCgyMDIwKQ0KdHJlZV9ncmlkIDwtIHR1bmVfZ3JpZCgNCiAgb2JqZWN0ID0gYm9vc3Rfd29ya2Zsb3csDQogIHJlc2FtcGxlcyA9IGJpa2VfZm9sZHMsDQogIGdyaWQgPSB0cmVlX2dyaWQNCikNCmBgYA0KDQojIyMjIFZpc3VhbGl6ZSB0dW5pbmcgcmVzdWx0cw0KDQpOb3cgdGhhdCB3ZSBoYXZlIHRyYWluZWQgbW9kZWxzIGZvciBtYW55IHBvc3NpYmxlIHBlbmFsdHkgcGFyYW1ldGVyLCBsZXQncyBleHBsb3JlIHRoZSByZXN1bHRzLg0KDQpBcyBhIGZpcnN0IHN0ZXAsIHdlJ2xsIHVzZSB0aGUgZnVuY3Rpb24gKipgY29sbGVjdF9tZXRyaWNzKClgKiogdG8gZXh0cmFjdCB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcyBmcm9tIHRoZSB0dW5pbmcgcmVzdWx0cy4NCg0KYGBge3IgZ3JpZF9yZXMsIGV4ZXJjaXNlID0gRiwgZXhlcmNpc2UuZXZhbCA9IEYsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCiMgRXh0cmFjdCBwZXJmb3JtYW5jZSBtZXRyaWNzDQp0cmVlX2dyaWQgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIHNsaWNlKDE6NSkNCg0KYGBgDQoNCk9uY2Ugd2UgaGF2ZSBvdXIgdHVuaW5nIHJlc3VsdHMsIHdlIGNhbiBib3RoIGV4cGxvcmUgdGhlbSB0aHJvdWdoIHZpc3VhbGl6YXRpb24gYW5kIHRoZW4gc2VsZWN0IHRoZSBiZXN0IHJlc3VsdC4NCg0KYGBge3IgZ3JpZF92aXosIGV4ZXJjaXNlID0gRiwgZXhlcmNpc2UuZXZhbCA9IEYsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCiMgVmlzdWFsaXplIHRoZSByZXN1bHRzDQp0cmVlX2dyaWQgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIG11dGF0ZSh0cmVlX2RlcHRoID0gZmFjdG9yKHRyZWVfZGVwdGgpKSAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBsZWFybl9yYXRlLCB5ID0gbWVhbiwNCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSB0cmVlX2RlcHRoKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDAuNikgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIGZhY2V0X3dyYXAofiAubWV0cmljLCBzY2FsZXMgPSAnZnJlZScsIG5yb3cgPSAyKSsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJwbGFzbWEiLCBiZWdpbiA9IC45LCBlbmQgPSAwKQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhhdCBvdXIgInN0dWJiaWVzdCIgdHJlZSwgd2l0aCBhIGRlcHRoIG9mIDEsIGlzIHRoZSB3b3JzdCBtb2RlbCBhY2NvcmRpbmcgdG8gYm90aCBtZXRyaWNzIChybXNlLCByc3EpIGFuZCBhY3Jvc3MgYWxsIGNhbmRpZGF0ZSB2YWx1ZXMgb2YgYGxlYXJuX3JhdGVgLiBBIHRyZWUgZGVwdGggb2YgNCBhbmQgYSBsZWFybl9yYXRlIG9mIDAuMSBzZWVtcyB0byBkbyB0aGUgdHJpY2shIExldCdzIGludmVzdGlnYXRlIHRoZXNlIHR1bmluZyBwYXJhbWV0ZXJzIGZ1cnRoZXIuIFdlIGNhbiB1c2UgYHNob3dfYmVzdCgpYCB0byBkaXNwbGF5IHRoZSB0b3Agc3ViLW1vZGVscyBhbmQgdGhlaXIgcGVyZm9ybWFuY2UgZXN0aW1hdGVzLg0KDQpgYGB7ciBncmlkX3Nob3csIGV4ZXJjaXNlID0gRiwgZXhlcmNpc2UuZXZhbCA9IEYsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCnRyZWVfZ3JpZCAlPiUgDQogIHNob3dfYmVzdCgncm1zZScpDQpgYGANCg0KV2UgY2FuIHRoZW4gdXNlIGBzZWxlY3RfYmVzdCgpYCB0byBmaW5kIHRoZSB0dW5pbmcgcGFyYW1ldGVyIGNvbWJpbmF0aW9uIHdpdGggdGhlIGJlc3QgcGVyZm9ybWFuY2UgdmFsdWVzLg0KDQpgYGB7ciBiZXN0X3RyZWUsIGV4ZXJjaXNlID0gRiwgZXhlcmNpc2UuZXZhbCA9IEYsbWVzc2FnZT1GLHdhcm5pbmc9Rn0NCmJlc3RfdHJlZSA8LSB0cmVlX2dyaWQgJT4lIA0KICBzZWxlY3RfYmVzdCgncm1zZScpDQoNCmJlc3RfdHJlZQ0KYGBgDQoNCiMjIDYuIEZpbmFsaXppbmcgb3VyIG1vZGVsDQoNCk5vdyB0aGF0IHdlIGhhdmUgdGhlIGJlc3QgcGVyZm9ybWFuY2UgdmFsdWVzLCB3ZSBjYW4gdXNlIGB0dW5lOjpmaW5hbGl6ZV93b3JrZmxvdygpYCB0byB1cGRhdGUgKG9yICJmaW5hbGl6ZSIpIG91ciB3b3JrZmxvdyBvYmplY3Qgd2l0aCB0aGUgYmVzdCBlc3RpbWF0ZSB2YWx1ZXMgZm9yIHRyZWVfZGVwdGggYW5kIGxlYXJuX3JhdGUuDQoNCmBgYHtyIGZpbmFsaXplLCBleGVyY2lzZSA9IEYsIGV4ZXJjaXNlLmV2YWwgPSBGLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIFVwZGF0ZSB3b3JrZmxvdw0KZmluYWxfd2YgPC0gYm9vc3Rfd29ya2Zsb3cgJT4lIA0KICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3RyZWUpDQoNCmZpbmFsX3dmDQpgYGANCg0KT3VyIHR1bmluZyBpcyBkb25lISDwn6WzIFdlIGhhdmUgdXBkYXRlZCBvdXIgd29ya2Zsb3cgd2l0aCB0aGUgYmVzdCBlc3RpbWF0ZWQgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzIQ0KDQojIyMjIFRoZSBsYXN0IGZpdDogYmFjayB0byBvdXIgdGVzdCBzZXQuDQoNCkZpbmFsbHksIGxldCdzIHJldHVybiB0byBvdXIgdGVzdCBkYXRhIGFuZCBlc3RpbWF0ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2Ugd2UgZXhwZWN0IHRvIHNlZSB3aXRoIG5ldyBkYXRhLiBXZSBjYW4gdXNlIHRoZSBmdW5jdGlvbiBbYGxhc3RfZml0KClgXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3R1bmUvcmVmZXJlbmNlL2xhc3RfZml0Lmh0bWwpIHdpdGggb3VyIGZpbmFsaXplZCBtb2RlbDsgdGhpcyBmdW5jdGlvbiAqZml0cyogdGhlIGZpbmFsaXplZCBtb2RlbCBvbiB0aGUgZnVsbCB0cmFpbmluZyBkYXRhIHNldCBhbmQgKmV2YWx1YXRlcyogdGhlIGZpbmFsaXplZCBtb2RlbCBvbiB0aGUgdGVzdGluZyBkYXRhLg0KDQpgYGB7ciBsYXN0X2ZpdCwgZXhlcmNpc2UgPSBGLCBleGVyY2lzZS5ldmFsID0gRixtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyBNYWtlIGEgbGFzdCBmaXQNCmZpbmFsX2ZpdCA8LSBmaW5hbF93ZiAlPiUgDQogIGxhc3RfZml0KGJpa2Vfc3BsaXQpDQoNCg0KIyBDb2xsZWN0IG1ldHJpY3MNCmZpbmFsX2ZpdCAlPiUgDQogIGNvbGxlY3RfbWV0cmljcygpDQpgYGANCg0KSG93J3MgdGhhdCBmb3IgYSB0dW5lIPCfjrYg4oCL8J+Sg+KAi/CflbrigIvigIvigIshIEFsc28sIHRoZXJlIHNlZW1zIHRvIGJlIHNvbWUgaW1wcm92ZW1lbnQgaW4gdGhlIGV2YWx1YXRpb24gbWV0cmljcyBjb21wYXJlZCB0byB1c2luZyB0aGUgZGVmYXVsdCB2YWx1ZXMgZm9yICpsZWFybl9yYXRlKiBhbmQgKnRyZWVfZGVwdGgqIGh5cGVycGFyYW1ldGVycy4gTm93LCB3ZSBsZWF2ZSBpdCB0byB5b3UgdG8gZXhwbG9yZSBob3cgdHVuaW5nIHRoZSBvdGhlciBoeXBlcnBhcmFtZXRlcnMgYWZmZWN0cyB0aGUgbW9kZWwgcGVyZm9ybWFuY2UuDQoNCldlJ3ZlIG5vdyBzZWVuIGEgbnVtYmVyIG9mIGNvbW1vbiB0ZWNobmlxdWVzIHVzZWQgdG8gdHJhaW4gcHJlZGljdGl2ZSBtb2RlbHMgZm9yIHJlZ3Jlc3Npb24uIEluIGEgcmVhbCBwcm9qZWN0LCB5b3UnZCBsaWtlbHkgdHJ5IGEgZmV3IG1vcmUgYWxnb3JpdGhtcywgaHlwZXJwYXJhbWV0ZXJzLCBhbmQgcHJlcHJvY2Vzc2luZyB0cmFuc2Zvcm1hdGlvbnM7IGJ1dCBieSBub3cgeW91IHNob3VsZCBoYXZlIGdvdCB0aGUgZ2VuZXJhbCBpZGVhIG9mIHRoZSBwcm9jZWR1cmUgdG8gZm9sbG93LiBZb3UgY2FuIGV4cGxvcmUgdGhlIFtyZWZlcmVuY2UgZG9jc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvZmluZC9wYXJzbmlwLyNtb2RlbHMpLCBvciB1c2UgdGhlIGBhcmdzKClgIGZ1bmN0aW9uIHRvIHNlZSB3aGljaCBwYXJzbmlwIG9iamVjdCBhcmd1bWVudHMgYXJlIGF2YWlsYWJsZS4NCg0KPiBVc2UgdGhpcyBbVGlkeW1vZGVscyByZWZlcmVuY2UgcGFnZV0oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvZmluZC9wYXJzbmlwLyNtb2RlbHMpIHRvIGV4cGxvcmUgbW9kZWwgdHlwZXMgYW5kIGVuZ2luZXMgYW5kIHRvIGV4cGxvcmUgbW9kZWwgYXJndW1lbnRzLg0KDQpMZXQncyBub3cgZXhwbG9yZSBob3cgeW91IGNhbiB1c2UgdGhlIHRyYWluZWQgbW9kZWwgd2l0aCBuZXcgZGF0YS4NCg0KIyMjIyBVc2UgdGhlIFRyYWluZWQgTW9kZWwNCg0KV2UnbGwgYmVnaW4gYnkgc2F2aW5nIG91ciBtb2RlbCBidXQgZmlyc3QsIGxldCdzIGV4dHJhY3QgdGhlICp0cmFpbmVkIHdvcmtmbG93KiBvYmplY3QgZnJvbSBgZmluYWxfZml0YCBvYmplY3QuDQoNCmBgYHtyIGxhc3RfZml0X3dmLCBleGVyY2lzZSA9IEYsIGV4ZXJjaXNlLmV2YWwgPSBGLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIEV4dHJhY3QgdHJhaW5lZCB3b3JrZmxvdw0KYmlrZV9ib29zdF9tb2RlbCA8LSBmaW5hbF9maXQkLndvcmtmbG93W1sxXV0NCg0KYGBgDQoNCk5vdywgd2UgY2FuIHNhdmUgdGhpcyBtb2RlbCB0byBiZSB1c2VkIGxhdGVyLg0KDQpgYGB7ciBzX2xhc3RfZml0X3dmLCBleGVyY2lzZSA9IEYsIGV4ZXJjaXNlLmV2YWwgPSBGLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIFNhdmUgdHJhaW5lZCB3b3JrZmxvdw0Kc2F2ZVJEUyhiaWtlX2Jvb3N0X21vZGVsLCBoZXJlOjpoZXJlKCJtb2RlbHMiLCAiYmlrZV9ib29zdF9tb2RlbC5yZHMiKSkNCmJlZXByOjpiZWVwKDApDQpgYGANCg0KTm93LCB3ZSBjYW4gbG9hZCBpdCB3aGVuZXZlciB3ZSBuZWVkIGl0LCBhbmQgdXNlIGl0IHRvIHByZWRpY3QgbGFiZWxzIGZvciBuZXcgZGF0YS4gVGhpcyBpcyBvZnRlbiBjYWxsZWQgKmBzY29yaW5nYCogb3IgKmBpbmZlcmVuY2luZ2AqLg0KDQpGb3IgZXhhbXBsZSwgbGV0cyB0cnkgYW5kIHByZWRpY3Qgc29tZSB2YWx1ZXMgZnJvbSBvdXIgdGVzdCBzZXQgdXNpbmcgdGhlIHNhdmVkIG1vZGVsLg0KDQpgYGB7ciBkZW1vX2xhc3RfZml0X3dmLCBleGVyY2lzZSA9IEYsIGV4ZXJjaXNlLmV2YWwgPSBGLG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQojIEV4dHJhY3QgcHJlZGljdG9ycw0KYmlrZV9uZXcgPC0gYmlrZV90ZXN0ICU+JSANCiAgc2xpY2UoNTo5KQ0KDQojIExvYWQgdGhlIG1vZGVsDQpsb2FkZWRfbW9kZWwgPC0gcmVhZFJEUygibW9kZWxzL2Jpa2VfYm9vc3RfbW9kZWwucmRzIikNCg0KDQojIFVzZSB0aGUgbW9kZWwgdG8gcHJlZGljdCByZW50YWxzDQpyZXN1bHRzIDwtIGJpa2VfbmV3ICU+JSANCiAgYmluZF9jb2xzKGxvYWRlZF9tb2RlbCAlPiUgcHJlZGljdChuZXdfZGF0YSA9IGJpa2VfbmV3KSkNCg0KcmVzdWx0cw0KDQpgYGANCg0KUGVSZmVjdCHwn5CxIEFsbCBpcyB3ZWxsIHRoYXQgZW5kcyB3aXRoIGEgd29ya2luZyBtb2RlbCwgdGltZSB0byBlbmQgdGhlIGN5Y2xlICoq8J+atCoqIQ0KDQojIyA3LiBTdW1tYXJ5DQoNCldlIG5lZWQgYSBiUmFrZSwgZG9uJ3Qgd2U/IPCfmIUgV2UgaG9wZSB5b3UgaGFkIGEgd2hlZWxpZSBnb29kIHRpbWUhDQoNCkluIHRoaXMgbW9kdWxlLCB3ZSBsZWFybnQgaG93IHJlZ3Jlc3Npb24gY2FuIGJlIHVzZWQgdG8gY3JlYXRlIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCB0aGF0IHByZWRpY3RzIG51bWVyaWMgdmFsdWVzLiBXZSBjeWNsZWQgb2ZmIGJ5IGRvaW5nIHNvbWUgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyB1c2luZyB0aGUgYFRpZHl2ZXJzZWAgdGhlbiB3ZSB1c2VkIHRoZSBgVGlkeW1vZGVsc2AgZnJhbWV3b3JrIGluIGBSYCB0byB0cmFpbiBhbmQgZXZhbHVhdGUgYSByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIGRpZmZlcmVudCBhbGdvcml0aG1zLCBkbyBzb21lIGRhdGEgcHJlcHJvY2Vzc2luZywgdHVuZWQgc29tZSBoeXBlcnBhcmFtZXRlcnMgYW5kIG1hZGUgYmV0dGVyIHByZWRpY3Rpb25zLg0KDQpXaGlsZSBgVGlkeW1vZGVsc2AgYW5kIGBzY2lraXQtbGVhcm5gIChQeXRob24pIGFyZSBwb3B1bGFyIGZyYW1ld29yayBmb3Igd3JpdGluZyBjb2RlIHRvIHRyYWluIHJlZ3Jlc3Npb24gbW9kZWxzLCB5b3UgY2FuIGFsc28gY3JlYXRlIG1hY2hpbmUgbGVhcm5pbmcgc29sdXRpb25zIGZvciByZWdyZXNzaW9uIHVzaW5nIHRoZSBncmFwaGljYWwgdG9vbHMgaW4gTWljcm9zb2Z0IEF6dXJlIE1hY2hpbmUgTGVhcm5pbmcuIFlvdSBjYW4gbGVhcm4gbW9yZSBhYm91dCBuby1jb2RlIGRldmVsb3BtZW50IG9mIHJlZ3Jlc3Npb24gbW9kZWxzIHVzaW5nIEF6dXJlIE1hY2hpbmUgTGVhcm5pbmcgaW4gdGhlIFtDcmVhdGUgYSBSZWdyZXNzaW9uIE1vZGVsIHdpdGggQXp1cmUgTWFjaGluZSBMZWFybmluZyBkZXNpZ25lcl0oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vZW4tdXMvbGVhcm4vbW9kdWxlcy9jcmVhdGUtcmVncmVzc2lvbi1tb2RlbC1henVyZS1tYWNoaW5lLWxlYXJuaW5nLWRlc2lnbmVyLykgbW9kdWxlLg0KDQojIyMjICoqQ2hhbGxlbmdlOiBQcmVkaWN0IFJlYWwgRXN0YXRlIFByaWNlcyoqDQoNClRoaW5rIHlvdSdyZSByZWFkeSB0byBjcmVhdGUgeW91ciBvd24gcmVncmVzc2lvbiBtb2RlbD8gVHJ5IHRoZSBjaGFsbGVuZ2Ugb2YgcHJlZGljdGluZyByZWFsIGVzdGF0ZSBwcm9wZXJ0eSBwcmljZXMgaW4gdGhlIFswMiAtIFJlYWwgRXN0YXRlIFJlZ3Jlc3Npb24gQ2hhbGxlbmdlLmlweW5iXShodHRwczovL2dpdGh1Yi5jb20vTWljcm9zb2Z0RG9jcy9tbC1iYXNpY3MvYmxvYi9tYXN0ZXIvY2hhbGxlbmdlcy8wMiUyMC0lMjBSZWFsJTIwRXN0YXRlJTIwUmVncmVzc2lvbiUyMENoYWxsZW5nZS5pcHluYikgbm90ZWJvb2shIEZpbmQgdGhlIGRhdGEgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcy90cmVlL21hc3Rlci9jaGFsbGVuZ2VzL2RhdGEpLg0KDQojIyMjIFRIQU5LIFlPVSBUTzoNCg0KYEFsbGlzb24gSG9yc3RgIGZvciBjcmVhdGluZyB0aGUgYW1hemluZyBpbGx1c3RyYXRpb25zIHRoYXQgbWFrZSBSIG1vcmUgd2VsY29taW5nIGFuZCBlbmdhZ2luZy4gRmluZCBtb3JlIGlsbHVzdHJhdGlvbnMgYXQgaGVyIFtnYWxsZXJ5XShodHRwczovL3d3dy5nb29nbGUuY29tL3VybD9xPWh0dHBzOi8vZ2l0aHViLmNvbS9hbGxpc29uaG9yc3Qvc3RhdHMtaWxsdXN0cmF0aW9ucyZzYT1EJnNvdXJjZT1lZGl0b3JzJnVzdD0xNjI2MzgwNzcyNTMwMDAwJnVzZz1BT3ZWYXczemNmeUNpekZRWnBrU0x6eGlpUUVNKS4NCg0KYEJldGhhbnlgLCAqR29sZCBNaWNyb3NvZnQgTGVhcm4gU3R1ZGVudCBBbWJhc3NhZG9yKiwgZm9yIHRoZSB2YWx1YWJsZSBmZWVkYmFjayBhbmQgc3VnZ2VzdGlvbnMuDQoNCiMjIyMgRlVSVEhFUiBSRUFESU5HDQoNCi0gICBNYXggS3VobiBhbmQgSnVsaWEgU2lsZ2UsIFsqVGlkeSBNb2RlbGluZyB3aXRoIFIqXShodHRwczovL3d3dy50bXdyLm9yZy8pKi4qDQoNCi0gICBLdWhuLCBNLCBhbmQgSyBKb2huc29uLiAyMDEzLiAqQXBwbGllZCBQcmVkaWN0aXZlIE1vZGVsaW5nKi4gU3ByaW5nZXIuDQoNCi0gICBCcmFkbGV5IEJvZWhta2UgJiBCcmFuZG9uIEdyZWVud2VsbCwgWypIYW5kcy1PbiBNYWNoaW5lIExlYXJuaW5nIHdpdGggUipdKGh0dHBzOi8vYnJhZGxleWJvZWhta2UuZ2l0aHViLmlvL0hPTUwvKSouKg0KDQotICAgSm9yZ2UgQ2ltZW50YWRhLCBbKk1hY2hpbmUgTGVhcm5pbmcgZm9yIFNvY2lhbCBTY2llbnRpc3RzKl0oaHR0cHM6Ly9jaW1lbnRhZGFqLmdpdGh1Yi5pby9tbF9zb2NzY2kvKSouKg0KDQotICAgVGlkeSBtb2RlbHMgW3JlZmVyZW5jZSB3ZWJzaXRlXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy9zdGFydC8pLg0KDQotICAgSC4gV2lja2hhbSBhbmQgRy4gR3JvbGVtdW5kLCBbKlIgZm9yIERhdGEgU2NpZW5jZTogVmlzdWFsaXplLCBNb2RlbCwgVHJhbnNmb3JtLCBUaWR5LCBhbmQgSW1wb3J0IERhdGEqXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykuDQoNCkJlIHN1cmUgdG8gam9pbiB1cyBpbiB0aGUgbmV4dCBSIG1vZHVsZSB3aGVyZSB3ZSdsbCBzbGljZSBhbmQgZGljZSBzb21lIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMuDQoNClRpbGwgdGhlbiAuLi4NCg0KSGFwcHkgbGVhcm5pbmcsDQoNCltFcmljIChSX2ljKV0oaHR0cHM6Ly90d2l0dGVyLmNvbS9lcmljbnRheSksICpHb2xkIE1pY3Jvc29mdCBMZWFybiBTdHVkZW50IEFtYmFzc2Fkb3IqLg0KDQohW0FydHdvcmsgYnkgXEBhbGxpc29uX2hvcnN0XShpbWFnZXMvcl9sZWFybmVyc19zbS5qcGVnKXt3aWR0aD0iNTY5In0NCg0KDQoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgcmVzdWx0YnMgPC0gcnNjb25uZWN0OjpycHVic1VwbG9hZCgNCiMgdGl0bGUgPSAnQ3JlYXRlIG1hY2hpbmUgbGVhUm5pbmcgbW9kZWxzOiBUcmFpbiBhbmQgRXZhbHVhdGUgUmVncmVzc2lvbiBNb2RlbHMnLA0KIyBjb250ZW50RmlsZSA9ICJDOi9Vc2Vycy9BRE1JTi9EZXNrdG9wL0ludG9kdWN0aW9uIHRvIFB5dGhvbiBmb3IgZGF0YSBzY2llbmNlL1IgZm9yIGRhdGEgc2NpZW5jZS9hUmR1aW5vL2NyZWF0ZV9tYWNoaW5lX2xlYXJuaW5nX21vZGVsc19SLzAyX1JlZ3Jlc3Npb24vVHJhaW5fZXZhbHVhdGVfcmVncmVzc2lvbl9tb2RlbHMuaHRtbCIsDQojIG9yaWdpbmFsRG9jID0gIlRyYWluX2V2YWx1YXRlX3JlZ3Jlc3Npb25fbW9kZWxzLlJtZCIpDQpgYGANCg0KDQoNCg==