1 Project Scope

The goal of this assignment is for you to try out different ways of implementing and configuring a recommender, and to evaluate your different approaches.

2 Introduction

This project builds two recommender systems: User Based Collaborative Filtering (UBCF) and Item Based Collaborative Filtering (ICBF) using data from the MovieLense database.

The project closely follows the tutorial in chapters 3 and 4 of the text, Building A Recommendation System with R by Suresh K. Gorka and Michele Usuelli.

3 Import Data

I began by downing the the MovieLense rating matrix which is within the Recommenderlab R package Link

## 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.

4 Data Exploration

4.1 Similarity of Users and Items

With the imported data, I took the top 100 users and the top 200 movies into an object called “ratings_movies”. This object is comprised of 100 users who have rated at least 200 movies.

## 358 x 116 rating matrix of class 'realRatingMatrix' with 22112 ratings.

In the code block below, I computed the similarity between the first four users and items in the ratings_movies object using the Pearson similarity method.

##           1         5         6         7
## 1 0.0000000 0.5878441 0.5833309 0.5243777
## 5 0.5878441 0.0000000 0.5234117 0.5091642
## 6 0.5833309 0.5234117 0.0000000 0.6123778
## 7 0.5243777 0.5091642 0.6123778 0.0000000

##                       Toy Story (1995) Get Shorty (1995) Twelve Monkeys (1995)
## Toy Story (1995)             0.0000000         0.5234734             0.5653624
## Get Shorty (1995)            0.5234734         0.0000000             0.5377089
## Twelve Monkeys (1995)        0.5653624         0.5377089             0.0000000
## Babe (1995)                  0.5531670         0.6005361             0.5566206
##                       Babe (1995)
## Toy Story (1995)        0.5531670
## Get Shorty (1995)       0.6005361
## Twelve Monkeys (1995)   0.5566206
## Babe (1995)             0.0000000

4.3 Visualization of the top 5 percent of users and movies

In the following sections, we see a Heatmap for the top 5 users and movies, the Average ratings per user, and the Average movie ratings.

5 Splitting the Data

I used two different methods for splitting the data. First, I manually created a test and train sets from ratings_movies. Next, I used K-Fold splitting to create 8 different chunks of data where a chunk is taken out, tested, and validated. The same is done with the other chunks and the average accuracy is taken.

6 Build Models - User Based Collaborative Filtering (UBCF)

6.1 Method One - Using the manual Split

UBCF model was built on the train set, recc_data_train.

## Recommender of type 'UBCF' for 'realRatingMatrix' 
## learned using 291 users.

The model recommends 5 movies to each user.

We can see the slots for the recc_predicted model as well as the predicted user ratings for each film.

## [1] "items"      "ratings"    "itemLabels" "n"
## $`7`
## [1] 4.534113 4.459819 4.394825 4.370785 4.254145
## 
## $`10`
## [1] 4.721341 4.633436 4.629731 4.610652 4.602927
## 
## $`15`
## [1] 3.389964 3.358901 3.347940 3.278984 3.276877
## 
## $`21`
## [1] 4.062008 3.929971 3.891427 3.821683 3.814816
## 
## $`42`
## [1] 4.463283 4.403875 4.367245 4.363597 4.359057

In the table below, we see the names of the five recommmended movies for the top 5 users.

7 10 15 21 42
Titanic (1997) Princess Bride, The (1987) Silence of the Lambs, The (1991) Usual Suspects, The (1995) Braveheart (1995)
Trainspotting (1996) Boot, Das (1981) Empire Strikes Back, The (1980) Shawshank Redemption, The (1994) Titanic (1997)
L.A. Confidential (1997) To Kill a Mockingbird (1962) Casablanca (1942) Schindler’s List (1993) Conspiracy Theory (1997)
Leaving Las Vegas (1995) Schindler’s List (1993) Usual Suspects, The (1995) Clockwork Orange, A (1971) Contact (1997)
Game, The (1997) Titanic (1997) Braveheart (1995) Apocalypse Now (1979) Air Force One (1997)

6.2 Method Two - using the K-FOld sets

For Method two, I built the UBCF model using the K-fold split data. The custom function, build_UBCF_Model, creates the UBCF recommender model.

The data used is from the eval sets. We see that the UBCF_eval_recommender is part of the Recommender package.

## [1] "Recommender"
## attr(,"package")
## [1] "recommenderlab"

We see that the data is normalize.

## [1] "center"

6.3 RMSE

I calculated the RMSE, MSE, and MAE both by user and by the entirety of the model.

##          RMSE       MSE       MAE
## 7   0.9259111 0.8573114 0.7428040
## 11  0.8982434 0.8068411 0.7330236
## 85  0.8311870 0.6908718 0.6980006
## 95  0.9936766 0.9873931 0.7961966
## 125 1.6367242 2.6788661 1.1488154
## 141 1.1074558 1.2264583 0.7645077
##      RMSE       MSE       MAE 
## 0.9906267 0.9813413 0.7753522

7 Precision and Recall

To further evaluate the UBCF model, I used the evaluate function which tests and validates using the 8 k-fold sets and recommendations starting at 10 to to 100 movies.

## UBCF run fold/sample [model time/prediction time]
##   1  [0sec/0.08sec] 
##   2  [0sec/0.03sec] 
##   3  [0sec/0.03sec] 
##   4  [0sec/0.05sec] 
##   5  [0sec/0.03sec] 
##   6  [0sec/0.03sec] 
##   7  [0sec/0.03sec] 
##   8  [0sec/0.03sec]
##        TP    FP    FN    TN precision     recall        TPR       FPR
## 10   4.56  5.44 38.80 57.20 0.4560000 0.09774008 0.09774008 0.0850141
## 20   8.82 11.18 34.54 51.46 0.4410000 0.19333183 0.19333183 0.1750696
## 30  13.50 16.50 29.86 46.14 0.4500000 0.30022102 0.30022102 0.2583568
## 40  17.86 22.14 25.50 40.50 0.4465000 0.39704363 0.39704363 0.3461648
## 50  22.06 27.94 21.30 34.70 0.4412000 0.49225501 0.49225501 0.4365857
## 60  26.42 33.58 16.94 29.06 0.4403333 0.59318206 0.59318206 0.5264497
## 70  30.64 39.36 12.72 23.28 0.4377143 0.68613332 0.68613332 0.6169256
## 80  34.54 45.46  8.82 17.18 0.4317500 0.78187313 0.78187313 0.7167004
## 90  38.40 51.60  4.96 11.04 0.4266667 0.88092366 0.88092366 0.8169180
## 100 41.76 58.24  1.60  4.40 0.4176000 0.96194529 0.96194529 0.9260601
##         TP     FP     FN     TN
## 10   38.54  41.46 322.32 445.68
## 20   76.04  83.96 284.82 403.18
## 30  112.80 127.20 248.06 359.94
## 40  148.78 171.22 212.08 315.92
## 50  185.44 214.56 175.42 272.58
## 60  221.76 258.24 139.10 228.90
## 70  256.96 303.04 103.90 184.10
## 80  289.00 351.00  71.86 136.14
## 90  320.40 399.60  40.46  87.54
## 100 346.60 453.40  14.26  33.74

7.1 The ROC curve

Shows the relationship between the True Positive Rate (TPR) and the False Positive Rate (FPR). The TPR is the number of TP divided by the sum of True Positives(TP) + False Negatives(FN). This rate shows whether the recommdended item was rated by the user. The FPR is the number of FP divided by the sum of False Positives(TP) + True Negatives(FN). FPR measures recommendations that were not rated by the user.

Precision is the percentage of recommended items that have been rated. It’s the number of FP divided by the sum of (TP + FP). Recall is the percentage of user rated movies that have been recommended. It’s the number of TP divided by the sum of (TP + FN).

We can see clearly in the chart below that as the number of recommended movies increases, Precision falls but Recall increases.

8 Build Models - Item Based Collaborative Filtering (IBCF)

For the Item Based Collaborative Filtering, I followed the exact same steps as for the User Based Collaborative Filtering (UBCF).

8.1 Method One - Using the manual Split

## Recommender of type 'IBCF' for 'realRatingMatrix' 
## learned using 291 users.
7 10 15 21 42
In & Out (1997) Godfather: Part II, The (1974) Groundhog Day (1993) Graduate, The (1967) Fish Called Wanda, A (1988)
Men in Black (1997) Truth About Cats & Dogs, The (1996) Rock, The (1996) People vs. Larry Flynt, The (1996) Get Shorty (1995)
Leaving Las Vegas (1995) Star Trek: First Contact (1996) Scream (1996) Jerry Maguire (1996) Murder at 1600 (1997)
Ransom (1996) Birdcage, The (1996) Leaving Las Vegas (1995) Sound of Music, The (1965) Blues Brothers, The (1980)
Evita (1996) Mr. Holland’s Opus (1995) Stand by Me (1986) Indiana Jones and the Last Crusade (1989) English Patient, The (1996)

8.3 RMSE

##         RMSE      MSE       MAE
## 7   1.155787 1.335844 0.9632382
## 11  1.202448 1.445880 0.9497916
## 85  1.069236 1.143266 0.8353018
## 95  1.349284 1.820566 0.9992662
## 125 1.693138 2.866715 1.1802906
## 141 1.184097 1.402085 0.8943180
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

##      RMSE       MSE       MAE 
## 1.2377853 1.5321124 0.9417777

9 Precision and Recall

## IBCF run fold/sample [model time/prediction time]
##   1  [0.03sec/0.02sec] 
##   2  [0.03sec/0.02sec] 
##   3  [0.03sec/0.02sec] 
##   4  [0.03sec/0.02sec] 
##   5  [0.03sec/0.02sec] 
##   6  [0.03sec/0.02sec] 
##   7  [0.05sec/0sec] 
##   8  [0.03sec/0sec]
##       TP    FP    FN    TN precision     recall        TPR        FPR
## 10  4.52  5.48 38.84 57.16 0.4520000 0.09930167 0.09930167 0.08648771
## 20  8.48 11.52 34.88 51.12 0.4240000 0.18888667 0.18888667 0.18435350
## 30 11.92 18.08 31.44 44.56 0.3973333 0.26377460 0.26377460 0.29071072
## 40 15.98 24.02 27.38 38.62 0.3995000 0.35537959 0.35537959 0.38629078
## 50 19.96 30.00 23.40 32.64 0.3994667 0.44639048 0.44639048 0.48208597
## 60 24.04 35.60 19.32 27.04 0.4026667 0.54149430 0.54149430 0.57206924

10 Summary

So which model, the UBCF or the IBCF, performed better? We see that the two models recommended entirely different sets of movies to the first five users.

7 10 15 21 42
Titanic (1997) Princess Bride, The (1987) Silence of the Lambs, The (1991) Usual Suspects, The (1995) Braveheart (1995)
Trainspotting (1996) Boot, Das (1981) Empire Strikes Back, The (1980) Shawshank Redemption, The (1994) Titanic (1997)
L.A. Confidential (1997) To Kill a Mockingbird (1962) Casablanca (1942) Schindler’s List (1993) Conspiracy Theory (1997)
Leaving Las Vegas (1995) Schindler’s List (1993) Usual Suspects, The (1995) Clockwork Orange, A (1971) Contact (1997)
Game, The (1997) Titanic (1997) Braveheart (1995) Apocalypse Now (1979) Air Force One (1997)
7 10 15 21 42
In & Out (1997) Godfather: Part II, The (1974) Groundhog Day (1993) Graduate, The (1967) Fish Called Wanda, A (1988)
Men in Black (1997) Truth About Cats & Dogs, The (1996) Rock, The (1996) People vs. Larry Flynt, The (1996) Get Shorty (1995)
Leaving Las Vegas (1995) Star Trek: First Contact (1996) Scream (1996) Jerry Maguire (1996) Murder at 1600 (1997)
Ransom (1996) Birdcage, The (1996) Leaving Las Vegas (1995) Sound of Music, The (1965) Blues Brothers, The (1980)
Evita (1996) Mr. Holland’s Opus (1995) Stand by Me (1986) Indiana Jones and the Last Crusade (1989) English Patient, The (1996)

10.1 Evaluation of models

To evaluate the two models, I created a list of UBCF and IBCF models with different parameters (pearson and cosine). This list was fed into the evaluate function and below. The visualizations below both show that the UBCF using the “pearson” method had the larger area under the curve.

In conclusion, this project has shown that if we want our model to be more precise with its recommendations, we would choose the UBCF model and recommend five or less movies. If the number of recommendations is 50 or above, then the UBCF-pearson model does not perform better than the other models.

## IBCF run fold/sample [model time/prediction time]
##   1  [0.03sec/0.02sec] 
##   2  [0.13sec/0.01sec] 
##   3  [0.03sec/0sec] 
##   4  [0.01sec/0.02sec] 
##   5  [0.03sec/0sec] 
##   6  [0.01sec/0.02sec] 
##   7  [0.02sec/0.01sec] 
##   8  [0.04sec/0.02sec] 
## IBCF run fold/sample [model time/prediction time]
##   1  [0.03sec/0.02sec] 
##   2  [0.05sec/0.18sec] 
##   3  [0.03sec/0.02sec] 
##   4  [0.04sec/0.02sec] 
##   5  [0.07sec/0.01sec] 
##   6  [0.03sec/0sec] 
##   7  [0.03sec/0sec] 
##   8  [0.03sec/0.02sec] 
## UBCF run fold/sample [model time/prediction time]
##   1  [0sec/0.03sec] 
##   2  [0sec/0.03sec] 
##   3  [0sec/0.04sec] 
##   4  [0sec/0.03sec] 
##   5  [0sec/0.03sec] 
##   6  [0.02sec/0.14sec] 
##   7  [0sec/0.03sec] 
##   8  [0sec/0.03sec] 
## UBCF run fold/sample [model time/prediction time]
##   1  [0sec/0.04sec] 
##   2  [0sec/0.03sec] 
##   3  [0sec/0.03sec] 
##   4  [0sec/0.03sec] 
##   5  [0sec/0.03sec] 
##   6  [0sec/0.03sec] 
##   7  [0sec/0.03sec] 
##   8  [0sec/0.03sec] 
## RANDOM run fold/sample [model time/prediction time]
##   1  [0sec/0.02sec] 
##   2  [0sec/0.01sec] 
##   3  [0sec/0.01sec] 
##   4  [0sec/0sec] 
##   5  [0sec/0sec] 
##   6  [0sec/0sec] 
##   7  [0sec/0.02sec] 
##   8  [0sec/0sec]

## integer(0)

LS0tDQp0aXRsZTogIkRBVEE2MTIgUHJvamVjdCAyIg0KYXV0aG9yOiAiSm9obiBLLiBIYW5jb2NrIg0KZGF0ZTogIjYvMTEvMjAyMCINCm91dHB1dDoNCiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogcGFwZXINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IG5vDQotLS0NCg0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoY2FUb29scykNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkocm1kZm9ybWF0cykNCmBgYA0KDQojIFByb2plY3QgU2NvcGUNClRoZSBnb2FsIG9mIHRoaXMgYXNzaWdubWVudCBpcyBmb3IgeW91IHRvIHRyeSBvdXQgZGlmZmVyZW50IHdheXMgb2YgaW1wbGVtZW50aW5nIGFuZCBjb25maWd1cmluZyBhIHJlY29tbWVuZGVyLCBhbmQgdG8gZXZhbHVhdGUgeW91ciBkaWZmZXJlbnQgYXBwcm9hY2hlcy4NCg0KIyBJbnRyb2R1Y3Rpb24gDQpUaGlzIHByb2plY3QgYnVpbGRzIHR3byByZWNvbW1lbmRlciBzeXN0ZW1zOiBVc2VyIEJhc2VkIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nIChVQkNGKSBhbmQgSXRlbSBCYXNlZCBDb2xsYWJvcmF0aXZlIEZpbHRlcmluZyAoSUNCRikgdXNpbmcgZGF0YSBmcm9tIHRoZSBNb3ZpZUxlbnNlIGRhdGFiYXNlLiANCg0KVGhlIHByb2plY3QgY2xvc2VseSBmb2xsb3dzIHRoZSB0dXRvcmlhbCBpbiBjaGFwdGVycyAzIGFuZCA0IG9mIHRoZSB0ZXh0LCBCdWlsZGluZyBBIFJlY29tbWVuZGF0aW9uIFN5c3RlbSB3aXRoIFIgYnkgU3VyZXNoIEsuIEdvcmthIGFuZCBNaWNoZWxlIFVzdWVsbGkuIA0KDQoNCiMgSW1wb3J0IERhdGENCg0KSSBiZWdhbiBieSBkb3duaW5nIHRoZSB0aGUgTW92aWVMZW5zZSByYXRpbmcgbWF0cml4IHdoaWNoIGlzIHdpdGhpbiB0aGUgUmVjb21tZW5kZXJsYWIgUiBwYWNrYWdlIFtMaW5rXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmVjb21tZW5kZXJsYWIvdmlnbmV0dGVzL3JlY29tbWVuZGVybGFiLnBkZikNCg0KYGBge3J9DQpkYXRhKCJNb3ZpZUxlbnNlIikNCk1vdmllTGVuc2UNCmBgYA0KDQojIERhdGEgRXhwbG9yYXRpb24NCg0KIyMgU2ltaWxhcml0eSBvZiBVc2VycyBhbmQgSXRlbXMNCg0KV2l0aCB0aGUgaW1wb3J0ZWQgZGF0YSwgSSB0b29rIHRoZSB0b3AgMTAwIHVzZXJzIGFuZCB0aGUgdG9wIDIwMCBtb3ZpZXMgaW50byBhbiBvYmplY3QgY2FsbGVkICJyYXRpbmdzX21vdmllcyIuIFRoaXMgb2JqZWN0IGlzIGNvbXByaXNlZCBvZiAxMDAgdXNlcnMgd2hvIGhhdmUgcmF0ZWQgYXQgbGVhc3QgMjAwIG1vdmllcy4NCg0KDQpgYGB7cn0NCg0KcmF0aW5nc19tb3ZpZXMgPC0gTW92aWVMZW5zZVtyb3dDb3VudHMoTW92aWVMZW5zZSkgPiAxMDAsIGNvbENvdW50cyhNb3ZpZUxlbnNlKSA+IDIwMF0NCnJhdGluZ3NfbW92aWVzIA0KYGBgDQoNCkluIHRoZSBjb2RlIGJsb2NrIGJlbG93LCBJIGNvbXB1dGVkIHRoZSBzaW1pbGFyaXR5IGJldHdlZW4gdGhlIGZpcnN0IGZvdXIgdXNlcnMgYW5kIGl0ZW1zIGluIHRoZSByYXRpbmdzX21vdmllcyBvYmplY3QgdXNpbmcgdGhlIFBlYXJzb24gc2ltaWxhcml0eSBtZXRob2QuDQoNCmBgYHtyfQ0Kc2ltaWxhcml0eV91c2VycyA8LSBzaW1pbGFyaXR5KHJhdGluZ3NfbW92aWVzWzE6NCwgXSwgbWV0aG9kID0icGVhcnNvbiIsIHdoaWNoID0gInVzZXJzIikNCmFzLm1hdHJpeChzaW1pbGFyaXR5X3VzZXJzKQ0KYGBgDQoNCmBgYHtyfQ0KaW1hZ2UoYXMubWF0cml4KHNpbWlsYXJpdHlfdXNlcnMpLCBtYWluID0gIlVzZXIgc2ltaWxhcml0eSIpDQpgYGANCg0KDQpgYGB7cn0NCnNpbWlsYXJpdHlfaXRlbXMgPC0gc2ltaWxhcml0eShyYXRpbmdzX21vdmllc1ssIDE6NF0sIG1ldGhvZCA9InBlYXJzb24iLCB3aGljaCA9ICJpdGVtcyIpDQphcy5tYXRyaXgoc2ltaWxhcml0eV9pdGVtcykNCmBgYA0KDQpgYGB7cn0NCmltYWdlKGFzLm1hdHJpeChzaW1pbGFyaXR5X2l0ZW1zKSwgbWFpbiA9ICJJdGVtIHNpbWlsYXJpdHkiKQ0KYGBgDQoNCiMjIEV4cGxvcmF0aW9uIG9mIFJhdGluZ3MNCg0KVGhlIHZpc3VhbGl6YXRpb24gYmVsb3cgc2hvd3MgdGhlIGZyZXF1ZW5jeSBkaXN0cmlidXRpb24gb2YgcmF0aW5ncyBhZnRlciByZW1vdmluZyB0aG9zZSBtb3ZpZXMgd2l0aG91dCByYXRpbmdzLiBXZSBzZWUgdGhhdCB0aGUgcmF0aW5nIG9mICI0IiBpcyB0aGUgbW9zdCBvZnRlbiB1c2VkIHJhdGluZyBvbiBhIHNjYWxlIG9mIDEgdG8gNS4gDQoNCmBgYHtyfQ0KdmVjdG9yX3JhdGluZ3MgPC0gYXMudmVjdG9yKHJhdGluZ3NfbW92aWVzQGRhdGEpDQp2ZWN0b3JfcmF0aW5ncyA8LSBmYWN0b3IodmVjdG9yX3JhdGluZ3NbdmVjdG9yX3JhdGluZ3MgIT0wXSkNCnZlY3Rvcl9yYXRpbmdzX2RmIDwtIGFzLmRhdGEuZnJhbWUodmVjdG9yX3JhdGluZ3MpDQoNCnFwbG90KHZlY3Rvcl9yYXRpbmdzKSArIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiB0aGUgcmF0aW5ncyIpDQoNCg0KDQpgYGANCg0KYGBge3J9DQp2aWV3c19wZXJfbW92aWUgPC0gY29sQ291bnRzKHJhdGluZ3NfbW92aWVzKQ0Kdmlld3NfcGVyX21vdmllX2RmIDwtIGFzLmRhdGEuZnJhbWUodmlld3NfcGVyX21vdmllKQ0Kdmlld3NfcGVyX21vdmllX2RmIDwtIGNiaW5kKG1vdmllcyA9IHJvd25hbWVzKHZpZXdzX3Blcl9tb3ZpZV9kZiksIHZpZXdzX3Blcl9tb3ZpZV9kZikNCnJvd25hbWVzKHZpZXdzX3Blcl9tb3ZpZV9kZikgPC0gMTpucm93KHZpZXdzX3Blcl9tb3ZpZV9kZikNCg0Kdmlld3NfcGVyX21vdmllX2RmICA8LSB2aWV3c19wZXJfbW92aWVfZGZbb3JkZXIodmlld3NfcGVyX21vdmllX2RmJHZpZXdzX3Blcl9tb3ZpZSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdDQoNCmhlYWQodmlld3NfcGVyX21vdmllX2RmLDUpDQoNCmBgYA0KDQpgYGB7cn0NCmdncGxvdCh2aWV3c19wZXJfbW92aWVfZGZbMTo1LCBdLCBhZXMoeD1tb3ZpZXMsIHk9dmlld3NfcGVyX21vdmllKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsY29sb3VyPSJncmVlbiIsIGZpbGw9InllbGxvdyIpICsgdGhlbWUoYXhpcy50ZXh0LnggPWVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KYGBgDQoNCg0KDQojIyBWaXN1YWxpemF0aW9uIG9mIHRoZSB0b3AgNSBwZXJjZW50IG9mIHVzZXJzIGFuZCBtb3ZpZXMNCg0KSW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucywgd2Ugc2VlIGEgSGVhdG1hcCBmb3IgdGhlIHRvcCA1IHVzZXJzIGFuZCBtb3ZpZXMsIHRoZSBBdmVyYWdlIHJhdGluZ3MgcGVyIHVzZXIsIGFuZCB0aGUgQXZlcmFnZSBtb3ZpZSByYXRpbmdzLiANCg0KYGBge3J9DQptaW5fbW92aWVzIDwtIHF1YW50aWxlKHJvd0NvdW50cyhyYXRpbmdzX21vdmllcyksIDAuOTUpDQptaW5fdXNlcnMgPC0gcXVhbnRpbGUoY29sQ291bnRzKHJhdGluZ3NfbW92aWVzKSwgMC45NSkNCmBgYA0KDQoNCg0KYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD03fQ0KaW1hZ2UocmF0aW5nc19tb3ZpZXNbcm93Q291bnRzKHJhdGluZ3NfbW92aWVzKSA+IG1pbl9tb3ZpZXMsDQpjb2xDb3VudHMocmF0aW5nc19tb3ZpZXMpID4gbWluX3VzZXJzXSwgbWFpbiA9ICJIZWF0bWFwIG9mIHRoZSB0b3AgdXNlcnMgYW5kIG1vdmllcyIpDQpgYGANCg0KIyMgQXZlcmFnZSBSYXRpbmdzIHBlciBVc2VyDQoNCg0KYGBge3J9DQphdmVyYWdlX3JhdGluZ3NfcGVyX3VzZXIgPC0gcm93TWVhbnMocmF0aW5nc19tb3ZpZXMpDQoNCnAxIDwtIGdncGxvdCgpICsgYWVzKGF2ZXJhZ2VfcmF0aW5nc19wZXJfdXNlcikrIGdlb21faGlzdG9ncmFtKGJpbndpZHRoPS4wNSwgY29sb3VyPSJibHVlIiwgZmlsbD0ib3JhbmdlIikNCnAxDQpgYGANCiMjIEF2ZXJhZ2UgTW92aWUgUmF0aW5ncw0KDQpgYGB7cn0NCmF2ZXJhZ2VfbW92aWVfcmF0aW5nczwtIGNvbE1lYW5zKHJhdGluZ3NfbW92aWVzKQ0KDQpwMiA8LSBnZ3Bsb3QoKSArIGFlcyhhdmVyYWdlX21vdmllX3JhdGluZ3MpKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD0uMDUsIGNvbG91cj0ieWVsbG93IiwgZmlsbD0iYmx1ZSIpDQpwMg0KYGBgDQoNCg0KIyBTcGxpdHRpbmcgdGhlIERhdGENCg0KSSB1c2VkIHR3byBkaWZmZXJlbnQgbWV0aG9kcyBmb3Igc3BsaXR0aW5nIHRoZSBkYXRhLiBGaXJzdCwgSSBtYW51YWxseSBjcmVhdGVkIGEgdGVzdCBhbmQgdHJhaW4gc2V0cyBmcm9tIHJhdGluZ3NfbW92aWVzLiBOZXh0LCBJIHVzZWQgSy1Gb2xkIHNwbGl0dGluZyB0byBjcmVhdGUgOCBkaWZmZXJlbnQgY2h1bmtzIG9mIGRhdGEgd2hlcmUgYSBjaHVuayBpcyB0YWtlbiBvdXQsIHRlc3RlZCwgYW5kIHZhbGlkYXRlZC4gVGhlIHNhbWUgaXMgZG9uZSB3aXRoIHRoZSBvdGhlciBjaHVua3MgYW5kIHRoZSBhdmVyYWdlIGFjY3VyYWN5IGlzIHRha2VuLg0KDQoNCiMjIE1hbnVhbGx5IFNwbGl0IHRoZSBEYXRhDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0Kd2hpY2hfdHJhaW4gPC0gc2FtcGxlKHggPSBjKFRSVUUsIEZBTFNFKSwgc2l6ZSA9IG5yb3cocmF0aW5nc19tb3ZpZXMpLCByZXBsYWNlID0gVFJVRSwgcHJvYiA9IGMoMC44LCAwLjIpKQ0KcmVjY19kYXRhX3RyYWluIDwtIHJhdGluZ3NfbW92aWVzW3doaWNoX3RyYWluLCBdDQpyZWNjX2RhdGFfdGVzdCA8LSByYXRpbmdzX21vdmllc1shd2hpY2hfdHJhaW4sIF0NCmBgYA0KDQoNCg0KIyMgSy1Gb2xkIFNwbGl0DQoNCg0KYGBge3J9DQpuX2ZvbGQgPC0gOA0KaXRlbXNfdG9fa2VlcCA8LSAxMA0KcmF0aW5nX3RocmVzaG9sZCA8LSAzDQpgYGANCg0KDQpgYGB7cn0NCmtfZm9sZF9zcGxpdCA8LSBmdW5jdGlvbihkYXRhLCBtZXRob2QsIG5fZm9sZCwgaXRlbXNfdG9fa2VlcCwgcmF0aW5nX3RocmVzaG9sZCl7DQogIA0KICAgICAgICByZXR1cm4oZXZhbHVhdGlvblNjaGVtZShkYXRhID0gZGF0YSwgbWV0aG9kID0gbWV0aG9kLCBrID0gbl9mb2xkLCBnaXZlbiA9IGl0ZW1zX3RvX2tlZXAsIGdvb2RSYXRpbmcgPSByYXRpbmdfdGhyZXNob2xkKSkNCiAgDQogIH0NCmBgYA0KDQpgYGB7cn0NCmV2YWxfc2V0cyA8LSBrX2ZvbGRfc3BsaXQocmF0aW5nc19tb3ZpZXMsICJjcm9zcy12YWxpZGF0aW9uIiwgbl9mb2xkLCBpdGVtc190b19rZWVwLCByYXRpbmdfdGhyZXNob2xkKSANCiAgICAgICAgICAgICAgICAgICAgICAgICANCmBgYA0KDQoNCmBgYHtyfQ0Kc2l6ZV9zZXRzIDwtIHNhcHBseShldmFsX3NldHNAcnVuc1RyYWluLCBsZW5ndGgpDQpzaXplX3NldHMNCmBgYA0KDQoNCg0KIyBCdWlsZCBNb2RlbHMgLSBVc2VyIEJhc2VkIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nIChVQkNGKQ0KDQojIyBNZXRob2QgT25lIC0gVXNpbmcgdGhlIG1hbnVhbCBTcGxpdA0KDQpVQkNGIG1vZGVsIHdhcyBidWlsdCBvbiB0aGUgdHJhaW4gc2V0LCByZWNjX2RhdGFfdHJhaW4uIA0KDQpgYGB7cn0NClVCQ0ZfcmVjY19tb2RlbCA8LSBSZWNvbW1lbmRlcihkYXRhID0gcmVjY19kYXRhX3RyYWluLCBtZXRob2QgPSAiVUJDRiIpDQpVQkNGX3JlY2NfbW9kZWwNCmBgYA0KDQpUaGUgbW9kZWwgcmVjb21tZW5kcyA1IG1vdmllcyB0byBlYWNoIHVzZXIuIA0KDQpgYGB7cn0NCm5fcmVjb21tZW5kZWQgPC0gNQ0KcmVjY19wcmVkaWN0ZWQgPC0gcHJlZGljdChvYmplY3QgPSBVQkNGX3JlY2NfbW9kZWwsIG5ld2RhdGEgPSByZWNjX2RhdGFfdGVzdCwgbiA9IG5fcmVjb21tZW5kZWQpDQpVQkNGX3JlY2NfbWF0cml4IDwtIHNhcHBseShyZWNjX3ByZWRpY3RlZEBpdGVtcywgZnVuY3Rpb24oeCl7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbmFtZXMocmF0aW5nc19tb3ZpZXMpW3hdIH0pDQoNCg0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIHNsb3RzIGZvciB0aGUgcmVjY19wcmVkaWN0ZWQgbW9kZWwgYXMgd2VsbCBhcyB0aGUgcHJlZGljdGVkIHVzZXIgcmF0aW5ncyBmb3IgZWFjaCBmaWxtLiANCg0KYGBge3J9DQpzbG90TmFtZXMocmVjY19wcmVkaWN0ZWQpDQpgYGANCmBgYHtyfQ0KcmVjY19wcmVkaWN0ZWRAcmF0aW5nc1sxOjVdDQpgYGANCg0KSW4gdGhlIHRhYmxlIGJlbG93LCB3ZSBzZWUgdGhlIG5hbWVzIG9mIHRoZSBmaXZlIHJlY29tbW1lbmRlZCBtb3ZpZXMgZm9yIHRoZSB0b3AgNSB1c2Vycy4NCg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmtuaXRyOjprYWJsZShVQkNGX3JlY2NfbWF0cml4WywgMTo1XSwibWFya2Rvd24iLCBhbGlnbiA9ICdjJywgY2FwdGlvbiA9ICJVQkNGIFJlY29tbWVuZGVkIE1vdmllcyIpDQpgYGANCg0KDQpgYGB7cn0NCm51bWJlcl9vZl9pdGVtcyA8LSBmYWN0b3IodGFibGUoVUJDRl9yZWNjX21hdHJpeCkpDQpjaGFydF90aXRsZSA8LSAiRGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgaXRlbXMgZm9yIFVCQ0YiDQpxcGxvdChudW1iZXJfb2ZfaXRlbXMpICsgZ2d0aXRsZShjaGFydF90aXRsZSkNCmBgYA0KDQoNCiMjIE1ldGhvZCBUd28gLSB1c2luZyB0aGUgSy1GT2xkIHNldHMNCg0KRm9yIE1ldGhvZCB0d28sIEkgYnVpbHQgdGhlIFVCQ0YgbW9kZWwgdXNpbmcgdGhlIEstZm9sZCBzcGxpdCBkYXRhLiBUaGUgY3VzdG9tIGZ1bmN0aW9uLCBidWlsZF9VQkNGX01vZGVsLCBjcmVhdGVzIHRoZSBVQkNGIHJlY29tbWVuZGVyIG1vZGVsLiANCg0KDQpgYGB7cn0NCmJ1aWxkX1VCQ0ZfTW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgbW9kZWxfdG9fZXZhbHVhdGUsIG1vZGVsX3BhcmFtZXRlcnMgKXsNCiAgICANCiAgICAgICAgICAgICAgICByZXR1cm4oIFJlY29tbWVuZGVyKGRhdGEgPSBkYXRhLCBtZXRob2QgPSBtb2RlbF90b19ldmFsdWF0ZSwgcGFyYW1ldGVyID0gbW9kZWxfcGFyYW1ldGVycykpDQogIH0NCmBgYA0KDQoNClRoZSBkYXRhIHVzZWQgaXMgZnJvbSB0aGUgZXZhbCBzZXRzLiAgV2Ugc2VlIHRoYXQgdGhlIFVCQ0ZfZXZhbF9yZWNvbW1lbmRlciBpcyBwYXJ0IG9mIHRoZSBSZWNvbW1lbmRlciBwYWNrYWdlLiAgDQoNCg0KDQpgYGB7cn0NCmRhdGEgPC0gZ2V0RGF0YShldmFsX3NldHMsICJ0cmFpbiIpDQptb2RlbF90b19ldmFsdWF0ZSA8LSAiVUJDRiINCm1vZGVsX3BhcmFtZXRlcnMgPC0gTlVMTA0KDQpVQkNGX2V2YWxfcmVjb21tZW5kZXIgPC0gYnVpbGRfVUJDRl9Nb2RlbChkYXRhLG1vZGVsX3RvX2V2YWx1YXRlLG1vZGVsX3BhcmFtZXRlcnMpIA0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoVUJDRl9ldmFsX3JlY29tbWVuZGVyKQ0KYGBgDQoNCmBgYHtyfQ0KVUJDRl9tb2RlbF9kZXRhaWxzIDwtIGdldE1vZGVsKFVCQ0ZfZXZhbF9yZWNvbW1lbmRlcikNCg0KYGBgDQoNCldlIHNlZSB0aGF0IHRoZSBkYXRhIGlzIG5vcm1hbGl6ZS4gDQoNCmBgYHtyfQ0KVUJDRl9tb2RlbF9kZXRhaWxzJG5vcm1hbGl6ZQ0KYGBgDQoNCg0KDQpgYGB7cn0NCml0ZW1zX3RvX3JlY29tbWVuZCA8LSA1DQpgYGANCg0KDQpgYGB7cn0NClVCQ0ZfZXZhbF9wcmVkaWN0aW9uIDwtIHByZWRpY3Qob2JqZWN0ID0gVUJDRl9ldmFsX3JlY29tbWVuZGVyLCBuZXdkYXRhID0gZ2V0RGF0YShldmFsX3NldHMsICJrbm93biIpLCBuID0gaXRlbXNfdG9fcmVjb21tZW5kLCB0eXBlID0gInJhdGluZ3MiKQ0KDQpgYGANCg0KDQojIyBSTVNFDQoNCkkgY2FsY3VsYXRlZCB0aGUgUk1TRSwgTVNFLCBhbmQgTUFFIGJvdGggYnkgdXNlciBhbmQgYnkgdGhlIGVudGlyZXR5IG9mIHRoZSBtb2RlbC4gDQoNCmBgYHtyfQ0KVUJDRl9ldmFsX2FjY3VyYWN5X3VzZXJzIDwtIGNhbGNQcmVkaWN0aW9uQWNjdXJhY3koeCA9IFVCQ0ZfZXZhbF9wcmVkaWN0aW9uLCBkYXRhID0gZ2V0RGF0YShldmFsX3NldHMsICJ1bmtub3duIiksIGJ5VXNlciA9VFJVRSkNClVCQ0ZfZXZhbF9hY2N1cmFjeSA8LSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KHggPSBVQkNGX2V2YWxfcHJlZGljdGlvbiwgZGF0YSA9IGdldERhdGEoZXZhbF9zZXRzLCAidW5rbm93biIpLCBieVVzZXIgPUZBTFNFKQ0KYGBgDQoNCg0KYGBge3J9DQpoZWFkKFVCQ0ZfZXZhbF9hY2N1cmFjeV91c2VycykNCmBgYA0KDQpgYGB7cn0NClVCQ0ZfZXZhbF9hY2N1cmFjeQ0KYGBgDQoNCiMgUHJlY2lzaW9uIGFuZCBSZWNhbGwNCg0KVG8gZnVydGhlciBldmFsdWF0ZSB0aGUgVUJDRiBtb2RlbCwgSSB1c2VkIHRoZSBldmFsdWF0ZSBmdW5jdGlvbiB3aGljaCB0ZXN0cyBhbmQgdmFsaWRhdGVzIHVzaW5nIHRoZSA4IGstZm9sZCBzZXRzIGFuZCByZWNvbW1lbmRhdGlvbnMgc3RhcnRpbmcgYXQgMTAgdG8gdG8gMTAwIG1vdmllcy4gDQoNCmBgYHtyfQ0KcmVzdWx0cyA8LSBldmFsdWF0ZSh4ID0gZXZhbF9zZXRzLCBtZXRob2QgPSBtb2RlbF90b19ldmFsdWF0ZSwgbiA9c2VxKDEwLCAxMDAsIDEwKSkNCg0KYGBgDQoNCmBgYHtyfQ0KZ2V0Q29uZnVzaW9uTWF0cml4KHJlc3VsdHMpW1sxXV0NCmBgYA0KDQpgYGB7cn0NCmNvbHVtbnNfdG9fc3VtIDwtIGMoIlRQIiwgIkZQIiwgIkZOIiwgIlROIikNCmluZGljZXNfc3VtbWVkIDwtIFJlZHVjZSgiKyIsIGdldENvbmZ1c2lvbk1hdHJpeChyZXN1bHRzKSlbLCBjb2x1bW5zX3RvX3N1bV0NCmluZGljZXNfc3VtbWVkDQpgYGANCg0KIyMgVGhlIFJPQyBjdXJ2ZQ0KDQpTaG93cyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIFRydWUgUG9zaXRpdmUgUmF0ZSAoVFBSKSBhbmQgdGhlIEZhbHNlIFBvc2l0aXZlIFJhdGUgKEZQUikuIFRoZSBUUFIgaXMgdGhlIG51bWJlciBvZiBUUCBkaXZpZGVkIGJ5IHRoZSBzdW0gb2YgVHJ1ZSBQb3NpdGl2ZXMoVFApICsgRmFsc2UgTmVnYXRpdmVzKEZOKS4gIFRoaXMgcmF0ZSBzaG93cyB3aGV0aGVyIHRoZSByZWNvbW1kZW5kZWQgaXRlbSB3YXMgcmF0ZWQgYnkgdGhlIHVzZXIuIFRoZSBGUFIgaXMgdGhlIG51bWJlciBvZiBGUCBkaXZpZGVkIGJ5IHRoZSBzdW0gb2YgRmFsc2UgUG9zaXRpdmVzKFRQKSArIFRydWUgTmVnYXRpdmVzKEZOKS4gRlBSIG1lYXN1cmVzIHJlY29tbWVuZGF0aW9ucyB0aGF0IHdlcmUgbm90IHJhdGVkIGJ5IHRoZSB1c2VyLiANCg0KDQoNCmBgYHtyfQ0KcGxvdChyZXN1bHRzLCBhbm5vdGF0ZSA9IFRSVUUsIG1haW4gPSAiUk9DIGN1cnZlIikNCmBgYA0KDQpQcmVjaXNpb24gaXMgIHRoZSBwZXJjZW50YWdlIG9mIHJlY29tbWVuZGVkIGl0ZW1zIHRoYXQgaGF2ZSBiZWVuIHJhdGVkLiBJdCdzIHRoZSBudW1iZXIgb2YgRlAgZGl2aWRlZCBieSB0aGUgc3VtIG9mIChUUCArIEZQKS4gUmVjYWxsIGlzIHRoZSBwZXJjZW50YWdlIG9mIHVzZXIgcmF0ZWQgbW92aWVzIHRoYXQgaGF2ZSBiZWVuDQpyZWNvbW1lbmRlZC4gSXQncyB0aGUgbnVtYmVyIG9mIFRQIGRpdmlkZWQgYnkgdGhlIHN1bSBvZiAoVFAgKyBGTikuDQoNCldlIGNhbiBzZWUgY2xlYXJseSBpbiB0aGUgY2hhcnQgYmVsb3cgdGhhdCBhcyB0aGUgbnVtYmVyIG9mIHJlY29tbWVuZGVkIG1vdmllcyBpbmNyZWFzZXMsIFByZWNpc2lvbiBmYWxscyBidXQgUmVjYWxsIGluY3JlYXNlcy4gDQoNCg0KDQpgYGB7cn0NCnBsb3QocmVzdWx0cywgInByZWMvcmVjIiwgYW5ub3RhdGUgPSBUUlVFLCBtYWluID0gIlByZWNpc2lvbi1yZWNhbGwiKQ0KYGBgDQoNCg0KIyBCdWlsZCBNb2RlbHMgLSBJdGVtIEJhc2VkIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nIChJQkNGKQ0KDQpGb3IgdGhlIEl0ZW0gQmFzZWQgQ29sbGFib3JhdGl2ZSBGaWx0ZXJpbmcsIEkgZm9sbG93ZWQgdGhlIGV4YWN0IHNhbWUgc3RlcHMgYXMgZm9yIHRoZSBVc2VyIEJhc2VkIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nIChVQkNGKS4gDQoNCiMjIE1ldGhvZCBPbmUgLSBVc2luZyB0aGUgbWFudWFsIFNwbGl0DQoNCmBgYHtyfQ0KSUJDRl9yZWNjX21vZGVsIDwtIFJlY29tbWVuZGVyKGRhdGEgPSByZWNjX2RhdGFfdHJhaW4sIG1ldGhvZCA9ICJJQkNGIikNCklCQ0ZfcmVjY19tb2RlbA0KYGBgDQoNCmBgYHtyfQ0Kbl9yZWNvbW1lbmRlZCA8LSA1DQpyZWNjX3ByZWRpY3RlZCA8LSBwcmVkaWN0KG9iamVjdCA9IElCQ0ZfcmVjY19tb2RlbCwgbmV3ZGF0YSA9IHJlY2NfZGF0YV90ZXN0LCBuID0gbl9yZWNvbW1lbmRlZCkNCklCQ0ZfcmVjY19tYXRyaXggPC0gc2FwcGx5KHJlY2NfcHJlZGljdGVkQGl0ZW1zLCBmdW5jdGlvbih4KXsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xuYW1lcyhyYXRpbmdzX21vdmllcylbeF0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KQ0KYGBgDQoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQprbml0cjo6a2FibGUoSUJDRl9yZWNjX21hdHJpeFssIDE6NV0sIm1hcmtkb3duIiwgYWxpZ24gPSAnYycsIGNhcHRpb24gPSAiVUJDRiBSZWNvbW1lbmRlZCBNb3ZpZXMiKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCm51bWJlcl9vZl9pdGVtcyA8LSBmYWN0b3IodGFibGUoVUJDRl9yZWNjX21hdHJpeCkpDQpjaGFydF90aXRsZSA8LSAiRGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgaXRlbXMgZm9yIElCQ0YiDQpxcGxvdChudW1iZXJfb2ZfaXRlbXMpICsgZ2d0aXRsZShjaGFydF90aXRsZSkNCmBgYA0KDQoNCg0KDQojIyBNZXRob2QgVHdvIC0gdXNpbmcgdGhlIEstRk9sZCBzZXRzDQoNCg0KYGBge3J9DQpidWlsZF9JQkNGX01vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIG1vZGVsX3RvX2V2YWx1YXRlLCBtb2RlbF9wYXJhbWV0ZXJzICl7DQogICAgDQogICAgICAgICAgICAgICAgcmV0dXJuKCBSZWNvbW1lbmRlcihkYXRhID0gZGF0YSwgbWV0aG9kID0gbW9kZWxfdG9fZXZhbHVhdGUsIHBhcmFtZXRlciA9IG1vZGVsX3BhcmFtZXRlcnMpKQ0KICB9DQpgYGANCg0KDQpgYGB7cn0NCmRhdGEgPC0gZ2V0RGF0YShldmFsX3NldHMsICJ0cmFpbiIpDQptb2RlbF90b19ldmFsdWF0ZSA8LSAiSUJDRiINCm1vZGVsX3BhcmFtZXRlcnMgPC0gTlVMTA0KSUJDRl9ldmFsX3JlY29tbWVuZGVyIDwtIGJ1aWxkX0lCQ0ZfTW9kZWwoZGF0YSxtb2RlbF90b19ldmFsdWF0ZSxtb2RlbF9wYXJhbWV0ZXJzKSANCmBgYA0KDQpgYGB7cn0NCklCQ0ZfZXZhbF9yZWNvbW1lbmRlcg0KYGBgDQoNCg0KYGBge3J9DQppdGVtc190b19yZWNvbW1lbmQgPC0gNQ0KYGBgDQoNCg0KYGBge3J9DQpJQkNGX2V2YWxfcHJlZGljdGlvbiA8LSBwcmVkaWN0KG9iamVjdCA9IElCQ0ZfZXZhbF9yZWNvbW1lbmRlciwgbmV3ZGF0YSA9IGdldERhdGEoZXZhbF9zZXRzLCAia25vd24iKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gaXRlbXNfdG9fcmVjb21tZW5kLCB0eXBlID0gInJhdGluZ3MiKQ0KY2xhc3MoSUJDRl9ldmFsX3ByZWRpY3Rpb24pDQpgYGANCg0KDQpgYGB7cn0NCm51bWJlcl9vZl9pdGVtcyA8LSBmYWN0b3IodGFibGUoSUJDRl9yZWNjX21hdHJpeCkpDQpjaGFydF90aXRsZSA8LSAiRGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgaXRlbXMgZm9yIElCQ0YiDQpgYGANCg0KYGBge3J9DQpxcGxvdChudW1iZXJfb2ZfaXRlbXMpICsgZ2d0aXRsZShjaGFydF90aXRsZSkNCmBgYA0KDQojIyBSTVNFDQoNCmBgYHtyfQ0KSUJDRl9ldmFsX2FjY3VyYWN5X3VzZXJzIDwtIGNhbGNQcmVkaWN0aW9uQWNjdXJhY3koeCA9IElCQ0ZfZXZhbF9wcmVkaWN0aW9uLCBkYXRhID0gZ2V0RGF0YShldmFsX3NldHMsICJ1bmtub3duIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5VXNlciA9VFJVRSkNCg0KSUJDRl9ldmFsX2FjY3VyYWN5IDwtIGNhbGNQcmVkaWN0aW9uQWNjdXJhY3koeCA9IElCQ0ZfZXZhbF9wcmVkaWN0aW9uLCBkYXRhID0gZ2V0RGF0YShldmFsX3NldHMsICJ1bmtub3duIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5VXNlciA9RkFMU0UpDQoNCmBgYA0KDQoNCg0KYGBge3J9DQpoZWFkKElCQ0ZfZXZhbF9hY2N1cmFjeV91c2VycykNCmBgYA0KDQpgYGB7cn0NCnFwbG90KElCQ0ZfZXZhbF9hY2N1cmFjeV91c2Vyc1ssICJSTVNFIl0pICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEyNSkgKw0KZ2d0aXRsZSgiRGlzdHJpYnV0aW9uIG9mIHRoZSBSTVNFIGJ5IHVzZXIiKQ0KYGBgDQoNCg0KYGBge3J9DQpJQkNGX2V2YWxfYWNjdXJhY3kNCmBgYA0KDQoNCiMgUHJlY2lzaW9uIGFuZCBSZWNhbGwNCg0KDQpgYGB7cn0NCnJlc3VsdHMgPC0gZXZhbHVhdGUoeCA9IGV2YWxfc2V0cywgbWV0aG9kID0gbW9kZWxfdG9fZXZhbHVhdGUsIG4gPXNlcSgxMCwgMTAwLCAxMCkpDQoNCmBgYA0KDQpgYGB7cn0NCmhlYWQoZ2V0Q29uZnVzaW9uTWF0cml4KHJlc3VsdHMpW1sxXV0pDQpgYGANCg0KYGBge3J9DQpwbG90KHJlc3VsdHMsIGFubm90YXRlID0gVFJVRSwgbWFpbiA9ICJST0MgY3VydmUiKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChyZXN1bHRzLCAicHJlYy9yZWMiLCBhbm5vdGF0ZSA9IFRSVUUsIG1haW4gPSAiUHJlY2lzaW9uLXJlY2FsbCIpDQpgYGANCg0KIyBTdW1tYXJ5DQoNClNvIHdoaWNoIG1vZGVsLCB0aGUgVUJDRiBvciB0aGUgSUJDRiwgcGVyZm9ybWVkIGJldHRlcj8gV2Ugc2VlIHRoYXQgdGhlIHR3byBtb2RlbHMgcmVjb21tZW5kZWQgZW50aXJlbHkgZGlmZmVyZW50IHNldHMgb2YgbW92aWVzIHRvIHRoZSBmaXJzdCBmaXZlIHVzZXJzLiANCg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmtuaXRyOjprYWJsZShVQkNGX3JlY2NfbWF0cml4WywgMTo1XSwibWFya2Rvd24iLCBhbGlnbiA9ICdjJywgY2FwdGlvbiA9ICJVQkNGIFJlY29tbWVuZGVkIE1vdmllcyIpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0V9DQprbml0cjo6a2FibGUoSUJDRl9yZWNjX21hdHJpeFssIDE6NV0sIm1hcmtkb3duIiwgYWxpZ24gPSAnYycsIGNhcHRpb24gPSAiVUJDRiBSZWNvbW1lbmRlZCBNb3ZpZXMiKQ0KYGBgDQoNCiMjIEV2YWx1YXRpb24gb2YgbW9kZWxzDQoNClRvIGV2YWx1YXRlIHRoZSB0d28gbW9kZWxzLCBJIGNyZWF0ZWQgYSBsaXN0IG9mIFVCQ0YgYW5kIElCQ0YgbW9kZWxzIHdpdGggZGlmZmVyZW50IHBhcmFtZXRlcnMgKHBlYXJzb24gYW5kIGNvc2luZSkuIFRoaXMgbGlzdCB3YXMgZmVkIGludG8gdGhlIGV2YWx1YXRlIGZ1bmN0aW9uIGFuZCBiZWxvdy4gVGhlIHZpc3VhbGl6YXRpb25zIGJlbG93IGJvdGggc2hvdyB0aGF0IHRoZSBVQkNGIHVzaW5nIHRoZSAicGVhcnNvbiIgbWV0aG9kIGhhZCB0aGUgbGFyZ2VyIGFyZWEgdW5kZXIgdGhlIGN1cnZlLiANCg0KSW4gY29uY2x1c2lvbiwgdGhpcyBwcm9qZWN0IGhhcyBzaG93biB0aGF0IGlmIHdlIHdhbnQgb3VyIG1vZGVsIHRvIGJlIG1vcmUgcHJlY2lzZSB3aXRoIGl0cyByZWNvbW1lbmRhdGlvbnMsIHdlIHdvdWxkIGNob29zZSB0aGUgVUJDRiBtb2RlbCBhbmQgcmVjb21tZW5kIGZpdmUgb3IgbGVzcyBtb3ZpZXMuIElmIHRoZSBudW1iZXIgb2YgcmVjb21tZW5kYXRpb25zIGlzIDUwIG9yIGFib3ZlLCB0aGVuIHRoZSBVQkNGLXBlYXJzb24gbW9kZWwgZG9lcyBub3QgcGVyZm9ybSBiZXR0ZXIgdGhhbiB0aGUgb3RoZXIgbW9kZWxzLiANCg0KDQpgYGB7cn0NCm1vZGVsc190b19ldmFsdWF0ZSA8LSBsaXN0KElCQ0ZfY29zID0gbGlzdChuYW1lID0gIklCQ0YiLCBwYXJhbSA9IGxpc3QobWV0aG9kID0iY29zaW5lIikpLA0KSUJDRl9jb3IgPSBsaXN0KG5hbWUgPSAiSUJDRiIsIHBhcmFtID0gbGlzdChtZXRob2QgPSJwZWFyc29uIikpLA0KVUJDRl9jb3MgPSBsaXN0KG5hbWUgPSAiVUJDRiIsIHBhcmFtID0gbGlzdChtZXRob2QgPSJjb3NpbmUiKSksDQpVQkNGX2NvciA9IGxpc3QobmFtZSA9ICJVQkNGIiwgcGFyYW0gPSBsaXN0KG1ldGhvZCA9InBlYXJzb24iKSksDQpyYW5kb20gPSBsaXN0KG5hbWUgPSAiUkFORE9NIiwgcGFyYW09TlVMTCkNCikNCg0Kbl9yZWNvbW1lbmRhdGlvbnMgPC0gYygxLCA1LCBzZXEoMTAsIDEwMCwgMTApKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmNvbXBhcmVfcmVzdWx0cyA8LSBldmFsdWF0ZSh4ID0gZXZhbF9zZXRzLCBtZXRob2QgPSBtb2RlbHNfdG9fZXZhbHVhdGUsIG49IG5fcmVjb21tZW5kYXRpb25zKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChjb21wYXJlX3Jlc3VsdHMsIGFubm90YXRlID0gMSwgbGVnZW5kID0gInRvcGxlZnQiKSArIHRpdGxlKCJST0MgY3VydmUiKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChjb21wYXJlX3Jlc3VsdHMsICJwcmVjL3JlYyIsIGFubm90YXRlID0gMSwgbGVnZW5kID0gImJvdHRvbXJpZ2h0IikNCnRpdGxlKCJQcmVjaXNpb24tcmVjYWxsIikNCmBgYA0KDQoNCg0KDQoNCg0K