What makes a driver more likely to score second place in a F1 race?

Data importing and wrangling

# loading required packages

library(dplyr)
library(lubridate)
library(tidyr)
library(caret)
library(readr)
# setting working directory
setwd("D:/QMSS_Spring_2020/AppliedDS")

For this project, I utilized df_races, df_drivers and df_results from Amazon S3 bucket (primarily df_results). I also tried using df_lap_times and df_pit_stops, both of those data frames contained extremely useful information that I thought could be useful to explain why a driver arrives in second place in a given race. Unfortunately, both of those datasets only contained data for more recent years and are missing all of the years prior to 1990s. Therefore, I decided to build the processed data based on those three data frames mentioned above.

# reading data imported from Amazon S3 
df_races <- read_csv("data/races.csv")
df_drivers <- read_csv("data/drivers.csv")
df_results <- read_csv("data/results.csv")
#df_lap_times <- read_csv("data/lap_times.csv")
#df_pit_stops <- read_csv("data/pit_stops.csv")
# removing unrelated variables and combining data frames
df_all <- df_results %>% select(-resultId, -number, -positionText, 
                                    -positionOrder, -time)

df_all <- df_all %>% right_join(df_drivers, by = "driverId") %>% 
                         select(-driverRef, -number, -code, -forename, -surname, 
                                -nationality, -url)

df_all <- df_all %>% right_join(df_races, by = "raceId") %>% 
                     select(-year, -name, -time, -url)
# mutating new feature 'year'
df_all$year <- as.numeric(year(df_all$date))

Initially I chose not to include points from df_results because I thought points is just another presentation of position, thus would make the regression analysis less generalization (and raise multi-collinearity issue). Nevertheless, later I realized the points earned for each position differs in different circuits. Additionally, the correlation ratio between position and points are lower than I assumed (I thought it would be over -0.8). As the consequence, I decided to include points in the following models.

# checking correlation between position and points, as well as mean points earned for each position
cor(df_results$positionOrder, df_results$points)
[1] -0.5665862
df_results %>% group_by(positionOrder) %>% summarise(mean(points))

I’m curious to see whether a driver from a club that has a long history of earning second places is more likely to earn more second places for that constructor. Below I checked the number of second places each constructor earned, then in the following code chunks, I created variable dummy constructor_2nd_place to identify the clubs that had earned second place more than 20 times (if true than the dummy equals 1, otherwise equals 0).

Furthermore, I also wanted to investigate if a driver who had earned second placed is more likely to score another second place. The dummy variable history_2nd_place is set to be 1 if the driver had earned second place in the past.

# number of second place earned for each constructor
df_all %>%  filter(position == 2) %>%
  group_by(constructorId) %>% summarise(n = n()) %>% arrange(desc(n))
# dummy variable second_place_driver, if a driver had earned second place in the past then variable = 1
second_place_driver <- df_all %>%  filter(position == 2, ) %>%
  group_by(driverId) %>% summarise(n = n()) %>% arrange(desc(n))
second_place_driver <- second_place_driver$driverId %>% as.vector()
# creating dummy variables for second place and if the driver completed the race
df_all$second_place <- recode(df_all$position, "2" = "1", .default = "0")
df_all$race_complete <- recode(df_all$statusId, "1" = "1", .default = "0")

# recoding the constructors who had earned 2nd place more than 20 times
df_all$constructor_2nd_place <- recode(df_all$constructorId, "6" = "6", "1" = "1", "3" = "3",
                                       "131" = "131", "9" = "9", "4" = "4", "25" = "25",
                                       "22" = "22", "32" = "32", "66" = "66", "34" = "34", default = "0", .missing = "0") 
df_all$constructor_2nd_place <- replace_na(df_all$constructor_2nd_place, "0")

# if the driver had ever earned 2nd place, then the dummy variable equals to 1
df_all$history_2nd_place <- ifelse(df_all$driverId %in% second_place_driver, 1, 0)

# mutating birthday based on race date and driver dob
df_all <- df_all %>% mutate(age = as.integer(round((as.Date(date) - as.Date(dob)) / 365.25, 0)))

# converting variable to either integers
df_all$position <- as.integer(df_all$position)
df_all$fastestLap <- as.integer(df_all$fastestLap)
df_all$rank <- as.integer(df_all$rank)
df_all$fastestLapSpeed <- as.integer(df_all$fastestLapSpeed)
df_all$fastestLapTimeSec <- period_to_seconds(ms(df_all$fastestLapTime))
df_all$race_complete <- as.integer(df_all$race_complete)
df_all$second_place <- as.integer(df_all$second_place)
df_all$milliseconds <- as.integer(df_all$milliseconds)
df_all$constructor_2nd_place <- as.integer(df_all$constructor_2nd_place)
# subsetting df_all to create a smaller df for regression
subset <- df_all %>% filter(year <= 2010) %>% 
                    select(second_place, constructor_2nd_place, grid, points, round, race_complete,
                            age, history_2nd_place)
# confirming the subset does not include any NAs
colSums(is.na(subset))
         second_place constructor_2nd_place                  grid                points                 round 
                    0                     0                     0                     0                     0 
        race_complete                   age     history_2nd_place 
                    0                     0                     0 
# subsetting df_all to create training and testing set for ml practice
subset2 <- df_all %>% select(year, constructor_2nd_place, grid, laps, points, 
                            second_place, race_complete, age, history_2nd_place) %>% 
                     rename(y = second_place)
subset2$y <- factor(subset2$y, labels = c("yes", "no"), levels = 1:0)

training <- subset2 %>% filter(year <= 2010) %>% select(-year)
testing <- subset2 %>% filter(year > 2010 & year <= 2017) %>% select(-year)

# confirming there's no NAs
colSums(is.na(training))
constructor_2nd_place                  grid                  laps                points                     y 
                    0                     0                     0                     0                     0 
        race_complete                   age     history_2nd_place 
                    0                     0                     0 
colSums(is.na(testing))
constructor_2nd_place                  grid                  laps                points                     y 
                    0                     0                     0                     0                     0 
        race_complete                   age     history_2nd_place 
                    0                     0                     0 
# exporting data
write.csv(subset, 'data/subset.csv')
write.csv(training, 'data/training.csv')
write.csv(testing, 'data/testing.csv')

Inferential model to explain why a driver arrives in second place in a race between 1950 and 2010.

For the first part of this project, I used logit regression trying to explain why a driver arrives in second place in a race between 1950 and 2010. Model1 returned an adjusted R2 of 0.25, suggesting 25% of the variance in whether a driver arrives the second in a given race is explained by the model. This is not ideal, but with the current information I did not a better solution. Model2 uses the center scaled version of the dataset, thus offering standarized coefficients. The standardized coefficients are used to determine the importance of variables in the model1, a larger coefficient value would indicate a more important variable.

  • second_place, the dependent variable (y), where 1 represents the driver finished second in the given race.
  • points, the number of points earned by the driver in that race. The model included a second degree polynomial term of points. As the coefficient at both first and second degree polynomial terms are negative, the curve of feature points is determined to be a concave (curve opens down), which means the points earned would over increase the chance of a driver earning second place, though the impact fades as the points earned increases. Both degree terms are statistically significant at 99.9% level.
  • race_complete, a dummy variable based on statusId and is coded to 1 if the driver completed the race. The coefficient sugguests that having completed the race successful will increase the chance of earning second place by 9.43%. This feature is statistically significant at 99.9% level.
  • age, age of the driver at the time of that race. This variable is not statistically significant in the model, indicates that driver’s age at time of race does not affect the likelihood of a driver arrives the second in a race.
  • history_2nd_place, dummy variable for whether the driver has a history of earning second places. This feature is statistically significant at 99% level, and if the driver had a history of earning second places, the chance of that driver winning another second place decreases by 0.9%. One possible interpretation would be earning second places would help a driver improve skill, so that driver can earn first places in the future.
  • constructor_2nd_place, a list of constructors/clubs IDs that had earned second place for more than 20 times. In the actual model, this feature is factorized, meaning each of the constructorId is treated as an independent dummy variable. For instance, if a driver comes from Renault or Mercedes, the likelihood of the driver earning second place each decrease by 1.76% and 7.26%. However, if the driver comes from Ferrari’s club, the chance of that driver earning second place increases by 1.24%.
  • grid, a grid number for each driver at the race beginnig. Similar to the previous variable, grid is factorized in the model1. The starting position do help explaining whether a driver arrives second in a given race. The regression outcome indicates that if the driver is located in the first grid when starting, the chance of him earning second place decreases by 5.64% (maybe ends up winning the race). But if the driver is located in a grid slot from 2 to 4, the chance of him arrives the second in the race increases.

Overall, according to standardized coefficient in model2, the most important feature is the polynomial terms of points. The next most important feature is history_2nd_place (if the driver have already earned second place).

I also included a series of interaction terms, all of the interaction terms are statistically significnat at 99.9% level. Below I would provide marginal effect of the most important interaction:

  • constructor_2nd_place and points, given the save level of points earned, the impact of whether the constructor is a frequent second place earner.
  • constructor_2nd_place and race_complete, given the driver had completed the race, the impact of whether the constructor is a frequent second place earner.
  • grid and points, given the save level of points earned, the impact of a grid position closer to front.
  • grid and race_complete, given the driver had completed the race, the impact of a grid position closer to front.
  • points and history_second_place, if the driver had a history of earning second place, what’s the impact of more points earned. This interaction term is tested to be the most important interaction term (according to standarized coefficient in model 2). If the driver had a history of eraning second place, 1 extra point earned would make that driver 4.65% more likely to earn another second place.
  • round and race_complete, holding the round number in a circuit constant, what’s the impact of completing the race in determining whether the driver earns the second place.

Lastly, I believe it’s simply an association we observed instead of an “explanation” or causation. In order to fulfill a causal relation, three criterias need to be met: spatial continuity, temporal succession and constant conjunction. Among variables discussed previously, the regressors do not precedes the y variable (second place) in time. At the same time, we cannot ensure the independence or unit homogeneity among variables. Therefore, I suggest the relationship we explained is simply an association.

a) Logistic regression without scaling

print(summary(model1))

Call:
lm(formula = second_place ~ poly(points, 2) + race_complete + 
    age + history_2nd_place + factor(constructor_2nd_place) + 
    factor(grid) + constructor_2nd_place:points + constructor_2nd_place:race_complete + 
    grid:points + grid:race_complete + points:history_2nd_place + 
    round:race_complete, data = subset)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.40873 -0.01118  0.00099  0.01175  0.88672 

Coefficients:
                                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)                         -2.363e-02  9.024e-03  -2.619  0.00883 ** 
poly(points, 2)1                    -7.311e+00  1.113e+00  -6.568 5.21e-11 ***
poly(points, 2)2                    -5.167e+00  2.220e-01 -23.276  < 2e-16 ***
race_complete                        9.426e-02  1.097e-02   8.590  < 2e-16 ***
age                                  3.574e-04  2.344e-04   1.525  0.12725    
history_2nd_place                   -9.788e-03  3.087e-03  -3.171  0.00152 ** 
factor(constructor_2nd_place)1      -4.318e-03  5.299e-03  -0.815  0.41517    
factor(constructor_2nd_place)3       3.883e-03  5.688e-03   0.683  0.49482    
factor(constructor_2nd_place)4      -1.760e-02  7.669e-03  -2.296  0.02171 *  
factor(constructor_2nd_place)6       1.235e-02  4.704e-03   2.627  0.00863 ** 
factor(constructor_2nd_place)9      -4.554e-03  1.204e-02  -0.378  0.70536    
factor(constructor_2nd_place)22     -3.609e-03  7.904e-03  -0.457  0.64791    
factor(constructor_2nd_place)25      8.207e-03  6.036e-03   1.360  0.17394    
factor(constructor_2nd_place)32     -4.990e-03  6.165e-03  -0.809  0.41825    
factor(constructor_2nd_place)34      2.730e-03  6.974e-03   0.391  0.69553    
factor(constructor_2nd_place)66      1.119e-02  7.667e-03   1.459  0.14459    
factor(constructor_2nd_place)131    -7.264e-02  2.434e-02  -2.984  0.00285 ** 
factor(grid)1                       -5.637e-02  8.896e-03  -6.336 2.40e-10 ***
factor(grid)2                        1.683e-02  8.482e-03   1.984  0.04729 *  
factor(grid)3                        4.645e-02  8.197e-03   5.667 1.47e-08 ***
factor(grid)4                        1.968e-02  7.988e-03   2.464  0.01376 *  
factor(grid)5                       -8.235e-03  7.864e-03  -1.047  0.29503    
factor(grid)6                       -7.201e-03  7.790e-03  -0.924  0.35527    
factor(grid)7                       -1.280e-02  7.684e-03  -1.665  0.09589 .  
factor(grid)8                       -1.433e-02  7.672e-03  -1.868  0.06179 .  
factor(grid)9                       -7.833e-03  7.603e-03  -1.030  0.30294    
factor(grid)10                      -1.302e-02  7.542e-03  -1.727  0.08423 .  
factor(grid)11                      -1.133e-02  7.505e-03  -1.510  0.13103    
factor(grid)12                      -6.723e-03  7.509e-03  -0.895  0.37066    
factor(grid)13                      -4.102e-03  7.465e-03  -0.550  0.58262    
factor(grid)14                      -3.056e-03  7.481e-03  -0.408  0.68294    
factor(grid)15                      -4.719e-03  7.450e-03  -0.633  0.52649    
factor(grid)16                      -2.148e-03  7.481e-03  -0.287  0.77403    
factor(grid)17                      -1.802e-03  7.505e-03  -0.240  0.81021    
factor(grid)18                      -3.098e-04  7.590e-03  -0.041  0.96744    
factor(grid)19                      -1.823e-04  7.616e-03  -0.024  0.98090    
factor(grid)20                       1.227e-03  7.667e-03   0.160  0.87282    
factor(grid)21                      -1.063e-03  8.229e-03  -0.129  0.89719    
factor(grid)22                      -3.510e-03  8.435e-03  -0.416  0.67734    
factor(grid)23                       1.710e-03  9.466e-03   0.181  0.85664    
factor(grid)24                      -1.407e-03  9.668e-03  -0.146  0.88429    
factor(grid)25                       3.146e-03  1.079e-02   0.292  0.77063    
factor(grid)26                       7.642e-04  1.172e-02   0.065  0.94801    
factor(grid)27                       1.580e-02  2.576e-02   0.613  0.53963    
factor(grid)28                       3.891e-03  3.159e-02   0.123  0.90198    
factor(grid)29                       3.014e-04  3.455e-02   0.009  0.99304    
factor(grid)30                       5.967e-03  3.954e-02   0.151  0.88005    
factor(grid)31                       1.411e-02  4.070e-02   0.347  0.72879    
factor(grid)32                       5.625e-02  4.187e-02   1.344  0.17912    
factor(grid)33                       2.479e-02  4.787e-02   0.518  0.60456    
factor(grid)34                      -3.778e-03  1.713e-01  -0.022  0.98241    
constructor_2nd_place:points        -1.817e-04  3.816e-05  -4.761 1.94e-06 ***
race_complete:constructor_2nd_place  1.304e-03  2.654e-04   4.915 8.96e-07 ***
points:grid                          8.803e-04  1.794e-04   4.906 9.35e-07 ***
race_complete:grid                  -7.057e-03  7.972e-04  -8.852  < 2e-16 ***
history_2nd_place:points             4.650e-02  2.611e-03  17.811  < 2e-16 ***
race_complete:round                 -2.707e-03  5.347e-04  -5.062 4.18e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1713 on 20722 degrees of freedom
Multiple R-squared:  0.252, Adjusted R-squared:   0.25 
F-statistic: 124.7 on 56 and 20722 DF,  p-value: < 2.2e-16

b) Logistic regression with scaling, to provide standardized coefficients

# model after scaling, offering standardized coefficients
scaled_subset <- scale(subset, center = TRUE, scale = TRUE) %>% as.data.frame()
model2 <- lm(second_place ~  poly(points, 2) + race_complete + age + history_2nd_place + 
               constructor_2nd_place + grid + constructor_2nd_place:points +
               constructor_2nd_place:race_complete + grid:points + grid:race_complete + 
               points:history_2nd_place + round:race_complete, 
               data = scaled_subset)
print(summary(model2))

Call:
lm(formula = second_place ~ poly(points, 2) + race_complete + 
    age + history_2nd_place + constructor_2nd_place + grid + 
    constructor_2nd_place:points + constructor_2nd_place:race_complete + 
    grid:points + grid:race_complete + points:history_2nd_place + 
    round:race_complete, data = scaled_subset)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.9191 -0.0264  0.0138  0.0537  4.3582 

Coefficients:
                                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)                          -0.091764   0.008390 -10.937  < 2e-16 ***
poly(points, 2)1                     32.030230   2.523366  12.693  < 2e-16 ***
poly(points, 2)2                    -26.107551   1.102145 -23.688  < 2e-16 ***
race_complete                         0.005283   0.009718   0.544   0.5867    
age                                   0.011818   0.006065   1.949   0.0513 .  
history_2nd_place                     0.109762   0.008720  12.587  < 2e-16 ***
constructor_2nd_place                 0.003215   0.006201   0.518   0.6042    
grid                                 -0.017777   0.007794  -2.281   0.0226 *  
constructor_2nd_place:points         -0.045258   0.007529  -6.011 1.87e-09 ***
race_complete:constructor_2nd_place   0.041256   0.008868   4.652 3.30e-06 ***
grid:points                           0.103095   0.016155   6.382 1.79e-10 ***
race_complete:grid                   -0.124061   0.012257 -10.122  < 2e-16 ***
history_2nd_place:points              0.304121   0.016440  18.499  < 2e-16 ***
race_complete:round                  -0.030395   0.005871  -5.177 2.28e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.8701 on 20765 degrees of freedom
Multiple R-squared:  0.2435,    Adjusted R-squared:  0.243 
F-statistic:   514 on 13 and 20765 DF,  p-value: < 2.2e-16

Predictive models using 1950 to 2010 as training set, 2011 to 2017 as testing set to predict drivers that come in second place.

For this part of the project, I splitted the dataset I created and assigned data from year 1950 to 2010 to training set, and 2011 to 2017 to testing set. Slightly different from the dataset I used for the inferential model, I included variable laps, which represents the number of laps a driver completed in a given race.

In the following predicitive models, I exlucded constructor_2nd_place variable as it would increase the overfitting in the testing set. Furthermore, I also changed the sets of interaction terms to include.

  • grid and points; constructor_2nd_place and points; grid and race_complete are the three sets of interations I kept. I included three new sets of interactions.
  • points and race_complete
  • points and laps
  • race_complete and laps

As the primary focus of supervised learning methods is not inference but prediction, I retained some features / interactions even though they don’t make too much sense, as long as they help predicting better.

a) Generalized Linear Model

print(confusionMatrix(z_glm, testing$y))
Confusion Matrix and Statistics

          Reference
Prediction   no  yes
       no  2785  137
       yes   79    0
                                         
               Accuracy : 0.928          
                 95% CI : (0.9182, 0.937)
    No Information Rate : 0.9543         
    P-Value [Acc > NIR] : 1.0000000      
                                         
                  Kappa : -0.0345        
                                         
 Mcnemar's Test P-Value : 0.0001052      
                                         
            Sensitivity : 0.9724         
            Specificity : 0.0000         
         Pos Pred Value : 0.9531         
         Neg Pred Value : 0.0000         
             Prevalence : 0.9543         
         Detection Rate : 0.9280         
   Detection Prevalence : 0.9737         
      Balanced Accuracy : 0.4862         
                                         
       'Positive' Class : no             
                                         

b) Bagging (treebag)

print(confusionMatrix(z_treebag, testing$y))
Confusion Matrix and Statistics

          Reference
Prediction   no  yes
       no  2733    1
       yes  131  136
                                          
               Accuracy : 0.956           
                 95% CI : (0.9481, 0.9631)
    No Information Rate : 0.9543          
    P-Value [Acc > NIR] : 0.3511          
                                          
                  Kappa : 0.6523          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.9543          
            Specificity : 0.9927          
         Pos Pred Value : 0.9996          
         Neg Pred Value : 0.5094          
             Prevalence : 0.9543          
         Detection Rate : 0.9107          
   Detection Prevalence : 0.9110          
      Balanced Accuracy : 0.9735          
                                          
       'Positive' Class : no              
                                          

c) Random Forest

print(confusionMatrix(z_rf1, testing$y))
Confusion Matrix and Statistics

          Reference
Prediction   no  yes
       no  2733    1
       yes  131  136
                                          
               Accuracy : 0.956           
                 95% CI : (0.9481, 0.9631)
    No Information Rate : 0.9543          
    P-Value [Acc > NIR] : 0.3511          
                                          
                  Kappa : 0.6523          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.9543          
            Specificity : 0.9927          
         Pos Pred Value : 0.9996          
         Neg Pred Value : 0.5094          
             Prevalence : 0.9543          
         Detection Rate : 0.9107          
   Detection Prevalence : 0.9110          
      Balanced Accuracy : 0.9735          
                                          
       'Positive' Class : no              
                                          
print(confusionMatrix(z_rf2, testing$y))
Confusion Matrix and Statistics

          Reference
Prediction   no  yes
       no  2703    1
       yes  161  136
                                          
               Accuracy : 0.946           
                 95% CI : (0.9373, 0.9538)
    No Information Rate : 0.9543          
    P-Value [Acc > NIR] : 0.9854          
                                          
                  Kappa : 0.6019          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.9438          
            Specificity : 0.9927          
         Pos Pred Value : 0.9996          
         Neg Pred Value : 0.4579          
             Prevalence : 0.9543          
         Detection Rate : 0.9007          
   Detection Prevalence : 0.9010          
      Balanced Accuracy : 0.9682          
                                          
       'Positive' Class : no              
                                          

Neural Network (MLP)

print(confusionMatrix(z_nn, testing$y))
Confusion Matrix and Statistics

          Reference
Prediction   no  yes
       no  2708  137
       yes  156    0
                                          
               Accuracy : 0.9024          
                 95% CI : (0.8912, 0.9128)
    No Information Rate : 0.9543          
    P-Value [Acc > NIR] : 1.000           
                                          
                  Kappa : -0.0511         
                                          
 Mcnemar's Test P-Value : 0.293           
                                          
            Sensitivity : 0.9455          
            Specificity : 0.0000          
         Pos Pred Value : 0.9518          
         Neg Pred Value : 0.0000          
             Prevalence : 0.9543          
         Detection Rate : 0.9024          
   Detection Prevalence : 0.9480          
      Balanced Accuracy : 0.4728          
                                          
       'Positive' Class : no              
                                          

Predictive model evaluation

Overall, the best predictive model from the algorithms/models above is the random forest model with 1000 trees. In that model, the accuracy score is 0.956, meaning 95.6% of the predicted y in the testing set matched the actual y. Breaking down the confusion matrix, the number of true positives and true negatives in this model are both the highest among all models I tried. The count of false positives is 131 and the count of false negative is just 1.

According to the varImp function (variable importance), the top 5 most important features in this model are: first and second degree polynomial term of points, interaction term between race_complete and points, points and laps, as well as variable age.

ggsave("plot1.png")
LS0tDQp0aXRsZTogIkFwcGxpZWQgRGF0YSBTY2llbmNlIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgZGZfcHJpbnQ6IHBhZ2VkDQogIGNvZGVfZm9sZGluZzogc2hvdw0KLS0tDQoNCiMgV2hhdCBtYWtlcyBhIGRyaXZlciBtb3JlIGxpa2VseSB0byBzY29yZSBzZWNvbmQgcGxhY2UgaW4gYSBGMSByYWNlPw0KDQojIyBEYXRhIGltcG9ydGluZyBhbmQgd3JhbmdsaW5nIA0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KIyBsb2FkaW5nIHJlcXVpcmVkIHBhY2thZ2VzDQoNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShyZWFkcikNCmBgYA0KDQpgYGB7cn0NCiMgc2V0dGluZyB3b3JraW5nIGRpcmVjdG9yeQ0Kc2V0d2QoIkQ6L1FNU1NfU3ByaW5nXzIwMjAvQXBwbGllZERTIikNCmBgYA0KDQpGb3IgdGhpcyBwcm9qZWN0LCBJIHV0aWxpemVkIGBkZl9yYWNlc2AsIGBkZl9kcml2ZXJzYCBhbmQgYGRmX3Jlc3VsdHNgIGZyb20gQW1hem9uIFMzIGJ1Y2tldCAocHJpbWFyaWx5IGRmX3Jlc3VsdHMpLiBJIGFsc28gdHJpZWQgdXNpbmcgYGRmX2xhcF90aW1lc2AgYW5kIGBkZl9waXRfc3RvcHNgLCBib3RoIG9mIHRob3NlIGRhdGEgZnJhbWVzIGNvbnRhaW5lZCBleHRyZW1lbHkgdXNlZnVsIGluZm9ybWF0aW9uIHRoYXQgSSB0aG91Z2h0IGNvdWxkIGJlIHVzZWZ1bCB0byBleHBsYWluIHdoeSBhIGRyaXZlciBhcnJpdmVzIGluIHNlY29uZCBwbGFjZSBpbiBhIGdpdmVuIHJhY2UuIFVuZm9ydHVuYXRlbHksIGJvdGggb2YgdGhvc2UgZGF0YXNldHMgb25seSBjb250YWluZWQgZGF0YSBmb3IgbW9yZSByZWNlbnQgeWVhcnMgYW5kIGFyZSBtaXNzaW5nIGFsbCBvZiB0aGUgeWVhcnMgcHJpb3IgdG8gMTk5MHMuIFRoZXJlZm9yZSwgSSBkZWNpZGVkIHRvIGJ1aWxkIHRoZSBwcm9jZXNzZWQgZGF0YSBiYXNlZCBvbiB0aG9zZSB0aHJlZSBkYXRhIGZyYW1lcyBtZW50aW9uZWQgYWJvdmUuIA0KDQpgYGB7ciBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCiMgcmVhZGluZyBkYXRhIGltcG9ydGVkIGZyb20gQW1hem9uIFMzIA0KZGZfcmFjZXMgPC0gcmVhZF9jc3YoImRhdGEvcmFjZXMuY3N2IikNCmRmX2RyaXZlcnMgPC0gcmVhZF9jc3YoImRhdGEvZHJpdmVycy5jc3YiKQ0KZGZfcmVzdWx0cyA8LSByZWFkX2NzdigiZGF0YS9yZXN1bHRzLmNzdiIpDQojZGZfbGFwX3RpbWVzIDwtIHJlYWRfY3N2KCJkYXRhL2xhcF90aW1lcy5jc3YiKQ0KI2RmX3BpdF9zdG9wcyA8LSByZWFkX2NzdigiZGF0YS9waXRfc3RvcHMuY3N2IikNCmBgYA0KDQpgYGB7cn0NCiMgcmVtb3ZpbmcgdW5yZWxhdGVkIHZhcmlhYmxlcyBhbmQgY29tYmluaW5nIGRhdGEgZnJhbWVzDQpkZl9hbGwgPC0gZGZfcmVzdWx0cyAlPiUgc2VsZWN0KC1yZXN1bHRJZCwgLW51bWJlciwgLXBvc2l0aW9uVGV4dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtcG9zaXRpb25PcmRlciwgLXRpbWUpDQoNCmRmX2FsbCA8LSBkZl9hbGwgJT4lIHJpZ2h0X2pvaW4oZGZfZHJpdmVycywgYnkgPSAiZHJpdmVySWQiKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KC1kcml2ZXJSZWYsIC1udW1iZXIsIC1jb2RlLCAtZm9yZW5hbWUsIC1zdXJuYW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLW5hdGlvbmFsaXR5LCAtdXJsKQ0KDQpkZl9hbGwgPC0gZGZfYWxsICU+JSByaWdodF9qb2luKGRmX3JhY2VzLCBieSA9ICJyYWNlSWQiKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLXllYXIsIC1uYW1lLCAtdGltZSwgLXVybCkNCmBgYA0KDQpgYGB7cn0NCiMgbXV0YXRpbmcgbmV3IGZlYXR1cmUgJ3llYXInDQpkZl9hbGwkeWVhciA8LSBhcy5udW1lcmljKHllYXIoZGZfYWxsJGRhdGUpKQ0KYGBgDQoNCkluaXRpYWxseSBJIGNob3NlIG5vdCB0byBpbmNsdWRlIGBwb2ludHNgIGZyb20gYGRmX3Jlc3VsdHNgIGJlY2F1c2UgSSB0aG91Z2h0IGBwb2ludHNgIGlzIGp1c3QgYW5vdGhlciBwcmVzZW50YXRpb24gb2YgYHBvc2l0aW9uYCwgdGh1cyB3b3VsZCBtYWtlIHRoZSByZWdyZXNzaW9uIGFuYWx5c2lzIGxlc3MgZ2VuZXJhbGl6YXRpb24gKGFuZCByYWlzZSBtdWx0aS1jb2xsaW5lYXJpdHkgaXNzdWUpLiBOZXZlcnRoZWxlc3MsIGxhdGVyIEkgcmVhbGl6ZWQgdGhlIHBvaW50cyBlYXJuZWQgZm9yIGVhY2ggcG9zaXRpb24gZGlmZmVycyBpbiBkaWZmZXJlbnQgY2lyY3VpdHMuIEFkZGl0aW9uYWxseSwgdGhlIGNvcnJlbGF0aW9uIHJhdGlvIGJldHdlZW4gYHBvc2l0aW9uYCBhbmQgYHBvaW50c2AgYXJlIGxvd2VyIHRoYW4gSSBhc3N1bWVkIChJIHRob3VnaHQgaXQgd291bGQgYmUgb3ZlciAtMC44KS4gQXMgdGhlIGNvbnNlcXVlbmNlLCBJIGRlY2lkZWQgdG8gaW5jbHVkZSBgcG9pbnRzYCBpbiB0aGUgZm9sbG93aW5nIG1vZGVscy4gDQoNCmBgYHtyfQ0KIyBjaGVja2luZyBjb3JyZWxhdGlvbiBiZXR3ZWVuIHBvc2l0aW9uIGFuZCBwb2ludHMsIGFzIHdlbGwgYXMgbWVhbiBwb2ludHMgZWFybmVkIGZvciBlYWNoIHBvc2l0aW9uDQpjb3IoZGZfcmVzdWx0cyRwb3NpdGlvbk9yZGVyLCBkZl9yZXN1bHRzJHBvaW50cykNCmRmX3Jlc3VsdHMgJT4lIGdyb3VwX2J5KHBvc2l0aW9uT3JkZXIpICU+JSBzdW1tYXJpc2UobWVhbihwb2ludHMpKQ0KYGBgDQoNCkknbSBjdXJpb3VzIHRvIHNlZSB3aGV0aGVyIGEgZHJpdmVyIGZyb20gYSBjbHViIHRoYXQgaGFzIGEgbG9uZyBoaXN0b3J5IG9mIGVhcm5pbmcgc2Vjb25kIHBsYWNlcyBpcyBtb3JlIGxpa2VseSB0byBlYXJuIG1vcmUgc2Vjb25kIHBsYWNlcyBmb3IgdGhhdCBjb25zdHJ1Y3Rvci4gQmVsb3cgSSBjaGVja2VkIHRoZSBudW1iZXIgb2Ygc2Vjb25kIHBsYWNlcyBlYWNoIGNvbnN0cnVjdG9yIGVhcm5lZCwgdGhlbiBpbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmtzLCBJIGNyZWF0ZWQgdmFyaWFibGUgZHVtbXkgYGNvbnN0cnVjdG9yXzJuZF9wbGFjZWAgdG8gaWRlbnRpZnkgdGhlIGNsdWJzIHRoYXQgaGFkIGVhcm5lZCBzZWNvbmQgcGxhY2UgbW9yZSB0aGFuIDIwIHRpbWVzIChpZiB0cnVlIHRoYW4gdGhlIGR1bW15IGVxdWFscyAxLCBvdGhlcndpc2UgZXF1YWxzIDApLg0KDQpGdXJ0aGVybW9yZSwgSSBhbHNvIHdhbnRlZCB0byBpbnZlc3RpZ2F0ZSBpZiBhIGRyaXZlciB3aG8gaGFkIGVhcm5lZCBzZWNvbmQgcGxhY2VkIGlzIG1vcmUgbGlrZWx5IHRvIHNjb3JlIGFub3RoZXIgc2Vjb25kIHBsYWNlLiBUaGUgZHVtbXkgdmFyaWFibGUgYGhpc3RvcnlfMm5kX3BsYWNlYCBpcyBzZXQgdG8gYmUgMSBpZiB0aGUgZHJpdmVyIGhhZCBlYXJuZWQgc2Vjb25kIHBsYWNlIGluIHRoZSBwYXN0LiANCmBgYHtyfQ0KIyBudW1iZXIgb2Ygc2Vjb25kIHBsYWNlIGVhcm5lZCBmb3IgZWFjaCBjb25zdHJ1Y3Rvcg0KZGZfYWxsICU+JSAgZmlsdGVyKHBvc2l0aW9uID09IDIpICU+JQ0KICBncm91cF9ieShjb25zdHJ1Y3RvcklkKSAlPiUgc3VtbWFyaXNlKG4gPSBuKCkpICU+JSBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KYGBge3J9DQojIGR1bW15IHZhcmlhYmxlIHNlY29uZF9wbGFjZV9kcml2ZXIsIGlmIGEgZHJpdmVyIGhhZCBlYXJuZWQgc2Vjb25kIHBsYWNlIGluIHRoZSBwYXN0IHRoZW4gdmFyaWFibGUgPSAxDQpzZWNvbmRfcGxhY2VfZHJpdmVyIDwtIGRmX2FsbCAlPiUgIGZpbHRlcihwb3NpdGlvbiA9PSAyLCApICU+JQ0KICBncm91cF9ieShkcml2ZXJJZCkgJT4lIHN1bW1hcmlzZShuID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKG4pKQ0Kc2Vjb25kX3BsYWNlX2RyaXZlciA8LSBzZWNvbmRfcGxhY2VfZHJpdmVyJGRyaXZlcklkICU+JSBhcy52ZWN0b3IoKQ0KYGBgDQoNCmBgYHtyIHdhcm5pbmcgPSBGQUxTRX0NCiMgY3JlYXRpbmcgZHVtbXkgdmFyaWFibGVzIGZvciBzZWNvbmQgcGxhY2UgYW5kIGlmIHRoZSBkcml2ZXIgY29tcGxldGVkIHRoZSByYWNlDQpkZl9hbGwkc2Vjb25kX3BsYWNlIDwtIHJlY29kZShkZl9hbGwkcG9zaXRpb24sICIyIiA9ICIxIiwgLmRlZmF1bHQgPSAiMCIpDQpkZl9hbGwkcmFjZV9jb21wbGV0ZSA8LSByZWNvZGUoZGZfYWxsJHN0YXR1c0lkLCAiMSIgPSAiMSIsIC5kZWZhdWx0ID0gIjAiKQ0KDQojIHJlY29kaW5nIHRoZSBjb25zdHJ1Y3RvcnMgd2hvIGhhZCBlYXJuZWQgMm5kIHBsYWNlIG1vcmUgdGhhbiAyMCB0aW1lcw0KZGZfYWxsJGNvbnN0cnVjdG9yXzJuZF9wbGFjZSA8LSByZWNvZGUoZGZfYWxsJGNvbnN0cnVjdG9ySWQsICI2IiA9ICI2IiwgIjEiID0gIjEiLCAiMyIgPSAiMyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMTMxIiA9ICIxMzEiLCAiOSIgPSAiOSIsICI0IiA9ICI0IiwgIjI1IiA9ICIyNSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMjIiID0gIjIyIiwgIjMyIiA9ICIzMiIsICI2NiIgPSAiNjYiLCAiMzQiID0gIjM0IiwgZGVmYXVsdCA9ICIwIiwgLm1pc3NpbmcgPSAiMCIpIA0KZGZfYWxsJGNvbnN0cnVjdG9yXzJuZF9wbGFjZSA8LSByZXBsYWNlX25hKGRmX2FsbCRjb25zdHJ1Y3Rvcl8ybmRfcGxhY2UsICIwIikNCg0KIyBpZiB0aGUgZHJpdmVyIGhhZCBldmVyIGVhcm5lZCAybmQgcGxhY2UsIHRoZW4gdGhlIGR1bW15IHZhcmlhYmxlIGVxdWFscyB0byAxDQpkZl9hbGwkaGlzdG9yeV8ybmRfcGxhY2UgPC0gaWZlbHNlKGRmX2FsbCRkcml2ZXJJZCAlaW4lIHNlY29uZF9wbGFjZV9kcml2ZXIsIDEsIDApDQoNCiMgbXV0YXRpbmcgYmlydGhkYXkgYmFzZWQgb24gcmFjZSBkYXRlIGFuZCBkcml2ZXIgZG9iDQpkZl9hbGwgPC0gZGZfYWxsICU+JSBtdXRhdGUoYWdlID0gYXMuaW50ZWdlcihyb3VuZCgoYXMuRGF0ZShkYXRlKSAtIGFzLkRhdGUoZG9iKSkgLyAzNjUuMjUsIDApKSkNCg0KIyBjb252ZXJ0aW5nIHZhcmlhYmxlIHRvIGVpdGhlciBpbnRlZ2Vycw0KZGZfYWxsJHBvc2l0aW9uIDwtIGFzLmludGVnZXIoZGZfYWxsJHBvc2l0aW9uKQ0KZGZfYWxsJGZhc3Rlc3RMYXAgPC0gYXMuaW50ZWdlcihkZl9hbGwkZmFzdGVzdExhcCkNCmRmX2FsbCRyYW5rIDwtIGFzLmludGVnZXIoZGZfYWxsJHJhbmspDQpkZl9hbGwkZmFzdGVzdExhcFNwZWVkIDwtIGFzLmludGVnZXIoZGZfYWxsJGZhc3Rlc3RMYXBTcGVlZCkNCmRmX2FsbCRmYXN0ZXN0TGFwVGltZVNlYyA8LSBwZXJpb2RfdG9fc2Vjb25kcyhtcyhkZl9hbGwkZmFzdGVzdExhcFRpbWUpKQ0KZGZfYWxsJHJhY2VfY29tcGxldGUgPC0gYXMuaW50ZWdlcihkZl9hbGwkcmFjZV9jb21wbGV0ZSkNCmRmX2FsbCRzZWNvbmRfcGxhY2UgPC0gYXMuaW50ZWdlcihkZl9hbGwkc2Vjb25kX3BsYWNlKQ0KZGZfYWxsJG1pbGxpc2Vjb25kcyA8LSBhcy5pbnRlZ2VyKGRmX2FsbCRtaWxsaXNlY29uZHMpDQpkZl9hbGwkY29uc3RydWN0b3JfMm5kX3BsYWNlIDwtIGFzLmludGVnZXIoZGZfYWxsJGNvbnN0cnVjdG9yXzJuZF9wbGFjZSkNCmBgYA0KDQpgYGB7cn0NCiMgc3Vic2V0dGluZyBkZl9hbGwgdG8gY3JlYXRlIGEgc21hbGxlciBkZiBmb3IgcmVncmVzc2lvbg0Kc3Vic2V0IDwtIGRmX2FsbCAlPiUgZmlsdGVyKHllYXIgPD0gMjAxMCkgJT4lIA0KICAgICAgICAgICAgICAgICAgICBzZWxlY3Qoc2Vjb25kX3BsYWNlLCBjb25zdHJ1Y3Rvcl8ybmRfcGxhY2UsIGdyaWQsIHBvaW50cywgcm91bmQsIHJhY2VfY29tcGxldGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWdlLCBoaXN0b3J5XzJuZF9wbGFjZSkNCiMgY29uZmlybWluZyB0aGUgc3Vic2V0IGRvZXMgbm90IGluY2x1ZGUgYW55IE5Bcw0KY29sU3Vtcyhpcy5uYShzdWJzZXQpKQ0KYGBgDQpgYGB7cn0NCiMgc3Vic2V0dGluZyBkZl9hbGwgdG8gY3JlYXRlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldCBmb3IgbWwgcHJhY3RpY2UNCnN1YnNldDIgPC0gZGZfYWxsICU+JSBzZWxlY3QoeWVhciwgY29uc3RydWN0b3JfMm5kX3BsYWNlLCBncmlkLCBsYXBzLCBwb2ludHMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlY29uZF9wbGFjZSwgcmFjZV9jb21wbGV0ZSwgYWdlLCBoaXN0b3J5XzJuZF9wbGFjZSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgcmVuYW1lKHkgPSBzZWNvbmRfcGxhY2UpDQpzdWJzZXQyJHkgPC0gZmFjdG9yKHN1YnNldDIkeSwgbGFiZWxzID0gYygieWVzIiwgIm5vIiksIGxldmVscyA9IDE6MCkNCg0KdHJhaW5pbmcgPC0gc3Vic2V0MiAlPiUgZmlsdGVyKHllYXIgPD0gMjAxMCkgJT4lIHNlbGVjdCgteWVhcikNCnRlc3RpbmcgPC0gc3Vic2V0MiAlPiUgZmlsdGVyKHllYXIgPiAyMDEwICYgeWVhciA8PSAyMDE3KSAlPiUgc2VsZWN0KC15ZWFyKQ0KDQojIGNvbmZpcm1pbmcgdGhlcmUncyBubyBOQXMNCmNvbFN1bXMoaXMubmEodHJhaW5pbmcpKQ0KY29sU3Vtcyhpcy5uYSh0ZXN0aW5nKSkNCmBgYA0KDQpgYGB7cn0NCiMgZXhwb3J0aW5nIGRhdGENCndyaXRlLmNzdihzdWJzZXQsICdkYXRhL3N1YnNldC5jc3YnKQ0Kd3JpdGUuY3N2KHRyYWluaW5nLCAnZGF0YS90cmFpbmluZy5jc3YnKQ0Kd3JpdGUuY3N2KHRlc3RpbmcsICdkYXRhL3Rlc3RpbmcuY3N2JykNCmBgYA0KDQojIyBJbmZlcmVudGlhbCBtb2RlbCB0byBleHBsYWluIHdoeSBhIGRyaXZlciBhcnJpdmVzIGluIHNlY29uZCBwbGFjZSBpbiBhIHJhY2UgYmV0d2VlbiAxOTUwIGFuZCAyMDEwLiB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KYGBge3J9DQojIHJlYWRpbmcgZGF0YQ0Kc3Vic2V0IDwtIHJlYWQuY3N2KCJkYXRhL3N1YnNldC5jc3YiKQ0KYGBgDQoNCkZvciB0aGUgZmlyc3QgcGFydCBvZiB0aGlzIHByb2plY3QsIEkgdXNlZCBsb2dpdCByZWdyZXNzaW9uIHRyeWluZyB0byBleHBsYWluIHdoeSBhIGRyaXZlciBhcnJpdmVzIGluIHNlY29uZCBwbGFjZSBpbiBhIHJhY2UgYmV0d2VlbiAxOTUwIGFuZCAyMDEwLiBNb2RlbDEgcmV0dXJuZWQgYW4gYWRqdXN0ZWQgUl4yXiBvZiAwLjI1LCBzdWdnZXN0aW5nIDI1JSBvZiB0aGUgdmFyaWFuY2UgaW4gd2hldGhlciBhIGRyaXZlciBhcnJpdmVzIHRoZSBzZWNvbmQgaW4gYSBnaXZlbiByYWNlIGlzIGV4cGxhaW5lZCBieSB0aGUgbW9kZWwuIFRoaXMgaXMgbm90IGlkZWFsLCBidXQgd2l0aCB0aGUgY3VycmVudCBpbmZvcm1hdGlvbiBJIGRpZCBub3QgYSBiZXR0ZXIgc29sdXRpb24uIE1vZGVsMiB1c2VzIHRoZSBjZW50ZXIgc2NhbGVkIHZlcnNpb24gb2YgdGhlIGRhdGFzZXQsIHRodXMgb2ZmZXJpbmcgc3RhbmRhcml6ZWQgY29lZmZpY2llbnRzLiBUaGUgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cyBhcmUgdXNlZCB0byBkZXRlcm1pbmUgdGhlIGltcG9ydGFuY2Ugb2YgdmFyaWFibGVzIGluIHRoZSBtb2RlbDEsIGEgbGFyZ2VyIGNvZWZmaWNpZW50IHZhbHVlIHdvdWxkIGluZGljYXRlIGEgbW9yZSBpbXBvcnRhbnQgdmFyaWFibGUuIA0KDQotIGBzZWNvbmRfcGxhY2VgLCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlICh5KSwgd2hlcmUgMSByZXByZXNlbnRzIHRoZSBkcml2ZXIgZmluaXNoZWQgc2Vjb25kIGluIHRoZSBnaXZlbiByYWNlLg0KLSBgcG9pbnRzYCwgdGhlIG51bWJlciBvZiBwb2ludHMgZWFybmVkIGJ5IHRoZSBkcml2ZXIgaW4gdGhhdCByYWNlLiBUaGUgbW9kZWwgaW5jbHVkZWQgYSBzZWNvbmQgZGVncmVlIHBvbHlub21pYWwgdGVybSBvZiBgcG9pbnRzYC4gQXMgdGhlIGNvZWZmaWNpZW50IGF0IGJvdGggZmlyc3QgYW5kIHNlY29uZCBkZWdyZWUgcG9seW5vbWlhbCB0ZXJtcyBhcmUgbmVnYXRpdmUsIHRoZSBjdXJ2ZSBvZiBmZWF0dXJlIGBwb2ludHNgIGlzIGRldGVybWluZWQgdG8gYmUgYSBjb25jYXZlIChjdXJ2ZSBvcGVucyBkb3duKSwgd2hpY2ggbWVhbnMgdGhlIHBvaW50cyBlYXJuZWQgd291bGQgb3ZlciBpbmNyZWFzZSB0aGUgY2hhbmNlIG9mIGEgZHJpdmVyIGVhcm5pbmcgc2Vjb25kIHBsYWNlLCB0aG91Z2ggdGhlIGltcGFjdCBmYWRlcyBhcyB0aGUgcG9pbnRzIGVhcm5lZCBpbmNyZWFzZXMuIEJvdGggZGVncmVlIHRlcm1zIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGF0IDk5LjklIGxldmVsLiANCi0gYHJhY2VfY29tcGxldGVgLCBhIGR1bW15IHZhcmlhYmxlIGJhc2VkIG9uIGBzdGF0dXNJZGAgYW5kIGlzIGNvZGVkIHRvIDEgaWYgdGhlIGRyaXZlciBjb21wbGV0ZWQgdGhlIHJhY2UuIFRoZSBjb2VmZmljaWVudCBzdWdndWVzdHMgdGhhdCBoYXZpbmcgY29tcGxldGVkIHRoZSByYWNlIHN1Y2Nlc3NmdWwgd2lsbCBpbmNyZWFzZSB0aGUgY2hhbmNlIG9mIGVhcm5pbmcgc2Vjb25kIHBsYWNlIGJ5IDkuNDMlLiBUaGlzIGZlYXR1cmUgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhdCA5OS45JSBsZXZlbC4gDQotIGBhZ2VgLCBhZ2Ugb2YgdGhlIGRyaXZlciBhdCB0aGUgdGltZSBvZiB0aGF0IHJhY2UuIFRoaXMgdmFyaWFibGUgaXMgbm90IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW4gdGhlIG1vZGVsLCBpbmRpY2F0ZXMgdGhhdCBkcml2ZXIncyBhZ2UgYXQgdGltZSBvZiByYWNlIGRvZXMgbm90IGFmZmVjdCB0aGUgbGlrZWxpaG9vZCBvZiBhIGRyaXZlciBhcnJpdmVzIHRoZSBzZWNvbmQgaW4gYSByYWNlLiANCi0gYGhpc3RvcnlfMm5kX3BsYWNlYCwgZHVtbXkgdmFyaWFibGUgZm9yIHdoZXRoZXIgdGhlIGRyaXZlciBoYXMgYSBoaXN0b3J5IG9mIGVhcm5pbmcgc2Vjb25kIHBsYWNlcy4gVGhpcyBmZWF0dXJlIGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgYXQgOTklIGxldmVsLCBhbmQgaWYgdGhlIGRyaXZlciBoYWQgYSBoaXN0b3J5IG9mIGVhcm5pbmcgc2Vjb25kIHBsYWNlcywgdGhlIGNoYW5jZSBvZiB0aGF0IGRyaXZlciB3aW5uaW5nIGFub3RoZXIgc2Vjb25kIHBsYWNlIGRlY3JlYXNlcyBieSAwLjklLiBPbmUgcG9zc2libGUgaW50ZXJwcmV0YXRpb24gd291bGQgYmUgZWFybmluZyBzZWNvbmQgcGxhY2VzIHdvdWxkIGhlbHAgYSBkcml2ZXIgaW1wcm92ZSBza2lsbCwgc28gdGhhdCBkcml2ZXIgY2FuIGVhcm4gZmlyc3QgcGxhY2VzIGluIHRoZSBmdXR1cmUuIA0KLSBgY29uc3RydWN0b3JfMm5kX3BsYWNlYCwgYSBsaXN0IG9mIGNvbnN0cnVjdG9ycy9jbHVicyBJRHMgdGhhdCBoYWQgZWFybmVkIHNlY29uZCBwbGFjZSBmb3IgbW9yZSB0aGFuIDIwIHRpbWVzLiBJbiB0aGUgYWN0dWFsIG1vZGVsLCB0aGlzIGZlYXR1cmUgaXMgZmFjdG9yaXplZCwgbWVhbmluZyBlYWNoIG9mIHRoZSBjb25zdHJ1Y3RvcklkIGlzIHRyZWF0ZWQgYXMgYW4gaW5kZXBlbmRlbnQgZHVtbXkgdmFyaWFibGUuIEZvciBpbnN0YW5jZSwgaWYgYSBkcml2ZXIgY29tZXMgZnJvbSBSZW5hdWx0IG9yIE1lcmNlZGVzLCB0aGUgbGlrZWxpaG9vZCBvZiB0aGUgZHJpdmVyIGVhcm5pbmcgc2Vjb25kIHBsYWNlIGVhY2ggZGVjcmVhc2UgYnkgMS43NiUgYW5kIDcuMjYlLiBIb3dldmVyLCBpZiB0aGUgZHJpdmVyIGNvbWVzIGZyb20gRmVycmFyaSdzIGNsdWIsIHRoZSBjaGFuY2Ugb2YgdGhhdCBkcml2ZXIgZWFybmluZyBzZWNvbmQgcGxhY2UgaW5jcmVhc2VzIGJ5IDEuMjQlLiANCi0gYGdyaWRgLCBhIGdyaWQgbnVtYmVyIGZvciBlYWNoIGRyaXZlciBhdCB0aGUgcmFjZSBiZWdpbm5pZy4gU2ltaWxhciB0byB0aGUgcHJldmlvdXMgdmFyaWFibGUsIGBncmlkYCBpcyBmYWN0b3JpemVkIGluIHRoZSBtb2RlbDEuIFRoZSBzdGFydGluZyBwb3NpdGlvbiBkbyBoZWxwIGV4cGxhaW5pbmcgd2hldGhlciBhIGRyaXZlciBhcnJpdmVzIHNlY29uZCBpbiBhIGdpdmVuIHJhY2UuIFRoZSByZWdyZXNzaW9uIG91dGNvbWUgaW5kaWNhdGVzIHRoYXQgaWYgdGhlIGRyaXZlciBpcyBsb2NhdGVkIGluIHRoZSBmaXJzdCBncmlkIHdoZW4gc3RhcnRpbmcsIHRoZSBjaGFuY2Ugb2YgaGltIGVhcm5pbmcgc2Vjb25kIHBsYWNlIGRlY3JlYXNlcyBieSA1LjY0JSAobWF5YmUgZW5kcyB1cCB3aW5uaW5nIHRoZSByYWNlKS4gQnV0IGlmIHRoZSBkcml2ZXIgaXMgbG9jYXRlZCBpbiBhIGdyaWQgc2xvdCBmcm9tIDIgdG8gNCwgdGhlIGNoYW5jZSBvZiBoaW0gYXJyaXZlcyB0aGUgc2Vjb25kIGluIHRoZSByYWNlIGluY3JlYXNlcy4gDQoNCk92ZXJhbGwsIGFjY29yZGluZyB0byBzdGFuZGFyZGl6ZWQgY29lZmZpY2llbnQgaW4gbW9kZWwyLCB0aGUgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZSBpcyB0aGUgcG9seW5vbWlhbCB0ZXJtcyBvZiBgcG9pbnRzYC4gVGhlIG5leHQgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZSBpcyBgaGlzdG9yeV8ybmRfcGxhY2VgIChpZiB0aGUgZHJpdmVyIGhhdmUgYWxyZWFkeSBlYXJuZWQgc2Vjb25kIHBsYWNlKS4gDQoNCkkgYWxzbyBpbmNsdWRlZCBhIHNlcmllcyBvZiBpbnRlcmFjdGlvbiB0ZXJtcywgYWxsIG9mIHRoZSBpbnRlcmFjdGlvbiB0ZXJtcyBhcmUgc3RhdGlzdGljYWxseSBzaWduaWZpY25hdCBhdCA5OS45JSBsZXZlbC4gQmVsb3cgSSB3b3VsZCBwcm92aWRlIG1hcmdpbmFsIGVmZmVjdCBvZiB0aGUgbW9zdCBpbXBvcnRhbnQgaW50ZXJhY3Rpb246DQoNCi0gYGNvbnN0cnVjdG9yXzJuZF9wbGFjZWAgYW5kIGBwb2ludHNgLCBnaXZlbiB0aGUgc2F2ZSBsZXZlbCBvZiBwb2ludHMgZWFybmVkLCB0aGUgaW1wYWN0IG9mIHdoZXRoZXIgdGhlIGNvbnN0cnVjdG9yIGlzIGEgZnJlcXVlbnQgc2Vjb25kIHBsYWNlIGVhcm5lci4NCi0gYGNvbnN0cnVjdG9yXzJuZF9wbGFjZWAgYW5kIGByYWNlX2NvbXBsZXRlYCwgZ2l2ZW4gdGhlIGRyaXZlciBoYWQgY29tcGxldGVkIHRoZSByYWNlLCB0aGUgaW1wYWN0IG9mIHdoZXRoZXIgdGhlIGNvbnN0cnVjdG9yIGlzIGEgZnJlcXVlbnQgc2Vjb25kIHBsYWNlIGVhcm5lci4NCi0gYGdyaWRgIGFuZCBgcG9pbnRzYCwgZ2l2ZW4gdGhlIHNhdmUgbGV2ZWwgb2YgcG9pbnRzIGVhcm5lZCwgdGhlIGltcGFjdCBvZiBhIGdyaWQgcG9zaXRpb24gY2xvc2VyIHRvIGZyb250Lg0KLSBgZ3JpZGAgYW5kIGByYWNlX2NvbXBsZXRlYCwgZ2l2ZW4gdGhlIGRyaXZlciBoYWQgY29tcGxldGVkIHRoZSByYWNlLCB0aGUgaW1wYWN0IG9mIGEgZ3JpZCBwb3NpdGlvbiBjbG9zZXIgdG8gZnJvbnQuDQotIGBwb2ludHNgIGFuZCBgaGlzdG9yeV9zZWNvbmRfcGxhY2VgLCBpZiB0aGUgZHJpdmVyIGhhZCBhIGhpc3Rvcnkgb2YgZWFybmluZyBzZWNvbmQgcGxhY2UsIHdoYXQncyB0aGUgaW1wYWN0IG9mIG1vcmUgcG9pbnRzIGVhcm5lZC4gVGhpcyBpbnRlcmFjdGlvbiB0ZXJtIGlzIHRlc3RlZCB0byBiZSB0aGUgbW9zdCBpbXBvcnRhbnQgaW50ZXJhY3Rpb24gdGVybSAoYWNjb3JkaW5nIHRvIHN0YW5kYXJpemVkIGNvZWZmaWNpZW50IGluIG1vZGVsIDIpLiBJZiB0aGUgZHJpdmVyIGhhZCBhIGhpc3Rvcnkgb2YgZXJhbmluZyBzZWNvbmQgcGxhY2UsIDEgZXh0cmEgcG9pbnQgZWFybmVkIHdvdWxkIG1ha2UgdGhhdCBkcml2ZXIgNC42NSUgbW9yZSBsaWtlbHkgdG8gZWFybiBhbm90aGVyIHNlY29uZCBwbGFjZS4gDQotIGByb3VuZGAgYW5kIGByYWNlX2NvbXBsZXRlYCwgaG9sZGluZyB0aGUgcm91bmQgbnVtYmVyIGluIGEgY2lyY3VpdCBjb25zdGFudCwgd2hhdCdzIHRoZSBpbXBhY3Qgb2YgY29tcGxldGluZyB0aGUgcmFjZSBpbiBkZXRlcm1pbmluZyB3aGV0aGVyIHRoZSBkcml2ZXIgZWFybnMgdGhlIHNlY29uZCBwbGFjZS4gDQoNCkxhc3RseSwgSSBiZWxpZXZlIGl0J3Mgc2ltcGx5IGFuIGFzc29jaWF0aW9uIHdlIG9ic2VydmVkIGluc3RlYWQgb2YgYW4gImV4cGxhbmF0aW9uIiBvciBjYXVzYXRpb24uIEluIG9yZGVyIHRvIGZ1bGZpbGwgYSBjYXVzYWwgcmVsYXRpb24sIHRocmVlIGNyaXRlcmlhcyBuZWVkIHRvIGJlIG1ldDogc3BhdGlhbCBjb250aW51aXR5LCB0ZW1wb3JhbCBzdWNjZXNzaW9uIGFuZCBjb25zdGFudCBjb25qdW5jdGlvbi4gQW1vbmcgdmFyaWFibGVzIGRpc2N1c3NlZCBwcmV2aW91c2x5LCB0aGUgcmVncmVzc29ycyBkbyBub3QgcHJlY2VkZXMgdGhlIHkgdmFyaWFibGUgKHNlY29uZCBwbGFjZSkgaW4gdGltZS4gQXQgdGhlIHNhbWUgdGltZSwgd2UgY2Fubm90IGVuc3VyZSB0aGUgaW5kZXBlbmRlbmNlIG9yIHVuaXQgaG9tb2dlbmVpdHkgYW1vbmcgdmFyaWFibGVzLiBUaGVyZWZvcmUsIEkgc3VnZ2VzdCB0aGUgcmVsYXRpb25zaGlwIHdlIGV4cGxhaW5lZCBpcyBzaW1wbHkgYW4gYXNzb2NpYXRpb24uIA0KDQojIyMgYSkgTG9naXN0aWMgcmVncmVzc2lvbiB3aXRob3V0IHNjYWxpbmcNCmBgYHtyfQ0KIyBsb2dpdCBtb2RlbCB3aXRob3V0IHNjYWxpbmcgDQptb2RlbDEgPC0gbG0oc2Vjb25kX3BsYWNlIH4gIHBvbHkocG9pbnRzLCAyKSArIHJhY2VfY29tcGxldGUgKyBhZ2UgKyBoaXN0b3J5XzJuZF9wbGFjZSArIA0KICAgICAgICAgICAgICAgZmFjdG9yKGNvbnN0cnVjdG9yXzJuZF9wbGFjZSkgKyBmYWN0b3IoZ3JpZCkgKyBjb25zdHJ1Y3Rvcl8ybmRfcGxhY2U6cG9pbnRzICsNCiAgICAgICAgICAgICAgIGNvbnN0cnVjdG9yXzJuZF9wbGFjZTpyYWNlX2NvbXBsZXRlICsgZ3JpZDpwb2ludHMgKyBncmlkOnJhY2VfY29tcGxldGUgKyANCiAgICAgICAgICAgICAgIHBvaW50czpoaXN0b3J5XzJuZF9wbGFjZSArIHJvdW5kOnJhY2VfY29tcGxldGUsDQogICAgICAgICAgICAgZGF0YSA9IHN1YnNldCkNCnByaW50KHN1bW1hcnkobW9kZWwxKSkNCmBgYA0KDQojIyMgYikgTG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIHNjYWxpbmcsIHRvIHByb3ZpZGUgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cw0KDQpgYGB7cn0NCiMgbW9kZWwgYWZ0ZXIgc2NhbGluZywgb2ZmZXJpbmcgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cw0Kc2NhbGVkX3N1YnNldCA8LSBzY2FsZShzdWJzZXQsIGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkgJT4lIGFzLmRhdGEuZnJhbWUoKQ0KbW9kZWwyIDwtIGxtKHNlY29uZF9wbGFjZSB+ICBwb2x5KHBvaW50cywgMikgKyByYWNlX2NvbXBsZXRlICsgYWdlICsgaGlzdG9yeV8ybmRfcGxhY2UgKyANCiAgICAgICAgICAgICAgIGNvbnN0cnVjdG9yXzJuZF9wbGFjZSArIGdyaWQgKyBjb25zdHJ1Y3Rvcl8ybmRfcGxhY2U6cG9pbnRzICsNCiAgICAgICAgICAgICAgIGNvbnN0cnVjdG9yXzJuZF9wbGFjZTpyYWNlX2NvbXBsZXRlICsgZ3JpZDpwb2ludHMgKyBncmlkOnJhY2VfY29tcGxldGUgKyANCiAgICAgICAgICAgICAgIHBvaW50czpoaXN0b3J5XzJuZF9wbGFjZSArIHJvdW5kOnJhY2VfY29tcGxldGUsIA0KICAgICAgICAgICAgICAgZGF0YSA9IHNjYWxlZF9zdWJzZXQpDQpwcmludChzdW1tYXJ5KG1vZGVsMikpDQpgYGANCg0KDQoNCiMjIFByZWRpY3RpdmUgbW9kZWxzIHVzaW5nIDE5NTAgdG8gMjAxMCBhcyB0cmFpbmluZyBzZXQsIDIwMTEgdG8gMjAxNyBhcyB0ZXN0aW5nIHNldCB0byBwcmVkaWN0IGRyaXZlcnMgdGhhdCBjb21lIGluIHNlY29uZCBwbGFjZS4gey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCkZvciB0aGlzIHBhcnQgb2YgdGhlIHByb2plY3QsIEkgc3BsaXR0ZWQgdGhlIGRhdGFzZXQgSSBjcmVhdGVkIGFuZCBhc3NpZ25lZCBkYXRhIGZyb20geWVhciAxOTUwIHRvIDIwMTAgdG8gdHJhaW5pbmcgc2V0LCBhbmQgMjAxMSB0byAyMDE3IHRvIHRlc3Rpbmcgc2V0LiBTbGlnaHRseSBkaWZmZXJlbnQgZnJvbSB0aGUgZGF0YXNldCBJIHVzZWQgZm9yIHRoZSBpbmZlcmVudGlhbCBtb2RlbCwgSSBpbmNsdWRlZCB2YXJpYWJsZSBgbGFwc2AsIHdoaWNoIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiBsYXBzIGEgZHJpdmVyIGNvbXBsZXRlZCBpbiBhIGdpdmVuIHJhY2UuIA0KDQpJbiB0aGUgZm9sbG93aW5nIHByZWRpY2l0aXZlIG1vZGVscywgSSBleGx1Y2RlZCBgY29uc3RydWN0b3JfMm5kX3BsYWNlYCB2YXJpYWJsZSBhcyBpdCB3b3VsZCBpbmNyZWFzZSB0aGUgb3ZlcmZpdHRpbmcgaW4gdGhlIHRlc3Rpbmcgc2V0LiBGdXJ0aGVybW9yZSwgSSBhbHNvIGNoYW5nZWQgdGhlIHNldHMgb2YgaW50ZXJhY3Rpb24gdGVybXMgdG8gaW5jbHVkZS4gDQoNCi0gYGdyaWRgIGFuZCBgcG9pbnRzYDsgYGNvbnN0cnVjdG9yXzJuZF9wbGFjZWAgYW5kIGBwb2ludHNgOyBgZ3JpZGAgYW5kIGByYWNlX2NvbXBsZXRlYCBhcmUgdGhlIHRocmVlIHNldHMgb2YgaW50ZXJhdGlvbnMgSSBrZXB0LiBJIGluY2x1ZGVkIHRocmVlIG5ldyBzZXRzIG9mIGludGVyYWN0aW9ucy4NCi0gYHBvaW50c2AgYW5kIGByYWNlX2NvbXBsZXRlYA0KLSBgcG9pbnRzYCBhbmQgYGxhcHNgDQotIGByYWNlX2NvbXBsZXRlYCBhbmQgYGxhcHNgDQoNCkFzIHRoZSBwcmltYXJ5IGZvY3VzIG9mIHN1cGVydmlzZWQgbGVhcm5pbmcgbWV0aG9kcyBpcyBub3QgaW5mZXJlbmNlIGJ1dCBwcmVkaWN0aW9uLCBJIHJldGFpbmVkIHNvbWUgZmVhdHVyZXMgLyBpbnRlcmFjdGlvbnMgZXZlbiB0aG91Z2ggdGhleSBkb24ndCBtYWtlIHRvbyBtdWNoIHNlbnNlLCBhcyBsb25nIGFzIHRoZXkgaGVscCBwcmVkaWN0aW5nIGJldHRlci4gDQoNCmBgYHtyfQ0KIyBsb2FkaW5nIGRhdGENCnRyYWluaW5nIDwtIHJlYWQuY3N2KCJkYXRhL3RyYWluaW5nLmNzdiIpDQp0ZXN0aW5nIDwtIHJlYWQuY3N2KCJkYXRhL3Rlc3RpbmcuY3N2IikNCmBgYA0KDQpgYGB7cn0NCiMgc2V0IHJhbmRvbSBzZWVkDQpzZXQuc2VlZCgyMDIwMDUxMCkNCmBgYA0KDQpgYGB7cn0NCiMgU2V0dGluZyB0cmFpbiBjb250cm9sIHRlcm1zDQp0cmFpbl9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeSkNCmBgYA0KDQojIyMgYSkgR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsDQpgYGB7ciB3YXJuaW5nID0gRkFMU0UsIGNhY2hlID0gVFJVRX0NCmdsbSA8LSB0cmFpbih5IH4gIGdyaWQgKyBwb2x5KHBvaW50cywgMikgKyByYWNlX2NvbXBsZXRlICsgDQogICAgICAgICAgICAgICBhZ2UgKyBoaXN0b3J5XzJuZF9wbGFjZSArIGdyaWQ6cG9pbnRzICArIA0KICAgICAgICAgICAgICAgcG9pbnRzOnJhY2VfY29tcGxldGUgKyBjb25zdHJ1Y3Rvcl8ybmRfcGxhY2U6cG9pbnRzICsgbGFwczpwb2ludHMgKyANCiAgICAgICAgICAgICAgIGxhcHM6cmFjZV9jb21wbGV0ZSArIGdyaWQ6cmFjZV9jb21wbGV0ZSwgDQogICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAiZ2xtIiwNCiAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIikpDQp6X2dsbSA8LSBwcmVkaWN0KGdsbSwgbmV3ZGF0YSA9IHRlc3RpbmcpDQpwcmludChjb25mdXNpb25NYXRyaXgoel9nbG0sIHRlc3RpbmckeSkpDQpgYGANCg0KIyMjIGIpIEJhZ2dpbmcgKHRyZWViYWcpDQpgYGB7ciB3YXJuaW5nID0gRkFMU0UsIGNhY2hlID0gVFJVRX0NCnRyZWViYWcgPC0gdHJhaW4oeSB+IGdyaWQgKyBwb2x5KHBvaW50cywgMikgKyByYWNlX2NvbXBsZXRlICsgDQogICAgICAgICAgICAgICBhZ2UgKyBoaXN0b3J5XzJuZF9wbGFjZSArIGdyaWQ6cG9pbnRzICArIA0KICAgICAgICAgICAgICAgcG9pbnRzOnJhY2VfY29tcGxldGUgKyBjb25zdHJ1Y3Rvcl8ybmRfcGxhY2U6cG9pbnRzICsgbGFwczpwb2ludHMgKyANCiAgICAgICAgICAgICAgIGxhcHM6cmFjZV9jb21wbGV0ZSArIGdyaWQ6cmFjZV9jb21wbGV0ZSAsIA0KICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAidHJlZWJhZyIsDQogICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIikpDQoNCnpfdHJlZWJhZyA8LSBwcmVkaWN0KHRyZWViYWcsIG5ld2RhdGEgPSB0ZXN0aW5nKQ0KcHJpbnQoY29uZnVzaW9uTWF0cml4KHpfdHJlZWJhZywgdGVzdGluZyR5KSkNCmBgYA0KDQojIyMgYykgUmFuZG9tIEZvcmVzdA0KYGBge3Igd2FybmluZyA9IEZBTFNFLCBjYWNoZSA9IFRSVUV9DQpyZjEgPC0gdHJhaW4oeSB+IGdyaWQgKyBwb2x5KHBvaW50cywgMikgKyByYWNlX2NvbXBsZXRlICsgDQogICAgICAgICAgICAgICBhZ2UgKyBoaXN0b3J5XzJuZF9wbGFjZSArIGdyaWQ6cG9pbnRzICArIA0KICAgICAgICAgICAgICAgcG9pbnRzOnJhY2VfY29tcGxldGUgKyBjb25zdHJ1Y3Rvcl8ybmRfcGxhY2U6cG9pbnRzICsgbGFwczpwb2ludHMgKyANCiAgICAgICAgICAgICAgIGxhcHM6cmFjZV9jb21wbGV0ZSArIGdyaWQ6cmFjZV9jb21wbGV0ZSwgZGF0YSA9IHRyYWluaW5nLCAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgIG50cmVlcyA9IDEwMDAsIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wpDQp6X3JmMTwtIHByZWRpY3QocmYxLCBuZXdkYXRhID0gdGVzdGluZykNCnByaW50KGNvbmZ1c2lvbk1hdHJpeCh6X3JmMSwgdGVzdGluZyR5KSkNCmBgYA0KDQpgYGB7ciB3YXJuaW5nID0gRkFMU0UsIGNhY2hlID0gVFJVRX0NCnJmMiA8LSB0cmFpbih5IH4gZ3JpZCArIHBvbHkocG9pbnRzLCAyKSArIHJhY2VfY29tcGxldGUgKyANCiAgICAgICAgICAgICAgIGFnZSArIGhpc3RvcnlfMm5kX3BsYWNlICsgZ3JpZDpwb2ludHMgICsgDQogICAgICAgICAgICAgICBwb2ludHM6cmFjZV9jb21wbGV0ZSArIGNvbnN0cnVjdG9yXzJuZF9wbGFjZTpwb2ludHMgKyBsYXBzOnBvaW50cyArIA0KICAgICAgICAgICAgICAgbGFwczpyYWNlX2NvbXBsZXRlICsgZ3JpZDpyYWNlX2NvbXBsZXRlLCBkYXRhID0gdHJhaW5pbmcsICBtZXRob2QgPSAicmYiLA0KICAgICAgICAgICAgbnRyZWVzID0gNTAwLCB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sKQ0Kel9yZjIgPC0gcHJlZGljdChyZjIsIG5ld2RhdGEgPSB0ZXN0aW5nKQ0KcHJpbnQoY29uZnVzaW9uTWF0cml4KHpfcmYyLCB0ZXN0aW5nJHkpKQ0KYGBgDQoNCiMjIyBOZXVyYWwgTmV0d29yayAoTUxQKQ0KYGBge3Igd2FybmluZyA9IEZBTFNFLCBjYWNoZSA9IFRSVUV9DQpubmV0R3JpZCA8LSBleHBhbmQuZ3JpZCguZGVjYXkgPSBjKDAsIDAuMDEsIC4xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIC5zaXplID0gYygxOjEwKSkNCm5uIDwtIHRyYWluKHkgfiBncmlkICsgcG9seShwb2ludHMsIDIpICsgcmFjZV9jb21wbGV0ZSArIA0KICAgICAgICAgICAgICAgYWdlICsgaGlzdG9yeV8ybmRfcGxhY2UgKyBncmlkOnBvaW50cyAgKyANCiAgICAgICAgICAgICAgIHBvaW50czpyYWNlX2NvbXBsZXRlICsgY29uc3RydWN0b3JfMm5kX3BsYWNlOnBvaW50cyArIGxhcHM6cG9pbnRzICsgDQogICAgICAgICAgICAgICBsYXBzOnJhY2VfY29tcGxldGUgKyBncmlkOnJhY2VfY29tcGxldGUsIA0KICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAibm5ldCIsDQogICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCB0dW5lR3JpZCA9IG5uZXRHcmlkLA0KICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLCB0cmFjZSA9IEZBTFNFKQ0Kel9ubiA8LSBwcmVkaWN0KG5uLCBuZXdkYXRhID0gdGVzdGluZykNCnByaW50KGNvbmZ1c2lvbk1hdHJpeCh6X25uLCB0ZXN0aW5nJHkpKQ0KYGBgDQoNCiMjIFByZWRpY3RpdmUgbW9kZWwgZXZhbHVhdGlvbg0KT3ZlcmFsbCwgdGhlIGJlc3QgcHJlZGljdGl2ZSBtb2RlbCBmcm9tIHRoZSBhbGdvcml0aG1zL21vZGVscyBhYm92ZSBpcyB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCB3aXRoIDEwMDAgdHJlZXMuIEluIHRoYXQgbW9kZWwsIHRoZSBhY2N1cmFjeSBzY29yZSBpcyAwLjk1NiwgbWVhbmluZyA5NS42JSBvZiB0aGUgcHJlZGljdGVkIHkgaW4gdGhlIHRlc3Rpbmcgc2V0IG1hdGNoZWQgdGhlIGFjdHVhbCB5LiBCcmVha2luZyBkb3duIHRoZSBjb25mdXNpb24gbWF0cml4LCB0aGUgbnVtYmVyIG9mIHRydWUgcG9zaXRpdmVzIGFuZCB0cnVlIG5lZ2F0aXZlcyBpbiB0aGlzIG1vZGVsIGFyZSBib3RoIHRoZSBoaWdoZXN0IGFtb25nIGFsbCBtb2RlbHMgSSB0cmllZC4gVGhlIGNvdW50IG9mIGZhbHNlIHBvc2l0aXZlcyBpcyAxMzEgYW5kIHRoZSBjb3VudCBvZiBmYWxzZSBuZWdhdGl2ZSBpcyBqdXN0IDEuIA0KDQpBY2NvcmRpbmcgdG8gdGhlIGB2YXJJbXBgIGZ1bmN0aW9uICh2YXJpYWJsZSBpbXBvcnRhbmNlKSwgdGhlIHRvcCA1IG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzIGluIHRoaXMgbW9kZWwgYXJlOiBmaXJzdCBhbmQgc2Vjb25kIGRlZ3JlZSBwb2x5bm9taWFsIHRlcm0gb2YgYHBvaW50c2AsIGludGVyYWN0aW9uIHRlcm0gYmV0d2VlbiBgcmFjZV9jb21wbGV0ZWAgYW5kIGBwb2ludHNgLCBgcG9pbnRzYCBhbmQgYGxhcHNgLCBhcyB3ZWxsIGFzIHZhcmlhYmxlIGFnZS4gDQoNCmBgYHtyfQ0KaW1wX3JhbmsgPC0gdmFySW1wKHJmMSkNCg0KI3NhdmluZyB2YXJpYWJsZSBpbXBvcnRhbmNlIGZpbGUNCnNhdmVSRFMoaW1wX3JhbmssIGZpbGUgPSAiZGF0YS9pbXBfcmFuay5yZHMiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBjcmVhdGUgdmlzdWFsaXphdGlvbg0KaW1wX3JhbmsgPC0gcmVhZFJEUygiZGF0YS9pbXBfcmFuay5yZHMiKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCnAxIDwtIGdncGxvdChkYXRhID0gaW1wX3JhbmssIGFlcyh4ID0gT3ZlcmFsbCkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgICAgICAgdGhlbWVfZWNvbm9taXN0KCkgKyBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UgUmFua2luZyIpDQoNCnAxDQpgYGANCg0KYGBge3J9DQpnZ3NhdmUoInBsb3QxLnBuZyIpDQpgYGANCg0K