1 - Description


MovieLens is a data set that contains ratings from the MovieLense website (http://movielens.org). This data set is broken into a number of different sizes for research purposes. We will be using the small data set containing 100,000 ratings applied to 9,000 movies by 700 users. The data set has already been filtered to include only users that have at least 10 reviews, we will reduce the data set even further to only look at the movies that have been reviewed at least 20 times. This should help us to zero in on the relevant information. We can come back later and adjust this parameter to tweak the recommender.

Using the MovieLense data set we will construct a user-based collaborative filter and an item-based collaborative filter recommender systems. We will then tweak the different parameters and see how this effects the accuracy of the recommendations. We will be using the recommenderlab package to assist the building of these systems.

2 - DataSet


The MovieLense data is broken into two data sets that we are interested in using. The first is the movies data that contains the movie ID, the title, and genres. The second is the user ratings data that contains the user ID, the movie ID, and the rating, and a time-stamp in UNIX time. We will drop the time-stamp data for this project.

2.1 - Reading in the Data

Reading in the required data set.

movies <- read.csv("~/GitHub/DATA643/data/ml-latest-small/movies.csv", header = TRUE, sep = ",",
                   stringsAsFactors = FALSE, encoding = "UTF-8")
ratings <- read.csv("~/GitHub/DATA643/data/ml-latest-small/ratings.csv", header = TRUE, sep =",",
                    stringsAsFactors = FALSE)
ratings <- ratings[,c(1,2,3)]

2.2 - Creating the User-Item Matrix

Now that we have our data set we will construct the user item matrix. The rows of the matrix represent the users and the columns of the matrix represent the movies. Converting the matrix to a real Rating Matrix reduces the size from almost 50MB to 1.7MB!

user_movie <- as(user_mat, "realRatingMatrix")
Error in .class1(object) : object 'user_mat' not found

2.3 - Reducing the Number of Movies

We know from the description of dataset that there are movies that have not been viewed as many times as others. We looking at the number of movies that have been reviewed at least 10 times we see that there are only 2083 movies that match this criteria. Reducing our data set to just these movies still gives us 80,295 ratings to work with.

user_movie
671 x 2083 rating matrix of class ‘realRatingMatrix’ with 80295 ratings.

2.4 - Creating Training and Test Data Sets

Given that we would like to compare a number of different models we need to create training and test sets that can be used for these comparisons. The training set will contain 80% of the data and the set set will contain the other 20%. We will use the built in recomenderlab function evaluationScheme to split the data. This will also allow us to us the build in evaluation functions to measure our performance.

movie
Evaluation scheme with 5 items given
Method: ‘split’ with 1 run(s).
Training set proportion: 0.800
Good ratings: >=3.000000
Data set: 671 x 2083 rating matrix of class ‘realRatingMatrix’ with 80295 ratings.

3 - Building and Evaluating the Models


For this section we will building and evaluating a number of different user-user and item-item collaborative filter models by varying the different parameters. We will also leverage some of built in features of recommenderlab to evaluate our various models at different recommendation levels. We will measure the RMSE, and look at the ROC plot and the precision-recall plots for various numbers of recommendations.

3.1 - User-Based Colaborative Filtering

We start by looking at the performance of the user-user CF recommender. We will be adjusting a number of the different available parameters to attempt the find the best performing model. Lets start with the neighborhood size.

3.1.1 - Neighborhood Size

We will start by looking at 6 different neighborhood sizes for the user-based CF.

user_nn_results <- evaluate(movie, user_nn, n = recs, progress = FALSE)
UBCF run fold/sample [model time/prediction time]
     1  [0.03sec/1.14sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.02sec/0.96sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.02sec/0.96sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.02sec/0.95sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.01sec/1.03sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.02sec/1.03sec] 

Drawing the ROC Curve

Dawing the Precision / Recall Curve

Calculating the RMSE

From the above graphs we see that there is effectively little difference between 40,50, and 60 nearest neighbors in the ROC and precision vs recall curves for all numbers of recommendations. Therefore we can pick any of these three values for the number of nearest neighbors to get similar results. Lets choose 50 for our best candidate and use this value to check other parameters. We see that using these parameters gives us a RMSE of 1.00462.

rmse_ubcf
   RMSE 
1.00462 

3.1.2 - Normalization Method

Our next parameter of interest is the method of normalization. Currently recommenderlab supports center and z-score. Lest see which of the two gives us the best results.

user_norm_results <- evaluate(movie, user_norm, n = recs, progress = FALSE)
UBCF run fold/sample [model time/prediction time]
     1  [0sec/1sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.02sec/1sec] 

Drawing the ROC Curve

Dawing the Precision / Recall Curve

Calculating the RMSE
We can see that the above graphs indicate that the Z-Score centering does a slightly better job at many of the recommendation levels. This is the same normalization technique that we used to calculate the RMSE above so we will continue using this as our best user based collaborative filtering model.

3.1.3 Distance Methods

The final parameter that we will be tweaking is the measurement of the distance or similarity of a user and their nearest neighbors. To do this we will look at three different measurements of the similarity; specifically the Pearson’s, Cosine, and Jaccard distances. Using the results from the previous analysis we will use the 50 nearest neighbors and the Z-score normalization as we analyze the distance parameter.

user_dist_results <- evaluate(movie, user_dist, n = recs, progress = FALSE)
UBCF run fold/sample [model time/prediction time]
     1  [0.03sec/0.64sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.01sec/1.02sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.01sec/0.71sec] 

Drawing the ROC Curve

Dawing the Precision / Recall Curve

Calculating the RMSE We can see from the above graphs that the Jaccard distance seems to slightly outperform the other distance methods although we do note that the Pearson’s distance has very strong performance for smaller numbers of recommendations. We will go ahead and calculate the RMSE for both to determine which is the best.

Jaccard Distance RMSE

rmse_dist
    RMSE 
1.012067 

Pearson’s Distance RMSE

rmse_dist
     RMSE 
0.9414067 

Interestingly we see that the best performance of the model comes from the Pearson’s Method of calculating the distance in the user based collaborative filtering model. Our best user based collaborative filtering model ends up incorporating the following parameters:

  • Uses the 50 nearest neighbors
  • Normalizes the data using the Z-Score
  • Calculates the similarity using Pearson’s Similarity

3.2 Item Based Collaborative Filtering

Having developed a best performing model using user based collaborative filtering we will now work on developing an item based collaborative filter. We will follow the same methodology as a above to find our best item based model and compare these two models to each other.

3.2.1 - Neighborhood Size

We will start by looking at 7 different neighborhood sizes for the item-based CF.

item_nn_results <- evaluate(movie, item_nn, n = recs, progress = FALSE)
IBCF run fold/sample [model time/prediction time]
     1  [66.75sec/0.05sec] 
IBCF run fold/sample [model time/prediction time]
     1  [46.15sec/0.05sec] 
IBCF run fold/sample [model time/prediction time]
     1  [45.82sec/0.06sec] 
IBCF run fold/sample [model time/prediction time]
     1  [45.35sec/0.04sec] 
IBCF run fold/sample [model time/prediction time]
     1  [45.9sec/0.05sec] 
IBCF run fold/sample [model time/prediction time]
     1  [41.04sec/0.05sec] 
IBCF run fold/sample [model time/prediction time]
     1  [40.8sec/0.04sec] 

Drawing the ROC Curve

Dawing the Precision / Recall Curve

Calculating the RMSE

From the above graphs we see that the best performance comes from including 10 items in the neighborhood in the ROC and precision vs recall curves for all numbers of recommendations. We see that using these parameters gives us a RMSE of 1.175974.

rmse_ibcf
    RMSE 
1.175974 

3.2.2 Normalization Methods

Our next parameter of interest is the method of normalization. Currently recommenderlab supports center and z-score. Lest see which of the two gives us the best results.

item_norm_results <- evaluate(movie, item_norm, n = recs, progress = FALSE)
IBCF run fold/sample [model time/prediction time]
     1  [41.19sec/0.06sec] 
IBCF run fold/sample [model time/prediction time]
     1  [42.73sec/0.04sec] 

Drawing the ROC Curve

Dawing the Precision / Recall Curve

Calculating the RMSE
We can see that the above graphs indicate that the Z-Score centering does a slightly better job at many of the recommendation levels. This is the same normalization technique that we used to calculate the RMSE above so we will continue using this as our best user based collaborative filtering model.

3.2.3 Distance Methods

The final parameter that we will be tweaking is the measurement of the distance or similarity of an item and their nearest neighbors. To do this we will look at three different measurements of the similarity; specifically the Pearson’s, Cosine, and Jaccard distances. Using the results from the previous analysis we will use the 10 nearest items and the Z-score normalization as we analyze the distance parameter.

item_dist_results <- evaluate(movie, item_dist, n = recs, progress = FALSE)
IBCF run fold/sample [model time/prediction time]
     1  

Drawing the ROC Curve

Dawing the Precision / Recall Curve

Calculating the RMSE We can see from the above graphs that the Jaccard distance seems to outperform the other distance methods although we do note that the Pearson’s distance has some very odd performance. We will go ahead and calculate the RMSE for both to determine which is the best.

rmse_item
    RMSE 
1.077888 

We see that the best performance of the model comes from the Jaccard Method of calculating the distance in the item based collaborative filtering model. Our best user based collaborative filtering model ends up incorporating the following parameters:

  • 10 Nearest Items
  • Normalized using the Z-Score
  • Similarity Calculated using Jaccard Method

4 - Conclustions

In this project we have attempted to build the best possible user-based and item-based collaborative filtering model by tweaking the parameters available to in the recomenderlab package. Now that we have these two models lets compare them to each other. Form our comparison below of the two models we see that, with our best parameter set, The UBCF model performs better on our test data set then the IBCF. The UBCF show better results in both the ROC curve and the precision recall curves. When we look at the RMSE, a measure of the accuracy of our predictions, we see that the UBCF has a RMSE of 0.9414067 while the IBCF has a RMSE of 1.077888. Finally we do get some interesting results when we look at times to compile and predict in the model. The UBCF took almost no time to compile and about .64 seconds to do it’s predictions while the IBCF took 37.12 seconds to compile the model but predicted the results in 0.05 seconds. If this trend continues over multiple runs of the two models then it may come down to how often you need to build a new model vs the number of times you need to provide recommendations with these two models. It appears that the UBCF give us the best accuracy at the cost of slower recommendations while the IBCF gives us slightly worse but faster performance.

final_results <- evaluate(movie, final_models, n = recs, progress = FALSE)
UBCF run fold/sample [model time/prediction time]
     1  [0.02sec/0.64sec] 
IBCF run fold/sample [model time/prediction time]
     1  [37.12sec/0.05sec] 

Drawing the ROC Curve

Dawing the Precision / Recall Curve

LS0tDQp0aXRsZTogJ0RBVEE2NDMgLSBQcm9qZWN0IDI6IEV2YWx1YXRpbmcgUmVjb21tZW5kZXIgU3lzdGVtcycNCmF1dGhvcjogIkVyaWsgTnlsYW5kZXIiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCmBgYHtSLCBpbmNsdWRlID0gRkFMU0V9DQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShyZWNvbW1lbmRlcmxhYikNCmBgYA0KIyMgMSAtIERlc2NyaXB0aW9uDQoqKioNCltNb3ZpZUxlbnNdKGh0dHBzOi8vZ3JvdXBsZW5zLm9yZy9kYXRhc2V0cy9tb3ZpZWxlbnMvKSBpcyBhIGRhdGEgc2V0IHRoYXQgY29udGFpbnMgcmF0aW5ncyBmcm9tIHRoZSBNb3ZpZUxlbnNlIHdlYnNpdGUgKGh0dHA6Ly9tb3ZpZWxlbnMub3JnKS4gVGhpcyBkYXRhIHNldCBpcyBicm9rZW4gaW50byBhIG51bWJlciBvZiBkaWZmZXJlbnQgc2l6ZXMgZm9yIHJlc2VhcmNoIHB1cnBvc2VzLiBXZSB3aWxsIGJlIHVzaW5nIHRoZSBzbWFsbCBkYXRhIHNldCBjb250YWluaW5nIDEwMCwwMDAgcmF0aW5ncyBhcHBsaWVkIHRvIDksMDAwIG1vdmllcyBieSA3MDAgdXNlcnMuIFRoZSBkYXRhIHNldCBoYXMgYWxyZWFkeSBiZWVuIGZpbHRlcmVkIHRvIGluY2x1ZGUgb25seSB1c2VycyB0aGF0IGhhdmUgYXQgbGVhc3QgMTAgcmV2aWV3cywgd2Ugd2lsbCByZWR1Y2UgdGhlIGRhdGEgc2V0IGV2ZW4gZnVydGhlciB0byBvbmx5IGxvb2sgYXQgdGhlIG1vdmllcyB0aGF0IGhhdmUgYmVlbiByZXZpZXdlZCBhdCBsZWFzdCAyMCB0aW1lcy4gVGhpcyBzaG91bGQgaGVscCB1cyB0byB6ZXJvIGluIG9uIHRoZSByZWxldmFudCBpbmZvcm1hdGlvbi4gV2UgY2FuIGNvbWUgYmFjayBsYXRlciBhbmQgYWRqdXN0IHRoaXMgcGFyYW1ldGVyIHRvIHR3ZWFrIHRoZSByZWNvbW1lbmRlci4NCg0KVXNpbmcgdGhlIE1vdmllTGVuc2UgZGF0YSBzZXQgd2Ugd2lsbCBjb25zdHJ1Y3QgYSB1c2VyLWJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyIGFuZCBhbiBpdGVtLWJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyIHJlY29tbWVuZGVyIHN5c3RlbXMuIFdlIHdpbGwgdGhlbiB0d2VhayB0aGUgZGlmZmVyZW50IHBhcmFtZXRlcnMgYW5kIHNlZSBob3cgdGhpcyBlZmZlY3RzIHRoZSBhY2N1cmFjeSBvZiB0aGUgcmVjb21tZW5kYXRpb25zLiBXZSB3aWxsIGJlIHVzaW5nIHRoZSAqcmVjb21tZW5kZXJsYWIqIHBhY2thZ2UgdG8gYXNzaXN0IHRoZSBidWlsZGluZyBvZiB0aGVzZSBzeXN0ZW1zLiANCg0KIyMgMiAtIERhdGFTZXQNCioqKg0KVGhlIE1vdmllTGVuc2UgZGF0YSBpcyBicm9rZW4gaW50byB0d28gZGF0YSBzZXRzIHRoYXQgd2UgYXJlIGludGVyZXN0ZWQgaW4gdXNpbmcuIFRoZSBmaXJzdCBpcyB0aGUgbW92aWVzIGRhdGEgdGhhdCBjb250YWlucyB0aGUgbW92aWUgSUQsIHRoZSB0aXRsZSwgYW5kIGdlbnJlcy4gVGhlIHNlY29uZCBpcyB0aGUgdXNlciByYXRpbmdzIGRhdGEgdGhhdCBjb250YWlucyB0aGUgdXNlciBJRCwgdGhlIG1vdmllIElELCBhbmQgdGhlIHJhdGluZywgYW5kIGEgdGltZS1zdGFtcCBpbiBVTklYIHRpbWUuIFdlIHdpbGwgZHJvcCB0aGUgdGltZS1zdGFtcCBkYXRhIGZvciB0aGlzIHByb2plY3QuDQoNCiMjIyMgMi4xIC0gUmVhZGluZyBpbiB0aGUgRGF0YQ0KUmVhZGluZyBpbiB0aGUgcmVxdWlyZWQgZGF0YSBzZXQuDQoNCmBgYHtSLCBpbmNsdWRlID0gVFJVRX0NCm1vdmllcyA8LSByZWFkLmNzdigifi9HaXRIdWIvREFUQTY0My9kYXRhL21sLWxhdGVzdC1zbWFsbC9tb3ZpZXMuY3N2IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIiwiLA0KICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwgZW5jb2RpbmcgPSAiVVRGLTgiKQ0KcmF0aW5ncyA8LSByZWFkLmNzdigifi9HaXRIdWIvREFUQTY0My9kYXRhL21sLWxhdGVzdC1zbWFsbC9yYXRpbmdzLmNzdiIsIGhlYWRlciA9IFRSVUUsIHNlcCA9IiwiLA0KICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpyYXRpbmdzIDwtIHJhdGluZ3NbLGMoMSwyLDMpXQ0KYGBgDQoNCiMjIyMgMi4yIC0gQ3JlYXRpbmcgdGhlIFVzZXItSXRlbSBNYXRyaXgNCk5vdyB0aGF0IHdlIGhhdmUgb3VyIGRhdGEgc2V0IHdlIHdpbGwgY29uc3RydWN0IHRoZSB1c2VyIGl0ZW0gbWF0cml4LiBUaGUgcm93cyBvZiB0aGUgbWF0cml4IHJlcHJlc2VudCB0aGUgdXNlcnMgYW5kIHRoZSBjb2x1bW5zIG9mIHRoZSBtYXRyaXggcmVwcmVzZW50IHRoZSBtb3ZpZXMuIENvbnZlcnRpbmcgdGhlIG1hdHJpeCB0byBhIHJlYWwgUmF0aW5nIE1hdHJpeCByZWR1Y2VzIHRoZSBzaXplIGZyb20gYWxtb3N0IDUwTUIgdG8gMS43TUIhDQpgYGB7Un0NCiMgQ3JlYXRpbmcgYSBzcGFyc2UgbWF0cml4IG9mIHVzZXJzIGFzIHJvd3MgYW5kIG1vdmllSWQgYXMgY29sdW1ucy4NCnVzZXJfbW92aWUgPC0gcmF0aW5ncyAlPiUNCiAgc3ByZWFkKGtleSA9IG1vdmllSWQsIHZhbHVlID0gcmF0aW5nKSAlPiUNCiAgYXMubWF0cml4KCkNCg0KdXNlcl9tb3ZpZSA9IHVzZXJfbW92aWVbLC0xXSAjcmVtb3ZlIHVzZXJJRCBjb2wuIFJvd3MgYXJlIHVzZXJJZHMsIGNvbHMgYXJlIG1vdmllSWRzDQoNCnVzZXJfbW92aWUgPC0gYXModXNlcl9tb3ZpZSwgInJlYWxSYXRpbmdNYXRyaXgiKQ0KYGBgDQoNCiMjIyMgMi4zIC0gUmVkdWNpbmcgdGhlIE51bWJlciBvZiBNb3ZpZXMNCldlIGtub3cgZnJvbSB0aGUgZGVzY3JpcHRpb24gb2YgZGF0YXNldCB0aGF0IHRoZXJlIGFyZSBtb3ZpZXMgdGhhdCBoYXZlIG5vdCBiZWVuIHZpZXdlZCBhcyBtYW55IHRpbWVzIGFzIG90aGVycy4gV2UgbG9va2luZyBhdCB0aGUgbnVtYmVyIG9mIG1vdmllcyB0aGF0IGhhdmUgYmVlbiByZXZpZXdlZCBhdCBsZWFzdCAxMCB0aW1lcyB3ZSBzZWUgdGhhdCB0aGVyZSBhcmUgb25seSAyMDgzIG1vdmllcyB0aGF0IG1hdGNoIHRoaXMgY3JpdGVyaWEuIFJlZHVjaW5nIG91ciBkYXRhIHNldCB0byBqdXN0IHRoZXNlIG1vdmllcyBzdGlsbCBnaXZlcyB1cyA4MCwyOTUgcmF0aW5ncyB0byB3b3JrIHdpdGguDQoNCmBgYHtSfQ0KZGltKHVzZXJfbW92aWVbLGNvbENvdW50cyh1c2VyX21vdmllKSA+IDEwXSkNCg0KdXNlcl9tb3ZpZSA8LSB1c2VyX21vdmllWyxjb2xDb3VudHModXNlcl9tb3ZpZSkgPiAxMF0NCnVzZXJfbW92aWUNCmBgYA0KDQojIyMjIDIuNCAtIENyZWF0aW5nIFRyYWluaW5nIGFuZCBUZXN0IERhdGEgU2V0cw0KR2l2ZW4gdGhhdCB3ZSB3b3VsZCBsaWtlIHRvIGNvbXBhcmUgYSBudW1iZXIgb2YgZGlmZmVyZW50IG1vZGVscyB3ZSBuZWVkIHRvIGNyZWF0ZSB0cmFpbmluZyBhbmQgdGVzdCBzZXRzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIHRoZXNlIGNvbXBhcmlzb25zLiBUaGUgdHJhaW5pbmcgc2V0IHdpbGwgY29udGFpbiA4MCUgb2YgdGhlIGRhdGEgYW5kIHRoZSBzZXQgc2V0IHdpbGwgY29udGFpbiB0aGUgb3RoZXIgMjAlLiBXZSB3aWxsIHVzZSB0aGUgYnVpbHQgaW4gKnJlY29tZW5kZXJsYWIqIGZ1bmN0aW9uICpldmFsdWF0aW9uU2NoZW1lKiB0byBzcGxpdCB0aGUgZGF0YS4gVGhpcyB3aWxsIGFsc28gYWxsb3cgdXMgdG8gdXMgdGhlIGJ1aWxkIGluIGV2YWx1YXRpb24gZnVuY3Rpb25zIHRvIG1lYXN1cmUgb3VyIHBlcmZvcm1hbmNlLg0KDQpgYGB7Un0NCnNldC5zZWVkKDQyKSAjIFdoYXQgb3RoZXIgc2VlZCBpcyB0aGVyZSENCg0KbW92aWUgPC0gZXZhbHVhdGlvblNjaGVtZSh1c2VyX21vdmllLCBtZXRob2QgPSAic3BsaXQiLCB0cmFpbiA9IC44LCBnaXZlbiA9IDUsIGdvb2RSYXRpbmcgPSAzKQ0KbW92aWUNCmBgYA0KDQojIyAzIC0gQnVpbGRpbmcgYW5kIEV2YWx1YXRpbmcgdGhlIE1vZGVscw0KKioqDQpGb3IgdGhpcyBzZWN0aW9uIHdlIHdpbGwgYnVpbGRpbmcgYW5kIGV2YWx1YXRpbmcgYSBudW1iZXIgb2YgZGlmZmVyZW50IHVzZXItdXNlciBhbmQgaXRlbS1pdGVtIGNvbGxhYm9yYXRpdmUgZmlsdGVyIG1vZGVscyBieSB2YXJ5aW5nIHRoZSBkaWZmZXJlbnQgcGFyYW1ldGVycy4gV2Ugd2lsbCBhbHNvIGxldmVyYWdlIHNvbWUgb2YgYnVpbHQgaW4gZmVhdHVyZXMgb2YgKnJlY29tbWVuZGVybGFiKiB0byBldmFsdWF0ZSBvdXIgdmFyaW91cyBtb2RlbHMgYXQgZGlmZmVyZW50IHJlY29tbWVuZGF0aW9uIGxldmVscy4gV2Ugd2lsbCBtZWFzdXJlIHRoZSBSTVNFLCBhbmQgbG9vayBhdCB0aGUgUk9DIHBsb3QgYW5kIHRoZSBwcmVjaXNpb24tcmVjYWxsIHBsb3RzIGZvciB2YXJpb3VzIG51bWJlcnMgb2YgcmVjb21tZW5kYXRpb25zLg0KDQojIyMgMy4xIC0gVXNlci1CYXNlZCBDb2xhYm9yYXRpdmUgRmlsdGVyaW5nDQpXZSBzdGFydCBieSBsb29raW5nIGF0IHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgdXNlci11c2VyIENGIHJlY29tbWVuZGVyLiBXZSB3aWxsIGJlIGFkanVzdGluZyBhIG51bWJlciBvZiB0aGUgZGlmZmVyZW50IGF2YWlsYWJsZSBwYXJhbWV0ZXJzIHRvIGF0dGVtcHQgdGhlIGZpbmQgdGhlIGJlc3QgcGVyZm9ybWluZyBtb2RlbC4gTGV0cyBzdGFydCB3aXRoIHRoZSBuZWlnaGJvcmhvb2Qgc2l6ZS4NCg0KIyMjIyAzLjEuMSAtIE5laWdoYm9yaG9vZCBTaXplIA0KV2Ugd2lsbCBzdGFydCBieSBsb29raW5nIGF0IDYgZGlmZmVyZW50IG5laWdoYm9yaG9vZCBzaXplcyBmb3IgdGhlIHVzZXItYmFzZWQgQ0YuDQpgYGB7Un0NCiNMZXZlcmFnaW5nIHJlY29tbWVuZGVybGFicyBhYmlsaXR5IHRvIHJ1biBtdWx0aXBsZSBtb2RlbHMgYXQgb25jZSBmb3IgZXZhbHVhdGlvbi4NCnVzZXJfbm4gPC0gbGlzdCgNCiAgIjEwIE5OIiA9IGxpc3QobmFtZT0iVUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5uPTEwKSksDQogICIyMCBOTiIgPSBsaXN0KG5hbWU9IlVCQ0YiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubj0yMCkpLA0KICAiMzAgTk4iID0gbGlzdChuYW1lPSJVQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm49MzApKSwNCiAgIjQwIE5OIiA9IGxpc3QobmFtZT0iVUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5uPTQwKSksDQogICI1MCBOTiIgPSBsaXN0KG5hbWU9IlVCQ0YiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubj01MCkpLA0KICAiNjAgTk4iID0gbGlzdChuYW1lPSJVQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm49NjApKQ0KKQ0KDQojIFJ1biB0aGUgYWxnb3JpdGhtIGFuZCBwcmVkaWNlIHRoIG5leHQgbiBtb3ZpZXMgZm9yIGNvbXBhcmlzb24gcHVycG9zZXMNCnJlY3MgPC0gYygxLDUsIDEwLCAxNSwgMjAsIDI1KQ0KdXNlcl9ubl9yZXN1bHRzIDwtIGV2YWx1YXRlKG1vdmllLCB1c2VyX25uLCBuID0gcmVjcywgcHJvZ3Jlc3MgPSBGQUxTRSkNCmBgYA0KDQoqKkRyYXdpbmcgdGhlIFJPQyBDdXJ2ZSoqDQpgYGB7Un0NCiMgRHJhdyB0aGUgUk9DIGN1cnZlDQpwbG90KHggPSB1c2VyX25uX3Jlc3VsdHMsIHkgPSAiUk9DIiwgYW5ub3RhdGUgPSA0LCBsZWdlbmQ9InRvcGxlZnQiKQ0KYGBgDQoNCioqRGF3aW5nIHRoZSBQcmVjaXNpb24gLyBSZWNhbGwgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIHByZWNpc2lvbiAvIHJlY2FsbCBjdXJ2ZQ0KcGxvdCh4ID0gdXNlcl9ubl9yZXN1bHRzLCB5ID0gInByZWMvcmVjIiwgYW5ub3RhdGUgPSA1KQ0KYGBgDQoNCioqQ2FsY3VsYXRpbmcgdGhlIFJNU0UqKiANCg0KRnJvbSB0aGUgYWJvdmUgZ3JhcGhzIHdlIHNlZSB0aGF0IHRoZXJlIGlzIGVmZmVjdGl2ZWx5IGxpdHRsZSBkaWZmZXJlbmNlIGJldHdlZW4gNDAsNTAsIGFuZCA2MCBuZWFyZXN0IG5laWdoYm9ycyBpbiB0aGUgUk9DIGFuZCBwcmVjaXNpb24gdnMgcmVjYWxsIGN1cnZlcyBmb3IgYWxsIG51bWJlcnMgb2YgcmVjb21tZW5kYXRpb25zLiBUaGVyZWZvcmUgd2UgY2FuIHBpY2sgYW55IG9mIHRoZXNlIHRocmVlIHZhbHVlcyBmb3IgdGhlIG51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyB0byBnZXQgc2ltaWxhciByZXN1bHRzLiBMZXRzIGNob29zZSA1MCBmb3Igb3VyIGJlc3QgY2FuZGlkYXRlIGFuZCB1c2UgdGhpcyB2YWx1ZSB0byBjaGVjayBvdGhlciBwYXJhbWV0ZXJzLiBXZSBzZWUgdGhhdCB1c2luZyB0aGVzZSBwYXJhbWV0ZXJzIGdpdmVzIHVzIGEgUk1TRSBvZiAxLjAwNDYyLiANCg0KYGBge1J9DQptb2RlbCA8LSBSZWNvbW1lbmRlcihnZXREYXRhKG1vdmllLCAidHJhaW4iKSwgbWV0aG9kID0gIlVCQ0YiLCANCiAgICAgICAgICAgICAgICAgICAgIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotU2NvcmUiLCBtZXRob2Q9IkNvc2luZSIsIG5uPTUwKSkNCg0KcHJlZGljdGlvbiA8LSBwcmVkaWN0KG1vZGVsLCBnZXREYXRhKG1vdmllLCAia25vd24iKSwgdHlwZT0icmF0aW5ncyIpDQoNCnJtc2VfdWJjZiA8LSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KHByZWRpY3Rpb24sIGdldERhdGEobW92aWUsICJ1bmtub3duIikpWzFdDQpybXNlX3ViY2YNCmBgYA0KDQojIyMjIDMuMS4yIC0gTm9ybWFsaXphdGlvbiBNZXRob2QNCk91ciBuZXh0IHBhcmFtZXRlciBvZiBpbnRlcmVzdCBpcyB0aGUgbWV0aG9kIG9mIG5vcm1hbGl6YXRpb24uIEN1cnJlbnRseSAqcmVjb21tZW5kZXJsYWIqIHN1cHBvcnRzIGNlbnRlciBhbmQgei1zY29yZS4gTGVzdCBzZWUgd2hpY2ggb2YgdGhlIHR3byBnaXZlcyB1cyB0aGUgYmVzdCByZXN1bHRzLg0KDQpgYGB7Un0NCnVzZXJfbm9ybSA8LSBsaXN0KA0KICAiQ2VudGVyIiA9IGxpc3QobmFtZT0iVUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gImNlbnRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm49NTApKSwNCiAgIlotc2NvcmUiID0gbGlzdChuYW1lPSJVQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm49NTApKQ0KKQ0KDQp1c2VyX25vcm1fcmVzdWx0cyA8LSBldmFsdWF0ZShtb3ZpZSwgdXNlcl9ub3JtLCBuID0gcmVjcywgcHJvZ3Jlc3MgPSBGQUxTRSkNCmBgYA0KKipEcmF3aW5nIHRoZSBST0MgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIFJPQyBjdXJ2ZQ0KcGxvdCh4ID0gdXNlcl9ub3JtX3Jlc3VsdHMsIHkgPSAiUk9DIiwgYW5ub3RhdGUgPSAxLCBsZWdlbmQ9InRvcGxlZnQiKQ0KYGBgDQoNCg0KKipEYXdpbmcgdGhlIFByZWNpc2lvbiAvIFJlY2FsbCBDdXJ2ZSoqDQpgYGB7Un0NCiMgRHJhdyB0aGUgcHJlY2lzaW9uIC8gcmVjYWxsIGN1cnZlDQpwbG90KHggPSB1c2VyX25vcm1fcmVzdWx0cywgeSA9ICJwcmVjL3JlYyIsIGFubm90YXRlID0gMSkNCmBgYA0KDQoqKkNhbGN1bGF0aW5nIHRoZSBSTVNFKiogIA0KV2UgY2FuIHNlZSB0aGF0IHRoZSBhYm92ZSBncmFwaHMgaW5kaWNhdGUgdGhhdCB0aGUgWi1TY29yZSBjZW50ZXJpbmcgZG9lcyBhIHNsaWdodGx5IGJldHRlciBqb2IgYXQgbWFueSBvZiB0aGUgcmVjb21tZW5kYXRpb24gbGV2ZWxzLiBUaGlzIGlzIHRoZSBzYW1lIG5vcm1hbGl6YXRpb24gdGVjaG5pcXVlIHRoYXQgd2UgdXNlZCB0byBjYWxjdWxhdGUgdGhlIFJNU0UgYWJvdmUgc28gd2Ugd2lsbCBjb250aW51ZSB1c2luZyB0aGlzIGFzIG91ciBiZXN0IHVzZXIgYmFzZWQgY29sbGFib3JhdGl2ZSBmaWx0ZXJpbmcgbW9kZWwuDQoNCiMjIyMgMy4xLjMgRGlzdGFuY2UgTWV0aG9kcw0KVGhlIGZpbmFsIHBhcmFtZXRlciB0aGF0IHdlIHdpbGwgYmUgdHdlYWtpbmcgaXMgdGhlIG1lYXN1cmVtZW50IG9mIHRoZSBkaXN0YW5jZSBvciBzaW1pbGFyaXR5IG9mIGEgdXNlciBhbmQgdGhlaXIgbmVhcmVzdCBuZWlnaGJvcnMuIFRvIGRvIHRoaXMgd2Ugd2lsbCBsb29rIGF0IHRocmVlIGRpZmZlcmVudCBtZWFzdXJlbWVudHMgb2YgdGhlIHNpbWlsYXJpdHk7IHNwZWNpZmljYWxseSB0aGUgUGVhcnNvbidzLCBDb3NpbmUsIGFuZCBKYWNjYXJkIGRpc3RhbmNlcy4gVXNpbmcgdGhlIHJlc3VsdHMgZnJvbSB0aGUgcHJldmlvdXMgYW5hbHlzaXMgd2Ugd2lsbCB1c2UgdGhlIDUwIG5lYXJlc3QgbmVpZ2hib3JzIGFuZCB0aGUgWi1zY29yZSBub3JtYWxpemF0aW9uIGFzIHdlIGFuYWx5emUgdGhlIGRpc3RhbmNlIHBhcmFtZXRlci4NCg0KYGBge1J9DQp1c2VyX2Rpc3QgPC0gbGlzdCgNCiAgIlBlYXJzb25zIiA9IGxpc3QobmFtZT0iVUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gInotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9InBlYXJzb24iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubj01MCkpLA0KICAiQ29zaW5lIiA9IGxpc3QobmFtZT0iVUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5uPTUwKSksDQogICJKYWNjYXJkIiA9IGxpc3QobmFtZT0iVUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9ImphY2NhcmQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubj01MCkpDQopDQoNCnVzZXJfZGlzdF9yZXN1bHRzIDwtIGV2YWx1YXRlKG1vdmllLCB1c2VyX2Rpc3QsIG4gPSByZWNzLCBwcm9ncmVzcyA9IEZBTFNFKQ0KYGBgDQoNCioqRHJhd2luZyB0aGUgUk9DIEN1cnZlKioNCmBgYHtSfQ0KIyBEcmF3IHRoZSBST0MgY3VydmUNCnBsb3QoeCA9IHVzZXJfZGlzdF9yZXN1bHRzLCB5ID0gIlJPQyIsIGFubm90YXRlID0gMywgbGVnZW5kPSJ0b3BsZWZ0IikNCmBgYA0KDQoqKkRhd2luZyB0aGUgUHJlY2lzaW9uIC8gUmVjYWxsIEN1cnZlKioNCmBgYHtSfQ0KIyBEcmF3IHRoZSBwcmVjaXNpb24gLyByZWNhbGwgY3VydmUNCnBsb3QoeCA9IHVzZXJfZGlzdF9yZXN1bHRzLCB5ID0gInByZWMvcmVjIiwgYW5ub3RhdGUgPSBjKDEsMykpDQpgYGANCg0KKipDYWxjdWxhdGluZyB0aGUgUk1TRSoqIA0KV2UgY2FuIHNlZSBmcm9tIHRoZSBhYm92ZSBncmFwaHMgdGhhdCB0aGUgSmFjY2FyZCBkaXN0YW5jZSBzZWVtcyB0byBzbGlnaHRseSBvdXRwZXJmb3JtIHRoZSBvdGhlciBkaXN0YW5jZSBtZXRob2RzIGFsdGhvdWdoIHdlIGRvIG5vdGUgdGhhdCB0aGUgUGVhcnNvbidzIGRpc3RhbmNlIGhhcyB2ZXJ5IHN0cm9uZyBwZXJmb3JtYW5jZSBmb3Igc21hbGxlciBudW1iZXJzIG9mIHJlY29tbWVuZGF0aW9ucy4gV2Ugd2lsbCBnbyBhaGVhZCBhbmQgY2FsY3VsYXRlIHRoZSBSTVNFIGZvciBib3RoIHRvIGRldGVybWluZSB3aGljaCBpcyB0aGUgYmVzdC4gIA0KDQoqKkphY2NhcmQgRGlzdGFuY2UgUk1TRSoqDQpgYGB7Un0NCm1vZGVsIDwtIFJlY29tbWVuZGVyKGdldERhdGEobW92aWUsICJ0cmFpbiIpLCBtZXRob2QgPSAiVUJDRiIsIA0KICAgICAgICAgICAgICAgICAgICAgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1TY29yZSIsIG1ldGhvZD0iamFjY2FyZCIsIG5uPTUwKSkNCg0KcHJlZGljdGlvbiA8LSBwcmVkaWN0KG1vZGVsLCBnZXREYXRhKG1vdmllLCAia25vd24iKSwgdHlwZT0icmF0aW5ncyIpDQoNCnJtc2VfZGlzdCA8LSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KHByZWRpY3Rpb24sIGdldERhdGEobW92aWUsICJ1bmtub3duIikpWzFdDQpybXNlX2Rpc3QNCmBgYA0KDQoqKlBlYXJzb24ncyBEaXN0YW5jZSBSTVNFKioNCmBgYHtSfQ0KbW9kZWwgPC0gUmVjb21tZW5kZXIoZ2V0RGF0YShtb3ZpZSwgInRyYWluIiksIG1ldGhvZCA9ICJVQkNGIiwgDQogICAgICAgICAgICAgICAgICAgICBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLVNjb3JlIiwgbWV0aG9kPSJwZWFyc29uIiwgbm49NTApKQ0KDQpwcmVkaWN0aW9uIDwtIHByZWRpY3QobW9kZWwsIGdldERhdGEobW92aWUsICJrbm93biIpLCB0eXBlPSJyYXRpbmdzIikNCg0Kcm1zZV9kaXN0IDwtIGNhbGNQcmVkaWN0aW9uQWNjdXJhY3kocHJlZGljdGlvbiwgZ2V0RGF0YShtb3ZpZSwgInVua25vd24iKSlbMV0NCnJtc2VfZGlzdA0KYGBgDQoNCkludGVyZXN0aW5nbHkgd2Ugc2VlIHRoYXQgdGhlIGJlc3QgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsIGNvbWVzIGZyb20gdGhlIFBlYXJzb24ncyBNZXRob2Qgb2YgY2FsY3VsYXRpbmcgdGhlIGRpc3RhbmNlIGluIHRoZSB1c2VyIGJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIG1vZGVsLiBPdXIgYmVzdCB1c2VyIGJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIG1vZGVsIGVuZHMgdXAgaW5jb3Jwb3JhdGluZyB0aGUgZm9sbG93aW5nIHBhcmFtZXRlcnM6ICANCg0KICAqIFVzZXMgdGhlIDUwIG5lYXJlc3QgbmVpZ2hib3JzDQogICogTm9ybWFsaXplcyB0aGUgZGF0YSB1c2luZyB0aGUgWi1TY29yZQ0KICAqIENhbGN1bGF0ZXMgdGhlIHNpbWlsYXJpdHkgdXNpbmcgUGVhcnNvbidzIFNpbWlsYXJpdHkNCiAgDQoNCiMjIyMgMy4yIEl0ZW0gQmFzZWQgQ29sbGFib3JhdGl2ZSBGaWx0ZXJpbmcgIA0KSGF2aW5nIGRldmVsb3BlZCBhIGJlc3QgcGVyZm9ybWluZyBtb2RlbCB1c2luZyB1c2VyIGJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIHdlIHdpbGwgbm93IHdvcmsgb24gZGV2ZWxvcGluZyBhbiBpdGVtIGJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyLiBXZSB3aWxsIGZvbGxvdyB0aGUgc2FtZSBtZXRob2RvbG9neSBhcyBhIGFib3ZlIHRvIGZpbmQgb3VyIGJlc3QgaXRlbSBiYXNlZCBtb2RlbCBhbmQgY29tcGFyZSB0aGVzZSB0d28gbW9kZWxzIHRvIGVhY2ggb3RoZXIuICANCg0KIyMjIyAzLjIuMSAtIE5laWdoYm9yaG9vZCBTaXplIA0KV2Ugd2lsbCBzdGFydCBieSBsb29raW5nIGF0IDcgZGlmZmVyZW50IG5laWdoYm9yaG9vZCBzaXplcyBmb3IgdGhlIGl0ZW0tYmFzZWQgQ0YuDQpgYGB7Un0NCiNMZXZlcmFnaW5nIHJlY29tbWVuZGVybGFicyBhYmlsaXR5IHRvIHJ1biBtdWx0aXBsZSBtb2RlbHMgYXQgb25jZSBmb3IgZXZhbHVhdGlvbi4NCml0ZW1fbm4gPC0gbGlzdCgNCiAgIjEwIEsiID0gbGlzdChuYW1lPSJJQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaz0xMCkpLA0KICAiMTUgSyIgPSBsaXN0KG5hbWU9IklCQ0YiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrPTE1KSksDQogICIyMCBLIiA9IGxpc3QobmFtZT0iSUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGs9MjApKSwNCiAgIjI1IEsiID0gbGlzdChuYW1lPSJJQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaz0yNSkpLA0KICAiMzAgSyIgPSBsaXN0KG5hbWU9IklCQ0YiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrPTMwKSksDQogICIzNSBLIiA9IGxpc3QobmFtZT0iSUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGs9MzUpKSwNCiAgIjQwIEsiID0gbGlzdChuYW1lPSJJQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iQ29zaW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaz00MCkpDQopDQoNCiMgUnVuIHRoZSBhbGdvcml0aG0gYW5kIHByZWRpY2UgdGhlIG5leHQgbiBtb3ZpZXMgZm9yIGNvbXBhcmlzb24gcHVycG9zZXMNCml0ZW1fbm5fcmVzdWx0cyA8LSBldmFsdWF0ZShtb3ZpZSwgaXRlbV9ubiwgbiA9IHJlY3MsIHByb2dyZXNzID0gRkFMU0UpDQpgYGANCg0KKipEcmF3aW5nIHRoZSBST0MgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIFJPQyBjdXJ2ZQ0KcGxvdCh4ID0gaXRlbV9ubl9yZXN1bHRzLCB5ID0gIlJPQyIsIGFubm90YXRlID0gMSwgbGVnZW5kPSJ0b3BsZWZ0IikNCmBgYA0KDQoqKkRhd2luZyB0aGUgUHJlY2lzaW9uIC8gUmVjYWxsIEN1cnZlKioNCmBgYHtSfQ0KIyBEcmF3IHRoZSBwcmVjaXNpb24gLyByZWNhbGwgY3VydmUNCnBsb3QoeCA9IGl0ZW1fbm5fcmVzdWx0cywgeSA9ICJwcmVjL3JlYyIsIGFubm90YXRlID0gMSkNCmBgYA0KDQoqKkNhbGN1bGF0aW5nIHRoZSBSTVNFKiogDQoNCkZyb20gdGhlIGFib3ZlIGdyYXBocyB3ZSBzZWUgdGhhdCB0aGUgYmVzdCBwZXJmb3JtYW5jZSBjb21lcyBmcm9tIGluY2x1ZGluZyAxMCBpdGVtcyBpbiB0aGUgbmVpZ2hib3Job29kIGluIHRoZSBST0MgYW5kIHByZWNpc2lvbiB2cyByZWNhbGwgY3VydmVzIGZvciBhbGwgbnVtYmVycyBvZiByZWNvbW1lbmRhdGlvbnMuIFdlIHNlZSB0aGF0IHVzaW5nIHRoZXNlIHBhcmFtZXRlcnMgZ2l2ZXMgdXMgYSBSTVNFIG9mIDEuMTc1OTc0Lg0KDQpgYGB7Un0NCml0ZW1fbW9kZWwgPC0gUmVjb21tZW5kZXIoZ2V0RGF0YShtb3ZpZSwgInRyYWluIiksIG1ldGhvZCA9ICJJQkNGIiwgDQogICAgICAgICAgICAgICAgICAgICBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLVNjb3JlIiwgbWV0aG9kPSJDb3NpbmUiLCBrPTEwKSkNCg0KaXRlbV9wcmVkaWN0aW9uIDwtIHByZWRpY3QoaXRlbV9tb2RlbCwgZ2V0RGF0YShtb3ZpZSwgImtub3duIiksIHR5cGU9InJhdGluZ3MiKQ0KDQpybXNlX2liY2YgPC0gY2FsY1ByZWRpY3Rpb25BY2N1cmFjeShpdGVtX3ByZWRpY3Rpb24sIGdldERhdGEobW92aWUsICJ1bmtub3duIikpWzFdDQpybXNlX2liY2YNCmBgYA0KDQojIyMjIDMuMi4yIE5vcm1hbGl6YXRpb24gTWV0aG9kcw0KT3VyIG5leHQgcGFyYW1ldGVyIG9mIGludGVyZXN0IGlzIHRoZSBtZXRob2Qgb2Ygbm9ybWFsaXphdGlvbi4gQ3VycmVudGx5ICpyZWNvbW1lbmRlcmxhYiogc3VwcG9ydHMgY2VudGVyIGFuZCB6LXNjb3JlLiBMZXN0IHNlZSB3aGljaCBvZiB0aGUgdHdvIGdpdmVzIHVzIHRoZSBiZXN0IHJlc3VsdHMuDQoNCmBgYHtSfQ0KaXRlbV9ub3JtIDwtIGxpc3QoDQogICJDZW50ZXIiID0gbGlzdChuYW1lPSJJQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiY2VudGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrPTEwKSksDQogICJaLXNjb3JlIiA9IGxpc3QobmFtZT0iSUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGs9MTApKQ0KKQ0KDQppdGVtX25vcm1fcmVzdWx0cyA8LSBldmFsdWF0ZShtb3ZpZSwgaXRlbV9ub3JtLCBuID0gcmVjcywgcHJvZ3Jlc3MgPSBGQUxTRSkNCmBgYA0KKipEcmF3aW5nIHRoZSBST0MgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIFJPQyBjdXJ2ZQ0KcGxvdCh4ID0gaXRlbV9ub3JtX3Jlc3VsdHMsIHkgPSAiUk9DIiwgYW5ub3RhdGUgPSAxLCBsZWdlbmQ9InRvcGxlZnQiKQ0KYGBgDQoNCg0KKipEYXdpbmcgdGhlIFByZWNpc2lvbiAvIFJlY2FsbCBDdXJ2ZSoqDQpgYGB7Un0NCiMgRHJhdyB0aGUgcHJlY2lzaW9uIC8gcmVjYWxsIGN1cnZlDQpwbG90KHggPSBpdGVtX25vcm1fcmVzdWx0cywgeSA9ICJwcmVjL3JlYyIsIGFubm90YXRlID0gMSkNCmBgYA0KDQoqKkNhbGN1bGF0aW5nIHRoZSBSTVNFKiogIA0KV2UgY2FuIHNlZSB0aGF0IHRoZSBhYm92ZSBncmFwaHMgaW5kaWNhdGUgdGhhdCB0aGUgWi1TY29yZSBjZW50ZXJpbmcgZG9lcyBhIHNsaWdodGx5IGJldHRlciBqb2IgYXQgbWFueSBvZiB0aGUgcmVjb21tZW5kYXRpb24gbGV2ZWxzLiBUaGlzIGlzIHRoZSBzYW1lIG5vcm1hbGl6YXRpb24gdGVjaG5pcXVlIHRoYXQgd2UgdXNlZCB0byBjYWxjdWxhdGUgdGhlIFJNU0UgYWJvdmUgc28gd2Ugd2lsbCBjb250aW51ZSB1c2luZyB0aGlzIGFzIG91ciBiZXN0IHVzZXIgYmFzZWQgY29sbGFib3JhdGl2ZSBmaWx0ZXJpbmcgbW9kZWwuDQoNCg0KIyMjIyAzLjIuMyBEaXN0YW5jZSBNZXRob2RzDQpUaGUgZmluYWwgcGFyYW1ldGVyIHRoYXQgd2Ugd2lsbCBiZSB0d2Vha2luZyBpcyB0aGUgbWVhc3VyZW1lbnQgb2YgdGhlIGRpc3RhbmNlIG9yIHNpbWlsYXJpdHkgb2YgYW4gaXRlbSBhbmQgdGhlaXIgbmVhcmVzdCBuZWlnaGJvcnMuIFRvIGRvIHRoaXMgd2Ugd2lsbCBsb29rIGF0IHRocmVlIGRpZmZlcmVudCBtZWFzdXJlbWVudHMgb2YgdGhlIHNpbWlsYXJpdHk7IHNwZWNpZmljYWxseSB0aGUgUGVhcnNvbidzLCBDb3NpbmUsIGFuZCBKYWNjYXJkIGRpc3RhbmNlcy4gVXNpbmcgdGhlIHJlc3VsdHMgZnJvbSB0aGUgcHJldmlvdXMgYW5hbHlzaXMgd2Ugd2lsbCB1c2UgdGhlIDEwIG5lYXJlc3QgaXRlbXMgYW5kIHRoZSBaLXNjb3JlIG5vcm1hbGl6YXRpb24gYXMgd2UgYW5hbHl6ZSB0aGUgZGlzdGFuY2UgcGFyYW1ldGVyLg0KDQpgYGB7Un0NCml0ZW1fZGlzdCA8LSBsaXN0KA0KICAiUGVhcnNvbnMiID0gbGlzdChuYW1lPSJJQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiei1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0icGVhcnNvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGs9MTApKSwNCiAgIkNvc2luZSIgPSBsaXN0KG5hbWU9IklCQ0YiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrPTEwKSksDQogICJKYWNjYXJkIiA9IGxpc3QobmFtZT0iSUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9ImphY2NhcmQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrPTEwKSkNCikNCg0KaXRlbV9kaXN0X3Jlc3VsdHMgPC0gZXZhbHVhdGUobW92aWUsIGl0ZW1fZGlzdCwgbiA9IHJlY3MsIHByb2dyZXNzID0gRkFMU0UpDQpgYGANCg0KKipEcmF3aW5nIHRoZSBST0MgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIFJPQyBjdXJ2ZQ0KcGxvdCh4ID0gaXRlbV9kaXN0X3Jlc3VsdHMsIHkgPSAiUk9DIiwgYW5ub3RhdGUgPSAzLCBsZWdlbmQ9InRvcGxlZnQiKQ0KYGBgDQoNCioqRGF3aW5nIHRoZSBQcmVjaXNpb24gLyBSZWNhbGwgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIHByZWNpc2lvbiAvIHJlY2FsbCBjdXJ2ZQ0KIHBsb3QoeCA9IGl0ZW1fZGlzdF9yZXN1bHRzLCB5ID0gInByZWMvcmVjIiwgYW5ub3RhdGUgPSBjKDEsMykpDQpgYGANCg0KKipDYWxjdWxhdGluZyB0aGUgUk1TRSoqIA0KV2UgY2FuIHNlZSBmcm9tIHRoZSBhYm92ZSBncmFwaHMgdGhhdCB0aGUgSmFjY2FyZCBkaXN0YW5jZSBzZWVtcyB0byBvdXRwZXJmb3JtIHRoZSBvdGhlciBkaXN0YW5jZSBtZXRob2RzIGFsdGhvdWdoIHdlIGRvIG5vdGUgdGhhdCB0aGUgUGVhcnNvbidzIGRpc3RhbmNlIGhhcyBzb21lIHZlcnkgb2RkIHBlcmZvcm1hbmNlLiBXZSB3aWxsIGdvIGFoZWFkIGFuZCBjYWxjdWxhdGUgdGhlIFJNU0UgZm9yIGJvdGggdG8gZGV0ZXJtaW5lIHdoaWNoIGlzIHRoZSBiZXN0LiAgDQoNCmBgYHtSfQ0KbW9kZWwgPC0gUmVjb21tZW5kZXIoZ2V0RGF0YShtb3ZpZSwgInRyYWluIiksIG1ldGhvZCA9ICJJQkNGIiwgDQogICAgICAgICAgICAgICAgICAgICBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLVNjb3JlIiwgbWV0aG9kPSJqYWNjYXJkIiwgaz0xMCkpDQoNCnByZWRpY3Rpb24gPC0gcHJlZGljdChtb2RlbCwgZ2V0RGF0YShtb3ZpZSwgImtub3duIiksIHR5cGU9InJhdGluZ3MiKQ0KDQpybXNlX2l0ZW0gPC0gY2FsY1ByZWRpY3Rpb25BY2N1cmFjeShwcmVkaWN0aW9uLCBnZXREYXRhKG1vdmllLCAidW5rbm93biIpKVsxXQ0Kcm1zZV9pdGVtDQpgYGANCg0KV2Ugc2VlIHRoYXQgdGhlIGJlc3QgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsIGNvbWVzIGZyb20gdGhlIEphY2NhcmQgTWV0aG9kIG9mIGNhbGN1bGF0aW5nIHRoZSBkaXN0YW5jZSBpbiB0aGUgaXRlbSBiYXNlZCBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBtb2RlbC4gT3VyIGJlc3QgdXNlciBiYXNlZCBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBtb2RlbCBlbmRzIHVwIGluY29ycG9yYXRpbmcgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzOiAgDQoNCiAgKiAxMCBOZWFyZXN0IEl0ZW1zDQogICogTm9ybWFsaXplZCB1c2luZyB0aGUgWi1TY29yZQ0KICAqIFNpbWlsYXJpdHkgQ2FsY3VsYXRlZCB1c2luZyBKYWNjYXJkIE1ldGhvZA0KDQoNCiMjIDQgLSBDb25jbHVzdGlvbnMNCkluIHRoaXMgcHJvamVjdCB3ZSBoYXZlIGF0dGVtcHRlZCB0byBidWlsZCB0aGUgYmVzdCBwb3NzaWJsZSB1c2VyLWJhc2VkIGFuZCBpdGVtLWJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIG1vZGVsIGJ5IHR3ZWFraW5nIHRoZSBwYXJhbWV0ZXJzIGF2YWlsYWJsZSB0byBpbiB0aGUgKnJlY29tZW5kZXJsYWIqIHBhY2thZ2UuIE5vdyB0aGF0IHdlIGhhdmUgdGhlc2UgdHdvIG1vZGVscyBsZXRzIGNvbXBhcmUgdGhlbSB0byBlYWNoIG90aGVyLiBGb3JtIG91ciBjb21wYXJpc29uIGJlbG93IG9mIHRoZSB0d28gbW9kZWxzIHdlIHNlZSB0aGF0LCB3aXRoIG91ciBiZXN0IHBhcmFtZXRlciBzZXQsIFRoZSBVQkNGIG1vZGVsIHBlcmZvcm1zIGJldHRlciBvbiBvdXIgdGVzdCBkYXRhIHNldCB0aGVuIHRoZSBJQkNGLiBUaGUgVUJDRiBzaG93IGJldHRlciByZXN1bHRzIGluIGJvdGggdGhlIFJPQyBjdXJ2ZSBhbmQgdGhlIHByZWNpc2lvbiByZWNhbGwgY3VydmVzLiBXaGVuIHdlIGxvb2sgYXQgdGhlIFJNU0UsIGEgbWVhc3VyZSBvZiB0aGUgYWNjdXJhY3kgb2Ygb3VyIHByZWRpY3Rpb25zLCB3ZSBzZWUgdGhhdCB0aGUgVUJDRiBoYXMgYSBSTVNFIG9mIDAuOTQxNDA2NyB3aGlsZSB0aGUgSUJDRiBoYXMgYSBSTVNFIG9mIDEuMDc3ODg4LiBGaW5hbGx5IHdlIGRvIGdldCBzb21lIGludGVyZXN0aW5nIHJlc3VsdHMgd2hlbiB3ZSBsb29rIGF0IHRpbWVzIHRvIGNvbXBpbGUgYW5kIHByZWRpY3QgaW4gdGhlIG1vZGVsLiBUaGUgVUJDRiB0b29rIGFsbW9zdCBubyB0aW1lIHRvIGNvbXBpbGUgYW5kIGFib3V0IC42NCBzZWNvbmRzIHRvIGRvIGl0J3MgcHJlZGljdGlvbnMgd2hpbGUgdGhlIElCQ0YgdG9vayAzNy4xMiBzZWNvbmRzIHRvIGNvbXBpbGUgdGhlIG1vZGVsIGJ1dCBwcmVkaWN0ZWQgdGhlIHJlc3VsdHMgaW4gMC4wNSBzZWNvbmRzLiBJZiB0aGlzIHRyZW5kIGNvbnRpbnVlcyBvdmVyIG11bHRpcGxlIHJ1bnMgb2YgdGhlIHR3byBtb2RlbHMgdGhlbiBpdCBtYXkgY29tZSBkb3duIHRvIGhvdyBvZnRlbiB5b3UgbmVlZCB0byBidWlsZCBhIG5ldyBtb2RlbCB2cyB0aGUgbnVtYmVyIG9mIHRpbWVzIHlvdSBuZWVkIHRvIHByb3ZpZGUgcmVjb21tZW5kYXRpb25zIHdpdGggdGhlc2UgdHdvIG1vZGVscy4gSXQgYXBwZWFycyB0aGF0IHRoZSBVQkNGIGdpdmUgdXMgdGhlIGJlc3QgYWNjdXJhY3kgYXQgdGhlIGNvc3Qgb2Ygc2xvd2VyIHJlY29tbWVuZGF0aW9ucyB3aGlsZSB0aGUgSUJDRiBnaXZlcyB1cyBzbGlnaHRseSB3b3JzZSBidXQgZmFzdGVyIHBlcmZvcm1hbmNlLiANCg0KYGBge1J9DQpmaW5hbF9tb2RlbHMgPC0gbGlzdCgNCiAgIlVCQ0YiID0gbGlzdChuYW1lPSJVQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiei1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0icGVhcnNvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5uPTUwKSksDQogICJJQkNGIiA9IGxpc3QobmFtZT0iSUJDRiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9ImphY2NhcmQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrPTEwKSkNCikNCg0KZmluYWxfcmVzdWx0cyA8LSBldmFsdWF0ZShtb3ZpZSwgZmluYWxfbW9kZWxzLCBuID0gcmVjcywgcHJvZ3Jlc3MgPSBGQUxTRSkNCmBgYA0KDQoqKkRyYXdpbmcgdGhlIFJPQyBDdXJ2ZSoqDQpgYGB7Un0NCiMgRHJhdyB0aGUgUk9DIGN1cnZlDQpwbG90KHggPSBmaW5hbF9yZXN1bHRzLCB5ID0gIlJPQyIsIGFubm90YXRlID0gYygxLDIpLCBsZWdlbmQ9InRvcGxlZnQiKQ0KYGBgDQoNCioqRGF3aW5nIHRoZSBQcmVjaXNpb24gLyBSZWNhbGwgQ3VydmUqKg0KYGBge1J9DQojIERyYXcgdGhlIHByZWNpc2lvbiAvIHJlY2FsbCBjdXJ2ZQ0KIHBsb3QoeCA9IGZpbmFsX3Jlc3VsdHMsIHkgPSAicHJlYy9yZWMiLCBhbm5vdGF0ZSA9IGMoMSwzKSkNCmBgYA0K