Data Science Stream

Topic 11B: Machine Learning III


Welcome to the eleventh computer lab for the Data Science stream of STM1001. This will be our third and final lab focusing on machine learning.

In this computer lab we will fit a variety of machine learning models to the Portuguese Vinho Verde wine1 we began analysing in Computer Lab 10B. We will also cover how to tune machine learning parameters in order to achieve better results.

This computer lab is designed to run alongside the content in the Introduction to Machine Learning in R supplement.

By the end of this lab, you will have gained a greater understanding of machine learning, and should feel comfortable conducting the processes involved in training and improving a variety of important machine learning models in RStudio.


1 Machine Learning (ML) Preparations

🏡 Before we proceed, please make sure you have read all the content in the Introduction to Machine Learning in R supplement and completed Computer Lab 10B. It may also be helpful to:

  • Use the same working directory as the one you used when completed Computer Lab 10B
  • Keep the supplement content and the Computer Lab 10B solutions open in separate tabs while you work through this lab material

1.1 Wine Data Review

🏡 Recall that the Portuguese Vinho Verde wine data consists of 11 feature variables relating to physicochemical aspects of the wine, namely:

  • fixed acidity
  • volatile acidity
  • citric acid
  • residual sugar
  • chlorides
  • free sulfur dioxide
  • total sulfur dioxide
  • density
  • pH
  • sulphates
  • alcohol

The only available outcome variable is quality, which is an integer score from 0 to 10 (with 0 denoting a terrible wine and 10 denoting an exceptional wine). Note that no wine actually receives an extreme rating of 0 or 10.

This data is split into two sets - one for red wine and one for white wine. As part of Computer Lab 10B, you should have already downloaded the red wine data set - we will continue to analyse this data set in this lab.

If you do not have this file saved on your device, please download winequality_red.csv from the LMS now.

1.2 ML Aim

💻 In Computer Lab 10B, we pre-processed our red wine data, split the data into training and validation data sets, and trained and validated a simple decision tree model.

The best accuracy achieved was 57.37% (using the training data) and 52.05% (using the validation data).

We next trained and validated a simple random forest model, and this outperformed the decision tree model, achieving an accuracy of 67.01% and 65.93% for the training and validation data respectively. This is a decent result, but we would like to do better.

Our aim now is to use our pre-processed training and validation data sets to train other machine learning models, in the hope that one or more of these new models will achieve a higher predictive accuracy than 67.01%.

We will also extend our skills using the train function, and try tweaking the tuning parameters for the different models in order to further improve our results, rather than simply using the default settings.

Note: The accuracies stated here may be slightly different to what you obtained, depending on your random seed.

1.3 Pre-prepared ML Code to Run

💻 In the interests of time, and since you may not have your code from the previous computer lab to hand, please run the code in the code chunk below. This is all the relevant code to get us up and running for the subsequent steps in this lab.

Note: You must have the winequality_red.csv saved in your current working directory for the code below to work.

Note: If a red warning message about Rtools appears, don’t worry, it is safe to ignore the message.

# Specify required packages
ml_packages <- c("caret", "gbm", "kernlab", "magrittr", "randomForest", "rpart.plot")
# Install missing packages
install.packages(setdiff(ml_packages, rownames(installed.packages())))
# Load all packages
lapply(ml_packages, library, character.only = TRUE)
# Load data 
red_wine <- read.csv(file = "winequality_red.csv", header = T)
red_wine$quality <- as.factor(red_wine$quality)
centre_scale <- preProcess(red_wine[, -12], 
                           method = c("center", "scale"))
red_wine_updated <- predict(centre_scale, red_wine)
set.seed(1650)
wine_train_index <- createDataPartition(red_wine_updated$quality, 
                                        p = 0.8, 
                                        list = FALSE, times = 1) 
red_wine_train <- red_wine_updated[wine_train_index, ]
red_wine_validate <- red_wine_updated[-wine_train_index, ]

set.seed(1650)
red_wine_dec_tree <- train(quality ~ .,
                           data = red_wine_train,
                           method = "rpart")

set.seed(1650)
red_wine_rf <- train(quality ~ .,
                           data = red_wine_train,
                           method = "rf")

2 Fine-Tuning ML Models

💻 So far, we have been using the default tuning parameters for our ML models. While these often do a great job, sometimes intelligently tweaking the tuning parameters can make all the difference.

Each machine learning model has a different set of tuning parameters, and there are dozens of different models, but don’t worry - we’ll just focus on a select few.

We can specify changes to tuning parameters using the optional argument tuneGrid within the train function. This can be a complicated argument, since, as noted above, each machine learning model has its own set of tuning parameters.

Note: The focus here is on having some fun and familiarising ourselves with the code required to fit different machine learning models - you are not expected to understand or explain all the mathematics behind these models.

2.1 Tuning a Decision Tree

💻 While our decision tree model performed poorly in Computer Lab 10B, it is worthwhile to know that there are some simple changes we could make to this model, to potentially improve its predictive accuracy. We will introduce these below.

The main tuning parameter for a decision tree model is cp - the complexity parameter, which has a default value of 0.01.

The complexity parameter penalises the decision tree if it has too many branches.

  • If the cp value is too low, your tree model may be overfitted, with an excessive number of branches.
  • Conversely, if the cp value is too high, your tree model may look more like a stick, i.e. be too simplistic to be of any use.

The code chunk below contains partially completed code, with the tuneGrid argument incorporated into the train function.

set.seed(1650)
red_wine_dec_tree_tuned <- train(... ,
                                 data = ... ,
                                 method = ... , 
                                 tuneGrid = expand.grid(cp = seq(0.001, 0.01, 0.001))
                                 )

Here:

  • We have specified that the cp values to test are \(0.001, 0.002, 0.003, \dots, 0.009, 0.01\).

Fill in the missing ... details in the code chunk above, and once you are happy with your code, try running it.

2.1.1

💻 Check your results for your modified model - as a result of adjusting the cp tuning parameter, has the top accuracy of the model increased from the original 57.37%?

🎧 Online students 💬 Enter your answer next to the question on the shared jamboard.

2.2 Tuning a Random Forest Model

💻 The main tuning parameter for a random forest model is the mtry argument, which represents the number of variables to use in each level. Generally, the default values for this are quite good, but it’s often worth trying out a few different values, just to check.

From your initial random forest model red_wine_rf, you may have found that an mtry value of 2 led to better results than mtry =6 or mtry=10. Therefore, let’s try some values around 2.

Use your code from 1.3 to fit a new random forest model that includes inside the train function the argument tuneGrid = expand.grid(mtry = c(1:3)).

Call this new model red_wine_rf_tuned.

Note: The training of this model might take a few minutes, depending on your device’s hardware.

2.3 Resampling Methods

💻 So far, we have used the default bootstrap resampling method boot when fitting all our machine learning models.

In fact, the train function accepts 14 different resampling options!

Don’t worry though, we will just consider one alternative to the boot method in this lab, namely the cross-validation (cv) method.

The cv method follows the process we are employing with our training and validation data, albeit on a smaller scale - i.e. the machine learning model will take a subset of the red_wine_train data, train the model, and then test it on the remaining portion of the red_wine_train data. We can also specify the number of cross-validation trials to perform - 10 is often considered adequate, but for this lab we will specify a larger number of 25.

2.3.1

💻 We can specify the machine learning resampling method to use within the train function via the argument trControl.

Take a look at the code below, and see if you can create a new decision tree model and random forest model that use the cv resampling method and the range of cp and mtry values respectively that are specified in 2.1 and 2.2 respectively.

Note: You will need to fill in the ... missing parts in the code).

tr_control <- trainControl(method = "cv",
                           number = 25)

set.seed(1650)
red_wine_dec_tree_tuned_cv <- train(quality ~ .,
                              data = ...,
                              trControl = tr_control,
                              method = "rpart",
                              tuneGrid = ...
                              )

set.seed(1650)
red_wine_rf_tuned_cv <- train(quality ~ .,
                              data = ... ,
                              trControl = tr_control,
                              method = "rf",
                              tuneGrid = ...
                              )

Hint: If you are not sure how to proceed, check the code chunk below:

tr_control <- trainControl(method = "cv",
                           number = 25)

set.seed(1650)
red_wine_rf_tuned_cv <- train(quality ~ .,
                              data = red_wine_train,
                              trControl = tr_control,
                              method = "rf",
                              tuneGrid = expand.grid(mtry = c(1:3))
                              )

# You will need a different tuneGrid specification for the decision tree model

2.3.2

💻 Compare your results for your three random forest models red_wine_rf, red_wine_rf_tuned and red_wine_rf_tuned_cv.

Which of your three approaches results in the best performing random forest model?

Note the specific tuning parameter values and resampling method that led to the best results.

🎧 Online students 💬 Enter your answer next to the question on the shared jamboard.

2.3.3

💻 Recall that we can easily produce two helpful plots for random forest models, namely:

  • A plot of the model accuracy based on the number of feature variables used for each decision tree in the random forest model
  • A plot of the importance of each feature variable in achieving an accurate model

Run the following R code to produce these plots now, for your initial random forest model:

ggplot(red_wine_rf)

dotPlot(varImp(red_wine_rf))

2.3.4

💻 Create the plots discussed in 2.3.3 for your other two random forest models, and comment on your results.

🎧 Online students 💬 Enter your answer next to the question on the shared jamboard.

3 Validating Results

💻 In addition to obtaining predictive accuracy estimates for each of our models, it is important to also check how the models perform when presented with our validation data. Recall that we carried out this check in earlier labs2.

An example application of this approach to the decision tree model results is shown below:

# Load magrittr package for piping
library(magrittr)

# count number of observations in validation data
validation_numbers <- dim(red_wine_validate)[1]

# Use the fitted model to predict quality values given the validation data
predict_red_wine_dec_tree <- predict(red_wine_dec_tree, 
                                     newdata =red_wine_validate)
# When run, the code below gives us the percentage of correct predictions
dec_tree_accuracy <- sum(predict_red_wine_dec_tree == 
                         red_wine_validate$quality) / validation_numbers * 100

dec_tree_accuracy %>% round(2)

3.1

💻 Select your best performing decision tree and random forest versions, then, following the process outlined above in 3, use your red_wine_validate data to assess the predictive accuracy of your selected models.

Which of your selected models has the better performance with the validation data?

4 Summarising Results

💻 Now that we have tried fitting and validating a selection of popular machine learning models, we might like to summarise our findings; this can make it easier to compare the results of the different methods.

4.1

💻 Machine learning models trained using the train function test combinations of tuning parameters to try to find a combination leading to a accurate prediction.

Create two lists to summarise the results of your different machine learning models, using the code below as a starting point.

Note: The ... parts of the code need to be filled in.

Note: We need to create separate lists for results obtained via the boot and cv resampling methods.

results_boot <- resamples(list(decision_tree = red_wine_dec_tree, 
                               decision_tree_tuned = red_wine_dec_tree_tuned,
                               random_forest = red_wine_rf, 
                               ...
                               )
                         )
summary(results_boot)

results_cv <- resamples(list(..., random_forest_tuned_cv = red_wine_rf_tuned_cv))

summary(results_cv)

Note: The column of interest in the output is actually the Mean column (the average accuracy achieved by the model over all the resamples), not the Max. column.

4.2

💻 Apply the dotplot function to your two results objects from 4.1 to plot the range of accuracy values for the different methods.

4.3

💻 To conclude, write a brief, simple summary of your findings, explaining your process and the results of your machine learning models. Overall, based on the results obtained and the training processes involved, do you have a preference for one of the machine learning models we have used in this computer lab?


🏡 Reconvene in main room to discuss results


🎧 Online students 💬 Volunteer to discuss your preference for machine learning model, out of those covered in these recent DS computer labs, or leave a comment in the shared jamboard.

5 Extension: Gradient Boosting Machine ML Models

💻 Another ensemble method that combines multiple decision trees is the gradient boosting machine model. There are several options for training a gradient boosting machine model in RStudio. We will use the stochastic gradient boosting method gbm.

Using the gbm argument within the train function, train a gradient boosting machine model, and name your model red_wine_boosted.

Again, don’t worry if it takes a couple of minutes for your code to run - this is normal.

Note: You can also include the argument verbose = F within the train function so no calculations are shown while the model is being fitted. Alternatively, if you would like to see the model fitting in action in the R console, you can leave this argument out.

5.1

💻 What is the best accuracy achieved by your red_wine_boosted gradient boosting machine model?

5.2

💻 For the gradient boosting machine model, the main tuning parameters are:

  • n.trees (the number of iterations i.e. the number of decision trees used) and
  • interaction.depth (the complexity of the trees).

The code chunk below contains partially completed code for a tuned gradient boosting machine model, with the tuneGrid argument incorporated into the train function.

set.seed(1650)
red_wine_boosted_tuned <- train(... ,
                                data = ... ,
                                method = ... , 
                                verbose = FALSE,
                                tuneGrid = expand.grid(interaction.depth = 3:6,
                                                       n.trees = seq(50, 200, 50),
                                                       shrinkage = 0.1,
                                                       n.minobsinnode = 10)
                               )

Here:

  • We have specified that the interaction depth can be between 3 and 6, rather than between 1 and 3 (usually, greater depth leads to better accuracy, although we have to be careful not to overfit).
  • We have also specified that the number of trees can be 50, 100, 150 or 200 (i.e. 200 trees is now our maximum, rather than 150).
  • Note that the arguments shrinkage and n.minobsinnode must be included within the tuneGrid function, otherwise the training will fail. The values for these arguments are simply the default ones.

5.3

💻 Fill in the missing ... details in the code chunk above in 5.2, and once you are happy with your code, try running it.

5.3.1

💻 Using the code in 2.3.1 above as a guide, train a new gradient boosting machine model that uses the cv resampling method and the tuning specifications used in 5.2.

Assign your output to a new object called red_wine_boosted_tuned_cv.

5.4

💻 Compare your results for your three gradient boosting machine models red_wine_boosted, red_wine_boosted_tuned and red_wine_boosted_tuned_cv.

Which of your three approaches results in the best performing gradient boosting machine model? Note down the specific tuning parameter values and resampling method that led to the best results.

🎧 Online students 💬 Enter your answer next to the question on the shared jamboard.

5.5

💻 To conclude our focus on gradient boosted machine tree models, use the plot function to visualise the results of the training process, for your best performing gradient boosting machine model.

🎧 Online students 💬 Enter your answer next to the question on the shared jamboard.

6 Extension: Additional ML Models

💻 Our focus in this lab so far has been on tree-based machine learning models, as these are flexible and can perform well in a variety of contexts. However, there are plenty more models to choose from, and so we will briefly introduce a few new options below:

  • Linear Discriminant Analysis Model
  • Support Vector Machine Model
  • k-Nearest-Neighbour Model

For these models, we will use the default tuning parameters and resampling methods.

6.1 LDA

💻 Fit a linear discriminant analysis model to your red_wine_train data via the method specification lda.

What is the best accuracy achieved by this method for the training data?

6.2 SVM

💻 Fit a support vector machine model to your red_wine_train data via the method specification svmLinear (there are other options, but these are more complicated and take longer to execute).

What is the best accuracy achieved by this method for the training data?

Note: In order to fit a svm model, we need to load the kernlab package. This should have been done already in ??, but if an error appears when fitting the model, just double-check this.

6.3 kNN

💻 The final method we will try is the k-Nearest-Neighbours model, which is selected via the method specification knn. Fit a kNN model to your red_wine_train data.

What is the best accuracy achieved by this method for the training data?


Well done, that concludes our work in machine learning.

Hopefully this lab has enhanced your understanding of how to conduct supervised machine learning in RStudio. This is just the beginning - there are so many different models and methods out there!


References

Cortez, P., A. Cerdeira, F. Almeida, T. Matos, and J. Reis. 2009. “Modeling Wine Preferences by Data Mining from Physicochemical Properties.” Decision Support Systems 47 (4): 547–53.
Thulin, M. 2021. Modern Statistics with R: From Wrangling and Exploring Data to Inference and Predictive Modelling.
UCI Machine Learning Repository. 2009. “Wine Quality Data Set[.csv File].” 2009. https://archive.ics.uci.edu/ml/datasets/Wine+Quality.


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.


  1. This data was obtained from the UCI Machine Learning Repository (2009) and originally collected by Cortez et al. (2009).↩︎

  2. This approach was also demonstrated in section 4.1 of the Introduction to Machine Learning in R supplement).↩︎

LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiAxMUIiDQpvdXRwdXQ6DQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KYmlibGlvZ3JhcGh5OiBTVE0xMDAxX0RTX0NMX3JlZmVyZW5jZXMuYmliIA0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCjxzdHlsZT4NCiNUT0Mgew0KICBiYWNrZ3JvdW5kOiB1cmwoImh0dHBzOi8vd3d3LmxhdHJvYmUuZWR1LmF1L19tZWRpYS9sYS10cm9iZS1hcGkvdjUvaW1nL2xvZ28uc3ZnIik7DQogIGJhY2tncm91bmQtc2l6ZTogY29udGFpbjsNCiAgcGFkZGluZy10b3A6IDgwcHggIWltcG9ydGFudDsNCiAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsNCn0NCjwvc3R5bGU+DQoNCiMjIyBEYXRhIFNjaWVuY2UgU3RyZWFtIHstfQ0KDQojIyMgVG9waWMgMTFCOiBNYWNoaW5lIExlYXJuaW5nIElJSSB7LX0NCg0KPGJyPg0KDQpXZWxjb21lIHRvIHRoZSBlbGV2ZW50aCBjb21wdXRlciBsYWIgZm9yIHRoZSBEYXRhIFNjaWVuY2Ugc3RyZWFtIG9mIFNUTTEwMDEuIFRoaXMgd2lsbCBiZSBvdXIgdGhpcmQgYW5kIGZpbmFsIGxhYiBmb2N1c2luZyBvbiBtYWNoaW5lIGxlYXJuaW5nLg0KDQpJbiB0aGlzIGNvbXB1dGVyIGxhYiB3ZSB3aWxsIGZpdCBhIHZhcmlldHkgb2YgbWFjaGluZSBsZWFybmluZyBtb2RlbHMgdG8gdGhlIFBvcnR1Z3Vlc2UgKlZpbmhvIFZlcmRlKiB3aW5lXltUaGlzIGRhdGEgd2FzIG9idGFpbmVkIGZyb20gdGhlIEBVQ0lXaW5lIGFuZCBvcmlnaW5hbGx5IGNvbGxlY3RlZCBieSBAd2luZS5dIHdlIGJlZ2FuIGFuYWx5c2luZyBpbiBbQ29tcHV0ZXIgTGFiIDEwQl0oaHR0cHM6Ly9ycHVicy5jb20vTFRVX1NUTTEwMDEvRFNNQ0wxMCkuIFdlIHdpbGwgYWxzbyBjb3ZlciBob3cgdG8gdHVuZSBtYWNoaW5lIGxlYXJuaW5nIHBhcmFtZXRlcnMgaW4gb3JkZXIgdG8gYWNoaWV2ZSBiZXR0ZXIgcmVzdWx0cy4NCg0KVGhpcyBjb21wdXRlciBsYWIgaXMgZGVzaWduZWQgdG8gcnVuIGFsb25nc2lkZSB0aGUgY29udGVudCBpbiB0aGUgW0ludHJvZHVjdGlvbiB0byBNYWNoaW5lIExlYXJuaW5nIGluIFIgc3VwcGxlbWVudF0oaHR0cHM6Ly9ib29rZG93bi5vcmcvcmVoay9zdG0xMDAxX2RzbV9pbnRyb2R1Y3Rpb25fdG9fbWFjaGluZV9sZWFybmluZ19pbl9yLykuDQoNCkJ5IHRoZSBlbmQgb2YgdGhpcyBsYWIsIHlvdSB3aWxsIGhhdmUgZ2FpbmVkIGEgZ3JlYXRlciB1bmRlcnN0YW5kaW5nIG9mIG1hY2hpbmUgbGVhcm5pbmcsIGFuZCBzaG91bGQgZmVlbCBjb21mb3J0YWJsZSBjb25kdWN0aW5nIHRoZSBwcm9jZXNzZXMgaW52b2x2ZWQgaW4gdHJhaW5pbmcgYW5kIGltcHJvdmluZyBhIHZhcmlldHkgb2YgaW1wb3J0YW50IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGluIFJTdHVkaW8uDQoNCjxicj4NCg0KIyBNYWNoaW5lIExlYXJuaW5nIChNTCkgUHJlcGFyYXRpb25zIHsjcHJlcH0NCg0KYHIgZW1vOjpqaSgiaG91c2Vfd2l0aF9nYXJkZW4iKWAgQmVmb3JlIHdlIHByb2NlZWQsIHBsZWFzZSBtYWtlIHN1cmUgeW91IGhhdmUgcmVhZCBhbGwgdGhlIGNvbnRlbnQgaW4gdGhlIFtJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci8pIGFuZCBjb21wbGV0ZWQgW0NvbXB1dGVyIExhYiAxMEJdKGh0dHBzOi8vcnB1YnMuY29tL0xUVV9TVE0xMDAxL0RTTUNMMTApLiBJdCBtYXkgYWxzbyBiZSBoZWxwZnVsIHRvOg0KDQoqIFVzZSB0aGUgc2FtZSB3b3JraW5nIGRpcmVjdG9yeSBhcyB0aGUgb25lIHlvdSB1c2VkIHdoZW4gY29tcGxldGVkIENvbXB1dGVyIExhYiAxMEINCiogS2VlcCB0aGUgc3VwcGxlbWVudCBjb250ZW50IGFuZCB0aGUgW0NvbXB1dGVyIExhYiAxMEIgc29sdXRpb25zXShodHRwczovL3JwdWJzLmNvbS9MVFVfU1RNMTAwMS9EU01DTDEwU29sKSBvcGVuIGluIHNlcGFyYXRlIHRhYnMgd2hpbGUgeW91IHdvcmsgdGhyb3VnaCB0aGlzIGxhYiBtYXRlcmlhbA0KDQojIyBXaW5lIERhdGEgUmV2aWV3DQoNCmByIGVtbzo6amkoImhvdXNlX3dpdGhfZ2FyZGVuIilgIFJlY2FsbCB0aGF0IHRoZSBQb3J0dWd1ZXNlICpWaW5obyBWZXJkZSogd2luZSBkYXRhIGNvbnNpc3RzIG9mIDExIGZlYXR1cmUgdmFyaWFibGVzIHJlbGF0aW5nIHRvIHBoeXNpY29jaGVtaWNhbCBhc3BlY3RzIG9mIHRoZSB3aW5lLCBuYW1lbHk6DQoNCiogZml4ZWQgYWNpZGl0eQ0KKiB2b2xhdGlsZSBhY2lkaXR5DQoqIGNpdHJpYyBhY2lkDQoqIHJlc2lkdWFsIHN1Z2FyDQoqIGNobG9yaWRlcw0KKiBmcmVlIHN1bGZ1ciBkaW94aWRlDQoqIHRvdGFsIHN1bGZ1ciBkaW94aWRlDQoqIGRlbnNpdHkNCiogcEgNCiogc3VscGhhdGVzDQoqIGFsY29ob2wNCg0KVGhlIG9ubHkgYXZhaWxhYmxlIG91dGNvbWUgdmFyaWFibGUgaXMgYHF1YWxpdHlgLCB3aGljaCBpcyBhbiBpbnRlZ2VyIHNjb3JlIGZyb20gMCB0byAxMCAod2l0aCAwIGRlbm90aW5nIGEgdGVycmlibGUgd2luZSBhbmQgMTAgZGVub3RpbmcgYW4gZXhjZXB0aW9uYWwgd2luZSkuIE5vdGUgdGhhdCBubyB3aW5lIGFjdHVhbGx5IHJlY2VpdmVzIGFuIGV4dHJlbWUgcmF0aW5nIG9mIDAgb3IgMTAuDQoNClRoaXMgZGF0YSBpcyBzcGxpdCBpbnRvIHR3byBzZXRzIC0gb25lIGZvciByZWQgd2luZSBhbmQgb25lIGZvciB3aGl0ZSB3aW5lLiANCkFzIHBhcnQgb2YgW0NvbXB1dGVyIExhYiAxMEJdKGh0dHBzOi8vcnB1YnMuY29tL0xUVV9TVE0xMDAxL0RTTUNMMTApLCB5b3Ugc2hvdWxkIGhhdmUgYWxyZWFkeSBkb3dubG9hZGVkIHRoZSByZWQgd2luZSBkYXRhIHNldCAtIHdlIHdpbGwgY29udGludWUgdG8gYW5hbHlzZSB0aGlzIGRhdGEgc2V0IGluIHRoaXMgbGFiLiANCg0KSWYgeW91IGRvIG5vdCBoYXZlIHRoaXMgZmlsZSBzYXZlZCBvbiB5b3VyIGRldmljZSwgcGxlYXNlIGRvd25sb2FkICBgd2luZXF1YWxpdHlfcmVkLmNzdmAgZnJvbSB0aGUgTE1TIG5vdy4NCg0KIyMgTUwgQWltDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEluIFtDb21wdXRlciBMYWIgMTBCXShodHRwczovL3JwdWJzLmNvbS9MVFVfU1RNMTAwMS9EU01DTDEwKSwgd2UgcHJlLXByb2Nlc3NlZCBvdXIgcmVkIHdpbmUgZGF0YSwgc3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhIHNldHMsIGFuZCB0cmFpbmVkIGFuZCB2YWxpZGF0ZWQgYSBzaW1wbGUgZGVjaXNpb24gdHJlZSBtb2RlbC4gDQoNClRoZSBiZXN0IGFjY3VyYWN5IGFjaGlldmVkIHdhcyA1Ny4zNyUgKHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhKSBhbmQgNTIuMDUlICh1c2luZyB0aGUgdmFsaWRhdGlvbiBkYXRhKS4NCg0KV2UgbmV4dCB0cmFpbmVkIGFuZCB2YWxpZGF0ZWQgYSBzaW1wbGUgcmFuZG9tIGZvcmVzdCBtb2RlbCwgYW5kIHRoaXMgb3V0cGVyZm9ybWVkIHRoZSBkZWNpc2lvbiB0cmVlIG1vZGVsLCBhY2hpZXZpbmcgYW4gYWNjdXJhY3kgb2YgNjcuMDElIGFuZCA2NS45MyUgZm9yIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhIHJlc3BlY3RpdmVseS4gVGhpcyBpcyBhIGRlY2VudCByZXN1bHQsIGJ1dCB3ZSB3b3VsZCBsaWtlIHRvIGRvIGJldHRlci4NCg0KT3VyIGFpbSBub3cgaXMgdG8gdXNlIG91ciBwcmUtcHJvY2Vzc2VkIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGRhdGEgc2V0cyB0byB0cmFpbiBvdGhlciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgaW4gdGhlIGhvcGUgdGhhdCBvbmUgb3IgbW9yZSBvZiB0aGVzZSBuZXcgbW9kZWxzIHdpbGwgYWNoaWV2ZSBhIGhpZ2hlciBwcmVkaWN0aXZlIGFjY3VyYWN5IHRoYW4gNjcuMDElLg0KDQpXZSB3aWxsIGFsc28gZXh0ZW5kIG91ciBza2lsbHMgdXNpbmcgdGhlIGB0cmFpbmAgZnVuY3Rpb24sIGFuZCB0cnkgdHdlYWtpbmcgdGhlIHR1bmluZyBwYXJhbWV0ZXJzIGZvciB0aGUgZGlmZmVyZW50IG1vZGVscyBpbiBvcmRlciB0byBmdXJ0aGVyIGltcHJvdmUgb3VyIHJlc3VsdHMsIHJhdGhlciB0aGFuIHNpbXBseSB1c2luZyB0aGUgZGVmYXVsdCBzZXR0aW5ncy4NCg0KKk5vdGU6IFRoZSBhY2N1cmFjaWVzIHN0YXRlZCBoZXJlIG1heSBiZSBzbGlnaHRseSBkaWZmZXJlbnQgdG8gd2hhdCB5b3Ugb2J0YWluZWQsIGRlcGVuZGluZyBvbiB5b3VyIHJhbmRvbSBzZWVkLioNCg0KIyMgUHJlLXByZXBhcmVkIE1MIENvZGUgdG8gUnVuIHsjc3RhcnRjb2RlfQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBJbiB0aGUgaW50ZXJlc3RzIG9mIHRpbWUsIGFuZCBzaW5jZSB5b3UgbWF5IG5vdCBoYXZlIHlvdXIgY29kZSBmcm9tIHRoZSBwcmV2aW91cyBjb21wdXRlciBsYWIgdG8gaGFuZCwgcGxlYXNlIHJ1biB0aGUgY29kZSBpbiB0aGUgY29kZSBjaHVuayBiZWxvdy4gVGhpcyBpcyBhbGwgdGhlIHJlbGV2YW50IGNvZGUgdG8gZ2V0IHVzIHVwIGFuZCBydW5uaW5nIGZvciB0aGUgc3Vic2VxdWVudCBzdGVwcyBpbiB0aGlzIGxhYi4NCg0KKk5vdGU6IFlvdSBtdXN0IGhhdmUgdGhlIGB3aW5lcXVhbGl0eV9yZWQuY3N2YCBzYXZlZCBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkgZm9yIHRoZSBjb2RlIGJlbG93IHRvIHdvcmsuKg0KDQoqTm90ZTogSWYgYSByZWQgd2FybmluZyBtZXNzYWdlIGFib3V0IFJ0b29scyBhcHBlYXJzLCBkb24ndCB3b3JyeSwgaXQgaXMgc2FmZSB0byBpZ25vcmUgdGhlIG1lc3NhZ2UuKg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQojIFNwZWNpZnkgcmVxdWlyZWQgcGFja2FnZXMNCm1sX3BhY2thZ2VzIDwtIGMoImNhcmV0IiwgImdibSIsICJrZXJubGFiIiwgIm1hZ3JpdHRyIiwgInJhbmRvbUZvcmVzdCIsICJycGFydC5wbG90IikNCiMgSW5zdGFsbCBtaXNzaW5nIHBhY2thZ2VzDQppbnN0YWxsLnBhY2thZ2VzKHNldGRpZmYobWxfcGFja2FnZXMsIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkpDQojIExvYWQgYWxsIHBhY2thZ2VzDQpsYXBwbHkobWxfcGFja2FnZXMsIGxpYnJhcnksIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCiMgTG9hZCBkYXRhIA0KcmVkX3dpbmUgPC0gcmVhZC5jc3YoZmlsZSA9ICJ3aW5lcXVhbGl0eV9yZWQuY3N2IiwgaGVhZGVyID0gVCkNCnJlZF93aW5lJHF1YWxpdHkgPC0gYXMuZmFjdG9yKHJlZF93aW5lJHF1YWxpdHkpDQpjZW50cmVfc2NhbGUgPC0gcHJlUHJvY2VzcyhyZWRfd2luZVssIC0xMl0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gYygiY2VudGVyIiwgInNjYWxlIikpDQpyZWRfd2luZV91cGRhdGVkIDwtIHByZWRpY3QoY2VudHJlX3NjYWxlLCByZWRfd2luZSkNCnNldC5zZWVkKDE2NTApDQp3aW5lX3RyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24ocmVkX3dpbmVfdXBkYXRlZCRxdWFsaXR5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwID0gMC44LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkgDQpyZWRfd2luZV90cmFpbiA8LSByZWRfd2luZV91cGRhdGVkW3dpbmVfdHJhaW5faW5kZXgsIF0NCnJlZF93aW5lX3ZhbGlkYXRlIDwtIHJlZF93aW5lX3VwZGF0ZWRbLXdpbmVfdHJhaW5faW5kZXgsIF0NCg0Kc2V0LnNlZWQoMTY1MCkNCnJlZF93aW5lX2RlY190cmVlIDwtIHRyYWluKHF1YWxpdHkgfiAuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHJlZF93aW5lX3RyYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IikNCg0Kc2V0LnNlZWQoMTY1MCkNCnJlZF93aW5lX3JmIDwtIHRyYWluKHF1YWxpdHkgfiAuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHJlZF93aW5lX3RyYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIikNCmBgYA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGluY2x1ZGUgPSBGfQ0KIyBMb2FkIHBhY2thZ2VzDQptbF9wYWNrYWdlcyA8LSBjKCJjYXJldCIsICJnYm0iLCAia2VybmxhYiIsICJtYWdyaXR0ciIsICJyYW5kb21Gb3Jlc3QiLCAicnBhcnQiLCAicnBhcnQucGxvdCIpDQoNCmluc3RhbGwucGFja2FnZXMoc2V0ZGlmZihtbF9wYWNrYWdlcywgcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpKSkNCmxhcHBseShtbF9wYWNrYWdlcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KYGBgDQoNCiMgRmluZS1UdW5pbmcgTUwgTW9kZWxzIHsjdHB9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFNvIGZhciwgd2UgaGF2ZSBiZWVuIHVzaW5nIHRoZSBkZWZhdWx0IHR1bmluZyBwYXJhbWV0ZXJzIGZvciBvdXIgTUwgbW9kZWxzLiBXaGlsZSB0aGVzZSBvZnRlbiBkbyBhIGdyZWF0IGpvYiwgc29tZXRpbWVzIGludGVsbGlnZW50bHkgdHdlYWtpbmcgdGhlIHR1bmluZyBwYXJhbWV0ZXJzIGNhbiBtYWtlIGFsbCB0aGUgZGlmZmVyZW5jZS4gDQoNCkVhY2ggbWFjaGluZSBsZWFybmluZyBtb2RlbCBoYXMgYSBkaWZmZXJlbnQgc2V0IG9mIHR1bmluZyBwYXJhbWV0ZXJzLCBhbmQgdGhlcmUgYXJlIGRvemVucyBvZiBkaWZmZXJlbnQgbW9kZWxzLCBidXQgZG9uJ3Qgd29ycnkgLSB3ZSdsbCBqdXN0IGZvY3VzIG9uIGEgc2VsZWN0IGZldy4NCg0KV2UgY2FuIHNwZWNpZnkgY2hhbmdlcyB0byB0dW5pbmcgcGFyYW1ldGVycyB1c2luZyB0aGUgb3B0aW9uYWwgYXJndW1lbnQgYHR1bmVHcmlkYCB3aXRoaW4gdGhlIGB0cmFpbmAgZnVuY3Rpb24uIFRoaXMgY2FuIGJlIGEgY29tcGxpY2F0ZWQgYXJndW1lbnQsIHNpbmNlLCBhcyBub3RlZCBhYm92ZSwgZWFjaCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIGhhcyBpdHMgb3duIHNldCBvZiB0dW5pbmcgcGFyYW1ldGVycy4NCg0KKk5vdGU6IFRoZSBmb2N1cyBoZXJlIGlzIG9uIGhhdmluZyBzb21lIGZ1biBhbmQgZmFtaWxpYXJpc2luZyBvdXJzZWx2ZXMgd2l0aCB0aGUgY29kZSByZXF1aXJlZCB0byBmaXQgZGlmZmVyZW50IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIC0geW91IGFyZSBub3QgZXhwZWN0ZWQgdG8gdW5kZXJzdGFuZCBvciBleHBsYWluIGFsbCB0aGUgbWF0aGVtYXRpY3MgYmVoaW5kIHRoZXNlIG1vZGVscy4qDQoNCiMjIFR1bmluZyBhIERlY2lzaW9uIFRyZWUgeyNkZWN0cmVlfQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBXaGlsZSBvdXIgKipkZWNpc2lvbiB0cmVlKiogbW9kZWwgcGVyZm9ybWVkIHBvb3JseSBpbiBbQ29tcHV0ZXIgTGFiIDEwQl0oaHR0cHM6Ly9ycHVicy5jb20vTFRVX1NUTTEwMDEvRFNNQ0wxMCksIGl0IGlzIHdvcnRod2hpbGUgdG8ga25vdyB0aGF0IHRoZXJlIGFyZSBzb21lIHNpbXBsZSBjaGFuZ2VzIHdlIGNvdWxkIG1ha2UgdG8gdGhpcyBtb2RlbCwgdG8gcG90ZW50aWFsbHkgaW1wcm92ZSBpdHMgcHJlZGljdGl2ZSBhY2N1cmFjeS4gV2Ugd2lsbCBpbnRyb2R1Y2UgdGhlc2UgYmVsb3cuDQoNClRoZSBtYWluIHR1bmluZyBwYXJhbWV0ZXIgZm9yIGEgZGVjaXNpb24gdHJlZSBtb2RlbCBpcyBgY3BgIC0gdGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyLCB3aGljaCBoYXMgYSBkZWZhdWx0IHZhbHVlIG9mIDAuMDEuIA0KDQpUaGUgY29tcGxleGl0eSBwYXJhbWV0ZXIgcGVuYWxpc2VzIHRoZSBkZWNpc2lvbiB0cmVlIGlmIGl0IGhhcyB0b28gbWFueSBicmFuY2hlcy4gDQoNCiogSWYgdGhlIGBjcGAgdmFsdWUgaXMgdG9vIGxvdywgeW91ciB0cmVlIG1vZGVsIG1heSBiZSBvdmVyZml0dGVkLCB3aXRoIGFuIGV4Y2Vzc2l2ZSBudW1iZXIgb2YgYnJhbmNoZXMuIA0KKiBDb252ZXJzZWx5LCBpZiB0aGUgYGNwYCB2YWx1ZSBpcyB0b28gaGlnaCwgeW91ciB0cmVlIG1vZGVsIG1heSBsb29rIG1vcmUgbGlrZSBhIHN0aWNrLCBpLmUuIGJlIHRvbyBzaW1wbGlzdGljIHRvIGJlIG9mIGFueSB1c2UuDQoNClRoZSBjb2RlIGNodW5rIGJlbG93IGNvbnRhaW5zIHBhcnRpYWxseSBjb21wbGV0ZWQgY29kZSwgd2l0aCB0aGUgYHR1bmVHcmlkYCBhcmd1bWVudCBpbmNvcnBvcmF0ZWQgaW50byB0aGUgYHRyYWluYCBmdW5jdGlvbi4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0Kc2V0LnNlZWQoMTY1MCkNCnJlZF93aW5lX2RlY190cmVlX3R1bmVkIDwtIHRyYWluKC4uLiAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLi4uICwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IC4uLiAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChjcCA9IHNlcSgwLjAwMSwgMC4wMSwgMC4wMDEpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KYGBgDQoNCkhlcmU6DQoNCiogV2UgaGF2ZSBzcGVjaWZpZWQgdGhhdCB0aGUgYGNwYCB2YWx1ZXMgdG8gdGVzdCBhcmUgJDAuMDAxLCAwLjAwMiwgMC4wMDMsIFxkb3RzLCAwLjAwOSwgMC4wMSQuDQoNCkZpbGwgaW4gdGhlIG1pc3NpbmcgYC4uLmAgZGV0YWlscyBpbiB0aGUgY29kZSBjaHVuayBhYm92ZSwgYW5kIG9uY2UgeW91IGFyZSBoYXBweSB3aXRoIHlvdXIgY29kZSwgdHJ5IHJ1bm5pbmcgaXQuDQoNCiMjIw0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBDaGVjayB5b3VyIHJlc3VsdHMgZm9yIHlvdXIgbW9kaWZpZWQgbW9kZWwgLSBhcyBhIHJlc3VsdCBvZiBhZGp1c3RpbmcgdGhlIGBjcGAgdHVuaW5nIHBhcmFtZXRlciwgaGFzIHRoZSB0b3AgYWNjdXJhY3kgb2YgdGhlIG1vZGVsIGluY3JlYXNlZCBmcm9tIHRoZSBvcmlnaW5hbCA1Ny4zNyU/DQoNCjxkZXRhaWxzPg0KICA8c3VtbWFyeT5gciBlbW86OmppKCJoZWFkcGhvbmVzIilgICoqT25saW5lIHN0dWRlbnRzKio8L3N1bW1hcnk+DQpgciBlbW86OmppKCJzcGVlY2hfYmFsbG9vbiIpYCAgRW50ZXIgeW91ciBhbnN3ZXIgbmV4dCB0byB0aGUgcXVlc3Rpb24gb24gdGhlIHNoYXJlZCBqYW1ib2FyZC4NCjwvZGV0YWlscz4gDQoNCiMjIFR1bmluZyBhIFJhbmRvbSBGb3Jlc3QgTW9kZWwgeyNyZnR1bmVkfQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBUaGUgbWFpbiB0dW5pbmcgcGFyYW1ldGVyIGZvciBhICoqcmFuZG9tIGZvcmVzdCoqIG1vZGVsIGlzIHRoZSBgbXRyeWAgYXJndW1lbnQsIHdoaWNoIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMgdG8gdXNlIGluIGVhY2ggbGV2ZWwuIA0KR2VuZXJhbGx5LCB0aGUgZGVmYXVsdCB2YWx1ZXMgZm9yIHRoaXMgYXJlIHF1aXRlIGdvb2QsIGJ1dCBpdCdzIG9mdGVuIHdvcnRoIHRyeWluZyBvdXQgYSBmZXcgZGlmZmVyZW50IHZhbHVlcywganVzdCB0byBjaGVjay4gDQoNCkZyb20geW91ciBpbml0aWFsIHJhbmRvbSBmb3Jlc3QgbW9kZWwgYHJlZF93aW5lX3JmYCwgeW91IG1heSBoYXZlIGZvdW5kIHRoYXQgYW4gYG10cnlgIHZhbHVlIG9mIDIgbGVkIHRvIGJldHRlciByZXN1bHRzIHRoYW4gYG10cnkgPTZgIG9yIGBtdHJ5PTEwYC4gVGhlcmVmb3JlLCBsZXQncyB0cnkgc29tZSB2YWx1ZXMgYXJvdW5kIDIuDQoNClVzZSB5b3VyIGNvZGUgZnJvbSBcQHJlZihzdGFydGNvZGUpIHRvIGZpdCBhIG5ldyByYW5kb20gZm9yZXN0IG1vZGVsIHRoYXQgaW5jbHVkZXMgaW5zaWRlIHRoZSBgdHJhaW5gIGZ1bmN0aW9uIHRoZSBhcmd1bWVudA0KYHR1bmVHcmlkID0gZXhwYW5kLmdyaWQobXRyeSA9IGMoMTozKSlgLg0KDQpDYWxsIHRoaXMgbmV3IG1vZGVsIGByZWRfd2luZV9yZl90dW5lZGAuDQoNCipOb3RlOiBUaGUgdHJhaW5pbmcgb2YgdGhpcyBtb2RlbCBtaWdodCB0YWtlIGEgZmV3IG1pbnV0ZXMsIGRlcGVuZGluZyBvbiB5b3VyIGRldmljZSdzIGhhcmR3YXJlLioNCg0KIyMgUmVzYW1wbGluZyBNZXRob2RzIHsjcmVzYW1wbGV9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFNvIGZhciwgd2UgaGF2ZSB1c2VkIHRoZSBkZWZhdWx0IGJvb3RzdHJhcCByZXNhbXBsaW5nIG1ldGhvZCBgYm9vdGAgd2hlbiBmaXR0aW5nIGFsbCBvdXIgbWFjaGluZSBsZWFybmluZyBtb2RlbHMuDQoNCkluIGZhY3QsIHRoZSBgdHJhaW5gIGZ1bmN0aW9uIGFjY2VwdHMgMTQgZGlmZmVyZW50IHJlc2FtcGxpbmcgb3B0aW9ucyEgDQoNCkRvbid0IHdvcnJ5IHRob3VnaCwgd2Ugd2lsbCBqdXN0IGNvbnNpZGVyIG9uZSBhbHRlcm5hdGl2ZSB0byB0aGUgYGJvb3RgIG1ldGhvZCBpbiB0aGlzIGxhYiwgbmFtZWx5IHRoZSBjcm9zcy12YWxpZGF0aW9uIChgY3ZgKSBtZXRob2QuIA0KIA0KVGhlIGBjdmAgbWV0aG9kIGZvbGxvd3MgdGhlIHByb2Nlc3Mgd2UgYXJlIGVtcGxveWluZyB3aXRoIG91ciB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhLCBhbGJlaXQgb24gYSBzbWFsbGVyIHNjYWxlIC0gaS5lLiB0aGUgbWFjaGluZSBsZWFybmluZyBtb2RlbCB3aWxsIHRha2UgYSBzdWJzZXQgb2YgdGhlIGByZWRfd2luZV90cmFpbmAgZGF0YSwgdHJhaW4gdGhlIG1vZGVsLCBhbmQgdGhlbiB0ZXN0IGl0IG9uIHRoZSByZW1haW5pbmcgcG9ydGlvbiBvZiB0aGUgYHJlZF93aW5lX3RyYWluYCBkYXRhLiBXZSBjYW4gYWxzbyBzcGVjaWZ5IHRoZSBudW1iZXIgb2YgY3Jvc3MtdmFsaWRhdGlvbiB0cmlhbHMgdG8gcGVyZm9ybSAtIDEwIGlzIG9mdGVuIGNvbnNpZGVyZWQgYWRlcXVhdGUsIGJ1dCBmb3IgdGhpcyBsYWIgd2Ugd2lsbCBzcGVjaWZ5IGEgbGFyZ2VyIG51bWJlciBvZiAyNS4NCg0KIyMjIHsjdHJDb250cm9sfQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBXZSBjYW4gc3BlY2lmeSB0aGUgbWFjaGluZSBsZWFybmluZyByZXNhbXBsaW5nIG1ldGhvZCB0byB1c2Ugd2l0aGluIHRoZSBgdHJhaW5gIGZ1bmN0aW9uIHZpYSB0aGUgYXJndW1lbnQgYHRyQ29udHJvbGAuDQoNClRha2UgYSBsb29rIGF0IHRoZSBjb2RlIGJlbG93LCBhbmQgc2VlIGlmIHlvdSBjYW4gY3JlYXRlIGEgbmV3IGRlY2lzaW9uIHRyZWUgbW9kZWwgYW5kIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdGhhdCB1c2UgdGhlIGBjdmAgcmVzYW1wbGluZyBtZXRob2QgYW5kIHRoZSByYW5nZSBvZiBgY3BgIGFuZCBgbXRyeWAgdmFsdWVzIHJlc3BlY3RpdmVseSB0aGF0IGFyZSBzcGVjaWZpZWQgaW4gXEByZWYoZGVjdHJlZSkgYW5kIFxAcmVmKHJmdHVuZWQpIHJlc3BlY3RpdmVseS4gDQoNCipOb3RlOiBZb3Ugd2lsbCBuZWVkIHRvIGZpbGwgaW4gdGhlIGAuLi5gIG1pc3NpbmcgcGFydHMgaW4gdGhlIGNvZGUpLioNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KdHJfY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IDI1KQ0KDQpzZXQuc2VlZCgxNjUwKQ0KcmVkX3dpbmVfZGVjX3RyZWVfdHVuZWRfY3YgPC0gdHJhaW4ocXVhbGl0eSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLi4uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJfY29udHJvbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJycGFydCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IC4uLg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQpzZXQuc2VlZCgxNjUwKQ0KcmVkX3dpbmVfcmZfdHVuZWRfY3YgPC0gdHJhaW4ocXVhbGl0eSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLi4uICwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyX2NvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSAuLi4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCmBgYA0KDQoqSGludDogSWYgeW91IGFyZSBub3Qgc3VyZSBob3cgdG8gcHJvY2VlZCwgY2hlY2sgdGhlIGNvZGUgY2h1bmsgYmVsb3c6Kg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQp0cl9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gMjUpDQoNCnNldC5zZWVkKDE2NTApDQpyZWRfd2luZV9yZl90dW5lZF9jdiA8LSB0cmFpbihxdWFsaXR5IH4gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSByZWRfd2luZV90cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyX2NvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChtdHJ5ID0gYygxOjMpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQojIFlvdSB3aWxsIG5lZWQgYSBkaWZmZXJlbnQgdHVuZUdyaWQgc3BlY2lmaWNhdGlvbiBmb3IgdGhlIGRlY2lzaW9uIHRyZWUgbW9kZWwNCmBgYA0KDQojIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgQ29tcGFyZSB5b3VyIHJlc3VsdHMgZm9yIHlvdXIgdGhyZWUgcmFuZG9tIGZvcmVzdCBtb2RlbHMgYHJlZF93aW5lX3JmYCwgYHJlZF93aW5lX3JmX3R1bmVkYCBhbmQgYHJlZF93aW5lX3JmX3R1bmVkX2N2YC4NCg0KV2hpY2ggb2YgeW91ciB0aHJlZSBhcHByb2FjaGVzIHJlc3VsdHMgaW4gdGhlIGJlc3QgcGVyZm9ybWluZyByYW5kb20gZm9yZXN0IG1vZGVsPyANCg0KTm90ZSB0aGUgc3BlY2lmaWMgdHVuaW5nIHBhcmFtZXRlciB2YWx1ZXMgYW5kIHJlc2FtcGxpbmcgbWV0aG9kIHRoYXQgbGVkIHRvIHRoZSBiZXN0IHJlc3VsdHMuDQoNCjxkZXRhaWxzPg0KICA8c3VtbWFyeT5gciBlbW86OmppKCJoZWFkcGhvbmVzIilgICoqT25saW5lIHN0dWRlbnRzKio8L3N1bW1hcnk+DQpgciBlbW86OmppKCJzcGVlY2hfYmFsbG9vbiIpYCAgRW50ZXIgeW91ciBhbnN3ZXIgbmV4dCB0byB0aGUgcXVlc3Rpb24gb24gdGhlIHNoYXJlZCBqYW1ib2FyZC4NCjwvZGV0YWlscz4gDQoNCiMjIyB7I3JmcGxvdHN9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFJlY2FsbCB0aGF0IHdlIGNhbiBlYXNpbHkgcHJvZHVjZSB0d28gaGVscGZ1bCBwbG90cyBmb3IgcmFuZG9tIGZvcmVzdCBtb2RlbHMsIG5hbWVseToNCg0KKiBBIHBsb3Qgb2YgdGhlIG1vZGVsIGFjY3VyYWN5IGJhc2VkIG9uIHRoZSBudW1iZXIgb2YgZmVhdHVyZSB2YXJpYWJsZXMgdXNlZCBmb3IgZWFjaCBkZWNpc2lvbiB0cmVlIGluIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsDQoqIEEgcGxvdCBvZiB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIGZlYXR1cmUgdmFyaWFibGUgaW4gYWNoaWV2aW5nIGFuIGFjY3VyYXRlIG1vZGVsDQoNClJ1biB0aGUgZm9sbG93aW5nIFIgY29kZSB0byBwcm9kdWNlIHRoZXNlIHBsb3RzIG5vdywgZm9yIHlvdXIgaW5pdGlhbCByYW5kb20gZm9yZXN0IG1vZGVsOg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpnZ3Bsb3QocmVkX3dpbmVfcmYpDQoNCmRvdFBsb3QodmFySW1wKHJlZF93aW5lX3JmKSkNCmBgYA0KDQojIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgQ3JlYXRlIHRoZSBwbG90cyBkaXNjdXNzZWQgaW4gXEByZWYocmZwbG90cykgZm9yIHlvdXIgb3RoZXIgdHdvIHJhbmRvbSBmb3Jlc3QgbW9kZWxzLCBhbmQgY29tbWVudCBvbiB5b3VyIHJlc3VsdHMuDQoNCjxkZXRhaWxzPg0KICA8c3VtbWFyeT5gciBlbW86OmppKCJoZWFkcGhvbmVzIilgICoqT25saW5lIHN0dWRlbnRzKio8L3N1bW1hcnk+DQpgciBlbW86OmppKCJzcGVlY2hfYmFsbG9vbiIpYCAgRW50ZXIgeW91ciBhbnN3ZXIgbmV4dCB0byB0aGUgcXVlc3Rpb24gb24gdGhlIHNoYXJlZCBqYW1ib2FyZC4NCjwvZGV0YWlscz4gDQoNCiMgVmFsaWRhdGluZyBSZXN1bHRzIHsjdmFsfQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCAgSW4gYWRkaXRpb24gdG8gb2J0YWluaW5nIHByZWRpY3RpdmUgYWNjdXJhY3kgZXN0aW1hdGVzIGZvciBlYWNoIG9mIG91ciBtb2RlbHMsIGl0IGlzIGltcG9ydGFudCB0byBhbHNvIGNoZWNrIGhvdyB0aGUgbW9kZWxzIHBlcmZvcm0gd2hlbiBwcmVzZW50ZWQgd2l0aCBvdXIgdmFsaWRhdGlvbiBkYXRhLiBSZWNhbGwgdGhhdCB3ZSBjYXJyaWVkIG91dCB0aGlzIGNoZWNrIGluIGVhcmxpZXIgbGFic15bVGhpcyBhcHByb2FjaCB3YXMgYWxzbyBkZW1vbnN0cmF0ZWQgaW4gW3NlY3Rpb24gNC4xIG9mIHRoZSBJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyBpbiBSIHN1cHBsZW1lbnRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JlaGsvc3RtMTAwMV9kc21faW50cm9kdWN0aW9uX3RvX21hY2hpbmVfbGVhcm5pbmdfaW5fci9tYWNoaW5lLWxlYXJuaW5nLW1vZGVsLWNsYXNzZXMuaHRtbCNleGFtcGxlLS0tZ3JhZGllbnQtYm9vc3RpbmctbWFjaGluZS1tb2RlbCkpLl0uDQoNCkFuIGV4YW1wbGUgYXBwbGljYXRpb24gb2YgdGhpcyBhcHByb2FjaCB0byB0aGUgZGVjaXNpb24gdHJlZSBtb2RlbCByZXN1bHRzIGlzIHNob3duIGJlbG93Og0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQojIExvYWQgbWFncml0dHIgcGFja2FnZSBmb3IgcGlwaW5nDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KDQojIGNvdW50IG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gdmFsaWRhdGlvbiBkYXRhDQp2YWxpZGF0aW9uX251bWJlcnMgPC0gZGltKHJlZF93aW5lX3ZhbGlkYXRlKVsxXQ0KDQojIFVzZSB0aGUgZml0dGVkIG1vZGVsIHRvIHByZWRpY3QgcXVhbGl0eSB2YWx1ZXMgZ2l2ZW4gdGhlIHZhbGlkYXRpb24gZGF0YQ0KcHJlZGljdF9yZWRfd2luZV9kZWNfdHJlZSA8LSBwcmVkaWN0KHJlZF93aW5lX2RlY190cmVlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID1yZWRfd2luZV92YWxpZGF0ZSkNCiMgV2hlbiBydW4sIHRoZSBjb2RlIGJlbG93IGdpdmVzIHVzIHRoZSBwZXJjZW50YWdlIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMNCmRlY190cmVlX2FjY3VyYWN5IDwtIHN1bShwcmVkaWN0X3JlZF93aW5lX2RlY190cmVlID09IA0KICAgICAgICAgICAgICAgICAgICAgICAgIHJlZF93aW5lX3ZhbGlkYXRlJHF1YWxpdHkpIC8gdmFsaWRhdGlvbl9udW1iZXJzICogMTAwDQoNCmRlY190cmVlX2FjY3VyYWN5ICU+JSByb3VuZCgyKQ0KYGBgDQoNCiMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFNlbGVjdCB5b3VyIGJlc3QgcGVyZm9ybWluZyBkZWNpc2lvbiB0cmVlIGFuZCByYW5kb20gZm9yZXN0IHZlcnNpb25zLCB0aGVuLCBmb2xsb3dpbmcgdGhlIHByb2Nlc3Mgb3V0bGluZWQgYWJvdmUgaW4gXEByZWYodmFsKSwgdXNlIHlvdXIgYHJlZF93aW5lX3ZhbGlkYXRlYCBkYXRhIHRvIGFzc2VzcyB0aGUgcHJlZGljdGl2ZSBhY2N1cmFjeSBvZiB5b3VyIHNlbGVjdGVkIG1vZGVscy4NCg0KV2hpY2ggb2YgeW91ciBzZWxlY3RlZCBtb2RlbHMgaGFzIHRoZSBiZXR0ZXIgcGVyZm9ybWFuY2Ugd2l0aCB0aGUgdmFsaWRhdGlvbiBkYXRhPw0KDQojIFN1bW1hcmlzaW5nIFJlc3VsdHMgeyNzdW19DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIE5vdyB0aGF0IHdlIGhhdmUgdHJpZWQgZml0dGluZyBhbmQgdmFsaWRhdGluZyBhIHNlbGVjdGlvbiBvZiBwb3B1bGFyIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLCB3ZSBtaWdodCBsaWtlIHRvIHN1bW1hcmlzZSBvdXIgZmluZGluZ3M7IHRoaXMgY2FuIG1ha2UgaXQgZWFzaWVyIHRvIGNvbXBhcmUgdGhlIHJlc3VsdHMgb2YgdGhlIGRpZmZlcmVudCBtZXRob2RzLg0KDQojIyB7I3Jlc3VsdHNzdW1tYXJ5fQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBNYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB0cmFpbmVkIHVzaW5nIHRoZSBgdHJhaW5gIGZ1bmN0aW9uIHRlc3QgY29tYmluYXRpb25zIG9mIHR1bmluZyBwYXJhbWV0ZXJzIHRvIHRyeSB0byBmaW5kIGEgY29tYmluYXRpb24gbGVhZGluZyB0byBhIGFjY3VyYXRlIHByZWRpY3Rpb24uIA0KDQpDcmVhdGUgdHdvIGxpc3RzIHRvIHN1bW1hcmlzZSB0aGUgcmVzdWx0cyBvZiB5b3VyIGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgdXNpbmcgdGhlIGNvZGUgYmVsb3cgYXMgYSBzdGFydGluZyBwb2ludC4gDQoNCipOb3RlOiBUaGUgYC4uLmAgcGFydHMgb2YgdGhlIGNvZGUgbmVlZCB0byBiZSBmaWxsZWQgaW4uKg0KDQoqTm90ZTogV2UgbmVlZCB0byBjcmVhdGUgc2VwYXJhdGUgbGlzdHMgZm9yIHJlc3VsdHMgb2J0YWluZWQgdmlhIHRoZSBgYm9vdGAgYW5kIGBjdmAgcmVzYW1wbGluZyBtZXRob2RzLioNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcmVzdWx0c19ib290IDwtIHJlc2FtcGxlcyhsaXN0KGRlY2lzaW9uX3RyZWUgPSByZWRfd2luZV9kZWNfdHJlZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjaXNpb25fdHJlZV90dW5lZCA9IHJlZF93aW5lX2RlY190cmVlX3R1bmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3QgPSByZWRfd2luZV9yZiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICkNCnN1bW1hcnkocmVzdWx0c19ib290KQ0KDQpyZXN1bHRzX2N2IDwtIHJlc2FtcGxlcyhsaXN0KC4uLiwgcmFuZG9tX2ZvcmVzdF90dW5lZF9jdiA9IHJlZF93aW5lX3JmX3R1bmVkX2N2KSkNCg0Kc3VtbWFyeShyZXN1bHRzX2N2KQ0KYGBgDQoNCipOb3RlOiBUaGUgY29sdW1uIG9mIGludGVyZXN0IGluIHRoZSBvdXRwdXQgaXMgYWN0dWFsbHkgdGhlIGBNZWFuYCBjb2x1bW4gKHRoZSBhdmVyYWdlIGFjY3VyYWN5IGFjaGlldmVkIGJ5IHRoZSBtb2RlbCBvdmVyIGFsbCB0aGUgcmVzYW1wbGVzKSwgbm90IHRoZSBgTWF4LmAgY29sdW1uLioNCg0KIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgQXBwbHkgdGhlIGBkb3RwbG90YCBmdW5jdGlvbiB0byB5b3VyIHR3byByZXN1bHRzIG9iamVjdHMgZnJvbSBcQHJlZihyZXN1bHRzc3VtbWFyeSkgdG8gcGxvdCB0aGUgcmFuZ2Ugb2YgYWNjdXJhY3kgdmFsdWVzIGZvciB0aGUgZGlmZmVyZW50IG1ldGhvZHMuDQoNCiMjDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFRvIGNvbmNsdWRlLCB3cml0ZSBhIGJyaWVmLCBzaW1wbGUgc3VtbWFyeSBvZiB5b3VyIGZpbmRpbmdzLCBleHBsYWluaW5nIHlvdXIgcHJvY2VzcyBhbmQgdGhlIHJlc3VsdHMgb2YgeW91ciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4NCk92ZXJhbGwsIGJhc2VkIG9uIHRoZSByZXN1bHRzIG9idGFpbmVkIGFuZCB0aGUgdHJhaW5pbmcgcHJvY2Vzc2VzIGludm9sdmVkLCBkbyB5b3UgaGF2ZSBhIHByZWZlcmVuY2UgZm9yIG9uZSBvZiB0aGUgbWFjaGluZSBsZWFybmluZyBtb2RlbHMgd2UgaGF2ZSB1c2VkIGluIHRoaXMgY29tcHV0ZXIgbGFiPw0KDQo8YnI+DQoNCiMjIyMgYHIgZW1vOjpqaSgiaG91c2Vfd2l0aF9nYXJkZW4iKWAgUmVjb252ZW5lIGluIG1haW4gcm9vbSB0byBkaXNjdXNzIHJlc3VsdHMgey19DQoNCjxicj4NCg0KPGRldGFpbHM+DQogIDxzdW1tYXJ5PmByIGVtbzo6amkoImhlYWRwaG9uZXMiKWAgKipPbmxpbmUgc3R1ZGVudHMqKjwvc3VtbWFyeT4NCmByIGVtbzo6amkoInNwZWVjaF9iYWxsb29uIilgICBWb2x1bnRlZXIgdG8gZGlzY3VzcyB5b3VyIHByZWZlcmVuY2UgZm9yIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwsIG91dCBvZiB0aG9zZSBjb3ZlcmVkIGluIHRoZXNlIHJlY2VudCBEUyBjb21wdXRlciBsYWJzLCBvciBsZWF2ZSBhIGNvbW1lbnQgaW4gdGhlIHNoYXJlZCBqYW1ib2FyZC4NCjwvZGV0YWlscz4gDQoNCiMgRXh0ZW5zaW9uOiBHcmFkaWVudCBCb29zdGluZyBNYWNoaW5lIE1MIE1vZGVscw0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBBbm90aGVyIGVuc2VtYmxlIG1ldGhvZCB0aGF0IGNvbWJpbmVzIG11bHRpcGxlIGRlY2lzaW9uIHRyZWVzIGlzIHRoZSAqKmdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUqKiBtb2RlbC4NClRoZXJlIGFyZSBzZXZlcmFsIG9wdGlvbnMgZm9yIHRyYWluaW5nIGEgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZSBtb2RlbCBpbiBSU3R1ZGlvLiANCldlIHdpbGwgdXNlIHRoZSBzdG9jaGFzdGljIGdyYWRpZW50IGJvb3N0aW5nIG1ldGhvZCBgZ2JtYC4gDQoNClVzaW5nIHRoZSBgZ2JtYCBhcmd1bWVudCB3aXRoaW4gdGhlIGB0cmFpbmAgZnVuY3Rpb24sIHRyYWluIGEgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZSBtb2RlbCwgYW5kIG5hbWUgeW91ciBtb2RlbCBgcmVkX3dpbmVfYm9vc3RlZGAuDQoNCkFnYWluLCBkb24ndCB3b3JyeSBpZiBpdCB0YWtlcyBhIGNvdXBsZSBvZiBtaW51dGVzIGZvciB5b3VyIGNvZGUgdG8gcnVuIC0gdGhpcyBpcyBub3JtYWwuDQoNCipOb3RlOiBZb3UgY2FuIGFsc28gaW5jbHVkZSB0aGUgYXJndW1lbnQgYHZlcmJvc2UgPSBGYCB3aXRoaW4gdGhlIGB0cmFpbmAgZnVuY3Rpb24gc28gbm8gY2FsY3VsYXRpb25zIGFyZSBzaG93biB3aGlsZSB0aGUgbW9kZWwgaXMgYmVpbmcgZml0dGVkLiBBbHRlcm5hdGl2ZWx5LCBpZiB5b3Ugd291bGQgbGlrZSB0byBzZWUgdGhlIG1vZGVsIGZpdHRpbmcgaW4gYWN0aW9uIGluIHRoZSBSIGNvbnNvbGUsIHlvdSBjYW4gbGVhdmUgdGhpcyBhcmd1bWVudCBvdXQuKg0KDQojIw0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBXaGF0IGlzIHRoZSBiZXN0IGFjY3VyYWN5IGFjaGlldmVkIGJ5IHlvdXIgYHJlZF93aW5lX2Jvb3N0ZWRgIGdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUgbW9kZWw/DQoNCiMjIHsjZ2JtdHVuZWR9DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEZvciB0aGUgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZSBtb2RlbCwgdGhlIG1haW4gdHVuaW5nIHBhcmFtZXRlcnMgYXJlOg0KDQoqIGBuLnRyZWVzYCAodGhlIG51bWJlciBvZiBpdGVyYXRpb25zIGkuZS4gdGhlIG51bWJlciBvZiBkZWNpc2lvbiB0cmVlcyB1c2VkKSBhbmQgDQoqIGBpbnRlcmFjdGlvbi5kZXB0aGAgKHRoZSBjb21wbGV4aXR5IG9mIHRoZSB0cmVlcykuDQoNClRoZSBjb2RlIGNodW5rIGJlbG93IGNvbnRhaW5zIHBhcnRpYWxseSBjb21wbGV0ZWQgY29kZSBmb3IgYSB0dW5lZCBncmFkaWVudCBib29zdGluZyBtYWNoaW5lIG1vZGVsLCB3aXRoIHRoZSBgdHVuZUdyaWRgIGFyZ3VtZW50IGluY29ycG9yYXRlZCBpbnRvIHRoZSBgdHJhaW5gIGZ1bmN0aW9uLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpzZXQuc2VlZCgxNjUwKQ0KcmVkX3dpbmVfYm9vc3RlZF90dW5lZCA8LSB0cmFpbiguLi4gLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLi4uICwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gLi4uICwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChpbnRlcmFjdGlvbi5kZXB0aCA9IDM6NiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuLnRyZWVzID0gc2VxKDUwLCAyMDAsIDUwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaHJpbmthZ2UgPSAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbi5taW5vYnNpbm5vZGUgPSAxMCkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQpgYGANCg0KSGVyZToNCg0KKiBXZSBoYXZlIHNwZWNpZmllZCB0aGF0IHRoZSBpbnRlcmFjdGlvbiBkZXB0aCBjYW4gYmUgYmV0d2VlbiAzIGFuZCA2LCByYXRoZXIgdGhhbiBiZXR3ZWVuIDEgYW5kIDMgKHVzdWFsbHksIGdyZWF0ZXIgZGVwdGggbGVhZHMgdG8gYmV0dGVyIGFjY3VyYWN5LCBhbHRob3VnaCB3ZSBoYXZlIHRvIGJlIGNhcmVmdWwgbm90IHRvIG92ZXJmaXQpLiANCiogV2UgaGF2ZSBhbHNvIHNwZWNpZmllZCB0aGF0IHRoZSBudW1iZXIgb2YgdHJlZXMgY2FuIGJlIDUwLCAxMDAsIDE1MCBvciAyMDAgKGkuZS4gMjAwIHRyZWVzIGlzIG5vdyBvdXIgbWF4aW11bSwgcmF0aGVyIHRoYW4gMTUwKS4NCiogTm90ZSB0aGF0IHRoZSBhcmd1bWVudHMgYHNocmlua2FnZWAgYW5kIGBuLm1pbm9ic2lubm9kZWAgbXVzdCBiZSBpbmNsdWRlZCB3aXRoaW4gdGhlIGB0dW5lR3JpZGAgZnVuY3Rpb24sIG90aGVyd2lzZSB0aGUgdHJhaW5pbmcgd2lsbCBmYWlsLg0KVGhlIHZhbHVlcyBmb3IgdGhlc2UgYXJndW1lbnRzIGFyZSBzaW1wbHkgdGhlIGRlZmF1bHQgb25lcy4NCg0KIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgRmlsbCBpbiB0aGUgbWlzc2luZyBgLi4uYCBkZXRhaWxzIGluIHRoZSBjb2RlIGNodW5rIGFib3ZlIGluIFxAcmVmKGdibXR1bmVkKSwgYW5kIG9uY2UgeW91IGFyZSBoYXBweSB3aXRoIHlvdXIgY29kZSwgdHJ5IHJ1bm5pbmcgaXQuDQoNCiMjIyANCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgVXNpbmcgdGhlIGNvZGUgaW4gXEByZWYodHJDb250cm9sKSBhYm92ZSBhcyBhIGd1aWRlLCB0cmFpbiBhIG5ldyBncmFkaWVudCBib29zdGluZyBtYWNoaW5lIG1vZGVsIHRoYXQgdXNlcyB0aGUgYGN2YCByZXNhbXBsaW5nIG1ldGhvZCBhbmQgdGhlIHR1bmluZyBzcGVjaWZpY2F0aW9ucyB1c2VkIGluIFxAcmVmKGdibXR1bmVkKS4NCg0KQXNzaWduIHlvdXIgb3V0cHV0IHRvIGEgbmV3IG9iamVjdCBjYWxsZWQgYHJlZF93aW5lX2Jvb3N0ZWRfdHVuZWRfY3ZgLg0KDQojIw0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBDb21wYXJlIHlvdXIgcmVzdWx0cyBmb3IgeW91ciB0aHJlZSBncmFkaWVudCBib29zdGluZyBtYWNoaW5lIG1vZGVscyBgcmVkX3dpbmVfYm9vc3RlZGAsIGByZWRfd2luZV9ib29zdGVkX3R1bmVkYCBhbmQgYHJlZF93aW5lX2Jvb3N0ZWRfdHVuZWRfY3ZgLg0KDQpXaGljaCBvZiB5b3VyIHRocmVlIGFwcHJvYWNoZXMgcmVzdWx0cyBpbiB0aGUgYmVzdCBwZXJmb3JtaW5nIGdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUgbW9kZWw/IA0KTm90ZSBkb3duIHRoZSBzcGVjaWZpYyB0dW5pbmcgcGFyYW1ldGVyIHZhbHVlcyBhbmQgcmVzYW1wbGluZyBtZXRob2QgdGhhdCBsZWQgdG8gdGhlIGJlc3QgcmVzdWx0cy4NCg0KPGRldGFpbHM+DQogIDxzdW1tYXJ5PmByIGVtbzo6amkoImhlYWRwaG9uZXMiKWAgKipPbmxpbmUgc3R1ZGVudHMqKjwvc3VtbWFyeT4NCmByIGVtbzo6amkoInNwZWVjaF9iYWxsb29uIilgICBFbnRlciB5b3VyIGFuc3dlciBuZXh0IHRvIHRoZSBxdWVzdGlvbiBvbiB0aGUgc2hhcmVkIGphbWJvYXJkLg0KPC9kZXRhaWxzPiANCg0KIyMNCg0KYHIgZW1vOjpqaSgiY29tcHV0ZXIiKWAgVG8gY29uY2x1ZGUgb3VyIGZvY3VzIG9uIGdyYWRpZW50IGJvb3N0ZWQgbWFjaGluZSB0cmVlIG1vZGVscywgdXNlIHRoZSBgcGxvdGAgZnVuY3Rpb24gdG8gdmlzdWFsaXNlIHRoZSByZXN1bHRzIG9mIHRoZSB0cmFpbmluZyBwcm9jZXNzLCBmb3IgeW91ciBiZXN0IHBlcmZvcm1pbmcgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZSBtb2RlbC4NCg0KPGRldGFpbHM+DQogIDxzdW1tYXJ5PmByIGVtbzo6amkoImhlYWRwaG9uZXMiKWAgKipPbmxpbmUgc3R1ZGVudHMqKjwvc3VtbWFyeT4NCmByIGVtbzo6amkoInNwZWVjaF9iYWxsb29uIilgICBFbnRlciB5b3VyIGFuc3dlciBuZXh0IHRvIHRoZSBxdWVzdGlvbiBvbiB0aGUgc2hhcmVkIGphbWJvYXJkLg0KPC9kZXRhaWxzPiANCg0KIyBFeHRlbnNpb246IEFkZGl0aW9uYWwgTUwgTW9kZWxzDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIE91ciBmb2N1cyBpbiB0aGlzIGxhYiBzbyBmYXIgaGFzIGJlZW4gb24gdHJlZS1iYXNlZCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgYXMgdGhlc2UgYXJlIGZsZXhpYmxlIGFuZCBjYW4gcGVyZm9ybSB3ZWxsIGluIGEgdmFyaWV0eSBvZiBjb250ZXh0cy4gSG93ZXZlciwgdGhlcmUgYXJlIHBsZW50eSBtb3JlIG1vZGVscyB0byBjaG9vc2UgZnJvbSwgYW5kIHNvIHdlIHdpbGwgYnJpZWZseSBpbnRyb2R1Y2UgYSBmZXcgbmV3IG9wdGlvbnMgYmVsb3c6DQoNCiogTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcyBNb2RlbA0KKiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIE1vZGVsDQoqIGstTmVhcmVzdC1OZWlnaGJvdXIgTW9kZWwNCg0KRm9yIHRoZXNlIG1vZGVscywgd2Ugd2lsbCB1c2UgdGhlIGRlZmF1bHQgdHVuaW5nIHBhcmFtZXRlcnMgYW5kIHJlc2FtcGxpbmcgbWV0aG9kcy4NCg0KIyMgTERBDQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIEZpdCBhIGxpbmVhciBkaXNjcmltaW5hbnQgYW5hbHlzaXMgbW9kZWwgdG8geW91ciBgcmVkX3dpbmVfdHJhaW5gIGRhdGEgdmlhIHRoZSBtZXRob2Qgc3BlY2lmaWNhdGlvbiBgbGRhYC4NCg0KV2hhdCBpcyB0aGUgYmVzdCBhY2N1cmFjeSBhY2hpZXZlZCBieSB0aGlzIG1ldGhvZCBmb3IgdGhlIHRyYWluaW5nIGRhdGE/DQoNCiMjIFNWTQ0KDQpgciBlbW86OmppKCJjb21wdXRlciIpYCBGaXQgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIG1vZGVsIHRvIHlvdXIgYHJlZF93aW5lX3RyYWluYCBkYXRhIHZpYSB0aGUgbWV0aG9kIHNwZWNpZmljYXRpb24gYHN2bUxpbmVhcmAgKHRoZXJlIGFyZSBvdGhlciBvcHRpb25zLCBidXQgdGhlc2UgYXJlIG1vcmUgY29tcGxpY2F0ZWQgYW5kIHRha2UgbG9uZ2VyIHRvIGV4ZWN1dGUpLiANCg0KV2hhdCBpcyB0aGUgYmVzdCBhY2N1cmFjeSBhY2hpZXZlZCBieSB0aGlzIG1ldGhvZCBmb3IgdGhlIHRyYWluaW5nIGRhdGE/DQogDQoqTm90ZTogSW4gb3JkZXIgdG8gZml0IGEgc3ZtIG1vZGVsLCB3ZSBuZWVkIHRvIGxvYWQgdGhlIGBrZXJubGFiYCBwYWNrYWdlLiBUaGlzIHNob3VsZCBoYXZlIGJlZW4gZG9uZSBhbHJlYWR5IGluIFxAcmVmKGxvYWQpLCBidXQgaWYgYW4gZXJyb3IgYXBwZWFycyB3aGVuIGZpdHRpbmcgdGhlIG1vZGVsLCBqdXN0IGRvdWJsZS1jaGVjayB0aGlzLioNCg0KIyMga05ODQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIFRoZSBmaW5hbCBtZXRob2Qgd2Ugd2lsbCB0cnkgaXMgdGhlIGstTmVhcmVzdC1OZWlnaGJvdXJzIG1vZGVsLCB3aGljaCBpcyBzZWxlY3RlZCB2aWEgdGhlIG1ldGhvZCBzcGVjaWZpY2F0aW9uIGBrbm5gLiBGaXQgYSBrTk4gbW9kZWwgdG8geW91ciBgcmVkX3dpbmVfdHJhaW5gIGRhdGEuDQoNCldoYXQgaXMgdGhlIGJlc3QgYWNjdXJhY3kgYWNoaWV2ZWQgYnkgdGhpcyBtZXRob2QgZm9yIHRoZSB0cmFpbmluZyBkYXRhPw0KDQo8YnI+DQoNCiMjIyMgV2VsbCBkb25lLCB0aGF0IGNvbmNsdWRlcyBvdXIgd29yayBpbiBtYWNoaW5lIGxlYXJuaW5nLiAjIyMjIHstfQ0KDQpIb3BlZnVsbHkgdGhpcyBsYWIgaGFzIGVuaGFuY2VkIHlvdXIgdW5kZXJzdGFuZGluZyBvZiBob3cgdG8gY29uZHVjdCBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgaW4gUlN0dWRpby4gVGhpcyBpcyBqdXN0IHRoZSBiZWdpbm5pbmcgLSB0aGVyZSBhcmUgc28gbWFueSBkaWZmZXJlbnQgbW9kZWxzIGFuZCBtZXRob2RzIG91dCB0aGVyZSENCg0KPGJyPg0KDQojIFJlZmVyZW5jZXMgey0gI1JlZn0NCjxkaXYgaWQ9InJlZnMiPjwvZGl2Pg0KDQo8YnI+DQoNCjxmb250IGNvbG9yID0gImdyZXkiPg0KVGhlc2Ugbm90ZXMgaGF2ZSBiZWVuIHByZXBhcmVkIGJ5IFJ1cGVydCBLdXZla2UuIFBsZWFzZSBub3RlIHRoYXQgc29tZSBvZiB0aGUgY29udGVudCBpbiB0aGVzZSBub3RlcyBoYXMgYmVlbiBkZXZlbG9wZWQgZnJvbSBjb250ZW50IGluIEBNb2RTdGF0LiBUaGUgY29weXJpZ2h0IGZvciB0aGUgbWF0ZXJpYWwgaW4gdGhlc2Ugbm90ZXMgcmVzaWRlcyB3aXRoIHRoZSBhdXRob3JzIG5hbWVkIGFib3ZlLCB3aXRoIHRoZSBEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNhbCBhbmQgUGh5c2ljYWwgU2NpZW5jZXMgYW5kIHdpdGggTGEgVHJvYmUgVW5pdmVyc2l0eS4gQ29weXJpZ2h0IGluIHRoaXMgd29yayBpcyB2ZXN0ZWQgaW4gTGEgVHJvYmUgVW5pdmVyc2l0eSBpbmNsdWRpbmcgYWxsIExhIFRyb2JlIFVuaXZlcnNpdHkgYnJhbmRpbmcgYW5kIG5hbWluZy4gVW5sZXNzIG90aGVyd2lzZSBzdGF0ZWQsIG1hdGVyaWFsIHdpdGhpbiB0aGlzIHdvcmsgaXMgbGljZW5zZWQgdW5kZXIgYSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLU5vbiBDb21tZXJjaWFsLU5vbiBEZXJpdmF0aXZlcyBMaWNlbnNlIA0KPGEgaHJlZiA9ICJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtbmQvNC4wL0NDIiB0YXJnZXQ9Il9ibGFuayI+IEJZLU5DLU5ELiA8L2E+DQo8L2ZvbnQ+