Required packages

library(dplyr)
library(lubridate)
library(tidyr)
library(Hmisc)
library(infotheo)

Executive Summary

Data Preprocessing is a process and the collection of operations needed to prepare all forms of untidy data for statistical analysis. The 5 major tasks for data include Get, Understand, Tidy & Manipulate, Scan and Transform. To achieve the first task, we located the right data sets and imported them using the Base R functions. Then, we merged the data sets to create a single data set for further preprocessing.

Next, we inspected the data set to understand the various aspects of it. We checked the attributes (metadata) as well as the structure of the data set to find out details such as types of variables, data structures, dimensions, etc. We made some appropriate changes in our data set such as data type conversions and labelling factor variables to make more sense of the data.

We then checked whether the data followed the tidy data principles. As each variable in our data did not have its own column, it was not in a tidy format. Hence, we reshaped it into a tidy format using appropriate method. Then, we created two new variables from the existing variables using the mutate function.

Afterwards, we scanned the data for missing values and inconsistencies. Since one of the variables in our data set had missing values, we applied basic imputation technique to replace them. Next, we scanned all numeric variables for outliers using box plot. We used multiple techniques to deal with the outliers including imputation, excluding outliers and capping. Finally, we applied transformation to one of the variables to change the scale for better understanding of the variable. We applied both equal width (distance) binning and equal depth (frequency) binning methods to discretize/transform the numerical variable into categorical counterpart.

Data

We are going to use three data sets: World Cups, World Cup Players, World Cup Matches. The World Cups data set contains information about all the World Cups held to date. It consists of the following variables:

The World Cup Matches data set contains information regarding all of the matches played in the World Cups held to date. It consists of the following variables:

The World Cup Players data set contains information regarding all of the players who played in the World Cup matches held to date. It consists of the following variables:

Data Source: https://www.kaggle.com/abecklas/fifa-world-cup

WorldCup_data <- read.csv("WorldCups.csv")
head(WorldCup_data)
WorldCupMatches_data <- read.csv("WorldCupMatches.csv")
head(WorldCupMatches_data)
WorldCupPlayers_data <- read.csv("WorldCupPlayers.csv")
head(WorldCupPlayers_data)
merged_data <- inner_join(WorldCupPlayers_data, WorldCupMatches_data, by = "MatchID")
merged_data <- inner_join(merged_data, WorldCup_data, by = "Year")
head(merged_data)

Explanation:

Our first step was to import the data sets using read.csv (Base R function) since the data was stored in csv files. Then, we used inner_join to merge World Cup Players data set with World Cup Matches data set by the key variable: MatchID, which is common in both data sets. Inner join retains the rows with matching key variable values in both data sets. Finally, using inner_join once again, we merged the resulting data set (World Cup Players + World Cup Matches) with World Cups data set by the common variable: Year. As a result, a single data set was created for our report.

Understand

attributes(merged_data)$class
[1] "data.frame"
attributes(merged_data)$names
 [1] "RoundID.x"            "MatchID"              "Team.Initials"        "Coach.Name"          
 [5] "Line.up"              "Shirt.Number"         "Player.Name"          "Position"            
 [9] "Event"                "Year"                 "Datetime"             "Stage"               
[13] "Stadium"              "City"                 "Home.Team.Name"       "Home.Team.Goals"     
[17] "Away.Team.Goals"      "Away.Team.Name"       "Win.conditions"       "Attendance.x"        
[21] "Half.time.Home.Goals" "Half.time.Away.Goals" "Referee"              "Assistant.1"         
[25] "Assistant.2"          "RoundID.y"            "Home.Team.Initials"   "Away.Team.Initials"  
[29] "Country"              "Winner"               "Runners.Up"           "Third"               
[33] "Fourth"               "GoalsScored"          "QualifiedTeams"       "MatchesPlayed"       
[37] "Attendance.y"        
str(merged_data)
'data.frame':   39256 obs. of  37 variables:
 $ RoundID.x           : int  201 201 201 201 201 201 201 201 201 201 ...
 $ MatchID             : int  1096 1096 1096 1096 1096 1096 1096 1096 1096 1096 ...
 $ Team.Initials       : Factor w/ 82 levels "ALG","ANG","ARG",..: 26 47 26 47 26 47 26 47 26 47 ...
 $ Coach.Name          : Factor w/ 335 levels "ACOSTA Nelson (URU)",..: 50 169 50 169 50 169 50 169 50 169 ...
 $ Line.up             : Factor w/ 2 levels "N","S": 2 2 2 2 2 2 2 2 2 2 ...
 $ Shirt.Number        : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Player.Name         : Factor w/ 7663 levels "?URI?I?","A BAUTISTA",..: 317 5519 4713 3865 1988 5988 482 2990 2035 1668 ...
 $ Position            : Factor w/ 4 levels "","C","GK","GKC": 3 3 1 1 1 2 1 1 1 1 ...
 $ Event               : Factor w/ 1894 levels "","G1'","G1' G42'",..: 1 1 308 527 1 1 325 1 1 1 ...
 $ Year                : int  1930 1930 1930 1930 1930 1930 1930 1930 1930 1930 ...
 $ Datetime            : Factor w/ 602 levels "01 Jul 1950 - 15:00 ",..: 205 205 205 205 205 205 205 205 205 205 ...
 $ Stage               : Factor w/ 23 levels "Final","First round",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ Stadium             : Factor w/ 181 levels "Arena Amazonia",..: 125 125 125 125 125 125 125 125 125 125 ...
 $ City                : Factor w/ 151 levels "Alicante ","Antibes ",..: 84 84 84 84 84 84 84 84 84 84 ...
 $ Home.Team.Name      : Factor w/ 78 levels "Algeria","Angola",..: 24 24 24 24 24 24 24 24 24 24 ...
 $ Home.Team.Goals     : int  4 4 4 4 4 4 4 4 4 4 ...
 $ Away.Team.Goals     : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Away.Team.Name      : Factor w/ 83 levels "Algeria","Angola",..: 46 46 46 46 46 46 46 46 46 46 ...
 $ Win.conditions      : Factor w/ 43 levels " "," win on penalties (2 - 3) ",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Attendance.x        : int  4444 4444 4444 4444 4444 4444 4444 4444 4444 4444 ...
 $ Half.time.Home.Goals: int  3 3 3 3 3 3 3 3 3 3 ...
 $ Half.time.Away.Goals: int  0 0 0 0 0 0 0 0 0 0 ...
 $ Referee             : Factor w/ 366 levels "ABD EL FATAH Essam (EGY)",..: 192 192 192 192 192 192 192 192 192 192 ...
 $ Assistant.1         : Factor w/ 387 levels "ABDUL HAMID Halim (MAS)",..: 87 87 87 87 87 87 87 87 87 87 ...
 $ Assistant.2         : Factor w/ 408 levels "ABDUL HAMID Halim (MAS)",..: 292 292 292 292 292 292 292 292 292 292 ...
 $ RoundID.y           : int  201 201 201 201 201 201 201 201 201 201 ...
 $ Home.Team.Initials  : Factor w/ 77 levels "ALG","ANG","ARG",..: 25 25 25 25 25 25 25 25 25 25 ...
 $ Away.Team.Initials  : Factor w/ 82 levels "ALG","ANG","ARG",..: 47 47 47 47 47 47 47 47 47 47 ...
 $ Country             : Factor w/ 15 levels "Argentina","Brazil",..: 14 14 14 14 14 14 14 14 14 14 ...
 $ Winner              : Factor w/ 9 levels "Argentina","Brazil",..: 9 9 9 9 9 9 9 9 9 9 ...
 $ Runners.Up          : Factor w/ 10 levels "Argentina","Brazil",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Third               : Factor w/ 14 levels "Austria","Brazil",..: 14 14 14 14 14 14 14 14 14 14 ...
 $ Fourth              : Factor w/ 16 levels "Austria","Belgium",..: 16 16 16 16 16 16 16 16 16 16 ...
 $ GoalsScored         : int  70 70 70 70 70 70 70 70 70 70 ...
 $ QualifiedTeams      : int  13 13 13 13 13 13 13 13 13 13 ...
 $ MatchesPlayed       : int  18 18 18 18 18 18 18 18 18 18 ...
 $ Attendance.y        : Factor w/ 20 levels "1.045.246","1.545.791",..: 17 17 17 17 17 17 17 17 17 17 ...
merged_data$Shirt.Number <- as.factor(merged_data$Shirt.Number)
class(merged_data$Shirt.Number)
[1] "factor"
merged_data$Event <- as.character(merged_data$Event)
class(merged_data$Event)
[1] "character"
merged_data$Year <- as.factor(merged_data$Year)
class(merged_data$Year)
[1] "factor"
merged_data$Datetime <- dmy_hm(merged_data$Datetime)
class(merged_data$Datetime)
[1] "POSIXct" "POSIXt" 
merged_data$Win.conditions <- as.character(merged_data$Win.conditions)
class(merged_data$Win.conditions)
[1] "character"
merged_data$Attendance.y <- as.character(merged_data$Attendance.y)
merged_data$Attendance.y <- gsub("\\.", "", merged_data$Attendance.y)
merged_data$Attendance.y <- as.integer(merged_data$Attendance.y)
class(merged_data$Attendance.y)
[1] "integer"
merged_data$Line.up <- factor(merged_data$Line.up, levels = c("N", "S"), labels = c("No", "Yes"))
class(merged_data$Line.up)
[1] "factor"
levels(merged_data$Line.up)
[1] "No"  "Yes"
merged_data <- merged_data[, -26]
colnames(merged_data) <- c("RoundID", "MatchID", "Player_Team_Initials", "Player_Team_Coach", "Starting_Line-up", "Player_Shirt_Number", "Player_Name", "Player_Position", "Player_Event", "World_Cup_Year", "Match_Datetime", "Match_Stage", "Match_Stadium", "Match_City", "Home_Team_Name", "Home_Team_Goals", "Away_Team_Goals", "Away_Team_Name", "Match_Detail", "Match_Attendance", "Half-time_Home_Goals", "Half-time_Away_Goals", "Match_Referee", "Match_Assistant_Referee_1", "Match_Assistant_Referee_2", "Home_Team_Initials", "Away_Team_Initials", "World_Cup_Host_Country", "Winner", "Runners-up", "Third_Position", "Fourth_Position", "World_Cup_Total_Goals", "Number_of_Qualified_Teams", "World_Cup_Total_Matches", "World_Cup_Total_Attendance")
colnames(merged_data)
 [1] "RoundID"                    "MatchID"                    "Player_Team_Initials"      
 [4] "Player_Team_Coach"          "Starting_Line-up"           "Player_Shirt_Number"       
 [7] "Player_Name"                "Player_Position"            "Player_Event"              
[10] "World_Cup_Year"             "Match_Datetime"             "Match_Stage"               
[13] "Match_Stadium"              "Match_City"                 "Home_Team_Name"            
[16] "Home_Team_Goals"            "Away_Team_Goals"            "Away_Team_Name"            
[19] "Match_Detail"               "Match_Attendance"           "Half-time_Home_Goals"      
[22] "Half-time_Away_Goals"       "Match_Referee"              "Match_Assistant_Referee_1" 
[25] "Match_Assistant_Referee_2"  "Home_Team_Initials"         "Away_Team_Initials"        
[28] "World_Cup_Host_Country"     "Winner"                     "Runners-up"                
[31] "Third_Position"             "Fourth_Position"            "World_Cup_Total_Goals"     
[34] "Number_of_Qualified_Teams"  "World_Cup_Total_Matches"    "World_Cup_Total_Attendance"

Explanation:

First, we checked the attributes (metadata) of the merged data set which returned variable names, object class and row names. We skipped printing the row names as they were meaningless and took a lot of space. Next, we inspected the structure of the data frame to find out the details of each variable such as data type, number of levels (for factor variables), etc. The str function also returned the total number of observations and variables in the data set.

Inspection of data set showed that the data consisted of multiple data types including numerics, characters, factors, date. However, some variables were not imported as correct data type. Hence, we made the following data type conversions to make more sense of the data:

  • Shirt.Number variable from integer to factor using as.factor() function.
  • Events variable from factor to character using as.character() function.
  • Year variable from integer to factor using as.factor() function.
  • Datetime variable from factor to POSIXct (date-time). As the order of elements in date was day, month, year, hour, minute we used the dmy_hm() function of lubridate package.
  • Win.conditions variable from factor to character using as.character() function.
  • Attendance.y variable was first changed from factor to character and then all of the periods were removed. Then, we converted the variable to integer data type.

Next, we labelled the levels of Line.up factor variable from “N” and “S” to “No” and “Yes” respectively. We then removed the redundant RoundID.y column (same as RoundID.x) by simply filtering the data frame. Finally, column names of the data frame were renamed to more suitable and meaningful names using colnames() function.

Tidy & Manipulate Data I

merged_data <- gather(merged_data, World_Cup_Position, Position_Holder, 29:32)
merged_data$World_Cup_Position <- as.factor(merged_data$World_Cup_Position)
merged_data$Position_Holder <- as.factor(merged_data$Position_Holder)
head(merged_data[, c("World_Cup_Position", "Position_Holder")])

Explanation:

The data did not conform to the tidy data principles as each variable did not have its own column: each World_Cup_Position variable value (i.e. winner, runners up, etc.) had a separate column. Hence, we reshaped the data using gather function (tidyr package) to create a new column for World_Cup_Position values.

The new columns created had character data type. We converted them to factor data type which was more appropriate.

Tidy & Manipulate Data II

merged_data <- mutate(merged_data, Match_Goal_Difference = Home_Team_Goals - Away_Team_Goals)
merged_data <- mutate(merged_data, World_Cup_Average_Goals = World_Cup_Total_Goals / World_Cup_Total_Matches)
head(merged_data[, c("Match_Goal_Difference", "World_Cup_Average_Goals")])

Explanation:

We created two new variables: Match_Goal_Difference and World_Cup_Average_Goals. For Match_Goal_Difference variable, we calculated the difference between Home_Team_Goals variable and Away_Team-Goals variable. For World_Cup_Average_Goals variable, we divided World_Cup_Total_Goals variable by World_Cup_Total_Matches variable. To complete this task, we used the mutate function (dplyr package).

Scan I

sum(is.na(merged_data))
[1] 736
colSums(is.na(merged_data))
                   RoundID                    MatchID       Player_Team_Initials 
                         0                          0                          0 
         Player_Team_Coach           Starting_Line-up        Player_Shirt_Number 
                         0                          0                          0 
               Player_Name            Player_Position               Player_Event 
                         0                          0                          0 
            World_Cup_Year             Match_Datetime                Match_Stage 
                         0                          0                          0 
             Match_Stadium                 Match_City             Home_Team_Name 
                         0                          0                          0 
           Home_Team_Goals            Away_Team_Goals             Away_Team_Name 
                         0                          0                          0 
              Match_Detail           Match_Attendance       Half-time_Home_Goals 
                         0                        736                          0 
      Half-time_Away_Goals              Match_Referee  Match_Assistant_Referee_1 
                         0                          0                          0 
 Match_Assistant_Referee_2         Home_Team_Initials         Away_Team_Initials 
                         0                          0                          0 
    World_Cup_Host_Country      World_Cup_Total_Goals  Number_of_Qualified_Teams 
                         0                          0                          0 
   World_Cup_Total_Matches World_Cup_Total_Attendance         World_Cup_Position 
                         0                          0                          0 
           Position_Holder      Match_Goal_Difference    World_Cup_Average_Goals 
                         0                          0                          0 
merged_data$Match_Attendance <- as.integer(impute(merged_data$Match_Attendance, fun = mean))
sum(is.na(merged_data))
[1] 0
is.specialInf <- function(x){
    sum(is.infinite(x))
}
sapply(merged_data, is.specialInf)
                   RoundID                    MatchID       Player_Team_Initials 
                         0                          0                          0 
         Player_Team_Coach           Starting_Line-up        Player_Shirt_Number 
                         0                          0                          0 
               Player_Name            Player_Position               Player_Event 
                         0                          0                          0 
            World_Cup_Year             Match_Datetime                Match_Stage 
                         0                          0                          0 
             Match_Stadium                 Match_City             Home_Team_Name 
                         0                          0                          0 
           Home_Team_Goals            Away_Team_Goals             Away_Team_Name 
                         0                          0                          0 
              Match_Detail           Match_Attendance       Half-time_Home_Goals 
                         0                          0                          0 
      Half-time_Away_Goals              Match_Referee  Match_Assistant_Referee_1 
                         0                          0                          0 
 Match_Assistant_Referee_2         Home_Team_Initials         Away_Team_Initials 
                         0                          0                          0 
    World_Cup_Host_Country      World_Cup_Total_Goals  Number_of_Qualified_Teams 
                         0                          0                          0 
   World_Cup_Total_Matches World_Cup_Total_Attendance         World_Cup_Position 
                         0                          0                          0 
           Position_Holder      Match_Goal_Difference    World_Cup_Average_Goals 
                         0                          0                          0 
is.specialNaN <- function(x){
    sum(is.nan(x))
}
sapply(merged_data, is.specialNaN)
                   RoundID                    MatchID       Player_Team_Initials 
                         0                          0                          0 
         Player_Team_Coach           Starting_Line-up        Player_Shirt_Number 
                         0                          0                          0 
               Player_Name            Player_Position               Player_Event 
                         0                          0                          0 
            World_Cup_Year             Match_Datetime                Match_Stage 
                         0                          0                          0 
             Match_Stadium                 Match_City             Home_Team_Name 
                         0                          0                          0 
           Home_Team_Goals            Away_Team_Goals             Away_Team_Name 
                         0                          0                          0 
              Match_Detail           Match_Attendance       Half-time_Home_Goals 
                         0                          0                          0 
      Half-time_Away_Goals              Match_Referee  Match_Assistant_Referee_1 
                         0                          0                          0 
 Match_Assistant_Referee_2         Home_Team_Initials         Away_Team_Initials 
                         0                          0                          0 
    World_Cup_Host_Country      World_Cup_Total_Goals  Number_of_Qualified_Teams 
                         0                          0                          0 
   World_Cup_Total_Matches World_Cup_Total_Attendance         World_Cup_Position 
                         0                          0                          0 
           Position_Holder      Match_Goal_Difference    World_Cup_Average_Goals 
                         0                          0                          0 

Explanation:

We scanned all variables for missing values using the is.na function. After identifying the variable which had missing values (i.e. Match_Attendance), we decided to apply basic imputation technique to replace the missing data. Since it was a numerical variable and a very small fraction of the data set was missing, we replaced the missing values with the mean value of that variable. We used impute function (Hmisc package) for this purpose.

Next, we checked each column of the data frame for special values (i.e. -Inf, Inf and NaN) using is.infinite and is.nan functions. We did not find any special values in our data set.

Scan II

length(boxplot(merged_data$Home_Team_Goals, plot=FALSE)$out)
[1] 18760
merged_data$Home_Team_Goals[merged_data$Home_Team_Goals > 3] <- median(merged_data$Home_Team_Goals)
length(boxplot(merged_data$Home_Team_Goals, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$Away_Team_Goals, plot=FALSE)$out)
[1] 14328
merged_data$Away_Team_Goals[merged_data$Away_Team_Goals > 2] <- median(merged_data$Away_Team_Goals)
length(boxplot(merged_data$Away_Team_Goals, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$Match_Attendance, plot=FALSE)$out)
[1] 1744
merged_data <- merged_data[merged_data$Match_Attendance < 100000, ]
length(boxplot(merged_data$Match_Attendance, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$`Half-time_Home_Goals`, plot=FALSE)$out)
[1] 7920
merged_data$`Half-time_Home_Goals`[merged_data$`Half-time_Home_Goals` > 2] <- median(merged_data$`Half-time_Home_Goals`)
length(boxplot(merged_data$`Half-time_Home_Goals`, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$`Half-time_Away_Goals`, plot=FALSE)$out)
[1] 2184
merged_data$`Half-time_Away_Goals`[merged_data$`Half-time_Away_Goals` > 2] <- median(merged_data$`Half-time_Away_Goals`)
length(boxplot(merged_data$`Half-time_Away_Goals`, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$Match_Goal_Difference, plot=FALSE)$out)
[1] 4748
cap <- function(x){
  quantiles <- quantile( x, c(.05, 0.25, 0.75, .95 ) )
  x[ x < quantiles[2] - 1.5*IQR(x) ] <- quantiles[1]
  x[ x > quantiles[3] + 1.5*IQR(x) ] <- quantiles[4]
  x
}
merged_data$Match_Goal_Difference <- cap(merged_data$Match_Goal_Difference)
length(boxplot(merged_data$Match_Goal_Difference, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$World_Cup_Average_Goals, plot=FALSE)$out)
[1] 22308
merged_data$World_Cup_Average_Goals[merged_data$World_Cup_Average_Goals > 3] <- mean(merged_data$World_Cup_Average_Goals)
length(boxplot(merged_data$World_Cup_Average_Goals, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$World_Cup_Total_Goals, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$Number_of_Qualified_Teams, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$World_Cup_Total_Matches, plot=FALSE)$out)
[1] 0
length(boxplot(merged_data$World_Cup_Total_Attendance, plot=FALSE)$out)
[1] 0

Explanation:

In this section, we scanned all of the numeric variables for outliers using box plots. Since there were a lot of numeric variables and the space was limited, we did not plot the box plots. We used multiple techniques to deal with the outliers: 1. For variables that contained significant number of outliers, we used mean or median imputation. 2. For variables that had small number of outliers, we excluded/deleted the outliers by simply filtering the data set. 3. For Match_Goal_Difference variable, we used the Capping technique which involves replacing the outliers with the nearest neighbours that are not outliers.

The remaining numerical variables did not have any outliers (as shown in the above code output).

Transform

average_goals <- select(merged_data, World_Cup_Average_Goals)
average_goals_binned_width <- discretize(merged_data$World_Cup_Average_Goals, disc = "equalwidth")
combined1 <- bind_cols(average_goals, average_goals_binned_width)
colnames(combined1) <- c("World_Cup_Average_Goals", "Equal Width Bin")
head(unique(combined1))
average_goals_binned_freq <- discretize(merged_data$World_Cup_Average_Goals, disc = "equalfreq")
combined2 <- bind_cols(average_goals, average_goals_binned_freq)
colnames(combined2) <- c("World_Cup_Average_Goals", "Equal Frequency Bin")
head(unique(combined2))
hist(merged_data$World_Cup_Average_Goals, main = "Histogram of World Cup Average Goals", xlab = "World Cup Average Goals")

hist(combined1$`Equal Width Bin`, main = "Histogram of Equal Width Binning", xlab = "Equal Width Bins")

hist(combined2$`Equal Frequency Bin`, main = "Histogram of Equal Frequency Binning", xlab = "Equal Frequency Bins")


Explanation:

In this section of the report, we applied transformation to World_Cup_Average_Goals variable. The purpose of the transformation was to change the scale for better understanding of the variable. We applied both equal width (distance) binning and equal depth (frequency) binning methods to discretise/transform the numerical variable into categorical counterpart.

In equal-width binning, the variable is divided into n intervals of equal size whereas in equal-depth binning method, the variable is divided into n intervals, each containing approximately the same number of observations. We applied these methods using the discretize function under the infotheo package. By binning the data, the scale of the continuous data was converted into discrete categories/bins. As a result, the distributional properties of the variable also changed which is visible in the above histograms.


LS0tDQp0aXRsZTogIk1BVEgyMzQ5IFNlbWVzdGVyIDEsIDIwMTgiDQphdXRob3I6IEFiZHVsbGFoIEFiaWQgKHMzNjk3OTk1KSwgTXVoYW1tYWQgVW1lciBNYXpoYXIgKHMzNjc3NzY4KSwgV2FsZWVkIFNoYWZxYXQNCiAgKHMzNjY5NTc2KQ0Kc3VidGl0bGU6IEFzc2lnbm1lbnQgMw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KDQojIyBSZXF1aXJlZCBwYWNrYWdlcyANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KEhtaXNjKQ0KbGlicmFyeShpbmZvdGhlbykNCmBgYA0KDQoNCiMjIEV4ZWN1dGl2ZSBTdW1tYXJ5IA0KDQpEYXRhIFByZXByb2Nlc3NpbmcgaXMgYSBwcm9jZXNzIGFuZCB0aGUgY29sbGVjdGlvbiBvZiBvcGVyYXRpb25zIG5lZWRlZCB0byBwcmVwYXJlIGFsbCBmb3JtcyBvZiB1bnRpZHkgZGF0YSBmb3Igc3RhdGlzdGljYWwgYW5hbHlzaXMuIFRoZSA1IG1ham9yIHRhc2tzIGZvciBkYXRhIGluY2x1ZGUgR2V0LCBVbmRlcnN0YW5kLCBUaWR5ICYgTWFuaXB1bGF0ZSwgU2NhbiBhbmQgVHJhbnNmb3JtLiBUbyBhY2hpZXZlIHRoZSBmaXJzdCB0YXNrLCB3ZSBsb2NhdGVkIHRoZSByaWdodCBkYXRhIHNldHMgYW5kIGltcG9ydGVkIHRoZW0gdXNpbmcgdGhlIEJhc2UgUiBmdW5jdGlvbnMuIFRoZW4sIHdlIG1lcmdlZCB0aGUgZGF0YSBzZXRzIHRvIGNyZWF0ZSBhIHNpbmdsZSBkYXRhIHNldCBmb3IgZnVydGhlciBwcmVwcm9jZXNzaW5nLg0KDQpOZXh0LCB3ZSBpbnNwZWN0ZWQgdGhlIGRhdGEgc2V0IHRvIHVuZGVyc3RhbmQgdGhlIHZhcmlvdXMgYXNwZWN0cyBvZiBpdC4gV2UgY2hlY2tlZCB0aGUgYXR0cmlidXRlcyAobWV0YWRhdGEpIGFzIHdlbGwgYXMgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSBzZXQgdG8gZmluZCBvdXQgZGV0YWlscyBzdWNoIGFzIHR5cGVzIG9mIHZhcmlhYmxlcywgZGF0YSBzdHJ1Y3R1cmVzLCBkaW1lbnNpb25zLCBldGMuIFdlIG1hZGUgc29tZSBhcHByb3ByaWF0ZSBjaGFuZ2VzIGluIG91ciBkYXRhIHNldCBzdWNoIGFzIGRhdGEgdHlwZSBjb252ZXJzaW9ucyBhbmQgbGFiZWxsaW5nIGZhY3RvciB2YXJpYWJsZXMgdG8gbWFrZSBtb3JlIHNlbnNlIG9mIHRoZSBkYXRhLg0KDQpXZSB0aGVuIGNoZWNrZWQgd2hldGhlciB0aGUgZGF0YSBmb2xsb3dlZCB0aGUgdGlkeSBkYXRhIHByaW5jaXBsZXMuIEFzIGVhY2ggdmFyaWFibGUgaW4gb3VyIGRhdGEgZGlkIG5vdCBoYXZlIGl0cyBvd24gY29sdW1uLCBpdCB3YXMgbm90IGluIGEgdGlkeSBmb3JtYXQuIEhlbmNlLCB3ZSByZXNoYXBlZCBpdCBpbnRvIGEgdGlkeSBmb3JtYXQgdXNpbmcgYXBwcm9wcmlhdGUgbWV0aG9kLiBUaGVuLCB3ZSBjcmVhdGVkIHR3byBuZXcgdmFyaWFibGVzIGZyb20gdGhlIGV4aXN0aW5nIHZhcmlhYmxlcyB1c2luZyB0aGUgbXV0YXRlIGZ1bmN0aW9uLg0KDQpBZnRlcndhcmRzLCB3ZSBzY2FubmVkIHRoZSBkYXRhIGZvciBtaXNzaW5nIHZhbHVlcyBhbmQgaW5jb25zaXN0ZW5jaWVzLiBTaW5jZSBvbmUgb2YgdGhlIHZhcmlhYmxlcyBpbiBvdXIgZGF0YSBzZXQgaGFkIG1pc3NpbmcgdmFsdWVzLCB3ZSBhcHBsaWVkIGJhc2ljIGltcHV0YXRpb24gdGVjaG5pcXVlIHRvIHJlcGxhY2UgdGhlbS4gTmV4dCwgd2Ugc2Nhbm5lZCBhbGwgbnVtZXJpYyB2YXJpYWJsZXMgZm9yIG91dGxpZXJzIHVzaW5nIGJveCBwbG90LiBXZSB1c2VkIG11bHRpcGxlIHRlY2huaXF1ZXMgdG8gZGVhbCB3aXRoIHRoZSBvdXRsaWVycyBpbmNsdWRpbmcgaW1wdXRhdGlvbiwgZXhjbHVkaW5nIG91dGxpZXJzIGFuZCBjYXBwaW5nLg0KRmluYWxseSwgd2UgYXBwbGllZCB0cmFuc2Zvcm1hdGlvbiB0byBvbmUgb2YgdGhlIHZhcmlhYmxlcyB0byBjaGFuZ2UgdGhlIHNjYWxlIGZvciBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgdmFyaWFibGUuIFdlIGFwcGxpZWQgYm90aCBlcXVhbCB3aWR0aCAoZGlzdGFuY2UpIGJpbm5pbmcgYW5kIGVxdWFsIGRlcHRoIChmcmVxdWVuY3kpIGJpbm5pbmcgbWV0aG9kcyB0byBkaXNjcmV0aXplL3RyYW5zZm9ybSB0aGUgbnVtZXJpY2FsIHZhcmlhYmxlIGludG8gY2F0ZWdvcmljYWwgY291bnRlcnBhcnQuDQoNCg0KIyMgRGF0YSANCg0KV2UgYXJlIGdvaW5nIHRvIHVzZSB0aHJlZSBkYXRhIHNldHM6IFdvcmxkIEN1cHMsIFdvcmxkIEN1cCBQbGF5ZXJzLCBXb3JsZCBDdXAgTWF0Y2hlcy4gVGhlIFdvcmxkIEN1cHMgZGF0YSBzZXQgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgYWxsIHRoZSBXb3JsZCBDdXBzIGhlbGQgdG8gZGF0ZS4gSXQgY29uc2lzdHMgb2YgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXM6DQoNCiogWWVhcjogeWVhciBpbiB3aGljaCB0aGUgd29ybGQgY3VwIHdhcyBoZWxkIA0KKiBDb3VudHJ5OiBjb3VudHJ5IHdoZXJlIHRoZSB3b3JsZCBjdXAgd2FzIGhlbGQNCiogV2lubmVyOiB0ZWFtIHRoYXQgd29uIHRoZSB3b3JsZCBjdXAgDQoqIFJ1bm5lcnMtVXA6IHRlYW0gdGhhdCBjYW1lIHNlY29uZA0KKiBUaGlyZDogdGVhbSB0aGF0IGNhbWUgdGhpcmQNCiogRm91cnRoOiB0ZWFtIHRoYXQgY2FtZSBmb3VydGgNCiogR29hbHNTY29yZWQ6IHRvdGFsIGdvYWxzIHNjb3JlZCBpbiB0aGUgd29ybGQgY3VwDQoqIFF1YWxpZmllZFRlYW1zOiBudW1iZXIgb2YgdGVhbXMgdGhhdCBxdWFsaWZpZWQgZm9yIHRoZSB3b3JsZCBjdXANCiogTWF0Y2hlc1BsYXllZDogdG90YWwgbWF0Y2hlcyBwbGF5ZWQgaW4gdGhlIHdvcmxkIGN1cA0KKiBBdHRlbmRhbmNlOiB0b3RhbCBhdHRlbmRhbmNlIGluIHRoZSB3b3JsZCBjdXANCg0KVGhlIFdvcmxkIEN1cCBNYXRjaGVzIGRhdGEgc2V0IGNvbnRhaW5zIGluZm9ybWF0aW9uIHJlZ2FyZGluZyBhbGwgb2YgdGhlIG1hdGNoZXMgcGxheWVkIGluIHRoZSBXb3JsZCBDdXBzIGhlbGQgdG8gZGF0ZS4gSXQgY29uc2lzdHMgb2YgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXM6DQoNCiogWWVhcjogeWVhciBpbiB3aGljaCB0aGUgd29ybGQgY3VwIHdhcyBoZWxkIA0KKiBEYXRldGltZTogZGF0ZSBhbmQgdGltZSBvZiB0aGUgbWF0Y2gNCiogU3RhZ2U6IHN0YWdlIG9mIHRoZSB3b3JsZCBjdXAgd2hlbiB0aGUgbWF0Y2ggd2FzIHBsYXllZCAoZS5nLiBxdWFydGVyLWZpbmFsLCBzZW1pLWZpbmFsKQ0KKiBzdGFkaXVtOiBzdGFkaXVtIG5hbWUgd2hlcmUgdGhlIG1hdGNoIHdhcyBwbGF5ZWQNCiogQ2l0eTogY2l0eSBuYW1lIHdoZXJlIHRoZSBtYXRjaCB3YXMgcGxheWVkDQoqIEhvbWUgVGVhbSBOYW1lOiBuYW1lIG9mIHRoZSBob21lIHRlYW0NCiogSG9tZSBUZWFtIEdvYWxzOiBudW1iZXIgb2YgZ29hbHMgc2NvcmVkIGJ5IHRoZSBob21lIHRlYW0NCiogQXdheSBUZWFtIEdvYWxzOiBudW1iZXIgb2YgZ29hbHMgc2NvcmVkIGJ5IHRoZSBhd2F5IHRlYW0NCiogQXdheSBUZWFtIE5hbWU6IG5hbWUgb2YgdGhlIGF3YXkgdGVhbQ0KKiBXaW4gQ29uZGl0aW9uczogc3BlY2lhbCBkZXRhaWwgcmVnYXJkaW5nIHRoZSB3aW4gKGlmIGFueSkNCiogQXR0ZW5kYW5jZTogbWF0Y2ggYXR0ZW5kYW5jZSANCiogSGFsZi10aW1lIEhvbWUgR29hbHM6IG51bWJlciBvZiBnb2FscyBzY29yZWQgYnkgdGhlIGhvbWUgdGVhbSBhdCBoYWxmLXRpbWUNCiogSGFsZi10aW1lIEF3YXkgR29hbHM6IG51bWJlciBvZiBnb2FscyBzY29yZWQgYnkgdGhlIGF3YXkgdGVhbSBhdCBoYWxmLXRpbWUNCiogUmVmZXJlZTogbmFtZSBvZiBtYXRjaCByZWZlcmVlDQoqIEFzc2lzdGFudCAxOiBuYW1lIG9mIGFzc2lzdGFudCByZWZlcmVlIDENCiogQXNzaXN0YW50IDI6IG5hbWUgb2YgYXNzaXN0YW50IHJlZmVyZWUgMg0KKiBSb3VuZElEOiBJRCBvZiB0aGUgcm91bmQNCiogTWF0Y2hJRDogdW5pcXVlIElEIG9mIHRoZSBtYXRjaA0KKiBIb21lIFRlYW0gSW5pdGlhbHM6IGhvbWUgdGVhbSdzIHRocmVlIGxldHRlciBpbml0aWFscw0KKiBBd2F5IFRlYW0gSW5pdGlhbHM6IGF3YXkgdGVhbSdzIHRocmVlIGxldHRlciBpbml0aWFscw0KDQpUaGUgV29ybGQgQ3VwIFBsYXllcnMgZGF0YSBzZXQgY29udGFpbnMgaW5mb3JtYXRpb24gcmVnYXJkaW5nIGFsbCBvZiB0aGUgcGxheWVycyB3aG8gcGxheWVkIGluIHRoZSBXb3JsZCBDdXAgbWF0Y2hlcyBoZWxkIHRvIGRhdGUuIEl0IGNvbnNpc3RzIG9mIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOg0KDQoqIFJvdW5kSUQ6IElEIG9mIHRoZSByb3VuZCANCiogTWF0Y2hJRDogdW5pcXVlIElEIG9mIHRoZSBtYXRjaA0KKiBUZWFtIEluaXRpYWxzOiB0aHJlZSBsZXR0ZXIgaW5pdGlhbHMgb2YgdGhlIHBsYXllcidzIHRlYW0NCiogQ29hY2ggTmFtZTogbmFtZSBvZiBwbGF5ZXIncyBjb2FjaA0KKiBMaW5lLXVwOiBpbmRpY2F0ZXMgd2hldGhlciB0aGUgcGxheWVyIHdhcyBpbiB0aGUgc3RhcnRpbmcgbGluZS11cCBvciBub3QNCiogU2hpcnQgTnVtYmVyOiBzaGlydCBudW1iZXIgb2YgdGhlIHBsYXllcg0KKiBQbGF5ZXIgTmFtZTogbmFtZSBvZiB0aGUgcGxheWVyDQoqIFBvc2l0aW9uOiBpbmRpY2F0b3IgZm9yIGdvYWxrZWVwZXJzIGFuZCBjYXB0YWlucw0KKiBFdmVudDogaW1wb3J0YW50IGV2ZW50IGludm9sdmluZyB0aGUgcGxheWVyIChpZiBhbnkpDQoNCioqRGF0YSBTb3VyY2UqKjogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9hYmVja2xhcy9maWZhLXdvcmxkLWN1cA0KDQpgYGB7cn0NCldvcmxkQ3VwX2RhdGEgPC0gcmVhZC5jc3YoIldvcmxkQ3Vwcy5jc3YiKQ0KaGVhZChXb3JsZEN1cF9kYXRhKQ0KDQpXb3JsZEN1cE1hdGNoZXNfZGF0YSA8LSByZWFkLmNzdigiV29ybGRDdXBNYXRjaGVzLmNzdiIpDQpoZWFkKFdvcmxkQ3VwTWF0Y2hlc19kYXRhKQ0KDQpXb3JsZEN1cFBsYXllcnNfZGF0YSA8LSByZWFkLmNzdigiV29ybGRDdXBQbGF5ZXJzLmNzdiIpDQpoZWFkKFdvcmxkQ3VwUGxheWVyc19kYXRhKQ0KDQptZXJnZWRfZGF0YSA8LSBpbm5lcl9qb2luKFdvcmxkQ3VwUGxheWVyc19kYXRhLCBXb3JsZEN1cE1hdGNoZXNfZGF0YSwgYnkgPSAiTWF0Y2hJRCIpDQptZXJnZWRfZGF0YSA8LSBpbm5lcl9qb2luKG1lcmdlZF9kYXRhLCBXb3JsZEN1cF9kYXRhLCBieSA9ICJZZWFyIikNCmhlYWQobWVyZ2VkX2RhdGEpDQpgYGANCg0KIyMjIyBFeHBsYW5hdGlvbjoNCg0KT3VyIGZpcnN0IHN0ZXAgd2FzIHRvIGltcG9ydCB0aGUgZGF0YSBzZXRzIHVzaW5nIHJlYWQuY3N2IChCYXNlIFIgZnVuY3Rpb24pIHNpbmNlIHRoZSBkYXRhIHdhcyBzdG9yZWQgaW4gY3N2IGZpbGVzLiBUaGVuLCB3ZSB1c2VkIGlubmVyX2pvaW4gdG8gbWVyZ2UgV29ybGQgQ3VwIFBsYXllcnMgZGF0YSBzZXQgd2l0aCBXb3JsZCBDdXAgTWF0Y2hlcyBkYXRhIHNldCBieSB0aGUga2V5IHZhcmlhYmxlOiBNYXRjaElELCB3aGljaCBpcyBjb21tb24gaW4gYm90aCBkYXRhIHNldHMuIElubmVyIGpvaW4gcmV0YWlucyB0aGUgcm93cyB3aXRoIG1hdGNoaW5nIGtleSB2YXJpYWJsZSB2YWx1ZXMgaW4gYm90aCBkYXRhIHNldHMuIEZpbmFsbHksIHVzaW5nIGlubmVyX2pvaW4gb25jZSBhZ2Fpbiwgd2UgbWVyZ2VkIHRoZSByZXN1bHRpbmcgZGF0YSBzZXQgKFdvcmxkIEN1cCBQbGF5ZXJzICsgV29ybGQgQ3VwIE1hdGNoZXMpIHdpdGggV29ybGQgQ3VwcyBkYXRhIHNldCBieSB0aGUgY29tbW9uIHZhcmlhYmxlOiBZZWFyLiBBcyBhIHJlc3VsdCwgYSBzaW5nbGUgZGF0YSBzZXQgd2FzIGNyZWF0ZWQgZm9yIG91ciByZXBvcnQuDQoNCg0KIyMgVW5kZXJzdGFuZCANCg0KYGBge3J9DQphdHRyaWJ1dGVzKG1lcmdlZF9kYXRhKSRjbGFzcw0KYXR0cmlidXRlcyhtZXJnZWRfZGF0YSkkbmFtZXMNCg0Kc3RyKG1lcmdlZF9kYXRhKQ0KDQptZXJnZWRfZGF0YSRTaGlydC5OdW1iZXIgPC0gYXMuZmFjdG9yKG1lcmdlZF9kYXRhJFNoaXJ0Lk51bWJlcikNCmNsYXNzKG1lcmdlZF9kYXRhJFNoaXJ0Lk51bWJlcikNCg0KbWVyZ2VkX2RhdGEkRXZlbnQgPC0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kYXRhJEV2ZW50KQ0KY2xhc3MobWVyZ2VkX2RhdGEkRXZlbnQpDQoNCm1lcmdlZF9kYXRhJFllYXIgPC0gYXMuZmFjdG9yKG1lcmdlZF9kYXRhJFllYXIpDQpjbGFzcyhtZXJnZWRfZGF0YSRZZWFyKQ0KDQptZXJnZWRfZGF0YSREYXRldGltZSA8LSBkbXlfaG0obWVyZ2VkX2RhdGEkRGF0ZXRpbWUpDQpjbGFzcyhtZXJnZWRfZGF0YSREYXRldGltZSkNCg0KbWVyZ2VkX2RhdGEkV2luLmNvbmRpdGlvbnMgPC0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kYXRhJFdpbi5jb25kaXRpb25zKQ0KY2xhc3MobWVyZ2VkX2RhdGEkV2luLmNvbmRpdGlvbnMpDQoNCm1lcmdlZF9kYXRhJEF0dGVuZGFuY2UueSA8LSBhcy5jaGFyYWN0ZXIobWVyZ2VkX2RhdGEkQXR0ZW5kYW5jZS55KQ0KbWVyZ2VkX2RhdGEkQXR0ZW5kYW5jZS55IDwtIGdzdWIoIlxcLiIsICIiLCBtZXJnZWRfZGF0YSRBdHRlbmRhbmNlLnkpDQptZXJnZWRfZGF0YSRBdHRlbmRhbmNlLnkgPC0gYXMuaW50ZWdlcihtZXJnZWRfZGF0YSRBdHRlbmRhbmNlLnkpDQpjbGFzcyhtZXJnZWRfZGF0YSRBdHRlbmRhbmNlLnkpDQoNCm1lcmdlZF9kYXRhJExpbmUudXAgPC0gZmFjdG9yKG1lcmdlZF9kYXRhJExpbmUudXAsIGxldmVscyA9IGMoIk4iLCAiUyIpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSkNCmNsYXNzKG1lcmdlZF9kYXRhJExpbmUudXApDQpsZXZlbHMobWVyZ2VkX2RhdGEkTGluZS51cCkNCg0KbWVyZ2VkX2RhdGEgPC0gbWVyZ2VkX2RhdGFbLCAtMjZdDQoNCmNvbG5hbWVzKG1lcmdlZF9kYXRhKSA8LSBjKCJSb3VuZElEIiwgIk1hdGNoSUQiLCAiUGxheWVyX1RlYW1fSW5pdGlhbHMiLCAiUGxheWVyX1RlYW1fQ29hY2giLCAiU3RhcnRpbmdfTGluZS11cCIsICJQbGF5ZXJfU2hpcnRfTnVtYmVyIiwgIlBsYXllcl9OYW1lIiwgIlBsYXllcl9Qb3NpdGlvbiIsICJQbGF5ZXJfRXZlbnQiLCAiV29ybGRfQ3VwX1llYXIiLCAiTWF0Y2hfRGF0ZXRpbWUiLCAiTWF0Y2hfU3RhZ2UiLCAiTWF0Y2hfU3RhZGl1bSIsICJNYXRjaF9DaXR5IiwgIkhvbWVfVGVhbV9OYW1lIiwgIkhvbWVfVGVhbV9Hb2FscyIsICJBd2F5X1RlYW1fR29hbHMiLCAiQXdheV9UZWFtX05hbWUiLCAiTWF0Y2hfRGV0YWlsIiwgIk1hdGNoX0F0dGVuZGFuY2UiLCAiSGFsZi10aW1lX0hvbWVfR29hbHMiLCAiSGFsZi10aW1lX0F3YXlfR29hbHMiLCAiTWF0Y2hfUmVmZXJlZSIsICJNYXRjaF9Bc3Npc3RhbnRfUmVmZXJlZV8xIiwgIk1hdGNoX0Fzc2lzdGFudF9SZWZlcmVlXzIiLCAiSG9tZV9UZWFtX0luaXRpYWxzIiwgIkF3YXlfVGVhbV9Jbml0aWFscyIsICJXb3JsZF9DdXBfSG9zdF9Db3VudHJ5IiwgIldpbm5lciIsICJSdW5uZXJzLXVwIiwgIlRoaXJkX1Bvc2l0aW9uIiwgIkZvdXJ0aF9Qb3NpdGlvbiIsICJXb3JsZF9DdXBfVG90YWxfR29hbHMiLCAiTnVtYmVyX29mX1F1YWxpZmllZF9UZWFtcyIsICJXb3JsZF9DdXBfVG90YWxfTWF0Y2hlcyIsICJXb3JsZF9DdXBfVG90YWxfQXR0ZW5kYW5jZSIpDQpjb2xuYW1lcyhtZXJnZWRfZGF0YSkNCmBgYA0KDQojIyMjIEV4cGxhbmF0aW9uOg0KDQpGaXJzdCwgd2UgY2hlY2tlZCB0aGUgYXR0cmlidXRlcyAobWV0YWRhdGEpIG9mIHRoZSBtZXJnZWQgZGF0YSBzZXQgd2hpY2ggcmV0dXJuZWQgdmFyaWFibGUgbmFtZXMsIG9iamVjdCBjbGFzcyBhbmQgcm93IG5hbWVzLiBXZSBza2lwcGVkIHByaW50aW5nIHRoZSByb3cgbmFtZXMgYXMgdGhleSB3ZXJlIG1lYW5pbmdsZXNzIGFuZCB0b29rIGEgbG90IG9mIHNwYWNlLiBOZXh0LCB3ZSBpbnNwZWN0ZWQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSBmcmFtZSB0byBmaW5kIG91dCB0aGUgZGV0YWlscyBvZiBlYWNoIHZhcmlhYmxlIHN1Y2ggYXMgZGF0YSB0eXBlLCBudW1iZXIgb2YgbGV2ZWxzIChmb3IgZmFjdG9yIHZhcmlhYmxlcyksIGV0Yy4gVGhlIHN0ciBmdW5jdGlvbiBhbHNvIHJldHVybmVkIHRoZSB0b3RhbCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGFuZCB2YXJpYWJsZXMgaW4gdGhlIGRhdGEgc2V0Lg0KDQpJbnNwZWN0aW9uIG9mIGRhdGEgc2V0IHNob3dlZCB0aGF0IHRoZSBkYXRhIGNvbnNpc3RlZCBvZiBtdWx0aXBsZSBkYXRhIHR5cGVzIGluY2x1ZGluZyBudW1lcmljcywgY2hhcmFjdGVycywgZmFjdG9ycywgZGF0ZS4gSG93ZXZlciwgc29tZSB2YXJpYWJsZXMgd2VyZSBub3QgaW1wb3J0ZWQgYXMgY29ycmVjdCBkYXRhIHR5cGUuIEhlbmNlLCB3ZSBtYWRlIHRoZSBmb2xsb3dpbmcgZGF0YSB0eXBlIGNvbnZlcnNpb25zIHRvIG1ha2UgbW9yZSBzZW5zZSBvZiB0aGUgZGF0YToNCg0KKiBTaGlydC5OdW1iZXIgdmFyaWFibGUgZnJvbSBpbnRlZ2VyIHRvIGZhY3RvciB1c2luZyBhcy5mYWN0b3IoKSBmdW5jdGlvbi4NCiogRXZlbnRzIHZhcmlhYmxlIGZyb20gZmFjdG9yIHRvIGNoYXJhY3RlciB1c2luZyBhcy5jaGFyYWN0ZXIoKSBmdW5jdGlvbi4NCiogWWVhciB2YXJpYWJsZSBmcm9tIGludGVnZXIgdG8gZmFjdG9yIHVzaW5nIGFzLmZhY3RvcigpIGZ1bmN0aW9uLg0KKiBEYXRldGltZSB2YXJpYWJsZSBmcm9tIGZhY3RvciB0byBQT1NJWGN0IChkYXRlLXRpbWUpLiBBcyB0aGUgb3JkZXIgb2YgZWxlbWVudHMgaW4gZGF0ZSB3YXMgZGF5LCBtb250aCwgeWVhciwgaG91ciwgbWludXRlIHdlIHVzZWQgdGhlIGRteV9obSgpIGZ1bmN0aW9uIG9mIGx1YnJpZGF0ZSBwYWNrYWdlLg0KKiBXaW4uY29uZGl0aW9ucyB2YXJpYWJsZSBmcm9tIGZhY3RvciB0byBjaGFyYWN0ZXIgdXNpbmcgYXMuY2hhcmFjdGVyKCkgZnVuY3Rpb24uDQoqIEF0dGVuZGFuY2UueSB2YXJpYWJsZSB3YXMgZmlyc3QgY2hhbmdlZCBmcm9tIGZhY3RvciB0byBjaGFyYWN0ZXIgYW5kIHRoZW4gYWxsIG9mIHRoZSBwZXJpb2RzIHdlcmUgcmVtb3ZlZC4gVGhlbiwgd2UgY29udmVydGVkIHRoZSB2YXJpYWJsZSB0byBpbnRlZ2VyIGRhdGEgdHlwZS4NCg0KTmV4dCwgd2UgbGFiZWxsZWQgdGhlIGxldmVscyBvZiBMaW5lLnVwIGZhY3RvciB2YXJpYWJsZSBmcm9tICJOIiBhbmQgIlMiIHRvICJObyIgYW5kICJZZXMiIHJlc3BlY3RpdmVseS4gV2UgdGhlbiByZW1vdmVkIHRoZSByZWR1bmRhbnQgUm91bmRJRC55IGNvbHVtbiAoc2FtZSBhcyBSb3VuZElELngpIGJ5IHNpbXBseSBmaWx0ZXJpbmcgdGhlIGRhdGEgZnJhbWUuIEZpbmFsbHksIGNvbHVtbiBuYW1lcyBvZiB0aGUgZGF0YSBmcmFtZSB3ZXJlIHJlbmFtZWQgdG8gbW9yZSBzdWl0YWJsZSBhbmQgbWVhbmluZ2Z1bCBuYW1lcyB1c2luZyBjb2xuYW1lcygpIGZ1bmN0aW9uLg0KDQoNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSANCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCm1lcmdlZF9kYXRhIDwtIGdhdGhlcihtZXJnZWRfZGF0YSwgV29ybGRfQ3VwX1Bvc2l0aW9uLCBQb3NpdGlvbl9Ib2xkZXIsIDI5OjMyKQ0KDQptZXJnZWRfZGF0YSRXb3JsZF9DdXBfUG9zaXRpb24gPC0gYXMuZmFjdG9yKG1lcmdlZF9kYXRhJFdvcmxkX0N1cF9Qb3NpdGlvbikNCm1lcmdlZF9kYXRhJFBvc2l0aW9uX0hvbGRlciA8LSBhcy5mYWN0b3IobWVyZ2VkX2RhdGEkUG9zaXRpb25fSG9sZGVyKQ0KDQpoZWFkKG1lcmdlZF9kYXRhWywgYygiV29ybGRfQ3VwX1Bvc2l0aW9uIiwgIlBvc2l0aW9uX0hvbGRlciIpXSkNCmBgYA0KDQojIyMjIEV4cGxhbmF0aW9uOg0KDQpUaGUgZGF0YSBkaWQgbm90IGNvbmZvcm0gdG8gdGhlIHRpZHkgZGF0YSBwcmluY2lwbGVzIGFzIGVhY2ggdmFyaWFibGUgZGlkIG5vdCBoYXZlIGl0cyBvd24gY29sdW1uOiBlYWNoIFdvcmxkX0N1cF9Qb3NpdGlvbiB2YXJpYWJsZSB2YWx1ZSAoaS5lLiB3aW5uZXIsIHJ1bm5lcnMgdXAsIGV0Yy4pIGhhZCBhIHNlcGFyYXRlIGNvbHVtbi4gSGVuY2UsIHdlIHJlc2hhcGVkIHRoZSBkYXRhIHVzaW5nIGdhdGhlciBmdW5jdGlvbiAodGlkeXIgcGFja2FnZSkgdG8gY3JlYXRlIGEgbmV3IGNvbHVtbiBmb3IgV29ybGRfQ3VwX1Bvc2l0aW9uIHZhbHVlcy4NCg0KVGhlIG5ldyBjb2x1bW5zIGNyZWF0ZWQgaGFkIGNoYXJhY3RlciBkYXRhIHR5cGUuIFdlIGNvbnZlcnRlZCB0aGVtIHRvIGZhY3RvciBkYXRhIHR5cGUgd2hpY2ggd2FzIG1vcmUgYXBwcm9wcmlhdGUuDQoNCg0KIyMJVGlkeSAmIE1hbmlwdWxhdGUgRGF0YSBJSSANCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCm1lcmdlZF9kYXRhIDwtIG11dGF0ZShtZXJnZWRfZGF0YSwgTWF0Y2hfR29hbF9EaWZmZXJlbmNlID0gSG9tZV9UZWFtX0dvYWxzIC0gQXdheV9UZWFtX0dvYWxzKQ0KbWVyZ2VkX2RhdGEgPC0gbXV0YXRlKG1lcmdlZF9kYXRhLCBXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscyA9IFdvcmxkX0N1cF9Ub3RhbF9Hb2FscyAvIFdvcmxkX0N1cF9Ub3RhbF9NYXRjaGVzKQ0KaGVhZChtZXJnZWRfZGF0YVssIGMoIk1hdGNoX0dvYWxfRGlmZmVyZW5jZSIsICJXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscyIpXSkNCmBgYA0KDQojIyMjIEV4cGxhbmF0aW9uOg0KDQpXZSBjcmVhdGVkIHR3byBuZXcgdmFyaWFibGVzOiBNYXRjaF9Hb2FsX0RpZmZlcmVuY2UgYW5kIFdvcmxkX0N1cF9BdmVyYWdlX0dvYWxzLiBGb3IgTWF0Y2hfR29hbF9EaWZmZXJlbmNlIHZhcmlhYmxlLCB3ZSBjYWxjdWxhdGVkIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gSG9tZV9UZWFtX0dvYWxzIHZhcmlhYmxlIGFuZCBBd2F5X1RlYW0tR29hbHMgdmFyaWFibGUuIEZvciBXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscyB2YXJpYWJsZSwgd2UgZGl2aWRlZCBXb3JsZF9DdXBfVG90YWxfR29hbHMgdmFyaWFibGUgYnkgV29ybGRfQ3VwX1RvdGFsX01hdGNoZXMgdmFyaWFibGUuIFRvIGNvbXBsZXRlIHRoaXMgdGFzaywgd2UgdXNlZCB0aGUgbXV0YXRlIGZ1bmN0aW9uIChkcGx5ciBwYWNrYWdlKS4NCg0KDQojIwlTY2FuIEkgDQoNCmBgYHtyfQ0Kc3VtKGlzLm5hKG1lcmdlZF9kYXRhKSkNCmNvbFN1bXMoaXMubmEobWVyZ2VkX2RhdGEpKQ0KDQptZXJnZWRfZGF0YSRNYXRjaF9BdHRlbmRhbmNlIDwtIGFzLmludGVnZXIoaW1wdXRlKG1lcmdlZF9kYXRhJE1hdGNoX0F0dGVuZGFuY2UsIGZ1biA9IG1lYW4pKQ0Kc3VtKGlzLm5hKG1lcmdlZF9kYXRhKSkNCg0KaXMuc3BlY2lhbEluZiA8LSBmdW5jdGlvbih4KXsNCiAgICBzdW0oaXMuaW5maW5pdGUoeCkpDQp9DQpzYXBwbHkobWVyZ2VkX2RhdGEsIGlzLnNwZWNpYWxJbmYpDQoNCmlzLnNwZWNpYWxOYU4gPC0gZnVuY3Rpb24oeCl7DQogICAgc3VtKGlzLm5hbih4KSkNCn0NCnNhcHBseShtZXJnZWRfZGF0YSwgaXMuc3BlY2lhbE5hTikNCmBgYA0KDQojIyMjIEV4cGxhbmF0aW9uOg0KDQpXZSBzY2FubmVkIGFsbCB2YXJpYWJsZXMgZm9yIG1pc3NpbmcgdmFsdWVzIHVzaW5nIHRoZSBpcy5uYSBmdW5jdGlvbi4gQWZ0ZXIgaWRlbnRpZnlpbmcgdGhlIHZhcmlhYmxlIHdoaWNoIGhhZCBtaXNzaW5nIHZhbHVlcyAoaS5lLiBNYXRjaF9BdHRlbmRhbmNlKSwgd2UgZGVjaWRlZCB0byBhcHBseSBiYXNpYyBpbXB1dGF0aW9uIHRlY2huaXF1ZSB0byByZXBsYWNlIHRoZSBtaXNzaW5nIGRhdGEuIFNpbmNlIGl0IHdhcyBhIG51bWVyaWNhbCB2YXJpYWJsZSBhbmQgYSB2ZXJ5IHNtYWxsIGZyYWN0aW9uIG9mIHRoZSBkYXRhIHNldCB3YXMgbWlzc2luZywgd2UgcmVwbGFjZWQgdGhlIG1pc3NpbmcgdmFsdWVzIHdpdGggdGhlIG1lYW4gdmFsdWUgb2YgdGhhdCB2YXJpYWJsZS4gV2UgdXNlZCBpbXB1dGUgZnVuY3Rpb24gKEhtaXNjIHBhY2thZ2UpIGZvciB0aGlzIHB1cnBvc2UuDQoNCk5leHQsIHdlIGNoZWNrZWQgZWFjaCBjb2x1bW4gb2YgdGhlIGRhdGEgZnJhbWUgZm9yIHNwZWNpYWwgdmFsdWVzIChpLmUuIC1JbmYsIEluZiBhbmQgTmFOKSB1c2luZyBpcy5pbmZpbml0ZSBhbmQgaXMubmFuIGZ1bmN0aW9ucy4gV2UgZGlkIG5vdCBmaW5kIGFueSBzcGVjaWFsIHZhbHVlcyBpbiBvdXIgZGF0YSBzZXQuDQoNCg0KIyMJU2NhbiBJSQ0KDQpgYGB7cn0NCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJEhvbWVfVGVhbV9Hb2FscywgcGxvdD1GQUxTRSkkb3V0KQ0KbWVyZ2VkX2RhdGEkSG9tZV9UZWFtX0dvYWxzW21lcmdlZF9kYXRhJEhvbWVfVGVhbV9Hb2FscyA+IDNdIDwtIG1lZGlhbihtZXJnZWRfZGF0YSRIb21lX1RlYW1fR29hbHMpDQpsZW5ndGgoYm94cGxvdChtZXJnZWRfZGF0YSRIb21lX1RlYW1fR29hbHMsIHBsb3Q9RkFMU0UpJG91dCkNCg0KbGVuZ3RoKGJveHBsb3QobWVyZ2VkX2RhdGEkQXdheV9UZWFtX0dvYWxzLCBwbG90PUZBTFNFKSRvdXQpDQptZXJnZWRfZGF0YSRBd2F5X1RlYW1fR29hbHNbbWVyZ2VkX2RhdGEkQXdheV9UZWFtX0dvYWxzID4gMl0gPC0gbWVkaWFuKG1lcmdlZF9kYXRhJEF3YXlfVGVhbV9Hb2FscykNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJEF3YXlfVGVhbV9Hb2FscywgcGxvdD1GQUxTRSkkb3V0KQ0KDQpsZW5ndGgoYm94cGxvdChtZXJnZWRfZGF0YSRNYXRjaF9BdHRlbmRhbmNlLCBwbG90PUZBTFNFKSRvdXQpDQptZXJnZWRfZGF0YSA8LSBtZXJnZWRfZGF0YVttZXJnZWRfZGF0YSRNYXRjaF9BdHRlbmRhbmNlIDwgMTAwMDAwLCBdDQpsZW5ndGgoYm94cGxvdChtZXJnZWRfZGF0YSRNYXRjaF9BdHRlbmRhbmNlLCBwbG90PUZBTFNFKSRvdXQpDQoNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJGBIYWxmLXRpbWVfSG9tZV9Hb2Fsc2AsIHBsb3Q9RkFMU0UpJG91dCkNCm1lcmdlZF9kYXRhJGBIYWxmLXRpbWVfSG9tZV9Hb2Fsc2BbbWVyZ2VkX2RhdGEkYEhhbGYtdGltZV9Ib21lX0dvYWxzYCA+IDJdIDwtIG1lZGlhbihtZXJnZWRfZGF0YSRgSGFsZi10aW1lX0hvbWVfR29hbHNgKQ0KbGVuZ3RoKGJveHBsb3QobWVyZ2VkX2RhdGEkYEhhbGYtdGltZV9Ib21lX0dvYWxzYCwgcGxvdD1GQUxTRSkkb3V0KQ0KDQpsZW5ndGgoYm94cGxvdChtZXJnZWRfZGF0YSRgSGFsZi10aW1lX0F3YXlfR29hbHNgLCBwbG90PUZBTFNFKSRvdXQpDQptZXJnZWRfZGF0YSRgSGFsZi10aW1lX0F3YXlfR29hbHNgW21lcmdlZF9kYXRhJGBIYWxmLXRpbWVfQXdheV9Hb2Fsc2AgPiAyXSA8LSBtZWRpYW4obWVyZ2VkX2RhdGEkYEhhbGYtdGltZV9Bd2F5X0dvYWxzYCkNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJGBIYWxmLXRpbWVfQXdheV9Hb2Fsc2AsIHBsb3Q9RkFMU0UpJG91dCkNCg0KbGVuZ3RoKGJveHBsb3QobWVyZ2VkX2RhdGEkTWF0Y2hfR29hbF9EaWZmZXJlbmNlLCBwbG90PUZBTFNFKSRvdXQpDQpjYXAgPC0gZnVuY3Rpb24oeCl7DQogIHF1YW50aWxlcyA8LSBxdWFudGlsZSggeCwgYyguMDUsIDAuMjUsIDAuNzUsIC45NSApICkNCiAgeFsgeCA8IHF1YW50aWxlc1syXSAtIDEuNSpJUVIoeCkgXSA8LSBxdWFudGlsZXNbMV0NCiAgeFsgeCA+IHF1YW50aWxlc1szXSArIDEuNSpJUVIoeCkgXSA8LSBxdWFudGlsZXNbNF0NCiAgeA0KfQ0KbWVyZ2VkX2RhdGEkTWF0Y2hfR29hbF9EaWZmZXJlbmNlIDwtIGNhcChtZXJnZWRfZGF0YSRNYXRjaF9Hb2FsX0RpZmZlcmVuY2UpDQpsZW5ndGgoYm94cGxvdChtZXJnZWRfZGF0YSRNYXRjaF9Hb2FsX0RpZmZlcmVuY2UsIHBsb3Q9RkFMU0UpJG91dCkNCg0KbGVuZ3RoKGJveHBsb3QobWVyZ2VkX2RhdGEkV29ybGRfQ3VwX0F2ZXJhZ2VfR29hbHMsIHBsb3Q9RkFMU0UpJG91dCkNCm1lcmdlZF9kYXRhJFdvcmxkX0N1cF9BdmVyYWdlX0dvYWxzW21lcmdlZF9kYXRhJFdvcmxkX0N1cF9BdmVyYWdlX0dvYWxzID4gM10gPC0gbWVhbihtZXJnZWRfZGF0YSRXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscykNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJFdvcmxkX0N1cF9BdmVyYWdlX0dvYWxzLCBwbG90PUZBTFNFKSRvdXQpDQoNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJFdvcmxkX0N1cF9Ub3RhbF9Hb2FscywgcGxvdD1GQUxTRSkkb3V0KQ0KDQpsZW5ndGgoYm94cGxvdChtZXJnZWRfZGF0YSROdW1iZXJfb2ZfUXVhbGlmaWVkX1RlYW1zLCBwbG90PUZBTFNFKSRvdXQpDQoNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJFdvcmxkX0N1cF9Ub3RhbF9NYXRjaGVzLCBwbG90PUZBTFNFKSRvdXQpDQoNCmxlbmd0aChib3hwbG90KG1lcmdlZF9kYXRhJFdvcmxkX0N1cF9Ub3RhbF9BdHRlbmRhbmNlLCBwbG90PUZBTFNFKSRvdXQpDQpgYGANCg0KIyMjIyBFeHBsYW5hdGlvbjoNCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBzY2FubmVkIGFsbCBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgZm9yIG91dGxpZXJzIHVzaW5nIGJveCBwbG90cy4gU2luY2UgdGhlcmUgd2VyZSBhIGxvdCBvZiBudW1lcmljIHZhcmlhYmxlcyBhbmQgdGhlIHNwYWNlIHdhcyBsaW1pdGVkLCB3ZSBkaWQgbm90IHBsb3QgdGhlIGJveCBwbG90cy4gV2UgdXNlZCBtdWx0aXBsZSB0ZWNobmlxdWVzIHRvIGRlYWwgd2l0aCB0aGUgb3V0bGllcnM6IDEuIEZvciB2YXJpYWJsZXMgdGhhdCBjb250YWluZWQgc2lnbmlmaWNhbnQgbnVtYmVyIG9mIG91dGxpZXJzLCB3ZSB1c2VkIG1lYW4gb3IgbWVkaWFuIGltcHV0YXRpb24uIDIuIEZvciB2YXJpYWJsZXMgdGhhdCBoYWQgc21hbGwgbnVtYmVyIG9mIG91dGxpZXJzLCB3ZSBleGNsdWRlZC9kZWxldGVkIHRoZSBvdXRsaWVycyBieSBzaW1wbHkgZmlsdGVyaW5nIHRoZSBkYXRhIHNldC4gMy4gRm9yIE1hdGNoX0dvYWxfRGlmZmVyZW5jZSB2YXJpYWJsZSwgd2UgdXNlZCB0aGUgQ2FwcGluZyB0ZWNobmlxdWUgd2hpY2ggaW52b2x2ZXMgcmVwbGFjaW5nIHRoZSBvdXRsaWVycyB3aXRoIHRoZSBuZWFyZXN0IG5laWdoYm91cnMgdGhhdCBhcmUgbm90IG91dGxpZXJzLg0KDQpUaGUgcmVtYWluaW5nIG51bWVyaWNhbCB2YXJpYWJsZXMgZGlkIG5vdCBoYXZlIGFueSBvdXRsaWVycyAoYXMgc2hvd24gaW4gdGhlIGFib3ZlIGNvZGUgb3V0cHV0KS4NCg0KDQojIwlUcmFuc2Zvcm0gDQoNCmBgYHtyfQ0KYXZlcmFnZV9nb2FscyA8LSBzZWxlY3QobWVyZ2VkX2RhdGEsIFdvcmxkX0N1cF9BdmVyYWdlX0dvYWxzKQ0KDQphdmVyYWdlX2dvYWxzX2Jpbm5lZF93aWR0aCA8LSBkaXNjcmV0aXplKG1lcmdlZF9kYXRhJFdvcmxkX0N1cF9BdmVyYWdlX0dvYWxzLCBkaXNjID0gImVxdWFsd2lkdGgiKQ0KY29tYmluZWQxIDwtIGJpbmRfY29scyhhdmVyYWdlX2dvYWxzLCBhdmVyYWdlX2dvYWxzX2Jpbm5lZF93aWR0aCkNCmNvbG5hbWVzKGNvbWJpbmVkMSkgPC0gYygiV29ybGRfQ3VwX0F2ZXJhZ2VfR29hbHMiLCAiRXF1YWwgV2lkdGggQmluIikNCmhlYWQodW5pcXVlKGNvbWJpbmVkMSkpDQoNCmF2ZXJhZ2VfZ29hbHNfYmlubmVkX2ZyZXEgPC0gZGlzY3JldGl6ZShtZXJnZWRfZGF0YSRXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscywgZGlzYyA9ICJlcXVhbGZyZXEiKQ0KY29tYmluZWQyIDwtIGJpbmRfY29scyhhdmVyYWdlX2dvYWxzLCBhdmVyYWdlX2dvYWxzX2Jpbm5lZF9mcmVxKQ0KY29sbmFtZXMoY29tYmluZWQyKSA8LSBjKCJXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscyIsICJFcXVhbCBGcmVxdWVuY3kgQmluIikNCmhlYWQodW5pcXVlKGNvbWJpbmVkMikpDQoNCmhpc3QobWVyZ2VkX2RhdGEkV29ybGRfQ3VwX0F2ZXJhZ2VfR29hbHMsIG1haW4gPSAiSGlzdG9ncmFtIG9mIFdvcmxkIEN1cCBBdmVyYWdlIEdvYWxzIiwgeGxhYiA9ICJXb3JsZCBDdXAgQXZlcmFnZSBHb2FscyIpDQpoaXN0KGNvbWJpbmVkMSRgRXF1YWwgV2lkdGggQmluYCwgbWFpbiA9ICJIaXN0b2dyYW0gb2YgRXF1YWwgV2lkdGggQmlubmluZyIsIHhsYWIgPSAiRXF1YWwgV2lkdGggQmlucyIpDQpoaXN0KGNvbWJpbmVkMiRgRXF1YWwgRnJlcXVlbmN5IEJpbmAsIG1haW4gPSAiSGlzdG9ncmFtIG9mIEVxdWFsIEZyZXF1ZW5jeSBCaW5uaW5nIiwgeGxhYiA9ICJFcXVhbCBGcmVxdWVuY3kgQmlucyIpDQpgYGANCg0KPGJyPg0KDQojIyMjIEV4cGxhbmF0aW9uOg0KDQpJbiB0aGlzIHNlY3Rpb24gb2YgdGhlIHJlcG9ydCwgd2UgYXBwbGllZCB0cmFuc2Zvcm1hdGlvbiB0byBXb3JsZF9DdXBfQXZlcmFnZV9Hb2FscyB2YXJpYWJsZS4gVGhlIHB1cnBvc2Ugb2YgdGhlIHRyYW5zZm9ybWF0aW9uIHdhcyB0byBjaGFuZ2UgdGhlIHNjYWxlIGZvciBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgdmFyaWFibGUuIFdlIGFwcGxpZWQgYm90aCBlcXVhbCB3aWR0aCAoZGlzdGFuY2UpIGJpbm5pbmcgYW5kIGVxdWFsIGRlcHRoIChmcmVxdWVuY3kpIGJpbm5pbmcgbWV0aG9kcyB0byBkaXNjcmV0aXNlL3RyYW5zZm9ybSB0aGUgbnVtZXJpY2FsIHZhcmlhYmxlIGludG8gY2F0ZWdvcmljYWwgY291bnRlcnBhcnQuDQoNCkluIGVxdWFsLXdpZHRoIGJpbm5pbmcsIHRoZSB2YXJpYWJsZSBpcyBkaXZpZGVkIGludG8gbiBpbnRlcnZhbHMgb2YgZXF1YWwgc2l6ZSB3aGVyZWFzIGluIGVxdWFsLWRlcHRoIGJpbm5pbmcgbWV0aG9kLCB0aGUgdmFyaWFibGUgaXMgZGl2aWRlZCBpbnRvIG4gaW50ZXJ2YWxzLCBlYWNoIGNvbnRhaW5pbmcgYXBwcm94aW1hdGVseSB0aGUgc2FtZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLiBXZSBhcHBsaWVkIHRoZXNlIG1ldGhvZHMgdXNpbmcgdGhlIGRpc2NyZXRpemUgZnVuY3Rpb24gdW5kZXIgdGhlIGluZm90aGVvIHBhY2thZ2UuIEJ5IGJpbm5pbmcgdGhlIGRhdGEsIHRoZSBzY2FsZSBvZiB0aGUgY29udGludW91cyBkYXRhIHdhcyBjb252ZXJ0ZWQgaW50byBkaXNjcmV0ZSBjYXRlZ29yaWVzL2JpbnMuIEFzIGEgcmVzdWx0LCB0aGUgZGlzdHJpYnV0aW9uYWwgcHJvcGVydGllcyBvZiB0aGUgdmFyaWFibGUgYWxzbyBjaGFuZ2VkIHdoaWNoIGlzIHZpc2libGUgaW4gdGhlIGFib3ZlIGhpc3RvZ3JhbXMuDQoNCjxicj4NCg==