1 - Introduction


In 1977 the idea of applying statistical analysis to American professional sports was popularized with Bill James and the introduction of Sabermetrics. However, professional sports tend to be very conservative and slow to change and you still see articles and comments from players and coaches on how their favored sport can not be boiled down to just numbers. Baseball began to explore the use of metrics in the 1990’s and early 2000’s and over the last 10 years we have slowly seen the NFL, NBA, HNL, and Professional Soccer Leagues begin to explore the application of data analytics in their own sports. In this project we will apply the tools that we have learned this semester to build a recommender system for NFL play calling.

My interest in this topic is also of a personal nature. I have spent 13 years as a high school football coach and spent 3 years as an offensive coordinator and play caller. Currently calling plays in football is based on a mixture of hours of video work, trying to find tendencies and weaknesses in the opponents defense and an intuition of how your opponent will adjust throughout the game. This leads to the production of the massive, although not nearly as large in high school, play calling cards with everything that you think that will work for any given situation. The idea for this recommender is to provide a type of play and a direction to run the play in. For example we would like to indicate that on a 1^{st} and 10 from the 20 yard line, a given team should run the ball over the right tackle or throw the ball to the deep middle of the field.

2 - Data and Methodology


For this recommender system we will be using the play-by-play data for the 2013 through 2016 NFL seasons. The data that we will be using is available at NFL Savant and contains every play for each of the seasons. The data set contains 41 different variables dealing with various aspects of the game. We will predominately be working with the variables that give us information on the game situation, the play type and the yards gained. The work to prepare the data is not included in this paper but involved creating numeric variables to describe the various categories and play types and merging the various years together. There was also a number of steps needed to clean up issues in the csv files. The fully cleaned data set is loaded below, it contains 123814 different plays which we will end up segmenting into run and pass plays. We also load two small csv files that contain the unique identifier for each of the teams and the unique identifiers for each of the plays.

The heart of our recommender will be a decomposition of the user-item matrix into a user factor matrix and an item factor matrix using Alternating Least Squares matrix factorization. This will be calculated twice, once for rushing plays and again for passing plays. To facilitate this, we will be using the spark system and the sparklyr package in R. We begin this process by connecting to spark.

3 - Preparing the Data for our Recommenders


Given that our system is designed to recommend plays based off of the maximum number of yards that can be gained, we quickly realized that the system would naturally settle on pass plays. To avoid this issue we decided to build two separate user-item matrices, one for passes and one for runs. This will allow us to recommend the best pass play and the best run play for a given game situation. To facilitate this we will separate the data into two separate data frames and explore our target variable of yards gained in each of the sets.

3.1 - Exploring the Rushing Plays

We begin by exploring the rushing plays that have occurred over the last 4 years in the NFL. We see that there have been 51,972 rushing plays with an average rush of 4.38 yards. We note that the standard deviation for the plays is larger then the mean. This is related to the explosive rushing plays which we will end up removing from the data set. Our first attempt at a recommendation system left these in but we found that they influenced to system to overestimate the yards per rush. We can see the explosive plays as the long tail of the distribution.

paste("Number of Plays:", length(pbp_rush$Yards), 
      ", Average Yards per Attempt:", round(mean(pbp_rush$Yards), 2),
      ", Standard Deviation:", round(sd(pbp_rush$Yards), 2))
[1] "Number of Plays: 51972 , Average Yards per Attempt: 4.38 , Standard Deviation: 6.38"

3.2 Exploring the Pass Plays

We next explore the passing plays from the last 4 years of the NFL and see that there have been 71,842 pass plays, however there is an interesting feature of the data set. When we graph the pass yardage we notice a massive spike at 0. While there are some passes that end up being zero yards, we note that the majority of these plays are incompletions. We decided that we should remove these plays to give the data a more normal shape. We do want to include the information from the completion percentage for each pass in the recommender and we will discuss possible methods for this later in the paper.

In the graph below we see that removing the incompletions creates a more normal looking data set. We will also notice that the data has a long tail that indicates that there are a number of explosive plays in the data set that we will end up removing to improve the recommendations. Removing the incompletions reduces the total number of pass plays to 46,740 plays with an average yardage of 11.64 yards.

paste("Number of Plays:", length(pbp_pass$Yards), 
      ", Average Yards per Attempt:", round(mean(pbp_pass$Yards), 2),
      ", Standard Deviation:", round(sd(pbp_pass$Yards), 2))
[1] "Number of Plays: 46740 , Average Yards per Attempt: 11.64 , Standard Deviation: 10.78"

4 - Creating User-Item Data Frames and Loading into Spark


Now that we have some idea of what is going on in rushing an passing plays for the last 4 years we can build our user item data frames for the two play types. We start by reducing each of the data sets to the situation identifiers (SitId), play identifiers (PlayId), and the Yard gained. We next load each of the data frames into our spark instance and once we have the data loaded we use some of the built in dplyr features in sparklyr to remove the explosive plays from each of the data sets. While there are various definitions for an explosive play, we have elected to remove all running plays more than 15 yards and all passing plays more than 35 yards.

5 - Building the Recommender


Now that we have our data in we use the following commands to compute the Alternating Least Squares model for the rushing and passing plays. These will result in two model object that contain the factor matrices for the users (SitId) and items (PlayId) once we build the factor matrices we will use them to predict the predicted yards for each of the SitId and PlayId pairs.

5.1 Constructing the ALS Models

The code below generates the ALS factorization in spark.

# Creating the models for each down and run pass
rush_model <- ml_als_factorization(rush_small, rating.column = "Yards", 
                                   user.column = "SitId",
                                   item.column = "Play",
                                   iter.max = 5, regularization.parameter = 0.01,
                                   implicit.preferences = TRUE, alpha = 1.0)

pass_model <- ml_als_factorization(pass_small, rating.column = "Yards", 
                                   user.column = "SitId",
                                   item.column = "Play",
                                   iter.max = 5, regularization.parameter = 0.01,
                                   implicit.preferences = TRUE, alpha = 1.0)

5.2 Calculating the Root Mean Squared Error

We have elected to use the root mean squared error (RMSE) as a measure of the accuracy of our recommender this measure will give us information about the average number of yards that each of the plays is off by.

Calculating the RMSE for the Rushing Plays

We find that the rushing recommender has a RMSE of 2.02. This indicates the average difference between the yardage from the recommender and the yardage from the actual play. Given that the average rush play is only 4 yards the two yard difference could lead to some poor recommendations. We recognize that this is an area that would need continued work before the system would be used in a live situation.

sqrt(mean(with(rush_small_pred, prediction-Yards)^2))
[1] 2.015488
Calculating the RMSE for the Passing Plays

We find that the passing recommender has a RMSE of 2.99. Given that the typical pass play is 11.64 yards we find this difference is much more reasonable for our system.

sqrt(mean(with(pass_small_pred, prediction-Yards)^2))
[1] 2.994098

6 - Building the NFL Play Recommender


Now that we have our factorization matrices for the rushing an passing plays we can now build our recommendation system. For this portion of the project we will use the following methodology. First, we will generate a user item matrix for both the passing plays and the rushing plays. second, we will then build our system to first check to see if a given game situation is in the recommender. If it is, we will return the top two pass plays and the top two run plays for the given situation. If we have not seen the game situation, ie a cold start type situation, then we will return the best two pass plays as judged by their average yardage gained and the best two run plays using the same criteria. Finally, we output these plays along with the predicted number of yards that play is expected to gain in the given game situation. We include sample output from the rushing and passing prediction matrices.

Rushing Prediction Matrix
head(rush_pred)
              100         102       103        104        105        106        107
110199 -0.5316073 -0.02593189 0.7152297 -0.1499245 -0.4852820 -1.6861091 -0.1685180
110298  1.4588747  0.27142259 0.5282111  0.3367665  0.2883610  0.3112495  0.3089785
110397  1.3459811  0.28720546 0.7872098  0.1133299  0.2612576  0.6498827  2.8592724
110496  1.7435216  0.74015563 1.3295096  1.2254416  0.7724459  3.3875516  3.7033791
110521  0.7611403  0.70469797 1.0808897  4.9070508  0.6993462  1.0585020  1.5448542
110534  0.0000000  0.00000000 0.0000000  0.0000000  0.0000000  0.0000000  0.0000000
Passing Prediction Matrix
head(pass_pred)
             201        202       211       212        221         222
110199 0.6969699  0.9937938 0.9844354  2.393720 0.11492312  1.43355413
110298 0.3796494  2.0993939 0.2195770  3.320521 1.97026154  0.04652605
110397 2.0909097  2.9813811 2.9533060  7.181160 0.34476933  4.30066227
110496 3.9814873  7.2553211 3.7701047 14.084616 3.75308341  5.93951272
110566 1.1920599 -0.6750632 0.7493586  3.331336 0.01272894 19.99186356
110568 2.7878797  3.9751750 3.9377415  9.574880 0.45969246  5.73421652

6.1 - Code for the Recommender

The following code takes a given game situation in terms of team, down, distance, and yard line and returns the top two passing plays and the top two rushing plays.

getPlay <- function(team, down, distance, yardline){
  teamId = teams$TeamId[teams$Teams == team]
  SitId = yardline + 100*distance + 10000*down + 100000*teamId
  SitId = as.character(SitId)
  
  if(SitId %in% rownames(pass_pred)){
    best_pass <- names(sort(pass_pred[SitId,], decreasing = TRUE)[1:2])
    print(paste("Top Pass Choice:", 
                plays$Plays[plays$PlayId == as.numeric(best_pass[1])]))
    print(paste("Predicted Gain: ",
                round(sort(pass_pred[SitId,], decreasing = TRUE)[1]), 
                "Yards"))
    
    print(paste("Second Pass Choice:",
                plays$Plays[plays$PlayId == as.numeric(best_pass[2])]))
    print(paste("Predicted Gain: ",
                round(sort(pass_pred[SitId,], decreasing = TRUE)[2]), 
                "Yards"))
  }
  else{
    best <- sort(apply(pass_pred, 2, function(x) mean(x, na.rm = TRUE)), decreasing = TRUE)
    best_pass <- names(best)
    print(paste("Top Pass Choice:", 
                plays$Plays[plays$PlayId == as.numeric(best_pass[1])]))
    print(paste("Predicted Gain: ",
                round(best[1]), 
                "Yards"))
    
    print(paste("Second Pass Choice:",
                plays$Plays[plays$PlayId == as.numeric(best_pass[2])]))
    print(paste("Predicted Gain: ",
                round(best[2]), 
                "Yards"))
  }
  
  if(SitId %in% rownames(rush_pred)){
    best_rush <- names(sort(rush_pred[SitId,], decreasing = TRUE)[1:2])
    print(paste("Top Run Choice:", 
                plays$Plays[plays$PlayId == as.numeric(best_rush[1])]))
    print(paste("Predicted Gain: ",
                round(sort(rush_pred[SitId,], decreasing = TRUE)[1]), 
                "Yards"))
    
    print(paste("Second Run Choice:",
                plays$Plays[plays$PlayId == as.numeric(best_rush[2])]))
    print(paste("Predicted Gain: ",
                round(sort(rush_pred[SitId,], decreasing = TRUE)[2]), 
                "Yards"))
  }
  else{
    best <- sort(apply(rush_pred, 2, function(x) mean(x, na.rm = TRUE)), decreasing = TRUE)
    best_rush <- names(best)
    print(paste("Top Run Choice:", 
                plays$Plays[plays$PlayId == as.numeric(best_rush[1])]))
    print(paste("Predicted Gain: ",
                round(best[1]), 
                "Yards"))
    
    print(paste("Second Pass Choice:",
                plays$Plays[plays$PlayId == as.numeric(best_rush[2])]))
    print(paste("Predicted Gain: ",
                round(best[2]), 
                "Yards"))
  }
}

6.2 - Sample Output

We now include some of the sample output from running the recommender. We start by including two different teams placed in the same situation. Here we look at the New England Patriots and the Arizona Cardinals in a 1^{st} and 10 from their own 20 yard line. We notice that we get different recommendations for the play which we expect and that we see that Arizona, who has a weaker rushing game, has smaller predicted yards gained in their runs.

getPlay('NE', 1, 10, 20)
[1] "Top Pass Choice: Pass Deep Left"
[1] "Predicted Gain:  31 Yards"
[1] "Second Pass Choice: Pass Deep Middle"
[1] "Predicted Gain:  22 Yards"
[1] "Top Run Choice: Run Right Tackle"
[1] "Predicted Gain:  7 Yards"
[1] "Second Run Choice: Run Left End"
[1] "Predicted Gain:  5 Yards"
getPlay('ARI', 1, 10, 20)
[1] "Top Pass Choice: Pass Deep Middle"
[1] "Predicted Gain:  22 Yards"
[1] "Second Pass Choice: Pass Deep Left"
[1] "Predicted Gain:  18 Yards"
[1] "Top Run Choice: Run Left End"
[1] "Predicted Gain:  4 Yards"
[1] "Second Run Choice: Run Left Tackle"
[1] "Predicted Gain:  4 Yards"

Finally lets look at the same team in a couple of different situations. We will look at the Seattle Seahawks in the following situations. We will first place them in a 2^{nd} and 5 from their own 45 yard line and then look at 1^{st} and 5 from their opponents 5 yard line. Once again we note that the recommended plays are different and that in the second situation near our opponents goal line we get run plays and pass plays with short yardage gains expected.

getPlay('SEA', 2, 5, 45)
[1] "Top Pass Choice: Pass Deep Left"
[1] "Predicted Gain:  18 Yards"
[1] "Second Pass Choice: Pass Deep Middle"
[1] "Predicted Gain:  10 Yards"
[1] "Top Run Choice: Run Center"
[1] "Predicted Gain:  3 Yards"
[1] "Second Run Choice: Run Left Guard"
[1] "Predicted Gain:  1 Yards"
getPlay('SEA', 1, 5, 95)
[1] "Top Pass Choice: Pass Deep Right"
[1] "Predicted Gain:  9 Yards"
[1] "Second Pass Choice: Pass Deep Left"
[1] "Predicted Gain:  7 Yards"
[1] "Top Run Choice: Run Left Guard"
[1] "Predicted Gain:  5 Yards"
[1] "Second Run Choice: Run Right Guard"
[1] "Predicted Gain:  3 Yards"

7 - Conclusions and Future Work

The NFL Play Recommender has proven to be a very interesting proof of concept that modern techniques in recommender systems can work with the relatively limited data from the last 4 years of the NFL to build a working recommendation system. Not only did we get a working system, we also constructed a system that has solid performance in the passing game and decent performance in the rushing game with a fairly straightforward code-base. Our final data sets ended up containing less and 100,000 observations of 7 rush plays and 6 passing plays. We would love to see what is possible if we had a full playbook worth of plays instead of basic directions for the play. We feel like method would allow us to become even more specific in what we recommend.

We also noticed an interesting trait of the passing portion of the recommendation system. The system tends to select the long pass plays. Given that we want the system to recommend the best yardage play in a given situation this makes sense. We hypothesize that there may be a few different ways of handling this problem that we would like to implement in future work. One solution to this problem would b to simply construct a third recommender that handles the short passing plays. This has the drawback of limiting the data set even further and may be unnecessary. The more interesting ways of handling this problem deal with using the incompletions as another data source. We could see using the probability of incompletion for a given pass and team combination serving as a binary variable that would serve to weight the passing plays. This could potentially decrease the power of deeper passes in the system but may serve to only recommend passes with a high probability of completion which takes away some of the gamble of play calling. It could be interesting to apply a weight of 0 or 1 base off of a random probability process but we would need to fully implement such a system to see if it woks. The finally technique could be to simply apply a post recommendation weight based on the probability completion an report this information to user of the system. This would place the decision making process in the hands of the coach and may be the most preferred method.

We really enjoyed getting to work with this data and were excited to see how well the system was able to perform. As a football coach we felt like this system would be a really nice tool to have on the sideline. Anything that can help a play-caller by giving us more information is a great help in the fast paced and stressful environment of football sideline. This project has proven, as much of the class has as well, to be a fascinating look at what can be done with data analytics and the modern statistical techniques.

LS0tDQp0aXRsZTogIkRBVEE2NDMgLSBGaW5hbCBQcm9qZWN0OiBSZWNvbW1lbmRpbmcgUGxheXMgaW4gdGhlIE5GTCINCmF1dGhvcjogIkVyaWsgTnlsYW5kZXIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHNwYXJrbHlyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmBgYA0KDQojIyMgMSAtIEludHJvZHVjdGlvbg0KKioqDQpJbiAxOTc3IHRoZSBpZGVhIG9mIGFwcGx5aW5nIHN0YXRpc3RpY2FsIGFuYWx5c2lzIHRvIEFtZXJpY2FuIHByb2Zlc3Npb25hbCBzcG9ydHMgd2FzIHBvcHVsYXJpemVkIHdpdGggQmlsbCBKYW1lcyBhbmQgdGhlIGludHJvZHVjdGlvbiBvZiBbU2FiZXJtZXRyaWNzXShodHRwOi8vc2Fici5vcmcvc2FiZXJtZXRyaWNzKS4gSG93ZXZlciwgcHJvZmVzc2lvbmFsIHNwb3J0cyB0ZW5kIHRvIGJlIHZlcnkgY29uc2VydmF0aXZlIGFuZCBzbG93IHRvIGNoYW5nZSBhbmQgeW91IHN0aWxsIHNlZSBhcnRpY2xlcyBhbmQgY29tbWVudHMgZnJvbSBwbGF5ZXJzIGFuZCBjb2FjaGVzIG9uIGhvdyB0aGVpciBmYXZvcmVkIHNwb3J0IGNhbiBub3QgYmUgYm9pbGVkIGRvd24gdG8ganVzdCBudW1iZXJzLiBCYXNlYmFsbCBiZWdhbiB0byBleHBsb3JlIHRoZSB1c2Ugb2YgbWV0cmljcyBpbiB0aGUgMTk5MCdzIGFuZCBlYXJseSAyMDAwJ3MgYW5kIG92ZXIgdGhlIGxhc3QgMTAgeWVhcnMgd2UgaGF2ZSBzbG93bHkgc2VlbiB0aGUgTkZMLCBOQkEsIEhOTCwgYW5kIFByb2Zlc3Npb25hbCBTb2NjZXIgTGVhZ3VlcyBiZWdpbiB0byBleHBsb3JlIHRoZSBhcHBsaWNhdGlvbiBvZiBkYXRhIGFuYWx5dGljcyBpbiB0aGVpciBvd24gc3BvcnRzLiBJbiB0aGlzIHByb2plY3Qgd2Ugd2lsbCBhcHBseSB0aGUgdG9vbHMgdGhhdCB3ZSBoYXZlIGxlYXJuZWQgdGhpcyBzZW1lc3RlciB0byBidWlsZCBhIHJlY29tbWVuZGVyIHN5c3RlbSBmb3IgTkZMIHBsYXkgY2FsbGluZy4gDQoNCk15IGludGVyZXN0IGluIHRoaXMgdG9waWMgaXMgYWxzbyBvZiBhIHBlcnNvbmFsIG5hdHVyZS4gSSBoYXZlIHNwZW50IDEzIHllYXJzIGFzIGEgaGlnaCBzY2hvb2wgZm9vdGJhbGwgY29hY2ggYW5kIHNwZW50IDMgeWVhcnMgYXMgYW4gb2ZmZW5zaXZlIGNvb3JkaW5hdG9yIGFuZCBwbGF5IGNhbGxlci4gQ3VycmVudGx5IGNhbGxpbmcgcGxheXMgaW4gZm9vdGJhbGwgaXMgYmFzZWQgb24gYSBtaXh0dXJlIG9mIGhvdXJzIG9mIHZpZGVvIHdvcmssIHRyeWluZyB0byBmaW5kIHRlbmRlbmNpZXMgYW5kIHdlYWtuZXNzZXMgaW4gdGhlIG9wcG9uZW50cyBkZWZlbnNlIGFuZCBhbiBpbnR1aXRpb24gb2YgaG93IHlvdXIgb3Bwb25lbnQgd2lsbCBhZGp1c3QgdGhyb3VnaG91dCB0aGUgZ2FtZS4gVGhpcyBsZWFkcyB0byB0aGUgcHJvZHVjdGlvbiBvZiB0aGUgbWFzc2l2ZSwgYWx0aG91Z2ggbm90IG5lYXJseSBhcyBsYXJnZSBpbiBoaWdoIHNjaG9vbCwgcGxheSBjYWxsaW5nIGNhcmRzIHdpdGggZXZlcnl0aGluZyB0aGF0IHlvdSB0aGluayB0aGF0IHdpbGwgd29yayBmb3IgYW55IGdpdmVuIHNpdHVhdGlvbi4gVGhlIGlkZWEgZm9yIHRoaXMgcmVjb21tZW5kZXIgaXMgdG8gcHJvdmlkZSBhIHR5cGUgb2YgcGxheSBhbmQgYSBkaXJlY3Rpb24gdG8gcnVuIHRoZSBwbGF5IGluLiBGb3IgZXhhbXBsZSB3ZSB3b3VsZCBsaWtlIHRvIGluZGljYXRlIHRoYXQgb24gYSAxXntzdH0gYW5kIDEwIGZyb20gdGhlIDIwIHlhcmQgbGluZSwgYSBnaXZlbiB0ZWFtIHNob3VsZCBydW4gdGhlIGJhbGwgb3ZlciB0aGUgcmlnaHQgdGFja2xlIG9yIHRocm93IHRoZSBiYWxsIHRvIHRoZSBkZWVwIG1pZGRsZSBvZiB0aGUgZmllbGQuDQoNCg0KIyMjIDIgLSBEYXRhIGFuZCBNZXRob2RvbG9neQ0KKioqDQpGb3IgdGhpcyByZWNvbW1lbmRlciBzeXN0ZW0gd2Ugd2lsbCBiZSB1c2luZyB0aGUgcGxheS1ieS1wbGF5IGRhdGEgZm9yIHRoZSAyMDEzIHRocm91Z2ggMjAxNiBORkwgc2Vhc29ucy4gVGhlIGRhdGEgdGhhdCB3ZSB3aWxsIGJlIHVzaW5nIGlzIGF2YWlsYWJsZSBhdCBbTkZMIFNhdmFudF0oaHR0cDovL25mbHNhdmFudC5jb20vYWJvdXQucGhwKSBhbmQgY29udGFpbnMgZXZlcnkgcGxheSBmb3IgZWFjaCBvZiB0aGUgc2Vhc29ucy4gVGhlIGRhdGEgc2V0IGNvbnRhaW5zIDQxIGRpZmZlcmVudCB2YXJpYWJsZXMgZGVhbGluZyB3aXRoIHZhcmlvdXMgYXNwZWN0cyBvZiB0aGUgZ2FtZS4gV2Ugd2lsbCBwcmVkb21pbmF0ZWx5IGJlIHdvcmtpbmcgd2l0aCB0aGUgdmFyaWFibGVzIHRoYXQgZ2l2ZSB1cyBpbmZvcm1hdGlvbiBvbiB0aGUgZ2FtZSBzaXR1YXRpb24sIHRoZSBwbGF5IHR5cGUgYW5kIHRoZSB5YXJkcyBnYWluZWQuIFRoZSB3b3JrIHRvIHByZXBhcmUgdGhlIGRhdGEgaXMgbm90IGluY2x1ZGVkIGluIHRoaXMgcGFwZXIgYnV0IGludm9sdmVkIGNyZWF0aW5nIG51bWVyaWMgdmFyaWFibGVzIHRvIGRlc2NyaWJlIHRoZSB2YXJpb3VzIGNhdGVnb3JpZXMgYW5kIHBsYXkgdHlwZXMgYW5kIG1lcmdpbmcgdGhlIHZhcmlvdXMgeWVhcnMgdG9nZXRoZXIuIFRoZXJlIHdhcyBhbHNvIGEgbnVtYmVyIG9mIHN0ZXBzIG5lZWRlZCB0byBjbGVhbiB1cCBpc3N1ZXMgaW4gdGhlIGNzdiBmaWxlcy4gVGhlIGZ1bGx5IGNsZWFuZWQgZGF0YSBzZXQgaXMgbG9hZGVkIGJlbG93LCBpdCBjb250YWlucyAxMjM4MTQgZGlmZmVyZW50IHBsYXlzIHdoaWNoIHdlIHdpbGwgZW5kIHVwIHNlZ21lbnRpbmcgaW50byBydW4gYW5kIHBhc3MgcGxheXMuIFdlIGFsc28gbG9hZCB0d28gc21hbGwgY3N2IGZpbGVzIHRoYXQgY29udGFpbiB0aGUgdW5pcXVlIGlkZW50aWZpZXIgZm9yIGVhY2ggb2YgdGhlIHRlYW1zIGFuZCB0aGUgdW5pcXVlIGlkZW50aWZpZXJzIGZvciBlYWNoIG9mIHRoZSBwbGF5cy4NCg0KYGBge3J9DQojIFJlYWRpbmcgdGhhdCBEYXRhDQpwYnAgPC0gcmVhZC5jc3YoIi4uL2RhdGEvbmZsL3BsYXlieXBsYXkuY3N2IiwgaGVhZGVyID0gVFJVRSkNCnRlYW1zIDwtIHJlYWQuY3N2KCIuLi9kYXRhL25mbC9UZWFtcy5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpwbGF5cyA8LSByZWFkLmNzdigiLi4vZGF0YS9uZmwvUGxheXMuY3N2IiwgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQpoZWFkKHBicCkNCmBgYA0KDQpUaGUgaGVhcnQgb2Ygb3VyIHJlY29tbWVuZGVyIHdpbGwgYmUgYSBkZWNvbXBvc2l0aW9uIG9mIHRoZSB1c2VyLWl0ZW0gbWF0cml4IGludG8gYSB1c2VyIGZhY3RvciBtYXRyaXggYW5kIGFuIGl0ZW0gZmFjdG9yIG1hdHJpeCB1c2luZyBBbHRlcm5hdGluZyBMZWFzdCBTcXVhcmVzIG1hdHJpeCBmYWN0b3JpemF0aW9uLiBUaGlzIHdpbGwgYmUgY2FsY3VsYXRlZCB0d2ljZSwgb25jZSBmb3IgcnVzaGluZyBwbGF5cyBhbmQgYWdhaW4gZm9yIHBhc3NpbmcgcGxheXMuIFRvIGZhY2lsaXRhdGUgdGhpcywgd2Ugd2lsbCBiZSB1c2luZyB0aGUgc3Bhcmsgc3lzdGVtIGFuZCB0aGUgKnNwYXJrbHlyKiBwYWNrYWdlIGluIFIuIFdlIGJlZ2luIHRoaXMgcHJvY2VzcyBieSBjb25uZWN0aW5nIHRvIHNwYXJrLg0KYGBge3J9DQojIEdlbmVyYXRpbmcgdGhlIHNwYXJrIGNvbm5lY3Rpb24NCnNjIDwtIHNwYXJrX2Nvbm5lY3QobWFzdGVyID0gImxvY2FsIikgDQpgYGANCg0KIyMjIDMgLSBQcmVwYXJpbmcgdGhlIERhdGEgZm9yIG91ciBSZWNvbW1lbmRlcnMNCioqKg0KR2l2ZW4gdGhhdCBvdXIgc3lzdGVtIGlzIGRlc2lnbmVkIHRvIHJlY29tbWVuZCBwbGF5cyBiYXNlZCBvZmYgb2YgdGhlIG1heGltdW0gbnVtYmVyIG9mIHlhcmRzIHRoYXQgY2FuIGJlIGdhaW5lZCwgd2UgcXVpY2tseSByZWFsaXplZCB0aGF0IHRoZSBzeXN0ZW0gd291bGQgbmF0dXJhbGx5IHNldHRsZSBvbiBwYXNzIHBsYXlzLiBUbyBhdm9pZCB0aGlzIGlzc3VlIHdlIGRlY2lkZWQgdG8gYnVpbGQgdHdvIHNlcGFyYXRlIHVzZXItaXRlbSBtYXRyaWNlcywgb25lIGZvciBwYXNzZXMgYW5kIG9uZSBmb3IgcnVucy4gVGhpcyB3aWxsIGFsbG93IHVzIHRvIHJlY29tbWVuZCB0aGUgYmVzdCBwYXNzIHBsYXkgYW5kIHRoZSBiZXN0IHJ1biBwbGF5IGZvciBhIGdpdmVuIGdhbWUgc2l0dWF0aW9uLiBUbyBmYWNpbGl0YXRlIHRoaXMgd2Ugd2lsbCBzZXBhcmF0ZSB0aGUgZGF0YSBpbnRvIHR3byBzZXBhcmF0ZSBkYXRhIGZyYW1lcyBhbmQgZXhwbG9yZSBvdXIgdGFyZ2V0IHZhcmlhYmxlIG9mIHlhcmRzIGdhaW5lZCBpbiBlYWNoIG9mIHRoZSBzZXRzLg0KDQoNCiMjIyMgMy4xIC0gRXhwbG9yaW5nIHRoZSBSdXNoaW5nIFBsYXlzDQpXZSBiZWdpbiBieSBleHBsb3JpbmcgdGhlIHJ1c2hpbmcgcGxheXMgdGhhdCBoYXZlIG9jY3VycmVkIG92ZXIgdGhlIGxhc3QgNCB5ZWFycyBpbiB0aGUgTkZMLiBXZSBzZWUgdGhhdCB0aGVyZSBoYXZlIGJlZW4gNTEsOTcyIHJ1c2hpbmcgcGxheXMgd2l0aCBhbiBhdmVyYWdlIHJ1c2ggb2YgNC4zOCB5YXJkcy4gV2Ugbm90ZSB0aGF0IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gZm9yIHRoZSBwbGF5cyBpcyBsYXJnZXIgdGhlbiB0aGUgbWVhbi4gVGhpcyBpcyByZWxhdGVkIHRvIHRoZSBleHBsb3NpdmUgcnVzaGluZyBwbGF5cyB3aGljaCB3ZSB3aWxsIGVuZCB1cCByZW1vdmluZyBmcm9tIHRoZSBkYXRhIHNldC4gT3VyIGZpcnN0IGF0dGVtcHQgYXQgYSByZWNvbW1lbmRhdGlvbiBzeXN0ZW0gbGVmdCB0aGVzZSBpbiBidXQgd2UgZm91bmQgdGhhdCB0aGV5IGluZmx1ZW5jZWQgdG8gc3lzdGVtIHRvIG92ZXJlc3RpbWF0ZSB0aGUgeWFyZHMgcGVyIHJ1c2guIFdlIGNhbiBzZWUgdGhlIGV4cGxvc2l2ZSBwbGF5cyBhcyB0aGUgbG9uZyB0YWlsIG9mIHRoZSBkaXN0cmlidXRpb24uDQpgYGB7cn0NCiMgU3Vic2V0aW5nIHRoZSBkYXRhIGludG8gcnVuIGFuZCBwYXNzDQpwYnBfcnVzaCA8LSBwYnAgJT4lDQogIGZpbHRlcihJc1J1c2ggPT0gMSkNCg0KcGFzdGUoIk51bWJlciBvZiBQbGF5czoiLCBsZW5ndGgocGJwX3J1c2gkWWFyZHMpLCANCiAgICAgICIsIEF2ZXJhZ2UgWWFyZHMgcGVyIEF0dGVtcHQ6Iiwgcm91bmQobWVhbihwYnBfcnVzaCRZYXJkcyksIDIpLA0KICAgICAgIiwgU3RhbmRhcmQgRGV2aWF0aW9uOiIsIHJvdW5kKHNkKHBicF9ydXNoJFlhcmRzKSwgMikpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHBicF9ydXNoLCBhZXMoWWFyZHMpKSArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9ICJibHVlIikgKyANCiAgZ2d0aXRsZSgiSGlzdG9ncmFtIG9mIFJ1c2ggWWFyZGFnZSIpDQpgYGANCg0KDQojIyMjIDMuMiBFeHBsb3JpbmcgdGhlIFBhc3MgUGxheXMNCldlIG5leHQgZXhwbG9yZSB0aGUgcGFzc2luZyBwbGF5cyBmcm9tIHRoZSBsYXN0IDQgeWVhcnMgb2YgdGhlIE5GTCBhbmQgc2VlIHRoYXQgdGhlcmUgaGF2ZSBiZWVuIDcxLDg0MiBwYXNzIHBsYXlzLCBob3dldmVyIHRoZXJlIGlzIGFuIGludGVyZXN0aW5nIGZlYXR1cmUgb2YgdGhlIGRhdGEgc2V0LiBXaGVuIHdlIGdyYXBoIHRoZSBwYXNzIHlhcmRhZ2Ugd2Ugbm90aWNlIGEgbWFzc2l2ZSBzcGlrZSBhdCAwLiBXaGlsZSB0aGVyZSBhcmUgc29tZSBwYXNzZXMgdGhhdCBlbmQgdXAgYmVpbmcgemVybyB5YXJkcywgd2Ugbm90ZSB0aGF0IHRoZSBtYWpvcml0eSBvZiB0aGVzZSBwbGF5cyBhcmUgaW5jb21wbGV0aW9ucy4gV2UgZGVjaWRlZCB0aGF0IHdlIHNob3VsZCByZW1vdmUgdGhlc2UgcGxheXMgdG8gZ2l2ZSB0aGUgZGF0YSBhIG1vcmUgbm9ybWFsIHNoYXBlLiBXZSBkbyB3YW50IHRvIGluY2x1ZGUgdGhlIGluZm9ybWF0aW9uIGZyb20gdGhlIGNvbXBsZXRpb24gcGVyY2VudGFnZSBmb3IgZWFjaCBwYXNzIGluIHRoZSByZWNvbW1lbmRlciBhbmQgd2Ugd2lsbCBkaXNjdXNzIHBvc3NpYmxlIG1ldGhvZHMgZm9yIHRoaXMgbGF0ZXIgaW4gdGhlIHBhcGVyLiANCmBgYHtyfQ0KcGJwX3Bhc3MgPC0gcGJwICU+JQ0KICBmaWx0ZXIoSXNQYXNzID09IDEpDQoNCmdncGxvdChkYXRhID0gcGJwX3Bhc3MsIGFlcyhZYXJkcykpICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gImJsdWUiKSArIA0KICBnZ3RpdGxlKCJIaXN0b2dyYW0gb2YgUGFzcyBZYXJkYWdlIikNCmBgYA0KDQoNCkluIHRoZSBncmFwaCBiZWxvdyB3ZSBzZWUgdGhhdCByZW1vdmluZyB0aGUgaW5jb21wbGV0aW9ucyBjcmVhdGVzIGEgbW9yZSBub3JtYWwgbG9va2luZyBkYXRhIHNldC4gV2Ugd2lsbCBhbHNvIG5vdGljZSB0aGF0IHRoZSBkYXRhIGhhcyBhIGxvbmcgdGFpbCB0aGF0IGluZGljYXRlcyB0aGF0IHRoZXJlIGFyZSBhIG51bWJlciBvZiBleHBsb3NpdmUgcGxheXMgaW4gdGhlIGRhdGEgc2V0IHRoYXQgd2Ugd2lsbCBlbmQgdXAgcmVtb3ZpbmcgdG8gaW1wcm92ZSB0aGUgcmVjb21tZW5kYXRpb25zLiBSZW1vdmluZyB0aGUgaW5jb21wbGV0aW9ucyByZWR1Y2VzIHRoZSB0b3RhbCBudW1iZXIgb2YgcGFzcyBwbGF5cyB0byA0Niw3NDAgcGxheXMgd2l0aCBhbiBhdmVyYWdlIHlhcmRhZ2Ugb2YgMTEuNjQgeWFyZHMuDQpgYGB7cn0NCnBicF9wYXNzIDwtIHBicF9wYXNzICU+JQ0KICBmaWx0ZXIoSXNJbmNvbXBsZXRlID09IDApICU+JQ0KICBmaWx0ZXIoUGxheSAhPSAyMDApDQoNCmdncGxvdChkYXRhID0gcGJwX3Bhc3MsIGFlcyhZYXJkcykpICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gImJsdWUiKSArIA0KICBnZ3RpdGxlKCJIaXN0b2dyYW0gb2YgUGFzcyBZYXJkYWdlIChJbmNvbXBsZXRlIFBhc3NlcyBSZW1vdmVkKSIpDQpgYGANCg0KYGBge3J9DQpwYXN0ZSgiTnVtYmVyIG9mIFBsYXlzOiIsIGxlbmd0aChwYnBfcGFzcyRZYXJkcyksIA0KICAgICAgIiwgQXZlcmFnZSBZYXJkcyBwZXIgQXR0ZW1wdDoiLCByb3VuZChtZWFuKHBicF9wYXNzJFlhcmRzKSwgMiksDQogICAgICAiLCBTdGFuZGFyZCBEZXZpYXRpb246Iiwgcm91bmQoc2QocGJwX3Bhc3MkWWFyZHMpLCAyKSkNCmBgYA0KDQojIyMgNCAtIENyZWF0aW5nIFVzZXItSXRlbSBEYXRhIEZyYW1lcyBhbmQgTG9hZGluZyBpbnRvIFNwYXJrDQoqKioNCk5vdyB0aGF0IHdlIGhhdmUgc29tZSBpZGVhIG9mIHdoYXQgaXMgZ29pbmcgb24gaW4gcnVzaGluZyBhbiBwYXNzaW5nIHBsYXlzIGZvciB0aGUgbGFzdCA0IHllYXJzIHdlIGNhbiBidWlsZCBvdXIgdXNlciBpdGVtIGRhdGEgZnJhbWVzIGZvciB0aGUgdHdvIHBsYXkgdHlwZXMuIFdlIHN0YXJ0IGJ5IHJlZHVjaW5nIGVhY2ggb2YgdGhlIGRhdGEgc2V0cyB0byB0aGUgc2l0dWF0aW9uIGlkZW50aWZpZXJzIChTaXRJZCksIHBsYXkgaWRlbnRpZmllcnMgKFBsYXlJZCksIGFuZCB0aGUgWWFyZCBnYWluZWQuIFdlIG5leHQgbG9hZCBlYWNoIG9mIHRoZSBkYXRhIGZyYW1lcyBpbnRvIG91ciBzcGFyayBpbnN0YW5jZSBhbmQgb25jZSB3ZSBoYXZlIHRoZSBkYXRhIGxvYWRlZCB3ZSB1c2Ugc29tZSBvZiB0aGUgYnVpbHQgaW4gKmRwbHlyKiBmZWF0dXJlcyBpbiAqc3BhcmtseXIqIHRvIHJlbW92ZSB0aGUgZXhwbG9zaXZlIHBsYXlzIGZyb20gZWFjaCBvZiB0aGUgZGF0YSBzZXRzLiBXaGlsZSB0aGVyZSBhcmUgdmFyaW91cyBkZWZpbml0aW9ucyBmb3IgYW4gZXhwbG9zaXZlIHBsYXksIHdlIGhhdmUgZWxlY3RlZCB0byByZW1vdmUgYWxsIHJ1bm5pbmcgcGxheXMgbW9yZSB0aGFuIDE1IHlhcmRzIGFuZCBhbGwgcGFzc2luZyBwbGF5cyBtb3JlIHRoYW4gMzUgeWFyZHMuIA0KYGBge3J9DQojIENyZWF0aW5nIG91ciBEYXRhIEZyYW1lcyBmb3IgdGhlIHNwYXJrIGluc3RhbmNlDQpydXNoIDwtIHBicF9ydXNoICU+JQ0KICBzZWxlY3QoU2l0SWQsIFBsYXksIFlhcmRzKQ0KDQpwYXNzIDwtIHBicF9wYXNzICU+JQ0KICBzZWxlY3QoU2l0SWQsIFBsYXksIFlhcmRzKQ0KDQojIFNlbmRpbmcgdGhlIERhdGEgRnJhbWVzIHRvIHNwYXJrDQpydXNoX3RibCA8LSBzZGZfY29weV90byhzYywgcnVzaCwgb3ZlcndyaXRlID0gVFJVRSkNCg0KcGFzc190YmwgPC0gc2RmX2NvcHlfdG8oc2MsIHBhc3MsIG92ZXJ3cml0ZSA9IFRSVUUpDQoNCiMgUmVtb3ZpbmcgdGhlIGV4cGxvc2l2ZSBwbGF5cw0KcnVzaF9zbWFsbCA8LSBydXNoX3RibCAlPiUNCiAgZmlsdGVyKFlhcmRzIDw9IDE1KQ0KDQpwYXNzX3NtYWxsIDwtIHBhc3NfdGJsICU+JQ0KICBmaWx0ZXIoWWFyZHMgPD0gMzUpDQoNCnJ1c2hfc21hbGwNCmBgYA0KDQpgYGB7cn0NCnBhc3Nfc21hbGwNCmBgYA0KDQojIyMgNSAtIEJ1aWxkaW5nIHRoZSBSZWNvbW1lbmRlcg0KKioqDQpOb3cgdGhhdCB3ZSBoYXZlIG91ciBkYXRhIGluIHdlIHVzZSB0aGUgZm9sbG93aW5nIGNvbW1hbmRzIHRvIGNvbXB1dGUgdGhlIEFsdGVybmF0aW5nIExlYXN0IFNxdWFyZXMgbW9kZWwgZm9yIHRoZSBydXNoaW5nIGFuZCBwYXNzaW5nIHBsYXlzLiBUaGVzZSB3aWxsIHJlc3VsdCBpbiB0d28gbW9kZWwgb2JqZWN0IHRoYXQgY29udGFpbiB0aGUgZmFjdG9yIG1hdHJpY2VzIGZvciB0aGUgdXNlcnMgKFNpdElkKSBhbmQgaXRlbXMgKFBsYXlJZCkgb25jZSB3ZSBidWlsZCB0aGUgZmFjdG9yIG1hdHJpY2VzIHdlIHdpbGwgdXNlIHRoZW0gdG8gcHJlZGljdCB0aGUgcHJlZGljdGVkIHlhcmRzIGZvciBlYWNoIG9mIHRoZSBTaXRJZCBhbmQgUGxheUlkIHBhaXJzLg0KDQojIyMjIDUuMSBDb25zdHJ1Y3RpbmcgdGhlIEFMUyBNb2RlbHMNClRoZSBjb2RlIGJlbG93IGdlbmVyYXRlcyB0aGUgQUxTIGZhY3Rvcml6YXRpb24gaW4gc3BhcmsuDQpgYGB7cn0NCiMgQ3JlYXRpbmcgdGhlIG1vZGVscyBmb3IgZWFjaCBkb3duIGFuZCBydW4gcGFzcw0KcnVzaF9tb2RlbCA8LSBtbF9hbHNfZmFjdG9yaXphdGlvbihydXNoX3NtYWxsLCByYXRpbmcuY29sdW1uID0gIllhcmRzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXIuY29sdW1uID0gIlNpdElkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS5jb2x1bW4gPSAiUGxheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXIubWF4ID0gNSwgcmVndWxhcml6YXRpb24ucGFyYW1ldGVyID0gMC4wMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wbGljaXQucHJlZmVyZW5jZXMgPSBUUlVFLCBhbHBoYSA9IDEuMCkNCg0KcGFzc19tb2RlbCA8LSBtbF9hbHNfZmFjdG9yaXphdGlvbihwYXNzX3NtYWxsLCByYXRpbmcuY29sdW1uID0gIllhcmRzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXIuY29sdW1uID0gIlNpdElkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS5jb2x1bW4gPSAiUGxheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXIubWF4ID0gNSwgcmVndWxhcml6YXRpb24ucGFyYW1ldGVyID0gMC4wMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wbGljaXQucHJlZmVyZW5jZXMgPSBUUlVFLCBhbHBoYSA9IDEuMCkNCmBgYA0KDQpgYGB7cn0NCiMgQ3JlYXRpbmcgdGhlIG1vZGVscyBmb3IgZWFjaCBkb3duIGFuZCBydW4gcGFzcw0KcnVzaF9tb2RlbCA8LSBtbF9hbHNfZmFjdG9yaXphdGlvbihydXNoX3NtYWxsLCByYXRpbmcuY29sdW1uID0gIllhcmRzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXIuY29sdW1uID0gIlNpdElkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS5jb2x1bW4gPSAiUGxheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXIubWF4ID0gNSwgcmVndWxhcml6YXRpb24ucGFyYW1ldGVyID0gMC4wMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wbGljaXQucHJlZmVyZW5jZXMgPSBUUlVFLCBhbHBoYSA9IDEuMCkNCg0KcGFzc19tb2RlbCA8LSBtbF9hbHNfZmFjdG9yaXphdGlvbihwYXNzX3NtYWxsLCByYXRpbmcuY29sdW1uID0gIllhcmRzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXIuY29sdW1uID0gIlNpdElkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS5jb2x1bW4gPSAiUGxheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXIubWF4ID0gNSwgcmVndWxhcml6YXRpb24ucGFyYW1ldGVyID0gMC4wMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wbGljaXQucHJlZmVyZW5jZXMgPSBUUlVFLCBhbHBoYSA9IDEuMCkNCmBgYA0KIyMjIyA1LjIgQ2FsY3VsYXRpbmcgdGhlIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yDQpXZSBoYXZlIGVsZWN0ZWQgdG8gdXNlIHRoZSByb290IG1lYW4gc3F1YXJlZCBlcnJvciAoUk1TRSkgYXMgYSBtZWFzdXJlIG9mIHRoZSBhY2N1cmFjeSBvZiBvdXIgcmVjb21tZW5kZXIgdGhpcyBtZWFzdXJlIHdpbGwgZ2l2ZSB1cyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgYXZlcmFnZSBudW1iZXIgb2YgeWFyZHMgdGhhdCBlYWNoIG9mIHRoZSBwbGF5cyBpcyBvZmYgYnkuDQoNCiMjIyMjIENhbGN1bGF0aW5nIHRoZSBSTVNFIGZvciB0aGUgUnVzaGluZyBQbGF5cw0KV2UgZmluZCB0aGF0IHRoZSBydXNoaW5nIHJlY29tbWVuZGVyIGhhcyBhIFJNU0Ugb2YgMi4wMi4gVGhpcyBpbmRpY2F0ZXMgdGhlIGF2ZXJhZ2UgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB5YXJkYWdlIGZyb20gdGhlIHJlY29tbWVuZGVyIGFuZCB0aGUgeWFyZGFnZSBmcm9tIHRoZSBhY3R1YWwgcGxheS4gR2l2ZW4gdGhhdCB0aGUgYXZlcmFnZSBydXNoIHBsYXkgaXMgb25seSA0IHlhcmRzIHRoZSB0d28geWFyZCBkaWZmZXJlbmNlIGNvdWxkIGxlYWQgdG8gc29tZSBwb29yIHJlY29tbWVuZGF0aW9ucy4gV2UgcmVjb2duaXplIHRoYXQgdGhpcyBpcyBhbiBhcmVhIHRoYXQgd291bGQgbmVlZCBjb250aW51ZWQgd29yayBiZWZvcmUgdGhlIHN5c3RlbSB3b3VsZCBiZSB1c2VkIGluIGEgbGl2ZSBzaXR1YXRpb24uDQpgYGB7ciBlY2hvPVRSVUV9DQpydXNoX3NtYWxsX3ByZWQgPC0gcnVzaF9tb2RlbCQubW9kZWwgJT4lDQogIGludm9rZSgidHJhbnNmb3JtIiwgc3BhcmtfZGF0YWZyYW1lKHJ1c2hfc21hbGwpKSAlPiUNCiAgY29sbGVjdCgpDQpgYGANCg0KYGBge3J9DQpzcXJ0KG1lYW4od2l0aChydXNoX3NtYWxsX3ByZWQsIHByZWRpY3Rpb24tWWFyZHMpXjIpKQ0KYGBgDQoNCiMjIyMjIENhbGN1bGF0aW5nIHRoZSBSTVNFIGZvciB0aGUgUGFzc2luZyBQbGF5cw0KV2UgZmluZCB0aGF0IHRoZSBwYXNzaW5nIHJlY29tbWVuZGVyIGhhcyBhIFJNU0Ugb2YgMi45OS4gR2l2ZW4gdGhhdCB0aGUgdHlwaWNhbCBwYXNzIHBsYXkgaXMgMTEuNjQgeWFyZHMgd2UgZmluZCB0aGlzIGRpZmZlcmVuY2UgaXMgbXVjaCBtb3JlIHJlYXNvbmFibGUgZm9yIG91ciBzeXN0ZW0uIA0KYGBge3IgZWNobz1UUlVFfQ0KcGFzc19zbWFsbF9wcmVkIDwtIHBhc3NfbW9kZWwkLm1vZGVsICU+JQ0KICBpbnZva2UoInRyYW5zZm9ybSIsIHNwYXJrX2RhdGFmcmFtZShwYXNzX3NtYWxsKSkgJT4lDQogIGNvbGxlY3QoKQ0KYGBgDQoNCmBgYHtyfQ0Kc3FydChtZWFuKHdpdGgocGFzc19zbWFsbF9wcmVkLCBwcmVkaWN0aW9uLVlhcmRzKV4yKSkNCmBgYA0KDQojIyMgNiAtIEJ1aWxkaW5nIHRoZSBORkwgUGxheSBSZWNvbW1lbmRlcg0KKioqDQpOb3cgdGhhdCB3ZSBoYXZlIG91ciBmYWN0b3JpemF0aW9uIG1hdHJpY2VzIGZvciB0aGUgcnVzaGluZyBhbiBwYXNzaW5nIHBsYXlzIHdlIGNhbiBub3cgYnVpbGQgb3VyIHJlY29tbWVuZGF0aW9uIHN5c3RlbS4gRm9yIHRoaXMgcG9ydGlvbiBvZiB0aGUgcHJvamVjdCB3ZSB3aWxsIHVzZSB0aGUgZm9sbG93aW5nIG1ldGhvZG9sb2d5LiBGaXJzdCwgd2Ugd2lsbCBnZW5lcmF0ZSBhIHVzZXIgaXRlbSBtYXRyaXggZm9yIGJvdGggdGhlIHBhc3NpbmcgcGxheXMgYW5kIHRoZSBydXNoaW5nIHBsYXlzLiBzZWNvbmQsIHdlIHdpbGwgdGhlbiBidWlsZCBvdXIgc3lzdGVtIHRvIGZpcnN0IGNoZWNrIHRvIHNlZSBpZiBhIGdpdmVuIGdhbWUgc2l0dWF0aW9uIGlzIGluIHRoZSByZWNvbW1lbmRlci4gSWYgaXQgaXMsIHdlIHdpbGwgcmV0dXJuIHRoZSB0b3AgdHdvIHBhc3MgcGxheXMgYW5kIHRoZSB0b3AgdHdvIHJ1biBwbGF5cyBmb3IgdGhlIGdpdmVuIHNpdHVhdGlvbi4gSWYgd2UgaGF2ZSBub3Qgc2VlbiB0aGUgZ2FtZSBzaXR1YXRpb24sIGllIGEgY29sZCBzdGFydCB0eXBlIHNpdHVhdGlvbiwgdGhlbiB3ZSB3aWxsIHJldHVybiB0aGUgYmVzdCB0d28gcGFzcyBwbGF5cyBhcyBqdWRnZWQgYnkgdGhlaXIgYXZlcmFnZSB5YXJkYWdlIGdhaW5lZCBhbmQgdGhlIGJlc3QgdHdvIHJ1biBwbGF5cyB1c2luZyB0aGUgc2FtZSBjcml0ZXJpYS4gRmluYWxseSwgd2Ugb3V0cHV0IHRoZXNlIHBsYXlzIGFsb25nIHdpdGggdGhlIHByZWRpY3RlZCBudW1iZXIgb2YgeWFyZHMgdGhhdCBwbGF5IGlzIGV4cGVjdGVkIHRvIGdhaW4gaW4gdGhlIGdpdmVuIGdhbWUgc2l0dWF0aW9uLiBXZSBpbmNsdWRlIHNhbXBsZSBvdXRwdXQgZnJvbSB0aGUgcnVzaGluZyBhbmQgcGFzc2luZyBwcmVkaWN0aW9uIG1hdHJpY2VzLg0KDQojIyMjIyBSdXNoaW5nIFByZWRpY3Rpb24gTWF0cml4DQpgYGB7cn0NCiMgRXh0cmFjdGluZyB0aGUgVXNlciBhbmQgSXRlbSBmYWN0b3IgbWF0cmljZXMgZm9yIHJ1c2hlcw0KcnVzaF9zaXRfbWF0cml4IDwtIGFzLm1hdHJpeChydXNoX21vZGVsJHVzZXIuZmFjdG9yc1ssLTFdKQ0Kcm93bmFtZXMocnVzaF9zaXRfbWF0cml4KSA8LSBhcy5tYXRyaXgocnVzaF9tb2RlbCR1c2VyLmZhY3RvcnNbMV0pDQpydXNoX3BsYXlfbWF0cml4IDwtIGFzLm1hdHJpeChydXNoX21vZGVsJGl0ZW0uZmFjdG9yc1ssLTFdKQ0Kcm93bmFtZXMocnVzaF9wbGF5X21hdHJpeCkgPC0gYXMubWF0cml4KHJ1c2hfbW9kZWwkaXRlbS5mYWN0b3JzWzFdKQ0KDQojIENhbGN1bGF0aW5nIHRoZSBwcmVkaWN0ZWQgcmF0aW5ncyBtYXRyaXgNCnJ1c2hfcHJlZCA8LSBydXNoX3NpdF9tYXRyaXggJSolIHQocnVzaF9wbGF5X21hdHJpeCkNCg0KaGVhZChydXNoX3ByZWQpDQpgYGANCg0KIyMjIyMgUGFzc2luZyBQcmVkaWN0aW9uIE1hdHJpeA0KYGBge3J9DQojIEV4dHJhY3RpbmcgdGhlIFVzZXIgYW5kIEl0ZW0gZmFjdG9yIG1hdHJpY2VzIGZvciBwYXNzZXMNCnBhc3Nfc2l0X21hdHJpeCA8LSBhcy5tYXRyaXgocGFzc19tb2RlbCR1c2VyLmZhY3RvcnNbLC0xXSkNCnJvd25hbWVzKHBhc3Nfc2l0X21hdHJpeCkgPC0gYXMubWF0cml4KHBhc3NfbW9kZWwkdXNlci5mYWN0b3JzWzFdKQ0KcGFzc19wbGF5X21hdHJpeCA8LSBhcy5tYXRyaXgocGFzc19tb2RlbCRpdGVtLmZhY3RvcnNbLC0xXSkNCnJvd25hbWVzKHBhc3NfcGxheV9tYXRyaXgpIDwtIGFzLm1hdHJpeChwYXNzX21vZGVsJGl0ZW0uZmFjdG9yc1sxXSkNCg0KIyBDYWxjdWxhdGluZyB0aGUgcHJlZGljdGVkIHJhdGluZ3MgbWF0cml4DQpwYXNzX3ByZWQgPC0gcGFzc19zaXRfbWF0cml4ICUqJSB0KHBhc3NfcGxheV9tYXRyaXgpDQoNCmhlYWQocGFzc19wcmVkKQ0KYGBgDQoNCiMjIyMgNi4xIC0gQ29kZSBmb3IgdGhlIFJlY29tbWVuZGVyDQpUaGUgZm9sbG93aW5nIGNvZGUgdGFrZXMgYSBnaXZlbiBnYW1lIHNpdHVhdGlvbiBpbiB0ZXJtcyBvZiB0ZWFtLCBkb3duLCBkaXN0YW5jZSwgYW5kIHlhcmQgbGluZSBhbmQgcmV0dXJucyB0aGUgdG9wIHR3byBwYXNzaW5nIHBsYXlzIGFuZCB0aGUgdG9wIHR3byBydXNoaW5nIHBsYXlzLg0KYGBge3J9DQpnZXRQbGF5IDwtIGZ1bmN0aW9uKHRlYW0sIGRvd24sIGRpc3RhbmNlLCB5YXJkbGluZSl7DQogIHRlYW1JZCA9IHRlYW1zJFRlYW1JZFt0ZWFtcyRUZWFtcyA9PSB0ZWFtXQ0KICBTaXRJZCA9IHlhcmRsaW5lICsgMTAwKmRpc3RhbmNlICsgMTAwMDAqZG93biArIDEwMDAwMCp0ZWFtSWQNCiAgU2l0SWQgPSBhcy5jaGFyYWN0ZXIoU2l0SWQpDQogIA0KICBpZihTaXRJZCAlaW4lIHJvd25hbWVzKHBhc3NfcHJlZCkpew0KICAgIGJlc3RfcGFzcyA8LSBuYW1lcyhzb3J0KHBhc3NfcHJlZFtTaXRJZCxdLCBkZWNyZWFzaW5nID0gVFJVRSlbMToyXSkNCiAgICBwcmludChwYXN0ZSgiVG9wIFBhc3MgQ2hvaWNlOiIsIA0KICAgICAgICAgICAgICAgIHBsYXlzJFBsYXlzW3BsYXlzJFBsYXlJZCA9PSBhcy5udW1lcmljKGJlc3RfcGFzc1sxXSldKSkNCiAgICBwcmludChwYXN0ZSgiUHJlZGljdGVkIEdhaW46ICIsDQogICAgICAgICAgICAgICAgcm91bmQoc29ydChwYXNzX3ByZWRbU2l0SWQsXSwgZGVjcmVhc2luZyA9IFRSVUUpWzFdKSwgDQogICAgICAgICAgICAgICAgIllhcmRzIikpDQogICAgDQogICAgcHJpbnQocGFzdGUoIlNlY29uZCBQYXNzIENob2ljZToiLA0KICAgICAgICAgICAgICAgIHBsYXlzJFBsYXlzW3BsYXlzJFBsYXlJZCA9PSBhcy5udW1lcmljKGJlc3RfcGFzc1syXSldKSkNCiAgICBwcmludChwYXN0ZSgiUHJlZGljdGVkIEdhaW46ICIsDQogICAgICAgICAgICAgICAgcm91bmQoc29ydChwYXNzX3ByZWRbU2l0SWQsXSwgZGVjcmVhc2luZyA9IFRSVUUpWzJdKSwgDQogICAgICAgICAgICAgICAgIllhcmRzIikpDQogIH0NCiAgZWxzZXsNCiAgICBiZXN0IDwtIHNvcnQoYXBwbHkocGFzc19wcmVkLCAyLCBmdW5jdGlvbih4KSBtZWFuKHgsIG5hLnJtID0gVFJVRSkpLCBkZWNyZWFzaW5nID0gVFJVRSkNCiAgICBiZXN0X3Bhc3MgPC0gbmFtZXMoYmVzdCkNCiAgICBwcmludChwYXN0ZSgiVG9wIFBhc3MgQ2hvaWNlOiIsIA0KICAgICAgICAgICAgICAgIHBsYXlzJFBsYXlzW3BsYXlzJFBsYXlJZCA9PSBhcy5udW1lcmljKGJlc3RfcGFzc1sxXSldKSkNCiAgICBwcmludChwYXN0ZSgiUHJlZGljdGVkIEdhaW46ICIsDQogICAgICAgICAgICAgICAgcm91bmQoYmVzdFsxXSksIA0KICAgICAgICAgICAgICAgICJZYXJkcyIpKQ0KICAgIA0KICAgIHByaW50KHBhc3RlKCJTZWNvbmQgUGFzcyBDaG9pY2U6IiwNCiAgICAgICAgICAgICAgICBwbGF5cyRQbGF5c1twbGF5cyRQbGF5SWQgPT0gYXMubnVtZXJpYyhiZXN0X3Bhc3NbMl0pXSkpDQogICAgcHJpbnQocGFzdGUoIlByZWRpY3RlZCBHYWluOiAiLA0KICAgICAgICAgICAgICAgIHJvdW5kKGJlc3RbMl0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgfQ0KICANCiAgaWYoU2l0SWQgJWluJSByb3duYW1lcyhydXNoX3ByZWQpKXsNCiAgICBiZXN0X3J1c2ggPC0gbmFtZXMoc29ydChydXNoX3ByZWRbU2l0SWQsXSwgZGVjcmVhc2luZyA9IFRSVUUpWzE6Ml0pDQogICAgcHJpbnQocGFzdGUoIlRvcCBSdW4gQ2hvaWNlOiIsIA0KICAgICAgICAgICAgICAgIHBsYXlzJFBsYXlzW3BsYXlzJFBsYXlJZCA9PSBhcy5udW1lcmljKGJlc3RfcnVzaFsxXSldKSkNCiAgICBwcmludChwYXN0ZSgiUHJlZGljdGVkIEdhaW46ICIsDQogICAgICAgICAgICAgICAgcm91bmQoc29ydChydXNoX3ByZWRbU2l0SWQsXSwgZGVjcmVhc2luZyA9IFRSVUUpWzFdKSwgDQogICAgICAgICAgICAgICAgIllhcmRzIikpDQogICAgDQogICAgcHJpbnQocGFzdGUoIlNlY29uZCBSdW4gQ2hvaWNlOiIsDQogICAgICAgICAgICAgICAgcGxheXMkUGxheXNbcGxheXMkUGxheUlkID09IGFzLm51bWVyaWMoYmVzdF9ydXNoWzJdKV0pKQ0KICAgIHByaW50KHBhc3RlKCJQcmVkaWN0ZWQgR2FpbjogIiwNCiAgICAgICAgICAgICAgICByb3VuZChzb3J0KHJ1c2hfcHJlZFtTaXRJZCxdLCBkZWNyZWFzaW5nID0gVFJVRSlbMl0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgfQ0KICBlbHNlew0KICAgIGJlc3QgPC0gc29ydChhcHBseShydXNoX3ByZWQsIDIsIGZ1bmN0aW9uKHgpIG1lYW4oeCwgbmEucm0gPSBUUlVFKSksIGRlY3JlYXNpbmcgPSBUUlVFKQ0KICAgIGJlc3RfcnVzaCA8LSBuYW1lcyhiZXN0KQ0KICAgIHByaW50KHBhc3RlKCJUb3AgUnVuIENob2ljZToiLCANCiAgICAgICAgICAgICAgICBwbGF5cyRQbGF5c1twbGF5cyRQbGF5SWQgPT0gYXMubnVtZXJpYyhiZXN0X3J1c2hbMV0pXSkpDQogICAgcHJpbnQocGFzdGUoIlByZWRpY3RlZCBHYWluOiAiLA0KICAgICAgICAgICAgICAgIHJvdW5kKGJlc3RbMV0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgICANCiAgICBwcmludChwYXN0ZSgiU2Vjb25kIFBhc3MgQ2hvaWNlOiIsDQogICAgICAgICAgICAgICAgcGxheXMkUGxheXNbcGxheXMkUGxheUlkID09IGFzLm51bWVyaWMoYmVzdF9ydXNoWzJdKV0pKQ0KICAgIHByaW50KHBhc3RlKCJQcmVkaWN0ZWQgR2FpbjogIiwNCiAgICAgICAgICAgICAgICByb3VuZChiZXN0WzJdKSwgDQogICAgICAgICAgICAgICAgIllhcmRzIikpDQogIH0NCn0NCmBgYA0KDQpgYGB7cn0NCmdldFBsYXkgPC0gZnVuY3Rpb24odGVhbSwgZG93biwgZGlzdGFuY2UsIHlhcmRsaW5lKXsNCiAgdGVhbUlkID0gdGVhbXMkVGVhbUlkW3RlYW1zJFRlYW1zID09IHRlYW1dDQogIFNpdElkID0geWFyZGxpbmUgKyAxMDAqZGlzdGFuY2UgKyAxMDAwMCpkb3duICsgMTAwMDAwKnRlYW1JZA0KICBTaXRJZCA9IGFzLmNoYXJhY3RlcihTaXRJZCkNCiAgDQogIGlmKFNpdElkICVpbiUgcm93bmFtZXMocGFzc19wcmVkKSl7DQogICAgYmVzdF9wYXNzIDwtIG5hbWVzKHNvcnQocGFzc19wcmVkW1NpdElkLF0sIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjJdKQ0KICAgIHByaW50KHBhc3RlKCJUb3AgUGFzcyBDaG9pY2U6IiwgDQogICAgICAgICAgICAgICAgcGxheXMkUGxheXNbcGxheXMkUGxheUlkID09IGFzLm51bWVyaWMoYmVzdF9wYXNzWzFdKV0pKQ0KICAgIHByaW50KHBhc3RlKCJQcmVkaWN0ZWQgR2FpbjogIiwNCiAgICAgICAgICAgICAgICByb3VuZChzb3J0KHBhc3NfcHJlZFtTaXRJZCxdLCBkZWNyZWFzaW5nID0gVFJVRSlbMV0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgICANCiAgICBwcmludChwYXN0ZSgiU2Vjb25kIFBhc3MgQ2hvaWNlOiIsDQogICAgICAgICAgICAgICAgcGxheXMkUGxheXNbcGxheXMkUGxheUlkID09IGFzLm51bWVyaWMoYmVzdF9wYXNzWzJdKV0pKQ0KICAgIHByaW50KHBhc3RlKCJQcmVkaWN0ZWQgR2FpbjogIiwNCiAgICAgICAgICAgICAgICByb3VuZChzb3J0KHBhc3NfcHJlZFtTaXRJZCxdLCBkZWNyZWFzaW5nID0gVFJVRSlbMl0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgfQ0KICBlbHNlew0KICAgIGJlc3QgPC0gc29ydChhcHBseShwYXNzX3ByZWQsIDIsIGZ1bmN0aW9uKHgpIG1lYW4oeCwgbmEucm0gPSBUUlVFKSksIGRlY3JlYXNpbmcgPSBUUlVFKQ0KICAgIGJlc3RfcGFzcyA8LSBuYW1lcyhiZXN0KQ0KICAgIHByaW50KHBhc3RlKCJUb3AgUGFzcyBDaG9pY2U6IiwgDQogICAgICAgICAgICAgICAgcGxheXMkUGxheXNbcGxheXMkUGxheUlkID09IGFzLm51bWVyaWMoYmVzdF9wYXNzWzFdKV0pKQ0KICAgIHByaW50KHBhc3RlKCJQcmVkaWN0ZWQgR2FpbjogIiwNCiAgICAgICAgICAgICAgICByb3VuZChiZXN0WzFdKSwgDQogICAgICAgICAgICAgICAgIllhcmRzIikpDQogICAgDQogICAgcHJpbnQocGFzdGUoIlNlY29uZCBQYXNzIENob2ljZToiLA0KICAgICAgICAgICAgICAgIHBsYXlzJFBsYXlzW3BsYXlzJFBsYXlJZCA9PSBhcy5udW1lcmljKGJlc3RfcGFzc1syXSldKSkNCiAgICBwcmludChwYXN0ZSgiUHJlZGljdGVkIEdhaW46ICIsDQogICAgICAgICAgICAgICAgcm91bmQoYmVzdFsyXSksIA0KICAgICAgICAgICAgICAgICJZYXJkcyIpKQ0KICB9DQogIA0KICBpZihTaXRJZCAlaW4lIHJvd25hbWVzKHJ1c2hfcHJlZCkpew0KICAgIGJlc3RfcnVzaCA8LSBuYW1lcyhzb3J0KHJ1c2hfcHJlZFtTaXRJZCxdLCBkZWNyZWFzaW5nID0gVFJVRSlbMToyXSkNCiAgICBwcmludChwYXN0ZSgiVG9wIFJ1biBDaG9pY2U6IiwgDQogICAgICAgICAgICAgICAgcGxheXMkUGxheXNbcGxheXMkUGxheUlkID09IGFzLm51bWVyaWMoYmVzdF9ydXNoWzFdKV0pKQ0KICAgIHByaW50KHBhc3RlKCJQcmVkaWN0ZWQgR2FpbjogIiwNCiAgICAgICAgICAgICAgICByb3VuZChzb3J0KHJ1c2hfcHJlZFtTaXRJZCxdLCBkZWNyZWFzaW5nID0gVFJVRSlbMV0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgICANCiAgICBwcmludChwYXN0ZSgiU2Vjb25kIFJ1biBDaG9pY2U6IiwNCiAgICAgICAgICAgICAgICBwbGF5cyRQbGF5c1twbGF5cyRQbGF5SWQgPT0gYXMubnVtZXJpYyhiZXN0X3J1c2hbMl0pXSkpDQogICAgcHJpbnQocGFzdGUoIlByZWRpY3RlZCBHYWluOiAiLA0KICAgICAgICAgICAgICAgIHJvdW5kKHNvcnQocnVzaF9wcmVkW1NpdElkLF0sIGRlY3JlYXNpbmcgPSBUUlVFKVsyXSksIA0KICAgICAgICAgICAgICAgICJZYXJkcyIpKQ0KICB9DQogIGVsc2V7DQogICAgYmVzdCA8LSBzb3J0KGFwcGx5KHJ1c2hfcHJlZCwgMiwgZnVuY3Rpb24oeCkgbWVhbih4LCBuYS5ybSA9IFRSVUUpKSwgZGVjcmVhc2luZyA9IFRSVUUpDQogICAgYmVzdF9ydXNoIDwtIG5hbWVzKGJlc3QpDQogICAgcHJpbnQocGFzdGUoIlRvcCBSdW4gQ2hvaWNlOiIsIA0KICAgICAgICAgICAgICAgIHBsYXlzJFBsYXlzW3BsYXlzJFBsYXlJZCA9PSBhcy5udW1lcmljKGJlc3RfcnVzaFsxXSldKSkNCiAgICBwcmludChwYXN0ZSgiUHJlZGljdGVkIEdhaW46ICIsDQogICAgICAgICAgICAgICAgcm91bmQoYmVzdFsxXSksIA0KICAgICAgICAgICAgICAgICJZYXJkcyIpKQ0KICAgIA0KICAgIHByaW50KHBhc3RlKCJTZWNvbmQgUGFzcyBDaG9pY2U6IiwNCiAgICAgICAgICAgICAgICBwbGF5cyRQbGF5c1twbGF5cyRQbGF5SWQgPT0gYXMubnVtZXJpYyhiZXN0X3J1c2hbMl0pXSkpDQogICAgcHJpbnQocGFzdGUoIlByZWRpY3RlZCBHYWluOiAiLA0KICAgICAgICAgICAgICAgIHJvdW5kKGJlc3RbMl0pLCANCiAgICAgICAgICAgICAgICAiWWFyZHMiKSkNCiAgfQ0KfQ0KYGBgDQoNCg0KIyMjIyA2LjIgLSBTYW1wbGUgT3V0cHV0DQpXZSBub3cgaW5jbHVkZSBzb21lIG9mIHRoZSBzYW1wbGUgb3V0cHV0IGZyb20gcnVubmluZyB0aGUgcmVjb21tZW5kZXIuIFdlIHN0YXJ0IGJ5IGluY2x1ZGluZyB0d28gZGlmZmVyZW50IHRlYW1zIHBsYWNlZCBpbiB0aGUgc2FtZSBzaXR1YXRpb24uIEhlcmUgd2UgbG9vayBhdCB0aGUgTmV3IEVuZ2xhbmQgUGF0cmlvdHMgYW5kIHRoZSBBcml6b25hIENhcmRpbmFscyBpbiBhIDFee3N0fSBhbmQgMTAgZnJvbSB0aGVpciBvd24gMjAgeWFyZCBsaW5lLiBXZSBub3RpY2UgdGhhdCB3ZSBnZXQgZGlmZmVyZW50IHJlY29tbWVuZGF0aW9ucyBmb3IgdGhlIHBsYXkgd2hpY2ggd2UgZXhwZWN0IGFuZCB0aGF0IHdlIHNlZSB0aGF0IEFyaXpvbmEsIHdobyBoYXMgYSB3ZWFrZXIgcnVzaGluZyBnYW1lLCBoYXMgc21hbGxlciBwcmVkaWN0ZWQgeWFyZHMgZ2FpbmVkIGluIHRoZWlyIHJ1bnMuDQpgYGB7cn0NCmdldFBsYXkoJ05FJywgMSwgMTAsIDIwKQ0KYGBgDQoNCmBgYHtyfQ0KZ2V0UGxheSgnQVJJJywgMSwgMTAsIDIwKQ0KYGBgDQoNCkZpbmFsbHkgbGV0cyBsb29rIGF0IHRoZSBzYW1lIHRlYW0gaW4gYSBjb3VwbGUgb2YgZGlmZmVyZW50IHNpdHVhdGlvbnMuIFdlIHdpbGwgbG9vayBhdCB0aGUgU2VhdHRsZSBTZWFoYXdrcyBpbiB0aGUgZm9sbG93aW5nIHNpdHVhdGlvbnMuIFdlIHdpbGwgZmlyc3QgcGxhY2UgdGhlbSBpbiBhIDJee25kfSBhbmQgNSBmcm9tIHRoZWlyIG93biA0NSB5YXJkIGxpbmUgIGFuZCB0aGVuIGxvb2sgYXQgMV57c3R9IGFuZCA1IGZyb20gdGhlaXIgb3Bwb25lbnRzIDUgIHlhcmQgbGluZS4gT25jZSBhZ2FpbiB3ZSBub3RlIHRoYXQgdGhlIHJlY29tbWVuZGVkIHBsYXlzIGFyZSBkaWZmZXJlbnQgYW5kIHRoYXQgaW4gdGhlIHNlY29uZCBzaXR1YXRpb24gbmVhciBvdXIgb3Bwb25lbnRzIGdvYWwgbGluZSB3ZSBnZXQgcnVuIHBsYXlzIGFuZCBwYXNzIHBsYXlzIHdpdGggc2hvcnQgeWFyZGFnZSBnYWlucyBleHBlY3RlZC4NCmBgYHtyfQ0KZ2V0UGxheSgnU0VBJywgMiwgNSwgNDUpDQpgYGANCg0KYGBge3J9DQpnZXRQbGF5KCdTRUEnLCAxLCA1LCA5NSkNCmBgYA0KDQojIyMgNyAtIENvbmNsdXNpb25zIGFuZCBGdXR1cmUgV29yaw0KVGhlIE5GTCBQbGF5IFJlY29tbWVuZGVyIGhhcyBwcm92ZW4gdG8gYmUgYSB2ZXJ5IGludGVyZXN0aW5nIHByb29mIG9mIGNvbmNlcHQgdGhhdCBtb2Rlcm4gdGVjaG5pcXVlcyBpbiByZWNvbW1lbmRlciBzeXN0ZW1zIGNhbiB3b3JrIHdpdGggdGhlIHJlbGF0aXZlbHkgbGltaXRlZCBkYXRhIGZyb20gdGhlIGxhc3QgNCB5ZWFycyBvZiB0aGUgTkZMIHRvIGJ1aWxkIGEgd29ya2luZyByZWNvbW1lbmRhdGlvbiBzeXN0ZW0uIE5vdCBvbmx5IGRpZCB3ZSBnZXQgYSB3b3JraW5nIHN5c3RlbSwgd2UgYWxzbyBjb25zdHJ1Y3RlZCBhIHN5c3RlbSB0aGF0IGhhcyBzb2xpZCBwZXJmb3JtYW5jZSBpbiB0aGUgcGFzc2luZyBnYW1lIGFuZCBkZWNlbnQgcGVyZm9ybWFuY2UgaW4gdGhlIHJ1c2hpbmcgZ2FtZSB3aXRoIGEgZmFpcmx5IHN0cmFpZ2h0Zm9yd2FyZCBjb2RlLWJhc2UuIE91ciBmaW5hbCBkYXRhIHNldHMgZW5kZWQgdXAgY29udGFpbmluZyBsZXNzIGFuZCAxMDAsMDAwIG9ic2VydmF0aW9ucyBvZiA3IHJ1c2ggcGxheXMgYW5kIDYgcGFzc2luZyBwbGF5cy4gV2Ugd291bGQgbG92ZSB0byBzZWUgd2hhdCBpcyBwb3NzaWJsZSBpZiB3ZSBoYWQgYSBmdWxsIHBsYXlib29rIHdvcnRoIG9mIHBsYXlzIGluc3RlYWQgb2YgYmFzaWMgZGlyZWN0aW9ucyBmb3IgdGhlIHBsYXkuIFdlIGZlZWwgbGlrZSBtZXRob2Qgd291bGQgYWxsb3cgdXMgdG8gYmVjb21lIGV2ZW4gbW9yZSBzcGVjaWZpYyBpbiB3aGF0IHdlIHJlY29tbWVuZC4gIA0KDQpXZSBhbHNvIG5vdGljZWQgYW4gaW50ZXJlc3RpbmcgdHJhaXQgb2YgdGhlIHBhc3NpbmcgcG9ydGlvbiBvZiB0aGUgcmVjb21tZW5kYXRpb24gc3lzdGVtLiBUaGUgc3lzdGVtIHRlbmRzIHRvIHNlbGVjdCB0aGUgbG9uZyBwYXNzIHBsYXlzLiBHaXZlbiB0aGF0IHdlIHdhbnQgdGhlIHN5c3RlbSB0byByZWNvbW1lbmQgdGhlIGJlc3QgeWFyZGFnZSBwbGF5IGluIGEgZ2l2ZW4gc2l0dWF0aW9uIHRoaXMgbWFrZXMgc2Vuc2UuIFdlIGh5cG90aGVzaXplIHRoYXQgdGhlcmUgbWF5IGJlIGEgZmV3IGRpZmZlcmVudCB3YXlzIG9mIGhhbmRsaW5nIHRoaXMgcHJvYmxlbSB0aGF0IHdlIHdvdWxkIGxpa2UgdG8gaW1wbGVtZW50IGluIGZ1dHVyZSB3b3JrLiBPbmUgc29sdXRpb24gdG8gdGhpcyBwcm9ibGVtIHdvdWxkIGIgdG8gc2ltcGx5IGNvbnN0cnVjdCBhIHRoaXJkIHJlY29tbWVuZGVyIHRoYXQgaGFuZGxlcyB0aGUgc2hvcnQgcGFzc2luZyBwbGF5cy4gVGhpcyBoYXMgdGhlIGRyYXdiYWNrIG9mIGxpbWl0aW5nIHRoZSBkYXRhIHNldCBldmVuIGZ1cnRoZXIgYW5kIG1heSBiZSB1bm5lY2Vzc2FyeS4gVGhlIG1vcmUgaW50ZXJlc3Rpbmcgd2F5cyBvZiBoYW5kbGluZyB0aGlzIHByb2JsZW0gZGVhbCB3aXRoIHVzaW5nIHRoZSBpbmNvbXBsZXRpb25zIGFzIGFub3RoZXIgZGF0YSBzb3VyY2UuIFdlIGNvdWxkIHNlZSB1c2luZyB0aGUgcHJvYmFiaWxpdHkgb2YgaW5jb21wbGV0aW9uIGZvciBhIGdpdmVuIHBhc3MgYW5kIHRlYW0gY29tYmluYXRpb24gc2VydmluZyBhcyBhIGJpbmFyeSB2YXJpYWJsZSB0aGF0IHdvdWxkIHNlcnZlIHRvIHdlaWdodCB0aGUgcGFzc2luZyBwbGF5cy4gVGhpcyBjb3VsZCBwb3RlbnRpYWxseSBkZWNyZWFzZSB0aGUgcG93ZXIgb2YgZGVlcGVyIHBhc3NlcyBpbiB0aGUgc3lzdGVtIGJ1dCBtYXkgc2VydmUgdG8gb25seSByZWNvbW1lbmQgcGFzc2VzIHdpdGggYSBoaWdoIHByb2JhYmlsaXR5IG9mIGNvbXBsZXRpb24gd2hpY2ggdGFrZXMgYXdheSBzb21lIG9mIHRoZSBnYW1ibGUgb2YgcGxheSBjYWxsaW5nLiBJdCBjb3VsZCBiZSBpbnRlcmVzdGluZyB0byBhcHBseSBhIHdlaWdodCBvZiAwIG9yIDEgYmFzZSBvZmYgb2YgYSByYW5kb20gcHJvYmFiaWxpdHkgcHJvY2VzcyBidXQgd2Ugd291bGQgbmVlZCB0byBmdWxseSBpbXBsZW1lbnQgc3VjaCBhIHN5c3RlbSB0byBzZWUgaWYgaXQgd29rcy4gVGhlIGZpbmFsbHkgdGVjaG5pcXVlIGNvdWxkIGJlIHRvIHNpbXBseSBhcHBseSBhIHBvc3QgcmVjb21tZW5kYXRpb24gd2VpZ2h0IGJhc2VkIG9uIHRoZSBwcm9iYWJpbGl0eSBjb21wbGV0aW9uIGFuIHJlcG9ydCB0aGlzIGluZm9ybWF0aW9uIHRvIHVzZXIgb2YgdGhlIHN5c3RlbS4gVGhpcyB3b3VsZCBwbGFjZSB0aGUgZGVjaXNpb24gbWFraW5nIHByb2Nlc3MgaW4gdGhlIGhhbmRzIG9mIHRoZSBjb2FjaCBhbmQgbWF5IGJlIHRoZSBtb3N0IHByZWZlcnJlZCBtZXRob2QuICANCg0KV2UgcmVhbGx5IGVuam95ZWQgZ2V0dGluZyB0byB3b3JrIHdpdGggdGhpcyBkYXRhIGFuZCB3ZXJlIGV4Y2l0ZWQgdG8gc2VlIGhvdyB3ZWxsIHRoZSBzeXN0ZW0gd2FzIGFibGUgdG8gcGVyZm9ybS4gQXMgYSBmb290YmFsbCBjb2FjaCB3ZSBmZWx0IGxpa2UgdGhpcyBzeXN0ZW0gd291bGQgYmUgYSByZWFsbHkgbmljZSB0b29sIHRvIGhhdmUgb24gdGhlIHNpZGVsaW5lLiBBbnl0aGluZyB0aGF0IGNhbiBoZWxwIGEgcGxheS1jYWxsZXIgYnkgZ2l2aW5nIHVzIG1vcmUgaW5mb3JtYXRpb24gaXMgYSBncmVhdCBoZWxwIGluIHRoZSBmYXN0IHBhY2VkIGFuZCBzdHJlc3NmdWwgZW52aXJvbm1lbnQgb2YgZm9vdGJhbGwgc2lkZWxpbmUuIFRoaXMgcHJvamVjdCBoYXMgcHJvdmVuLCBhcyBtdWNoIG9mIHRoZSBjbGFzcyBoYXMgYXMgd2VsbCwgdG8gYmUgYSBmYXNjaW5hdGluZyBsb29rIGF0IHdoYXQgY2FuIGJlIGRvbmUgd2l0aCBkYXRhIGFuYWx5dGljcyBhbmQgdGhlIG1vZGVybiBzdGF0aXN0aWNhbCB0ZWNobmlxdWVzLg==