MovieLense Recommendation System in R

Item Based Collaborative Filtering (IBCF) recommends items on the basis of the similarity matrix. this algorithm is efficient and scalable. In this project we will use the demo MovieLens dataset.

Identify which items are similar in terms of having been purchased by the same people Recommend to a new user the items that are similar to its purchases

Loading required package: Matrix
Loading required package: arules

Attaching package: <U+393C><U+3E31>arules<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:dplyr<U+393C><U+3E32>:

    recode

The following object is masked from <U+393C><U+3E31>package:car<U+393C><U+3E32>:

    recode

The following objects are masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

    abbreviate, write

Loading required package: proxy

Attaching package: <U+393C><U+3E31>proxy<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:Matrix<U+393C><U+3E32>:

    as.matrix

The following objects are masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    as.dist, dist

The following object is masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

    as.matrix

Loading required package: registry
943 x 1664 rating matrix of class ‘realRatingMatrix’ with 99392 ratings.

Each row of MovieLense corresponds to a user, and each column corresponds to a movie. There are more than 943 x 1664 = 1,500,000 combinations between a user and a movie. Therefore, storing the complete matrix would require more than 1,500,000 cells. However, not every user has watched every movie. Therefore, there are fewer than 100,000 ratings, and the matrix is sparse.

Let’s explore in detail.

[1] "realRatingMatrix"
attr(,"package")
[1] "recommenderlab"

Let’s take a look at the methods that we can apply on the objects of this class:

 [1] [                      [<-                    binarize              
 [4] calcPredictionAccuracy coerce                 colCounts             
 [7] colMeans               colSds                 colSums               
[10] denormalize            dim                    dimnames              
[13] dimnames<-             dissimilarity          evaluationScheme      
[16] getData.frame          getList                getNormalize          
[19] getRatingMatrix        getRatings             getTopNLists          
[22] image                  normalize              nratings              
[25] Recommender            removeKnownRatings     rowCounts             
[28] rowMeans               rowSds                 rowSums               
[31] sample                 show                   similarity            
see '?methods' for accessing help and source code
1388448 bytes
12740464 bytes
9.17604692433566 bytes

MovieLense occupies much less space than the equivalent standard R matrix. The rate is about 1:9, and the reason is the sparsity of MovieLense. A standard R matrix object stores all the missing values as 0s, so it stores 15 times more cells.

Computing the similarity matrix

Determine how similar the first five users are with each other. Let’s compute this using the cosine distance

[1] "dist"

dist is a base R class, we can use it in different ways.

Let’s convert similarity_users into a matrix to visualize it.

           1          2          3          4
1 0.00000000 0.16893670 0.03827203 0.06634975
2 0.16893670 0.00000000 0.09706862 0.15310468
3 0.03827203 0.09706862 0.00000000 0.33343036
4 0.06634975 0.15310468 0.33343036 0.00000000

The more red the cell is, the more similar two users are. Note that the diagonal is red, since it’s comparing each user with itself:

                  Toy Story (1995) GoldenEye (1995) Four Rooms (1995) Get Shorty (1995)
Toy Story (1995)         0.0000000        0.4023822         0.3302448         0.4549379
GoldenEye (1995)         0.4023822        0.0000000         0.2730692         0.5025708
Four Rooms (1995)        0.3302448        0.2730692         0.0000000         0.3248664
Get Shorty (1995)        0.4549379        0.5025708         0.3248664         0.0000000

Similar to the preceding screenshot, we can visualize the matrix using this image:

The similarity is the base of collaborative filtering models.

[1] "ALS_realRatingMatrix"          "ALS_implicit_realRatingMatrix"
[3] "IBCF_realRatingMatrix"         "POPULAR_realRatingMatrix"     
[5] "RANDOM_realRatingMatrix"       "RERECOMMEND_realRatingMatrix" 
[7] "SVD_realRatingMatrix"          "SVDF_realRatingMatrix"        
[9] "UBCF_realRatingMatrix"        

Descriptions

$ALS_realRatingMatrix
[1] "Recommender for explicit ratings based on latent factors, calculated by alternating least squares algorithm."

$ALS_implicit_realRatingMatrix
[1] "Recommender for implicit data based on latent factors, calculated by alternating least squares algorithm."

$IBCF_realRatingMatrix
[1] "Recommender based on item-based collaborative filtering."

$POPULAR_realRatingMatrix
[1] "Recommender based on item popularity."

$RANDOM_realRatingMatrix
[1] "Produce random recommendations (real ratings)."

$RERECOMMEND_realRatingMatrix
[1] "Re-recommends highly rated items (real ratings)."

$SVD_realRatingMatrix
[1] "Recommender based on SVD approximation with column-mean imputation."

$SVDF_realRatingMatrix
[1] "Recommender based on Funk SVD with gradient descend."

$UBCF_realRatingMatrix
[1] "Recommender based on user-based collaborative filtering."
$k
[1] 30

$method
[1] "Cosine"

$normalize
[1] "center"

$normalize_sim_matrix
[1] FALSE

$alpha
[1] 0.5

$na_as_zero
[1] FALSE

Data exploration

[1] "realRatingMatrix"
attr(,"package")
[1] "recommenderlab"

MovieLense is a realRatingMatrix object containing a dataset about movie ratings. Each row corresponds to a user, each column to a movie, and each value to a rating.

[1]  943 1664

There are 943 users and 1664 movies. realRatingMatrix is an S4 class

[1] "data"      "normalize"
[1] "dgCMatrix"
attr(,"package")
[1] "Matrix"
[1]  943 1664

MovieLense(@)data belongs to the dgCMatrix class that inherits from Matrix. In order to perform custom data exploration, we might need to access this slot.

Exploring the values of the rating

[1] 5 4 0 3 1 2

The ratings are integers in the range 0-5. Let’s count the occurrences of each of them.

vector_ratings
      0       1       2       3       4       5 
1469760    6059   11307   27002   33947   21077 

According to the documentation, a rating equal to 0 represents a missing value, so we can remove them from vector_ratings. We can also build a frequency plot of the ratings. In order to visualize a bar plot with frequencies, we can use ggplot2. Let’s convert them into categories using factor and build a quick chart:

Let’s go ahead and visualize. The following image shows the distribution of the ratings. Most of the ratings are above 2, and the most common is 4.

Exploring which movies have been viewed

colCounts: This is the number of non-missing values for each column colMeans: This is the average value for each column

*Which are the most viewed movies (TOP 10)?

Sort the movies by number of views:

Let’s visualize the first six rows and build a histogram:

In the preceding chart, you can notice that Star Wars (1977) is the most viewed movie, exceeding the others by about 100 views.

*Which are the least viewed movies (BOTTOM 10)?

Explore Average Ratings

Let’s visualize by creating a chart. The following image shows the distribution of the average movie rating:

The highest value is around 3, and there are a few movies whose rating is either 1 or 5. Probably, the reason is that these movies received a rating from a few people only, so we shouldn’t take them into account.

Let’s remove the movies whose number of views is below a defined threshold, for instance, below 100:

The following image shows the distribution of the relevant average ratings:

All the rankings are between 2.3 and 4.5. As expected, we removed the extremes. The highest value changes, and now, it is around 4.

Let’s build the heatmap using image. The following image displays the heatmap of the rating matrix:

The above heatmap is a bit difficult to read. Let’s build the heat map using image(). The following image shows the heatmap of the first rows and columns:

Top percentile of users and movies, let’s use quantile function

   99% 
440.96 
   99% 
371.07 

Another heat map:

Questions:

Users who have rated at least 50 movies Movies that have been watched at least 100 times

560 x 332 rating matrix of class ‘realRatingMatrix’ with 55298 ratings.

The ratings_movies object contains about half of the users and a fifth of the movies in comparison with MovieLense.

Let’s visualize the top 2 percent of users and movies in the new matrix:

Let’s build the heatmap:

As we already noticed, some rows are darker than the others. This might mean that some users give higher ratings to all the movies. However, we have visualized the top movies only. In order to have an overview of all the users, let’s take a look at the distribution of the average rating by user:

Let’s visualize the distribution. As suspected, the average rating varies a lot across different users.

Normalize the data.

Let’s take a look at the average rating by users:

[1] 0

Let’s visualize the normalized matrix

The first difference that we can notice is the colors, and this is because the data is continuous. Previously, the rating was an integer between 1 and 5. After the normalization, the rating can be any number between -5 and 5.

There are still some lines that are more blue and some that are more red. The reason is that we are visualizing only the top movies. We already checked that the average rating is 0 for each user.

Binarizing the data

Let’s select this 5 percent using quantile. The row and column counts are the same as the original matrix, so we can still apply rowCounts and colCounts on ratings_movies:

Let’s build the heat map:

Only a few cells contain unwatched movies. This is just because we selected the top users and movies.

Let’s use the same approach to compute and visualize the other binary matrix The cells having a rating above the threshold will have their value equal to 1 and the other cells will be 0s:

Let’s build the heat map:

As expected, we have more white cells now. Depending on the model, we can leave the ratings matrix as it is or normalize/binarize it.

In this section, we prepared the data to perform recommendations. In the upcoming sections, we will build collaborative filtering models.

Training and Test Sets

First, we randomly define the which_train vector that is TRUE for users in the training set and FALSE for the others. We will set the probability in the training set as 80 percent:

[1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE

Let’s define the training and the test sets:

Sample Code:

Recommendation model

Data: This is the training set Method: This is the name of the technique *Parameters: These are some optional parameters of the technique

IBCF, which stands for item-based collaborative filtering. Below outputs are the parameters.

$k
[1] 30

$method
[1] "Cosine"

$normalize
[1] "center"

$normalize_sim_matrix
[1] FALSE

$alpha
[1] 0.5

$na_as_zero
[1] FALSE

So let’s build it.

Recommender of type ‘IBCF’ for ‘realRatingMatrix’ 
learned using 111 users.
[1] "Recommender"
attr(,"package")
[1] "recommenderlab"

We’ll extract some of the details (description and parameters).

[1] "IBCF: Reduced similarity matrix"

The model_details$sim component contains the similarity matrix. Let’s check its structure:

[1] "dgCMatrix"
attr(,"package")
[1] "Matrix"
[1] 332 332

model_details$sim is a square matrix whose size is equal to the number of items. Let’s build heat map.

Most of the values are equal to 0. The reason is that each row contains only k elements.

[1] 30
row_sums
 30 
332 

So each row has 30 elements greater than 0. However, the matrix is not supposed to be symmetric. In fact, the number of non-null elements for each column depends on how many times the corresponding movie was included in the top k of another movie. Let’s check the distribution of the number of elements by column:

Let’s build the distribution chart:

As expected, there are a few movies that are similar to many others. Let’s see which are the movies with the most elements:

[1] "Usual Suspects, The (1995)"             "Sling Blade (1996)"                    
[3] "Star Wars (1977)"                       "Fargo (1996)"                          
[5] "Monty Python and the Holy Grail (1974)" "Casablanca (1942)"                     
Recommendations as ‘topNList’ with n = 6 for 449 users. 

The recc_predicted object contains the recommendations

[1] "topNList"
attr(,"package")
[1] "recommenderlab"
[1] "items"      "ratings"    "itemLabels" "n"         

For instance, these are the recommendations for the first user:

[1] 285 326 327 279 185 201

We would need to extract the recommended movies from recc_predicted(@)item labels:

[1] "Speed (1994)"            "Peacemaker, The (1997)"  "Scream 2 (1997)"        
[4] "Shine (1996)"            "Evita (1996)"            "Schindler's List (1993)"

Let’s define a function of a matrix with the recommendations for each user:

[1]   6 449

Let’s visualize the recommendations for the first four users:

     1                         2                                       
[1,] "Speed (1994)"            "While You Were Sleeping (1995)"        
[2,] "Peacemaker, The (1997)"  "Much Ado About Nothing (1993)"         
[3,] "Scream 2 (1997)"         "Cold Comfort Farm (1995)"              
[4,] "Shine (1996)"            "Citizen Kane (1941)"                   
[5,] "Evita (1996)"            "Ghost and the Darkness, The (1996)"    
[6,] "Schindler's List (1993)" "Monty Python and the Holy Grail (1974)"
     3                               5                                
[1,] "Aladdin (1992)"                "American President, The (1995)" 
[2,] "In & Out (1997)"               "Full Monty, The (1997)"         
[3,] "Mission: Impossible (1996)"    "Pulp Fiction (1994)"            
[4,] "Room with a View, A (1986)"    "Welcome to the Dollhouse (1995)"
[5,] "Magnificent Seven, The (1954)" "Peacemaker, The (1997)"         
[6,] "Aliens (1986)"                 "Air Force One (1997)"           

Now, we can identify the most recommended movies. For this purpose, we will define a vector with all the recommendations, and we will build a frequency plot:

The distribution chart that shows the distribution of the number of items for IBCF:

Let’s see which are the most popular recommended movies:

As you can see from the preceding table, the movie “Mr. Smith Goes to Washington” has been recommended the most times.

IBCF recommends items on the basis of the similarity matrix. this algorithm is efficient and scalable,

Building the recommendation model

$method
[1] "cosine"

$nn
[1] 25

$sample
[1] FALSE

$normalize
[1] "center"

Model with default parameters:

Recommender of type ‘UBCF’ for ‘realRatingMatrix’ 
learned using 111 users.

Components of the model:

[1] "description" "data"        "method"      "nn"          "sample"      "normalize"  
[7] "verbose"    

The below object contains the rating matrix. UBCF is a lazy-learning technique, which means that it needs to access all the data to perform a prediction.

111 x 332 rating matrix of class ‘realRatingMatrix’ with 10507 ratings.
Normalized using center on rows.

Apply model model to test set

Let’s find the top six recommendations for each new user

Recommendations as ‘topNList’ with n = 6 for 449 users. 

Let’s define a funtionc of a matrix with the recommendations to the test set users:

Let’s take a look at the first four users:

     1                         2                                       
[1,] "Speed (1994)"            "While You Were Sleeping (1995)"        
[2,] "Peacemaker, The (1997)"  "Much Ado About Nothing (1993)"         
[3,] "Scream 2 (1997)"         "Cold Comfort Farm (1995)"              
[4,] "Shine (1996)"            "Citizen Kane (1941)"                   
[5,] "Evita (1996)"            "Ghost and the Darkness, The (1996)"    
[6,] "Schindler's List (1993)" "Monty Python and the Holy Grail (1974)"
     3                               5                                
[1,] "Aladdin (1992)"                "American President, The (1995)" 
[2,] "In & Out (1997)"               "Full Monty, The (1997)"         
[3,] "Mission: Impossible (1996)"    "Pulp Fiction (1994)"            
[4,] "Room with a View, A (1986)"    "Welcome to the Dollhouse (1995)"
[5,] "Magnificent Seven, The (1954)" "Peacemaker, The (1997)"         
[6,] "Aliens (1986)"                 "Air Force One (1997)"           

Frequency Chart

We will compute how many times each movie got recommended and build the related frequency histogram

number_of_items
 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 30 
 9 22 28 34 29 20 32 27 21 16 12 13 14  5  5  4  8  3  3  5  4  1  1  1  3  1  1  2 

Compared with the IBCF, the distribution has a longer tail. This means that there are some movies that are recommended much more often than the others. The maximum is 34, compared with 11 for IBCF.

Let’s take a look at the top titles:

The Godfather is the top movie title.

Collaborative filtering on binary data

Data preparation

Let’s build ratings_movies_watched using the binarize method as follows:

1 if the user purchased (or liked) the item, and 0 otherwise. This case is different from the previous cases, so it should be treated separately. Similar to the other cases, the techniques are item-based and user-based.

In our case, starting from ratings_movies, we can build a ratings_movies_watched matrix whose values will be 1 if the user viewed the movie, and 0 otherwise. We built it in one of the Binarizing the data sections.

Binarizing method as as before with IBCF

So, we can answer that on the average, each user watched about 100 movies, and only a few watched more than 200 movies.

Let’s define our training and test sets:

Item-based collaborative filtering on binary data

Same as before in exception to input parameter method equal to Jaccard

Same as before, let’s recommend six items to each of the users in the test set:

Let’s further examine the recommendations for the first four users.

Note: The approach is similar to IBCF using a rating matrix. Since we are not taking account of the ratings, the result will be less accurate.

EOF

LS0tDQp0aXRsZTogIklCQ0YgLyBVQkNGIC0gTW92aWUgUmVjb21tZW5kYXRpb24gU3lzdGVtIC0gUiBOb3RlYm9vayINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQogDQojTW92aWVMZW5zZSBSZWNvbW1lbmRhdGlvbiBTeXN0ZW0gaW4gUg0KDQpJdGVtIEJhc2VkIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nIChJQkNGKSByZWNvbW1lbmRzIGl0ZW1zIG9uIHRoZSBiYXNpcyBvZiB0aGUgc2ltaWxhcml0eSBtYXRyaXguIHRoaXMgYWxnb3JpdGhtIGlzIGVmZmljaWVudCBhbmQgc2NhbGFibGUuIEluIHRoaXMgcHJvamVjdCB3ZSB3aWxsIHVzZSB0aGUgZGVtbyBNb3ZpZUxlbnMgZGF0YXNldC4NCg0KKklkZW50aWZ5IHdoaWNoIGl0ZW1zIGFyZSBzaW1pbGFyIGluIHRlcm1zIG9mIGhhdmluZyBiZWVuIHB1cmNoYXNlZCBieSB0aGUgc2FtZSBwZW9wbGUNCipSZWNvbW1lbmQgdG8gYSBuZXcgdXNlciB0aGUgaXRlbXMgdGhhdCBhcmUgc2ltaWxhciB0byBpdHMgcHVyY2hhc2VzDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpzZXQuc2VlZCgxKQ0KZGF0YShNb3ZpZUxlbnNlKQ0KTW92aWVMZW5zZQ0KYGBgDQoNCkVhY2ggcm93IG9mIE1vdmllTGVuc2UgY29ycmVzcG9uZHMgdG8gYSB1c2VyLCBhbmQgZWFjaCBjb2x1bW4gY29ycmVzcG9uZHMgdG8gYQ0KbW92aWUuIFRoZXJlIGFyZSBtb3JlIHRoYW4gOTQzIHggMTY2NCA9IDEsNTAwLDAwMCBjb21iaW5hdGlvbnMgYmV0d2VlbiBhIHVzZXIgYW5kDQphIG1vdmllLiBUaGVyZWZvcmUsIHN0b3JpbmcgdGhlIGNvbXBsZXRlIG1hdHJpeCB3b3VsZCByZXF1aXJlIG1vcmUgdGhhbiAxLDUwMCwwMDANCmNlbGxzLiBIb3dldmVyLCBub3QgZXZlcnkgdXNlciBoYXMgd2F0Y2hlZCBldmVyeSBtb3ZpZS4gVGhlcmVmb3JlLCB0aGVyZSBhcmUgZmV3ZXINCnRoYW4gMTAwLDAwMCByYXRpbmdzLCBhbmQgdGhlIG1hdHJpeCBpcyBzcGFyc2UuDQoNCkxldCdzIGV4cGxvcmUgaW4gZGV0YWlsLg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpjbGFzcyhNb3ZpZUxlbnNlKQ0KYGBgDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBtZXRob2RzIHRoYXQgd2UgY2FuIGFwcGx5IG9uIHRoZSBvYmplY3RzIG9mIHRoaXMgY2xhc3M6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm1ldGhvZHMoY2xhc3M9Y2xhc3MoTW92aWVMZW5zZSkpDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kb2JqZWN0LnNpemUoTW92aWVMZW5zZSkNCmBgYA0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpvYmplY3Quc2l6ZShhcyhNb3ZpZUxlbnNlLCAibWF0cml4IikpDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kb2JqZWN0LnNpemUoYXMoTW92aWVMZW5zZSwgIm1hdHJpeCIpKSAvIG9iamVjdC5zaXplKE1vdmllTGVuc2UpDQpgYGANCg0KTW92aWVMZW5zZSBvY2N1cGllcyBtdWNoIGxlc3Mgc3BhY2UgdGhhbiB0aGUgZXF1aXZhbGVudCBzdGFuZGFyZCBSIG1hdHJpeC4gVGhlIHJhdGUgaXMgYWJvdXQgMTo5LCBhbmQgdGhlIHJlYXNvbiBpcyB0aGUgc3BhcnNpdHkgb2YgTW92aWVMZW5zZS4gQSBzdGFuZGFyZCBSIG1hdHJpeCBvYmplY3Qgc3RvcmVzIGFsbCB0aGUgbWlzc2luZyB2YWx1ZXMgYXMgMHMsIHNvIGl0IHN0b3JlcyAxNSB0aW1lcyBtb3JlIGNlbGxzLg0KDQojI0NvbXB1dGluZyB0aGUgc2ltaWxhcml0eSBtYXRyaXgNCg0KRGV0ZXJtaW5lIGhvdyBzaW1pbGFyIHRoZSBmaXJzdCBmaXZlIHVzZXJzIGFyZSB3aXRoIGVhY2ggb3RoZXIuIExldCdzIGNvbXB1dGUgdGhpcyB1c2luZyB0aGUgY29zaW5lIGRpc3RhbmNlDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnNpbWlsYXJpdHlfdXNlcnMgPC0gc2ltaWxhcml0eShNb3ZpZUxlbnNlWzE6NCwgXSwgbWV0aG9kID0gImNvc2luZSIsIHdoaWNoID0gInVzZXJzIikNCmNsYXNzKHNpbWlsYXJpdHlfdXNlcnMpDQpgYGANCg0KZGlzdCBpcyBhIGJhc2UgUiBjbGFzcywgd2UgY2FuIHVzZSBpdCBpbiBkaWZmZXJlbnQgd2F5cy4NCg0KDQpMZXQncyBjb252ZXJ0IHNpbWlsYXJpdHlfdXNlcnMgaW50byBhIG1hdHJpeCB0byB2aXN1YWxpemUgaXQuDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmFzLm1hdHJpeChzaW1pbGFyaXR5X3VzZXJzKQ0KYGBgDQoNClRoZSBtb3JlIHJlZCB0aGUgY2VsbCBpcywgdGhlIG1vcmUgc2ltaWxhciB0d28gdXNlcnMgYXJlLiBOb3RlIHRoYXQgdGhlIGRpYWdvbmFsIGlzIHJlZCwNCnNpbmNlIGl0J3MgY29tcGFyaW5nIGVhY2ggdXNlciB3aXRoIGl0c2VsZjoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KaW1hZ2UoYXMubWF0cml4KHNpbWlsYXJpdHlfdXNlcnMpLCBtYWluID0gIlVzZXIgc2ltaWxhcml0eSIpDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kc2ltaWxhcml0eV9pdGVtcyA8LSBzaW1pbGFyaXR5KE1vdmllTGVuc2VbLCAxOjRdLCBtZXRob2QgPSAiY29zaW5lIiwgd2hpY2ggPSAiaXRlbXMiKQ0KYXMubWF0cml4KHNpbWlsYXJpdHlfaXRlbXMpDQpgYGANCg0KU2ltaWxhciB0byB0aGUgcHJlY2VkaW5nIHNjcmVlbnNob3QsIHdlIGNhbiB2aXN1YWxpemUgdGhlIG1hdHJpeCB1c2luZyB0aGlzIGltYWdlOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQppbWFnZShhcy5tYXRyaXgoc2ltaWxhcml0eV9pdGVtcyksIG1haW4gPSAiSXRlbSBzaW1pbGFyaXR5IikNCmBgYA0KDQpUaGUgc2ltaWxhcml0eSBpcyB0aGUgYmFzZSBvZiBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBtb2RlbHMuDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnJlY29tbWVuZGVyX21vZGVscyA8LSByZWNvbW1lbmRlclJlZ2lzdHJ5JGdldF9lbnRyaWVzKGRhdGFUeXBlID0gInJlYWxSYXRpbmdNYXRyaXgiKQ0KbmFtZXMocmVjb21tZW5kZXJfbW9kZWxzKQ0KYGBgDQoNCiMjRGVzY3JpcHRpb25zDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmxhcHBseShyZWNvbW1lbmRlcl9tb2RlbHMsICJbWyIsICJkZXNjcmlwdGlvbiIpDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjb21tZW5kZXJfbW9kZWxzJElCQ0ZfcmVhbFJhdGluZ01hdHJpeCRwYXJhbWV0ZXJzDQpgYGANCg0KIyNEYXRhIGV4cGxvcmF0aW9uDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmxpYnJhcnkoInJlY29tbWVuZGVybGFiIikNCmxpYnJhcnkoImdncGxvdDIiKQ0KZGF0YShNb3ZpZUxlbnNlKQ0KY2xhc3MoTW92aWVMZW5zZSkNCmBgYA0KDQpNb3ZpZUxlbnNlIGlzIGEgcmVhbFJhdGluZ01hdHJpeCBvYmplY3QgY29udGFpbmluZyBhIGRhdGFzZXQgYWJvdXQgbW92aWUgcmF0aW5ncy4gRWFjaCByb3cgY29ycmVzcG9uZHMgdG8gYSB1c2VyLCBlYWNoIGNvbHVtbiB0byBhIG1vdmllLCBhbmQgZWFjaCB2YWx1ZSB0byBhIHJhdGluZy4NCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KZGltKE1vdmllTGVuc2UpDQpgYGANCg0KVGhlcmUgYXJlIDk0MyB1c2VycyBhbmQgMTY2NCBtb3ZpZXMuIHJlYWxSYXRpbmdNYXRyaXggaXMgYW4gUzQgY2xhc3MNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kc2xvdE5hbWVzKE1vdmllTGVuc2UpDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KY2xhc3MoTW92aWVMZW5zZUBkYXRhKQ0KYGBgDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmRpbShNb3ZpZUxlbnNlQGRhdGEpDQpgYGANCg0KTW92aWVMZW5zZShAKWRhdGEgYmVsb25ncyB0byB0aGUgZGdDTWF0cml4IGNsYXNzIHRoYXQgaW5oZXJpdHMgZnJvbSBNYXRyaXguIEluIG9yZGVyDQp0byBwZXJmb3JtIGN1c3RvbSBkYXRhIGV4cGxvcmF0aW9uLCB3ZSBtaWdodCBuZWVkIHRvIGFjY2VzcyB0aGlzIHNsb3QuDQoNCiMjRXhwbG9yaW5nIHRoZSB2YWx1ZXMgb2YgdGhlIHJhdGluZw0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQp2ZWN0b3JfcmF0aW5ncyA8LSBhcy52ZWN0b3IoTW92aWVMZW5zZUBkYXRhKQ0KdW5pcXVlKHZlY3Rvcl9yYXRpbmdzKQ0KYGBgDQoNClRoZSByYXRpbmdzIGFyZSBpbnRlZ2VycyBpbiB0aGUgcmFuZ2UgMC01LiBMZXQncyBjb3VudCB0aGUgb2NjdXJyZW5jZXMgb2YgZWFjaCBvZiB0aGVtLg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQp0YWJsZV9yYXRpbmdzIDwtIHRhYmxlKHZlY3Rvcl9yYXRpbmdzKQ0KdGFibGVfcmF0aW5ncw0KYGBgDQoNCkFjY29yZGluZyB0byB0aGUgZG9jdW1lbnRhdGlvbiwgYSByYXRpbmcgZXF1YWwgdG8gMCByZXByZXNlbnRzIGEgbWlzc2luZyB2YWx1ZSwgc28gd2UgY2FuIHJlbW92ZSB0aGVtIGZyb20gdmVjdG9yX3JhdGluZ3MuIFdlIGNhbiBhbHNvIGJ1aWxkIGEgZnJlcXVlbmN5IHBsb3Qgb2YgdGhlIHJhdGluZ3MuIEluIG9yZGVyIHRvIHZpc3VhbGl6ZSBhIGJhciBwbG90IHdpdGggZnJlcXVlbmNpZXMsIHdlIGNhbiB1c2UgZ2dwbG90Mi4gTGV0J3MgY29udmVydCB0aGVtIGludG8gY2F0ZWdvcmllcyB1c2luZyBmYWN0b3IgYW5kIGJ1aWxkIGEgcXVpY2sgY2hhcnQ6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnZlY3Rvcl9yYXRpbmdzIDwtIHZlY3Rvcl9yYXRpbmdzW3ZlY3Rvcl9yYXRpbmdzICE9IDBdDQp2ZWN0b3JfcmF0aW5ncyA8LSBmYWN0b3IodmVjdG9yX3JhdGluZ3MpDQpgYGANCg0KTGV0J3MgZ28gYWhlYWQgYW5kIHZpc3VhbGl6ZS4gVGhlIGZvbGxvd2luZyBpbWFnZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSByYXRpbmdzLiBNb3N0IG9mIHRoZSByYXRpbmdzIGFyZSBhYm92ZSAyLCBhbmQgdGhlIG1vc3QgY29tbW9uIGlzIDQuDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnFwbG90KHZlY3Rvcl9yYXRpbmdzKSArIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiB0aGUgcmF0aW5ncyIpDQpgYGANCg0KIyNFeHBsb3Jpbmcgd2hpY2ggbW92aWVzIGhhdmUgYmVlbiB2aWV3ZWQgDQoNCipjb2xDb3VudHM6IFRoaXMgaXMgdGhlIG51bWJlciBvZiBub24tbWlzc2luZyB2YWx1ZXMgZm9yIGVhY2ggY29sdW1uDQoqY29sTWVhbnM6IFRoaXMgaXMgdGhlIGF2ZXJhZ2UgdmFsdWUgZm9yIGVhY2ggY29sdW1uDQoNCipXaGljaCBhcmUgdGhlIG1vc3Qgdmlld2VkIG1vdmllcyAoVE9QIDEwKT8NCg0KU29ydCB0aGUgbW92aWVzIGJ5IG51bWJlciBvZiB2aWV3czoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kdmlld3NfcGVyX21vdmllIDwtIGNvbENvdW50cyhNb3ZpZUxlbnNlKQ0KDQp0YWJsZV92aWV3cyA8LSBkYXRhLmZyYW1lKA0KICBtb3ZpZSA9IG5hbWVzKHZpZXdzX3Blcl9tb3ZpZSksDQogIHZpZXdzID0gdmlld3NfcGVyX21vdmllKQ0KDQp0YWJsZV92aWV3cyA8LSB0YWJsZV92aWV3c1tvcmRlcih0YWJsZV92aWV3cyR2aWV3cywgZGVjcmVhc2luZyA9IFRSVUUpLCBdDQoNCnRhYmxlX3ZpZXdzDQpgYGANCg0KTGV0J3MgdmlzdWFsaXplIHRoZSBmaXJzdCBzaXggcm93cyBhbmQgYnVpbGQgYSBoaXN0b2dyYW06DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmdncGxvdCh0YWJsZV92aWV3c1sxOjYsIF0sIGFlcyh4ID0gbW92aWUsIHkgPSB2aWV3cykpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIA0KICBnZ3RpdGxlKCJOdW1iZXIgb2Ygdmlld3Mgb2YgdGhlIHRvcCBtb3ZpZXMiKQ0KYGBgDQoNCkluIHRoZSBwcmVjZWRpbmcgY2hhcnQsIHlvdSBjYW4gbm90aWNlIHRoYXQgU3RhciBXYXJzICgxOTc3KSBpcyB0aGUgbW9zdCB2aWV3ZWQNCm1vdmllLCBleGNlZWRpbmcgdGhlIG90aGVycyBieSBhYm91dCAxMDAgdmlld3MuDQoNCipXaGljaCBhcmUgdGhlIGxlYXN0IHZpZXdlZCBtb3ZpZXMgKEJPVFRPTSAxMCk/DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnZpZXdzX3Blcl9tb3ZpZSA8LSBjb2xDb3VudHMoTW92aWVMZW5zZSkNCg0KdGFibGVfdmlld3MgPC0gZGF0YS5mcmFtZSgNCiAgbW92aWUgPSBuYW1lcyh2aWV3c19wZXJfbW92aWUpLA0KICB2aWV3cyA9IHZpZXdzX3Blcl9tb3ZpZSkNCg0KdGFibGVfdmlld3MgPC0gdGFibGVfdmlld3Nbb3JkZXIodGFibGVfdmlld3Mkdmlld3MsIGRlY3JlYXNpbmcgPSBGQUxTRSksIF0NCnRhYmxlX3ZpZXdzDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KZ2dwbG90KHRhYmxlX3ZpZXdzWzE6NiwgXSwgYWVzKHggPSBtb3ZpZSwgeSA9IHZpZXdzKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgDQogIGdndGl0bGUoIk51bWJlciBvZiB2aWV3cyBvZiB0aGUgdG9wIG1vdmllcyIpDQpgYGANCg0KI0V4cGxvcmUgQXZlcmFnZSBSYXRpbmdzDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmF2ZXJhZ2VfcmF0aW5ncyA8LSBjb2xNZWFucyhNb3ZpZUxlbnNlKQ0KYGBgDQoNCkxldCdzIHZpc3VhbGl6ZSBieSBjcmVhdGluZyBhIGNoYXJ0LiBUaGUgZm9sbG93aW5nIGltYWdlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGF2ZXJhZ2UgbW92aWUgcmF0aW5nOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpxcGxvdChhdmVyYWdlX3JhdGluZ3MpICsgDQogIHN0YXRfYmluKGJpbndpZHRoID0gMC4xKSArIA0KICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgdGhlIGF2ZXJhZ2UgbW92aWUgcmF0aW5nIikNCmBgYA0KDQpUaGUgaGlnaGVzdCB2YWx1ZSBpcyBhcm91bmQgMywgYW5kIHRoZXJlIGFyZSBhIGZldyBtb3ZpZXMgd2hvc2UgcmF0aW5nIGlzIGVpdGhlciAxIG9yIDUuIFByb2JhYmx5LCB0aGUgcmVhc29uIGlzIHRoYXQgdGhlc2UgbW92aWVzIHJlY2VpdmVkIGEgcmF0aW5nIGZyb20gYSBmZXcgcGVvcGxlIG9ubHksIHNvIHdlIHNob3VsZG4ndCB0YWtlIHRoZW0gaW50byBhY2NvdW50Lg0KDQpMZXQncyByZW1vdmUgdGhlIG1vdmllcyB3aG9zZSBudW1iZXIgb2Ygdmlld3MgaXMgYmVsb3cgYSBkZWZpbmVkIHRocmVzaG9sZCwgZm9yIGluc3RhbmNlLCBiZWxvdyAxMDA6DQoNClRoZSBmb2xsb3dpbmcgaW1hZ2Ugc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVsZXZhbnQgYXZlcmFnZSByYXRpbmdzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQphdmVyYWdlX3JhdGluZ3NfcmVsZXZhbnQgPC0gYXZlcmFnZV9yYXRpbmdzW3ZpZXdzX3Blcl9tb3ZpZSA+IDEwMF0NCnFwbG90KGF2ZXJhZ2VfcmF0aW5nc19yZWxldmFudCkgKyBzdGF0X2JpbihiaW53aWR0aCA9IDAuMSkgKyANCiAgZ2d0aXRsZShwYXN0ZSgiRGlzdHJpYnV0aW9uIG9mIHRoZSByZWxldmFudCBhdmVyYWdlIHJhdGluZ3MiKSkNCmBgYA0KDQpBbGwgdGhlIHJhbmtpbmdzIGFyZSBiZXR3ZWVuIDIuMyBhbmQgNC41LiBBcyBleHBlY3RlZCwgd2UgcmVtb3ZlZCB0aGUgZXh0cmVtZXMuIFRoZSBoaWdoZXN0IHZhbHVlIGNoYW5nZXMsIGFuZCBub3csIGl0IGlzIGFyb3VuZCA0Lg0KDQpMZXQncyBidWlsZCB0aGUgaGVhdG1hcCB1c2luZyBpbWFnZS4gVGhlIGZvbGxvd2luZyBpbWFnZSBkaXNwbGF5cyB0aGUgaGVhdG1hcCBvZiB0aGUgcmF0aW5nIG1hdHJpeDoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KaW1hZ2UoTW92aWVMZW5zZSwgbWFpbiA9ICJIZWF0bWFwIG9mIHRoZSByYXRpbmcgbWF0cml4IikNCmBgYA0KDQpUaGUgYWJvdmUgaGVhdG1hcCBpcyBhIGJpdCBkaWZmaWN1bHQgdG8gcmVhZC4gTGV0J3MgYnVpbGQgdGhlIGhlYXQgbWFwIHVzaW5nIGltYWdlKCkuIFRoZSBmb2xsb3dpbmcgaW1hZ2Ugc2hvd3MgdGhlIGhlYXRtYXAgb2YgdGhlIGZpcnN0IHJvd3MgYW5kIGNvbHVtbnM6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmltYWdlKE1vdmllTGVuc2VbMToxMCwgMToxNV0sIG1haW4gPSAiSGVhdG1hcCBvZiB0aGUgZmlyc3Qgcm93cyBhbmQgY29sdW1ucyIpDQpgYGANCg0KVG9wIHBlcmNlbnRpbGUgb2YgdXNlcnMgYW5kIG1vdmllcywgbGV0J3MgdXNlIHF1YW50aWxlIGZ1bmN0aW9uDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm1pbl9uX21vdmllcyA8LSBxdWFudGlsZShyb3dDb3VudHMoTW92aWVMZW5zZSksIDAuOTkpDQptaW5fbl9tb3ZpZXMNCmBgYA0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQptaW5fbl91c2VycyA8LSBxdWFudGlsZShjb2xDb3VudHMoTW92aWVMZW5zZSksIDAuOTkpDQptaW5fbl91c2Vycw0KYGBgDQoNCkFub3RoZXIgaGVhdCBtYXA6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmltYWdlKE1vdmllTGVuc2Vbcm93Q291bnRzKE1vdmllTGVuc2UpID4gbWluX25fbW92aWVzLCBjb2xDb3VudHMoTW92aWVMZW5zZSkgPiBtaW5fbl91c2Vyc10sIG1haW4gPSAiSGVhdG1hcCBvZiB0aGUgdG9wIHVzZXJzIGFuZCBtb3ZpZXMiKQ0KYGBgDQoNCiMjUXVlc3Rpb25zOiANCipVc2VycyB3aG8gaGF2ZSByYXRlZCBhdCBsZWFzdCA1MCBtb3ZpZXMNCipNb3ZpZXMgdGhhdCBoYXZlIGJlZW4gd2F0Y2hlZCBhdCBsZWFzdCAxMDAgdGltZXMNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmF0aW5nc19tb3ZpZXMgPC0gTW92aWVMZW5zZVtyb3dDb3VudHMoTW92aWVMZW5zZSkgPiA1MCwgY29sQ291bnRzKE1vdmllTGVuc2UpID4gMTAwXSANCnJhdGluZ3NfbW92aWVzDQpgYGANCg0KVGhlIHJhdGluZ3NfbW92aWVzIG9iamVjdCBjb250YWlucyBhYm91dCBoYWxmIG9mIHRoZSB1c2VycyBhbmQgYSBmaWZ0aCBvZiB0aGUgbW92aWVzDQppbiBjb21wYXJpc29uIHdpdGggTW92aWVMZW5zZS4NCg0KTGV0J3MgdmlzdWFsaXplIHRoZSB0b3AgMiBwZXJjZW50IG9mIHVzZXJzIGFuZCBtb3ZpZXMgaW4gdGhlIG5ldyBtYXRyaXg6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm1pbl9tb3ZpZXMgPC0gcXVhbnRpbGUocm93Q291bnRzKHJhdGluZ3NfbW92aWVzKSwgMC45OCkNCm1pbl91c2VycyA8LSBxdWFudGlsZShjb2xDb3VudHMocmF0aW5nc19tb3ZpZXMpLCAwLjk4KQ0KYGBgDQoNCkxldCdzIGJ1aWxkIHRoZSBoZWF0bWFwOg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KaW1hZ2UocmF0aW5nc19tb3ZpZXNbcm93Q291bnRzKHJhdGluZ3NfbW92aWVzKSA+IG1pbl9tb3ZpZXMsIGNvbENvdW50cyhyYXRpbmdzX21vdmllcykgPiBtaW5fdXNlcnNdLCBtYWluID0gIkhlYXRtYXAgb2YgdGhlIHRvcCB1c2VycyBhbmQgbW92aWVzIikNCmBgYA0KDQpBcyB3ZSBhbHJlYWR5IG5vdGljZWQsIHNvbWUgcm93cyBhcmUgZGFya2VyIHRoYW4gdGhlIG90aGVycy4gVGhpcyBtaWdodCBtZWFuIHRoYXQNCnNvbWUgdXNlcnMgZ2l2ZSBoaWdoZXIgcmF0aW5ncyB0byBhbGwgdGhlIG1vdmllcy4gSG93ZXZlciwgd2UgaGF2ZSB2aXN1YWxpemVkIHRoZQ0KdG9wIG1vdmllcyBvbmx5LiBJbiBvcmRlciB0byBoYXZlIGFuIG92ZXJ2aWV3IG9mIGFsbCB0aGUgdXNlcnMsIGxldCdzIHRha2UgYSBsb29rIGF0IHRoZQ0KZGlzdHJpYnV0aW9uIG9mIHRoZSBhdmVyYWdlIHJhdGluZyBieSB1c2VyOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQphdmVyYWdlX3JhdGluZ3NfcGVyX3VzZXIgPC0gcm93TWVhbnMocmF0aW5nc19tb3ZpZXMpDQpgYGANCg0KTGV0J3MgdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24uIEFzIHN1c3BlY3RlZCwgdGhlIGF2ZXJhZ2UgcmF0aW5nIHZhcmllcyBhIGxvdCBhY3Jvc3MgZGlmZmVyZW50IHVzZXJzLg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpxcGxvdChhdmVyYWdlX3JhdGluZ3NfcGVyX3VzZXIpICsgc3RhdF9iaW4oYmlud2lkdGggPSAwLjEpICsgDQogIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiB0aGUgYXZlcmFnZSByYXRpbmcgcGVyIHVzZXIiKQ0KYGBgDQoNCk5vcm1hbGl6ZSB0aGUgZGF0YS4NCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmF0aW5nc19tb3ZpZXNfbm9ybSA8LSBub3JtYWxpemUocmF0aW5nc19tb3ZpZXMpDQpgYGANCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGF2ZXJhZ2UgcmF0aW5nIGJ5IHVzZXJzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpzdW0ocm93TWVhbnMocmF0aW5nc19tb3ZpZXNfbm9ybSkgPiAwLjAwMDAxKQ0KYGBgDQoNCkxldCdzIHZpc3VhbGl6ZSB0aGUgbm9ybWFsaXplZCBtYXRyaXgNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KaW1hZ2UocmF0aW5nc19tb3ZpZXNfbm9ybVtyb3dDb3VudHMocmF0aW5nc19tb3ZpZXNfbm9ybSkgPiBtaW5fbW92aWVzLCBjb2xDb3VudHMocmF0aW5nc19tb3ZpZXNfbm9ybSkgPiBtaW5fdXNlcnNdLCBtYWluID0gIkhlYXRtYXAgb2YgdGhlIHRvcCB1c2VycyBhbmQgbW92aWVzIikNCmBgYA0KDQpUaGUgZmlyc3QgZGlmZmVyZW5jZSB0aGF0IHdlIGNhbiBub3RpY2UgaXMgdGhlIGNvbG9ycywgYW5kIHRoaXMgaXMgYmVjYXVzZSB0aGUgZGF0YQ0KaXMgY29udGludW91cy4gUHJldmlvdXNseSwgdGhlIHJhdGluZyB3YXMgYW4gaW50ZWdlciBiZXR3ZWVuIDEgYW5kIDUuIEFmdGVyIHRoZQ0Kbm9ybWFsaXphdGlvbiwgdGhlIHJhdGluZyBjYW4gYmUgYW55IG51bWJlciBiZXR3ZWVuIC01IGFuZCA1Lg0KDQpUaGVyZSBhcmUgc3RpbGwgc29tZSBsaW5lcyB0aGF0IGFyZSBtb3JlIGJsdWUgYW5kIHNvbWUgdGhhdCBhcmUgbW9yZSByZWQuIFRoZSByZWFzb24NCmlzIHRoYXQgd2UgYXJlIHZpc3VhbGl6aW5nIG9ubHkgdGhlIHRvcCBtb3ZpZXMuIFdlIGFscmVhZHkgY2hlY2tlZCB0aGF0IHRoZSBhdmVyYWdlDQpyYXRpbmcgaXMgMCBmb3IgZWFjaCB1c2VyLg0KDQojI0JpbmFyaXppbmcgdGhlIGRhdGENCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmF0aW5nc19tb3ZpZXNfd2F0Y2hlZCA8LSBiaW5hcml6ZShyYXRpbmdzX21vdmllcywgbWluUmF0aW5nID0gMSkNCmBgYA0KDQpMZXQncyBzZWxlY3QgdGhpcyA1IHBlcmNlbnQgdXNpbmcgcXVhbnRpbGUuIFRoZSByb3cgYW5kIGNvbHVtbiBjb3VudHMgYXJlIHRoZSBzYW1lIGFzIHRoZSBvcmlnaW5hbCBtYXRyaXgsIHNvIHdlIGNhbiBzdGlsbCBhcHBseSByb3dDb3VudHMgYW5kIGNvbENvdW50cyBvbiByYXRpbmdzX21vdmllczoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KbWluX21vdmllc19iaW5hcnkgPC0gcXVhbnRpbGUocm93Q291bnRzKHJhdGluZ3NfbW92aWVzKSwgMC45NSkNCm1pbl91c2Vyc19iaW5hcnkgPC0gcXVhbnRpbGUoY29sQ291bnRzKHJhdGluZ3NfbW92aWVzKSwgMC45NSkNCmBgYA0KDQpMZXQncyBidWlsZCB0aGUgaGVhdCBtYXA6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmltYWdlKHJhdGluZ3NfbW92aWVzX3dhdGNoZWRbcm93Q291bnRzKHJhdGluZ3NfbW92aWVzKSA+IG1pbl9tb3ZpZXNfYmluYXJ5LGNvbENvdW50cyhyYXRpbmdzX21vdmllcykgPiBtaW5fdXNlcnNfYmluYXJ5XSwgbWFpbiA9ICJIZWF0bWFwIG9mIHRoZSB0b3AgdXNlcnMgYW5kIG1vdmllcyIpDQpgYGANCg0KT25seSBhIGZldyBjZWxscyBjb250YWluIHVud2F0Y2hlZCBtb3ZpZXMuIFRoaXMgaXMganVzdCBiZWNhdXNlIHdlIHNlbGVjdGVkIHRoZSB0b3ANCnVzZXJzIGFuZCBtb3ZpZXMuDQoNCkxldCdzIHVzZSB0aGUgc2FtZSBhcHByb2FjaCB0byBjb21wdXRlIGFuZCB2aXN1YWxpemUgdGhlIG90aGVyIGJpbmFyeSBtYXRyaXggVGhlDQpjZWxscyBoYXZpbmcgYSByYXRpbmcgYWJvdmUgdGhlIHRocmVzaG9sZCB3aWxsIGhhdmUgdGhlaXIgdmFsdWUgZXF1YWwgdG8gMSBhbmQgdGhlDQpvdGhlciBjZWxscyB3aWxsIGJlIDBzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpyYXRpbmdzX21vdmllc19nb29kIDwtIGJpbmFyaXplKHJhdGluZ3NfbW92aWVzLCBtaW5SYXRpbmcgPSAzKQ0KYGBgDQoNCkxldCdzIGJ1aWxkIHRoZSBoZWF0IG1hcDoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KaW1hZ2UocmF0aW5nc19tb3ZpZXNfZ29vZFtyb3dDb3VudHMocmF0aW5nc19tb3ZpZXMpID4gbWluX21vdmllc19iaW5hcnksIGNvbENvdW50cyhyYXRpbmdzX21vdmllcykgPiBtaW5fdXNlcnNfYmluYXJ5XSwgbWFpbiA9ICJIZWF0bWFwIG9mIHRoZSB0b3AgdXNlcnMgYW5kIG1vdmllcyIpDQpgYGANCg0KQXMgZXhwZWN0ZWQsIHdlIGhhdmUgbW9yZSB3aGl0ZSBjZWxscyBub3cuIERlcGVuZGluZyBvbiB0aGUgbW9kZWwsIHdlIGNhbiBsZWF2ZQ0KdGhlIHJhdGluZ3MgbWF0cml4IGFzIGl0IGlzIG9yIG5vcm1hbGl6ZS9iaW5hcml6ZSBpdC4NCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBwcmVwYXJlZCB0aGUgZGF0YSB0byBwZXJmb3JtIHJlY29tbWVuZGF0aW9ucy4gSW4gdGhlIHVwY29taW5nDQpzZWN0aW9ucywgd2Ugd2lsbCBidWlsZCBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBtb2RlbHMuDQoNCg0KI1RyYWluaW5nIGFuZCBUZXN0IFNldHMNCg0KRmlyc3QsIHdlIHJhbmRvbWx5IGRlZmluZSB0aGUgd2hpY2hfdHJhaW4gdmVjdG9yIHRoYXQgaXMgVFJVRSBmb3IgdXNlcnMgaW4gdGhlIHRyYWluaW5nIHNldCBhbmQgRkFMU0UgZm9yIHRoZSBvdGhlcnMuIFdlIHdpbGwgc2V0IHRoZSBwcm9iYWJpbGl0eSBpbiB0aGUgdHJhaW5pbmcgc2V0IGFzIDgwIHBlcmNlbnQ6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCndoaWNoX3RyYWluIDwtIHNhbXBsZSh4ID0gYyhUUlVFLCBGQUxTRSksIHNpemUgPSBucm93KHJhdGluZ3NfbW92aWVzKSwgcmVwbGFjZSA9IFRSVUUsIHByb2IgPSBjKDAuOCwgMC4yKSkNCmhlYWQod2hpY2hfdHJhaW4pDQpgYGANCg0KTGV0J3MgZGVmaW5lIHRoZSB0cmFpbmluZyBhbmQgdGhlIHRlc3Qgc2V0czoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjY19kYXRhX3RyYWluIDwtIHJhdGluZ3NfbW92aWVzW3doaWNoX3RyYWluLCBdDQpyZWNjX2RhdGFfdGVzdCA8LSByYXRpbmdzX21vdmllc1shd2hpY2hfdHJhaW4sIF0NCmBgYA0KDQpTYW1wbGUgQ29kZToNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kd2hpY2hfc2V0IDwtIHNhbXBsZSh4ID0gMTo1LCBzaXplID0gbnJvdyhyYXRpbmdzX21vdmllcyksIHJlcGxhY2UgPSBUUlVFKQ0KZm9yKGlfbW9kZWwgaW4gMTo1KSANCnsNCiAgd2hpY2hfdHJhaW4gPC0gd2hpY2hfc2V0ID09IGlfbW9kZWwNCiAgcmVjY19kYXRhX3RyYWluIDwtIHJhdGluZ3NfbW92aWVzW3doaWNoX3RyYWluLCBdDQogIHJlY2NfZGF0YV90ZXN0IDwtIHJhdGluZ3NfbW92aWVzWyF3aGljaF90cmFpbiwgXQ0KfQ0KYGBgDQoNCiNSZWNvbW1lbmRhdGlvbiBtb2RlbA0KDQoqRGF0YTogVGhpcyBpcyB0aGUgdHJhaW5pbmcgc2V0DQoqTWV0aG9kOiBUaGlzIGlzIHRoZSBuYW1lIG9mIHRoZSB0ZWNobmlxdWUNCipQYXJhbWV0ZXJzOiBUaGVzZSBhcmUgc29tZSBvcHRpb25hbCBwYXJhbWV0ZXJzIG9mIHRoZSB0ZWNobmlxdWUNCg0KSUJDRiwgd2hpY2ggc3RhbmRzIGZvciBpdGVtLWJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nLiBCZWxvdyBvdXRwdXRzIGFyZSB0aGUgcGFyYW1ldGVycy4NCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjb21tZW5kZXJfbW9kZWxzIDwtIHJlY29tbWVuZGVyUmVnaXN0cnkkZ2V0X2VudHJpZXMoZGF0YVR5cGUgPSAicmVhbFJhdGluZ01hdHJpeCIpDQpyZWNvbW1lbmRlcl9tb2RlbHMkSUJDRl9yZWFsUmF0aW5nTWF0cml4JHBhcmFtZXRlcnMNCmBgYA0KDQpTbyBsZXQncyBidWlsZCBpdC4NCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjY19tb2RlbCA8LSBSZWNvbW1lbmRlcihkYXRhID0gcmVjY19kYXRhX3RyYWluLCBtZXRob2QgPSAiSUJDRiIsIHBhcmFtZXRlciA9IGxpc3QoayA9IDMwKSkNCnJlY2NfbW9kZWwNCmBgYA0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpjbGFzcyhyZWNjX21vZGVsKQ0KYGBgDQoNCldlJ2xsIGV4dHJhY3Qgc29tZSBvZiB0aGUgZGV0YWlscyAoZGVzY3JpcHRpb24gYW5kIHBhcmFtZXRlcnMpLg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQptb2RlbF9kZXRhaWxzIDwtIGdldE1vZGVsKHJlY2NfbW9kZWwpDQptb2RlbF9kZXRhaWxzJGRlc2NyaXB0aW9uDQpgYGANCg0KVGhlIG1vZGVsX2RldGFpbHMkc2ltIGNvbXBvbmVudCBjb250YWlucyB0aGUgc2ltaWxhcml0eSBtYXRyaXguIExldCdzIGNoZWNrIGl0cyBzdHJ1Y3R1cmU6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmNsYXNzKG1vZGVsX2RldGFpbHMkc2ltKQ0KYGBgDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCmRpbShtb2RlbF9kZXRhaWxzJHNpbSkNCmBgYA0KDQptb2RlbF9kZXRhaWxzJHNpbSBpcyBhIHNxdWFyZSBtYXRyaXggd2hvc2Ugc2l6ZSBpcyBlcXVhbCB0byB0aGUgbnVtYmVyIG9mIGl0ZW1zLiBMZXQncyBidWlsZCBoZWF0IG1hcC4NCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kbl9pdGVtc190b3AgPC0gMjANCmltYWdlKG1vZGVsX2RldGFpbHMkc2ltWzE6bl9pdGVtc190b3AsIDE6bl9pdGVtc190b3BdLCBtYWluID0gIkhlYXRtYXAgb2YgdGhlIGZpcnN0IHJvd3MgYW5kIGNvbHVtbnMiKQ0KYGBgDQoNCk1vc3Qgb2YgdGhlIHZhbHVlcyBhcmUgZXF1YWwgdG8gMC4gVGhlIHJlYXNvbiBpcyB0aGF0IGVhY2ggcm93IGNvbnRhaW5zIG9ubHkgayBlbGVtZW50cy4NCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KbW9kZWxfZGV0YWlscyRrDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kcm93X3N1bXMgPC0gcm93U3Vtcyhtb2RlbF9kZXRhaWxzJHNpbSA+IDApDQp0YWJsZShyb3dfc3VtcykNCmBgYA0KDQpTbyBlYWNoIHJvdyBoYXMgMzAgZWxlbWVudHMgZ3JlYXRlciB0aGFuIDAuIEhvd2V2ZXIsIHRoZSBtYXRyaXggaXMgbm90IHN1cHBvc2VkIHRvIGJlIHN5bW1ldHJpYy4gSW4gZmFjdCwgdGhlIG51bWJlciBvZiBub24tbnVsbCBlbGVtZW50cyBmb3IgZWFjaCBjb2x1bW4gZGVwZW5kcyBvbiBob3cgbWFueSB0aW1lcyB0aGUgY29ycmVzcG9uZGluZyBtb3ZpZSB3YXMgaW5jbHVkZWQgaW4gdGhlIHRvcCBrIG9mDQphbm90aGVyIG1vdmllLiBMZXQncyBjaGVjayB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgZWxlbWVudHMgYnkgY29sdW1uOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpjb2xfc3VtcyA8LSBjb2xTdW1zKG1vZGVsX2RldGFpbHMkc2ltID4gMCkNCmBgYA0KDQpMZXQncyBidWlsZCB0aGUgZGlzdHJpYnV0aW9uIGNoYXJ0Og0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpxcGxvdChjb2xfc3VtcykgKyBzdGF0X2JpbihiaW53aWR0aCA9IDEpICsgZ2d0aXRsZSgiRGlzdHJpYnV0aW9uIG9mIHRoZSBjb2x1bW4gY291bnQiKQ0KYGBgDQoNCkFzIGV4cGVjdGVkLCB0aGVyZSBhcmUgYSBmZXcgbW92aWVzIHRoYXQgYXJlIHNpbWlsYXIgdG8gbWFueSBvdGhlcnMuIExldCdzIHNlZSB3aGljaCBhcmUgdGhlIG1vdmllcyB3aXRoIHRoZSBtb3N0IGVsZW1lbnRzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQp3aGljaF9tYXggPC0gb3JkZXIoY29sX3N1bXMsIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjZdDQpyb3duYW1lcyhtb2RlbF9kZXRhaWxzJHNpbSlbd2hpY2hfbWF4XQ0KYGBgDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm5fcmVjb21tZW5kZWQgPC0gNg0KcmVjY19wcmVkaWN0ZWQgPC0gcHJlZGljdChvYmplY3QgPSByZWNjX21vZGVsLCBuZXdkYXRhID0gcmVjY19kYXRhX3Rlc3QsIG4gPSBuX3JlY29tbWVuZGVkKQ0KcmVjY19wcmVkaWN0ZWQNCmBgYA0KDQpUaGUgcmVjY19wcmVkaWN0ZWQgb2JqZWN0IGNvbnRhaW5zIHRoZSByZWNvbW1lbmRhdGlvbnMNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KY2xhc3MocmVjY19wcmVkaWN0ZWQpDQpgYGANCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kc2xvdE5hbWVzKHJlY2NfcHJlZGljdGVkKQ0KYGBgDQoNCkZvciBpbnN0YW5jZSwgdGhlc2UgYXJlIHRoZSByZWNvbW1lbmRhdGlvbnMgZm9yIHRoZSBmaXJzdCB1c2VyOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpyZWNjX3ByZWRpY3RlZEBpdGVtc1tbMV1dDQpgYGANCg0KV2Ugd291bGQgbmVlZCB0byBleHRyYWN0IHRoZSByZWNvbW1lbmRlZCBtb3ZpZXMgZnJvbSByZWNjX3ByZWRpY3RlZChAKWl0ZW0gbGFiZWxzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpyZWNjX3VzZXJfMSA8LSByZWNjX3ByZWRpY3RlZEBpdGVtc1tbMV1dDQptb3ZpZXNfdXNlcl8xIDwtIHJlY2NfcHJlZGljdGVkQGl0ZW1MYWJlbHNbcmVjY191c2VyXzFdDQptb3ZpZXNfdXNlcl8xDQpgYGANCg0KTGV0J3MgZGVmaW5lIGEgZnVuY3Rpb24gb2YgYSBtYXRyaXggd2l0aCB0aGUgcmVjb21tZW5kYXRpb25zIGZvciBlYWNoIHVzZXI6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnJlY2NfbWF0cml4IDwtIHNhcHBseShyZWNjX3ByZWRpY3RlZEBpdGVtcywgZnVuY3Rpb24oeCkgew0KICBjb2xuYW1lcyhyYXRpbmdzX21vdmllcylbeF0NCn0pDQoNCmRpbShyZWNjX21hdHJpeCkNCmBgYA0KDQpMZXQncyB2aXN1YWxpemUgdGhlIHJlY29tbWVuZGF0aW9ucyBmb3IgdGhlIGZpcnN0IGZvdXIgdXNlcnM6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnJlY2NfbWF0cml4WywgMTo0XQ0KYGBgDQoNCk5vdywgd2UgY2FuIGlkZW50aWZ5IHRoZSBtb3N0IHJlY29tbWVuZGVkIG1vdmllcy4gRm9yIHRoaXMgcHVycG9zZSwgd2Ugd2lsbCBkZWZpbmUgYSB2ZWN0b3Igd2l0aCBhbGwgdGhlIHJlY29tbWVuZGF0aW9ucywgYW5kIHdlIHdpbGwgYnVpbGQgYSBmcmVxdWVuY3kgcGxvdDoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KbnVtYmVyX29mX2l0ZW1zIDwtIGZhY3Rvcih0YWJsZShyZWNjX21hdHJpeCkpDQpjaGFydF90aXRsZSA8LSAiRGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgaXRlbXMgZm9yIElCQ0YiDQpgYGANCg0KVGhlIGRpc3RyaWJ1dGlvbiBjaGFydCB0aGF0IHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG51bWJlciBvZiBpdGVtcyBmb3IgSUJDRjoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcXBsb3QobnVtYmVyX29mX2l0ZW1zKSArIA0KICBnZ3RpdGxlKGNoYXJ0X3RpdGxlKQ0KYGBgDQoNCkxldCdzIHNlZSB3aGljaCBhcmUgdGhlIG1vc3QgcG9wdWxhciByZWNvbW1lbmRlZCBtb3ZpZXM6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm51bWJlcl9vZl9pdGVtc19zb3J0ZWQgPC0gc29ydChudW1iZXJfb2ZfaXRlbXMsIGRlY3JlYXNpbmcgPSBUUlVFKQ0KbnVtYmVyX29mX2l0ZW1zX3RvcCA8LSBoZWFkKG51bWJlcl9vZl9pdGVtc19zb3J0ZWQsIG4gPSA0KQ0KdGFibGVfdG9wIDwtIGRhdGEuZnJhbWUobmFtZXMobnVtYmVyX29mX2l0ZW1zX3RvcCksIG51bWJlcl9vZl9pdGVtc190b3ApDQp0YWJsZV90b3ANCmBgYA0KDQpBcyB5b3UgY2FuIHNlZSBmcm9tIHRoZSBwcmVjZWRpbmcgdGFibGUsIHRoZSBtb3ZpZSAiTXIuIFNtaXRoIEdvZXMgdG8gV2FzaGluZ3RvbiIgaGFzIGJlZW4gcmVjb21tZW5kZWQgdGhlIG1vc3QgdGltZXMuDQoNCklCQ0YgcmVjb21tZW5kcyBpdGVtcyBvbiB0aGUgYmFzaXMgb2YgdGhlIHNpbWlsYXJpdHkgbWF0cml4LiB0aGlzIGFsZ29yaXRobSBpcyBlZmZpY2llbnQgYW5kIHNjYWxhYmxlLA0KDQoNCiNCdWlsZGluZyB0aGUgcmVjb21tZW5kYXRpb24gbW9kZWwNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjb21tZW5kZXJfbW9kZWxzIDwtIHJlY29tbWVuZGVyUmVnaXN0cnkkZ2V0X2VudHJpZXMoZGF0YVR5cGUgPSAicmVhbFJhdGluZ01hdHJpeCIpDQpyZWNvbW1lbmRlcl9tb2RlbHMkVUJDRl9yZWFsUmF0aW5nTWF0cml4JHBhcmFtZXRlcnMNCmBgYA0KDQpNb2RlbCB3aXRoIGRlZmF1bHQgcGFyYW1ldGVyczoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjY19tb2RlbCA8LSBSZWNvbW1lbmRlcihkYXRhID0gcmVjY19kYXRhX3RyYWluLCBtZXRob2QgPSAiVUJDRiIpDQpyZWNjX21vZGVsDQpgYGANCg0KQ29tcG9uZW50cyBvZiB0aGUgbW9kZWw6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm1vZGVsX2RldGFpbHMgPC0gZ2V0TW9kZWwocmVjY19tb2RlbCkNCm5hbWVzKG1vZGVsX2RldGFpbHMpDQpgYGANCg0KVGhlIGJlbG93IG9iamVjdCBjb250YWlucyB0aGUgcmF0aW5nIG1hdHJpeC4gVUJDRiBpcyBhIGxhenktbGVhcm5pbmcgdGVjaG5pcXVlLCB3aGljaCBtZWFucyB0aGF0IGl0IG5lZWRzIHRvIGFjY2VzcyBhbGwgdGhlIGRhdGEgdG8gcGVyZm9ybSBhIHByZWRpY3Rpb24uDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm1vZGVsX2RldGFpbHMkZGF0YQ0KYGBgDQoNCiMjQXBwbHkgbW9kZWwgbW9kZWwgdG8gdGVzdCBzZXQNCg0KTGV0J3MgZmluZCB0aGUgdG9wIHNpeCByZWNvbW1lbmRhdGlvbnMgZm9yIGVhY2ggbmV3IHVzZXINCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0Kbl9yZWNvbW1lbmRlZCA8LSA2DQpyZWNjX3ByZWRpY3RlZCA8LSBwcmVkaWN0KG9iamVjdCA9IHJlY2NfbW9kZWwsIG5ld2RhdGEgPSByZWNjX2RhdGFfdGVzdCwgbiA9IG5fcmVjb21tZW5kZWQpIA0KcmVjY19wcmVkaWN0ZWQNCmBgYA0KDQpMZXQncyBkZWZpbmUgYSBmdW50aW9uYyBvZiBhIG1hdHJpeCB3aXRoIHRoZSByZWNvbW1lbmRhdGlvbnMgdG8gdGhlIHRlc3Qgc2V0IHVzZXJzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpyZWNjX21hdHJpeCA8LSBzYXBwbHkocmVjY19wcmVkaWN0ZWRAaXRlbXMsIGZ1bmN0aW9uKHgpeyAgY29sbmFtZXMocmF0aW5nc19tb3ZpZXMpW3hdIH0pDQpkaW0ocmVjY19tYXRyaXgpDQpgYGANCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGZpcnN0IGZvdXIgdXNlcnM6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnJlY2NfbWF0cml4WywgMTo0XQ0KYGBgDQoNCiMjRnJlcXVlbmN5IENoYXJ0DQoNCldlIHdpbGwgY29tcHV0ZSBob3cgbWFueSB0aW1lcyBlYWNoIG1vdmllIGdvdCByZWNvbW1lbmRlZCBhbmQgYnVpbGQgdGhlIHJlbGF0ZWQgZnJlcXVlbmN5IGhpc3RvZ3JhbQ0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQp0YWJsZShudW1iZXJfb2ZfaXRlbXMpDQpgYGANCg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQpudW1iZXJfb2ZfaXRlbXMgPC0gZmFjdG9yKHRhYmxlKHJlY2NfbWF0cml4KSkNCmNoYXJ0X3RpdGxlIDwtICJEaXN0cmlidXRpb24gb2YgdGhlIG51bWJlciBvZiBpdGVtcyBmb3IgVUJDRiINCnFwbG90KG51bWJlcl9vZl9pdGVtcykgKyANCiAgZ2d0aXRsZShjaGFydF90aXRsZSkNCmBgYA0KDQpDb21wYXJlZCB3aXRoIHRoZSBJQkNGLCB0aGUgZGlzdHJpYnV0aW9uIGhhcyBhIGxvbmdlciB0YWlsLiBUaGlzIG1lYW5zIHRoYXQgdGhlcmUgYXJlIHNvbWUgbW92aWVzIHRoYXQgYXJlIHJlY29tbWVuZGVkIG11Y2ggbW9yZSBvZnRlbiB0aGFuIHRoZSBvdGhlcnMuIFRoZSBtYXhpbXVtIGlzIDM0LCBjb21wYXJlZCB3aXRoIDExIGZvciBJQkNGLg0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgdG9wIHRpdGxlczoNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KbnVtYmVyX29mX2l0ZW1zX3NvcnRlZCA8LSBzb3J0KG51bWJlcl9vZl9pdGVtcywgZGVjcmVhc2luZyA9IFRSVUUpDQpudW1iZXJfb2ZfaXRlbXNfdG9wIDwtIGhlYWQobnVtYmVyX29mX2l0ZW1zX3NvcnRlZCwgbiA9IDQpDQp0YWJsZV90b3AgPC0gZGF0YS5mcmFtZShuYW1lcyhudW1iZXJfb2ZfaXRlbXNfdG9wKSwgbnVtYmVyX29mX2l0ZW1zX3RvcCkNCnRhYmxlX3RvcA0KYGBgDQoNClRoZSBHb2RmYXRoZXIgaXMgdGhlIHRvcCBtb3ZpZSB0aXRsZS4NCg0KI0NvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIG9uIGJpbmFyeSBkYXRhDQoNCiMjRGF0YSBwcmVwYXJhdGlvbg0KDQpMZXQncyBidWlsZCByYXRpbmdzX21vdmllc193YXRjaGVkIHVzaW5nIHRoZSBiaW5hcml6ZSBtZXRob2QgYXMgZm9sbG93czoNCg0KMSBpZiB0aGUgdXNlciBwdXJjaGFzZWQgKG9yIGxpa2VkKSB0aGUgaXRlbSwgYW5kIDAgb3RoZXJ3aXNlLiBUaGlzIGNhc2UgaXMgZGlmZmVyZW50IGZyb20gdGhlIHByZXZpb3VzIGNhc2VzLCBzbyBpdCBzaG91bGQgYmUgdHJlYXRlZCBzZXBhcmF0ZWx5LiBTaW1pbGFyIHRvIHRoZSBvdGhlciBjYXNlcywgdGhlIHRlY2huaXF1ZXMgYXJlIGl0ZW0tYmFzZWQgYW5kIHVzZXItYmFzZWQuDQoNCkluIG91ciBjYXNlLCBzdGFydGluZyBmcm9tIHJhdGluZ3NfbW92aWVzLCB3ZSBjYW4gYnVpbGQgYSByYXRpbmdzX21vdmllc193YXRjaGVkIG1hdHJpeCB3aG9zZSB2YWx1ZXMgd2lsbCBiZSAxIGlmIHRoZSB1c2VyIHZpZXdlZCB0aGUgbW92aWUsIGFuZCAwIG90aGVyd2lzZS4gV2UgYnVpbHQgaXQgaW4gb25lIG9mIHRoZSBCaW5hcml6aW5nIHRoZSBkYXRhIHNlY3Rpb25zLg0KDQpCaW5hcml6aW5nIG1ldGhvZCBhcyBhcyBiZWZvcmUgd2l0aCBJQkNGDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnJhdGluZ3NfbW92aWVzX3dhdGNoZWQgPC0gYmluYXJpemUocmF0aW5nc19tb3ZpZXMsIG1pblJhdGluZyA9IDEpDQoNCnFwbG90KHJvd1N1bXMocmF0aW5nc19tb3ZpZXNfd2F0Y2hlZCkpICsgc3RhdF9iaW4oYmlud2lkdGggPSAxMCkgKyANCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbihyb3dTdW1zKHJhdGluZ3NfbW92aWVzX3dhdGNoZWQpKSwgY29sID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsgDQogIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBtb3ZpZXMgYnkgdXNlciIpDQpgYGANCg0KU28sIHdlIGNhbiBhbnN3ZXIgdGhhdCBvbiB0aGUgYXZlcmFnZSwgZWFjaCB1c2VyIHdhdGNoZWQgYWJvdXQgMTAwIG1vdmllcywgYW5kIG9ubHkgYSBmZXcgd2F0Y2hlZCBtb3JlIHRoYW4gMjAwIG1vdmllcy4NCg0KTGV0J3MgZGVmaW5lIG91ciB0cmFpbmluZyBhbmQgdGVzdCBzZXRzOg0KDQpgYGB7ciBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQp3aGljaF90cmFpbiA8LSBzYW1wbGUoeCA9IGMoVFJVRSwgRkFMU0UpLCBzaXplID0gbnJvdyhyYXRpbmdzX21vdmllcyksIHJlcGxhY2UgPSBUUlVFLCBwcm9iID0gYygwLjgsIDAuMikpDQpyZWNjX2RhdGFfdHJhaW4gPC0gcmF0aW5nc19tb3ZpZXNbd2hpY2hfdHJhaW4sIF0NCnJlY2NfZGF0YV90ZXN0IDwtIHJhdGluZ3NfbW92aWVzWyF3aGljaF90cmFpbiwgXQ0KYGBgDQoNCiNJdGVtLWJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIG9uIGJpbmFyeSBkYXRhDQoNClNhbWUgYXMgYmVmb3JlIGluIGV4Y2VwdGlvbiB0byBpbnB1dCBwYXJhbWV0ZXIgbWV0aG9kIGVxdWFsIHRvIEphY2NhcmQNCg0KYGBge3IgZXZhbD1UUlVFLCBlY2hvPUZBTFNFfQ0KcmVjY19tb2RlbCA8LSBSZWNvbW1lbmRlcihkYXRhID0gcmVjY19kYXRhX3RyYWluLCBtZXRob2QgPSAiSUJDRiIsIHBhcmFtZXRlciA9IGxpc3QobWV0aG9kID0gIkphY2NhcmQiKSkNCm1vZGVsX2RldGFpbHMgPC0gZ2V0TW9kZWwocmVjY19tb2RlbCkNCmBgYA0KDQpTYW1lIGFzIGJlZm9yZSwgbGV0J3MgcmVjb21tZW5kIHNpeCBpdGVtcyB0byBlYWNoIG9mIHRoZSB1c2VycyBpbiB0aGUgdGVzdCBzZXQ6DQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCm5fcmVjb21tZW5kZWQgPC0gNg0KDQpyZWNjX3ByZWRpY3RlZCA8LSBwcmVkaWN0KG9iamVjdCA9IHJlY2NfbW9kZWwsIG5ld2RhdGEgPSByZWNjX2RhdGFfdGVzdCwgbiA9IG5fcmVjb21tZW5kZWQpDQoNCnJlY2NfbWF0cml4IDwtIHNhcHBseShyZWNjX3ByZWRpY3RlZEBpdGVtcywgZnVuY3Rpb24oeCl7DQogIGNvbG5hbWVzKHJhdGluZ3NfbW92aWVzKVt4XQ0KfSkNCmBgYA0KDQpMZXQncyBmdXJ0aGVyIGV4YW1pbmUgdGhlIHJlY29tbWVuZGF0aW9ucyBmb3IgdGhlIGZpcnN0IGZvdXIgdXNlcnMuDQoNCmBgYHtyIGV2YWw9VFJVRSwgZWNobz1GQUxTRX0NCnJlY2NfbWF0cml4WywgMTo0XQ0KYGBgDQoNCk5vdGU6IFRoZSBhcHByb2FjaCBpcyBzaW1pbGFyIHRvIElCQ0YgdXNpbmcgYSByYXRpbmcgbWF0cml4LiBTaW5jZSB3ZSBhcmUgbm90IHRha2luZw0KYWNjb3VudCBvZiB0aGUgcmF0aW5ncywgdGhlIHJlc3VsdCB3aWxsIGJlIGxlc3MgYWNjdXJhdGUuDQoNCkVPRg==