Data Science Stream
Topic 9B: Machine Learning I
Welcome to the ninth computer lab for the Data Science stream of STM1001.
In this computer lab we will practice fitting our first machine learning model.
This computer lab is designed to run alongside the content in the Introduction to Machine Learning in R supplement. The material in this supplement provides all the background information on machine learning and machine learning terminology you will need to complete this lab.
The amount of material in this lab is smaller than usual, to ensure that you have plenty of time to read over the different sections of the Introduction to Machine Learning in R supplement as we go.
By the end of this lab, you should have developed a solid foundational understanding of how to conduct simple supervised machine learning tasks in RStudio. You should be starting to feel comfortable preparing data for supervised machine learning tasks, and know how to assess the performance of a machine learning model. In future weeks we will develop your ML skills further.
Let’s get started!
Preparations
🏡 Before we proceed, please make sure you have read at least sections 1 and 2 of the Introduction to Machine Learning in R supplement - if you haven’t, these lab questions will be unnecessarily difficult to understand and complete. It will be helpful to keep this content open in a separate tab while you work through this lab.
Load Required Packages
💻 In order to conduct our machine learning processes in RStudio in this lab and in subsequent labs, we will need to install and load several R packages, chief among which is the caret
package (Kuhn et al. 2021).
Run the code below to install and load the R packages required for this lab:
# Install packages
install.packages(c("caret", "magrittr", "rpart.plot"))
# Load packages
library(caret)
library(rpart.plot)
Predicting Penguin Species
💻 In Section 3 onwards of the Introduction to Machine Learning in R supplement, we introduced various ML pre-processing techniques via an example task involving the familiar penguins
data set from the palmerpenguins
R package (Horst, Hill, and Gorman 2020).
To ease us into learning about machine learning, we will focus on extending this example in this lab. Specifically, we will aim to train a simple machine learning model which can accurately predict the species
of a penguin living in the Palmer Archipelago, using a number of feature variables.
Penguin Data
💻 For our machine learning work, we will use the following feature variables from the penguins
data set:
island
bill_length_mm
bill_depth_mm
flipper_length_mm
body_mass_g
sex
Our outcome variable will remain species
, with the options Adelie
, Chinstrap
and Gentoo
.
💻 Run the code below to load the penguins
data in RStudio, select the chosen variables, and assign them to the object ml_penguins
:
library(palmerpenguins)
ml_penguins <- na.omit(penguins[, -8]) # note we ignore the year variable here
Aim
🏡 Our aim is to train a machine learning model which can accurately predict the species
of penguin, based on inputs from the feature variables specified in 2.1 above.
What type of problem class would this task classify as?
🎧 Online students
💬 Enter your answer next to the question on the shared jamboard.
Initial Data Visualisation
🏡 We can use the featurePlot
function from the caret
package to produce scatter plots of the observed values for all the feature variables in our data set.
Run the code below to do this:
featurePlot(x = ml_penguins[, -1], y = ml_penguins$species,
plot = "pairs", auto.key = list(columns = 3))
🏡 Based on the scatter plots produced in 2.4, can you think of any potential problems we might encounter when trying to predict certain species?
Note: It’s ok if you’re not sure about this yet - after all, we have only just started learning about machine learning. For more details about this scatter plot matrix, refer to Section 3.1.2 of the Introduction to Machine Learning in R supplement.
🎧 Online students
💬 Enter your answer next to the question on the shared jamboard.
Pre-Processing the Penguin data
💻 Before we begin fitting a machine learning model, we should conduct some pre-processing checks, as outlined in Section 3.2 of the Introduction to Machine Learning in R supplement.
Dummy Variables
💻 First, note that two of our feature variables, namely island
and sex
, are categorical. Therefore we will need to create some dummy variables - remember, all the feature variables we use in our ML model need to be numeric in format.
Following the code provided in Section 3.2.1 of the Introduction to Machine Learning in R supplement, reclassify the island
and sex
feature variables as dummy variables.
Name your updated object ml_penguins_updated
.
Hint: If you are not sure how to proceed, check the code chunk below:
# Load a package to help with the restructure of the data
library(tibble)
# Use the dummayVars function to create a full set of dummy variables for the ml_penguins data
dummy_penguins <- dummyVars(species ~ ., data = ml_penguins)
# Use the predict function to update our ml_penguins feature variables with
# both island and sex dummy variables
ml_penguins_updated <- as_tibble(predict(dummy_penguins, newdata = ml_penguins))
# Prepend the outcome variable to our updated data set, otherwise it will be lost
ml_penguins_updated <- cbind(species = ml_penguins$species, ml_penguins_updated)
💻 Note that we have three dummy variables now for island
, as there are three islands, and we have two dummy variables for sex
.
Use the head
command to check the first few rows of data in ml_penguins_updated
, and make sure you understand the new notation before proceeding.
Highly Influential Samples
💻 Our next step is to check for and remove any samples in our data which could exert excessive influence on the fit of our ML model.
We can use the function nearZeroVar
from the caret
package to obtain details on the freqRatio
and percentUnique
values for each of the variables in our data set.
Use the nearZeroVar
function to assess the feature variables in ml_penguins_updated
. Run this function twice, once with saveMetrics = T
and once with saveMetrics = F
.
Hint: If you are not sure how to proceed, check section 3.2.3 of the Introduction to Machine Learning in R supplement.
💻 Recall that the nearZeroVar
function can include additional arguments, freqCut
and uniqueCut
, that specify cut-off values for the freqRatio
and percentUnique
results respectively.
Re-run your code from 3.3, and this time specify a cut-off value of 2 for the freqRatio
values and a cut-off value of 5 for the percentUnique
values.
Note: For details on specifying cut-off values, check section 3.2.4 of the Introduction to Machine Learning in R supplement.
💻 Based on the nearZeroVar
function results from 3.3.1, check for potentially problematic variables.
Which feature variable has the highest freqRatio
value, and which feature variable has the lowest percentUnique
value?
Are there any feature variables which you would recommend removing? Why or why not?
🎧 Online students
💬 Enter your answer next to the question on the shared jamboard.
💻 Next, we should check for correlated feature variables.
Generally, some correlation between feature variables is to be expected, but often it is beneficial to remove highly correlated feature variables from our data.
Run the code below to compute a correlation matrix for the ml_penguins_updated
feature variables, and to check for extreme correlations close to 1 in magnitude.
base_cor <- cor(ml_penguins_updated[, 5:8])
extreme_cor <- sum(abs(base_cor[upper.tri(base_cor)]) > .999)
extreme_cor
Note: We do not consider dummy variables here, since they originate from categorical variables.
Hint: Refer to section 3.2.5 of the Introduction to Machine Learning in R supplement for additional details.
💻 Run the base_cor
object to assess the spread of correlation values.
What are the largest negative and positive correlation values? Do these seem problematic?
🎧 Online students
💬 Enter your answer next to the question on the shared jamboard.
💻 Suppose our correlation limit for highly correlated feature variables is a relatively strict value of 0.7.
If we run the code findCorrelation(base_cor, cutoff = .7)
, we obtain the output 3
.
At first, that can seem somewhat unhelpful. What exactly does the 3
mean here?
Well, it is telling us that, within the subset of feature variables assessed in the base_cor
object, the third variable exceeds our specified cut-off value of 0.7.
If we check our base_cor
object, we see that the feature variable in column 3 is flipper_length_mm
, which has a high correlation of 0.873 with body_mass_g
.
💻 If we would now like to remove the feature variable flipper_length_mm
from our data set, we need to be careful. Column 3 in the base_cor
matrix does not correspond to column 3 in our ml_penguins_updated
object - we only assessed columns 5 to 8 of ml_penguins_updated
when computing correlations.
Therefore we actually want to remove column 7 from our ml_penguins_updated
object. Run the code below to do this now:
ml_penguins_filtered <- ml_penguins_updated[, - 7] # flipper_length_mm has been removed
If we compute a new correlation matrix for the non-dummy feature variables in our filtered data set, we see that the highest magnitude correlation value is now 0.589451 - much better!
Note: When removing a variable from a data set, it is always a good idea to check your new object, e.g. head(ml_penguins_filtered)
to verify you have removed the intended variable.
Training and Validation Data
💻 Before we train our ML model, our final step is to split our data into training and validation sets.
Use the createDataPartition
function from the caret
package to split the ml_penguins_filtered
data 80/20.
Note: The data partitioning into training or validation categories is random to an extent, so if you do not run the set.seed(1650)
commands shown in the code chunks below, your results from this point onwards may differ slightly to those presented in the subsequent question solutions, since your training and validation data sets will most likely contain slightly different sets of observations.
The code below is partially completed, just fill in the ...
missing parts:
set.seed(1650)
train_index <- createDataPartition(... ,
p = ... ,
list = FALSE, times = 1)
Hint: Remember that the argument p
denotes the split. If you are stuck, you can check section 4.1 of the Introduction to Machine Learning in R supplement, and/or the code chunk below:
set.seed(1650)
train_index <- createDataPartition(ml_penguins_filtered$species,
p = .8, # here p designates the split - 80/20
list = FALSE, times = 1)
💻 Next, assign the ml_penguins_filtered
data into the training and validation sets, and name these penguin_train
and penguin_validate
respectively. Check the code below for a head start:
penguin_validate <- ml_penguins_filtered[-train_index, ]
Hint: If you are stuck, you can check the code chunk below:
# Note here we are using the values in the train_index object
# (whereas for the validation set, we select the values not in the train_index object)
penguin_train <- ml_penguins_filtered[train_index, ]
Fitting a Decision Tree Machine Learning Model
💻 Now that the preparation phase is finally complete, we are ready to fit our first machine learning model.
The focus in this lab will be to introduce you to the train
function from the caret
package. We can fit a variety of machine learning models using this function (although some will also require other packages). We will start with a simple model, the Decision Tree, and then extend to other models in future labs.
Using the train
function, the basic code framework to fit each model is as follows:
object <- train(... ~ ., # specify relationship between outcome and feature variables
data = ... , # specify training data
method = "specify method here")
Regardless of what algorithm you use, there will be three main arguments you will need to include in your train
function:
- The relationship between the outcome variable and the feature variables
- The data set
- The method/algorithm to use
Let’s cover these in more detail.
1: In the first argument we specify the relationship between the outcome variable and the feature variables.
For example, if our outcome variable was called outcome
, and we had two feature variables, feature1
and feature2
, the first part of our code could look like this:
object <- train(outcome ~ feature1 + feature2,
...)
In general however, we will have more than two feature variables to include (sometimes dozens more!). Therefore, we can use the shortcut outcome ~.
to specify that all variables in the data set, apart from outcome
, should be included as feature variables in the model.
As a result, when training a supervised learning machine learning model using the train
function, typically all you will need to do when specifying your first argument is identify the name of your outcome variable, and include this name in place of outcome
in outcome ~.
.
2: For the data
argument, you will need to specify your pre-processed data set.
3: For the method
argument, you will need to specify the machine learning method you would like to use - each has a different name.
Some models will include additional arguments, usually specified within the argument tuneGrid
, and we will explain these where relevant.
Let’s begin.
Decision Tree
💻 One of the simplest machine learning models we can use is a decision tree.
Using the information in 5, and the partially complete code in the code chunk below, fit a decision tree to your pre-processed penguin_train
training data.
set.seed(1650)
penguin_decision_tree <- train(... ~ .,
data = ...,
method = "rpart")
Note: The decision tree method name is rpart
(which is unintuitive).
Once you are happy with your code, run it, and then run the object penguin_decision_tree
to see the output. Your output should look like the output in the code chunk below:
CART
268 samples
8 predictor
3 classes: 'Adelie', 'Chinstrap', 'Gentoo'
No pre-processing
Resampling: Bootstrapped (25 reps)
Summary of sample sizes: 268, 268, 268, 268, 268, 268, ...
Resampling results across tuning parameters:
cp Accuracy Kappa
0.01324503 0.9436108 0.9117821
0.35761589 0.8089728 0.6885822
0.56291391 0.5649392 0.2597508
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.01324503.
💻 We are mainly interested here in the Accuracy
values, for different tuning parameter values (we can ignore the Kappa
values).
As we can see, the best accuracy achieved was 94.36%, which is very impressive.
Note: Don’t worry if your results look slightly different - perhaps you did not run all the set.seed(1650)
commands?
💻 Use the rpart.plot
function (as shown below) to visualise the penguin_decision_tree
decision tree model.
rpart.plot(penguin_decision_tree$finalModel)
Recall that the values under the penguins’ species
names in the coloured boxes (nodes) show the percentages of Adelie, Chinstrap and Gentoo penguins respectively, that have been categorised as belonging to that node of the decision tree.
Note: The rpart.plot
package required here should have been installed and loaded in 1.
🎧 Online students
💬 Volunteer to share your screen and explain your answers to this question.
Validating Results
💻 While we have a predictive accuracy estimate for our decision tree model, it is important to remember that this has been computed using the training data.
We would also like to check how the model performs when presented with new data - i.e. our validation data!
When conducting machine learning, there is a risk of overfitting our models to our training data. This can result in the models having excellent accuracy when assessing the training data, but having subpar performance when presented with new data.
This is why we have put aside some of our data as validation data in 4, so that we can perform cross-validation.
If the accuracy of the model remains similar when presented with the validation data, then we can be more confident in our model’s reported performance.
💻 There are several ways to perform cross-validation. One of the simplest is demonstrated in section 4.3 of the Introduction to Machine Learning in R supplement.
An example application of this approach to the penguin_decision_tree
model results is shown below. Inspect and then run this code.
# Load magrittr package for piping
library(magrittr)
# count number of observations in validation data
validation_numbers <- nrow(penguin_validate)
# Use the fitted model to predict quality values given the validation data
predict_penguin_decision_tree <- predict(penguin_decision_tree,
newdata =penguin_validate)
# When run, the code below gives us the percentage of correct predictions
dec_tree_accuracy <- sum(predict_penguin_decision_tree ==
penguin_validate$species) / validation_numbers * 100
dec_tree_accuracy %>% round(2)
💻 Discuss the results of the cross-validation with your class. Do you think the decision tree ML model we have trained is a good model?
🎧 Online students
💬 Enter your answer next to the question on the shared jamboard.
Great work, that’s everything for today. Don’t worry if you did not complete everything in the designated lab time, there is a lot to learn!
Hopefully this lab has provided you with a better understanding of the fundamentals of machine learning - as we can see, it’s actually not that complicated to train a machine learning model in RStudio.
Next week, we will continue learning about machine learning, and focus on a new data set.
References
Horst, Allison Marie, Alison Presmanes Hill, and Kristen B Gorman. 2020.
Palmerpenguins: Palmer Archipelago (Antarctica) Penguin Data.
https://doi.org/10.5281/zenodo.3960218.
Kuhn, M., J. Wing, S. Weston, A. Williams, C. Keefer, A. Engelhardt, T. Cooper, et al. 2021.
caret: Classification and Regression Training.
https://cran.r-project.org/web/packages/caret/index.html.
Thulin, M. 2021. Modern Statistics with R: From Wrangling and Exploring Data to Inference and Predictive Modelling.
These notes have been prepared by Rupert Kuveke. Please note that some of the content in these notes has been developed from content in Thulin (2021). The copyright for the material in these notes resides with the authors named above, with the Department of Mathematical and Physical Sciences and with La Trobe University. Copyright in this work is vested in La Trobe University including all La Trobe University branding and naming. Unless otherwise stated, material within this work is licensed under a Creative Commons Attribution-Non Commercial-Non Derivatives License
BY-NC-ND.
LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA5QiINCm91dHB1dDoNCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOiANCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQpiaWJsaW9ncmFwaHk6IFNUTTEwMDFfRFNfQ0xfcmVmZXJlbmNlcy5iaWIgDQpsaW5rLWNpdGF0aW9uczogeWVzDQotLS0NCg0KPHN0eWxlPg0KI1RPQyB7DQogIGJhY2tncm91bmQ6IHVybCgiaHR0cHM6Ly93d3cubGF0cm9iZS5lZHUuYXUvX21lZGlhL2xhLXRyb2JlLWFwaS92NS9pbWcvbG9nby5zdmciKTsNCiAgYmFja2dyb3VuZC1zaXplOiBjb250YWluOw0KICBwYWRkaW5nLXRvcDogODBweCAhaW1wb3J0YW50Ow0KICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0Ow0KfQ0KPC9zdHlsZT4NCg0KIyMjIERhdGEgU2NpZW5jZSBTdHJlYW0gey19DQoNCiMjIyBUb3BpYyA5QjogTWFjaGluZSBMZWFybmluZyBJIHstfQ0KDQo8YnI+DQoNCldlbGNvbWUgdG8gdGhlIG5pbnRoIGNvbXB1dGVyIGxhYiBmb3IgdGhlIERhdGEgU2NpZW5jZSBzdHJlYW0gb2YgU1RNMTAwMS4NCg0KSW4gdGhpcyBjb21wdXRlciBsYWIgd2Ugd2lsbCBwcmFjdGljZSBmaXR0aW5nIG91ciBmaXJzdCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsLg0KDQpUaGlzIGNvbXB1dGVyIGxhYiBpcyBkZXNpZ25lZCB0byBydW4gYWxvbmdzaWRlIHRoZSBjb250ZW50IGluIHRoZSBbSW50cm9kdWN0aW9uIHRvIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBzdXBwbGVtZW50XShodHRwczovL2Jvb2tkb3duLm9yZy9yZWhrL3N0bTEwMDFfZHNtX2ludHJvZHVjdGlvbl90b19tYWNoaW5lX2xlYXJuaW5nX2luX3IvKS4gVGhlIG1hdGVyaWFsIGluIHRoaXMgc3VwcGxlbWVudCBwcm92aWRlcyBhbGwgdGhlIGJhY2tncm91bmQgaW5mb3JtYXRpb24gb24gbWFjaGluZSBsZWFybmluZyBhbmQgbWFjaGluZSBsZWFybmluZyB0ZXJtaW5vbG9neSB5b3Ugd2lsbCBuZWVkIHRvIGNvbXBsZXRlIHRoaXMgbGFiLiANCg0KVGhlIGFtb3VudCBvZiBtYXRlcmlhbCBpbiB0aGlzIGxhYiBpcyBzbWFsbGVyIHRoYW4gdXN1YWwsIHRvIGVuc3VyZSB0aGF0IHlvdSBoYXZlIHBsZW50eSBvZiB0aW1lIHRvIHJlYWQgb3ZlciB0aGUgZGlmZmVyZW50IHNlY3Rpb25zIG9mIHRoZSBbSW50cm9kdWN0aW9uIHRvIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBzdXBwbGVtZW50XShodHRwczovL2Jvb2tkb3duLm9yZy9yZWhrL3N0bTEwMDFfZHNtX2ludHJvZHVjdGlvbl90b19tYWNoaW5lX2xlYXJuaW5nX2luX3IvKSBhcyB3ZSBnby4NCg0KQnkgdGhlIGVuZCBvZiB0aGlzIGxhYiwgeW91IHNob3VsZCBoYXZlIGRldmVsb3BlZCBhIHNvbGlkIGZvdW5kYXRpb25hbCB1bmRlcnN0YW5kaW5nIG9mIGhvdyB0byBjb25kdWN0IHNpbXBsZSBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgdGFza3MgaW4gUlN0dWRpby4gWW91IHNob3VsZCBiZSBzdGFydGluZyB0byBmZWVsIGNvbWZvcnRhYmxlIHByZXBhcmluZyBkYXRhIGZvciBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgdGFza3MsIGFuZCBrbm93IGhvdyB0byBhc3Nlc3MgdGhlIHBlcmZvcm1hbmNlIG9mIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbC4gSW4gZnV0dXJlIHdlZWtzIHdlIHdpbGwgZGV2ZWxvcCB5b3VyIE1MIHNraWxscyBmdXJ0aGVyLiANCg0KTGV0J3MgZ2V0IHN0YXJ0ZWQhDQoNCjxicj4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQpgciBlbW86OmppKCJob3VzZV93aXRoX2dhcmRlbiIpYCBCZWZvcmUgd2UgcHJvY2VlZCwgcGxlYXNlIG1ha2Ugc3VyZSB5b3UgaGF2ZSByZWFkIGF0IGxlYXN0IHNlY3Rpb25zIDEgYW5kIDIgb2YgdGhlIFtJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci8pIC0gaWYgeW91IGhhdmVuJ3QsIHRoZXNlIGxhYiBxdWVzdGlvbnMgd2lsbCBiZSB1bm5lY2Vzc2FyaWx5IGRpZmZpY3VsdCB0byB1bmRlcnN0YW5kIGFuZCBjb21wbGV0ZS4gSXQgd2lsbCBiZSBoZWxwZnVsIHRvIGtlZXAgdGhpcyBjb250ZW50IG9wZW4gaW4gYSBzZXBhcmF0ZSB0YWIgd2hpbGUgeW91IHdvcmsgdGhyb3VnaCB0aGlzIGxhYi4NCg0KIyMgTG9hZCBSZXF1aXJlZCBQYWNrYWdlcyB7I2xvYWR9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEluIG9yZGVyIHRvIGNvbmR1Y3Qgb3VyIG1hY2hpbmUgbGVhcm5pbmcgcHJvY2Vzc2VzIGluIFJTdHVkaW8gaW4gdGhpcyBsYWIgYW5kIGluIHN1YnNlcXVlbnQgbGFicywgd2Ugd2lsbCBuZWVkIHRvIGluc3RhbGwgYW5kIGxvYWQgc2V2ZXJhbCBSIHBhY2thZ2VzLCBjaGllZiBhbW9uZyB3aGljaCBpcyB0aGUgYGNhcmV0YCBwYWNrYWdlIFtAY2FyZXRdLg0KDQpSdW4gdGhlIGNvZGUgYmVsb3cgdG8gaW5zdGFsbCBhbmQgbG9hZCB0aGUgUiBwYWNrYWdlcyByZXF1aXJlZCBmb3IgdGhpcyBsYWI6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiMgSW5zdGFsbCBwYWNrYWdlcw0KaW5zdGFsbC5wYWNrYWdlcyhjKCJjYXJldCIsICJtYWdyaXR0ciIsICJycGFydC5wbG90IikpDQoNCiMgTG9hZCBwYWNrYWdlcw0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCmBgYA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGluY2x1ZGUgPSBGfQ0KIyBTcGVjaWZ5IHJlcXVpcmVkIHBhY2thZ2VzDQptbF9wYWNrYWdlcyA8LSBjKCJjYXJldCIsICJtYWdyaXR0ciIsICJycGFydC5wbG90IikNCiMgSW5zdGFsbCBtaXNzaW5nIHBhY2thZ2VzDQppbnN0YWxsLnBhY2thZ2VzKHNldGRpZmYobWxfcGFja2FnZXMsIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkpDQojIExvYWQgYWxsIHBhY2thZ2VzDQpsYXBwbHkobWxfcGFja2FnZXMsIGxpYnJhcnksIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCmBgYA0KDQojIFByZWRpY3RpbmcgUGVuZ3VpbiBTcGVjaWVzDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEluIFtTZWN0aW9uIDMgb253YXJkcyBvZiB0aGUgSW50cm9kdWN0aW9uIHRvIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBzdXBwbGVtZW50XShodHRwczovL2Jvb2tkb3duLm9yZy9yZWhrL3N0bTEwMDFfZHNtX2ludHJvZHVjdGlvbl90b19tYWNoaW5lX2xlYXJuaW5nX2luX3Ivc3VwZXJ2aXNlZC1tYWNoaW5lLWxlYXJuaW5nLXVzaW5nLXRoZS1jYXJldC1yLXBhY2thZ2UuaHRtbCksIHdlIGludHJvZHVjZWQgdmFyaW91cyBNTCBwcmUtcHJvY2Vzc2luZyB0ZWNobmlxdWVzIHZpYSBhbiBleGFtcGxlIHRhc2sgaW52b2x2aW5nIHRoZSBmYW1pbGlhciBgcGVuZ3VpbnNgIGRhdGEgc2V0IGZyb20gdGhlIGBwYWxtZXJwZW5ndWluc2AgUiBwYWNrYWdlIFtAcGVuZ3VpbnNdLg0KDQpUbyBlYXNlIHVzIGludG8gbGVhcm5pbmcgYWJvdXQgbWFjaGluZSBsZWFybmluZywgd2Ugd2lsbCBmb2N1cyBvbiBleHRlbmRpbmcgdGhpcyBleGFtcGxlIGluIHRoaXMgbGFiLiBTcGVjaWZpY2FsbHksIHdlIHdpbGwgYWltIHRvIHRyYWluIGEgc2ltcGxlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgd2hpY2ggY2FuIGFjY3VyYXRlbHkgcHJlZGljdCB0aGUgYHNwZWNpZXNgIG9mIGEgcGVuZ3VpbiBsaXZpbmcgaW4gdGhlIFBhbG1lciBBcmNoaXBlbGFnbywgdXNpbmcgYSBudW1iZXIgb2YgZmVhdHVyZSB2YXJpYWJsZXMuDQoNCiMjIFBlbmd1aW4gRGF0YSB7I3BuZWdpbn0NCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgRm9yIG91ciBtYWNoaW5lIGxlYXJuaW5nIHdvcmssIHdlIHdpbGwgdXNlIHRoZSBmb2xsb3dpbmcgZmVhdHVyZSB2YXJpYWJsZXMgZnJvbSB0aGUgYHBlbmd1aW5zYCBkYXRhIHNldDoNCg0KKiBgaXNsYW5kYA0KKiBgYmlsbF9sZW5ndGhfbW1gDQoqIGBiaWxsX2RlcHRoX21tYA0KKiBgZmxpcHBlcl9sZW5ndGhfbW1gDQoqIGBib2R5X21hc3NfZ2ANCiogYHNleGANCg0KT3VyIG91dGNvbWUgdmFyaWFibGUgd2lsbCByZW1haW4gYHNwZWNpZXNgLCB3aXRoIHRoZSBvcHRpb25zIGBBZGVsaWVgLCBgQ2hpbnN0cmFwYCBhbmQgYEdlbnRvb2AuDQoNCiMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFJ1biB0aGUgY29kZSBiZWxvdyB0byBsb2FkIHRoZSBgcGVuZ3VpbnNgIGRhdGEgaW4gUlN0dWRpbywgc2VsZWN0IHRoZSBjaG9zZW4gdmFyaWFibGVzLCBhbmQgYXNzaWduIHRoZW0gdG8gdGhlIG9iamVjdCBgbWxfcGVuZ3VpbnNgOg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQ0KDQptbF9wZW5ndWlucyA8LSBuYS5vbWl0KHBlbmd1aW5zWywgLThdKSAjIG5vdGUgd2UgaWdub3JlIHRoZSB5ZWFyIHZhcmlhYmxlIGhlcmUNCmBgYA0KDQojIyBBaW0NCg0KYHIgZW1vOjpqaSgiaG91c2Vfd2l0aF9nYXJkZW4iKWAgIE91ciBhaW0gaXMgdG8gdHJhaW4gYSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIHdoaWNoIGNhbiBhY2N1cmF0ZWx5IHByZWRpY3QgdGhlIGBzcGVjaWVzYCBvZiBwZW5ndWluLCBiYXNlZCBvbiBpbnB1dHMgZnJvbSB0aGUgZmVhdHVyZSB2YXJpYWJsZXMgc3BlY2lmaWVkIGluIFxAcmVmKHBuZWdpbikgYWJvdmUuDQoNCldoYXQgdHlwZSBvZiBwcm9ibGVtIGNsYXNzIHdvdWxkIHRoaXMgdGFzayBjbGFzc2lmeSBhcz8NCg0KPGRldGFpbHM+DQogIDxzdW1tYXJ5PmByIGVtbzo6amkoImhlYWRwaG9uZXMiKWAgKipPbmxpbmUgc3R1ZGVudHMqKjwvc3VtbWFyeT4NCmByIGVtbzo6amkoInNwZWVjaF9iYWxsb29uIilgICBFbnRlciB5b3VyIGFuc3dlciBuZXh0IHRvIHRoZSBxdWVzdGlvbiBvbiB0aGUgc2hhcmVkIGphbWJvYXJkLg0KPC9kZXRhaWxzPiANCg0KIyMgSW5pdGlhbCBEYXRhIFZpc3VhbGlzYXRpb24gIHsjcHJvYmxlbX0NCg0KYHIgZW1vOjpqaSgiaG91c2Vfd2l0aF9nYXJkZW4iKWAgV2UgY2FuIHVzZSB0aGUgYGZlYXR1cmVQbG90YCBmdW5jdGlvbiBmcm9tIHRoZSBgY2FyZXRgIHBhY2thZ2UgdG8gcHJvZHVjZSBzY2F0dGVyIHBsb3RzIG9mIHRoZSBvYnNlcnZlZCB2YWx1ZXMgZm9yIGFsbCB0aGUgZmVhdHVyZSB2YXJpYWJsZXMgaW4gb3VyIGRhdGEgc2V0LiANCg0KUnVuIHRoZSBjb2RlIGJlbG93IHRvIGRvIHRoaXM6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmZlYXR1cmVQbG90KHggPSBtbF9wZW5ndWluc1ssIC0xXSwgeSA9IG1sX3Blbmd1aW5zJHNwZWNpZXMsIA0KICAgICAgICAgICAgcGxvdCA9ICJwYWlycyIsIGF1dG8ua2V5ID0gbGlzdChjb2x1bW5zID0gMykpDQpgYGANCg0KIyMjDQoNCmByIGVtbzo6amkoImhvdXNlX3dpdGhfZ2FyZGVuIilgIEJhc2VkIG9uIHRoZSBzY2F0dGVyIHBsb3RzIHByb2R1Y2VkIGluIFxAcmVmKHByb2JsZW0pLCBjYW4geW91IHRoaW5rIG9mIGFueSBwb3RlbnRpYWwgcHJvYmxlbXMgd2UgbWlnaHQgZW5jb3VudGVyIHdoZW4gdHJ5aW5nIHRvIHByZWRpY3QgY2VydGFpbiBzcGVjaWVzPw0KDQoqTm90ZTogSXQncyBvayBpZiB5b3UncmUgbm90IHN1cmUgYWJvdXQgdGhpcyB5ZXQgLSBhZnRlciBhbGwsIHdlIGhhdmUgb25seSBqdXN0IHN0YXJ0ZWQgbGVhcm5pbmcgYWJvdXQgbWFjaGluZSBsZWFybmluZy4gRm9yIG1vcmUgZGV0YWlscyBhYm91dCB0aGlzIHNjYXR0ZXIgcGxvdCBtYXRyaXgsIHJlZmVyIHRvIFtTZWN0aW9uIDMuMS4yIG9mIHRoZSBJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci9zdXBlcnZpc2VkLW1hY2hpbmUtbGVhcm5pbmctdXNpbmctdGhlLWNhcmV0LXItcGFja2FnZS5odG1sI2RhdGEtdmlzdWFsaXNhdGlvbikuKg0KDQo8ZGV0YWlscz4NCiAgPHN1bW1hcnk+YHIgZW1vOjpqaSgiaGVhZHBob25lcyIpYCAqKk9ubGluZSBzdHVkZW50cyoqPC9zdW1tYXJ5Pg0KYHIgZW1vOjpqaSgic3BlZWNoX2JhbGxvb24iKWAgIEVudGVyIHlvdXIgYW5zd2VyIG5leHQgdG8gdGhlIHF1ZXN0aW9uIG9uIHRoZSBzaGFyZWQgamFtYm9hcmQuDQo8L2RldGFpbHM+IA0KDQojIFByZS1Qcm9jZXNzaW5nIHRoZSBQZW5ndWluIGRhdGEgeyNwcmVwcm99DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEJlZm9yZSB3ZSBiZWdpbiBmaXR0aW5nIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCwgd2Ugc2hvdWxkIGNvbmR1Y3Qgc29tZSBwcmUtcHJvY2Vzc2luZyBjaGVja3MsIGFzIG91dGxpbmVkIGluIFtTZWN0aW9uIDMuMiBvZiB0aGUgSW50cm9kdWN0aW9uIHRvIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBzdXBwbGVtZW50XShodHRwczovL2Jvb2tkb3duLm9yZy9yZWhrL3N0bTEwMDFfZHNtX2ludHJvZHVjdGlvbl90b19tYWNoaW5lX2xlYXJuaW5nX2luX3Ivc3VwZXJ2aXNlZC1tYWNoaW5lLWxlYXJuaW5nLXVzaW5nLXRoZS1jYXJldC1yLXBhY2thZ2UuaHRtbCNwcmUtcHJvY2Vzc2luZy0xKS4NCg0KIyMgRHVtbXkgVmFyaWFibGVzIHsjZHVtbXl9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEZpcnN0LCBub3RlIHRoYXQgdHdvIG9mIG91ciBmZWF0dXJlIHZhcmlhYmxlcywgbmFtZWx5IGBpc2xhbmRgIGFuZCBgc2V4YCwgYXJlIGNhdGVnb3JpY2FsLiBUaGVyZWZvcmUgd2Ugd2lsbCBuZWVkIHRvIGNyZWF0ZSBzb21lIGR1bW15IHZhcmlhYmxlcyAtIHJlbWVtYmVyLCBhbGwgdGhlIGZlYXR1cmUgdmFyaWFibGVzIHdlIHVzZSBpbiBvdXIgTUwgbW9kZWwgbmVlZCB0byBiZSBudW1lcmljIGluIGZvcm1hdC4NCg0KRm9sbG93aW5nIHRoZSBjb2RlIHByb3ZpZGVkIGluIFtTZWN0aW9uIDMuMi4xIG9mIHRoZSBJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci9zdXBlcnZpc2VkLW1hY2hpbmUtbGVhcm5pbmctdXNpbmctdGhlLWNhcmV0LXItcGFja2FnZS5odG1sI2R1bW15LXZhcmlhYmxlcyksIHJlY2xhc3NpZnkgdGhlIGBpc2xhbmRgIGFuZCBgc2V4YCBmZWF0dXJlIHZhcmlhYmxlcyBhcyBkdW1teSB2YXJpYWJsZXMuDQoNCk5hbWUgeW91ciB1cGRhdGVkIG9iamVjdCBgbWxfcGVuZ3VpbnNfdXBkYXRlZGAuDQoNCipIaW50OiBJZiB5b3UgYXJlIG5vdCBzdXJlIGhvdyB0byBwcm9jZWVkLCBjaGVjayB0aGUgY29kZSBjaHVuayBiZWxvdzoqDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiMgTG9hZCBhIHBhY2thZ2UgdG8gaGVscCB3aXRoIHRoZSByZXN0cnVjdHVyZSBvZiB0aGUgZGF0YQ0KbGlicmFyeSh0aWJibGUpIA0KDQojIFVzZSB0aGUgZHVtbWF5VmFycyBmdW5jdGlvbiB0byBjcmVhdGUgYSBmdWxsIHNldCBvZiBkdW1teSB2YXJpYWJsZXMgZm9yIHRoZSBtbF9wZW5ndWlucyBkYXRhDQpkdW1teV9wZW5ndWlucyA8LSBkdW1teVZhcnMoc3BlY2llcyB+IC4sIGRhdGEgPSBtbF9wZW5ndWlucykNCg0KIyBVc2UgdGhlIHByZWRpY3QgZnVuY3Rpb24gdG8gdXBkYXRlIG91ciBtbF9wZW5ndWlucyBmZWF0dXJlIHZhcmlhYmxlcyB3aXRoIA0KIyBib3RoIGlzbGFuZCBhbmQgc2V4IGR1bW15IHZhcmlhYmxlcw0KbWxfcGVuZ3VpbnNfdXBkYXRlZCA8LSBhc190aWJibGUocHJlZGljdChkdW1teV9wZW5ndWlucywgbmV3ZGF0YSA9IG1sX3Blbmd1aW5zKSkNCg0KIyBQcmVwZW5kIHRoZSBvdXRjb21lIHZhcmlhYmxlIHRvIG91ciB1cGRhdGVkIGRhdGEgc2V0LCBvdGhlcndpc2UgaXQgd2lsbCBiZSBsb3N0DQptbF9wZW5ndWluc191cGRhdGVkIDwtIGNiaW5kKHNwZWNpZXMgPSBtbF9wZW5ndWlucyRzcGVjaWVzLCBtbF9wZW5ndWluc191cGRhdGVkKQ0KYGBgDQoNCiMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIE5vdGUgdGhhdCB3ZSBoYXZlIHRocmVlIGR1bW15IHZhcmlhYmxlcyBub3cgZm9yIGBpc2xhbmRgLCBhcyB0aGVyZSBhcmUgdGhyZWUgaXNsYW5kcywgYW5kIHdlIGhhdmUgdHdvIGR1bW15IHZhcmlhYmxlcyBmb3IgYHNleGAuDQoNClVzZSB0aGUgYGhlYWRgIGNvbW1hbmQgdG8gY2hlY2sgdGhlIGZpcnN0IGZldyByb3dzIG9mIGRhdGEgaW4gYG1sX3Blbmd1aW5zX3VwZGF0ZWRgLCBhbmQgbWFrZSBzdXJlIHlvdSB1bmRlcnN0YW5kIHRoZSBuZXcgbm90YXRpb24gYmVmb3JlIHByb2NlZWRpbmcuDQoNCiMjIEhpZ2hseSBJbmZsdWVudGlhbCBTYW1wbGVzIHsjZXhjZXNzaXZlaW5mbHVlbmNlfQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBPdXIgbmV4dCBzdGVwIGlzIHRvIGNoZWNrIGZvciBhbmQgcmVtb3ZlIGFueSBzYW1wbGVzIGluIG91ciBkYXRhIHdoaWNoIGNvdWxkIGV4ZXJ0IGV4Y2Vzc2l2ZSBpbmZsdWVuY2Ugb24gdGhlIGZpdCBvZiBvdXIgTUwgbW9kZWwuDQoNCldlIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIGBuZWFyWmVyb1ZhcmAgZnJvbSB0aGUgYGNhcmV0YCBwYWNrYWdlIHRvIG9idGFpbiBkZXRhaWxzIG9uIHRoZSBgZnJlcVJhdGlvYCBhbmQgYHBlcmNlbnRVbmlxdWVgIHZhbHVlcyBmb3IgZWFjaCBvZiB0aGUgdmFyaWFibGVzIGluIG91ciBkYXRhIHNldC4gDQoNClVzZSB0aGUgYG5lYXJaZXJvVmFyYCBmdW5jdGlvbiB0byBhc3Nlc3MgdGhlIGZlYXR1cmUgdmFyaWFibGVzIGluIGBtbF9wZW5ndWluc191cGRhdGVkYC4gUnVuIHRoaXMgZnVuY3Rpb24gdHdpY2UsIG9uY2Ugd2l0aCBgc2F2ZU1ldHJpY3MgPSBUYCBhbmQgb25jZSB3aXRoIGBzYXZlTWV0cmljcyA9IEZgLg0KDQoqSGludDogSWYgeW91IGFyZSBub3Qgc3VyZSBob3cgdG8gcHJvY2VlZCwgY2hlY2sgc2VjdGlvbiAzLjIuMyBvZiB0aGUgW0ludHJvZHVjdGlvbiB0byBNYWNoaW5lIExlYXJuaW5nIGluIFIgc3VwcGxlbWVudF0oaHR0cHM6Ly9ib29rZG93bi5vcmcvcmVoay9zdG0xMDAxX2RzbV9pbnRyb2R1Y3Rpb25fdG9fbWFjaGluZV9sZWFybmluZ19pbl9yL3N1cGVydmlzZWQtbWFjaGluZS1sZWFybmluZy11c2luZy10aGUtY2FyZXQtci1wYWNrYWdlLmh0bWwjemVyby1hbmQtbmVhci16ZXJvLXZhcmlhbmNlLWZlYXR1cmUtdmFyaWFibGVzKS4qDQoNCiMjIyB7I2N1dG9mZn0NCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgUmVjYWxsIHRoYXQgdGhlIGBuZWFyWmVyb1ZhcmAgZnVuY3Rpb24gY2FuIGluY2x1ZGUgYWRkaXRpb25hbCBhcmd1bWVudHMsIGBmcmVxQ3V0YCBhbmQgYHVuaXF1ZUN1dGAsIHRoYXQgc3BlY2lmeSBjdXQtb2ZmIHZhbHVlcyBmb3IgdGhlIGBmcmVxUmF0aW9gIGFuZCBgcGVyY2VudFVuaXF1ZWAgcmVzdWx0cyByZXNwZWN0aXZlbHkuDQoNClJlLXJ1biB5b3VyIGNvZGUgZnJvbSBcQHJlZihleGNlc3NpdmVpbmZsdWVuY2UpLCBhbmQgdGhpcyB0aW1lIHNwZWNpZnkgYSBjdXQtb2ZmIHZhbHVlIG9mIDIgZm9yIHRoZSBgZnJlcVJhdGlvYCB2YWx1ZXMgYW5kIGEgY3V0LW9mZiB2YWx1ZSBvZiA1IGZvciB0aGUgYHBlcmNlbnRVbmlxdWVgIHZhbHVlcy4NCg0KKk5vdGU6IEZvciBkZXRhaWxzIG9uIHNwZWNpZnlpbmcgY3V0LW9mZiB2YWx1ZXMsIGNoZWNrIHNlY3Rpb24gMy4yLjQgb2YgdGhlIFtJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci9zdXBlcnZpc2VkLW1hY2hpbmUtbGVhcm5pbmctdXNpbmctdGhlLWNhcmV0LXItcGFja2FnZS5odG1sI3plcm8tYW5kLW5lYXItemVyby12YXJpYW5jZS1mZWF0dXJlLXZhcmlhYmxlcykuKg0KDQoNCiMjIw0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBCYXNlZCBvbiB0aGUgYG5lYXJaZXJvVmFyYCBmdW5jdGlvbiByZXN1bHRzIGZyb20gXEByZWYoY3V0b2ZmKSwgY2hlY2sgZm9yIHBvdGVudGlhbGx5IHByb2JsZW1hdGljIHZhcmlhYmxlcy4gDQpXaGljaCBmZWF0dXJlIHZhcmlhYmxlIGhhcyB0aGUgaGlnaGVzdCBgZnJlcVJhdGlvYCB2YWx1ZSwgYW5kIHdoaWNoIGZlYXR1cmUgdmFyaWFibGUgaGFzIHRoZSBsb3dlc3QgYHBlcmNlbnRVbmlxdWVgIHZhbHVlPw0KDQpBcmUgdGhlcmUgYW55IGZlYXR1cmUgdmFyaWFibGVzIHdoaWNoIHlvdSB3b3VsZCByZWNvbW1lbmQgcmVtb3Zpbmc/IFdoeSBvciB3aHkgbm90Pw0KDQo8ZGV0YWlscz4NCiAgPHN1bW1hcnk+YHIgZW1vOjpqaSgiaGVhZHBob25lcyIpYCAqKk9ubGluZSBzdHVkZW50cyoqPC9zdW1tYXJ5Pg0KYHIgZW1vOjpqaSgic3BlZWNoX2JhbGxvb24iKWAgIEVudGVyIHlvdXIgYW5zd2VyIG5leHQgdG8gdGhlIHF1ZXN0aW9uIG9uIHRoZSBzaGFyZWQgamFtYm9hcmQuDQo8L2RldGFpbHM+IA0KDQojIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgTmV4dCwgd2Ugc2hvdWxkIGNoZWNrIGZvciBjb3JyZWxhdGVkIGZlYXR1cmUgdmFyaWFibGVzLiANCkdlbmVyYWxseSwgc29tZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGZlYXR1cmUgdmFyaWFibGVzIGlzIHRvIGJlIGV4cGVjdGVkLCBidXQgb2Z0ZW4gaXQgaXMgYmVuZWZpY2lhbCB0byByZW1vdmUgaGlnaGx5IGNvcnJlbGF0ZWQgZmVhdHVyZSB2YXJpYWJsZXMgZnJvbSBvdXIgZGF0YS4NCg0KUnVuIHRoZSBjb2RlIGJlbG93IHRvIGNvbXB1dGUgYSBjb3JyZWxhdGlvbiBtYXRyaXggZm9yIHRoZSBgbWxfcGVuZ3VpbnNfdXBkYXRlZGAgZmVhdHVyZSB2YXJpYWJsZXMsIGFuZCAgdG8gY2hlY2sgZm9yIGV4dHJlbWUgY29ycmVsYXRpb25zIGNsb3NlIHRvIDEgaW4gbWFnbml0dWRlLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpiYXNlX2NvciA8LSAgY29yKG1sX3Blbmd1aW5zX3VwZGF0ZWRbLCA1OjhdKQ0KZXh0cmVtZV9jb3IgPC0gc3VtKGFicyhiYXNlX2Nvclt1cHBlci50cmkoYmFzZV9jb3IpXSkgPiAuOTk5KQ0KZXh0cmVtZV9jb3INCmBgYA0KDQoqTm90ZTogV2UgZG8gbm90IGNvbnNpZGVyIGR1bW15IHZhcmlhYmxlcyBoZXJlLCBzaW5jZSB0aGV5IG9yaWdpbmF0ZSBmcm9tIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4qDQoNCipIaW50OiBSZWZlciB0byBzZWN0aW9uIDMuMi41IG9mIHRoZSBbSW50cm9kdWN0aW9uIHRvIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBzdXBwbGVtZW50XShodHRwczovL2Jvb2tkb3duLm9yZy9yZWhrL3N0bTEwMDFfZHNtX2ludHJvZHVjdGlvbl90b19tYWNoaW5lX2xlYXJuaW5nX2luX3Ivc3VwZXJ2aXNlZC1tYWNoaW5lLWxlYXJuaW5nLXVzaW5nLXRoZS1jYXJldC1yLXBhY2thZ2UuaHRtbCNjb3JyZWxhdGVkLWZlYXR1cmUtdmFyaWFibGVzKSBmb3IgYWRkaXRpb25hbCBkZXRhaWxzLioNCg0KIyMjIA0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBSdW4gdGhlIGBiYXNlX2NvcmAgb2JqZWN0IHRvIGFzc2VzcyB0aGUgc3ByZWFkIG9mIGNvcnJlbGF0aW9uIHZhbHVlcy4gDQoNCldoYXQgYXJlIHRoZSBsYXJnZXN0IG5lZ2F0aXZlIGFuZCBwb3NpdGl2ZSBjb3JyZWxhdGlvbiB2YWx1ZXM/IERvIHRoZXNlIHNlZW0gcHJvYmxlbWF0aWM/DQoNCjxkZXRhaWxzPg0KICA8c3VtbWFyeT5gciBlbW86OmppKCJoZWFkcGhvbmVzIilgICoqT25saW5lIHN0dWRlbnRzKio8L3N1bW1hcnk+DQpgciBlbW86OmppKCJzcGVlY2hfYmFsbG9vbiIpYCAgRW50ZXIgeW91ciBhbnN3ZXIgbmV4dCB0byB0aGUgcXVlc3Rpb24gb24gdGhlIHNoYXJlZCBqYW1ib2FyZC4NCjwvZGV0YWlscz4gDQoNCiMjIyB7I2NvcmxpbWl0fQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBTdXBwb3NlIG91ciBjb3JyZWxhdGlvbiBsaW1pdCBmb3IgaGlnaGx5IGNvcnJlbGF0ZWQgZmVhdHVyZSB2YXJpYWJsZXMgaXMgYSByZWxhdGl2ZWx5IHN0cmljdCB2YWx1ZSBvZiAwLjcuDQoNCklmIHdlIHJ1biB0aGUgY29kZSBgZmluZENvcnJlbGF0aW9uKGJhc2VfY29yLCBjdXRvZmYgPSAuNylgLCB3ZSBvYnRhaW4gdGhlIG91dHB1dCBgM2AuDQoNCkF0IGZpcnN0LCB0aGF0IGNhbiBzZWVtIHNvbWV3aGF0IHVuaGVscGZ1bC4gV2hhdCBleGFjdGx5IGRvZXMgdGhlIGAzYCBtZWFuIGhlcmU/DQoNCldlbGwsIGl0IGlzIHRlbGxpbmcgdXMgdGhhdCwgd2l0aGluIHRoZSBzdWJzZXQgb2YgZmVhdHVyZSB2YXJpYWJsZXMgYXNzZXNzZWQgaW4gdGhlIGBiYXNlX2NvcmAgb2JqZWN0LCB0aGUgdGhpcmQgdmFyaWFibGUgZXhjZWVkcyBvdXIgc3BlY2lmaWVkIGN1dC1vZmYgdmFsdWUgb2YgMC43Lg0KDQpJZiB3ZSBjaGVjayBvdXIgYGJhc2VfY29yYCBvYmplY3QsIHdlIHNlZSB0aGF0IHRoZSBmZWF0dXJlIHZhcmlhYmxlIGluIGNvbHVtbiAzIGlzIGBmbGlwcGVyX2xlbmd0aF9tbWAsIHdoaWNoIGhhcyBhIGhpZ2ggY29ycmVsYXRpb24gb2YgMC44NzMgd2l0aCBgYm9keV9tYXNzX2dgLg0KDQojIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgSWYgd2Ugd291bGQgbm93IGxpa2UgdG8gcmVtb3ZlIHRoZSBmZWF0dXJlIHZhcmlhYmxlIGBmbGlwcGVyX2xlbmd0aF9tbWAgZnJvbSBvdXIgZGF0YSBzZXQsIHdlIG5lZWQgdG8gYmUgY2FyZWZ1bC4gQ29sdW1uIDMgaW4gdGhlIGBiYXNlX2NvcmAgbWF0cml4ICoqZG9lcyBub3QgY29ycmVzcG9uZCB0byBjb2x1bW4gMyoqIGluIG91ciBgbWxfcGVuZ3VpbnNfdXBkYXRlZGAgb2JqZWN0IC0gd2Ugb25seSBhc3Nlc3NlZCBjb2x1bW5zIDUgdG8gOCBvZiBgbWxfcGVuZ3VpbnNfdXBkYXRlZGAgd2hlbiBjb21wdXRpbmcgY29ycmVsYXRpb25zLiANCg0KVGhlcmVmb3JlIHdlIGFjdHVhbGx5IHdhbnQgdG8gcmVtb3ZlIGNvbHVtbiA3IGZyb20gb3VyIGBtbF9wZW5ndWluc191cGRhdGVkYCBvYmplY3QuIFJ1biB0aGUgY29kZSBiZWxvdyB0byBkbyB0aGlzIG5vdzoNCiAgDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NCm1sX3Blbmd1aW5zX2ZpbHRlcmVkIDwtIG1sX3Blbmd1aW5zX3VwZGF0ZWRbLCAtIDddICMgZmxpcHBlcl9sZW5ndGhfbW0gaGFzIGJlZW4gcmVtb3ZlZA0KYGBgDQoNCklmIHdlIGNvbXB1dGUgYSBuZXcgY29ycmVsYXRpb24gbWF0cml4IGZvciB0aGUgbm9uLWR1bW15IGZlYXR1cmUgdmFyaWFibGVzIGluIG91ciBmaWx0ZXJlZCBkYXRhIHNldCwgd2Ugc2VlIHRoYXQgdGhlIGhpZ2hlc3QgbWFnbml0dWRlIGNvcnJlbGF0aW9uIHZhbHVlIGlzIG5vdyAwLjU4OTQ1MSAtIG11Y2ggYmV0dGVyIQ0KDQoqTm90ZTogV2hlbiByZW1vdmluZyBhIHZhcmlhYmxlIGZyb20gYSBkYXRhIHNldCwgaXQgaXMgYWx3YXlzIGEgZ29vZCBpZGVhIHRvIGNoZWNrIHlvdXIgbmV3IG9iamVjdCwgZS5nLiBgaGVhZChtbF9wZW5ndWluc19maWx0ZXJlZClgIHRvIHZlcmlmeSB5b3UgaGF2ZSByZW1vdmVkIHRoZSBpbnRlbmRlZCB2YXJpYWJsZS4qDQoNCiMgVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gRGF0YSB7I3RyYWlufQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBCZWZvcmUgd2UgdHJhaW4gb3VyIE1MIG1vZGVsLCBvdXIgZmluYWwgc3RlcCBpcyB0byBzcGxpdCBvdXIgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMuDQpVc2UgdGhlIGBjcmVhdGVEYXRhUGFydGl0aW9uYCBmdW5jdGlvbiBmcm9tIHRoZSBgY2FyZXRgIHBhY2thZ2UgdG8gc3BsaXQgdGhlIGBtbF9wZW5ndWluc19maWx0ZXJlZGAgZGF0YSA4MC8yMC4NCg0KKk5vdGU6IFRoZSBkYXRhIHBhcnRpdGlvbmluZyBpbnRvIHRyYWluaW5nIG9yIHZhbGlkYXRpb24gY2F0ZWdvcmllcyBpcyByYW5kb20gdG8gYW4gZXh0ZW50LCBzbyBpZiB5b3UgZG8gbm90IHJ1biB0aGUgYHNldC5zZWVkKDE2NTApYCBjb21tYW5kcyBzaG93biBpbiB0aGUgY29kZSBjaHVua3MgYmVsb3csIHlvdXIgcmVzdWx0cyBmcm9tIHRoaXMgcG9pbnQgb253YXJkcyBtYXkgZGlmZmVyIHNsaWdodGx5IHRvIHRob3NlIHByZXNlbnRlZCBpbiB0aGUgc3Vic2VxdWVudCBxdWVzdGlvbiBzb2x1dGlvbnMsIHNpbmNlIHlvdXIgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gZGF0YSBzZXRzIHdpbGwgbW9zdCBsaWtlbHkgY29udGFpbiBzbGlnaHRseSBkaWZmZXJlbnQgc2V0cyBvZiBvYnNlcnZhdGlvbnMuKg0KDQpUaGUgY29kZSBiZWxvdyBpcyBwYXJ0aWFsbHkgY29tcGxldGVkLCBqdXN0IGZpbGwgaW4gdGhlIGAuLi5gIG1pc3NpbmcgcGFydHM6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCnNldC5zZWVkKDE2NTApDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKC4uLiAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwID0gLi4uICwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QgPSBGQUxTRSwgdGltZXMgPSAxKSANCmBgYA0KDQoqSGludDogUmVtZW1iZXIgdGhhdCB0aGUgYXJndW1lbnQgYHBgIGRlbm90ZXMgdGhlIHNwbGl0LiBJZiB5b3UgYXJlIHN0dWNrLCB5b3UgY2FuIGNoZWNrIFtzZWN0aW9uIDQuMSBvZiB0aGUgSW50cm9kdWN0aW9uIHRvIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBzdXBwbGVtZW50XShodHRwczovL2Jvb2tkb3duLm9yZy9yZWhrL3N0bTEwMDFfZHNtX2ludHJvZHVjdGlvbl90b19tYWNoaW5lX2xlYXJuaW5nX2luX3IvbWFjaGluZS1sZWFybmluZy1tb2RlbHMuaHRtbCNkYXRhLXNwbGl0dGluZyksIGFuZC9vciB0aGUgY29kZSBjaHVuayBiZWxvdzoqDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCnNldC5zZWVkKDE2NTApDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKG1sX3Blbmd1aW5zX2ZpbHRlcmVkJHNwZWNpZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAgPSAuOCwgIyBoZXJlIHAgZGVzaWduYXRlcyB0aGUgc3BsaXQgLSA4MC8yMA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkgDQpgYGANCg0KIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgTmV4dCwgYXNzaWduIHRoZSBgbWxfcGVuZ3VpbnNfZmlsdGVyZWRgIGRhdGEgaW50byB0aGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cywgYW5kIG5hbWUgdGhlc2UgYHBlbmd1aW5fdHJhaW5gIGFuZCBgcGVuZ3Vpbl92YWxpZGF0ZWANCnJlc3BlY3RpdmVseS4gQ2hlY2sgdGhlIGNvZGUgYmVsb3cgZm9yIGEgaGVhZCBzdGFydDoNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGVuZ3Vpbl92YWxpZGF0ZSA8LSBtbF9wZW5ndWluc19maWx0ZXJlZFstdHJhaW5faW5kZXgsIF0NCmBgYA0KDQoqSGludDogSWYgeW91IGFyZSBzdHVjaywgeW91IGNhbiBjaGVjayB0aGUgY29kZSBjaHVuayBiZWxvdzoqDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiMgTm90ZSBoZXJlIHdlIGFyZSB1c2luZyB0aGUgdmFsdWVzIGluIHRoZSB0cmFpbl9pbmRleCBvYmplY3QNCiMgKHdoZXJlYXMgZm9yIHRoZSB2YWxpZGF0aW9uIHNldCwgd2Ugc2VsZWN0IHRoZSB2YWx1ZXMgbm90IGluIHRoZSB0cmFpbl9pbmRleCBvYmplY3QpDQpwZW5ndWluX3RyYWluIDwtIG1sX3Blbmd1aW5zX2ZpbHRlcmVkW3RyYWluX2luZGV4LCBdDQpgYGANCg0KIyBGaXR0aW5nIGEgRGVjaXNpb24gVHJlZSBNYWNoaW5lIExlYXJuaW5nIE1vZGVsIHsjZml0fQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBOb3cgdGhhdCB0aGUgcHJlcGFyYXRpb24gcGhhc2UgaXMgZmluYWxseSBjb21wbGV0ZSwgd2UgYXJlIHJlYWR5IHRvIGZpdCBvdXIgZmlyc3QgbWFjaGluZSBsZWFybmluZyBtb2RlbC4NCg0KVGhlIGZvY3VzIGluIHRoaXMgbGFiIHdpbGwgYmUgdG8gaW50cm9kdWNlIHlvdSB0byB0aGUgYHRyYWluYCBmdW5jdGlvbiBmcm9tIHRoZSBgY2FyZXRgIHBhY2thZ2UuIFdlIGNhbiBmaXQgYSB2YXJpZXR5IG9mIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHVzaW5nIHRoaXMgZnVuY3Rpb24gKGFsdGhvdWdoIHNvbWUgd2lsbCBhbHNvIHJlcXVpcmUgb3RoZXIgcGFja2FnZXMpLiBXZSB3aWxsIHN0YXJ0IHdpdGggYSBzaW1wbGUgbW9kZWwsIHRoZSBEZWNpc2lvbiBUcmVlLCBhbmQgdGhlbiBleHRlbmQgdG8gb3RoZXIgbW9kZWxzIGluIGZ1dHVyZSBsYWJzLg0KDQpVc2luZyB0aGUgYHRyYWluYCBmdW5jdGlvbiwgdGhlIGJhc2ljIGNvZGUgZnJhbWV3b3JrIHRvIGZpdCBlYWNoIG1vZGVsIGlzIGFzIGZvbGxvd3M6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCm9iamVjdCA8LSB0cmFpbiguLi4gfiAuLCAjIHNwZWNpZnkgcmVsYXRpb25zaGlwIGJldHdlZW4gb3V0Y29tZSBhbmQgZmVhdHVyZSB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICBkYXRhID0gLi4uICwgIyBzcGVjaWZ5IHRyYWluaW5nIGRhdGENCiAgICAgICAgICAgICAgICBtZXRob2QgPSAic3BlY2lmeSBtZXRob2QgaGVyZSIpDQpgYGANCg0KUmVnYXJkbGVzcyBvZiB3aGF0IGFsZ29yaXRobSB5b3UgdXNlLCB0aGVyZSB3aWxsIGJlIHRocmVlIG1haW4gYXJndW1lbnRzIHlvdSB3aWxsIG5lZWQgdG8gaW5jbHVkZSBpbiB5b3VyIGB0cmFpbmAgZnVuY3Rpb246DQoNCjEuIFRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgb3V0Y29tZSB2YXJpYWJsZSBhbmQgdGhlIGZlYXR1cmUgdmFyaWFibGVzDQoyLiBUaGUgZGF0YSBzZXQNCjMuIFRoZSBtZXRob2QvYWxnb3JpdGhtIHRvIHVzZQ0KDQpMZXQncyBjb3ZlciB0aGVzZSBpbiBtb3JlIGRldGFpbC4NCg0KKioxOioqIEluIHRoZSBmaXJzdCBhcmd1bWVudCB3ZSBzcGVjaWZ5IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgb3V0Y29tZSB2YXJpYWJsZSBhbmQgdGhlIGZlYXR1cmUgdmFyaWFibGVzLg0KDQpGb3IgZXhhbXBsZSwgaWYgb3VyIG91dGNvbWUgdmFyaWFibGUgd2FzIGNhbGxlZCBgb3V0Y29tZWAsIGFuZCB3ZSBoYWQgdHdvIGZlYXR1cmUgdmFyaWFibGVzLCBgZmVhdHVyZTFgIGFuZCBgZmVhdHVyZTJgLCB0aGUgZmlyc3QgcGFydCBvZiBvdXIgY29kZSBjb3VsZCBsb29rIGxpa2UgdGhpczoNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0Kb2JqZWN0IDwtIHRyYWluKG91dGNvbWUgfiBmZWF0dXJlMSArIGZlYXR1cmUyLCANCiAgICAgICAgICAgICAgICAuLi4pDQpgYGANCg0KSW4gZ2VuZXJhbCBob3dldmVyLCB3ZSB3aWxsIGhhdmUgbW9yZSB0aGFuIHR3byBmZWF0dXJlIHZhcmlhYmxlcyB0byBpbmNsdWRlIChzb21ldGltZXMgZG96ZW5zIG1vcmUhKS4gVGhlcmVmb3JlLCB3ZSBjYW4gdXNlIHRoZSBzaG9ydGN1dCBgb3V0Y29tZSB+LmAgdG8gc3BlY2lmeSB0aGF0IGFsbCB2YXJpYWJsZXMgaW4gdGhlIGRhdGEgc2V0LCBhcGFydCBmcm9tIGBvdXRjb21lYCwgc2hvdWxkIGJlIGluY2x1ZGVkIGFzIGZlYXR1cmUgdmFyaWFibGVzIGluIHRoZSBtb2RlbC4NCg0KQXMgYSByZXN1bHQsIHdoZW4gdHJhaW5pbmcgYSBzdXBlcnZpc2VkIGxlYXJuaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgdXNpbmcgdGhlIGB0cmFpbmAgZnVuY3Rpb24sIHR5cGljYWxseSBhbGwgeW91IHdpbGwgbmVlZCB0byBkbyB3aGVuIHNwZWNpZnlpbmcgeW91ciBmaXJzdCBhcmd1bWVudCBpcyBpZGVudGlmeSB0aGUgbmFtZSBvZiB5b3VyIG91dGNvbWUgdmFyaWFibGUsIGFuZCBpbmNsdWRlIHRoaXMgbmFtZSBpbiBwbGFjZSBvZiBgb3V0Y29tZWAgaW4gYG91dGNvbWUgfi5gLg0KDQoqKjI6KiogRm9yIHRoZSBgZGF0YWAgYXJndW1lbnQsIHlvdSB3aWxsIG5lZWQgdG8gc3BlY2lmeSB5b3VyIHByZS1wcm9jZXNzZWQgZGF0YSBzZXQuDQoNCioqMzoqKiBGb3IgdGhlIGBtZXRob2RgIGFyZ3VtZW50LCB5b3Ugd2lsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIG1hY2hpbmUgbGVhcm5pbmcgbWV0aG9kIHlvdSB3b3VsZCBsaWtlIHRvIHVzZSAtIGVhY2ggaGFzIGEgZGlmZmVyZW50IG5hbWUuDQoNClNvbWUgbW9kZWxzIHdpbGwgaW5jbHVkZSBhZGRpdGlvbmFsIGFyZ3VtZW50cywgdXN1YWxseSBzcGVjaWZpZWQgd2l0aGluIHRoZSBhcmd1bWVudCBgdHVuZUdyaWRgLCBhbmQgd2Ugd2lsbCBleHBsYWluIHRoZXNlIHdoZXJlIHJlbGV2YW50Lg0KDQpMZXQncyBiZWdpbi4NCg0KIyMgRGVjaXNpb24gVHJlZSB7I2RlY3RyZWV9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIE9uZSBvZiB0aGUgc2ltcGxlc3QgbWFjaGluZSBsZWFybmluZyBtb2RlbHMgd2UgY2FuIHVzZSBpcyBhIGRlY2lzaW9uIHRyZWUuIA0KDQpVc2luZyB0aGUgaW5mb3JtYXRpb24gaW4gXEByZWYoZml0KSwgYW5kIHRoZSBwYXJ0aWFsbHkgY29tcGxldGUgY29kZSBpbiB0aGUgY29kZSBjaHVuayBiZWxvdywgZml0IGEgZGVjaXNpb24gdHJlZSB0byB5b3VyIHByZS1wcm9jZXNzZWQgYHBlbmd1aW5fdHJhaW5gIHRyYWluaW5nIGRhdGEuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCnNldC5zZWVkKDE2NTApIA0KcGVuZ3Vpbl9kZWNpc2lvbl90cmVlIDwtIHRyYWluKC4uLiB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IC4uLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiKQ0KYGBgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IEYsIGluY2x1ZGUgPSBGLCBjYWNoZSA9IFR9DQpzZXQuc2VlZCgxNjUwKSANCnBlbmd1aW5fZGVjaXNpb25fdHJlZSA8LSB0cmFpbihzcGVjaWVzIH4gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gcGVuZ3Vpbl90cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiKQ0KYGBgDQoNCipOb3RlOiBUaGUgZGVjaXNpb24gdHJlZSBtZXRob2QgbmFtZSBpcyBgcnBhcnRgICh3aGljaCBpcyB1bmludHVpdGl2ZSkuKiANCg0KT25jZSB5b3UgYXJlIGhhcHB5IHdpdGggeW91ciBjb2RlLCBydW4gaXQsIGFuZCB0aGVuIHJ1biB0aGUgb2JqZWN0IGBwZW5ndWluX2RlY2lzaW9uX3RyZWVgIHRvIHNlZSB0aGUgb3V0cHV0LiBZb3VyIG91dHB1dCBzaG91bGQgbG9vayBsaWtlIHRoZSBvdXRwdXQgaW4gdGhlIGNvZGUgY2h1bmsgYmVsb3c6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCkNBUlQgDQoNCjI2OCBzYW1wbGVzDQogIDggcHJlZGljdG9yDQogIDMgY2xhc3NlczogJ0FkZWxpZScsICdDaGluc3RyYXAnLCAnR2VudG9vJyANCg0KTm8gcHJlLXByb2Nlc3NpbmcNClJlc2FtcGxpbmc6IEJvb3RzdHJhcHBlZCAoMjUgcmVwcykgDQpTdW1tYXJ5IG9mIHNhbXBsZSBzaXplczogMjY4LCAyNjgsIDI2OCwgMjY4LCAyNjgsIDI2OCwgLi4uIA0KUmVzYW1wbGluZyByZXN1bHRzIGFjcm9zcyB0dW5pbmcgcGFyYW1ldGVyczoNCg0KICBjcCAgICAgICAgICBBY2N1cmFjeSAgIEthcHBhICAgIA0KICAwLjAxMzI0NTAzICAwLjk0MzYxMDggIDAuOTExNzgyMQ0KICAwLjM1NzYxNTg5ICAwLjgwODk3MjggIDAuNjg4NTgyMg0KICAwLjU2MjkxMzkxICAwLjU2NDkzOTIgIDAuMjU5NzUwOA0KDQpBY2N1cmFjeSB3YXMgdXNlZCB0byBzZWxlY3QgdGhlIG9wdGltYWwgbW9kZWwgdXNpbmcgdGhlIGxhcmdlc3QgdmFsdWUuDQpUaGUgZmluYWwgdmFsdWUgdXNlZCBmb3IgdGhlIG1vZGVsIHdhcyBjcCA9IDAuMDEzMjQ1MDMuDQpgYGANCg0KIyMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFdlIGFyZSBtYWlubHkgaW50ZXJlc3RlZCBoZXJlIGluIHRoZSBgQWNjdXJhY3lgIHZhbHVlcywgZm9yIGRpZmZlcmVudCB0dW5pbmcgcGFyYW1ldGVyIHZhbHVlcyAod2UgY2FuIGlnbm9yZSB0aGUgYEthcHBhYCB2YWx1ZXMpLg0KQXMgd2UgY2FuIHNlZSwgdGhlIGJlc3QgYWNjdXJhY3kgYWNoaWV2ZWQgd2FzIDk0LjM2JSwgd2hpY2ggaXMgdmVyeSBpbXByZXNzaXZlLg0KDQoqTm90ZTogRG9uJ3Qgd29ycnkgaWYgeW91ciByZXN1bHRzIGxvb2sgc2xpZ2h0bHkgZGlmZmVyZW50IC0gcGVyaGFwcyB5b3UgZGlkIG5vdCBydW4gYWxsIHRoZSBgc2V0LnNlZWQoMTY1MClgIGNvbW1hbmRzPyoNCg0KIyMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFVzZSB0aGUgYHJwYXJ0LnBsb3RgIGZ1bmN0aW9uIChhcyBzaG93biBiZWxvdykgdG8gdmlzdWFsaXNlIHRoZSBgcGVuZ3Vpbl9kZWNpc2lvbl90cmVlYCBkZWNpc2lvbiB0cmVlIG1vZGVsLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpycGFydC5wbG90KHBlbmd1aW5fZGVjaXNpb25fdHJlZSRmaW5hbE1vZGVsKQ0KYGBgDQoNClJlY2FsbCB0aGF0IHRoZSB2YWx1ZXMgdW5kZXIgdGhlIHBlbmd1aW5zJyBgc3BlY2llc2AgbmFtZXMgaW4gdGhlIGNvbG91cmVkIGJveGVzIChub2Rlcykgc2hvdyB0aGUgcGVyY2VudGFnZXMgb2YgQWRlbGllLCBDaGluc3RyYXAgYW5kIEdlbnRvbyBwZW5ndWlucyByZXNwZWN0aXZlbHksIHRoYXQgaGF2ZSBiZWVuIGNhdGVnb3Jpc2VkIGFzIGJlbG9uZ2luZyB0byB0aGF0IG5vZGUgb2YgdGhlIGRlY2lzaW9uIHRyZWUuDQoNCipOb3RlOiBUaGUgYHJwYXJ0LnBsb3RgIHBhY2thZ2UgcmVxdWlyZWQgaGVyZSBzaG91bGQgaGF2ZSBiZWVuIGluc3RhbGxlZCBhbmQgbG9hZGVkIGluIFxAcmVmKHByZXApLioNCg0KPGRldGFpbHM+DQogIDxzdW1tYXJ5PmByIGVtbzo6amkoImhlYWRwaG9uZXMiKWAgKipPbmxpbmUgc3R1ZGVudHMqKjwvc3VtbWFyeT4NCmByIGVtbzo6amkoInNwZWVjaF9iYWxsb29uIilgICBWb2x1bnRlZXIgdG8gc2hhcmUgeW91ciBzY3JlZW4gYW5kIGV4cGxhaW4geW91ciBhbnN3ZXJzIHRvIHRoaXMgcXVlc3Rpb24uDQo8L2RldGFpbHM+IA0KIA0KDQojIFZhbGlkYXRpbmcgUmVzdWx0cyB7I3ZhbH0NCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgV2hpbGUgd2UgaGF2ZSBhIHByZWRpY3RpdmUgYWNjdXJhY3kgZXN0aW1hdGUgZm9yIG91ciBkZWNpc2lvbiB0cmVlIG1vZGVsLCBpdCBpcyBpbXBvcnRhbnQgdG8gcmVtZW1iZXIgdGhhdCB0aGlzIGhhcyBiZWVuIGNvbXB1dGVkIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhLg0KDQpXZSB3b3VsZCBhbHNvIGxpa2UgdG8gY2hlY2sgaG93IHRoZSBtb2RlbCBwZXJmb3JtcyB3aGVuIHByZXNlbnRlZCB3aXRoIG5ldyBkYXRhIC0gaS5lLiBvdXIgdmFsaWRhdGlvbiBkYXRhIQ0KDQpXaGVuIGNvbmR1Y3RpbmcgbWFjaGluZSBsZWFybmluZywgdGhlcmUgaXMgYSByaXNrIG9mIG92ZXJmaXR0aW5nIG91ciBtb2RlbHMgdG8gb3VyIHRyYWluaW5nIGRhdGEuIFRoaXMgY2FuIHJlc3VsdCBpbiB0aGUgbW9kZWxzIGhhdmluZyBleGNlbGxlbnQgYWNjdXJhY3kgKndoZW4gYXNzZXNzaW5nIHRoZSB0cmFpbmluZyBkYXRhKiwgYnV0IGhhdmluZyBzdWJwYXIgcGVyZm9ybWFuY2Ugd2hlbiBwcmVzZW50ZWQgd2l0aCBuZXcgZGF0YS4gDQoNClRoaXMgaXMgd2h5IHdlIGhhdmUgcHV0IGFzaWRlIHNvbWUgb2Ygb3VyIGRhdGEgYXMgdmFsaWRhdGlvbiBkYXRhIGluIFxAcmVmKHRyYWluKSwgc28gdGhhdCB3ZSBjYW4gcGVyZm9ybSAqY3Jvc3MtdmFsaWRhdGlvbiouDQpJZiB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsIHJlbWFpbnMgc2ltaWxhciB3aGVuIHByZXNlbnRlZCB3aXRoIHRoZSB2YWxpZGF0aW9uIGRhdGEsIHRoZW4gd2UgY2FuIGJlIG1vcmUgY29uZmlkZW50IGluIG91ciBtb2RlbCdzIHJlcG9ydGVkIHBlcmZvcm1hbmNlLg0KDQojIw0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIHBlcmZvcm0gY3Jvc3MtdmFsaWRhdGlvbi4gT25lIG9mIHRoZSBzaW1wbGVzdCBpcyBkZW1vbnN0cmF0ZWQgaW4gc2VjdGlvbiA0LjMgb2YgdGhlIFtJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci9tYWNoaW5lLWxlYXJuaW5nLW1vZGVscy5odG1sI2V4YW1wbGUtZml0dGluZy1hLWdyYWRpZW50LWJvb3N0aW5nLW1hY2hpbmUtbW9kZWwpLiANCg0KQW4gZXhhbXBsZSBhcHBsaWNhdGlvbiBvZiB0aGlzIGFwcHJvYWNoIHRvIHRoZSBgcGVuZ3Vpbl9kZWNpc2lvbl90cmVlYCBtb2RlbCByZXN1bHRzIGlzIHNob3duIGJlbG93LiBJbnNwZWN0IGFuZCB0aGVuIHJ1biB0aGlzIGNvZGUuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiMgTG9hZCBtYWdyaXR0ciBwYWNrYWdlIGZvciBwaXBpbmcNCmxpYnJhcnkobWFncml0dHIpDQoNCiMgY291bnQgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiB2YWxpZGF0aW9uIGRhdGENCnZhbGlkYXRpb25fbnVtYmVycyA8LSBucm93KHBlbmd1aW5fdmFsaWRhdGUpDQoNCiMgVXNlIHRoZSBmaXR0ZWQgbW9kZWwgdG8gcHJlZGljdCBxdWFsaXR5IHZhbHVlcyBnaXZlbiB0aGUgdmFsaWRhdGlvbiBkYXRhDQpwcmVkaWN0X3Blbmd1aW5fZGVjaXNpb25fdHJlZSA8LSBwcmVkaWN0KHBlbmd1aW5fZGVjaXNpb25fdHJlZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID1wZW5ndWluX3ZhbGlkYXRlKQ0KIyBXaGVuIHJ1biwgdGhlIGNvZGUgYmVsb3cgZ2l2ZXMgdXMgdGhlIHBlcmNlbnRhZ2Ugb2YgY29ycmVjdCBwcmVkaWN0aW9ucw0KZGVjX3RyZWVfYWNjdXJhY3kgPC0gc3VtKHByZWRpY3RfcGVuZ3Vpbl9kZWNpc2lvbl90cmVlID09IA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBlbmd1aW5fdmFsaWRhdGUkc3BlY2llcykgLyB2YWxpZGF0aW9uX251bWJlcnMgKiAxMDANCg0KZGVjX3RyZWVfYWNjdXJhY3kgJT4lIHJvdW5kKDIpIA0KYGBgDQoNCiMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIERpc2N1c3MgdGhlIHJlc3VsdHMgb2YgdGhlIGNyb3NzLXZhbGlkYXRpb24gd2l0aCB5b3VyIGNsYXNzLiBEbyB5b3UgdGhpbmsgdGhlIGRlY2lzaW9uIHRyZWUgTUwgbW9kZWwgd2UgaGF2ZSB0cmFpbmVkIGlzIGEgZ29vZCBtb2RlbD8NCg0KPGRldGFpbHM+DQogIDxzdW1tYXJ5PmByIGVtbzo6amkoImhlYWRwaG9uZXMiKWAgKipPbmxpbmUgc3R1ZGVudHMqKjwvc3VtbWFyeT4NCmByIGVtbzo6amkoInNwZWVjaF9iYWxsb29uIilgICBFbnRlciB5b3VyIGFuc3dlciBuZXh0IHRvIHRoZSBxdWVzdGlvbiBvbiB0aGUgc2hhcmVkIGphbWJvYXJkLg0KPC9kZXRhaWxzPiANCg0KPGJyPg0KDQojIyMjIEdyZWF0IHdvcmssIHRoYXQncyBldmVyeXRoaW5nIGZvciB0b2RheS4gRG9uJ3Qgd29ycnkgaWYgeW91IGRpZCBub3QgY29tcGxldGUgZXZlcnl0aGluZyBpbiB0aGUgZGVzaWduYXRlZCBsYWIgdGltZSwgdGhlcmUgaXMgYSBsb3QgdG8gbGVhcm4hICMjIyMgey19DQoNCkhvcGVmdWxseSB0aGlzIGxhYiBoYXMgcHJvdmlkZWQgeW91IHdpdGggYSBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgZnVuZGFtZW50YWxzIG9mIG1hY2hpbmUgbGVhcm5pbmcgLSBhcyB3ZSBjYW4gc2VlLCBpdCdzIGFjdHVhbGx5IG5vdCB0aGF0IGNvbXBsaWNhdGVkIHRvIHRyYWluIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCBpbiBSU3R1ZGlvLg0KDQpOZXh0IHdlZWssIHdlIHdpbGwgY29udGludWUgbGVhcm5pbmcgYWJvdXQgbWFjaGluZSBsZWFybmluZywgYW5kIGZvY3VzIG9uIGEgbmV3IGRhdGEgc2V0Lg0KDQo8YnI+DQoNCiMgUmVmZXJlbmNlcyB7LSAjUmVmfQ0KPGRpdiBpZD0icmVmcyI+PC9kaXY+DQoNCjxicj4NCg0KPGZvbnQgY29sb3IgPSAiZ3JleSI+DQpUaGVzZSBub3RlcyBoYXZlIGJlZW4gcHJlcGFyZWQgYnkgUnVwZXJ0IEt1dmVrZS4gUGxlYXNlIG5vdGUgdGhhdCBzb21lIG9mIHRoZSBjb250ZW50IGluIHRoZXNlIG5vdGVzIGhhcyBiZWVuIGRldmVsb3BlZCBmcm9tIGNvbnRlbnQgaW4gQE1vZFN0YXQuIFRoZSBjb3B5cmlnaHQgZm9yIHRoZSBtYXRlcmlhbCBpbiB0aGVzZSBub3RlcyByZXNpZGVzIHdpdGggdGhlIGF1dGhvcnMgbmFtZWQgYWJvdmUsIHdpdGggdGhlIERlcGFydG1lbnQgb2YgTWF0aGVtYXRpY2FsIGFuZCBQaHlzaWNhbCBTY2llbmNlcyBhbmQgd2l0aCBMYSBUcm9iZSBVbml2ZXJzaXR5LiBDb3B5cmlnaHQgaW4gdGhpcyB3b3JrIGlzIHZlc3RlZCBpbiBMYSBUcm9iZSBVbml2ZXJzaXR5IGluY2x1ZGluZyBhbGwgTGEgVHJvYmUgVW5pdmVyc2l0eSBicmFuZGluZyBhbmQgbmFtaW5nLiBVbmxlc3Mgb3RoZXJ3aXNlIHN0YXRlZCwgbWF0ZXJpYWwgd2l0aGluIHRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tTm9uIENvbW1lcmNpYWwtTm9uIERlcml2YXRpdmVzIExpY2Vuc2UgDQo8YSBocmVmID0gImh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1uZC80LjAvQ0MiIHRhcmdldD0iX2JsYW5rIj4gQlktTkMtTkQuIDwvYT4NCjwvZm9udD4=