Required packages

library(dplyr)
library(readr)
library(foreign)
#library(gdata)
library(rvest)
library(tidyr)
#library(deductive)
#library(validate)
library(Hmisc)
library(stringr)
library(outliers)
library(MVN)
library(infotheo)
library(MASS)
#library(caret)
#library(MLR)
library(ggplot2)
library(knitr)

Executive Summary

In this assignment, 2 datasets relating to Film Criticism are joined together for analysis. The first dataset focusses on the Bechdel test; a measure of a film’s inclusion and representation of female characters. This dataset also includes various details about the film, such as budget and year of publication. The dataset was obtained from an article on the representation of women in film from the website FiveThirtyEight. The second dataset is of IMDB’s user review scores. This includes all films on IMDB with at least 10 votes. The datasets are merged, and each variable examined, cleaned and reformatted. Variables are examined for missing and invalid values, and new variables are created.

Data

The first dataset being used for this assignment is from an article published on FiveThirtyEight in 2017 entitled “The Dollar-And-Cents Case Against Hollywood’s Exclusion of Women” written by Walt Hickey. The dataset was taken from FiveThirtyEight’s GitHub under Creative Commons Attribution 4.0 International License.

The Bechdel test was proposed by comic artist Alice Bechdel in a work in her comic “Dykes to Wacth Out For” in 1985 to assess gender representation in films. The criteria of the test are as follows. The movie must contain at least two female characters. The two female characters must engage in dialogue with each other. The subject of this dialogue must not be a man. The Bechdel dataset contains a film’s title, year of first release, unique ID, and several fields on the film’s budget and gross, including normalisations to 2013 US Dollars. The test itself encoded in 3 different ways as well, including a simple binary, and a more granulary test which describes at which ‘level’ a film may fail the test. This dataset contains results of the test for 1794 movies.

The second dataset considered is IMDB’s user review score table. This was extracted directly from IMDB through thier Data Portal, in tsv.gz format. This table contains the average user review score for 870172 films and television shows, as well as the film’s IMDB id, a unique identifying code assigned by imdb. The datasets join logically on the IMDB id. Also included is the number of votes a movie received to attain its score.

IMDB Ratings Dataset

imdb <- read.table("title.ratings.tsv.gz",header = TRUE) #ratings for everything on imdb
str(imdb)
'data.frame':   870172 obs. of  3 variables:
 $ tconst       : Factor w/ 870172 levels "tt0000001","tt0000002",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ averageRating: num  5.8 6.4 6.6 6.4 6.2 5.6 5.5 5.6 5.6 6.9 ...
 $ numVotes     : int  1416 167 1013 100 1712 87 571 1520 68 5073 ...
head(imdb)

Bechdel Test Data

bechdel <- read_csv("bechdel.csv")
Parsed with column specification:
cols(
  year = col_integer(),
  imdb = col_character(),
  title = col_character(),
  test = col_character(),
  clean_test = col_character(),
  binary = col_character(),
  budget = col_integer(),
  domgross = col_character(),
  intgross = col_character(),
  code = col_character(),
  `budget_2013$` = col_integer(),
  `domgross_2013$` = col_character(),
  `intgross_2013$` = col_character(),
  `period code` = col_integer(),
  `decade code` = col_integer()
)
str(bechdel)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   1794 obs. of  15 variables:
 $ year          : int  2013 2012 2013 2013 2013 2013 2013 2013 2013 2013 ...
 $ imdb          : chr  "tt1711425" "tt1343727" "tt2024544" "tt1272878" ...
 $ title         : chr  "21 &amp; Over" "Dredd 3D" "12 Years a Slave" "2 Guns" ...
 $ test          : chr  "notalk" "ok-disagree" "notalk-disagree" "notalk" ...
 $ clean_test    : chr  "notalk" "ok" "notalk" "notalk" ...
 $ binary        : chr  "FAIL" "PASS" "FAIL" "FAIL" ...
 $ budget        : int  13000000 45000000 20000000 61000000 40000000 225000000 92000000 12000000 13000000 130000000 ...
 $ domgross      : chr  "25682380" "13414714" "53107035" "75612460" ...
 $ intgross      : chr  "42195766" "40868994" "158607035" "132493015" ...
 $ code          : chr  "2013FAIL" "2012PASS" "2013FAIL" "2013FAIL" ...
 $ budget_2013$  : int  13000000 45658735 20000000 61000000 40000000 225000000 92000000 12000000 13000000 130000000 ...
 $ domgross_2013$: chr  "25682380" "13611086" "53107035" "75612460" ...
 $ intgross_2013$: chr  "42195766" "41467257" "158607035" "132493015" ...
 $ period code   : int  1 1 1 1 1 1 1 1 1 1 ...
 $ decade code   : int  1 1 1 1 1 1 1 1 1 1 ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 15
  .. ..$ year          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ imdb          : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ title         : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ test          : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ clean_test    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ binary        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ budget        : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ domgross      : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ intgross      : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ code          : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ budget_2013$  : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ domgross_2013$: list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ intgross_2013$: list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ period code   : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ decade code   : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"
head(bechdel)

Having Imported the data I checked the column formats. The read.table function used to open the tsv.gz file has given all values of the IMDB id as factors. Since every value of the variable is unique, a factor is inappropriate, and the value will be re-encoded as a character variable. The bechdel dataset has also made some questionable decisions in reading the data. Most values have been interperated as Character, aside from a few integers, which will be explored in the understand section.

Once the IMDB IDs are both encoded as character variables, the two datasets join naturally on this feature. I am performing an inner join, so that only rows contained in both datsets appear in the final data frame.

colnames(imdb) <- c("imdb_id","score","ratings")
imdb$imdb_id <- as.character(imdb$imdb_id)
df <- inner_join(bechdel, imdb, by = c("imdb" = "imdb_id"))
nrow(imdb)
[1] 870172
nrow(bechdel)
[1] 1794
nrow(df)
[1] 1791

It seems as though there are 3 films in the Bechdel dataset which are not found in the IMDB one. This is not a large amount, so we will be able to proceed. Out of interest, the below code shows us which films these are. Mismatch is a vector of logical values, which are true if the IMDB ID in the bechdel data frame is not found in the IMDB data frame. Then we filter out all of the rows in the Bechdel data set for which this value is false.

mismatch <- !(bechdel$imdb %in% df$imdb)
filter(bechdel, mismatch)

Understand

Now that we have joined the two datasets together, we will proceed to examine each feature. Str(df) will provide us an overview of each feature.

str(df)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   1791 obs. of  17 variables:
 $ year          : int  2013 2012 2013 2013 2013 2013 2013 2013 2013 2013 ...
 $ imdb          : chr  "tt1711425" "tt1343727" "tt2024544" "tt1272878" ...
 $ title         : chr  "21 &amp; Over" "Dredd 3D" "12 Years a Slave" "2 Guns" ...
 $ test          : chr  "notalk" "ok-disagree" "notalk-disagree" "notalk" ...
 $ clean_test    : chr  "notalk" "ok" "notalk" "notalk" ...
 $ binary        : chr  "FAIL" "PASS" "FAIL" "FAIL" ...
 $ budget        : int  13000000 45000000 20000000 61000000 40000000 225000000 92000000 12000000 13000000 130000000 ...
 $ domgross      : chr  "25682380" "13414714" "53107035" "75612460" ...
 $ intgross      : chr  "42195766" "40868994" "158607035" "132493015" ...
 $ code          : chr  "2013FAIL" "2012PASS" "2013FAIL" "2013FAIL" ...
 $ budget_2013$  : int  13000000 45658735 20000000 61000000 40000000 225000000 92000000 12000000 13000000 130000000 ...
 $ domgross_2013$: chr  "25682380" "13611086" "53107035" "75612460" ...
 $ intgross_2013$: chr  "42195766" "41467257" "158607035" "132493015" ...
 $ period code   : int  1 1 1 1 1 1 1 1 1 1 ...
 $ decade code   : int  1 1 1 1 1 1 1 1 1 1 ...
 $ score         : num  5.9 7.1 8.1 6.7 7.5 6.3 5.3 7.8 5.7 4.8 ...
 $ ratings       : int  67062 228358 550087 176462 75189 131490 183079 247112 31208 176837 ...

Based on the above output, we will make the below set of changes

Year - Leave as is. This could be changed to a date type, or we could even look for more information if the exact date of release becomes relevant to an investigation of the data.

Imdb - Renamed to imdb_id for clarity.

  colnames(df)[2] <- "imdb_id"

Title - Character makes sense, no data type change needed. Special characters have been escaped with ‘&’, so we will replace these with the actual characters.

sum(str_detect(df$title,"&"))
[1] 98
# 98 titles contain the '&' character
special_characters <- filter(df,str_detect(df$title,"&"))
head(special_characters$title)
[1] "21 &amp; Over"                      "Ender&#39;s Game"                   "Hansel &amp; Gretel: Witch Hunters" "Lee Daniels&#39; The Butler"       
[5] "Pain &amp; Gain"                    "The World&#39;s End"               

We can see a lot of ‘&amp;’ and ‘&#39;’ in these, which correspond to the ‘&’ and apostrophe characters respectively. I will replace these, and then determine whether any other special characters remain

  df$title <- str_replace(df$title, "&amp;","&")
  df$title <- str_replace(df$title, "&#39;","'")
  # since there are now legitimate ampersands, we will instead look for the pattern of a special character of the format '&XXX;'
  filter(df,str_detect(df$title,"&.+;"))

For the remaining titles, the following replacements are made.

  df$title <- str_replace(df$title, "&uuml;","ü")
  df$title <- str_replace(df$title, "&agrave","à")
  df$title <- str_replace(df$title, "&aring","å")
  df$title <- str_replace(df$title, "&auml","ä")

test - The result of the Bechdel test. This and Clean_test encode the same information

  df$test <- df$test %>% factor()
  df$clean_test <- df$clean_test %>% factor()
  levels(df$test)
 [1] "dubious"          "dubious-disagree" "men"              "men-disagree"     "notalk"           "notalk-disagree"  "nowomen"          "nowomen-disagree"
 [9] "ok"               "ok-disagree"     
  levels(df$clean_test)
[1] "dubious" "men"     "notalk"  "nowomen" "ok"     

The cleantest variable is easier to interpret, so we will drop the test variable and continue.

df$test <- NULL

It makes sense to give clean_test an ordering, as a movie in which there are female characters who do not talk could be considered more inclusive than one which does not contain any female characters at all.

  df$clean_test <- df$clean_test %>% factor(levels = c("nowomen","notalk","men","dubious","ok"),ordered = TRUE)

binary - The Bechdel test as a simple pass or fail. We will make this a logical value

  df$binary <- ifelse(df$binary=="PASS",TRUE,FALSE)

Finiancial Information - Budget has been read as an integer, and the domestic and international grosses have been read as character data type. These will all be converted into numerical data types.

  df$budget <- as.numeric(df$budget)
  df$`budget_2013$` <- as.numeric(df$`budget_2013$`)
  df$domgross <- as.numeric(df$domgross)
NAs introduced by coercion
  df$intgross <- as.numeric(df$intgross)
NAs introduced by coercion
  df$`domgross_2013$` <- as.numeric(df$`domgross_2013$`)
NAs introduced by coercion
  df$`intgross_2013$` <- as.numeric(df$`intgross_2013$`)
NAs introduced by coercion

This has introduced some NA values. This code will find the number of rows for which any of these values is NA.

  sum(is.na(df$domgross) 
      | is.na(df$intgross) 
      | is.na(df$`domgross_2013$`)
      | is.na(df$`intgross_2013$`)
      | is.na(df$budget)
      | is.na(df$`budget_2013$`))
[1] 18

Since this is a very small proportion of the dataset, we should be safe to simply drop these rows if needed. However, I will leave these as is for now, as the movies’ gross may not be the focus of our investigation.

code - used to subset the data for the article. This will be re-encoded as a factor, non-ordered.

  df$code <- as.factor(df$code)

Decade code - The initial values of these codes are not clear in their meanings. We will re-encode them into something more legible.

    str(df$`decade code` %>% as.factor)
 Factor w/ 3 levels "1","2","3": 1 1 1 1 1 1 1 1 1 1 ...
  head(filter(df,df$`decade code`==3)) #90s
  head(filter(df,df$`decade code`==2)) #2000s
  head(filter(df,df$`decade code`==1)) #2010's

The decade codes correspond to the 90’s, 2000’s and 2010’s in the order 3,2,1. We will recast these as an ordered factor, in chronological order.

  df$`decade code` <- factor(df$`decade code`, levels = c(3,2,1),labels = c("1990's","2000's","2010's"),ordered = TRUE)

Period Code - The meaning of this variable is not clear, but appears to have been used to subdivide the dataset for the FiveThirtyEight article. We will drop this variable for now.

df$`period code` <- NULL

Tidy & Manipulate Data I

Tidy data principles state that each row should correspond to a single observation, each column should correspond to a single variable and each type of observational unit should form a table. In this case our data conforms to these principles. * Each row represents a single film * Each column represents a single piece of information about a film * The table encodes a variety of information on a per-film basis. The Code variable could be interpreted as untidy, as it is a combination of the Year and Binary variables used to categorise films together. Since we already have this information, we can drop the variable rather than attempt to tidy it.

df$code <- NULL

Tidy & Manipulate Data II

I have chosen to firstly create a set of variables which encode the profitability of a film. The first of these will be the profit, which will be the total gross (domestic plus international) minus the film’s budget.

df$profit <- df$domgross + df$intgross - df$budget

To better compare films from different years, we should create some time insensitive measures of profit. Profit_2013 will make the same calculation based on the 2013 $US data values.

df$profit_2013 <- df$`domgross_2013$` + df$`intgross_2013$` - df$`budget_2013$`

Finally, to compare the profits of different films, it may be more instructive to assess them based on the ratio of gross to budget instead.

df$profit_ratio <- (df$domgross + df$intgross)/df$budget

Scan I

The text of the title column has already been cleaned up in an earlier section. First we will check for NA values.

colSums(is.na(df))
          year        imdb_id          title     clean_test         binary         budget       domgross       intgross   budget_2013$ domgross_2013$ 
             0              0              0              0              0              0             17             11              0             18 
intgross_2013$    decade code          score        ratings         profit    profit_2013   profit_ratio 
            11            179              0              0             17             18             17 

It was already established that there were a few NA values in the financial information, these can safely be worked around. The decade code is also missing a number of values.

head(filter(df, is.na(df$`decade code`)))

It seems like this is because there are some films from decades besides those encoded in the decade code.

filter(df, is.na(df$`decade code`)) %>% distinct(year)

There are films from the 1970’s and 1980’s in the dataset. Rather than redefining the factor levels to include these, we will make a more robust version of the decade value, in case we use this code again with a dataset that includes new decades in the future.

#Delete decade code
df$`decade code` <- NULL
# df$year %% 10 is the year mod 10, which will be the one's digit
df$decade <- df$year - (df$year %% 10)
distinct(df,df$decade)
#now reencode as a factor.
df$decade <- df$decade %>% factor(ordered = TRUE)
levels(df$decade)
[1] "1970" "1980" "1990" "2000" "2010"

Scan II

I will step through the outlier investigation with the budget variable first. Let’s take a look at the boxplot of the budget.

df$budget %>% boxplot(main = "Boxplot of Budget")

There are quite a lot of outliers towards the higher end of the dataset. Let’s now look at the z-scores

z.scores <- df$budget %>%  scores(type = "z")
z.scores %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.9299 -0.6806 -0.3481  0.0000  0.3169  7.9025 

We can consider an outlier to be a value with a z-score greater than 3.

which(z.scores > 3)
 [1]    6   37   38   44   49   50   64   75   78   84  100  106  122  128  149  157  159  164  201  217  257  300  311  376  382  394  425  426  441  455  485
[32]  521  550  617  633  705  718  781  796  821  865  983 1443
length(which(z.scores > 3))
[1] 43

There are 43 such outliers. Let us remake the boxplot excluding these 43 films.

df$budget[-which(z.scores > 3)] %>% boxplot(main = "Budget, outliers removed")

df[which(z.scores>3),]

Now that we have eliminated the outliers from the data, we appear to have created more outliers in the reduced set, but the boxplot appears much more reasonable. Looking at the films included among the outliers, there are quite a few very significant films included, such as Titanic and Avatar. Removing the outliers seems like a poor choice. I have decided that the best approach is to leave the outliers in, and in the following section perform a binning transformation to group films into different budget groupings.

Now, we will quickly run through the same outlier investigation on the other financial variables.

par(mfrow = c(2,2))
df$domgross %>% boxplot(main = "Domestic Gross")
df$intgross %>% boxplot(main = "International Gross")
df$`domgross_2013$` %>% boxplot(main = "Domestic Gross ($US 2013)")
df$`intgross_2013$` %>% boxplot(main = "International Gross ($US 2013)")

To work with the z-scores, we will need to exclude the NA values found in these variables.

df.na_omit <- df %>% na.omit()
z.scores.dg <- df.na_omit$domgross %>% scores(type = "z")
z.scores.ig <- df.na_omit$intgross %>% scores(type = "z")
z.scores.dg13 <- df.na_omit$`domgross_2013$` %>%  scores(type = "z")
z.scores.ig13 <- df.na_omit$`intgross_2013$` %>%  scores(type = "z")
z.scores.dg %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.8599 -0.6571 -0.3364  0.0000  0.3009  8.5968 
z.scores.ig %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.7162 -0.5912 -0.3521  0.0000  0.1873 12.4949 
z.scores.dg13 %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.7551 -0.5927 -0.3111  0.0000  0.2100 13.2996 
z.scores.ig13 %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.6991 -0.5805 -0.3586  0.0000  0.1526 10.4677 
length(which(z.scores.dg > 3))
[1] 37
length(which(z.scores.ig > 3))
[1] 49
length(which(z.scores.dg13 > 3))
[1] 32
length(which(z.scores.ig13 > 3))
[1] 40
par(mfrow=c(1,2))
df$domgross %>% boxplot(main = "Domestic Gross")
df.na_omit$domgross[-which(z.scores.dg > 3)] %>% boxplot(main = "Outliers eliminated")

df$intgross %>% boxplot(main = "International Gross")
df.na_omit$intgross[-which(z.scores.ig > 3)] %>% boxplot(main = "Outliers eliminated")

df$`domgross_2013$` %>% boxplot(main = "Domestic Gross ($US 2013)")
df.na_omit$`domgross_2013$`[-which(z.scores.dg13 > 3)] %>% boxplot(main = "Outliers eliminated")

df$`intgross_2013$` %>% boxplot(main = "International Gross ($US 2013)")
df.na_omit$`intgross_2013$`[-which(z.scores.ig13 > 3)] %>% boxplot(main = "Outliers eliminated")

Aside from the financial information of the films, there is also the user ratings scores to investigate.

df$score %>% boxplot(main = "Viewer Review Scores")

hist(df$score, main = "Histogram of Viewer Scores")

df$score %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   6.200   6.800   6.758   7.400   9.300 

The user review score for these movies is quite positively skewed, but there is not any issue with outliers, or invalid values (such as those outside of the 1-10 range). In the next section we will normalise the review scores.

Transform

We will apply a transformation to normalise the movie scores about a mean of 0.

scores <- scale(df$score)
hist(scores)

This normalised score will be added to the data frame as score_norm

df$score_norm <- scores

We will also perform a binning transformation to the films based on their budgets. This will be an equal frequency binning, to split the films into 10 even categories. For comparison between films from different years, we will use the Budget 2013 USD value.

df$budget_binned <- discretize(df$`budget_2013$`, disc = "equalfreq", nbins = 10)

Now, we have arrived at our final data set, ready for analysis.



LS0tDQp0aXRsZTogIk1BVEgyMzQ5IFNlbWVzdGVyIDIsIDIwMTgiDQphdXRob3I6ICJBbGlzdGFpciBXaWxrcyAtIHMzNjQ5MjUyIg0Kc3VidGl0bGU6IEFzc2lnbm1lbnQgMw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQojIyBSZXF1aXJlZCBwYWNrYWdlcw0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShmb3JlaWduKQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KEhtaXNjKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShvdXRsaWVycykNCmxpYnJhcnkoTVZOKQ0KbGlicmFyeShpbmZvdGhlbykNCmxpYnJhcnkoTUFTUykNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoa25pdHIpDQoNCmBgYA0KDQojIyBFeGVjdXRpdmUgU3VtbWFyeSANCkluIHRoaXMgYXNzaWdubWVudCwgMiBkYXRhc2V0cyByZWxhdGluZyB0byBGaWxtIENyaXRpY2lzbSBhcmUgam9pbmVkIHRvZ2V0aGVyIGZvciBhbmFseXNpcy4gVGhlIGZpcnN0IGRhdGFzZXQgZm9jdXNzZXMgb24gdGhlIEJlY2hkZWwgdGVzdDsgYSBtZWFzdXJlIG9mIGEgZmlsbSdzIGluY2x1c2lvbiBhbmQgcmVwcmVzZW50YXRpb24gb2YgZmVtYWxlIGNoYXJhY3RlcnMuIFRoaXMgZGF0YXNldCBhbHNvIGluY2x1ZGVzIHZhcmlvdXMgZGV0YWlscyBhYm91dCB0aGUgZmlsbSwgc3VjaCBhcyBidWRnZXQgYW5kIHllYXIgb2YgcHVibGljYXRpb24uIFRoZSBkYXRhc2V0IHdhcyBvYnRhaW5lZCBmcm9tIGFuIGFydGljbGUgb24gdGhlIHJlcHJlc2VudGF0aW9uIG9mIHdvbWVuIGluIGZpbG0gZnJvbSB0aGUgd2Vic2l0ZSBbRml2ZVRoaXJ0eUVpZ2h0XShodHRwczovL2ZpdmV0aGlydHllaWdodC5jb20vKS4gVGhlIHNlY29uZCBkYXRhc2V0IGlzIG9mIElNREIncyB1c2VyIHJldmlldyBzY29yZXMuIFRoaXMgaW5jbHVkZXMgYWxsIGZpbG1zIG9uIElNREIgd2l0aCBhdCBsZWFzdCAxMCB2b3Rlcy4NClRoZSBkYXRhc2V0cyBhcmUgbWVyZ2VkLCBhbmQgZWFjaCB2YXJpYWJsZSBleGFtaW5lZCwgY2xlYW5lZCBhbmQgcmVmb3JtYXR0ZWQuIFZhcmlhYmxlcyBhcmUgZXhhbWluZWQgZm9yIG1pc3NpbmcgYW5kIGludmFsaWQgdmFsdWVzLCBhbmQgbmV3IHZhcmlhYmxlcyBhcmUgY3JlYXRlZC4NCg0KIyMgRGF0YSANCg0KVGhlIGZpcnN0IGRhdGFzZXQgYmVpbmcgdXNlZCBmb3IgdGhpcyBhc3NpZ25tZW50IGlzIGZyb20gYW4gYXJ0aWNsZSBwdWJsaXNoZWQgb24gRml2ZVRoaXJ0eUVpZ2h0IGluIDIwMTcgZW50aXRsZWQgIlRoZSBEb2xsYXItQW5kLUNlbnRzIENhc2UgQWdhaW5zdCBIb2xseXdvb2QncyBFeGNsdXNpb24gb2YgV29tZW4iIHdyaXR0ZW4gYnkgV2FsdCBIaWNrZXkuIFRoZSBkYXRhc2V0IHdhcyB0YWtlbiBmcm9tIEZpdmVUaGlydHlFaWdodCdzIFtHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9maXZldGhpcnR5ZWlnaHQvZGF0YS90cmVlL21hc3Rlci9iZWNoZGVsKSB1bmRlciAgQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbiA0LjAgSW50ZXJuYXRpb25hbCBMaWNlbnNlLg0KDQpUaGUgQmVjaGRlbCB0ZXN0IHdhcyBwcm9wb3NlZCBieSBjb21pYyBhcnRpc3QgQWxpY2UgQmVjaGRlbCBpbiBhIHdvcmsgaW4gaGVyIGNvbWljICJEeWtlcyB0byBXYWN0aCBPdXQgRm9yIiBpbiAxOTg1IHRvIGFzc2VzcyBnZW5kZXIgcmVwcmVzZW50YXRpb24gaW4gZmlsbXMuIFRoZSBjcml0ZXJpYSBvZiB0aGUgdGVzdCBhcmUgYXMgZm9sbG93cy4gVGhlIG1vdmllIG11c3QgY29udGFpbiBhdCBsZWFzdCB0d28gZmVtYWxlIGNoYXJhY3RlcnMuIFRoZSB0d28gZmVtYWxlIGNoYXJhY3RlcnMgbXVzdCBlbmdhZ2UgaW4gZGlhbG9ndWUgd2l0aCBlYWNoIG90aGVyLiBUaGUgc3ViamVjdCBvZiB0aGlzIGRpYWxvZ3VlIG11c3Qgbm90IGJlIGEgbWFuLiBUaGUgQmVjaGRlbCBkYXRhc2V0IGNvbnRhaW5zIGEgZmlsbSdzIHRpdGxlLCB5ZWFyIG9mIGZpcnN0IHJlbGVhc2UsIHVuaXF1ZSBJRCwgYW5kIHNldmVyYWwgZmllbGRzIG9uIHRoZSBmaWxtJ3MgYnVkZ2V0IGFuZCBncm9zcywgaW5jbHVkaW5nIG5vcm1hbGlzYXRpb25zIHRvIDIwMTMgVVMgRG9sbGFycy4gVGhlIHRlc3QgaXRzZWxmIGVuY29kZWQgaW4gMyBkaWZmZXJlbnQgd2F5cyBhcyB3ZWxsLCBpbmNsdWRpbmcgYSBzaW1wbGUgYmluYXJ5LCBhbmQgYSBtb3JlIGdyYW51bGFyeSB0ZXN0IHdoaWNoIGRlc2NyaWJlcyBhdCB3aGljaCAnbGV2ZWwnIGEgZmlsbSBtYXkgZmFpbCB0aGUgdGVzdC4gVGhpcyBkYXRhc2V0IGNvbnRhaW5zIHJlc3VsdHMgb2YgdGhlIHRlc3QgZm9yIDE3OTQgbW92aWVzLg0KDQpUaGUgc2Vjb25kIGRhdGFzZXQgY29uc2lkZXJlZCBpcyBJTURCJ3MgdXNlciByZXZpZXcgc2NvcmUgdGFibGUuIFRoaXMgd2FzIGV4dHJhY3RlZCBkaXJlY3RseSBmcm9tIElNREIgdGhyb3VnaCB0aGllciBbRGF0YSBQb3J0YWxdKHh4eCksIGluIHRzdi5neiBmb3JtYXQuIFRoaXMgdGFibGUgY29udGFpbnMgdGhlIGF2ZXJhZ2UgdXNlciByZXZpZXcgc2NvcmUgZm9yIDg3MDE3MiBmaWxtcyBhbmQgdGVsZXZpc2lvbiBzaG93cywgYXMgd2VsbCBhcyB0aGUgZmlsbSdzIElNREIgaWQsIGEgdW5pcXVlIGlkZW50aWZ5aW5nIGNvZGUgYXNzaWduZWQgYnkgaW1kYi4gVGhlIGRhdGFzZXRzIGpvaW4gbG9naWNhbGx5IG9uIHRoZSBJTURCIGlkLiBBbHNvIGluY2x1ZGVkIGlzIHRoZSBudW1iZXIgb2Ygdm90ZXMgYSBtb3ZpZSByZWNlaXZlZCB0byBhdHRhaW4gaXRzIHNjb3JlLg0KDQojIyBJTURCIFJhdGluZ3MgRGF0YXNldA0KYGBge3J9DQppbWRiIDwtIHJlYWQudGFibGUoInRpdGxlLnJhdGluZ3MudHN2Lmd6IixoZWFkZXIgPSBUUlVFKSAjcmF0aW5ncyBmb3IgZXZlcnl0aGluZyBvbiBpbWRiDQpzdHIoaW1kYikNCmhlYWQoaW1kYikNCmBgYA0KDQojIyBCZWNoZGVsIFRlc3QgRGF0YQ0KYGBge3J9DQpiZWNoZGVsIDwtIHJlYWRfY3N2KCJiZWNoZGVsLmNzdiIpDQpzdHIoYmVjaGRlbCkNCmhlYWQoYmVjaGRlbCkNCmBgYA0KDQpIYXZpbmcgSW1wb3J0ZWQgdGhlIGRhdGEgSSBjaGVja2VkIHRoZSBjb2x1bW4gZm9ybWF0cy4gVGhlIHJlYWQudGFibGUgZnVuY3Rpb24gdXNlZCB0byBvcGVuIHRoZSB0c3YuZ3ogZmlsZSBoYXMgZ2l2ZW4gYWxsIHZhbHVlcyBvZiB0aGUgSU1EQiBpZCBhcyBmYWN0b3JzLiBTaW5jZSBldmVyeSB2YWx1ZSBvZiB0aGUgdmFyaWFibGUgaXMgdW5pcXVlLCBhIGZhY3RvciBpcyBpbmFwcHJvcHJpYXRlLCBhbmQgdGhlIHZhbHVlIHdpbGwgYmUgcmUtZW5jb2RlZCBhcyBhIGNoYXJhY3RlciB2YXJpYWJsZS4gDQpUaGUgYmVjaGRlbCBkYXRhc2V0IGhhcyBhbHNvIG1hZGUgc29tZSBxdWVzdGlvbmFibGUgZGVjaXNpb25zIGluIHJlYWRpbmcgdGhlIGRhdGEuIE1vc3QgdmFsdWVzIGhhdmUgYmVlbiBpbnRlcnBlcmF0ZWQgYXMgQ2hhcmFjdGVyLCBhc2lkZSBmcm9tIGEgZmV3IGludGVnZXJzLCB3aGljaCB3aWxsIGJlIGV4cGxvcmVkIGluIHRoZSAqdW5kZXJzdGFuZCogc2VjdGlvbi4NCg0KT25jZSB0aGUgSU1EQiBJRHMgYXJlIGJvdGggZW5jb2RlZCBhcyBjaGFyYWN0ZXIgdmFyaWFibGVzLCB0aGUgdHdvIGRhdGFzZXRzIGpvaW4gbmF0dXJhbGx5IG9uIHRoaXMgZmVhdHVyZS4gSSBhbSBwZXJmb3JtaW5nIGFuIGlubmVyIGpvaW4sIHNvIHRoYXQgb25seSByb3dzIGNvbnRhaW5lZCBpbiBib3RoIGRhdHNldHMgYXBwZWFyIGluIHRoZSBmaW5hbCBkYXRhIGZyYW1lLg0KYGBge3J9DQpjb2xuYW1lcyhpbWRiKSA8LSBjKCJpbWRiX2lkIiwic2NvcmUiLCJyYXRpbmdzIikNCmltZGIkaW1kYl9pZCA8LSBhcy5jaGFyYWN0ZXIoaW1kYiRpbWRiX2lkKQ0KDQpkZiA8LSBpbm5lcl9qb2luKGJlY2hkZWwsIGltZGIsIGJ5ID0gYygiaW1kYiIgPSAiaW1kYl9pZCIpKQ0KbnJvdyhpbWRiKQ0KbnJvdyhiZWNoZGVsKQ0KbnJvdyhkZikNCmBgYA0KSXQgc2VlbXMgYXMgdGhvdWdoIHRoZXJlIGFyZSAzIGZpbG1zIGluIHRoZSBCZWNoZGVsIGRhdGFzZXQgd2hpY2ggYXJlIG5vdCBmb3VuZCBpbiB0aGUgSU1EQiBvbmUuIA0KVGhpcyBpcyBub3QgYSBsYXJnZSBhbW91bnQsIHNvIHdlIHdpbGwgYmUgYWJsZSB0byBwcm9jZWVkLiBPdXQgb2YgaW50ZXJlc3QsIHRoZSBiZWxvdyBjb2RlIHNob3dzIHVzIHdoaWNoIGZpbG1zIHRoZXNlIGFyZS4NCk1pc21hdGNoIGlzIGEgdmVjdG9yIG9mIGxvZ2ljYWwgdmFsdWVzLCB3aGljaCBhcmUgdHJ1ZSBpZiB0aGUgSU1EQiBJRCBpbiB0aGUgYmVjaGRlbCBkYXRhIGZyYW1lIGlzIG5vdCBmb3VuZCBpbiB0aGUgSU1EQiBkYXRhIGZyYW1lLiBUaGVuIHdlIGZpbHRlciBvdXQgYWxsIG9mIHRoZSByb3dzIGluIHRoZSBCZWNoZGVsIGRhdGEgc2V0IGZvciB3aGljaCB0aGlzIHZhbHVlIGlzIGZhbHNlLg0KYGBge3J9DQptaXNtYXRjaCA8LSAhKGJlY2hkZWwkaW1kYiAlaW4lIGRmJGltZGIpDQpmaWx0ZXIoYmVjaGRlbCwgbWlzbWF0Y2gpDQpgYGANCg0KDQojIyBVbmRlcnN0YW5kIA0KDQpOb3cgdGhhdCB3ZSBoYXZlIGpvaW5lZCB0aGUgdHdvIGRhdGFzZXRzIHRvZ2V0aGVyLCB3ZSB3aWxsIHByb2NlZWQgdG8gZXhhbWluZSBlYWNoIGZlYXR1cmUuIFN0cihkZikgd2lsbCBwcm92aWRlIHVzIGFuIG92ZXJ2aWV3IG9mIGVhY2ggZmVhdHVyZS4NCmBgYHtyfQ0Kc3RyKGRmKQ0KYGBgDQpCYXNlZCBvbiB0aGUgYWJvdmUgb3V0cHV0LCB3ZSB3aWxsIG1ha2UgdGhlIGJlbG93IHNldCBvZiBjaGFuZ2VzDQoNCioqWWVhcioqICAgIC0gTGVhdmUgYXMgaXMuIFRoaXMgY291bGQgYmUgY2hhbmdlZCB0byBhIGRhdGUgdHlwZSwgb3Igd2UgY291bGQgZXZlbiBsb29rIGZvciBtb3JlIGluZm9ybWF0aW9uIGlmIHRoZSBleGFjdCBkYXRlIG9mIHJlbGVhc2UgYmVjb21lcyByZWxldmFudCB0byBhbiBpbnZlc3RpZ2F0aW9uIG9mIHRoZSBkYXRhLg0KDQoqKkltZGIqKiAgICAtIFJlbmFtZWQgdG8gaW1kYl9pZCBmb3IgY2xhcml0eS4NCmBgYHtyfQ0KICBjb2xuYW1lcyhkZilbMl0gPC0gImltZGJfaWQiDQpgYGANCioqVGl0bGUqKiAgIC0gQ2hhcmFjdGVyIG1ha2VzIHNlbnNlLCBubyBkYXRhIHR5cGUgY2hhbmdlIG5lZWRlZC4gU3BlY2lhbCBjaGFyYWN0ZXJzIGhhdmUgYmVlbiBlc2NhcGVkIHdpdGggJyYnLCBzbyB3ZSB3aWxsIHJlcGxhY2UgdGhlc2Ugd2l0aCB0aGUgYWN0dWFsIGNoYXJhY3RlcnMuDQpgYGB7cn0NCnN1bShzdHJfZGV0ZWN0KGRmJHRpdGxlLCImIikpDQojIDk4IHRpdGxlcyBjb250YWluIHRoZSAnJicgY2hhcmFjdGVyDQpzcGVjaWFsX2NoYXJhY3RlcnMgPC0gZmlsdGVyKGRmLHN0cl9kZXRlY3QoZGYkdGl0bGUsIiYiKSkNCmhlYWQoc3BlY2lhbF9jaGFyYWN0ZXJzJHRpdGxlKQ0KYGBgDQpXZSBjYW4gc2VlIGEgbG90IG9mICdcJmFtcDsnIGFuZCAnXCYjMzk7JyBpbiB0aGVzZSwgd2hpY2ggY29ycmVzcG9uZCB0byB0aGUgJyYnIGFuZCBhcG9zdHJvcGhlIGNoYXJhY3RlcnMgcmVzcGVjdGl2ZWx5LiBJIHdpbGwgcmVwbGFjZSB0aGVzZSwgYW5kIHRoZW4gZGV0ZXJtaW5lIHdoZXRoZXIgYW55IG90aGVyIHNwZWNpYWwgY2hhcmFjdGVycyByZW1haW4NCg0KYGBge3J9DQogIGRmJHRpdGxlIDwtIHN0cl9yZXBsYWNlKGRmJHRpdGxlLCAiJmFtcDsiLCImIikNCiAgZGYkdGl0bGUgPC0gc3RyX3JlcGxhY2UoZGYkdGl0bGUsICImIzM5OyIsIiciKQ0KICAjIHNpbmNlIHRoZXJlIGFyZSBub3cgbGVnaXRpbWF0ZSBhbXBlcnNhbmRzLCB3ZSB3aWxsIGluc3RlYWQgbG9vayBmb3IgdGhlIHBhdHRlcm4gb2YgYSBzcGVjaWFsIGNoYXJhY3RlciBvZiB0aGUgZm9ybWF0ICcmWFhYOycNCiAgZmlsdGVyKGRmLHN0cl9kZXRlY3QoZGYkdGl0bGUsIiYuKzsiKSkNCmBgYA0KDQpGb3IgdGhlIHJlbWFpbmluZyB0aXRsZXMsIHRoZSBmb2xsb3dpbmcgcmVwbGFjZW1lbnRzIGFyZSBtYWRlLg0KYGBge3J9DQogIGRmJHRpdGxlIDwtIHN0cl9yZXBsYWNlKGRmJHRpdGxlLCAiJnV1bWw7Iiwi/CIpDQogIGRmJHRpdGxlIDwtIHN0cl9yZXBsYWNlKGRmJHRpdGxlLCAiJmFncmF2ZSIsIuAiKQ0KICBkZiR0aXRsZSA8LSBzdHJfcmVwbGFjZShkZiR0aXRsZSwgIiZhcmluZyIsIuUiKQ0KICBkZiR0aXRsZSA8LSBzdHJfcmVwbGFjZShkZiR0aXRsZSwgIiZhdW1sIiwi5CIpDQpgYGANCg0KKip0ZXN0KiogICAgLSBUaGUgcmVzdWx0IG9mIHRoZSBCZWNoZGVsIHRlc3QuIFRoaXMgYW5kICoqQ2xlYW5fdGVzdCoqIGVuY29kZSB0aGUgc2FtZSBpbmZvcm1hdGlvbg0KYGBge3J9DQogIGRmJHRlc3QgPC0gZGYkdGVzdCAlPiUgZmFjdG9yKCkNCiAgZGYkY2xlYW5fdGVzdCA8LSBkZiRjbGVhbl90ZXN0ICU+JSBmYWN0b3IoKQ0KICBsZXZlbHMoZGYkdGVzdCkNCiAgbGV2ZWxzKGRmJGNsZWFuX3Rlc3QpDQpgYGANClRoZSBjbGVhbnRlc3QgdmFyaWFibGUgaXMgZWFzaWVyIHRvIGludGVycHJldCwgc28gd2Ugd2lsbCBkcm9wIHRoZSB0ZXN0IHZhcmlhYmxlIGFuZCBjb250aW51ZS4NCg0KYGBge3J9DQpkZiR0ZXN0IDwtIE5VTEwNCmBgYA0KSXQgbWFrZXMgc2Vuc2UgdG8gZ2l2ZSBjbGVhbl90ZXN0IGFuIG9yZGVyaW5nLCBhcyBhIG1vdmllIGluIHdoaWNoIHRoZXJlIGFyZSBmZW1hbGUgY2hhcmFjdGVycyB3aG8gZG8gbm90IHRhbGsgY291bGQgYmUgY29uc2lkZXJlZCBtb3JlIGluY2x1c2l2ZSB0aGFuIG9uZSB3aGljaCBkb2VzIG5vdCBjb250YWluIGFueSBmZW1hbGUgY2hhcmFjdGVycyBhdCBhbGwuDQpgYGB7cn0NCiAgZGYkY2xlYW5fdGVzdCA8LSBkZiRjbGVhbl90ZXN0ICU+JSBmYWN0b3IobGV2ZWxzID0gYygibm93b21lbiIsIm5vdGFsayIsIm1lbiIsImR1YmlvdXMiLCJvayIpLG9yZGVyZWQgPSBUUlVFKQ0KYGBgDQoNCioqYmluYXJ5KiogIC0gVGhlIEJlY2hkZWwgdGVzdCBhcyBhIHNpbXBsZSBwYXNzIG9yIGZhaWwuIFdlIHdpbGwgbWFrZSB0aGlzIGEgbG9naWNhbCB2YWx1ZQ0KYGBge3J9DQogIGRmJGJpbmFyeSA8LSBpZmVsc2UoZGYkYmluYXJ5PT0iUEFTUyIsVFJVRSxGQUxTRSkNCmBgYA0KKipGaW5pYW5jaWFsIEluZm9ybWF0aW9uKiogIC0gICBCdWRnZXQgaGFzIGJlZW4gcmVhZCBhcyBhbiBpbnRlZ2VyLCBhbmQgdGhlIGRvbWVzdGljIGFuZCBpbnRlcm5hdGlvbmFsIGdyb3NzZXMgaGF2ZSBiZWVuIHJlYWQgYXMgY2hhcmFjdGVyIGRhdGEgdHlwZS4gVGhlc2Ugd2lsbCBhbGwgYmUgY29udmVydGVkIGludG8gbnVtZXJpY2FsIGRhdGEgdHlwZXMuDQpgYGB7cn0NCiAgZGYkYnVkZ2V0IDwtIGFzLm51bWVyaWMoZGYkYnVkZ2V0KQ0KICBkZiRgYnVkZ2V0XzIwMTMkYCA8LSBhcy5udW1lcmljKGRmJGBidWRnZXRfMjAxMyRgKQ0KICBkZiRkb21ncm9zcyA8LSBhcy5udW1lcmljKGRmJGRvbWdyb3NzKQ0KICBkZiRpbnRncm9zcyA8LSBhcy5udW1lcmljKGRmJGludGdyb3NzKQ0KICBkZiRgZG9tZ3Jvc3NfMjAxMyRgIDwtIGFzLm51bWVyaWMoZGYkYGRvbWdyb3NzXzIwMTMkYCkNCiAgZGYkYGludGdyb3NzXzIwMTMkYCA8LSBhcy5udW1lcmljKGRmJGBpbnRncm9zc18yMDEzJGApDQpgYGANCg0KVGhpcyBoYXMgaW50cm9kdWNlZCBzb21lIE5BIHZhbHVlcy4gVGhpcyBjb2RlIHdpbGwgZmluZCB0aGUgbnVtYmVyIG9mIHJvd3MgZm9yIHdoaWNoIGFueSBvZiB0aGVzZSB2YWx1ZXMgaXMgTkEuDQpgYGB7cn0NCiAgc3VtKGlzLm5hKGRmJGRvbWdyb3NzKSANCiAgICAgIHwgaXMubmEoZGYkaW50Z3Jvc3MpIA0KICAgICAgfCBpcy5uYShkZiRgZG9tZ3Jvc3NfMjAxMyRgKQ0KICAgICAgfCBpcy5uYShkZiRgaW50Z3Jvc3NfMjAxMyRgKQ0KICAgICAgfCBpcy5uYShkZiRidWRnZXQpDQogICAgICB8IGlzLm5hKGRmJGBidWRnZXRfMjAxMyRgKSkNCmBgYA0KDQpTaW5jZSB0aGlzIGlzIGEgdmVyeSBzbWFsbCBwcm9wb3J0aW9uIG9mIHRoZSBkYXRhc2V0LCB3ZSBzaG91bGQgYmUgc2FmZSB0byBzaW1wbHkgZHJvcCB0aGVzZSByb3dzIGlmIG5lZWRlZC4gSG93ZXZlciwgSSB3aWxsIGxlYXZlIHRoZXNlIGFzIGlzIGZvciBub3csIGFzIHRoZSBtb3ZpZXMnIGdyb3NzIG1heSBub3QgYmUgdGhlIGZvY3VzIG9mIG91ciBpbnZlc3RpZ2F0aW9uLg0KDQoqKmNvZGUqKiAgICAtIHVzZWQgdG8gc3Vic2V0IHRoZSBkYXRhIGZvciB0aGUgYXJ0aWNsZS4gVGhpcyB3aWxsIGJlIHJlLWVuY29kZWQgYXMgYSBmYWN0b3IsIG5vbi1vcmRlcmVkLiANCmBgYHtyfQ0KICBkZiRjb2RlIDwtIGFzLmZhY3RvcihkZiRjb2RlKQ0KYGBgDQoNCioqRGVjYWRlIGNvZGUqKiAtIFRoZSBpbml0aWFsIHZhbHVlcyBvZiB0aGVzZSBjb2RlcyBhcmUgbm90IGNsZWFyIGluIHRoZWlyIG1lYW5pbmdzLiBXZSB3aWxsIHJlLWVuY29kZSB0aGVtIGludG8gc29tZXRoaW5nIG1vcmUgbGVnaWJsZS4NCmBgYHtyfQ0KICAgIHN0cihkZiRgZGVjYWRlIGNvZGVgICU+JSBhcy5mYWN0b3IpDQogIGhlYWQoZmlsdGVyKGRmLGRmJGBkZWNhZGUgY29kZWA9PTMpKSAjOTBzDQogIGhlYWQoZmlsdGVyKGRmLGRmJGBkZWNhZGUgY29kZWA9PTIpKSAjMjAwMHMNCiAgaGVhZChmaWx0ZXIoZGYsZGYkYGRlY2FkZSBjb2RlYD09MSkpICMyMDEwJ3MNCmBgYA0KVGhlIGRlY2FkZSBjb2RlcyBjb3JyZXNwb25kIHRvIHRoZSA5MCdzLCAyMDAwJ3MgYW5kIDIwMTAncyBpbiB0aGUgb3JkZXIgMywyLDEuIFdlIHdpbGwgcmVjYXN0IHRoZXNlIGFzIGFuIG9yZGVyZWQgZmFjdG9yLCBpbiBjaHJvbm9sb2dpY2FsIG9yZGVyLg0KYGBge3J9DQogIGRmJGBkZWNhZGUgY29kZWAgPC0gZmFjdG9yKGRmJGBkZWNhZGUgY29kZWAsIGxldmVscyA9IGMoMywyLDEpLGxhYmVscyA9IGMoIjE5OTAncyIsIjIwMDAncyIsIjIwMTAncyIpLG9yZGVyZWQgPSBUUlVFKQ0KYGBgDQoNCioqUGVyaW9kIENvZGUqKiAtIFRoZSBtZWFuaW5nIG9mIHRoaXMgdmFyaWFibGUgaXMgbm90IGNsZWFyLCBidXQgYXBwZWFycyB0byBoYXZlIGJlZW4gdXNlZCB0byBzdWJkaXZpZGUgdGhlIGRhdGFzZXQgZm9yIHRoZSBGaXZlVGhpcnR5RWlnaHQgYXJ0aWNsZS4gV2Ugd2lsbCBkcm9wIHRoaXMgdmFyaWFibGUgZm9yIG5vdy4NCmBgYHtyfQ0KZGYkYHBlcmlvZCBjb2RlYCA8LSBOVUxMDQpgYGANCg0KIyMJVGlkeSAmIE1hbmlwdWxhdGUgRGF0YSBJIA0KDQpUaWR5IGRhdGEgcHJpbmNpcGxlcyBzdGF0ZSB0aGF0IGVhY2ggcm93IHNob3VsZCBjb3JyZXNwb25kIHRvIGEgc2luZ2xlIG9ic2VydmF0aW9uLCBlYWNoIGNvbHVtbiBzaG91bGQgY29ycmVzcG9uZCB0byBhIHNpbmdsZSB2YXJpYWJsZSBhbmQgZWFjaCB0eXBlIG9mIG9ic2VydmF0aW9uYWwgdW5pdCBzaG91bGQgZm9ybSBhIHRhYmxlLiBJbiB0aGlzIGNhc2Ugb3VyIGRhdGEgY29uZm9ybXMgdG8gdGhlc2UgcHJpbmNpcGxlcy4NCiAqIEVhY2ggcm93IHJlcHJlc2VudHMgYSBzaW5nbGUgZmlsbQ0KICogRWFjaCBjb2x1bW4gcmVwcmVzZW50cyBhIHNpbmdsZSBwaWVjZSBvZiBpbmZvcm1hdGlvbiBhYm91dCBhIGZpbG0NCiAqIFRoZSB0YWJsZSBlbmNvZGVzIGEgdmFyaWV0eSBvZiBpbmZvcm1hdGlvbiBvbiBhIHBlci1maWxtIGJhc2lzLg0KVGhlIENvZGUgdmFyaWFibGUgY291bGQgYmUgaW50ZXJwcmV0ZWQgYXMgdW50aWR5LCBhcyBpdCBpcyBhIGNvbWJpbmF0aW9uIG9mIHRoZSBZZWFyIGFuZCBCaW5hcnkgdmFyaWFibGVzIHVzZWQgdG8gY2F0ZWdvcmlzZSBmaWxtcyB0b2dldGhlci4gU2luY2Ugd2UgYWxyZWFkeSBoYXZlIHRoaXMgaW5mb3JtYXRpb24sIHdlIGNhbiBkcm9wIHRoZSB2YXJpYWJsZSByYXRoZXIgdGhhbiBhdHRlbXB0IHRvIHRpZHkgaXQuDQpgYGB7cn0NCmRmJGNvZGUgPC0gTlVMTA0KYGBgDQoNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSUkgDQoNCkkgaGF2ZSBjaG9zZW4gdG8gZmlyc3RseSBjcmVhdGUgYSBzZXQgb2YgdmFyaWFibGVzIHdoaWNoIGVuY29kZSB0aGUgcHJvZml0YWJpbGl0eSBvZiBhIGZpbG0uIFRoZSBmaXJzdCBvZiB0aGVzZSB3aWxsIGJlIHRoZSBwcm9maXQsIHdoaWNoIHdpbGwgYmUgdGhlIHRvdGFsIGdyb3NzIChkb21lc3RpYyBwbHVzIGludGVybmF0aW9uYWwpIG1pbnVzIHRoZSBmaWxtJ3MgYnVkZ2V0Lg0KYGBge3J9DQpkZiRwcm9maXQgPC0gZGYkZG9tZ3Jvc3MgKyBkZiRpbnRncm9zcyAtIGRmJGJ1ZGdldA0KYGBgDQpUbyBiZXR0ZXIgY29tcGFyZSBmaWxtcyBmcm9tIGRpZmZlcmVudCB5ZWFycywgd2Ugc2hvdWxkIGNyZWF0ZSBzb21lIHRpbWUgaW5zZW5zaXRpdmUgbWVhc3VyZXMgb2YgcHJvZml0LiBQcm9maXRfMjAxMyB3aWxsIG1ha2UgdGhlIHNhbWUgY2FsY3VsYXRpb24gYmFzZWQgb24gdGhlIDIwMTMgJFVTIGRhdGEgdmFsdWVzLg0KYGBge3J9DQpkZiRwcm9maXRfMjAxMyA8LSBkZiRgZG9tZ3Jvc3NfMjAxMyRgICsgZGYkYGludGdyb3NzXzIwMTMkYCAtIGRmJGBidWRnZXRfMjAxMyRgDQpgYGANCg0KRmluYWxseSwgdG8gY29tcGFyZSB0aGUgcHJvZml0cyBvZiBkaWZmZXJlbnQgZmlsbXMsIGl0IG1heSBiZSBtb3JlIGluc3RydWN0aXZlIHRvIGFzc2VzcyB0aGVtIGJhc2VkIG9uIHRoZSByYXRpbyBvZiBncm9zcyB0byBidWRnZXQgaW5zdGVhZC4NCmBgYHtyfQ0KZGYkcHJvZml0X3JhdGlvIDwtIChkZiRkb21ncm9zcyArIGRmJGludGdyb3NzKS9kZiRidWRnZXQNCmBgYA0KDQojIwlTY2FuIEkgDQoNClRoZSB0ZXh0IG9mIHRoZSB0aXRsZSBjb2x1bW4gaGFzIGFscmVhZHkgYmVlbiBjbGVhbmVkIHVwIGluIGFuIGVhcmxpZXIgc2VjdGlvbi4gRmlyc3Qgd2Ugd2lsbCBjaGVjayBmb3IgTkEgdmFsdWVzLg0KYGBge3J9DQpjb2xTdW1zKGlzLm5hKGRmKSkNCmBgYA0KSXQgd2FzIGFscmVhZHkgZXN0YWJsaXNoZWQgdGhhdCB0aGVyZSB3ZXJlIGEgZmV3IE5BIHZhbHVlcyBpbiB0aGUgZmluYW5jaWFsIGluZm9ybWF0aW9uLCB0aGVzZSBjYW4gc2FmZWx5IGJlIHdvcmtlZCBhcm91bmQuIFRoZSBkZWNhZGUgY29kZSBpcyBhbHNvIG1pc3NpbmcgYSBudW1iZXIgb2YgdmFsdWVzLg0KYGBge3J9DQpoZWFkKGZpbHRlcihkZiwgaXMubmEoZGYkYGRlY2FkZSBjb2RlYCkpKQ0KYGBgDQpJdCBzZWVtcyBsaWtlIHRoaXMgaXMgYmVjYXVzZSB0aGVyZSBhcmUgc29tZSBmaWxtcyBmcm9tIGRlY2FkZXMgYmVzaWRlcyB0aG9zZSBlbmNvZGVkIGluIHRoZSBkZWNhZGUgY29kZS4NCmBgYHtyfQ0KZmlsdGVyKGRmLCBpcy5uYShkZiRgZGVjYWRlIGNvZGVgKSkgJT4lIGRpc3RpbmN0KHllYXIpDQpgYGANClRoZXJlIGFyZSBmaWxtcyBmcm9tIHRoZSAxOTcwJ3MgYW5kIDE5ODAncyBpbiB0aGUgZGF0YXNldC4gUmF0aGVyIHRoYW4gcmVkZWZpbmluZyB0aGUgZmFjdG9yIGxldmVscyB0byBpbmNsdWRlIHRoZXNlLCB3ZSB3aWxsIG1ha2UgYSBtb3JlIHJvYnVzdCB2ZXJzaW9uIG9mIHRoZSBkZWNhZGUgdmFsdWUsIGluIGNhc2Ugd2UgdXNlIHRoaXMgY29kZSBhZ2FpbiB3aXRoIGEgZGF0YXNldCB0aGF0IGluY2x1ZGVzIG5ldyBkZWNhZGVzIGluIHRoZSBmdXR1cmUuDQpgYGB7cn0NCiNEZWxldGUgZGVjYWRlIGNvZGUNCmRmJGBkZWNhZGUgY29kZWAgPC0gTlVMTA0KIyBkZiR5ZWFyICUlIDEwIGlzIHRoZSB5ZWFyIG1vZCAxMCwgd2hpY2ggd2lsbCBiZSB0aGUgb25lJ3MgZGlnaXQNCmRmJGRlY2FkZSA8LSBkZiR5ZWFyIC0gKGRmJHllYXIgJSUgMTApDQpkaXN0aW5jdChkZixkZiRkZWNhZGUpDQojbm93IHJlZW5jb2RlIGFzIGEgZmFjdG9yLg0KZGYkZGVjYWRlIDwtIGRmJGRlY2FkZSAlPiUgZmFjdG9yKG9yZGVyZWQgPSBUUlVFKQ0KbGV2ZWxzKGRmJGRlY2FkZSkNCmBgYA0KDQoNCg0KDQojIwlTY2FuIElJDQoNCkkgd2lsbCBzdGVwIHRocm91Z2ggdGhlIG91dGxpZXIgaW52ZXN0aWdhdGlvbiB3aXRoIHRoZSBidWRnZXQgdmFyaWFibGUgZmlyc3QuIExldCdzIHRha2UgYSBsb29rIGF0IHRoZSBib3hwbG90IG9mIHRoZSBidWRnZXQuDQpgYGB7cn0NCmRmJGJ1ZGdldCAlPiUgYm94cGxvdChtYWluID0gIkJveHBsb3Qgb2YgQnVkZ2V0IikNCmBgYA0KVGhlcmUgYXJlIHF1aXRlIGEgbG90IG9mIG91dGxpZXJzIHRvd2FyZHMgdGhlIGhpZ2hlciBlbmQgb2YgdGhlIGRhdGFzZXQuIExldCdzIG5vdyBsb29rIGF0IHRoZSB6LXNjb3Jlcw0KYGBge3J9DQp6LnNjb3JlcyA8LSBkZiRidWRnZXQgJT4lICBzY29yZXModHlwZSA9ICJ6IikNCnouc2NvcmVzICU+JSBzdW1tYXJ5KCkNCmBgYA0KV2UgY2FuIGNvbnNpZGVyIGFuIG91dGxpZXIgdG8gYmUgYSB2YWx1ZSB3aXRoIGEgei1zY29yZSBncmVhdGVyIHRoYW4gMy4NCmBgYHtyfQ0KbGVuZ3RoKHdoaWNoKHouc2NvcmVzID4gMykpDQpgYGANClRoZXJlIGFyZSA0MyBzdWNoIG91dGxpZXJzLg0KTGV0IHVzIHJlbWFrZSB0aGUgYm94cGxvdCBleGNsdWRpbmcgdGhlc2UgNDMgZmlsbXMuDQpgYGB7cn0NCmRmJGJ1ZGdldFstd2hpY2goei5zY29yZXMgPiAzKV0gJT4lIGJveHBsb3QobWFpbiA9ICJCdWRnZXQsIG91dGxpZXJzIHJlbW92ZWQiKQ0KZGZbd2hpY2goei5zY29yZXM+MyksXQ0KYGBgDQpOb3cgdGhhdCB3ZSBoYXZlIGVsaW1pbmF0ZWQgdGhlIG91dGxpZXJzIGZyb20gdGhlIGRhdGEsIHdlIGFwcGVhciB0byBoYXZlIGNyZWF0ZWQgbW9yZSBvdXRsaWVycyBpbiB0aGUgcmVkdWNlZCBzZXQsIGJ1dCB0aGUgYm94cGxvdCBhcHBlYXJzIG11Y2ggbW9yZSByZWFzb25hYmxlLg0KTG9va2luZyBhdCB0aGUgZmlsbXMgaW5jbHVkZWQgYW1vbmcgdGhlIG91dGxpZXJzLCB0aGVyZSBhcmUgcXVpdGUgYSBmZXcgdmVyeSBzaWduaWZpY2FudCBmaWxtcyBpbmNsdWRlZCwgc3VjaCBhcyBUaXRhbmljIGFuZCBBdmF0YXIuIFJlbW92aW5nIHRoZSBvdXRsaWVycyBzZWVtcyBsaWtlIGEgcG9vciBjaG9pY2UuIEkgaGF2ZSBkZWNpZGVkIHRoYXQgdGhlIGJlc3QgYXBwcm9hY2ggaXMgdG8gbGVhdmUgdGhlIG91dGxpZXJzIGluLCBhbmQgaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9uIHBlcmZvcm0gYSBiaW5uaW5nIHRyYW5zZm9ybWF0aW9uIHRvIGdyb3VwIGZpbG1zIGludG8gZGlmZmVyZW50IGJ1ZGdldCBncm91cGluZ3MuDQoNCk5vdywgd2Ugd2lsbCBxdWlja2x5IHJ1biB0aHJvdWdoIHRoZSBzYW1lIG91dGxpZXIgaW52ZXN0aWdhdGlvbiBvbiB0aGUgb3RoZXIgZmluYW5jaWFsIHZhcmlhYmxlcy4NCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLDIpKQ0KZGYkZG9tZ3Jvc3MgJT4lIGJveHBsb3QobWFpbiA9ICJEb21lc3RpYyBHcm9zcyIpDQpkZiRpbnRncm9zcyAlPiUgYm94cGxvdChtYWluID0gIkludGVybmF0aW9uYWwgR3Jvc3MiKQ0KZGYkYGRvbWdyb3NzXzIwMTMkYCAlPiUgYm94cGxvdChtYWluID0gIkRvbWVzdGljIEdyb3NzICgkVVMgMjAxMykiKQ0KZGYkYGludGdyb3NzXzIwMTMkYCAlPiUgYm94cGxvdChtYWluID0gIkludGVybmF0aW9uYWwgR3Jvc3MgKCRVUyAyMDEzKSIpDQpgYGANClRvIHdvcmsgd2l0aCB0aGUgei1zY29yZXMsIHdlIHdpbGwgbmVlZCB0byBleGNsdWRlIHRoZSBOQSB2YWx1ZXMgZm91bmQgaW4gdGhlc2UgdmFyaWFibGVzLg0KYGBge3J9DQpkZi5uYV9vbWl0IDwtIGRmICU+JSBuYS5vbWl0KCkNCg0Kei5zY29yZXMuZGcgPC0gZGYubmFfb21pdCRkb21ncm9zcyAlPiUgc2NvcmVzKHR5cGUgPSAieiIpDQp6LnNjb3Jlcy5pZyA8LSBkZi5uYV9vbWl0JGludGdyb3NzICU+JSBzY29yZXModHlwZSA9ICJ6IikNCnouc2NvcmVzLmRnMTMgPC0gZGYubmFfb21pdCRgZG9tZ3Jvc3NfMjAxMyRgICU+JSAgc2NvcmVzKHR5cGUgPSAieiIpDQp6LnNjb3Jlcy5pZzEzIDwtIGRmLm5hX29taXQkYGludGdyb3NzXzIwMTMkYCAlPiUgIHNjb3Jlcyh0eXBlID0gInoiKQ0Kei5zY29yZXMuZGcgJT4lIHN1bW1hcnkoKQ0Kei5zY29yZXMuaWcgJT4lIHN1bW1hcnkoKQ0Kei5zY29yZXMuZGcxMyAlPiUgc3VtbWFyeSgpDQp6LnNjb3Jlcy5pZzEzICU+JSBzdW1tYXJ5KCkNCmBgYA0KYGBge3J9DQpsZW5ndGgod2hpY2goei5zY29yZXMuZGcgPiAzKSkNCmxlbmd0aCh3aGljaCh6LnNjb3Jlcy5pZyA+IDMpKQ0KbGVuZ3RoKHdoaWNoKHouc2NvcmVzLmRnMTMgPiAzKSkNCmxlbmd0aCh3aGljaCh6LnNjb3Jlcy5pZzEzID4gMykpDQpgYGANCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwyKSkNCmRmJGRvbWdyb3NzICU+JSBib3hwbG90KG1haW4gPSAiRG9tZXN0aWMgR3Jvc3MiKQ0KZGYubmFfb21pdCRkb21ncm9zc1std2hpY2goei5zY29yZXMuZGcgPiAzKV0gJT4lIGJveHBsb3QobWFpbiA9ICJPdXRsaWVycyBlbGltaW5hdGVkIikNCmRmJGludGdyb3NzICU+JSBib3hwbG90KG1haW4gPSAiSW50ZXJuYXRpb25hbCBHcm9zcyIpDQpkZi5uYV9vbWl0JGludGdyb3NzWy13aGljaCh6LnNjb3Jlcy5pZyA+IDMpXSAlPiUgYm94cGxvdChtYWluID0gIk91dGxpZXJzIGVsaW1pbmF0ZWQiKQ0KZGYkYGRvbWdyb3NzXzIwMTMkYCAlPiUgYm94cGxvdChtYWluID0gIkRvbWVzdGljIEdyb3NzICgkVVMgMjAxMykiKQ0KZGYubmFfb21pdCRgZG9tZ3Jvc3NfMjAxMyRgWy13aGljaCh6LnNjb3Jlcy5kZzEzID4gMyldICU+JSBib3hwbG90KG1haW4gPSAiT3V0bGllcnMgZWxpbWluYXRlZCIpDQpkZiRgaW50Z3Jvc3NfMjAxMyRgICU+JSBib3hwbG90KG1haW4gPSAiSW50ZXJuYXRpb25hbCBHcm9zcyAoJFVTIDIwMTMpIikNCmRmLm5hX29taXQkYGludGdyb3NzXzIwMTMkYFstd2hpY2goei5zY29yZXMuaWcxMyA+IDMpXSAlPiUgYm94cGxvdChtYWluID0gIk91dGxpZXJzIGVsaW1pbmF0ZWQiKQ0KYGBgDQpBc2lkZSBmcm9tIHRoZSBmaW5hbmNpYWwgaW5mb3JtYXRpb24gb2YgdGhlIGZpbG1zLCB0aGVyZSBpcyBhbHNvIHRoZSB1c2VyIHJhdGluZ3Mgc2NvcmVzIHRvIGludmVzdGlnYXRlLg0KYGBge3J9DQpkZiRzY29yZSAlPiUgYm94cGxvdChtYWluID0gIlZpZXdlciBSZXZpZXcgU2NvcmVzIikNCmhpc3QoZGYkc2NvcmUsIG1haW4gPSAiSGlzdG9ncmFtIG9mIFZpZXdlciBTY29yZXMiKQ0KYGBgDQpgYGB7cn0NCmRmJHNjb3JlICU+JSBzdW1tYXJ5KCkNCmBgYA0KVGhlIHVzZXIgcmV2aWV3IHNjb3JlIGZvciB0aGVzZSBtb3ZpZXMgaXMgcXVpdGUgcG9zaXRpdmVseSBza2V3ZWQsIGJ1dCB0aGVyZSBpcyBub3QgYW55IGlzc3VlIHdpdGggb3V0bGllcnMsIG9yIGludmFsaWQgdmFsdWVzIChzdWNoIGFzIHRob3NlIG91dHNpZGUgb2YgdGhlIDEtMTAgcmFuZ2UpLiBJbiB0aGUgbmV4dCBzZWN0aW9uIHdlIHdpbGwgbm9ybWFsaXNlIHRoZSByZXZpZXcgc2NvcmVzLg0KDQoNCg0KIyMJVHJhbnNmb3JtIA0KDQpXZSB3aWxsIGFwcGx5IGEgdHJhbnNmb3JtYXRpb24gdG8gbm9ybWFsaXNlIHRoZSBtb3ZpZSBzY29yZXMgYWJvdXQgYSBtZWFuIG9mIDAuDQpgYGB7cn0NCnNjb3JlcyA8LSBzY2FsZShkZiRzY29yZSkNCmhpc3Qoc2NvcmVzKQ0KYGBgDQpUaGlzIG5vcm1hbGlzZWQgc2NvcmUgd2lsbCBiZSBhZGRlZCB0byB0aGUgZGF0YSBmcmFtZSBhcyBzY29yZV9ub3JtDQpgYGB7cn0NCmRmJHNjb3JlX25vcm0gPC0gc2NvcmVzDQpgYGANCldlIHdpbGwgYWxzbyBwZXJmb3JtIGEgYmlubmluZyB0cmFuc2Zvcm1hdGlvbiB0byB0aGUgZmlsbXMgYmFzZWQgb24gdGhlaXIgYnVkZ2V0cy4NClRoaXMgd2lsbCBiZSBhbiBlcXVhbCBmcmVxdWVuY3kgYmlubmluZywgdG8gc3BsaXQgdGhlIGZpbG1zIGludG8gMTAgZXZlbiBjYXRlZ29yaWVzLiBGb3IgY29tcGFyaXNvbiBiZXR3ZWVuIGZpbG1zIGZyb20gZGlmZmVyZW50IHllYXJzLCB3ZSB3aWxsIHVzZSB0aGUgQnVkZ2V0IDIwMTMgVVNEIHZhbHVlLg0KDQpgYGB7cn0NCmRmJGJ1ZGdldF9iaW5uZWQgPC0gZGlzY3JldGl6ZShkZiRgYnVkZ2V0XzIwMTMkYCwgZGlzYyA9ICJlcXVhbGZyZXEiLCBuYmlucyA9IDEwKQ0KYGBgDQoNCk5vdywgd2UgaGF2ZSBhcnJpdmVkIGF0IG91ciBmaW5hbCBkYXRhIHNldCwgcmVhZHkgZm9yIGFuYWx5c2lzLg0KDQo8YnI+DQo8YnI+DQo=