Required packages

library(readr)
library(dplyr)
library(tidyr)
library(Hmisc)
library(stringr)
library(lubridate)
library(outliers)
library(MVN)
library(ggplot2)
library(knitr)
library(gdata)
library(mlr)
library(forecast)

Executive Summary

Data preprocessing plays a crucial role in statistical analysis as it can significantly influence the statistical conclusion based on data. It can broadly be described as the cleaning or preparing all forms of untidy data(incomplete, noisy and inconsistent data) for statistical analysis. We have performed all the 5 major tasks of preprocessing namely- get, understand, tidy& manipulate,scan and transform.

First we imported the two datasets using readr function, these two datasets were sorted and subset was created before merging by ‘inner_join’ function. This merged dataset is understood in terms of its class and structure and, wherever needed,the class of variables were converted into date, factor etc.

After understanding the data, the data is checked for its tidyness using Hadley Wickham and Grolemund (2016) tidy data rules and the PlayerID which is alpha-numeric is separated into two variables Name and ID. The data is also manipulated using ‘dyplyr’ package wherein the career in terms of years of a player is found out using mutate() function. Later, for scanning the missing values and outliers the data is again subset with two numerical and one character variables. Both the numerical variables were scanned for the missing values using ‘is.na’ function and the missing values were imputed with the mean of the respective variables. Both the numerical variables are also scanned for outliers by creating using Tukey’s fencing method and number of outliers were confirmed by distance method(Z score) and the outliers detected were removed by capping function.

In the end, the values of one of the numerical variable were transformed by mathematical operations to decrease the skewness and convert it to a normal distribution.This was attained by raising the data to higher power(power transformation) and by taking the root(square root transformation).

Data

The data chosen for this assignment is about National Football League (NFL), a professional American football league.The NFL is one of the four major professional sports leagues in North America, and the highest professional level of American football in the world. The data is sourced from Kaggle.com, an open data source. The selected datasets can be found at the URL: https://www.kaggle.com/kendallgillies/nflstatistics/home.

There are two sets of data- main dataset and allied dataset. The first main group of statistics is ‘basic.csv’ where the basic statistics are provided for each player. The data has the following variables- Age Birth Place Birthday College Attended Current Satus Current Team Experience(number of seasons played) Height(inches) High School Attended High School Location Name of the player Number PlayerId Position Weight(lbs) Years played

The second main group of statistics gathered for each player are their career statistics. Each player has a main role but they have statistics in other areas as well. we have taken the career staistics of the players on the offensive line, stored in ‘offensive.csv’ and has following variables: PlayerId Name Position Year Team GamesPlayed GamesStarted

The Working directory is loaded and data is imported with readr “read_csv” function. The csv files can also be imported into R using base R “read.csv” function, however, read.csv function automatically converts strings/character(class/data type) into factor which is taken care of by changing “stringsAsFactors” clause.

setwd("C:/Users/Neeraj/Desktop/data preprocessing/assignment 3")
basic <- read_csv("basic.csv")
Parsed with column specification:
cols(
  Age = col_integer(),
  `Birth Place` = col_character(),
  Birthday = col_character(),
  College = col_character(),
  `Current Status` = col_character(),
  `Current Team` = col_character(),
  Experience = col_character(),
  `Height (inches)` = col_integer(),
  `High School` = col_character(),
  `High School Location` = col_character(),
  Name = col_character(),
  Number = col_integer(),
  `Player Id` = col_character(),
  Position = col_character(),
  `Weight (lbs)` = col_integer(),
  `Years Played` = col_character()
)
offensive <- read_csv("offensive.csv")
Parsed with column specification:
cols(
  `Player Id` = col_character(),
  Name = col_character(),
  Position = col_character(),
  Year = col_integer(),
  Team = col_character(),
  `Games Played` = col_integer(),
  `Games Started` = col_character()
)

The first six rows of the main dataset is displayed using head() function.

head(basic)

The first six rows of the allied dataset is displayed using head() function.

head(offensive)

In the below chunk, the main data is sorted for clarity and easy computation. The original data is subset and seven useful variables are taken from a total of 16 variables. these 7 variables are: PlayerID,Birthday, college, current status,Experience, Height, Weight.

basic_new <- basic[ ,c(13,3,4,5,7,8,15)]
basic_new

The columns of the allied dataset are also sorted and renamed to enhance the coherence of the dataset. Here, the 7 variables from the original dataset are summarised in to minimum and maximum year with the total number of games played and grouped on their playerID to better summarise the data. The first few observations are also displayed.

offensive_new <- offensive %>% group_by(`Player Id`) %>% summarise(start_year = min(Year, na.rm = FALSE),
          end_year = max(Year, na.rm = FALSE),
          total_games = sum(`Games Played`, na.rm = FALSE))
head(offensive_new)

Both the subsets of main dataset(“basic_new”) and the allied dataset(“offensive_new”) is merged together as “NFLdata”" using the inner_join() function on the key variable(“Player Id”). This merging condensed/shrinked the data into 2885 observations of 10 variables, the first six observations of which are displayed here using the head() function.

NFLdata <- basic_new %>% inner_join(offensive_new, by = "Player Id")
head(NFLdata)

Understand

The class() function is used to check the class/type of an individual variable/object.

class(NFLdata$College)
[1] "character"
class(NFLdata$`Height (inches)`)
[1] "integer"

The class of Birthday variable is shown as “character”. This is because when date and time data are imported into R they will often default to a character string (or factors if you are using stringsAsFactors = FALSE option). Hence, we need to convert strings to proper date format.

class(NFLdata$Birthday)
[1] "character"

There are different ways to convert this variable into date format.First one is by using “as.Date()” function under Base R and the other way is by using “lubridate” package. However, under the Base R function the default date format is YYYY-MM-DD and for the different format the format argument must be specified explicitly Whereas the “lubridate” package can automatically recognise the common separators and we only need to specify the order of the date elments. Here, the class of Birthday variable using “lubridate” package and checked again for confirmation.

NFLdata$Birthday <- mdy(NFLdata$Birthday)
class(NFLdata$Birthday)
[1] "Date"

The ‘Current Status’ variable is also converted to factor from character variable and checked.

NFLdata$`Current Status`<- as.factor(NFLdata$`Current Status`)
class(NFLdata$`Current Status`)
[1] "factor"

The ‘Experience’ variable is sorted into similar types, factored, levelled and labelled and ordered as per the player experience.

NFLdata$Experience <- factor( c(NFLdata$Experience), 
levels = c("Rookie",    "0 Season", "1 Season", "1st season",   "2 Seasons",    "2nd season",   "3 Seasons",    "3rd season",   "4 Seasons",    "4th season",   "5 Seasons",    "5th season",   "6 Seasons",    "6th season",   "7 Seasons",    "7th season",   "8 Seasons",    "8th season",   "9 Seasons",    "9th season",   "10 Seasons",   "10th season",  "11 Seasons",   "11th season",  "12 Seasons",   "13 Seasons",   "14 Seasons",   "14th season",  "17 Seasons",   "18 Seasons",   "18th season"), 
labels = c("Rookie",    "0 Season", "1 Season", "1 Season", "2 Seasons",    "2 Seasons",    "3 Seasons",    "3 Seasons",    "4 Seasons",    "4 Seasons",    "5 Seasons",    "5 Seasons",    "6 Seasons",    "6 Seasons",    "7 Seasons",    "7 Seasons",    "8 Seasons",    "8 Seasons",    "9 Seasons",    "9 Seasons",    "10 Seasons",   "10 Seasons",   "11 Seasons",   "11 Seasons",   "12 Seasons",   "13 Seasons",   "14 Seasons",   "14 Seasons",   "17 Seasons",   "18 Seasons",   "18 Seasons"), ordered = TRUE)

we can also check the class and other characteristics of data using str()function which compactly displays the structure of the whole dataset.

str(NFLdata)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   2885 obs. of  10 variables:
 $ Player Id      : chr  "jimraiff/2523700" "rosspetty/2523136" "billschuler/2525128" "georgesergienko/2525335" ...
 $ Birthday       : Date, format: "1930-12-21" ...
 $ College        : chr  "Dayton" "Illinois" "Yale" "American International" ...
 $ Current Status : Factor w/ 4 levels "Active","Physically unable to perform",..: 3 3 3 3 3 3 1 1 3 1 ...
 $ Experience     : Ord.factor w/ 18 levels "Rookie"<"0 Season"<..: 3 3 4 6 10 3 10 5 6 4 ...
 $ Height (inches): int  70 73 72 73 77 78 76 75 76 77 ...
 $ Weight (lbs)   : int  235 180 215 248 325 275 305 309 265 316 ...
 $ start_year     : num  1954 1920 1947 1943 1997 ...
 $ end_year       : num  1954 1920 1948 1946 2004 ...
 $ total_games    : int  3 10 23 30 54 6 112 17 35 14 ...

Tidy & Manipulate Data I

As per Hadley Wickham and Grolemund (2016), the three tidy data rules are: (a)Each variable must have its own column.(b) Each observation must have its own row.(c)Each value must have its own cell.On the basis of these tidy rules, the dataset in general can be considered as tidy data. However, in the original dataset, it is found that both the first name and last name of players are given in one column separated by a coma. As we are not sure about which one is first and last name of the players, the name is not separated in order to avoid confusion. In the below chunk, the PlayerId is separated into Names and ID variables using the ‘tidyr’ package separate() function.

NFLdata1 <- NFLdata %>% separate(`Player Id`, into = c("Name", "ID"), sep = "/")
head(NFLdata1)

Tidy & Manipulate Data II

To calculate the time frame of a player’s career, the mutate() function is used to create a new column,‘career_year’, by using the data of start_year and end_year. It seems that few players have entered and exited in their playing career in the same year however taking only the difference of their start year and end year will give zero year which is not correct logically. In order to solve this practical conundrum, one is added implicitly so that both the start year and end year are included to correctly represent the career_years.

mutate(NFLdata1,
       career_years = end_year - start_year + 1)

Scan I

In the below chunk, the numerical variables of the dataset are further subset into ‘NFL_subset’ with three variables namely- ID, Height(inches) and Weight(lbs) to scan the missing values/incostistencies and outliers and for data transformation.

NFL_subset<- NFLdata1[ ,c(2,7,8)]
NFL_subset

The colSums() function is applied to compute the total missing values in each column of the dataset.There are 56 and 18 missing values(NA’s) in Height and weight respectively.

colSums(is.na(NFL_subset))
             ID Height (inches)    Weight (lbs) 
              0              56              18 

The which() function is used to identify the location of NA’s in the Height variable.

which(is.na(NFL_subset$`Height (inches)`))
 [1]   13   86   93  109  144  225  254  273  319  351  368  395  416
[14]  498  551  606  633  654  676  700  740  741  786  873  956 1042
[27] 1123 1129 1321 1507 1555 1658 1693 1724 1804 1874 1916 1996 2043
[40] 2061 2194 2342 2358 2423 2453 2469 2541 2557 2610 2745 2756 2778
[53] 2797 2813 2815 2882

There are various ways to handle the missing values in the dataset like excluding the missing values or replace the missing values with some constant or with mean,median or mode. Although the number of NA’s is very small(almost 2%) and can be excluded. However, the exclusion/deletion of NA’s sometimes leads to biased subset of data. Hence, the missing values are imputed, here, with the mean of the Height variable using base R functions. Alternatively, Hmisc package can also be used for imputation of mean, median or mode.

NFL_subset$`Height (inches)`[is.na(NFL_subset$`Height (inches)`)] <- mean(NFL_subset$`Height (inches)`, na.rm = TRUE)

The Height variable is checked again for NA’s to confirm the imputation of mean into the missing values.

which(is.na(NFL_subset$`Height (inches)`))
integer(0)

After the height variable, the same procedure is followed to impute the NA’s in weight variable as well and the number of NA’s are checked and confirmed after the imputation of the mean of the Weight variable.

NFL_subset$`Weight (lbs)`[is.na(NFL_subset$`Weight (lbs)`)] <- mean(NFL_subset$`Weight (lbs)`, na.rm = TRUE)
which(is.na(NFL_subset$`Weight (lbs)`))
integer(0)

Scan II

There are two main methods for detecting the univariate outliers- Tukey’s method of outlier detection and distance based method. In the below chunk, the box plot is created using the Tukey’s method and outliers are seen lying outside the limits called ‘fences’.

NFL_subset$`Height (inches)` %>%  boxplot(main="Box Plot of Height", ylab = "Inches")

As box plot is a visual representation of outliers and in order to proceed with the handling of the outliers, their number should be confirmed. The distance based methods(like Z-score method etc.) help in confirming the exact number of outliers. Hence, in the chunk below, the Z-Score is calculated for Height variable using score() function from “outlier” package. The summary() function displays the minimum(-3.4370) & maximum(2.4930) Z-score of the Height variable.

zscores_Height <- NFL_subset$`Height (inches)` %>%  scores(type = "z")
zscores_Height %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-3.4370 -0.6697  0.1210  0.0000  0.5164  2.4930 

An observation is regarded as an outlier if the absolute value of its z-score is greater than 3. Therefore, in the following chunk, the total number of outliers according to the Z-score is calculated using length() function which comes out to be 14.

length (which( abs(zscores_Height) >3 ))
[1] 14

There are various approaches to handle the outliers like- excluding or deleting, imputing, capping/winsorising or transforming and binning. However,before deciding the approach to handle them, the outliers should be analysed as to whether they are a result of data entry/processing error and if so, they can be deleted or imputed. Due to lack of certainty for the reasons of outliers, the capping(a.k.a winsorising) approach is used. For capping, the user-defined function mentioned below(as cap)is applied to the height variable and assigned to Height_capped.

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
}
Height_capped <- NFL_subset$`Height (inches)` %>% cap()

A boxplot of Height_capped is created again to confirm there are no remaining outliers.

Height_capped %>%  boxplot(main="Box Plot of Height", ylab = "Inches")

After Height, the Weight is also checked for the outliers and is handled in the similar fashion.(i.e - creation of boxplot, checking exact number of outliers via Z-score method and then handling the outliers via capping.) In the end, the boxplot is created again to check for the remaining outliers.

NFL_subset$`Weight (lbs)` %>%  boxplot(main="Box Plot of Weight", ylab = "Lbs")

zscores_Weight <- NFL_subset$`Weight (lbs)` %>%  scores(type = "z")
zscores_Weight %>% summary()
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-5.52961 -0.81285 -0.05903  0.00000  0.91017  2.52550 
length (which( abs(zscores_Weight) >3 ))
[1] 10
Weight_capped <- NFL_subset$`Weight (lbs)` %>% cap()
Weight_capped %>%  boxplot(main="Box Plot of Weight", ylab = "Lbs")

Transform

It is necessary to perform some data transformations on the tidy data before it can be used for modeling. Data transformation can be used in a variety of situations like: (1) To change the scale of a variable or standardise the values of a variable for better understanding. (2)To transform complex non-linear relationships into linear relationships (3) To reduce skewness and/or heterogeneity of variance and make it a normal distribution.

Here, the distribution of Height variable is visualised with the help of a histogram. The resulting histogram appears to be slightly left skewed.

hist(Height_capped)

As the histogram of height distribution is slightly skewed to the left, the data is transformed by raising the power to 5 and further by taking the fourth root. After performing the mathematical operations, a histogram is again plotted to check the transformed distribution. The transformation not always results in a perfect symmetrical shape. The distribution got better than before,hence, we can proceed with the resulting transformation.

height_transformed<- (Height_capped^5)^1/4
hist(height_transformed)
NAs produced by integer overflow



LS0tDQp0aXRsZTogIk1BVEgyMzQ5IFNlbWVzdGVyIDIsIDIwMTgiDQphdXRob3I6DQogIC0gTmVlcmFqIFNlaHJhd2F0LCBTdHVkZW50SUQtIHMzNzExNzEyDQogIC0gUmlhIFRhbHdhciwgU3R1ZGVudElELSBzMzcyOTYxOA0KICAtIFJhZGhpa2EgU2FudG9zaCBaYXdhciwgU3R1ZGVudElELSBzMzczNDkzOQ0Kc3VidGl0bGU6IEFzc2lnbm1lbnQgMw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQotLS0NCg0KDQojIyBSZXF1aXJlZCBwYWNrYWdlcyANCg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoSG1pc2MpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkob3V0bGllcnMpDQpsaWJyYXJ5KE1WTikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGdkYXRhKQ0KbGlicmFyeShtbHIpDQpsaWJyYXJ5KGZvcmVjYXN0KQ0KDQoNCmBgYA0KDQoNCiMjIEV4ZWN1dGl2ZSBTdW1tYXJ5IA0KDQpEYXRhIHByZXByb2Nlc3NpbmcgcGxheXMgYSBjcnVjaWFsIHJvbGUgaW4gc3RhdGlzdGljYWwgYW5hbHlzaXMgYXMgaXQgY2FuIHNpZ25pZmljYW50bHkgaW5mbHVlbmNlIHRoZSBzdGF0aXN0aWNhbCBjb25jbHVzaW9uIGJhc2VkIG9uIGRhdGEuIEl0IGNhbiBicm9hZGx5IGJlIGRlc2NyaWJlZCBhcyB0aGUgY2xlYW5pbmcgb3IgcHJlcGFyaW5nIGFsbCBmb3JtcyBvZiB1bnRpZHkgZGF0YShpbmNvbXBsZXRlLCBub2lzeSBhbmQgaW5jb25zaXN0ZW50IGRhdGEpIGZvciBzdGF0aXN0aWNhbCBhbmFseXNpcy4NCldlIGhhdmUgcGVyZm9ybWVkIGFsbCB0aGUgNSBtYWpvciB0YXNrcyBvZiBwcmVwcm9jZXNzaW5nIG5hbWVseS0gZ2V0LCB1bmRlcnN0YW5kLCB0aWR5JiBtYW5pcHVsYXRlLHNjYW4gYW5kIHRyYW5zZm9ybS4NCg0KRmlyc3Qgd2UgaW1wb3J0ZWQgdGhlIHR3byBkYXRhc2V0cyB1c2luZyByZWFkciBmdW5jdGlvbiwgdGhlc2UgdHdvIGRhdGFzZXRzIHdlcmUgc29ydGVkIGFuZCBzdWJzZXQgd2FzIGNyZWF0ZWQgYmVmb3JlIG1lcmdpbmcgYnkgJ2lubmVyX2pvaW4nIGZ1bmN0aW9uLiBUaGlzIG1lcmdlZCBkYXRhc2V0IGlzIHVuZGVyc3Rvb2QgaW4gdGVybXMgb2YgaXRzIGNsYXNzIGFuZCBzdHJ1Y3R1cmUgYW5kLCB3aGVyZXZlciBuZWVkZWQsdGhlIGNsYXNzIG9mIHZhcmlhYmxlcyB3ZXJlIGNvbnZlcnRlZCBpbnRvIGRhdGUsIGZhY3RvciBldGMuDQoNCkFmdGVyIHVuZGVyc3RhbmRpbmcgdGhlIGRhdGEsIHRoZSBkYXRhIGlzIGNoZWNrZWQgZm9yIGl0cyB0aWR5bmVzcyB1c2luZyBIYWRsZXkgV2lja2hhbSBhbmQgR3JvbGVtdW5kICgyMDE2KSB0aWR5IGRhdGEgcnVsZXMgYW5kIHRoZSBQbGF5ZXJJRCB3aGljaCBpcyBhbHBoYS1udW1lcmljIGlzIHNlcGFyYXRlZCBpbnRvIHR3byB2YXJpYWJsZXMgTmFtZSBhbmQgSUQuIFRoZSBkYXRhIGlzIGFsc28gbWFuaXB1bGF0ZWQgdXNpbmcgJ2R5cGx5cicgcGFja2FnZSB3aGVyZWluIHRoZSBjYXJlZXIgaW4gdGVybXMgb2YgeWVhcnMgb2YgYSBwbGF5ZXIgaXMgZm91bmQgb3V0IHVzaW5nIG11dGF0ZSgpIGZ1bmN0aW9uLg0KTGF0ZXIsIGZvciBzY2FubmluZyB0aGUgbWlzc2luZyB2YWx1ZXMgYW5kIG91dGxpZXJzIHRoZSBkYXRhIGlzIGFnYWluIHN1YnNldCB3aXRoIHR3byBudW1lcmljYWwgYW5kIG9uZSBjaGFyYWN0ZXIgdmFyaWFibGVzLiBCb3RoIHRoZSBudW1lcmljYWwgdmFyaWFibGVzIHdlcmUgc2Nhbm5lZCBmb3IgdGhlIG1pc3NpbmcgdmFsdWVzIHVzaW5nICdpcy5uYScgZnVuY3Rpb24gYW5kIHRoZSBtaXNzaW5nIHZhbHVlcyB3ZXJlIGltcHV0ZWQgd2l0aCB0aGUgbWVhbiBvZiB0aGUgcmVzcGVjdGl2ZSB2YXJpYWJsZXMuICBCb3RoIHRoZSBudW1lcmljYWwgdmFyaWFibGVzIGFyZSBhbHNvIHNjYW5uZWQgZm9yIG91dGxpZXJzIGJ5IGNyZWF0aW5nIHVzaW5nIFR1a2V5J3MgZmVuY2luZyBtZXRob2QgYW5kIG51bWJlciBvZiBvdXRsaWVycyB3ZXJlIGNvbmZpcm1lZCBieSBkaXN0YW5jZSBtZXRob2QoWiBzY29yZSkgYW5kIHRoZSBvdXRsaWVycyBkZXRlY3RlZCB3ZXJlIHJlbW92ZWQgYnkgY2FwcGluZyBmdW5jdGlvbi4gDQoNCkluIHRoZSBlbmQsIHRoZSB2YWx1ZXMgb2Ygb25lIG9mIHRoZSBudW1lcmljYWwgdmFyaWFibGUgd2VyZSB0cmFuc2Zvcm1lZCBieSBtYXRoZW1hdGljYWwgb3BlcmF0aW9ucyB0byBkZWNyZWFzZSB0aGUgc2tld25lc3MgYW5kIGNvbnZlcnQgaXQgdG8gYSBub3JtYWwgZGlzdHJpYnV0aW9uLlRoaXMgd2FzIGF0dGFpbmVkIGJ5IHJhaXNpbmcgdGhlIGRhdGEgdG8gaGlnaGVyIHBvd2VyKHBvd2VyIHRyYW5zZm9ybWF0aW9uKSBhbmQgYnkgdGFraW5nIHRoZSByb290KHNxdWFyZSByb290IHRyYW5zZm9ybWF0aW9uKS4NCg0KDQojIyBEYXRhIA0KDQpUaGUgZGF0YSBjaG9zZW4gZm9yIHRoaXMgYXNzaWdubWVudCBpcyBhYm91dCBOYXRpb25hbCBGb290YmFsbCBMZWFndWUgKE5GTCksIGEgcHJvZmVzc2lvbmFsIEFtZXJpY2FuIGZvb3RiYWxsIGxlYWd1ZS5UaGUgTkZMIGlzIG9uZSBvZiB0aGUgZm91ciBtYWpvciBwcm9mZXNzaW9uYWwgc3BvcnRzIGxlYWd1ZXMgaW4gTm9ydGggQW1lcmljYSwgYW5kIHRoZSBoaWdoZXN0IHByb2Zlc3Npb25hbCBsZXZlbCBvZiBBbWVyaWNhbiBmb290YmFsbCBpbiB0aGUgd29ybGQuIFRoZSBkYXRhIGlzIHNvdXJjZWQgZnJvbSBLYWdnbGUuY29tLCBhbiBvcGVuIGRhdGEgc291cmNlLiBUaGUgc2VsZWN0ZWQgZGF0YXNldHMgY2FuIGJlIGZvdW5kIGF0IHRoZSBVUkw6IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20va2VuZGFsbGdpbGxpZXMvbmZsc3RhdGlzdGljcy9ob21lLg0KDQpUaGVyZSBhcmUgdHdvIHNldHMgb2YgZGF0YS0gbWFpbiBkYXRhc2V0IGFuZCBhbGxpZWQgZGF0YXNldC4NClRoZSBmaXJzdCBtYWluIGdyb3VwIG9mIHN0YXRpc3RpY3MgaXMgJ2Jhc2ljLmNzdicgd2hlcmUgdGhlIGJhc2ljIHN0YXRpc3RpY3MgYXJlIHByb3ZpZGVkIGZvciBlYWNoIHBsYXllci4gVGhlIGRhdGEgaGFzIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzLSANCkFnZQ0KQmlydGggUGxhY2UNCkJpcnRoZGF5DQpDb2xsZWdlIEF0dGVuZGVkDQpDdXJyZW50IFNhdHVzDQpDdXJyZW50IFRlYW0NCkV4cGVyaWVuY2UobnVtYmVyIG9mIHNlYXNvbnMgcGxheWVkKQ0KSGVpZ2h0KGluY2hlcykNCkhpZ2ggU2Nob29sIEF0dGVuZGVkDQpIaWdoIFNjaG9vbCBMb2NhdGlvbg0KTmFtZSBvZiB0aGUgcGxheWVyDQpOdW1iZXINClBsYXllcklkDQpQb3NpdGlvbg0KV2VpZ2h0KGxicykNClllYXJzIHBsYXllZA0KDQpUaGUgc2Vjb25kIG1haW4gZ3JvdXAgb2Ygc3RhdGlzdGljcyBnYXRoZXJlZCBmb3IgZWFjaCBwbGF5ZXIgYXJlIHRoZWlyIGNhcmVlciBzdGF0aXN0aWNzLiBFYWNoIHBsYXllciBoYXMgYSBtYWluIHJvbGUgYnV0IHRoZXkgaGF2ZSBzdGF0aXN0aWNzIGluIG90aGVyIGFyZWFzIGFzIHdlbGwuIHdlIGhhdmUgdGFrZW4gdGhlIGNhcmVlciBzdGFpc3RpY3Mgb2YgdGhlIHBsYXllcnMgb24gdGhlIG9mZmVuc2l2ZSBsaW5lLCBzdG9yZWQgaW4gJ29mZmVuc2l2ZS5jc3YnIGFuZCBoYXMgZm9sbG93aW5nIHZhcmlhYmxlczoNClBsYXllcklkDQpOYW1lDQpQb3NpdGlvbg0KWWVhcg0KVGVhbQ0KR2FtZXNQbGF5ZWQNCkdhbWVzU3RhcnRlZA0KDQpUaGUgV29ya2luZyBkaXJlY3RvcnkgaXMgbG9hZGVkIGFuZCBkYXRhIGlzIGltcG9ydGVkIHdpdGggcmVhZHIgInJlYWRfY3N2IiBmdW5jdGlvbi4gVGhlIGNzdiBmaWxlcyBjYW4gYWxzbyBiZSBpbXBvcnRlZCBpbnRvIFIgdXNpbmcgYmFzZSBSICJyZWFkLmNzdiIgZnVuY3Rpb24sIGhvd2V2ZXIsIHJlYWQuY3N2IGZ1bmN0aW9uIGF1dG9tYXRpY2FsbHkgY29udmVydHMgc3RyaW5ncy9jaGFyYWN0ZXIoY2xhc3MvZGF0YSB0eXBlKSBpbnRvIGZhY3RvciB3aGljaCBpcyB0YWtlbiBjYXJlIG9mIGJ5ICBjaGFuZ2luZyAic3RyaW5nc0FzRmFjdG9ycyIgY2xhdXNlLg0KDQpgYGB7cn0NCnNldHdkKCJDOi9Vc2Vycy9OZWVyYWovRGVza3RvcC9kYXRhIHByZXByb2Nlc3NpbmcvYXNzaWdubWVudCAzIikNCmJhc2ljIDwtIHJlYWRfY3N2KCJiYXNpYy5jc3YiKQ0Kb2ZmZW5zaXZlIDwtIHJlYWRfY3N2KCJvZmZlbnNpdmUuY3N2IikNCmBgYA0KDQpUaGUgZmlyc3Qgc2l4IHJvd3Mgb2YgdGhlIG1haW4gZGF0YXNldCBpcyBkaXNwbGF5ZWQgdXNpbmcgaGVhZCgpIGZ1bmN0aW9uLg0KYGBge3J9DQpoZWFkKGJhc2ljKQ0KYGBgDQoNClRoZSBmaXJzdCBzaXggcm93cyBvZiB0aGUgYWxsaWVkIGRhdGFzZXQgaXMgZGlzcGxheWVkIHVzaW5nIGhlYWQoKSBmdW5jdGlvbi4NCmBgYHtyfQ0KaGVhZChvZmZlbnNpdmUpDQpgYGANCg0KSW4gdGhlIGJlbG93IGNodW5rLCB0aGUgbWFpbiBkYXRhIGlzIHNvcnRlZCBmb3IgY2xhcml0eSBhbmQgZWFzeSBjb21wdXRhdGlvbi4gVGhlIG9yaWdpbmFsIGRhdGEgaXMgc3Vic2V0IGFuZCBzZXZlbiB1c2VmdWwgdmFyaWFibGVzIGFyZSB0YWtlbiBmcm9tIGEgdG90YWwgb2YgMTYgdmFyaWFibGVzLiB0aGVzZSA3IHZhcmlhYmxlcyBhcmU6IFBsYXllcklELEJpcnRoZGF5LCBjb2xsZWdlLCBjdXJyZW50IHN0YXR1cyxFeHBlcmllbmNlLCBIZWlnaHQsIFdlaWdodC4NCmBgYHtyfQ0KYmFzaWNfbmV3IDwtIGJhc2ljWyAsYygxMywzLDQsNSw3LDgsMTUpXQ0KYmFzaWNfbmV3DQpgYGANCg0KDQpUaGUgY29sdW1ucyBvZiB0aGUgYWxsaWVkIGRhdGFzZXQgYXJlIGFsc28gc29ydGVkIGFuZCByZW5hbWVkIHRvIGVuaGFuY2UgdGhlIGNvaGVyZW5jZSBvZiB0aGUgZGF0YXNldC4gSGVyZSwgdGhlIDcgdmFyaWFibGVzIGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQgYXJlIHN1bW1hcmlzZWQgaW4gdG8gbWluaW11bSBhbmQgbWF4aW11bSB5ZWFyIHdpdGggdGhlIHRvdGFsIG51bWJlciBvZiBnYW1lcyBwbGF5ZWQgYW5kIGdyb3VwZWQgb24gdGhlaXIgcGxheWVySUQgdG8gYmV0dGVyIHN1bW1hcmlzZSB0aGUgZGF0YS4gVGhlIGZpcnN0IGZldyBvYnNlcnZhdGlvbnMgYXJlIGFsc28gZGlzcGxheWVkLg0KYGBge3J9DQpvZmZlbnNpdmVfbmV3IDwtIG9mZmVuc2l2ZSAlPiUgZ3JvdXBfYnkoYFBsYXllciBJZGApICU+JSBzdW1tYXJpc2Uoc3RhcnRfeWVhciA9IG1pbihZZWFyLCBuYS5ybSA9IEZBTFNFKSwNCiAgICAgICAgICBlbmRfeWVhciA9IG1heChZZWFyLCBuYS5ybSA9IEZBTFNFKSwNCiAgICAgICAgICB0b3RhbF9nYW1lcyA9IHN1bShgR2FtZXMgUGxheWVkYCwgbmEucm0gPSBGQUxTRSkpDQoNCmhlYWQob2ZmZW5zaXZlX25ldykNCmBgYA0KDQpCb3RoIHRoZSBzdWJzZXRzIG9mIG1haW4gZGF0YXNldCgiYmFzaWNfbmV3IikgYW5kIHRoZSBhbGxpZWQgZGF0YXNldCgib2ZmZW5zaXZlX25ldyIpIGlzIG1lcmdlZCB0b2dldGhlciBhcyAiTkZMZGF0YSIiIHVzaW5nIHRoZSBpbm5lcl9qb2luKCkgZnVuY3Rpb24gb24gdGhlIGtleSB2YXJpYWJsZSgiUGxheWVyIElkIikuIFRoaXMgbWVyZ2luZyBjb25kZW5zZWQvc2hyaW5rZWQgdGhlIGRhdGEgaW50byAyODg1IG9ic2VydmF0aW9ucyBvZiAxMCB2YXJpYWJsZXMsIHRoZSBmaXJzdCBzaXggb2JzZXJ2YXRpb25zIG9mIHdoaWNoIGFyZSBkaXNwbGF5ZWQgaGVyZSB1c2luZyB0aGUgaGVhZCgpIGZ1bmN0aW9uLg0KYGBge3J9DQpORkxkYXRhIDwtIGJhc2ljX25ldyAlPiUgaW5uZXJfam9pbihvZmZlbnNpdmVfbmV3LCBieSA9ICJQbGF5ZXIgSWQiKQ0KaGVhZChORkxkYXRhKQ0KDQpgYGANCg0KDQojIyBVbmRlcnN0YW5kIA0KDQpUaGUgY2xhc3MoKSBmdW5jdGlvbiBpcyB1c2VkIHRvIGNoZWNrIHRoZSBjbGFzcy90eXBlIG9mIGFuIGluZGl2aWR1YWwgdmFyaWFibGUvb2JqZWN0Lg0KDQpgYGB7cn0NCmNsYXNzKE5GTGRhdGEkQ29sbGVnZSkNCmBgYA0KDQpgYGB7cn0NCmNsYXNzKE5GTGRhdGEkYEhlaWdodCAoaW5jaGVzKWApDQpgYGANCg0KDQoNClRoZSBjbGFzcyBvZiAgQmlydGhkYXkgdmFyaWFibGUgaXMgc2hvd24gYXMgImNoYXJhY3RlciIuIFRoaXMgaXMgYmVjYXVzZSB3aGVuIGRhdGUgYW5kIHRpbWUgZGF0YSBhcmUgaW1wb3J0ZWQgaW50byBSIHRoZXkgd2lsbCBvZnRlbiBkZWZhdWx0IHRvIGEgY2hhcmFjdGVyIHN0cmluZyAob3IgZmFjdG9ycyBpZiB5b3UgYXJlIHVzaW5nICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0Ugb3B0aW9uKS4gSGVuY2UsIHdlIG5lZWQgdG8gY29udmVydCBzdHJpbmdzIHRvIHByb3BlciBkYXRlIGZvcm1hdC4NCmBgYHtyfQ0KY2xhc3MoTkZMZGF0YSRCaXJ0aGRheSkNCmBgYA0KDQpUaGVyZSBhcmUgZGlmZmVyZW50IHdheXMgdG8gY29udmVydCB0aGlzIHZhcmlhYmxlIGludG8gZGF0ZSBmb3JtYXQuRmlyc3Qgb25lIGlzIGJ5IHVzaW5nICJhcy5EYXRlKCkiIGZ1bmN0aW9uIHVuZGVyIEJhc2UgUiBhbmQgdGhlIG90aGVyIHdheSBpcyBieSB1c2luZyAibHVicmlkYXRlIiBwYWNrYWdlLiBIb3dldmVyLCB1bmRlciB0aGUgQmFzZSBSIGZ1bmN0aW9uIHRoZSBkZWZhdWx0IGRhdGUgZm9ybWF0IGlzIFlZWVktTU0tREQgYW5kIGZvciB0aGUgZGlmZmVyZW50IGZvcm1hdCB0aGUgZm9ybWF0IGFyZ3VtZW50IG11c3QgYmUgc3BlY2lmaWVkIGV4cGxpY2l0bHkgV2hlcmVhcyB0aGUgImx1YnJpZGF0ZSIgcGFja2FnZSBjYW4gYXV0b21hdGljYWxseSByZWNvZ25pc2UgdGhlIGNvbW1vbiBzZXBhcmF0b3JzIGFuZCB3ZSBvbmx5IG5lZWQgdG8gc3BlY2lmeSB0aGUgb3JkZXIgb2YgdGhlIGRhdGUgZWxtZW50cy4gSGVyZSwgdGhlIGNsYXNzIG9mIEJpcnRoZGF5IHZhcmlhYmxlIHVzaW5nICJsdWJyaWRhdGUiIHBhY2thZ2UgYW5kIGNoZWNrZWQgYWdhaW4gZm9yIGNvbmZpcm1hdGlvbi4NCg0KYGBge3J9DQpORkxkYXRhJEJpcnRoZGF5IDwtIG1keShORkxkYXRhJEJpcnRoZGF5KQ0KY2xhc3MoTkZMZGF0YSRCaXJ0aGRheSkNCmBgYA0KDQpUaGUgJ0N1cnJlbnQgU3RhdHVzJyB2YXJpYWJsZSBpcyBhbHNvIGNvbnZlcnRlZCB0byBmYWN0b3IgZnJvbSBjaGFyYWN0ZXIgdmFyaWFibGUgYW5kIGNoZWNrZWQuDQpgYGB7cn0NCk5GTGRhdGEkYEN1cnJlbnQgU3RhdHVzYDwtIGFzLmZhY3RvcihORkxkYXRhJGBDdXJyZW50IFN0YXR1c2ApDQpjbGFzcyhORkxkYXRhJGBDdXJyZW50IFN0YXR1c2ApDQpgYGANCg0KDQpUaGUgJ0V4cGVyaWVuY2UnIHZhcmlhYmxlIGlzIHNvcnRlZCBpbnRvIHNpbWlsYXIgdHlwZXMsIGZhY3RvcmVkLCBsZXZlbGxlZCBhbmQgbGFiZWxsZWQgYW5kIG9yZGVyZWQgYXMgcGVyIHRoZSBwbGF5ZXIgZXhwZXJpZW5jZS4NCmBgYHtyfQ0KTkZMZGF0YSRFeHBlcmllbmNlIDwtIGZhY3RvciggYyhORkxkYXRhJEV4cGVyaWVuY2UpLCANCmxldmVscyA9IGMoIlJvb2tpZSIsCSIwIFNlYXNvbiIsCSIxIFNlYXNvbiIsCSIxc3Qgc2Vhc29uIiwJIjIgU2Vhc29ucyIsCSIybmQgc2Vhc29uIiwJIjMgU2Vhc29ucyIsCSIzcmQgc2Vhc29uIiwJIjQgU2Vhc29ucyIsCSI0dGggc2Vhc29uIiwJIjUgU2Vhc29ucyIsCSI1dGggc2Vhc29uIiwJIjYgU2Vhc29ucyIsCSI2dGggc2Vhc29uIiwJIjcgU2Vhc29ucyIsCSI3dGggc2Vhc29uIiwJIjggU2Vhc29ucyIsCSI4dGggc2Vhc29uIiwJIjkgU2Vhc29ucyIsCSI5dGggc2Vhc29uIiwJIjEwIFNlYXNvbnMiLAkiMTB0aCBzZWFzb24iLAkiMTEgU2Vhc29ucyIsCSIxMXRoIHNlYXNvbiIsCSIxMiBTZWFzb25zIiwJIjEzIFNlYXNvbnMiLAkiMTQgU2Vhc29ucyIsCSIxNHRoIHNlYXNvbiIsCSIxNyBTZWFzb25zIiwJIjE4IFNlYXNvbnMiLAkiMTh0aCBzZWFzb24iKSwgDQpsYWJlbHMgPSBjKCJSb29raWUiLAkiMCBTZWFzb24iLAkiMSBTZWFzb24iLAkiMSBTZWFzb24iLAkiMiBTZWFzb25zIiwJIjIgU2Vhc29ucyIsCSIzIFNlYXNvbnMiLAkiMyBTZWFzb25zIiwJIjQgU2Vhc29ucyIsCSI0IFNlYXNvbnMiLAkiNSBTZWFzb25zIiwJIjUgU2Vhc29ucyIsCSI2IFNlYXNvbnMiLAkiNiBTZWFzb25zIiwJIjcgU2Vhc29ucyIsCSI3IFNlYXNvbnMiLAkiOCBTZWFzb25zIiwJIjggU2Vhc29ucyIsCSI5IFNlYXNvbnMiLAkiOSBTZWFzb25zIiwJIjEwIFNlYXNvbnMiLAkiMTAgU2Vhc29ucyIsCSIxMSBTZWFzb25zIiwJIjExIFNlYXNvbnMiLAkiMTIgU2Vhc29ucyIsCSIxMyBTZWFzb25zIiwJIjE0IFNlYXNvbnMiLAkiMTQgU2Vhc29ucyIsCSIxNyBTZWFzb25zIiwJIjE4IFNlYXNvbnMiLAkiMTggU2Vhc29ucyIpLCBvcmRlcmVkID0gVFJVRSkNCg0KYGBgDQoNCg0Kd2UgY2FuIGFsc28gY2hlY2sgdGhlIGNsYXNzIGFuZCBvdGhlciBjaGFyYWN0ZXJpc3RpY3Mgb2YgZGF0YSB1c2luZyBzdHIoKWZ1bmN0aW9uIHdoaWNoIGNvbXBhY3RseSBkaXNwbGF5cyB0aGUgc3RydWN0dXJlIG9mIHRoZSB3aG9sZSBkYXRhc2V0Lg0KYGBge3J9DQpzdHIoTkZMZGF0YSkNCmBgYA0KDQoNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSANCkFzIHBlciBIYWRsZXkgV2lja2hhbSBhbmQgR3JvbGVtdW5kICgyMDE2KSwgdGhlIHRocmVlIHRpZHkgZGF0YSBydWxlcyBhcmU6IChhKUVhY2ggdmFyaWFibGUgbXVzdCBoYXZlIGl0cyBvd24gY29sdW1uLihiKSBFYWNoIG9ic2VydmF0aW9uIG11c3QgaGF2ZSBpdHMgb3duIHJvdy4oYylFYWNoIHZhbHVlIG11c3QgaGF2ZSBpdHMgb3duIGNlbGwuT24gdGhlIGJhc2lzIG9mIHRoZXNlIHRpZHkgcnVsZXMsIHRoZSBkYXRhc2V0IGluIGdlbmVyYWwgY2FuIGJlIGNvbnNpZGVyZWQgYXMgdGlkeSBkYXRhLiBIb3dldmVyLCBpbiB0aGUgb3JpZ2luYWwgZGF0YXNldCwgaXQgaXMgZm91bmQgdGhhdCBib3RoIHRoZSBmaXJzdCBuYW1lIGFuZCBsYXN0IG5hbWUgb2YgcGxheWVycyBhcmUgZ2l2ZW4gaW4gb25lIGNvbHVtbiBzZXBhcmF0ZWQgYnkgYSBjb21hLiBBcyB3ZSBhcmUgbm90IHN1cmUgYWJvdXQgd2hpY2ggb25lIGlzIGZpcnN0IGFuZCBsYXN0IG5hbWUgb2YgdGhlIHBsYXllcnMsIHRoZSBuYW1lIGlzIG5vdCBzZXBhcmF0ZWQgaW4gb3JkZXIgdG8gYXZvaWQgY29uZnVzaW9uLiANCkluIHRoZSBiZWxvdyBjaHVuaywgdGhlIFBsYXllcklkIGlzIHNlcGFyYXRlZCBpbnRvIE5hbWVzIGFuZCBJRCB2YXJpYWJsZXMgdXNpbmcgdGhlICd0aWR5cicgcGFja2FnZSBzZXBhcmF0ZSgpIGZ1bmN0aW9uLiANCg0KYGBge3J9DQpORkxkYXRhMSA8LSBORkxkYXRhICU+JSBzZXBhcmF0ZShgUGxheWVyIElkYCwgaW50byA9IGMoIk5hbWUiLCAiSUQiKSwgc2VwID0gIi8iKQ0KaGVhZChORkxkYXRhMSkNCmBgYA0KDQoNCg0KIyMJVGlkeSAmIE1hbmlwdWxhdGUgRGF0YSBJSSANCg0KVG8gY2FsY3VsYXRlIHRoZSB0aW1lIGZyYW1lIG9mIGEgcGxheWVyJ3MgY2FyZWVyLCB0aGUgbXV0YXRlKCkgZnVuY3Rpb24gaXMgdXNlZCB0byBjcmVhdGUgYSBuZXcgY29sdW1uLCdjYXJlZXJfeWVhcicsIGJ5IHVzaW5nIHRoZSBkYXRhIG9mIHN0YXJ0X3llYXIgYW5kIGVuZF95ZWFyLiBJdCBzZWVtcyB0aGF0IGZldyBwbGF5ZXJzIGhhdmUgZW50ZXJlZCBhbmQgZXhpdGVkIGluIHRoZWlyIHBsYXlpbmcgY2FyZWVyIGluIHRoZSBzYW1lIHllYXIgaG93ZXZlciB0YWtpbmcgb25seSB0aGUgIGRpZmZlcmVuY2Ugb2YgdGhlaXIgc3RhcnQgeWVhciBhbmQgZW5kIHllYXIgd2lsbCBnaXZlIHplcm8geWVhciB3aGljaCBpcyBub3QgY29ycmVjdCBsb2dpY2FsbHkuIEluIG9yZGVyIHRvIHNvbHZlIHRoaXMgcHJhY3RpY2FsIGNvbnVuZHJ1bSwgb25lIGlzIGFkZGVkIGltcGxpY2l0bHkgc28gdGhhdCBib3RoIHRoZSBzdGFydCB5ZWFyIGFuZCBlbmQgeWVhciBhcmUgaW5jbHVkZWQgdG8gY29ycmVjdGx5IHJlcHJlc2VudCB0aGUgY2FyZWVyX3llYXJzLiANCmBgYHtyfQ0KbXV0YXRlKE5GTGRhdGExLA0KICAgICAgIGNhcmVlcl95ZWFycyA9IGVuZF95ZWFyIC0gc3RhcnRfeWVhciArIDEpDQoNCmBgYA0KDQoNCiMjCVNjYW4gSSANCg0KDQpJbiB0aGUgYmVsb3cgY2h1bmssIHRoZSBudW1lcmljYWwgdmFyaWFibGVzIG9mIHRoZSBkYXRhc2V0IGFyZSBmdXJ0aGVyIHN1YnNldCBpbnRvICdORkxfc3Vic2V0JyB3aXRoIHRocmVlIHZhcmlhYmxlcyBuYW1lbHktIElELCBIZWlnaHQoaW5jaGVzKSBhbmQgV2VpZ2h0KGxicykgdG8gc2NhbiB0aGUgbWlzc2luZyB2YWx1ZXMvaW5jb3N0aXN0ZW5jaWVzIGFuZCBvdXRsaWVycyBhbmQgZm9yIGRhdGEgdHJhbnNmb3JtYXRpb24uDQpgYGB7cn0NCk5GTF9zdWJzZXQ8LSBORkxkYXRhMVsgLGMoMiw3LDgpXQ0KTkZMX3N1YnNldA0KYGBgDQoNClRoZSBjb2xTdW1zKCkgZnVuY3Rpb24gaXMgYXBwbGllZCB0byBjb21wdXRlIHRoZSB0b3RhbCBtaXNzaW5nIHZhbHVlcyBpbiBlYWNoIGNvbHVtbiBvZiB0aGUgZGF0YXNldC5UaGVyZSBhcmUgNTYgYW5kIDE4IG1pc3NpbmcgdmFsdWVzKE5BJ3MpIGluIEhlaWdodCBhbmQgd2VpZ2h0IHJlc3BlY3RpdmVseS4NCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShORkxfc3Vic2V0KSkNCmBgYA0KDQoNClRoZSB3aGljaCgpIGZ1bmN0aW9uIGlzIHVzZWQgdG8gaWRlbnRpZnkgdGhlIGxvY2F0aW9uIG9mIE5BJ3MgaW4gdGhlIEhlaWdodCB2YXJpYWJsZS4NCmBgYHtyfQ0Kd2hpY2goaXMubmEoTkZMX3N1YnNldCRgSGVpZ2h0IChpbmNoZXMpYCkpDQpgYGANCg0KVGhlcmUgYXJlIHZhcmlvdXMgd2F5cyB0byBoYW5kbGUgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhc2V0IGxpa2UgZXhjbHVkaW5nIHRoZSBtaXNzaW5nIHZhbHVlcyBvciByZXBsYWNlIHRoZSBtaXNzaW5nIHZhbHVlcyB3aXRoIHNvbWUgY29uc3RhbnQgb3Igd2l0aCBtZWFuLG1lZGlhbiBvciBtb2RlLiBBbHRob3VnaCB0aGUgbnVtYmVyIG9mIE5BJ3MgaXMgdmVyeSBzbWFsbChhbG1vc3QgMiUpIGFuZCBjYW4gYmUgZXhjbHVkZWQuIEhvd2V2ZXIsIHRoZSBleGNsdXNpb24vZGVsZXRpb24gb2YgTkEncyBzb21ldGltZXMgbGVhZHMgdG8gYmlhc2VkIHN1YnNldCBvZiBkYXRhLiBIZW5jZSwgdGhlIG1pc3NpbmcgdmFsdWVzIGFyZSBpbXB1dGVkLCBoZXJlLCB3aXRoIHRoZSBtZWFuIG9mIHRoZSBIZWlnaHQgdmFyaWFibGUgdXNpbmcgYmFzZSBSIGZ1bmN0aW9ucy4gQWx0ZXJuYXRpdmVseSwgSG1pc2MgcGFja2FnZSBjYW4gYWxzbyBiZSB1c2VkIGZvciBpbXB1dGF0aW9uIG9mIG1lYW4sIG1lZGlhbiBvciBtb2RlLg0KYGBge3J9DQpORkxfc3Vic2V0JGBIZWlnaHQgKGluY2hlcylgW2lzLm5hKE5GTF9zdWJzZXQkYEhlaWdodCAoaW5jaGVzKWApXSA8LSBtZWFuKE5GTF9zdWJzZXQkYEhlaWdodCAoaW5jaGVzKWAsIG5hLnJtID0gVFJVRSkNCmBgYA0KDQpUaGUgSGVpZ2h0IHZhcmlhYmxlIGlzIGNoZWNrZWQgYWdhaW4gZm9yIE5BJ3MgdG8gY29uZmlybSB0aGUgaW1wdXRhdGlvbiBvZiBtZWFuIGludG8gdGhlIG1pc3NpbmcgdmFsdWVzLg0KYGBge3J9DQp3aGljaChpcy5uYShORkxfc3Vic2V0JGBIZWlnaHQgKGluY2hlcylgKSkNCmBgYA0KDQoNCkFmdGVyIHRoZSBoZWlnaHQgdmFyaWFibGUsIHRoZSBzYW1lIHByb2NlZHVyZSBpcyBmb2xsb3dlZCB0byBpbXB1dGUgdGhlIE5BJ3MgaW4gd2VpZ2h0IHZhcmlhYmxlIGFzIHdlbGwgYW5kIHRoZSBudW1iZXIgb2YgTkEncyBhcmUgY2hlY2tlZCBhbmQgY29uZmlybWVkIGFmdGVyIHRoZSBpbXB1dGF0aW9uIG9mIHRoZSBtZWFuIG9mIHRoZSBXZWlnaHQgdmFyaWFibGUuDQoNCmBgYHtyfQ0KTkZMX3N1YnNldCRgV2VpZ2h0IChsYnMpYFtpcy5uYShORkxfc3Vic2V0JGBXZWlnaHQgKGxicylgKV0gPC0gbWVhbihORkxfc3Vic2V0JGBXZWlnaHQgKGxicylgLCBuYS5ybSA9IFRSVUUpDQoNCndoaWNoKGlzLm5hKE5GTF9zdWJzZXQkYFdlaWdodCAobGJzKWApKQ0KDQpgYGANCg0KDQojIwlTY2FuIElJDQoNClRoZXJlIGFyZSB0d28gbWFpbiBtZXRob2RzIGZvciBkZXRlY3RpbmcgdGhlIHVuaXZhcmlhdGUgb3V0bGllcnMtIFR1a2V5J3MgbWV0aG9kIG9mIG91dGxpZXIgZGV0ZWN0aW9uIGFuZCBkaXN0YW5jZSBiYXNlZCBtZXRob2QuIEluIHRoZSBiZWxvdyBjaHVuaywgdGhlIGJveCBwbG90IGlzIGNyZWF0ZWQgdXNpbmcgdGhlIFR1a2V5J3MgbWV0aG9kIGFuZCBvdXRsaWVycyBhcmUgc2VlbiBseWluZyBvdXRzaWRlIHRoZSBsaW1pdHMgY2FsbGVkICdmZW5jZXMnLg0KYGBge3J9DQpORkxfc3Vic2V0JGBIZWlnaHQgKGluY2hlcylgICU+JSAgYm94cGxvdChtYWluPSJCb3ggUGxvdCBvZiBIZWlnaHQiLCB5bGFiID0gIkluY2hlcyIpDQpgYGANCg0KQXMgYm94IHBsb3QgaXMgYSB2aXN1YWwgcmVwcmVzZW50YXRpb24gb2Ygb3V0bGllcnMgYW5kIGluIG9yZGVyIHRvIHByb2NlZWQgd2l0aCB0aGUgaGFuZGxpbmcgb2YgdGhlIG91dGxpZXJzLCB0aGVpciBudW1iZXIgc2hvdWxkIGJlIGNvbmZpcm1lZC4gVGhlIGRpc3RhbmNlIGJhc2VkIG1ldGhvZHMobGlrZSBaLXNjb3JlIG1ldGhvZCBldGMuKSBoZWxwIGluIGNvbmZpcm1pbmcgdGhlIGV4YWN0IG51bWJlciBvZiBvdXRsaWVycy4gSGVuY2UsIGluIHRoZSBjaHVuayBiZWxvdywgdGhlIFotU2NvcmUgaXMgY2FsY3VsYXRlZCBmb3IgSGVpZ2h0IHZhcmlhYmxlIHVzaW5nIHNjb3JlKCkgZnVuY3Rpb24gZnJvbSAib3V0bGllciIgcGFja2FnZS4gVGhlIHN1bW1hcnkoKSBmdW5jdGlvbiBkaXNwbGF5cyB0aGUgbWluaW11bSgtMy40MzcwKSAmIG1heGltdW0oMi40OTMwKSBaLXNjb3JlIG9mIHRoZSBIZWlnaHQgdmFyaWFibGUuDQpgYGB7cn0NCnpzY29yZXNfSGVpZ2h0IDwtIE5GTF9zdWJzZXQkYEhlaWdodCAoaW5jaGVzKWAgJT4lICBzY29yZXModHlwZSA9ICJ6IikNCnpzY29yZXNfSGVpZ2h0ICU+JSBzdW1tYXJ5KCkNCmBgYA0KDQpBbiBvYnNlcnZhdGlvbiBpcyByZWdhcmRlZCBhcyBhbiBvdXRsaWVyIGlmIHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiBpdHMgei1zY29yZSBpcyBncmVhdGVyIHRoYW4gMy4gVGhlcmVmb3JlLCBpbiB0aGUgZm9sbG93aW5nIGNodW5rLCB0aGUgdG90YWwgbnVtYmVyIG9mIG91dGxpZXJzIGFjY29yZGluZyB0byB0aGUgWi1zY29yZSBpcyBjYWxjdWxhdGVkIHVzaW5nIGxlbmd0aCgpIGZ1bmN0aW9uIHdoaWNoIGNvbWVzIG91dCB0byBiZSAxNC4NCmBgYHtyfQ0KbGVuZ3RoICh3aGljaCggYWJzKHpzY29yZXNfSGVpZ2h0KSA+MyApKQ0KYGBgDQoNCg0KVGhlcmUgYXJlIHZhcmlvdXMgYXBwcm9hY2hlcyB0byBoYW5kbGUgdGhlIG91dGxpZXJzIGxpa2UtIGV4Y2x1ZGluZyBvciBkZWxldGluZywgaW1wdXRpbmcsIGNhcHBpbmcvd2luc29yaXNpbmcgb3IgdHJhbnNmb3JtaW5nIGFuZCBiaW5uaW5nLiBIb3dldmVyLGJlZm9yZSBkZWNpZGluZyB0aGUgYXBwcm9hY2ggdG8gaGFuZGxlIHRoZW0sIHRoZSBvdXRsaWVycyBzaG91bGQgYmUgYW5hbHlzZWQgYXMgdG8gd2hldGhlciB0aGV5IGFyZSBhIHJlc3VsdCBvZiBkYXRhIGVudHJ5L3Byb2Nlc3NpbmcgZXJyb3IgYW5kIGlmIHNvLCB0aGV5IGNhbiBiZSBkZWxldGVkIG9yIGltcHV0ZWQuIER1ZSB0byBsYWNrIG9mIGNlcnRhaW50eSBmb3IgdGhlIHJlYXNvbnMgb2Ygb3V0bGllcnMsIHRoZSBjYXBwaW5nKGEuay5hIHdpbnNvcmlzaW5nKSBhcHByb2FjaCBpcyB1c2VkLiBGb3IgY2FwcGluZywgdGhlIHVzZXItZGVmaW5lZCBmdW5jdGlvbiBtZW50aW9uZWQgYmVsb3coYXMgY2FwKWlzIGFwcGxpZWQgdG8gdGhlIGhlaWdodCB2YXJpYWJsZSBhbmQgYXNzaWduZWQgdG8gSGVpZ2h0X2NhcHBlZC4NCmBgYHtyfQ0KY2FwIDwtIGZ1bmN0aW9uKHgpew0KICAgIHF1YW50aWxlcyA8LSBxdWFudGlsZSggeCwgYyguMDUsIDAuMjUsIDAuNzUsIC45NSApICkNCiAgICB4WyB4IDwgcXVhbnRpbGVzWzJdIC0gMS41KklRUih4KSBdIDwtIHF1YW50aWxlc1sxXQ0KICAgIHhbIHggPiBxdWFudGlsZXNbM10gKyAxLjUqSVFSKHgpIF0gPC0gcXVhbnRpbGVzWzRdDQogICAgeA0KfQ0KDQpIZWlnaHRfY2FwcGVkIDwtIE5GTF9zdWJzZXQkYEhlaWdodCAoaW5jaGVzKWAgJT4lIGNhcCgpDQpgYGANCg0KDQpBIGJveHBsb3Qgb2YgSGVpZ2h0X2NhcHBlZCBpcyBjcmVhdGVkIGFnYWluIHRvIGNvbmZpcm0gdGhlcmUgYXJlIG5vIHJlbWFpbmluZyBvdXRsaWVycy4NCmBgYHtyfQ0KSGVpZ2h0X2NhcHBlZCAlPiUgIGJveHBsb3QobWFpbj0iQm94IFBsb3Qgb2YgSGVpZ2h0IiwgeWxhYiA9ICJJbmNoZXMiKQ0KYGBgDQoNCkFmdGVyIEhlaWdodCwgdGhlIFdlaWdodCBpcyBhbHNvIGNoZWNrZWQgZm9yIHRoZSBvdXRsaWVycyBhbmQgaXMgaGFuZGxlZCBpbiB0aGUgc2ltaWxhciBmYXNoaW9uLihpLmUgLSBjcmVhdGlvbiBvZiBib3hwbG90LCBjaGVja2luZyBleGFjdCBudW1iZXIgb2Ygb3V0bGllcnMgdmlhIFotc2NvcmUgbWV0aG9kIGFuZCB0aGVuIGhhbmRsaW5nIHRoZSBvdXRsaWVycyB2aWEgY2FwcGluZy4pIEluIHRoZSBlbmQsIHRoZSBib3hwbG90IGlzIGNyZWF0ZWQgYWdhaW4gdG8gY2hlY2sgZm9yIHRoZSByZW1haW5pbmcgb3V0bGllcnMuDQpgYGB7cn0NCk5GTF9zdWJzZXQkYFdlaWdodCAobGJzKWAgJT4lICBib3hwbG90KG1haW49IkJveCBQbG90IG9mIFdlaWdodCIsIHlsYWIgPSAiTGJzIikNCmBgYA0KDQoNCmBgYHtyfQ0KenNjb3Jlc19XZWlnaHQgPC0gTkZMX3N1YnNldCRgV2VpZ2h0IChsYnMpYCAlPiUgIHNjb3Jlcyh0eXBlID0gInoiKQ0KenNjb3Jlc19XZWlnaHQgJT4lIHN1bW1hcnkoKQ0KYGBgDQoNCmBgYHtyfQ0KbGVuZ3RoICh3aGljaCggYWJzKHpzY29yZXNfV2VpZ2h0KSA+MyApKQ0KYGBgDQoNCg0KYGBge3J9DQpXZWlnaHRfY2FwcGVkIDwtIE5GTF9zdWJzZXQkYFdlaWdodCAobGJzKWAgJT4lIGNhcCgpDQpgYGANCg0KYGBge3J9DQpXZWlnaHRfY2FwcGVkICU+JSAgYm94cGxvdChtYWluPSJCb3ggUGxvdCBvZiBXZWlnaHQiLCB5bGFiID0gIkxicyIpDQpgYGANCg0KDQojIwlUcmFuc2Zvcm0gDQoNCkl0IGlzIG5lY2Vzc2FyeSB0byBwZXJmb3JtIHNvbWUgZGF0YSB0cmFuc2Zvcm1hdGlvbnMgb24gdGhlIHRpZHkgZGF0YSBiZWZvcmUgaXQgY2FuIGJlIHVzZWQgZm9yIG1vZGVsaW5nLiBEYXRhIHRyYW5zZm9ybWF0aW9uIGNhbiBiZSB1c2VkIGluIGEgdmFyaWV0eSBvZiBzaXR1YXRpb25zIGxpa2U6ICgxKSBUbyBjaGFuZ2UgdGhlIHNjYWxlIG9mIGEgdmFyaWFibGUgb3Igc3RhbmRhcmRpc2UgdGhlIHZhbHVlcyBvZiBhIHZhcmlhYmxlIGZvciBiZXR0ZXIgdW5kZXJzdGFuZGluZy4NCigyKVRvIHRyYW5zZm9ybSBjb21wbGV4IG5vbi1saW5lYXIgcmVsYXRpb25zaGlwcyBpbnRvIGxpbmVhciByZWxhdGlvbnNoaXBzICgzKSBUbyByZWR1Y2Ugc2tld25lc3MgYW5kL29yIGhldGVyb2dlbmVpdHkgb2YgdmFyaWFuY2UgYW5kIG1ha2UgaXQgYSBub3JtYWwgZGlzdHJpYnV0aW9uLg0KDQpIZXJlLCB0aGUgZGlzdHJpYnV0aW9uIG9mIEhlaWdodCB2YXJpYWJsZSBpcyB2aXN1YWxpc2VkIHdpdGggdGhlIGhlbHAgb2YgYSBoaXN0b2dyYW0uIFRoZSByZXN1bHRpbmcgaGlzdG9ncmFtIGFwcGVhcnMgdG8gYmUgc2xpZ2h0bHkgbGVmdCBza2V3ZWQuDQpgYGB7cn0NCmhpc3QoSGVpZ2h0X2NhcHBlZCkNCmBgYA0KDQpBcyB0aGUgaGlzdG9ncmFtIG9mIGhlaWdodCBkaXN0cmlidXRpb24gaXMgc2xpZ2h0bHkgc2tld2VkIHRvIHRoZSBsZWZ0LCB0aGUgZGF0YSBpcyB0cmFuc2Zvcm1lZCBieSByYWlzaW5nIHRoZSBwb3dlciB0byA1IGFuZCBmdXJ0aGVyIGJ5IHRha2luZyB0aGUgZm91cnRoIHJvb3QuIEFmdGVyIHBlcmZvcm1pbmcgdGhlIG1hdGhlbWF0aWNhbCBvcGVyYXRpb25zLCBhIGhpc3RvZ3JhbSBpcyBhZ2FpbiBwbG90dGVkIHRvIGNoZWNrIHRoZSB0cmFuc2Zvcm1lZCBkaXN0cmlidXRpb24uIFRoZSB0cmFuc2Zvcm1hdGlvbiBub3QgYWx3YXlzIHJlc3VsdHMgaW4gYSBwZXJmZWN0IHN5bW1ldHJpY2FsIHNoYXBlLiBUaGUgZGlzdHJpYnV0aW9uIGdvdCBiZXR0ZXIgdGhhbiBiZWZvcmUsaGVuY2UsIHdlIGNhbiBwcm9jZWVkIHdpdGggdGhlIHJlc3VsdGluZyB0cmFuc2Zvcm1hdGlvbi4NCmBgYHtyfQ0KaGVpZ2h0X3RyYW5zZm9ybWVkPC0gKEhlaWdodF9jYXBwZWReNSleMS80DQpoaXN0KGhlaWdodF90cmFuc2Zvcm1lZCkNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCjxicj4NCjxicj4NCg==