Required packages
Below is the list of packages used in the project.
#Packages required for my code
library(tidyverse)
library(lubridate)
library(outliers)
library(forecast)
library(knitr)
library(Hmisc)
Executive Summary
Data pre-processing is an important stage of the data analysis lifecycle which is done primarily along with data import and before modeling/machine learning/visual reports. It is not nessesarily carried out in the same order but follows a high level order of data preperation stages.
Below are some of the important stages:
1. Problem definition by data :
- The problem area is defined at this point which will focus on the data required to solve the problem/problem-space. It will also help in understanding the void of certain critical data which can support creation of the same from existing data. It is also important to iron out details about storage and analytical platforms that are necessary to carry out the analysis
- I have carried out this steps first to understand the datasets needed for the analysis. 2 datasets are imported into R and merged for holistic analysis of all the museum’s artworks and artists
2. Data import:
- This is the stage of bringing all the necessary data onto 1 platform to be processed further into a final analytical dataset or multiple datasets
- Data is usually stored into databases, warehouses or local flat files
- I have imported files directly from the file URLs onto R where further processing will be carried out
3. Understand data:
- This stage involves exploring the structure and the current stage of the data to identify possible next steps needed. The dimensions, data types, summary statistics, invalid values, data dictionary, merge keys are some of the things to look at.
- It is recommended that the analyst understand the data completely before proceeding
- I have done this steps next, but have not moved towards manipulating them just yet. Some pre-processing is needed on the whole dataset before moving towards individual variables/observations
4. Tidy data:
- This is an important stage to clean the data and to bring it to the format that is appealing to look at, and to streamline further analysis.
- Involves converting to tidy format which involves converting attributes into columns, rows into observations and values into a unit of observation
- I have carried this steps right after since there was a lot of tidying that was needed on the dataset before moving towards further processing or beautification/formatting
5. Manipulate data:
- This step involves further changing the structure of the data by merging multiple datasets together and creating additional, meaningful variables and remove non impactful rows and variables and structuring data in an appealing format for viewing as well
- I have carried out a lot of operations at this steps which include individual variable manipulations, formatting and then merging clean datasets together (Initial step of understanding data helps out with this step)
6. Scan and treat missing/outlier values:
- This is an important step to either get rid of missing/special/outlier observations or proxy them with likely/most common values from existing data. Statistical tests and summaries are used to carry out this steps.
- Imputing with mean/median/mode is a popular option. I have split this section into Missing values and Outlier values sections and releavant treatment is done in detail for all relevant variables
- The most important step at this point is to go back to the business and request additional/improved data which is the best approach to deal with the data, if available
7. Transforming data:
- This steps involves transforming data into formats favorable to modeling, inter-variables relationships, to reduce skewness/outliers or to better understand the data)
- I have carried out this step last to get the data ready for visualization or further modeling activities. All numeric columns are viewed under the lens of multiple transformations and appropriate transformations are done, wherever applicable
Data Summary
The MoMA collection was established in 1929 and is a digital database of artworks by various artists across the world, and acquired by the museum across the last 150 years. The 2 datasets feature artwork details and artist details respectively and contain a variety of attributes outlined and summarized below. Both are in public domain under a CC0 License.
work_arts (Columns: 29, Rows: 138,185, URL: artworks.csv)
Columns:
* Title - Title of the artwork (Level of data)
* Artist - Artist name (multiple artists of an artwork are concatenated as untidy data)
* ConstituentID - Artist ID (Also the primary join key for Artists dataset)
* ArtistBio - Artist nationality and active years
* Gender - Gender of the artist
* BeginDate, EndDate - active years of artist
* Medium - medium used to create the art (paint, print, photograph etc)
* Dimensions - physical dimensions of the artwork (contains Height, Width and Depth)
* CreditLine - Purchase line by the architect (purchase, gift, partial gift by a person/organization etc)
* Classification - Type of artwork (Architecture, design, illustrated book etc)
* Department - Higher level classification (architecture, drawing, film, painting etc)
* DateAcquired - Date of acquiring by the Museum
* URL, ThumbnailURL - link to artwork and thumbnail
* Physical dimensions (cm) - Circumference, Depth, Diameter, Height, Length, Weight, Width etc)
Artists (Columns: 9, Rows: 15,333, URL: Artists.csv)
Columns:
* ConstituentID - Primary key and ID of the artist
* DisplayName - Artist name
* ArtistBio, Nationality, Gender, BeginDate, EndDate - same as above
* Wiki QID - Unique identifier in Wikidata for the artist (Also known as Wikidata item identifier)
* ULAN - This is the Getty ULAN ID for the artist (can be used to search in Getty databases
Brief Summary of steps taken and a few early findings:
1. I have looked at the head() and str() (str shown in next section) of each dataset (and explored the entire dataset) to understand the variables, data types and identify immediate next steps
2. I have looked at the structure of the data to identify join keys, level of data and the pre-processing steps needed for each of the columns for further analysis (There are a lot of columns that need manipulations to bring to the right format)
3. The work_arts data is untidy with multiple artists (of an artwork) concatenated in a singe cell and the same is applicable for each of the artist’s attribute in work_arts
4. artworks.csv is imported as work_arts for quick access using R shortcut functions (varying from artists.csv). artists.csv is imported as-is
5. imported csv are converted to dataframe using as.data.frame() while importing using read_csv()
6. I have identified a few redundant columns with ‘ArtistBio’ & ‘Dimensions’ but they will come handy to replace missing values wherever applicable
7. There are also few rows/columns with a majority of missing values. They will be manipulated appropriately in subsequent sections
8. work_arts is huge in size and will further increase after tidying, so an unbiased subset will be also created in subsequent sections (because my RStudio couldn’t process operations without this)
9. Merge key is ‘ConstituentID’ which is also the primary key for artists dataframe
- ‘artists’ has unique details about the artist which need to be brought into the work_arts dataset using the merge operation
#Dataset 1:
work_arts <- read_csv("https://media.githubusercontent.com/media/MuseumofModernArt/collection/master/Artworks.csv") %>% as.data.frame(row.names = NULL)
print(head(work_arts, n = 5))
#Dataset 2:
artists <- read_csv("https://media.githubusercontent.com/media/MuseumofModernArt/collection/master/Artists.csv") %>% as.data.frame(row.names = NULL)
print(head(artists, n = 5))
Understand 1 (Explore only)
Summary of steps and required data type conversions:
Steps:
1. I have checked the structure of datasets using str() and type()
2. I have explored individual columns and identified necessary data type conversions required (outlined below). These will be done post tidying data in the next step)
3. The colSums() of NAs show columns with missing values. Columns with a majority of missing values can be removed after judging its significance for further analysis
4. count of unique values is used to identify possible factor columns mentioned below
Conversions needed (to be executed after tidying and unnesting columns):
1. ConstituentID, BeginDate, EndDate - convert from chr to int
2. Gender - convert from chr to factor (apply lower case conversion), (add factor labels)
3. Nationality, Classification, Department, Catalogued - convert from chr to factor
4. Columns with majority NAs (“Circumference (cm)”, “Diameter (cm)”, “Length (cm)”, “Weight (kg)”, “Seat Height (cm)”,“Duration (sec.)”)
#Understand work_arts
str(work_arts)
'data.frame': 138185 obs. of 29 variables:
$ Title : chr "Ferdinandsbrücke Project, Vienna, Austria (Elevation, preliminary version)" "City of Music, National Superior Conservatory of Music and Dance, Paris, France, View from interior courtyard" "Villa near Vienna Project, Outside Vienna, Austria, Elevation" "The Manhattan Transcripts Project, New York, New York, Introductory panel to Episode 1: The Park" ...
$ Artist : chr "Otto Wagner" "Christian de Portzamparc" "Emil Hoppe" "Bernard Tschumi" ...
$ ConstituentID : chr "6210" "7470" "7605" "7056" ...
$ ArtistBio : chr "(Austrian, 1841–1918)" "(French, born 1944)" "(Austrian, 1876–1957)" "(French and Swiss, born Switzerland 1944)" ...
$ Nationality : chr "(Austrian)" "(French)" "(Austrian)" "()" ...
$ BeginDate : chr "(1841)" "(1944)" "(1876)" "(1944)" ...
$ EndDate : chr "(1918)" "(0)" "(1957)" "(0)" ...
$ Gender : chr "(Male)" "(Male)" "(Male)" "(Male)" ...
$ Date : chr "1896" "1987" "1903" "1980" ...
$ Medium : chr "Ink and cut-and-pasted painted pages on paper" "Paint and colored pencil on print" "Graphite, pen, color pencil, ink, and gouache on tracing paper" "Photographic reproduction with colored synthetic laminate" ...
$ Dimensions : chr "19 1/8 x 66 1/2\" (48.6 x 168.9 cm)" "16 x 11 3/4\" (40.6 x 29.8 cm)" "13 1/2 x 12 1/2\" (34.3 x 31.8 cm)" "20 x 20\" (50.8 x 50.8 cm)" ...
$ CreditLine : chr "Fractional and promised gift of Jo Carole and Ronald S. Lauder" "Gift of the architect in honor of Lily Auchincloss" "Gift of Jo Carole and Ronald S. Lauder" "Purchase and partial gift of the architect in honor of Lily Auchincloss" ...
$ AccessionNumber : chr "885.1996" "1.1995" "1.1997" "2.1995" ...
$ Classification : chr "Architecture" "Architecture" "Architecture" "Architecture" ...
$ Department : chr "Architecture & Design" "Architecture & Design" "Architecture & Design" "Architecture & Design" ...
$ DateAcquired : Date, format: "1996-04-09" ...
$ Cataloged : chr "Y" "Y" "Y" "Y" ...
$ ObjectID : num 2 3 4 5 6 7 8 9 10 11 ...
$ URL : chr "http://www.moma.org/collection/works/2" "http://www.moma.org/collection/works/3" "http://www.moma.org/collection/works/4" "http://www.moma.org/collection/works/5" ...
$ ThumbnailURL : chr "http://www.moma.org/media/W1siZiIsIjU5NDA1Il0sWyJwIiwiY29udmVydCIsIi1yZXNpemUgMzAweDMwMFx1MDAzZSJdXQ.jpg?sha=137b8455b1ec6167" "http://www.moma.org/media/W1siZiIsIjk3Il0sWyJwIiwiY29udmVydCIsIi1yZXNpemUgMzAweDMwMFx1MDAzZSJdXQ.jpg?sha=55b65fa4368fe00a" "http://www.moma.org/media/W1siZiIsIjk4Il0sWyJwIiwiY29udmVydCIsIi1yZXNpemUgMzAweDMwMFx1MDAzZSJdXQ.jpg?sha=fdcfca4db3acac1f" "http://www.moma.org/media/W1siZiIsIjEyNCJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDMwMHgzMDBcdTAwM2UiXV0.jpg?sha=c89b9071486760a5" ...
$ Circumference (cm): logi NA NA NA NA NA NA ...
$ Depth (cm) : num NA NA NA NA NA NA NA NA NA NA ...
$ Diameter (cm) : num NA NA NA NA NA NA NA NA NA NA ...
$ Height (cm) : num 48.6 40.6 34.3 50.8 38.4 ...
$ Length (cm) : logi NA NA NA NA NA NA ...
$ Weight (kg) : num NA NA NA NA NA NA NA NA NA NA ...
$ Width (cm) : num 168.9 29.8 31.8 50.8 19.1 ...
$ Seat Height (cm) : logi NA NA NA NA NA NA ...
$ Duration (sec.) : logi NA NA NA NA NA NA ...
- attr(*, "problems")= tibble [2,927 x 5] (S3: tbl_df/tbl/data.frame)
..$ row : int [1:2927] 1002 1003 1004 1005 1007 1008 1011 1041 1042 1045 ...
..$ col : chr [1:2927] "Length (cm)" "Length (cm)" "Length (cm)" "Length (cm)" ...
..$ expected: chr [1:2927] "1/0/T/F/TRUE/FALSE" "1/0/T/F/TRUE/FALSE" "1/0/T/F/TRUE/FALSE" "1/0/T/F/TRUE/FALSE" ...
..$ actual : chr [1:2927] "17.8" "19.0" "18.1" "17.78" ...
..$ file : chr [1:2927] "'https://media.githubusercontent.com/media/MuseumofModernArt/collection/master/Artworks.csv'" "'https://media.githubusercontent.com/media/MuseumofModernArt/collection/master/Artworks.csv'" "'https://media.githubusercontent.com/media/MuseumofModernArt/collection/master/Artworks.csv'" "'https://media.githubusercontent.com/media/MuseumofModernArt/collection/master/Artworks.csv'" ...
- attr(*, "spec")=
.. cols(
.. Title = col_character(),
.. Artist = col_character(),
.. ConstituentID = col_character(),
.. ArtistBio = col_character(),
.. Nationality = col_character(),
.. BeginDate = col_character(),
.. EndDate = col_character(),
.. Gender = col_character(),
.. Date = col_character(),
.. Medium = col_character(),
.. Dimensions = col_character(),
.. CreditLine = col_character(),
.. AccessionNumber = col_character(),
.. Classification = col_character(),
.. Department = col_character(),
.. DateAcquired = col_date(format = ""),
.. Cataloged = col_character(),
.. ObjectID = col_double(),
.. URL = col_character(),
.. ThumbnailURL = col_character(),
.. `Circumference (cm)` = col_logical(),
.. `Depth (cm)` = col_double(),
.. `Diameter (cm)` = col_double(),
.. `Height (cm)` = col_double(),
.. `Length (cm)` = col_logical(),
.. `Weight (kg)` = col_double(),
.. `Width (cm)` = col_double(),
.. `Seat Height (cm)` = col_logical(),
.. `Duration (sec.)` = col_logical()
.. )
#Summary of data types. Need data type conversions (see summary after code chuck)
sapply(work_arts, class) %>% as.data.frame() %>% set_names("colType") %>% count(colType)
work_arts %>% {colSums(is.na(.))} #check count NAs per column
Title Artist ConstituentID
39 1309 1309
ArtistBio Nationality BeginDate
6029 1309 1309
EndDate Gender Date
1309 1309 2214
Medium Dimensions CreditLine
9735 9894 2431
AccessionNumber Classification Department
0 0 0
DateAcquired Cataloged ObjectID
7134 0 0
URL ThumbnailURL Circumference (cm)
51265 62089 138185
Depth (cm) Diameter (cm) Height (cm)
124392 136717 17934
Length (cm) Weight (kg) Width (cm)
138185 137896 18856
Seat Height (cm) Duration (sec.)
138185 138185
work_arts %>% sapply(function(x) length(unique(x))) #check unique per each columns
Title Artist ConstituentID
98573 13706 13746
ArtistBio Nationality BeginDate
8168 1054 2216
EndDate Gender Date
1285 410 9374
Medium Dimensions CreditLine
21251 84352 7224
AccessionNumber Classification Department
138185 32 8
DateAcquired Cataloged ObjectID
1756 2 138185
URL ThumbnailURL Circumference (cm)
86921 75496 1
Depth (cm) Diameter (cm) Height (cm)
1369 511 3816
Length (cm) Weight (kg) Width (cm)
1 201 3848
Seat Height (cm) Duration (sec.)
1 1
#Understand artists
str(artists)
'data.frame': 15233 obs. of 9 variables:
$ ConstituentID: num 1 2 3 4 5 6 7 9 10 11 ...
$ DisplayName : chr "Robert Arneson" "Doroteo Arnaiz" "Bill Arnold" "Charles Arnoldi" ...
$ ArtistBio : chr "American, 1930–1992" "Spanish, born 1936" "American, born 1941" "American, born 1946" ...
$ Nationality : chr "American" "Spanish" "American" "American" ...
$ Gender : chr "Male" "Male" "Male" "Male" ...
$ BeginDate : num 1930 1936 1941 1946 1941 ...
$ EndDate : num 1992 0 0 0 0 ...
$ Wiki QID : chr NA NA NA "Q1063584" ...
$ ULAN : num NA NA NA 5e+08 NA ...
- attr(*, "spec")=
.. cols(
.. ConstituentID = col_double(),
.. DisplayName = col_character(),
.. ArtistBio = col_character(),
.. Nationality = col_character(),
.. Gender = col_character(),
.. BeginDate = col_double(),
.. EndDate = col_double(),
.. `Wiki QID` = col_character(),
.. ULAN = col_double()
.. )
#Summary ff data types
sapply(artists, class) %>% as.data.frame() %>% set_names("colType") %>% count(colType)
artists %>% {colSums(is.na(.))} #check count NAs per column
ConstituentID DisplayName ArtistBio Nationality
0 0 2203 2448
Gender BeginDate EndDate Wiki QID
3131 0 0 11975
ULAN
12297
artists %>% sapply(function(x) length(unique(x))) #check unique per each column
ConstituentID DisplayName ArtistBio Nationality
15233 15178 7084 119
Gender BeginDate EndDate Wiki QID
7 236 169 3254
ULAN
2934
Tidy & Manipulate Data I (and II)
The work_arts dataset is untidy because:
1. It has multiple values concatenated into a single cell, so eaech row doesn’t correspond to a single observation
2. Multiple columns are present in a single column, ex: artistBio and Dimensions (while individual columns are present, they will be used for data treatment before being tidied)
3. Few columns like Dimensions colum is difficult to understand due to a lot of garbage values with important information
The below steps are taken (most correspond to tidying data):
1. strFunc is a user defined function used to identify number of concat values in artist attributes like Artist, ConstituentID, Nationality, BeginDate, EndDate and Gender. Nationality is used as an anchor and concat values of 3 or more are excluded (Data is already huge and R was already at the breaking point for executing in my machine)
- The requirement of creating a new column in the assignment is done here 2. A filter is used next to filter out artworks with title longer than 50 characters (using nchar())
3. Text-heavy, unimportant/redundant columns like ArtistBio, CreditLine, URL and ThumbnailURL are removed at this point to make the data lesser in size
4. Concat columns are unnested into individual rows using unnest() function which converts individual item into its own row (This essentially did what separate+gather operations would do)
5. Distinct of the dataset is used to get rid of duplicate observations, if any
6. Columns with >90% NAs were removed to reduce data size (assuming no critical analysis on these dimensions at the moment). COlumns removed are Circumference, Diameter, Length, Wiegth, Seat Height, Duration
7. A large sample of 20000 observations is extracted for further analysis using a random sample (This is done to avoid heavy lag for further executions. Assumption here is that the sample derived exhibits the properties of the original dataset)
8. set.seed(5) is used below for reproducible results and dataset is saved as wa_filt
9. Now the data is in a tidy format, so further operations will focus on the data type, consistent levels and string cleaning activities
10. Additional comments in code chunks
#Tidy up work_arts:
#Filter-in observations with concat values less than 3 by using custom function and creating
#column 'flag' to filter. This is done to reduce the data size
strFunc <- function(x) {str_count(pattern = "[)] [(]", string = x)}
work_arts<-work_arts %>% mutate(flag = strFunc(Nationality)) %>% filter(flag<3) %>% select(-flag)
#Remove columns with unimportant and text-heavy string data like ArtistBio, CreditLine, URL, ThumbnailURL
#Create col 'nchar' to filter out 'Title' with characters <= 50. This is done to reduce the data size (Recommended by TA)
work_arts <- work_arts %>% select(-c(ArtistBio, CreditLine, URL, ThumbnailURL)) %>%
mutate(nchar = nchar(work_arts$Title)) %>% filter(nchar<=50) %>% select(-nchar)
#Unnest and convert multiple columns in the data to long format (Do before changing data types as clean up
#is needed before type change). 'regEx' expressions are used to split and unnest based on earlier analysis
work_arts <- work_arts %>% mutate(Artist = str_split(pattern = ", ", string = Artist)) %>% unnest(Artist) %>%
mutate(ConstituentID = str_split(pattern = ", ", string = ConstituentID)) %>% unnest(ConstituentID) %>%
mutate(Nationality = str_split(pattern = "[)] [(]", string = Nationality)) %>% unnest(Nationality) %>%
mutate(BeginDate = str_split(pattern = "[)] [(]", string = BeginDate)) %>% unnest(BeginDate) %>%
mutate(EndDate = str_split(pattern = "[)] [(]", string = EndDate)) %>% unnest(EndDate) %>%
mutate(Gender = str_split(pattern = "[)] [(]", string = Gender)) %>% unnest(Gender) %>% distinct()
#drop columns which contain mostly NA values. 6 columns have at least 90% NAs.
#Remove these using a select function and -c()
work_arts <- work_arts %>%
select(-c("Circumference (cm)", "Diameter (cm)", "Length (cm)", "Weight (kg)",
"Seat Height (cm)","Duration (sec.)"))
#As the data size is high, a sample data is created from the dataset using a sample_n() function from dplyr.
#Set.seed() is used for reproducible results, and a sample of 20,000 rows is random chosen from the data.
#Assign the sample dataset to a new name wa_filt'
set.seed(5)
wa_filt <- work_arts %>% sample_n(size = 20000, replace = F)
Understand 2 (Execution after tidying data)
This section focuses on data formatting and accuracy of column representations
1. A replacement operation is carried out to get rid of leading, trailing ‘(’ or ‘)’ characters which are replaced with "". This is done for Nationality, BeginDate, EndDate and Gender
2. Upper case in Gender is converted to lower for consistent representation and to aid creation of factor levels
3. Gender is converted to a Factor variable and relevant labels are provided. Since it is an unordered column, ordered is set to FALSE
4. ‘Unknown’ and ‘Nationality unknown’ are converted to ‘unknown’ for consistent representation (assuming a biz requirement for consistency)
5. ‘(not assigned)’ in Classification is converted to ‘Not Assigned’ in line with the other levels in the column
6. ConstituentID, BeginDate, EndDate are converted to Integer data type
7. Nationality, Classification, Department and Cataloged are converted to factor variables (levels are not changed at the moment, but can be converted as per biz requirement. Gender is already converted with labels above)
8. BeginDate, EndDate and Date are converted to date type by converting the existing ‘year’ format to ‘ymd’ by adding the beginning of year attributes (day, month is added as per necessary formatting). This will aid any further analysis based on date operations like difference in duration, comparison to a custom date or help in updating more accurate dates when the museum is able to procure more information later)
9. ‘Date’ is renamed to ‘DateMade’ for readability with other data columns like ‘BeginDate’, ‘EndDate’ and ‘DateAcquired’
10. NAs in merge column are checked before the subsequent merge (The analysis is run already before, and no NAs are found. Code is not run again to avoid maxing page limit)
11. Additional comments in code chunks
#Clean up columns with '(' ')' in column names created by unnest() function in 4 columns
wa_filt <- wa_filt %>% mutate(Nationality = str_replace_all(wa_filt$Nationality, pattern = "[()]", replacement = ""),
BeginDate = str_replace_all(wa_filt$BeginDate, pattern = "[()]", replacement = ""),
EndDate = str_replace_all(wa_filt$EndDate, pattern = "[()]", replacement = ""),
Gender = str_replace_all(wa_filt$Gender, pattern = "[()]", replacement = ""))
#Convert Gender to lower case and convert to factor with relevant labels
wa_filt$Gender <- tolower(wa_filt$Gender)
wa_filt$Gender <- factor(wa_filt$Gender, levels = c("male", "female", ""),
labels = c("Male", "Female", "Unknown"), ordered = FALSE)
#Convert Nationality to valid values using case when statement. Convert to factor next
wa_filt <- wa_filt %>% mutate(Nationality = case_when(as.character(Nationality)==""~"Unknown",
as.character(Nationality)=="Nationality unknown"~"Unknown",
TRUE~as.character(Nationality)))
#Convert Classification values to consistent format
wa_filt$Classification[wa_filt$Classification=="(not assigned)"] <- "Not Assigned"
#Convert relevant columns to Integer and factor type (Gender already converted to Factor above)
wa_filt<-wa_filt %>% mutate_at(c("ConstituentID", "BeginDate", "EndDate"), .funs = as.integer)
wa_filt<-wa_filt %>% mutate_at(c("Nationality","Classification", "Department", "Cataloged"), .funs = as.factor)
#convert years to date
wa_filt$BeginDate <- as.Date(paste(wa_filt$BeginDate, "1", "1", sep = "-"), format = "%Y-%m-%d")
wa_filt$EndDate <- as.Date(paste(wa_filt$EndDate, "1", "1", sep = "-"), format = "%Y-%m-%d")
wa_filt$Date <- as.Date(paste(wa_filt$Date, "1", "1", sep = "-"), format = "%Y-%m-%d")
wa_filt <- plyr::rename(x = wa_filt, replace = c("Date" = "DateMade"))
#check for missing values in merge keys i.e. ConstituentID. Result from before: No missing values. Ready for merge
Merge Data to create Master dataset
- wa_filt is merged with artists based on ‘ConstituentID’ column which is the primary key for artists and foreign key for wa_filt.
- As other columns in artists already present in wa_filt, only ‘wiki QID’ and ‘ULAN’ columns are brought into the ‘mergedArt’ dataset
- Sum of NAs in mergedArt are explored for the subsequent section on missing value identification and treatment. This information guides the next section
#Merge 'wa_filt' and 'artists' on key 'ConstituentID'
mergedArt <- wa_filt %>% left_join(artists[,c("ConstituentID", "Wiki QID", "ULAN")], by = "ConstituentID")
colSums(is.na(mergedArt)) #Identify columns with missing values for treatment in next section
Title Artist ConstituentID Nationality
0 0 0 0
BeginDate EndDate Gender DateMade
0 0 0 4132
Medium Dimensions AccessionNumber Classification
636 657 0 0
Department DateAcquired Cataloged ObjectID
0 580 0 0
Depth (cm) Height (cm) Width (cm) Wiki QID
11395 1646 2167 13422
ULAN
13517
Scan I (Missing values)
- Missing values will be identified and relevant treatment technique is used
- A user fucntion to select the mode of a Categorical column is created as the impute function from Hmisc didn’t provide the mode as verified by the count(, sort = TRUE) %>% head(1) method
- DateMade has missing values which are imputed using the Mode of the column (i.e. ‘1971/01/01’). It has ~4k missing values so removing missing values is not recommended. Also, the mode is highly occurring value for the column
- DateAcquired is also imputed with the mode of the column (i.e. ‘2013-10-24’). Around ~6.5k values correspond to the mode. Since the missing values are just 580 in count, excluding selectively during analysis is also an option, but i decided to use the mode as it is highly common as well.
- Medium has ~636 missing values and is imputed with the mode (i.e. ‘Gelatin silver print’) which occurs ~6.7k times
- Dimensions has around 657 missing values but is also considered a redundant column, and for the time being is imputed with the mode (i.e. ‘6 11/16 × 9 11/16" (17 × 24.6 cm)’) since its required for the next step of extracting individual dimensions to treat dimensions column (where they are missing), and is preferred for the subsequent text-heavy operations. It is likely that this column will be dropped after the extraction is executed. Other alternative is to leave the column as-is due to the low count of missing values, but i went ahead with imputing to avoid issues with subsequent string operations. (Refer 9. below)
- Depth has ~50% missing values and is kept as-is for any critical analysis later on. Also the extraction of dimensions will aid in reducing missing values in the column. (Refer 9. below)
- Wiki QID and ULAN have ~60% missing values, but since they are important attributes of the artists, they are not excluded from the dataset and kept as-is.
- Numeric value treatment ((Depth (cm), Height (cm), Width (cm)). Since ‘dimensions’ has the same attributes in it, extract the data from the string column to replace wherever NA. And after that, imputing with either mean or median where NAs are still present (median opted since there are highly skewed outliers. shown below in last sub-point)
- Dimensions is a very dynamic variable with the required info at different positions of the column. So extracting the dimensions is done from the end of string based on locations of the brackets of the last dimension-set. Ex: extract (1cm x 2cm x 5cm) from the garbage values ‘..2cm..1cm x 2cm…. (1cm x 2cm x 5cm)’.
- I have used a series of steps with multiple user defined functions to avoid using stringi package (This was very difficult to achieve!!!). Steps involved are extracting the start and end locations of the braces (and extract into a column by extracting values from str_locate_all() into a list and extracting only the relevant values which are present towards the end of each nested list and a converting integer(0) to NAs using applyNAToStr() function in the code), then use str_sub() to extract the dimensions, replace garbage values, split based on ‘x’, and trim whitespaces, which provides a list of lists
- The requirement of creating a new column in the assignment is also done here
- Extract the Height, Width, Depth respectively using sapply(“[”) and relevant position in the list (e.g. 1,2,3) and convert to double
- Wherever original columns have missing values, relevant non-missing values are replaced across each of the 3 columns (Height, Width, Depth)
- As seen by hist(), there are large outliers in both Height and Width, hence median is used to impute the missing values instead of mean
- All numeric columns are checked for special values like Inf, -Inf and NAN. There are no special values in the dataset
- Additional comments in code chunks
#Missing value identification and treatment
getMode <- function(v) #function to get mode as impute from Hmisc didn't give the right mode as confirmed by count()
{ uniqv <- unique(v)
uniqv[which.max(tabulate(match(v, uniqv)))]}
#Check missing value for DateMade and DateAcquired
mergedArt %>% count(DateMade, sort = TRUE) %>% head(1)#Mode is '1971/01/01' which can be used for missing values in DateMade
mergedArt$DateMade[is.na(mergedArt$DateMade)] <- getMode(mergedArt$DateMade) #mode is 1971-01-01
mergedArt %>% count(DateAcquired, sort = TRUE) %>% head(1) #mode is 2013-10-24. Many items were acquired that day
mergedArt$DateAcquired[is.na(mergedArt$DateAcquired)] <- getMode(mergedArt$DateAcquired)
#Check missing values for categorical columns
mergedArt %>% count(Medium, sort = TRUE) %>% head(1) #mode
mergedArt[is.na(mergedArt$Medium),]$Medium <- getMode(mergedArt$Medium) #'Gelatin silver print' is the mode with ~6.7k obs
mergedArt %>% count(Dimensions, sort= TRUE) %>% head(1) #mode
mergedArt[is.na(mergedArt$Dimensions),]$Dimensions <- getMode(mergedArt$Dimensions)
#As 'Depth (cm)' has more than 50% missing values, the column can actually be excluded,
#but this has to be done before ruling out statistical significance and business rules.
#Since its a numeric dimension column, i am assuming its of importance and retain it. WIll be used with na.rm=TRUE
#Wiki QID & ULAN on the other hand has >60% missing and doesn't have any pattern as its an ID column,
#But since its an important attribute of the artist, the columns are not excluded from the dataset
#Numeric missing value treatment (Depth (cm), Height (cm), Width (cm)). Since 'dimensions' has the same attributes in it, extract the data from the string column to replace wherever NA. And after that impute with either mean or median
#Dimensions is a very dynamic variable with the required info at different positions of the column. So extract the dimensions from the end of string based on locations of the brackets of the last dimension-set. Ex: extract (1cm x 2cm x 5cm) from the garbage values '..2cm..1cm x 2cm.... (1cm x 2cm x 5cm)'
#I have used a series of steps with multiple user defined functions to avoid using stringi package
applyNAToStr<- function(x) #function to apply NA wherever the penultimate function below creates integer(0) values, i.e. blank values, thus allowing conversion to column (DimensionsStart, DimensionsEnd) to add back to the dataset
{if(length(x) == 0)
return (NA)
return (x)}
mergedArt$DimensionsStart <- mergedArt$Dimensions %>%
str_locate_all(., pattern = "[(]") %>% lapply(function(x) x[,1]) %>%
lapply(function(x) x[length(x)]) %>% sapply(applyNAToStr) #Apply a series of user defined functions to extract the start position of the required dimensions
mergedArt$DimensionsEnd <- mergedArt$Dimensions %>%
str_locate_all(., pattern = "[)]") %>% lapply(function(x) x[,1]) %>%
lapply(function(x) x[length(x)]) %>% sapply(applyNAToStr) #End position of the required dimensions
mergedArt$newDim <- mergedArt$Dimensions %>%
str_sub(start = mergedArt$DimensionsStart, end = mergedArt$DimensionsEnd) %>%
str_replace("[(]", "") %>% str_replace("[)]", "") %>% str_replace("cm", "") %>%
str_split("[x×]") %>% sapply(str_trim, side = c("both")) #Extract the dimensions dynamically from the column
mergedArt$newHeight<-sapply(mergedArt$newDim, "[", 1) %>% as.double() #Extract Height into a new temporary Dimension column
NAs introduced by coercion
mergedArt$newWidth<-sapply(mergedArt$newDim, "[", 2) %>% as.double() #Extract Width into a new temporary Dimension column
NAs introduced by coercion
mergedArt$newDepth<-sapply(mergedArt$newDim, "[", 3) %>% as.double() #Extract Depth into a new temporary Dimension column
NAs introduced by coercion
mergedArt <- mergedArt %>% mutate(`Height (cm)` = if_else(is.na(`Height (cm)`), newHeight, `Height (cm)`),
`Width (cm)` = if_else(is.na(`Width (cm)`), newWidth, `Width (cm)`),
`Depth (cm)` = if_else(is.na(`Depth (cm)`), newDepth, `Depth (cm)`)) %>% select(-newHeight, -newWidth, -newDepth, -DimensionsStart, -DimensionsEnd) #replace with values in new temp columns wherever dimensions are NAs and remove the temporary Dimension columns
par(mfrow=c(1, 2))
mergedArt$`Height (cm)` %>% hist() #Has a few outliers with very high values. Use median for imputing
mergedArt$`Width (cm)` %>% hist() #Has a few outliers with very high values. Use median for imputing

mergedArt[is.na(mergedArt$`Height (cm)`),]$`Height (cm)` <- median(mergedArt$`Height (cm)`, na.rm = TRUE)
mergedArt[is.na(mergedArt$`Width (cm)`),]$`Width (cm)` <- median(mergedArt$`Width (cm)`, na.rm = TRUE)
#Check for Special (NAN, Inf, -Inf) values
checkSpecialValues <- function(x)
{
is.infinite(x) | is.nan(x)
}
mergedArt %>% select_if(.predicate = is.numeric) %>% sapply(function(x) sum(checkSpecialValues(x))) #No special values
ConstituentID ObjectID Depth (cm) Height (cm)
0 0 0 0
Width (cm) ULAN
0 0
Scan II (Outliers)
- Instead of starting with the hist() or boxplot() to visualize the outliers, i went ahead with the z-score approach to identify the outliers on all numeric columns using select_if(is.numeric). The head() of the zScores dataset is quickly visualized. Values have been converted to zscores as identified from the abs(zscores)>3
- Histogram is then checked for each column to ensure normal distribution for using z-scores. Height and Width slightly resemble normal distribution while Depth doesn’t. Further analysis follows
- Height seems to have 223 outliers as identified by zScores from 1574.8cm to 215.9cm. However most of them are specific artworks which have larger height than majority of them. SO removing them would create a biased dataset, so capped can be used on Height column.
- Before that, counts of top 10 outliers is also viewed. The boxplot of the column is also viewed. Top outliers from largest to smallest is viewed (only top 10 shown using head() because of the limit)
- Capping function is created to cap to 5% and 95% quantiles. Capping is done for Height column
- Width has 253 outliers from 1705cm to 164.9cm. These all correspond to specific artworks and hence can be safely assumed to be actual data and not a documenting error. Capping is carried out on Width column to avoid creating a biased dataset by removing. Top 10 outliers are also viewed in the process (same as height)
- Depth has ~50% missing values, so it would not be highly recommended to treat the data. Depth column in the data is provided where painting with 3 dimensions need to be explicitly analyzed if needed, and hence are not deliberately captured for many artworks while creating data. Also, z-scores are not applicable since it doesn’t even remotely resemble normal distribution. My assumption is that the column would not feature in any modeling activity and hence is not capped or treated at the moment
- Boxplot is also plotted for each of the columns for visual representation of the distribution and help in treating the outliers
- ConstituentID, ULAN and ObjectID are unique identifiers and do not have a quantitative aspect, hence outlier treatment is not carried out for them.
- Additional comments in code chunks
#outlier detection and treatment:
par(mfrow = (c(1,2)))
mergedArt$`Height (cm)` %>% hist(xlab = "Height", breaks = 25) #check if Height is remotely similar to normal distribution, to use z-scores. It slightly resembles normal distribution, so we will go ahead with z-scores
zScores<-mergedArt %>% select_if(.predicate = is.numeric) %>% scores(type = "z") #Create z scores summary for all numeric values. Individual columns focused on next
zScores %>% head(5)
#'Height' has 223 outliers as shown using z scores from 1574.8cm to 215.9cm.
#'However most are specific artworks so excluding them would create a biased data. so use capping to quartile
mergedArt$`Height (cm)` %>% boxplot(las=2, xlab = "Height") #Explore the outliers using boxplot

mergedArt %>% count(`Height (cm)`, sort = TRUE) %>% arrange(-`Height (cm)`) %>% head(10) #Identify the value count of top 10 outlier values
zHeight <- scores(mergedArt$`Height (cm)`, type="z")
mergedArt$`Height (cm)`[which(abs(zHeight)>3)] %>% as.data.frame() %>% set_names(c("outl_Height")) %>%
arrange(-outl_Height) %>% head(10) #top 10 largest outlier values
cap <- function(x){ #function to cap to nearest non-outlier value
quantiles <- quantile( x, c(.05, 0.25, 0.75, .95 ), na.rm = TRUE )
x[ x < quantiles[2] - 1.5*IQR(x, na.rm = TRUE) ] <- quantiles[1]
x[ x > quantiles[3] + 1.5*IQR(x, na.rm = TRUE) ] <- quantiles[4]
x}
mergedArt$`Height (cm)` <- mergedArt$`Height (cm)` %>% cap()
#outliers in 'Width'. There are 253 outliers from 1705cm to 164.9cm. All specific artworks except for 3.
#Cap the outliers to avoid biased data
mergedArt$`Width (cm)` %>% hist(xlab = "Width") #Check distribution to ensure that it remotely resembles normal distribution to apply z-scores
mergedArt$`Width (cm)` %>% boxplot(las=2, xlab = "Width") #Explore the outliers using boxplot

zWidth <- scores(mergedArt$`Width (cm)`, type="z")
mergedArt$`Width (cm)`[which(abs(zWidth)>3)] %>% as.data.frame() %>% set_names(c("outl_Width")) %>%
arrange(-outl_Width) %>% head(10) #top 10 largest outlier values
mergedArt$`Width (cm)` <- mergedArt$`Width (cm)` %>% cap()
#outliers in 'Depth'. This column has more than 50% NAs and most of the remaining values are 0 (as
#most of the painting have a depth ~0). There is no benefit of treating outliers for this column
#This column is available in case artworks with 3 dimensions need to be analyzed explicitly
mergedArt$`Depth (cm)` %>% hist(xlab = "Depth")#No outliers using z-scores since the data is not normally distributed.
mergedArt$`Depth (cm)` %>% boxplot(xlab = "Depth") #Using Tukey's emthod there are many outliers that are detected. But, since most of the data is either NA or 0, outlier detection wont make sense for this column

LS0tDQp0aXRsZTogIk1BVEgyMzQ5IERhdGEgV3JhbmdsaW5nIg0KYXV0aG9yOiAiUkFWSSBNRUxXWU4gQVJBTkhBIDogUzM4NTIwNTIiDQpzdWJ0aXRsZTogQXNzaWdubWVudCAyDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCiMjIFJlcXVpcmVkIHBhY2thZ2VzIA0KDQpCZWxvdyBpcyB0aGUgbGlzdCBvZiBwYWNrYWdlcyB1c2VkIGluIHRoZSBwcm9qZWN0Lg0KDQpgYGB7ciBQYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNQYWNrYWdlcyByZXF1aXJlZCBmb3IgbXkgY29kZQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkob3V0bGllcnMpDQpsaWJyYXJ5KGZvcmVjYXN0KQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoSG1pc2MpDQpgYGANCg0KDQojIyBFeGVjdXRpdmUgU3VtbWFyeSANCg0KRGF0YSBwcmUtcHJvY2Vzc2luZyBpcyBhbiBpbXBvcnRhbnQgc3RhZ2Ugb2YgdGhlIGRhdGEgYW5hbHlzaXMgbGlmZWN5Y2xlIHdoaWNoIGlzIGRvbmUgcHJpbWFyaWx5IGFsb25nIHdpdGggZGF0YSBpbXBvcnQgYW5kIGJlZm9yZSBtb2RlbGluZy9tYWNoaW5lIGxlYXJuaW5nL3Zpc3VhbCByZXBvcnRzLiBJdCBpcyBub3QgbmVzc2VzYXJpbHkgY2FycmllZCBvdXQgaW4gdGhlIHNhbWUgb3JkZXIgYnV0IGZvbGxvd3MgYSBoaWdoIGxldmVsIG9yZGVyIG9mIGRhdGEgcHJlcGVyYXRpb24gc3RhZ2VzLiAgDQoNCioqQmVsb3cgYXJlIHNvbWUgb2YgdGhlIGltcG9ydGFudCBzdGFnZXM6KiogIA0KKioxLiBQcm9ibGVtIGRlZmluaXRpb24gYnkgZGF0YSA6KiogICANClwgICBcICAgXCAgIFwgICBcICAtIFRoZSBwcm9ibGVtIGFyZWEgaXMgZGVmaW5lZCBhdCB0aGlzIHBvaW50IHdoaWNoIHdpbGwgZm9jdXMgb24gdGhlIGRhdGEgcmVxdWlyZWQgdG8gc29sdmUgdGhlIHByb2JsZW0vcHJvYmxlbS1zcGFjZS4gSXQgd2lsbCBhbHNvIGhlbHAgaW4gdW5kZXJzdGFuZGluZyB0aGUgdm9pZCBvZiBjZXJ0YWluIGNyaXRpY2FsIGRhdGEgd2hpY2ggY2FuIHN1cHBvcnQgY3JlYXRpb24gb2YgdGhlIHNhbWUgZnJvbSBleGlzdGluZyBkYXRhLiBJdCBpcyBhbHNvIGltcG9ydGFudCB0byBpcm9uIG91dCBkZXRhaWxzIGFib3V0IHN0b3JhZ2UgYW5kIGFuYWx5dGljYWwgcGxhdGZvcm1zIHRoYXQgYXJlIG5lY2Vzc2FyeSB0byBjYXJyeSBvdXQgdGhlIGFuYWx5c2lzICANClwgICBcICAgXCAgIFwgICBcICAtICpJIGhhdmUgY2FycmllZCBvdXQgdGhpcyBzdGVwcyBmaXJzdCB0byB1bmRlcnN0YW5kIHRoZSBkYXRhc2V0cyBuZWVkZWQgZm9yIHRoZSBhbmFseXNpcy4gMiBkYXRhc2V0cyBhcmUgaW1wb3J0ZWQgaW50byBSIGFuZCBtZXJnZWQgZm9yIGhvbGlzdGljIGFuYWx5c2lzIG9mIGFsbCB0aGUgbXVzZXVtJ3MgYXJ0d29ya3MgYW5kIGFydGlzdHMqICANCioqMi4gRGF0YSBpbXBvcnQ6KiogIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gVGhpcyBpcyB0aGUgc3RhZ2Ugb2YgYnJpbmdpbmcgYWxsIHRoZSBuZWNlc3NhcnkgZGF0YSBvbnRvIDEgcGxhdGZvcm0gdG8gYmUgcHJvY2Vzc2VkIGZ1cnRoZXIgaW50byBhIGZpbmFsIGFuYWx5dGljYWwgZGF0YXNldCBvciBtdWx0aXBsZSBkYXRhc2V0cyAgDQpcICAgXCAgIFwgICBcICAgXCAgLSBEYXRhIGlzIHVzdWFsbHkgc3RvcmVkIGludG8gZGF0YWJhc2VzLCB3YXJlaG91c2VzIG9yIGxvY2FsIGZsYXQgZmlsZXMgICANCiAgICBcICAgXCAgIFwgICBcICAgXCAgLSAqSSBoYXZlIGltcG9ydGVkIGZpbGVzIGRpcmVjdGx5IGZyb20gdGhlIGZpbGUgVVJMcyBvbnRvIFIgd2hlcmUgZnVydGhlciBwcm9jZXNzaW5nIHdpbGwgYmUgY2FycmllZCBvdXQqICANCioqMy4gVW5kZXJzdGFuZCBkYXRhOioqICANClwgICBcICAgXCAgIFwgICBcICAtIFRoaXMgc3RhZ2UgaW52b2x2ZXMgZXhwbG9yaW5nIHRoZSBzdHJ1Y3R1cmUgYW5kIHRoZSBjdXJyZW50IHN0YWdlIG9mIHRoZSBkYXRhIHRvIGlkZW50aWZ5IHBvc3NpYmxlIG5leHQgc3RlcHMgbmVlZGVkLiBUaGUgZGltZW5zaW9ucywgZGF0YSB0eXBlcywgc3VtbWFyeSBzdGF0aXN0aWNzLCBpbnZhbGlkIHZhbHVlcywgZGF0YSBkaWN0aW9uYXJ5LCBtZXJnZSBrZXlzIGFyZSBzb21lIG9mIHRoZSB0aGluZ3MgdG8gbG9vayBhdC4gIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gSXQgaXMgcmVjb21tZW5kZWQgdGhhdCB0aGUgYW5hbHlzdCB1bmRlcnN0YW5kIHRoZSBkYXRhIGNvbXBsZXRlbHkgYmVmb3JlIHByb2NlZWRpbmcgIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gKkkgaGF2ZSBkb25lIHRoaXMgc3RlcHMgbmV4dCwgYnV0IGhhdmUgbm90IG1vdmVkIHRvd2FyZHMgbWFuaXB1bGF0aW5nIHRoZW0ganVzdCB5ZXQuIFNvbWUgcHJlLXByb2Nlc3NpbmcgaXMgbmVlZGVkIG9uIHRoZSB3aG9sZSBkYXRhc2V0IGJlZm9yZSBtb3ZpbmcgdG93YXJkcyBpbmRpdmlkdWFsIHZhcmlhYmxlcy9vYnNlcnZhdGlvbnMqICANCioqNC4gVGlkeSBkYXRhOioqICANClwgICBcICAgXCAgIFwgICBcICAtIFRoaXMgaXMgYW4gaW1wb3J0YW50IHN0YWdlIHRvIGNsZWFuIHRoZSBkYXRhIGFuZCB0byBicmluZyBpdCB0byB0aGUgZm9ybWF0IHRoYXQgaXMgYXBwZWFsaW5nIHRvIGxvb2sgYXQsIGFuZCB0byBzdHJlYW1saW5lIGZ1cnRoZXIgYW5hbHlzaXMuICAgDQpcICAgXCAgIFwgICBcICAgXCAgLSBJbnZvbHZlcyBjb252ZXJ0aW5nIHRvIHRpZHkgZm9ybWF0IHdoaWNoIGludm9sdmVzIGNvbnZlcnRpbmcgYXR0cmlidXRlcyBpbnRvIGNvbHVtbnMsIHJvd3MgaW50byBvYnNlcnZhdGlvbnMgYW5kIHZhbHVlcyBpbnRvIGEgdW5pdCBvZiBvYnNlcnZhdGlvbiAgIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gKkkgaGF2ZSBjYXJyaWVkIHRoaXMgc3RlcHMgcmlnaHQgYWZ0ZXIgc2luY2UgdGhlcmUgd2FzIGEgbG90IG9mIHRpZHlpbmcgdGhhdCB3YXMgbmVlZGVkIG9uIHRoZSBkYXRhc2V0IGJlZm9yZSBtb3ZpbmcgdG93YXJkcyBmdXJ0aGVyIHByb2Nlc3Npbmcgb3IgYmVhdXRpZmljYXRpb24vZm9ybWF0dGluZyogICANCioqNS4gTWFuaXB1bGF0ZSBkYXRhOioqICANClwgICBcICAgXCAgIFwgICBcICAtIFRoaXMgc3RlcCBpbnZvbHZlcyBmdXJ0aGVyIGNoYW5naW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgYnkgbWVyZ2luZyBtdWx0aXBsZSBkYXRhc2V0cyB0b2dldGhlciBhbmQgY3JlYXRpbmcgYWRkaXRpb25hbCwgbWVhbmluZ2Z1bCB2YXJpYWJsZXMgYW5kIHJlbW92ZSBub24gaW1wYWN0ZnVsIHJvd3MgYW5kIHZhcmlhYmxlcyBhbmQgc3RydWN0dXJpbmcgZGF0YSBpbiBhbiBhcHBlYWxpbmcgZm9ybWF0IGZvciB2aWV3aW5nIGFzIHdlbGwgIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gKkkgaGF2ZSBjYXJyaWVkIG91dCBhIGxvdCBvZiBvcGVyYXRpb25zIGF0IHRoaXMgc3RlcHMgd2hpY2ggaW5jbHVkZSBpbmRpdmlkdWFsIHZhcmlhYmxlIG1hbmlwdWxhdGlvbnMsIGZvcm1hdHRpbmcgYW5kIHRoZW4gbWVyZ2luZyBjbGVhbiBkYXRhc2V0cyB0b2dldGhlciAoSW5pdGlhbCBzdGVwIG9mIHVuZGVyc3RhbmRpbmcgZGF0YSBoZWxwcyBvdXQgd2l0aCB0aGlzIHN0ZXApKiAgDQoqKjYuIFNjYW4gYW5kIHRyZWF0IG1pc3Npbmcvb3V0bGllciB2YWx1ZXM6KiogIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gVGhpcyBpcyBhbiBpbXBvcnRhbnQgc3RlcCB0byBlaXRoZXIgZ2V0IHJpZCBvZiBtaXNzaW5nL3NwZWNpYWwvb3V0bGllciBvYnNlcnZhdGlvbnMgb3IgcHJveHkgdGhlbSB3aXRoIGxpa2VseS9tb3N0IGNvbW1vbiB2YWx1ZXMgZnJvbSBleGlzdGluZyBkYXRhLiBTdGF0aXN0aWNhbCB0ZXN0cyBhbmQgc3VtbWFyaWVzIGFyZSB1c2VkIHRvIGNhcnJ5IG91dCB0aGlzIHN0ZXBzLiAgIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gKkltcHV0aW5nIHdpdGggbWVhbi9tZWRpYW4vbW9kZSBpcyBhIHBvcHVsYXIgb3B0aW9uLiBJIGhhdmUgc3BsaXQgdGhpcyBzZWN0aW9uIGludG8gTWlzc2luZyB2YWx1ZXMgYW5kIE91dGxpZXIgdmFsdWVzIHNlY3Rpb25zIGFuZCByZWxlYXZhbnQgdHJlYXRtZW50IGlzIGRvbmUgaW4gZGV0YWlsIGZvciBhbGwgcmVsZXZhbnQgdmFyaWFibGVzKiAgDQpcICAgXCAgIFwgICBcICAgXCAgLSAqVGhlIG1vc3QgaW1wb3J0YW50IHN0ZXAgYXQgdGhpcyBwb2ludCBpcyB0byBnbyBiYWNrIHRvIHRoZSBidXNpbmVzcyBhbmQgcmVxdWVzdCBhZGRpdGlvbmFsL2ltcHJvdmVkIGRhdGEgd2hpY2ggaXMgdGhlIGJlc3QgYXBwcm9hY2ggdG8gZGVhbCB3aXRoIHRoZSBkYXRhLCBpZiBhdmFpbGFibGUqICANCioqNy4gVHJhbnNmb3JtaW5nIGRhdGE6KiogIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gVGhpcyBzdGVwcyBpbnZvbHZlcyB0cmFuc2Zvcm1pbmcgZGF0YSBpbnRvIGZvcm1hdHMgZmF2b3JhYmxlIHRvIG1vZGVsaW5nLCBpbnRlci12YXJpYWJsZXMgcmVsYXRpb25zaGlwcywgdG8gcmVkdWNlIHNrZXduZXNzL291dGxpZXJzIG9yIHRvIGJldHRlciB1bmRlcnN0YW5kIHRoZSBkYXRhKSAgIA0KXCAgIFwgICBcICAgXCAgIFwgIC0gKkkgaGF2ZSBjYXJyaWVkIG91dCB0aGlzIHN0ZXAgbGFzdCB0byBnZXQgdGhlIGRhdGEgcmVhZHkgZm9yIHZpc3VhbGl6YXRpb24gb3IgZnVydGhlciBtb2RlbGluZyBhY3Rpdml0aWVzLiBBbGwgbnVtZXJpYyBjb2x1bW5zIGFyZSB2aWV3ZWQgdW5kZXIgdGhlIGxlbnMgb2YgbXVsdGlwbGUgdHJhbnNmb3JtYXRpb25zIGFuZCBhcHByb3ByaWF0ZSB0cmFuc2Zvcm1hdGlvbnMgYXJlIGRvbmUsIHdoZXJldmVyIGFwcGxpY2FibGUqICANCg0KDQojIyBEYXRhIFN1bW1hcnkNClRoZSBbTW9NQSBjb2xsZWN0aW9uXShodHRwczovL3d3dy5tb21hLm9yZy9jb2xsZWN0aW9uLykgd2FzIGVzdGFibGlzaGVkIGluIDE5MjkgYW5kIGlzIGEgZGlnaXRhbCBkYXRhYmFzZSBvZiBhcnR3b3JrcyBieSB2YXJpb3VzIGFydGlzdHMgYWNyb3NzIHRoZSB3b3JsZCwgYW5kIGFjcXVpcmVkIGJ5IHRoZSBtdXNldW0gYWNyb3NzIHRoZSBsYXN0IDE1MCB5ZWFycy4gVGhlIDIgZGF0YXNldHMgZmVhdHVyZSBhcnR3b3JrIGRldGFpbHMgYW5kIGFydGlzdCBkZXRhaWxzIHJlc3BlY3RpdmVseSBhbmQgY29udGFpbiBhIHZhcmlldHkgb2YgYXR0cmlidXRlcyBvdXRsaW5lZCBhbmQgc3VtbWFyaXplZCBiZWxvdy4gQm90aCBhcmUgaW4gcHVibGljIGRvbWFpbiB1bmRlciBhIENDMCBMaWNlbnNlLg0KDQoqKndvcmtfYXJ0cyoqIChDb2x1bW5zOiAyOSwgUm93czogMTM4LDE4NSwgVVJMOiBbYXJ0d29ya3MuY3N2XShodHRwczovL21lZGlhLmdpdGh1YnVzZXJjb250ZW50LmNvbS9tZWRpYS9NdXNldW1vZk1vZGVybkFydC9jb2xsZWN0aW9uL21hc3Rlci9BcnR3b3Jrcy5jc3YpKSAgDQpDb2x1bW5zOiAgIA0KKiBUaXRsZSAtICpUaXRsZSBvZiB0aGUgYXJ0d29yayAoTGV2ZWwgb2YgZGF0YSkqICAgIA0KKiBBcnRpc3QgLSAqQXJ0aXN0IG5hbWUgKG11bHRpcGxlIGFydGlzdHMgb2YgYW4gYXJ0d29yayBhcmUgY29uY2F0ZW5hdGVkIGFzIHVudGlkeSBkYXRhKSogICANCiogQ29uc3RpdHVlbnRJRCAtICpBcnRpc3QgSUQgKEFsc28gdGhlIHByaW1hcnkgam9pbiBrZXkgZm9yIEFydGlzdHMgZGF0YXNldCkqICAgDQoqIEFydGlzdEJpbyAtICpBcnRpc3QgbmF0aW9uYWxpdHkgYW5kIGFjdGl2ZSB5ZWFycyogICANCiogR2VuZGVyIC0gKkdlbmRlciBvZiB0aGUgYXJ0aXN0KiAgIA0KKiBCZWdpbkRhdGUsICpFbmREYXRlIC0gYWN0aXZlIHllYXJzIG9mIGFydGlzdCogICANCiogTWVkaXVtIC0gKm1lZGl1bSB1c2VkIHRvIGNyZWF0ZSB0aGUgYXJ0IChwYWludCwgcHJpbnQsIHBob3RvZ3JhcGggZXRjKSogICANCiogRGltZW5zaW9ucyAtICpwaHlzaWNhbCBkaW1lbnNpb25zIG9mIHRoZSBhcnR3b3JrIChjb250YWlucyBIZWlnaHQsIFdpZHRoIGFuZCBEZXB0aCkqICAgDQoqIENyZWRpdExpbmUgLSAqUHVyY2hhc2UgbGluZSBieSB0aGUgYXJjaGl0ZWN0IChwdXJjaGFzZSwgZ2lmdCwgcGFydGlhbCBnaWZ0IGJ5IGEgcGVyc29uL29yZ2FuaXphdGlvbiBldGMpKiAgIA0KKiBDbGFzc2lmaWNhdGlvbiAtICpUeXBlIG9mIGFydHdvcmsgKEFyY2hpdGVjdHVyZSwgZGVzaWduLCBpbGx1c3RyYXRlZCBib29rIGV0YykqICAgDQoqIERlcGFydG1lbnQgLSAqSGlnaGVyIGxldmVsIGNsYXNzaWZpY2F0aW9uIChhcmNoaXRlY3R1cmUsIGRyYXdpbmcsIGZpbG0sIHBhaW50aW5nIGV0YykqICAgDQoqIERhdGVBY3F1aXJlZCAtICpEYXRlIG9mIGFjcXVpcmluZyBieSB0aGUgTXVzZXVtKiAgIA0KKiBVUkwsIFRodW1ibmFpbFVSTCAtICpsaW5rIHRvIGFydHdvcmsgYW5kIHRodW1ibmFpbCogICANCiogUGh5c2ljYWwgZGltZW5zaW9ucyAoY20pIC0gKkNpcmN1bWZlcmVuY2UsIERlcHRoLCBEaWFtZXRlciwgSGVpZ2h0LCBMZW5ndGgsIFdlaWdodCwgV2lkdGggZXRjKSogICANCg0KKipBcnRpc3RzKiogKENvbHVtbnM6IDksIFJvd3M6IDE1LDMzMywgVVJMOiBbQXJ0aXN0cy5jc3ZdKGh0dHBzOi8vbWVkaWEuZ2l0aHVidXNlcmNvbnRlbnQuY29tL21lZGlhL011c2V1bW9mTW9kZXJuQXJ0L2NvbGxlY3Rpb24vbWFzdGVyL0FydGlzdHMuY3N2KSkgIA0KQ29sdW1uczogICANCiogQ29uc3RpdHVlbnRJRCAtICpQcmltYXJ5IGtleSBhbmQgSUQgb2YgdGhlIGFydGlzdCogICANCiogRGlzcGxheU5hbWUgLSAqQXJ0aXN0IG5hbWUqICAgDQoqIEFydGlzdEJpbywgKk5hdGlvbmFsaXR5LCBHZW5kZXIsIEJlZ2luRGF0ZSwgRW5kRGF0ZSAtIHNhbWUgYXMgYWJvdmUqICAgDQoqIFdpa2kgUUlEIC0gICpVbmlxdWUgaWRlbnRpZmllciBpbiBXaWtpZGF0YSBmb3IgdGhlIGFydGlzdCAoQWxzbyBrbm93biBhcyBXaWtpZGF0YSBpdGVtIGlkZW50aWZpZXIpKiAgIA0KKiBVTEFOIC0gKlRoaXMgaXMgdGhlIEdldHR5IFVMQU4gSUQgZm9yIHRoZSBhcnRpc3QgKGNhbiBiZSB1c2VkIHRvIHNlYXJjaCBpbiBHZXR0eSBkYXRhYmFzZXMqICAgDQoNCioqQnJpZWYgU3VtbWFyeSBvZiBzdGVwcyB0YWtlbiBhbmQgYSBmZXcgZWFybHkgZmluZGluZ3M6KiogIA0KMS4gSSBoYXZlIGxvb2tlZCBhdCB0aGUgKmhlYWQoKSogYW5kICpzdHIoKSogKihzdHIgc2hvd24gaW4gbmV4dCBzZWN0aW9uKSogb2YgZWFjaCBkYXRhc2V0ICooYW5kIGV4cGxvcmVkIHRoZSBlbnRpcmUgZGF0YXNldCkqIHRvIHVuZGVyc3RhbmQgdGhlIHZhcmlhYmxlcywgZGF0YSB0eXBlcyBhbmQgaWRlbnRpZnkgaW1tZWRpYXRlIG5leHQgc3RlcHMgICANCjIuIEkgaGF2ZSBsb29rZWQgYXQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSB0byBpZGVudGlmeSBqb2luIGtleXMsIGxldmVsIG9mIGRhdGEgYW5kIHRoZSBwcmUtcHJvY2Vzc2luZyBzdGVwcyBuZWVkZWQgZm9yIGVhY2ggb2YgdGhlIGNvbHVtbnMgZm9yIGZ1cnRoZXIgYW5hbHlzaXMgKihUaGVyZSBhcmUgYSBsb3Qgb2YgY29sdW1ucyB0aGF0IG5lZWQgbWFuaXB1bGF0aW9ucyB0byBicmluZyB0byB0aGUgcmlnaHQgZm9ybWF0KSogIA0KMy4gVGhlIHdvcmtfYXJ0cyBkYXRhIGlzIHVudGlkeSB3aXRoIG11bHRpcGxlIGFydGlzdHMgKihvZiBhbiBhcnR3b3JrKSogY29uY2F0ZW5hdGVkIGluIGEgc2luZ2UgY2VsbCBhbmQgdGhlIHNhbWUgaXMgYXBwbGljYWJsZSBmb3IgZWFjaCBvZiB0aGUgYXJ0aXN0J3MgYXR0cmlidXRlIGluIHdvcmtfYXJ0cyAgDQo0LiBhcnR3b3Jrcy5jc3YgaXMgaW1wb3J0ZWQgYXMgKip3b3JrX2FydHMqKiBmb3IgcXVpY2sgYWNjZXNzIHVzaW5nIFIgc2hvcnRjdXQgZnVuY3Rpb25zICoodmFyeWluZyBmcm9tIGFydGlzdHMuY3N2KSouIGFydGlzdHMuY3N2IGlzIGltcG9ydGVkIGFzLWlzICANCjUuIGltcG9ydGVkIGNzdiBhcmUgY29udmVydGVkIHRvIGRhdGFmcmFtZSB1c2luZyBhcy5kYXRhLmZyYW1lKCkgd2hpbGUgaW1wb3J0aW5nIHVzaW5nIHJlYWRfY3N2KCkgIA0KNi4gSSBoYXZlIGlkZW50aWZpZWQgYSBmZXcgcmVkdW5kYW50IGNvbHVtbnMgd2l0aCAnKkFydGlzdEJpbyonICYgJypEaW1lbnNpb25zKicgYnV0IHRoZXkgd2lsbCBjb21lIGhhbmR5IHRvIHJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgd2hlcmV2ZXIgYXBwbGljYWJsZSAgDQo3LiBUaGVyZSBhcmUgYWxzbyBmZXcgcm93cy9jb2x1bW5zIHdpdGggYSBtYWpvcml0eSBvZiBtaXNzaW5nIHZhbHVlcy4gVGhleSB3aWxsIGJlIG1hbmlwdWxhdGVkIGFwcHJvcHJpYXRlbHkgaW4gc3Vic2VxdWVudCBzZWN0aW9ucyAgDQo4LiAqKndvcmtfYXJ0cyoqIGlzIGh1Z2UgaW4gc2l6ZSBhbmQgd2lsbCBmdXJ0aGVyIGluY3JlYXNlIGFmdGVyIHRpZHlpbmcsIHNvIGFuIHVuYmlhc2VkIHN1YnNldCB3aWxsIGJlIGFsc28gY3JlYXRlZCBpbiBzdWJzZXF1ZW50IHNlY3Rpb25zICooYmVjYXVzZSBteSBSU3R1ZGlvIGNvdWxkbid0IHByb2Nlc3Mgb3BlcmF0aW9ucyB3aXRob3V0IHRoaXMpKiAgDQo5LiBNZXJnZSBrZXkgaXMgJypDb25zdGl0dWVudElEKicgd2hpY2ggaXMgYWxzbyB0aGUgcHJpbWFyeSBrZXkgZm9yICoqYXJ0aXN0cyoqIGRhdGFmcmFtZSAgDQpcICAgXCAgIFwgICBcICAgXCAgLSAqJyoqYXJ0aXN0cyoqJyBoYXMgdW5pcXVlIGRldGFpbHMgYWJvdXQgdGhlIGFydGlzdCB3aGljaCBuZWVkIHRvIGJlIGJyb3VnaHQgaW50byB0aGUgKip3b3JrX2FydHMqKiBkYXRhc2V0IHVzaW5nIHRoZSBtZXJnZSBvcGVyYXRpb24qICANCg0KYGBge3IgRGF0YSBJbXBvcnQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojRGF0YXNldCAxOg0Kd29ya19hcnRzIDwtIHJlYWRfY3N2KCJodHRwczovL21lZGlhLmdpdGh1YnVzZXJjb250ZW50LmNvbS9tZWRpYS9NdXNldW1vZk1vZGVybkFydC9jb2xsZWN0aW9uL21hc3Rlci9BcnR3b3Jrcy5jc3YiKSAlPiUgYXMuZGF0YS5mcmFtZShyb3cubmFtZXMgPSBOVUxMKQ0KcHJpbnQoaGVhZCh3b3JrX2FydHMsIG4gPSA1KSkNCiNEYXRhc2V0IDI6DQphcnRpc3RzIDwtIHJlYWRfY3N2KCJodHRwczovL21lZGlhLmdpdGh1YnVzZXJjb250ZW50LmNvbS9tZWRpYS9NdXNldW1vZk1vZGVybkFydC9jb2xsZWN0aW9uL21hc3Rlci9BcnRpc3RzLmNzdiIpICU+JSBhcy5kYXRhLmZyYW1lKHJvdy5uYW1lcyA9IE5VTEwpDQpwcmludChoZWFkKGFydGlzdHMsIG4gPSA1KSkNCmBgYA0KDQojIyBVbmRlcnN0YW5kIDEgKEV4cGxvcmUgb25seSkgDQoNCioqU3VtbWFyeSBvZiBzdGVwcyBhbmQgcmVxdWlyZWQgZGF0YSB0eXBlIGNvbnZlcnNpb25zOioqICANCioqU3RlcHM6KiogIA0KMS4gSSBoYXZlIGNoZWNrZWQgdGhlIHN0cnVjdHVyZSBvZiBkYXRhc2V0cyB1c2luZyBzdHIoKSBhbmQgdHlwZSgpICAgDQoyLiBJIGhhdmUgZXhwbG9yZWQgaW5kaXZpZHVhbCBjb2x1bW5zIGFuZCBpZGVudGlmaWVkIG5lY2Vzc2FyeSBkYXRhIHR5cGUgY29udmVyc2lvbnMgcmVxdWlyZWQgKG91dGxpbmVkIGJlbG93KS4gVGhlc2Ugd2lsbCBiZSBkb25lIHBvc3QgdGlkeWluZyBkYXRhIGluIHRoZSBuZXh0IHN0ZXApICAgDQozLiBUaGUgY29sU3VtcygpIG9mIE5BcyBzaG93IGNvbHVtbnMgd2l0aCBtaXNzaW5nIHZhbHVlcy4gQ29sdW1ucyB3aXRoIGEgbWFqb3JpdHkgb2YgbWlzc2luZyB2YWx1ZXMgY2FuIGJlIHJlbW92ZWQgYWZ0ZXIganVkZ2luZyBpdHMgc2lnbmlmaWNhbmNlIGZvciBmdXJ0aGVyIGFuYWx5c2lzICANCjQuIGNvdW50IG9mIHVuaXF1ZSB2YWx1ZXMgaXMgdXNlZCB0byBpZGVudGlmeSBwb3NzaWJsZSBmYWN0b3IgY29sdW1ucyBtZW50aW9uZWQgYmVsb3cgIA0KDQoqKkNvbnZlcnNpb25zIG5lZWRlZCAodG8gYmUgZXhlY3V0ZWQgYWZ0ZXIgdGlkeWluZyBhbmQgdW5uZXN0aW5nIGNvbHVtbnMpOioqICAgDQoxLiBDb25zdGl0dWVudElELCBCZWdpbkRhdGUsIEVuZERhdGUgLSBjb252ZXJ0IGZyb20gY2hyIHRvIGludCAgDQoyLiBHZW5kZXIgLSBjb252ZXJ0IGZyb20gY2hyIHRvIGZhY3RvciAoYXBwbHkgbG93ZXIgY2FzZSBjb252ZXJzaW9uKSwgKGFkZCBmYWN0b3IgbGFiZWxzKSAgICANCjMuIE5hdGlvbmFsaXR5LCBDbGFzc2lmaWNhdGlvbiwgRGVwYXJ0bWVudCwgQ2F0YWxvZ3VlZCAtIGNvbnZlcnQgZnJvbSBjaHIgdG8gZmFjdG9yICANCjQuIENvbHVtbnMgd2l0aCBtYWpvcml0eSBOQXMgKCJDaXJjdW1mZXJlbmNlIChjbSkiLCAiRGlhbWV0ZXIgKGNtKSIsICJMZW5ndGggKGNtKSIsICJXZWlnaHQgKGtnKSIsICJTZWF0IEhlaWdodCAoY20pIiwiRHVyYXRpb24gKHNlYy4pIikgIA0KDQpgYGB7ciBFeHBsb3JlIERhdGF9DQojVW5kZXJzdGFuZCB3b3JrX2FydHMNCnN0cih3b3JrX2FydHMpDQojU3VtbWFyeSBvZiBkYXRhIHR5cGVzLiBOZWVkIGRhdGEgdHlwZSBjb252ZXJzaW9ucyAoc2VlIHN1bW1hcnkgYWZ0ZXIgY29kZSBjaHVjaykNCnNhcHBseSh3b3JrX2FydHMsIGNsYXNzKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBzZXRfbmFtZXMoImNvbFR5cGUiKSAlPiUgY291bnQoY29sVHlwZSkNCndvcmtfYXJ0cyAlPiUge2NvbFN1bXMoaXMubmEoLikpfSAjY2hlY2sgY291bnQgTkFzIHBlciBjb2x1bW4NCndvcmtfYXJ0cyAlPiUgc2FwcGx5KGZ1bmN0aW9uKHgpIGxlbmd0aCh1bmlxdWUoeCkpKSAjY2hlY2sgdW5pcXVlIHBlciBlYWNoIGNvbHVtbnMNCg0KI1VuZGVyc3RhbmQgYXJ0aXN0cw0Kc3RyKGFydGlzdHMpDQojU3VtbWFyeSBmZiBkYXRhIHR5cGVzDQpzYXBwbHkoYXJ0aXN0cywgY2xhc3MpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHNldF9uYW1lcygiY29sVHlwZSIpICU+JSBjb3VudChjb2xUeXBlKQ0KYXJ0aXN0cyAlPiUge2NvbFN1bXMoaXMubmEoLikpfSAjY2hlY2sgY291bnQgTkFzIHBlciBjb2x1bW4NCmFydGlzdHMgJT4lIHNhcHBseShmdW5jdGlvbih4KSBsZW5ndGgodW5pcXVlKHgpKSkgI2NoZWNrIHVuaXF1ZSBwZXIgZWFjaCBjb2x1bW4NCg0KYGBgDQoNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSAoYW5kIElJKQ0KDQoqKlRoZSB3b3JrX2FydHMgZGF0YXNldCBpcyB1bnRpZHkgYmVjYXVzZToqKiAgDQoxLiBJdCBoYXMgbXVsdGlwbGUgdmFsdWVzIGNvbmNhdGVuYXRlZCBpbnRvIGEgc2luZ2xlIGNlbGwsIHNvIGVhZWNoIHJvdyBkb2Vzbid0IGNvcnJlc3BvbmQgdG8gYSBzaW5nbGUgb2JzZXJ2YXRpb24gIA0KMi4gTXVsdGlwbGUgY29sdW1ucyBhcmUgcHJlc2VudCBpbiBhIHNpbmdsZSBjb2x1bW4sIGV4OiBhcnRpc3RCaW8gYW5kIERpbWVuc2lvbnMgKHdoaWxlIGluZGl2aWR1YWwgY29sdW1ucyBhcmUgcHJlc2VudCwgdGhleSB3aWxsIGJlIHVzZWQgZm9yIGRhdGEgdHJlYXRtZW50IGJlZm9yZSBiZWluZyB0aWRpZWQpICANCjMuIEZldyBjb2x1bW5zIGxpa2UgRGltZW5zaW9ucyBjb2x1bSBpcyBkaWZmaWN1bHQgdG8gdW5kZXJzdGFuZCBkdWUgdG8gYSBsb3Qgb2YgZ2FyYmFnZSB2YWx1ZXMgd2l0aCBpbXBvcnRhbnQgaW5mb3JtYXRpb24gIA0KDQoqKlRoZSBiZWxvdyBzdGVwcyBhcmUgdGFrZW4gKG1vc3QgY29ycmVzcG9uZCB0byB0aWR5aW5nIGRhdGEpOioqICANCjEuIHN0ckZ1bmMgaXMgYSB1c2VyIGRlZmluZWQgZnVuY3Rpb24gdXNlZCB0byBpZGVudGlmeSBudW1iZXIgb2YgY29uY2F0IHZhbHVlcyBpbiBhcnRpc3QgYXR0cmlidXRlcyBsaWtlIEFydGlzdCwgQ29uc3RpdHVlbnRJRCwgTmF0aW9uYWxpdHksIEJlZ2luRGF0ZSwgRW5kRGF0ZSBhbmQgR2VuZGVyLiBOYXRpb25hbGl0eSBpcyB1c2VkIGFzIGFuIGFuY2hvciBhbmQgY29uY2F0IHZhbHVlcyBvZiAzIG9yIG1vcmUgYXJlIGV4Y2x1ZGVkIChEYXRhIGlzIGFscmVhZHkgaHVnZSBhbmQgUiB3YXMgYWxyZWFkeSBhdCB0aGUgYnJlYWtpbmcgcG9pbnQgZm9yIGV4ZWN1dGluZyBpbiBteSBtYWNoaW5lKSAgIA0KXCAgIFwgICBcICAgXCAgIFwgICotIFRoZSByZXF1aXJlbWVudCBvZiBjcmVhdGluZyBhIG5ldyBjb2x1bW4gaW4gdGhlIGFzc2lnbm1lbnQgaXMgZG9uZSBoZXJlKg0KMi4gQSBmaWx0ZXIgaXMgdXNlZCBuZXh0IHRvIGZpbHRlciBvdXQgYXJ0d29ya3Mgd2l0aCB0aXRsZSBsb25nZXIgdGhhbiA1MCBjaGFyYWN0ZXJzICh1c2luZyBuY2hhcigpKSAgDQozLiBUZXh0LWhlYXZ5LCB1bmltcG9ydGFudC9yZWR1bmRhbnQgY29sdW1ucyBsaWtlIEFydGlzdEJpbywgQ3JlZGl0TGluZSwgVVJMIGFuZCBUaHVtYm5haWxVUkwgYXJlIHJlbW92ZWQgYXQgdGhpcyBwb2ludCB0byBtYWtlIHRoZSBkYXRhIGxlc3NlciBpbiBzaXplICANCjQuIENvbmNhdCBjb2x1bW5zIGFyZSB1bm5lc3RlZCBpbnRvIGluZGl2aWR1YWwgcm93cyB1c2luZyB1bm5lc3QoKSBmdW5jdGlvbiB3aGljaCBjb252ZXJ0cyBpbmRpdmlkdWFsIGl0ZW0gaW50byBpdHMgb3duIHJvdyAoVGhpcyBlc3NlbnRpYWxseSBkaWQgd2hhdCBzZXBhcmF0ZStnYXRoZXIgb3BlcmF0aW9ucyB3b3VsZCBkbykgIA0KNS4gRGlzdGluY3Qgb2YgdGhlIGRhdGFzZXQgaXMgdXNlZCB0byBnZXQgcmlkIG9mIGR1cGxpY2F0ZSBvYnNlcnZhdGlvbnMsIGlmIGFueSAgDQo2LiBDb2x1bW5zIHdpdGggPjkwJSBOQXMgd2VyZSByZW1vdmVkIHRvIHJlZHVjZSBkYXRhIHNpemUgKGFzc3VtaW5nIG5vIGNyaXRpY2FsIGFuYWx5c2lzIG9uIHRoZXNlIGRpbWVuc2lvbnMgYXQgdGhlIG1vbWVudCkuIENPbHVtbnMgcmVtb3ZlZCBhcmUgQ2lyY3VtZmVyZW5jZSwgRGlhbWV0ZXIsIExlbmd0aCwgV2llZ3RoLCBTZWF0IEhlaWdodCwgRHVyYXRpb24gIA0KNy4gQSBsYXJnZSBzYW1wbGUgb2YgMjAwMDAgb2JzZXJ2YXRpb25zIGlzIGV4dHJhY3RlZCBmb3IgZnVydGhlciBhbmFseXNpcyB1c2luZyBhIHJhbmRvbSBzYW1wbGUgKFRoaXMgaXMgZG9uZSB0byBhdm9pZCBoZWF2eSBsYWcgZm9yIGZ1cnRoZXIgZXhlY3V0aW9ucy4gQXNzdW1wdGlvbiBoZXJlIGlzIHRoYXQgdGhlIHNhbXBsZSBkZXJpdmVkIGV4aGliaXRzIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBvcmlnaW5hbCBkYXRhc2V0KSAgDQo4LiBzZXQuc2VlZCg1KSBpcyB1c2VkIGJlbG93IGZvciByZXByb2R1Y2libGUgcmVzdWx0cyBhbmQgZGF0YXNldCBpcyBzYXZlZCBhcyB3YV9maWx0ICANCjkuIE5vdyB0aGUgZGF0YSBpcyBpbiBhIHRpZHkgZm9ybWF0LCBzbyBmdXJ0aGVyIG9wZXJhdGlvbnMgd2lsbCBmb2N1cyBvbiB0aGUgZGF0YSB0eXBlLCBjb25zaXN0ZW50IGxldmVscyBhbmQgc3RyaW5nIGNsZWFuaW5nIGFjdGl2aXRpZXMgIA0KMTAuIEFkZGl0aW9uYWwgY29tbWVudHMgaW4gY29kZSBjaHVua3MgIA0KDQoNCmBgYHtyIFRpZHkgYW5kIE1hbmlwdWxhdGV9DQojVGlkeSB1cCB3b3JrX2FydHM6DQojRmlsdGVyLWluIG9ic2VydmF0aW9ucyB3aXRoIGNvbmNhdCB2YWx1ZXMgbGVzcyB0aGFuIDMgYnkgdXNpbmcgY3VzdG9tIGZ1bmN0aW9uIGFuZCBjcmVhdGluZyANCiNjb2x1bW4gJ2ZsYWcnIHRvIGZpbHRlci4gVGhpcyBpcyBkb25lIHRvIHJlZHVjZSB0aGUgZGF0YSBzaXplDQpzdHJGdW5jIDwtIGZ1bmN0aW9uKHgpIHtzdHJfY291bnQocGF0dGVybiA9ICJbKV0gWyhdIiwgc3RyaW5nID0geCl9DQp3b3JrX2FydHM8LXdvcmtfYXJ0cyAlPiUgbXV0YXRlKGZsYWcgPSBzdHJGdW5jKE5hdGlvbmFsaXR5KSkgJT4lIGZpbHRlcihmbGFnPDMpICU+JSBzZWxlY3QoLWZsYWcpDQoNCiNSZW1vdmUgY29sdW1ucyB3aXRoIHVuaW1wb3J0YW50IGFuZCB0ZXh0LWhlYXZ5IHN0cmluZyBkYXRhIGxpa2UgQXJ0aXN0QmlvLCBDcmVkaXRMaW5lLCBVUkwsIFRodW1ibmFpbFVSTA0KI0NyZWF0ZSBjb2wgJ25jaGFyJyB0byBmaWx0ZXIgb3V0ICdUaXRsZScgd2l0aCBjaGFyYWN0ZXJzIDw9IDUwLiBUaGlzIGlzIGRvbmUgdG8gcmVkdWNlIHRoZSBkYXRhIHNpemUgKFJlY29tbWVuZGVkIGJ5IFRBKQ0Kd29ya19hcnRzIDwtIHdvcmtfYXJ0cyAlPiUgc2VsZWN0KC1jKEFydGlzdEJpbywgQ3JlZGl0TGluZSwgVVJMLCBUaHVtYm5haWxVUkwpKSAgJT4lICANCiAgbXV0YXRlKG5jaGFyID0gbmNoYXIod29ya19hcnRzJFRpdGxlKSkgICU+JSBmaWx0ZXIobmNoYXI8PTUwKSAlPiUgc2VsZWN0KC1uY2hhcikNCg0KI1VubmVzdCBhbmQgY29udmVydCBtdWx0aXBsZSBjb2x1bW5zIGluIHRoZSBkYXRhIHRvIGxvbmcgZm9ybWF0IChEbyBiZWZvcmUgY2hhbmdpbmcgZGF0YSB0eXBlcyBhcyBjbGVhbiB1cCANCiNpcyBuZWVkZWQgYmVmb3JlIHR5cGUgY2hhbmdlKS4gJ3JlZ0V4JyBleHByZXNzaW9ucyBhcmUgdXNlZCB0byBzcGxpdCBhbmQgdW5uZXN0IGJhc2VkIG9uIGVhcmxpZXIgYW5hbHlzaXMNCndvcmtfYXJ0cyA8LSB3b3JrX2FydHMgJT4lIG11dGF0ZShBcnRpc3QgPSBzdHJfc3BsaXQocGF0dGVybiA9ICIsICIsIHN0cmluZyA9IEFydGlzdCkpICU+JSB1bm5lc3QoQXJ0aXN0KSAlPiUNCiAgbXV0YXRlKENvbnN0aXR1ZW50SUQgPSBzdHJfc3BsaXQocGF0dGVybiA9ICIsICIsIHN0cmluZyA9IENvbnN0aXR1ZW50SUQpKSAlPiUgdW5uZXN0KENvbnN0aXR1ZW50SUQpICU+JQ0KICBtdXRhdGUoTmF0aW9uYWxpdHkgPSBzdHJfc3BsaXQocGF0dGVybiA9ICJbKV0gWyhdIiwgc3RyaW5nID0gTmF0aW9uYWxpdHkpKSAlPiUgdW5uZXN0KE5hdGlvbmFsaXR5KSAlPiUNCiAgbXV0YXRlKEJlZ2luRGF0ZSA9IHN0cl9zcGxpdChwYXR0ZXJuID0gIlspXSBbKF0iLCBzdHJpbmcgPSBCZWdpbkRhdGUpKSAlPiUgdW5uZXN0KEJlZ2luRGF0ZSkgJT4lDQogIG11dGF0ZShFbmREYXRlID0gc3RyX3NwbGl0KHBhdHRlcm4gPSAiWyldIFsoXSIsIHN0cmluZyA9IEVuZERhdGUpKSAlPiUgdW5uZXN0KEVuZERhdGUpICU+JSANCiAgbXV0YXRlKEdlbmRlciA9IHN0cl9zcGxpdChwYXR0ZXJuID0gIlspXSBbKF0iLCBzdHJpbmcgPSBHZW5kZXIpKSAlPiUgdW5uZXN0KEdlbmRlcikgJT4lIGRpc3RpbmN0KCkNCg0KI2Ryb3AgY29sdW1ucyB3aGljaCBjb250YWluIG1vc3RseSBOQSB2YWx1ZXMuIDYgY29sdW1ucyBoYXZlIGF0IGxlYXN0IDkwJSBOQXMuIA0KI1JlbW92ZSB0aGVzZSB1c2luZyBhIHNlbGVjdCBmdW5jdGlvbiBhbmQgLWMoKQ0Kd29ya19hcnRzIDwtIHdvcmtfYXJ0cyAlPiUNCiAgc2VsZWN0KC1jKCJDaXJjdW1mZXJlbmNlIChjbSkiLCAiRGlhbWV0ZXIgKGNtKSIsICJMZW5ndGggKGNtKSIsICJXZWlnaHQgKGtnKSIsIA0KICAgICAgICAgICAgIlNlYXQgSGVpZ2h0IChjbSkiLCJEdXJhdGlvbiAoc2VjLikiKSkNCg0KI0FzIHRoZSBkYXRhIHNpemUgaXMgaGlnaCwgYSBzYW1wbGUgZGF0YSBpcyBjcmVhdGVkIGZyb20gdGhlIGRhdGFzZXQgdXNpbmcgYSBzYW1wbGVfbigpIGZ1bmN0aW9uIGZyb20gZHBseXIuDQojU2V0LnNlZWQoKSBpcyB1c2VkIGZvciByZXByb2R1Y2libGUgcmVzdWx0cywgYW5kIGEgc2FtcGxlIG9mIDIwLDAwMCByb3dzIGlzIHJhbmRvbSBjaG9zZW4gZnJvbSB0aGUgZGF0YS4NCiNBc3NpZ24gdGhlIHNhbXBsZSBkYXRhc2V0IHRvIGEgbmV3IG5hbWUgd2FfZmlsdCcNCnNldC5zZWVkKDUpDQp3YV9maWx0IDwtIHdvcmtfYXJ0cyAlPiUgc2FtcGxlX24oc2l6ZSA9IDIwMDAwLCByZXBsYWNlID0gRikNCg0KYGBgDQoNCiMjCVVuZGVyc3RhbmQgMiAoRXhlY3V0aW9uIGFmdGVyIHRpZHlpbmcgZGF0YSkNCg0KKipUaGlzIHNlY3Rpb24gZm9jdXNlcyBvbiBkYXRhIGZvcm1hdHRpbmcgYW5kIGFjY3VyYWN5IG9mIGNvbHVtbiByZXByZXNlbnRhdGlvbnMqKiAgDQoxLiBBIHJlcGxhY2VtZW50IG9wZXJhdGlvbiBpcyBjYXJyaWVkIG91dCB0byBnZXQgcmlkIG9mIGxlYWRpbmcsIHRyYWlsaW5nICcoJyBvciAnKScgY2hhcmFjdGVycyB3aGljaCBhcmUgcmVwbGFjZWQgd2l0aCAiIi4gVGhpcyBpcyBkb25lIGZvciBOYXRpb25hbGl0eSwgQmVnaW5EYXRlLCBFbmREYXRlIGFuZCBHZW5kZXIgIA0KMi4gVXBwZXIgY2FzZSBpbiBHZW5kZXIgaXMgY29udmVydGVkIHRvIGxvd2VyIGZvciBjb25zaXN0ZW50IHJlcHJlc2VudGF0aW9uIGFuZCB0byBhaWQgY3JlYXRpb24gb2YgZmFjdG9yIGxldmVscyAgDQozLiBHZW5kZXIgaXMgY29udmVydGVkIHRvIGEgRmFjdG9yIHZhcmlhYmxlIGFuZCByZWxldmFudCBsYWJlbHMgYXJlIHByb3ZpZGVkLiBTaW5jZSBpdCBpcyBhbiB1bm9yZGVyZWQgY29sdW1uLCBvcmRlcmVkIGlzIHNldCB0byBGQUxTRSAgDQo0LiAnVW5rbm93bicgYW5kICdOYXRpb25hbGl0eSB1bmtub3duJyBhcmUgY29udmVydGVkIHRvICd1bmtub3duJyBmb3IgY29uc2lzdGVudCByZXByZXNlbnRhdGlvbiAoYXNzdW1pbmcgYSBiaXogcmVxdWlyZW1lbnQgZm9yIGNvbnNpc3RlbmN5KSAgDQo1LiAnKG5vdCBhc3NpZ25lZCknIGluIENsYXNzaWZpY2F0aW9uIGlzIGNvbnZlcnRlZCB0byAnTm90IEFzc2lnbmVkJyBpbiBsaW5lIHdpdGggdGhlIG90aGVyIGxldmVscyBpbiB0aGUgY29sdW1uICANCjYuIENvbnN0aXR1ZW50SUQsIEJlZ2luRGF0ZSwgRW5kRGF0ZSBhcmUgY29udmVydGVkIHRvIEludGVnZXIgZGF0YSB0eXBlICANCjcuIE5hdGlvbmFsaXR5LCBDbGFzc2lmaWNhdGlvbiwgRGVwYXJ0bWVudCBhbmQgQ2F0YWxvZ2VkIGFyZSBjb252ZXJ0ZWQgdG8gZmFjdG9yIHZhcmlhYmxlcyAobGV2ZWxzIGFyZSBub3QgY2hhbmdlZCBhdCB0aGUgbW9tZW50LCBidXQgY2FuIGJlIGNvbnZlcnRlZCBhcyBwZXIgYml6IHJlcXVpcmVtZW50LiBHZW5kZXIgaXMgYWxyZWFkeSBjb252ZXJ0ZWQgd2l0aCBsYWJlbHMgYWJvdmUpICANCjguIEJlZ2luRGF0ZSwgRW5kRGF0ZSBhbmQgRGF0ZSBhcmUgY29udmVydGVkIHRvIGRhdGUgdHlwZSBieSBjb252ZXJ0aW5nIHRoZSBleGlzdGluZyAneWVhcicgZm9ybWF0IHRvICd5bWQnIGJ5IGFkZGluZyB0aGUgYmVnaW5uaW5nIG9mIHllYXIgYXR0cmlidXRlcyAoZGF5LCBtb250aCBpcyBhZGRlZCBhcyBwZXIgbmVjZXNzYXJ5IGZvcm1hdHRpbmcpLiBUaGlzIHdpbGwgYWlkIGFueSBmdXJ0aGVyIGFuYWx5c2lzIGJhc2VkIG9uIGRhdGUgb3BlcmF0aW9ucyBsaWtlIGRpZmZlcmVuY2UgaW4gZHVyYXRpb24sIGNvbXBhcmlzb24gdG8gYSBjdXN0b20gZGF0ZSBvciBoZWxwIGluIHVwZGF0aW5nIG1vcmUgYWNjdXJhdGUgZGF0ZXMgd2hlbiB0aGUgbXVzZXVtIGlzIGFibGUgdG8gcHJvY3VyZSBtb3JlIGluZm9ybWF0aW9uIGxhdGVyKSAgDQo5LiAnRGF0ZScgaXMgcmVuYW1lZCB0byAnRGF0ZU1hZGUnIGZvciByZWFkYWJpbGl0eSB3aXRoIG90aGVyIGRhdGEgY29sdW1ucyBsaWtlICdCZWdpbkRhdGUnLCAnRW5kRGF0ZScgYW5kICdEYXRlQWNxdWlyZWQnICANCjEwLiBOQXMgaW4gbWVyZ2UgY29sdW1uIGFyZSBjaGVja2VkIGJlZm9yZSB0aGUgc3Vic2VxdWVudCBtZXJnZSAoVGhlIGFuYWx5c2lzIGlzIHJ1biBhbHJlYWR5IGJlZm9yZSwgYW5kIG5vIE5BcyBhcmUgZm91bmQuIENvZGUgaXMgbm90IHJ1biBhZ2FpbiB0byBhdm9pZCBtYXhpbmcgcGFnZSBsaW1pdCkgIA0KMTEuIEFkZGl0aW9uYWwgY29tbWVudHMgaW4gY29kZSBjaHVua3MgIA0KDQoNCmBgYHtyIENsZWFuIGRhdGFzZXR9DQoNCiNDbGVhbiB1cCBjb2x1bW5zIHdpdGggJygnICcpJyBpbiBjb2x1bW4gbmFtZXMgY3JlYXRlZCBieSB1bm5lc3QoKSBmdW5jdGlvbiBpbiA0IGNvbHVtbnMgDQp3YV9maWx0IDwtIHdhX2ZpbHQgJT4lIG11dGF0ZShOYXRpb25hbGl0eSA9IHN0cl9yZXBsYWNlX2FsbCh3YV9maWx0JE5hdGlvbmFsaXR5LCBwYXR0ZXJuID0gIlsoKV0iLCByZXBsYWNlbWVudCA9ICIiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJlZ2luRGF0ZSA9IHN0cl9yZXBsYWNlX2FsbCh3YV9maWx0JEJlZ2luRGF0ZSwgcGF0dGVybiA9ICJbKCldIiwgcmVwbGFjZW1lbnQgPSAiIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFbmREYXRlID0gc3RyX3JlcGxhY2VfYWxsKHdhX2ZpbHQkRW5kRGF0ZSwgcGF0dGVybiA9ICJbKCldIiwgcmVwbGFjZW1lbnQgPSAiIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5kZXIgPSBzdHJfcmVwbGFjZV9hbGwod2FfZmlsdCRHZW5kZXIsIHBhdHRlcm4gPSAiWygpXSIsIHJlcGxhY2VtZW50ID0gIiIpKQ0KDQojQ29udmVydCBHZW5kZXIgdG8gbG93ZXIgY2FzZSBhbmQgY29udmVydCB0byBmYWN0b3Igd2l0aCByZWxldmFudCBsYWJlbHMNCndhX2ZpbHQkR2VuZGVyIDwtIHRvbG93ZXIod2FfZmlsdCRHZW5kZXIpDQp3YV9maWx0JEdlbmRlciA8LSBmYWN0b3Iod2FfZmlsdCRHZW5kZXIsIGxldmVscyA9IGMoIm1hbGUiLCAiZmVtYWxlIiwgIiIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJNYWxlIiwgIkZlbWFsZSIsICJVbmtub3duIiksIG9yZGVyZWQgPSBGQUxTRSkNCg0KI0NvbnZlcnQgTmF0aW9uYWxpdHkgdG8gdmFsaWQgdmFsdWVzIHVzaW5nIGNhc2Ugd2hlbiBzdGF0ZW1lbnQuIENvbnZlcnQgdG8gZmFjdG9yIG5leHQNCndhX2ZpbHQgPC0gd2FfZmlsdCAlPiUgbXV0YXRlKE5hdGlvbmFsaXR5ID0gY2FzZV93aGVuKGFzLmNoYXJhY3RlcihOYXRpb25hbGl0eSk9PSIifiJVbmtub3duIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihOYXRpb25hbGl0eSk9PSJOYXRpb25hbGl0eSB1bmtub3duIn4iVW5rbm93biIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRX5hcy5jaGFyYWN0ZXIoTmF0aW9uYWxpdHkpKSkNCg0KI0NvbnZlcnQgQ2xhc3NpZmljYXRpb24gdmFsdWVzIHRvIGNvbnNpc3RlbnQgZm9ybWF0DQp3YV9maWx0JENsYXNzaWZpY2F0aW9uW3dhX2ZpbHQkQ2xhc3NpZmljYXRpb249PSIobm90IGFzc2lnbmVkKSJdIDwtICJOb3QgQXNzaWduZWQiDQoNCiNDb252ZXJ0IHJlbGV2YW50IGNvbHVtbnMgdG8gSW50ZWdlciBhbmQgZmFjdG9yIHR5cGUgKEdlbmRlciBhbHJlYWR5IGNvbnZlcnRlZCB0byBGYWN0b3IgYWJvdmUpDQp3YV9maWx0PC13YV9maWx0ICU+JSBtdXRhdGVfYXQoYygiQ29uc3RpdHVlbnRJRCIsICJCZWdpbkRhdGUiLCAiRW5kRGF0ZSIpLCAuZnVucyA9IGFzLmludGVnZXIpDQp3YV9maWx0PC13YV9maWx0ICU+JSBtdXRhdGVfYXQoYygiTmF0aW9uYWxpdHkiLCJDbGFzc2lmaWNhdGlvbiIsICJEZXBhcnRtZW50IiwgIkNhdGFsb2dlZCIpLCAuZnVucyA9IGFzLmZhY3RvcikNCg0KI2NvbnZlcnQgeWVhcnMgdG8gZGF0ZQ0Kd2FfZmlsdCRCZWdpbkRhdGUgPC0gYXMuRGF0ZShwYXN0ZSh3YV9maWx0JEJlZ2luRGF0ZSwgIjEiLCAiMSIsIHNlcCA9ICItIiksIGZvcm1hdCA9ICIlWS0lbS0lZCIpDQp3YV9maWx0JEVuZERhdGUgPC0gYXMuRGF0ZShwYXN0ZSh3YV9maWx0JEVuZERhdGUsICIxIiwgIjEiLCBzZXAgPSAiLSIpLCBmb3JtYXQgPSAiJVktJW0tJWQiKQ0Kd2FfZmlsdCREYXRlIDwtIGFzLkRhdGUocGFzdGUod2FfZmlsdCREYXRlLCAiMSIsICIxIiwgc2VwID0gIi0iKSwgZm9ybWF0ID0gIiVZLSVtLSVkIikNCndhX2ZpbHQgPC0gcGx5cjo6cmVuYW1lKHggPSB3YV9maWx0LCByZXBsYWNlID0gYygiRGF0ZSIgPSAiRGF0ZU1hZGUiKSkNCg0KI2NoZWNrIGZvciBtaXNzaW5nIHZhbHVlcyBpbiBtZXJnZSBrZXlzIGkuZS4gQ29uc3RpdHVlbnRJRC4gUmVzdWx0IGZyb20gYmVmb3JlOiBObyBtaXNzaW5nIHZhbHVlcy4gUmVhZHkgZm9yIG1lcmdlDQpgYGANCiMjIE1lcmdlIERhdGEgdG8gY3JlYXRlIE1hc3RlciBkYXRhc2V0IA0KDQoxLiB3YV9maWx0IGlzIG1lcmdlZCB3aXRoIGFydGlzdHMgYmFzZWQgb24gJ0NvbnN0aXR1ZW50SUQnIGNvbHVtbiB3aGljaCBpcyB0aGUgcHJpbWFyeSBrZXkgZm9yIGFydGlzdHMgYW5kIGZvcmVpZ24ga2V5IGZvciB3YV9maWx0LiAgIA0KMi4gQXMgb3RoZXIgY29sdW1ucyBpbiBhcnRpc3RzIGFscmVhZHkgcHJlc2VudCBpbiB3YV9maWx0LCBvbmx5ICd3aWtpIFFJRCcgYW5kICdVTEFOJyBjb2x1bW5zIGFyZSBicm91Z2h0IGludG8gdGhlICoqJ21lcmdlZEFydCcqKiBkYXRhc2V0ICANCjMuIFN1bSBvZiBOQXMgaW4gbWVyZ2VkQXJ0IGFyZSBleHBsb3JlZCBmb3IgdGhlIHN1YnNlcXVlbnQgc2VjdGlvbiBvbiBtaXNzaW5nIHZhbHVlIGlkZW50aWZpY2F0aW9uIGFuZCB0cmVhdG1lbnQuIFRoaXMgaW5mb3JtYXRpb24gZ3VpZGVzIHRoZSBuZXh0IHNlY3Rpb24gIA0KDQpgYGB7ciBNZXJnZSBEYXRhfQ0KI01lcmdlICd3YV9maWx0JyBhbmQgJ2FydGlzdHMnIG9uIGtleSAnQ29uc3RpdHVlbnRJRCcNCm1lcmdlZEFydCA8LSB3YV9maWx0ICU+JSBsZWZ0X2pvaW4oYXJ0aXN0c1ssYygiQ29uc3RpdHVlbnRJRCIsICJXaWtpIFFJRCIsICJVTEFOIildLCBieSA9ICJDb25zdGl0dWVudElEIikNCmNvbFN1bXMoaXMubmEobWVyZ2VkQXJ0KSkgI0lkZW50aWZ5IGNvbHVtbnMgd2l0aCBtaXNzaW5nIHZhbHVlcyBmb3IgdHJlYXRtZW50IGluIG5leHQgc2VjdGlvbg0KYGBgDQoNCg0KIyMJU2NhbiBJIChNaXNzaW5nIHZhbHVlcykNCg0KMS4gTWlzc2luZyB2YWx1ZXMgd2lsbCBiZSBpZGVudGlmaWVkIGFuZCByZWxldmFudCB0cmVhdG1lbnQgdGVjaG5pcXVlIGlzIHVzZWQgIA0KMi4gQSB1c2VyIGZ1Y250aW9uIHRvIHNlbGVjdCB0aGUgbW9kZSBvZiBhIENhdGVnb3JpY2FsIGNvbHVtbiBpcyBjcmVhdGVkIGFzIHRoZSBpbXB1dGUgZnVuY3Rpb24gZnJvbSBIbWlzYyBkaWRuJ3QgcHJvdmlkZSB0aGUgbW9kZSBhcyB2ZXJpZmllZCBieSB0aGUgY291bnQoLCBzb3J0ID0gVFJVRSkgJT4lIGhlYWQoMSkgbWV0aG9kICANCjMuIERhdGVNYWRlIGhhcyBtaXNzaW5nIHZhbHVlcyB3aGljaCBhcmUgaW1wdXRlZCB1c2luZyB0aGUgTW9kZSBvZiB0aGUgY29sdW1uICAoaS5lLiAnMTk3MS8wMS8wMScpLiBJdCBoYXMgfjRrIG1pc3NpbmcgdmFsdWVzIHNvIHJlbW92aW5nIG1pc3NpbmcgdmFsdWVzIGlzIG5vdCByZWNvbW1lbmRlZC4gQWxzbywgdGhlIG1vZGUgaXMgaGlnaGx5IG9jY3VycmluZyB2YWx1ZSBmb3IgdGhlIGNvbHVtbiAgDQo0LiBEYXRlQWNxdWlyZWQgaXMgYWxzbyBpbXB1dGVkIHdpdGggdGhlIG1vZGUgb2YgdGhlIGNvbHVtbiAoaS5lLiAnMjAxMy0xMC0yNCcpLiBBcm91bmQgfjYuNWsgdmFsdWVzIGNvcnJlc3BvbmQgdG8gdGhlIG1vZGUuIFNpbmNlIHRoZSBtaXNzaW5nIHZhbHVlcyBhcmUganVzdCA1ODAgaW4gY291bnQsIGV4Y2x1ZGluZyBzZWxlY3RpdmVseSBkdXJpbmcgYW5hbHlzaXMgaXMgYWxzbyBhbiBvcHRpb24sIGJ1dCBpIGRlY2lkZWQgdG8gdXNlIHRoZSBtb2RlIGFzIGl0IGlzIGhpZ2hseSBjb21tb24gYXMgd2VsbC4gIA0KNS4gTWVkaXVtIGhhcyB+NjM2IG1pc3NpbmcgdmFsdWVzIGFuZCBpcyBpbXB1dGVkIHdpdGggdGhlIG1vZGUgKGkuZS4gJ0dlbGF0aW4gc2lsdmVyIHByaW50Jykgd2hpY2ggb2NjdXJzIH42LjdrIHRpbWVzICANCjYuIERpbWVuc2lvbnMgaGFzIGFyb3VuZCA2NTcgbWlzc2luZyB2YWx1ZXMgYnV0IGlzIGFsc28gY29uc2lkZXJlZCBhIHJlZHVuZGFudCBjb2x1bW4sIGFuZCBmb3IgdGhlIHRpbWUgYmVpbmcgaXMgaW1wdXRlZCB3aXRoIHRoZSBtb2RlIChpLmUuICc2IDExLzE2IMOXIDkgMTEvMTYiICgxNyDDlyAyNC42IGNtKScpIHNpbmNlIGl0cyByZXF1aXJlZCBmb3IgdGhlIG5leHQgc3RlcCBvZiBleHRyYWN0aW5nIGluZGl2aWR1YWwgZGltZW5zaW9ucyB0byB0cmVhdCBkaW1lbnNpb25zIGNvbHVtbiAod2hlcmUgdGhleSBhcmUgbWlzc2luZyksIGFuZCBpcyBwcmVmZXJyZWQgZm9yIHRoZSBzdWJzZXF1ZW50IHRleHQtaGVhdnkgb3BlcmF0aW9ucy4gSXQgaXMgbGlrZWx5IHRoYXQgdGhpcyBjb2x1bW4gd2lsbCBiZSBkcm9wcGVkIGFmdGVyIHRoZSBleHRyYWN0aW9uIGlzIGV4ZWN1dGVkLiBPdGhlciBhbHRlcm5hdGl2ZSBpcyB0byBsZWF2ZSB0aGUgY29sdW1uIGFzLWlzIGR1ZSB0byB0aGUgbG93IGNvdW50IG9mIG1pc3NpbmcgdmFsdWVzLCBidXQgaSB3ZW50IGFoZWFkIHdpdGggaW1wdXRpbmcgdG8gYXZvaWQgaXNzdWVzIHdpdGggc3Vic2VxdWVudCBzdHJpbmcgb3BlcmF0aW9ucy4gKFJlZmVyIDkuIGJlbG93KSAgDQo3LiBEZXB0aCBoYXMgfjUwJSBtaXNzaW5nIHZhbHVlcyBhbmQgaXMga2VwdCBhcy1pcyBmb3IgYW55IGNyaXRpY2FsIGFuYWx5c2lzIGxhdGVyIG9uLiBBbHNvIHRoZSBleHRyYWN0aW9uIG9mIGRpbWVuc2lvbnMgd2lsbCBhaWQgaW4gcmVkdWNpbmcgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIGNvbHVtbi4gKFJlZmVyIDkuIGJlbG93KSAgDQo4LiBXaWtpIFFJRCBhbmQgVUxBTiBoYXZlIH42MCUgbWlzc2luZyB2YWx1ZXMsIGJ1dCBzaW5jZSB0aGV5IGFyZSBpbXBvcnRhbnQgYXR0cmlidXRlcyBvZiB0aGUgYXJ0aXN0cywgdGhleSBhcmUgbm90IGV4Y2x1ZGVkIGZyb20gdGhlIGRhdGFzZXQgYW5kIGtlcHQgYXMtaXMuICANCjkuIE51bWVyaWMgdmFsdWUgdHJlYXRtZW50ICgoRGVwdGggKGNtKSwgSGVpZ2h0IChjbSksIFdpZHRoIChjbSkpLiBTaW5jZSAnZGltZW5zaW9ucycgaGFzIHRoZSBzYW1lIGF0dHJpYnV0ZXMgaW4gaXQsIGV4dHJhY3QgdGhlIGRhdGEgZnJvbSB0aGUgc3RyaW5nIGNvbHVtbiB0byByZXBsYWNlIHdoZXJldmVyIE5BLiBBbmQgYWZ0ZXIgdGhhdCwgaW1wdXRpbmcgd2l0aCBlaXRoZXIgbWVhbiBvciBtZWRpYW4gd2hlcmUgTkFzIGFyZSBzdGlsbCBwcmVzZW50IChtZWRpYW4gb3B0ZWQgc2luY2UgdGhlcmUgYXJlIGhpZ2hseSBza2V3ZWQgb3V0bGllcnMuIHNob3duIGJlbG93IGluIGxhc3Qgc3ViLXBvaW50KSAgICANClwgICBcICAgXCAgIFwgICBcICAqLSBEaW1lbnNpb25zIGlzIGEgdmVyeSBkeW5hbWljIHZhcmlhYmxlIHdpdGggdGhlIHJlcXVpcmVkIGluZm8gYXQgZGlmZmVyZW50IHBvc2l0aW9ucyBvZiB0aGUgY29sdW1uLiBTbyBleHRyYWN0aW5nIHRoZSBkaW1lbnNpb25zIGlzIGRvbmUgZnJvbSB0aGUgZW5kIG9mIHN0cmluZyBiYXNlZCBvbiBsb2NhdGlvbnMgb2YgdGhlIGJyYWNrZXRzIG9mIHRoZSBsYXN0IGRpbWVuc2lvbi1zZXQuIEV4OiBleHRyYWN0ICgxY20geCAyY20geCA1Y20pIGZyb20gdGhlIGdhcmJhZ2UgdmFsdWVzICcuLjJjbS4uMWNtIHggMmNtLi4uLiAoMWNtIHggMmNtIHggNWNtKScuKiAgICANClwgICBcICAgXCAgIFwgICBcICAqLSBJIGhhdmUgdXNlZCBhIHNlcmllcyBvZiBzdGVwcyB3aXRoIG11bHRpcGxlIHVzZXIgZGVmaW5lZCBmdW5jdGlvbnMgdG8gYXZvaWQgdXNpbmcgc3RyaW5naSBwYWNrYWdlIChUaGlzIHdhcyB2ZXJ5IGRpZmZpY3VsdCB0byBhY2hpZXZlISEhKS4gU3RlcHMgaW52b2x2ZWQgYXJlIGV4dHJhY3RpbmcgdGhlIHN0YXJ0IGFuZCBlbmQgbG9jYXRpb25zIG9mIHRoZSBicmFjZXMgKGFuZCBleHRyYWN0IGludG8gYSBjb2x1bW4gYnkgZXh0cmFjdGluZyB2YWx1ZXMgZnJvbSBzdHJfbG9jYXRlX2FsbCgpIGludG8gYSBsaXN0IGFuZCBleHRyYWN0aW5nIG9ubHkgdGhlIHJlbGV2YW50IHZhbHVlcyB3aGljaCBhcmUgcHJlc2VudCB0b3dhcmRzIHRoZSBlbmQgb2YgZWFjaCBuZXN0ZWQgbGlzdCBhbmQgYSBjb252ZXJ0aW5nIGludGVnZXIoMCkgdG8gTkFzIHVzaW5nIGFwcGx5TkFUb1N0cigpIGZ1bmN0aW9uIGluIHRoZSBjb2RlKSwgdGhlbiB1c2Ugc3RyX3N1YigpIHRvIGV4dHJhY3QgdGhlIGRpbWVuc2lvbnMsIHJlcGxhY2UgZ2FyYmFnZSB2YWx1ZXMsIHNwbGl0IGJhc2VkIG9uICd4JywgYW5kIHRyaW0gd2hpdGVzcGFjZXMsIHdoaWNoIHByb3ZpZGVzIGEgbGlzdCBvZiBsaXN0cyogICAgDQpcICAgXCAgIFwgICBcICAgXCAgKi0gVGhlIHJlcXVpcmVtZW50IG9mIGNyZWF0aW5nIGEgbmV3IGNvbHVtbiBpbiB0aGUgYXNzaWdubWVudCBpcyBhbHNvIGRvbmUgaGVyZSogIA0KXCAgIFwgICBcICAgXCAgIFwgICotIEV4dHJhY3QgdGhlIEhlaWdodCwgV2lkdGgsIERlcHRoIHJlc3BlY3RpdmVseSB1c2luZyBzYXBwbHkoIlsiKSBhbmQgcmVsZXZhbnQgcG9zaXRpb24gaW4gdGhlIGxpc3QgKGUuZy4gMSwyLDMpIGFuZCBjb252ZXJ0IHRvIGRvdWJsZSogICAgDQpcICAgXCAgIFwgICBcICAgXCAgKi0gV2hlcmV2ZXIgb3JpZ2luYWwgY29sdW1ucyBoYXZlIG1pc3NpbmcgdmFsdWVzLCByZWxldmFudCBub24tbWlzc2luZyB2YWx1ZXMgYXJlIHJlcGxhY2VkIGFjcm9zcyBlYWNoIG9mIHRoZSAzIGNvbHVtbnMgKEhlaWdodCwgV2lkdGgsIERlcHRoKSogICAgDQpcICAgXCAgIFwgICBcICAgXCAgKi0gQXMgc2VlbiBieSBoaXN0KCksIHRoZXJlIGFyZSBsYXJnZSBvdXRsaWVycyBpbiBib3RoIEhlaWdodCBhbmQgV2lkdGgsIGhlbmNlIG1lZGlhbiBpcyB1c2VkIHRvIGltcHV0ZSB0aGUgbWlzc2luZyB2YWx1ZXMgaW5zdGVhZCBvZiBtZWFuKiAgDQoxMS4gQWxsIG51bWVyaWMgY29sdW1ucyBhcmUgY2hlY2tlZCBmb3Igc3BlY2lhbCB2YWx1ZXMgbGlrZSBJbmYsIC1JbmYgYW5kIE5BTi4gVGhlcmUgYXJlIG5vIHNwZWNpYWwgdmFsdWVzIGluIHRoZSBkYXRhc2V0DQoxMC4gQWRkaXRpb25hbCBjb21tZW50cyBpbiBjb2RlIGNodW5rcyAgDQoNCmBgYHtyIE1pc3NpbmcgVmFsdWVzLCBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD04fQ0KI01pc3NpbmcgdmFsdWUgaWRlbnRpZmljYXRpb24gYW5kIHRyZWF0bWVudA0KZ2V0TW9kZSA8LSBmdW5jdGlvbih2KSAjZnVuY3Rpb24gdG8gZ2V0IG1vZGUgYXMgaW1wdXRlIGZyb20gSG1pc2MgZGlkbid0IGdpdmUgdGhlIHJpZ2h0IG1vZGUgYXMgY29uZmlybWVkIGJ5IGNvdW50KCkNCnsgdW5pcXYgPC0gdW5pcXVlKHYpDQp1bmlxdlt3aGljaC5tYXgodGFidWxhdGUobWF0Y2godiwgdW5pcXYpKSldfQ0KDQojQ2hlY2sgbWlzc2luZyB2YWx1ZSBmb3IgRGF0ZU1hZGUgYW5kIERhdGVBY3F1aXJlZA0KbWVyZ2VkQXJ0ICU+JSBjb3VudChEYXRlTWFkZSwgc29ydCA9IFRSVUUpICU+JSBoZWFkKDEpI01vZGUgaXMgJzE5NzEvMDEvMDEnIHdoaWNoIGNhbiBiZSB1c2VkIGZvciBtaXNzaW5nIHZhbHVlcyBpbiBEYXRlTWFkZQ0KbWVyZ2VkQXJ0JERhdGVNYWRlW2lzLm5hKG1lcmdlZEFydCREYXRlTWFkZSldIDwtIGdldE1vZGUobWVyZ2VkQXJ0JERhdGVNYWRlKSAjbW9kZSBpcyAxOTcxLTAxLTAxDQoNCm1lcmdlZEFydCAlPiUgY291bnQoRGF0ZUFjcXVpcmVkLCBzb3J0ID0gVFJVRSkgJT4lIGhlYWQoMSkgI21vZGUgaXMgMjAxMy0xMC0yNC4gTWFueSBpdGVtcyB3ZXJlIGFjcXVpcmVkIHRoYXQgZGF5DQptZXJnZWRBcnQkRGF0ZUFjcXVpcmVkW2lzLm5hKG1lcmdlZEFydCREYXRlQWNxdWlyZWQpXSA8LSBnZXRNb2RlKG1lcmdlZEFydCREYXRlQWNxdWlyZWQpIA0KDQojQ2hlY2sgbWlzc2luZyB2YWx1ZXMgZm9yIGNhdGVnb3JpY2FsIGNvbHVtbnMNCm1lcmdlZEFydCAlPiUgY291bnQoTWVkaXVtLCBzb3J0ID0gVFJVRSkgJT4lIGhlYWQoMSkgI21vZGUNCm1lcmdlZEFydFtpcy5uYShtZXJnZWRBcnQkTWVkaXVtKSxdJE1lZGl1bSAgPC0gZ2V0TW9kZShtZXJnZWRBcnQkTWVkaXVtKSAjJ0dlbGF0aW4gc2lsdmVyIHByaW50JyBpcyB0aGUgbW9kZSB3aXRoIH42LjdrIG9icw0KDQptZXJnZWRBcnQgJT4lIGNvdW50KERpbWVuc2lvbnMsIHNvcnQ9IFRSVUUpICU+JSBoZWFkKDEpICNtb2RlDQptZXJnZWRBcnRbaXMubmEobWVyZ2VkQXJ0JERpbWVuc2lvbnMpLF0kRGltZW5zaW9ucyAgPC0gZ2V0TW9kZShtZXJnZWRBcnQkRGltZW5zaW9ucykNCg0KI0FzICdEZXB0aCAoY20pJyBoYXMgbW9yZSB0aGFuIDUwJSBtaXNzaW5nIHZhbHVlcywgdGhlIGNvbHVtbiBjYW4gYWN0dWFsbHkgYmUgZXhjbHVkZWQsIA0KI2J1dCB0aGlzIGhhcyB0byBiZSBkb25lIGJlZm9yZSBydWxpbmcgb3V0IHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSBhbmQgYnVzaW5lc3MgcnVsZXMuIA0KI1NpbmNlIGl0cyBhIG51bWVyaWMgZGltZW5zaW9uIGNvbHVtbiwgaSBhbSBhc3N1bWluZyBpdHMgb2YgaW1wb3J0YW5jZSBhbmQgcmV0YWluIGl0LiBXSWxsIGJlIHVzZWQgd2l0aCBuYS5ybT1UUlVFDQoNCiNXaWtpIFFJRCAmIFVMQU4gb24gdGhlIG90aGVyIGhhbmQgaGFzID42MCUgbWlzc2luZyBhbmQgZG9lc24ndCBoYXZlIGFueSBwYXR0ZXJuIGFzIGl0cyBhbiBJRCBjb2x1bW4sIA0KI0J1dCBzaW5jZSBpdHMgYW4gaW1wb3J0YW50IGF0dHJpYnV0ZSBvZiB0aGUgYXJ0aXN0LCB0aGUgY29sdW1ucyBhcmUgbm90IGV4Y2x1ZGVkIGZyb20gdGhlIGRhdGFzZXQNCg0KI051bWVyaWMgbWlzc2luZyB2YWx1ZSB0cmVhdG1lbnQgKERlcHRoIChjbSksIEhlaWdodCAoY20pLCBXaWR0aCAoY20pKS4gU2luY2UgJ2RpbWVuc2lvbnMnIGhhcyB0aGUgc2FtZSBhdHRyaWJ1dGVzIGluIGl0LCBleHRyYWN0IHRoZSBkYXRhIGZyb20gdGhlIHN0cmluZyBjb2x1bW4gdG8gcmVwbGFjZSB3aGVyZXZlciBOQS4gQW5kIGFmdGVyIHRoYXQgaW1wdXRlIHdpdGggZWl0aGVyIG1lYW4gb3IgbWVkaWFuDQojRGltZW5zaW9ucyBpcyBhIHZlcnkgZHluYW1pYyB2YXJpYWJsZSB3aXRoIHRoZSByZXF1aXJlZCBpbmZvIGF0IGRpZmZlcmVudCBwb3NpdGlvbnMgb2YgdGhlIGNvbHVtbi4gU28gZXh0cmFjdCB0aGUgZGltZW5zaW9ucyBmcm9tIHRoZSBlbmQgb2Ygc3RyaW5nIGJhc2VkIG9uIGxvY2F0aW9ucyBvZiB0aGUgYnJhY2tldHMgb2YgdGhlIGxhc3QgZGltZW5zaW9uLXNldC4gRXg6IGV4dHJhY3QgKDFjbSB4IDJjbSB4IDVjbSkgZnJvbSB0aGUgZ2FyYmFnZSB2YWx1ZXMgJy4uMmNtLi4xY20geCAyY20uLi4uICgxY20geCAyY20geCA1Y20pJw0KI0kgaGF2ZSB1c2VkIGEgc2VyaWVzIG9mIHN0ZXBzIHdpdGggbXVsdGlwbGUgdXNlciBkZWZpbmVkIGZ1bmN0aW9ucyB0byBhdm9pZCB1c2luZyBzdHJpbmdpIHBhY2thZ2UNCg0KYXBwbHlOQVRvU3RyPC0gZnVuY3Rpb24oeCkgI2Z1bmN0aW9uIHRvIGFwcGx5IE5BIHdoZXJldmVyIHRoZSBwZW51bHRpbWF0ZSBmdW5jdGlvbiBiZWxvdyBjcmVhdGVzIGludGVnZXIoMCkgdmFsdWVzLCBpLmUuIGJsYW5rIHZhbHVlcywgIHRodXMgYWxsb3dpbmcgY29udmVyc2lvbiB0byBjb2x1bW4gKERpbWVuc2lvbnNTdGFydCwgRGltZW5zaW9uc0VuZCkgdG8gYWRkIGJhY2sgdG8gdGhlIGRhdGFzZXQNCg0Ke2lmKGxlbmd0aCh4KSA9PSAwKQ0KICAgIHJldHVybiAoTkEpDQogIHJldHVybiAoeCl9DQoNCm1lcmdlZEFydCREaW1lbnNpb25zU3RhcnQgPC0gbWVyZ2VkQXJ0JERpbWVuc2lvbnMgJT4lIA0KICBzdHJfbG9jYXRlX2FsbCguLCBwYXR0ZXJuID0gIlsoXSIpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCkgeFssMV0pICU+JQ0KICBsYXBwbHkoZnVuY3Rpb24oeCkgeFtsZW5ndGgoeCldKSAlPiUgc2FwcGx5KGFwcGx5TkFUb1N0cikgI0FwcGx5IGEgc2VyaWVzIG9mIHVzZXIgZGVmaW5lZCBmdW5jdGlvbnMgdG8gZXh0cmFjdCB0aGUgc3RhcnQgcG9zaXRpb24gb2YgdGhlIHJlcXVpcmVkIGRpbWVuc2lvbnMNCg0KbWVyZ2VkQXJ0JERpbWVuc2lvbnNFbmQgPC0gbWVyZ2VkQXJ0JERpbWVuc2lvbnMgJT4lIA0KICBzdHJfbG9jYXRlX2FsbCguLCBwYXR0ZXJuID0gIlspXSIpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCkgeFssMV0pICU+JQ0KICBsYXBwbHkoZnVuY3Rpb24oeCkgeFtsZW5ndGgoeCldKSAlPiUgc2FwcGx5KGFwcGx5TkFUb1N0cikgI0VuZCBwb3NpdGlvbiBvZiB0aGUgcmVxdWlyZWQgZGltZW5zaW9ucw0KDQptZXJnZWRBcnQkbmV3RGltIDwtIG1lcmdlZEFydCREaW1lbnNpb25zICU+JSANCiAgc3RyX3N1YihzdGFydCA9IG1lcmdlZEFydCREaW1lbnNpb25zU3RhcnQsIGVuZCA9IG1lcmdlZEFydCREaW1lbnNpb25zRW5kKSAlPiUgDQogIHN0cl9yZXBsYWNlKCJbKF0iLCAiIikgJT4lIHN0cl9yZXBsYWNlKCJbKV0iLCAiIikgJT4lIHN0cl9yZXBsYWNlKCJjbSIsICIiKSAlPiUgDQogIHN0cl9zcGxpdCgiW3jDl10iKSAlPiUgc2FwcGx5KHN0cl90cmltLCBzaWRlID0gYygiYm90aCIpKSAjRXh0cmFjdCB0aGUgZGltZW5zaW9ucyBkeW5hbWljYWxseSBmcm9tIHRoZSBjb2x1bW4NCg0KDQptZXJnZWRBcnQkbmV3SGVpZ2h0PC1zYXBwbHkobWVyZ2VkQXJ0JG5ld0RpbSwgIlsiLCAxKSAlPiUgYXMuZG91YmxlKCkgI0V4dHJhY3QgSGVpZ2h0IGludG8gYSBuZXcgdGVtcG9yYXJ5IERpbWVuc2lvbiBjb2x1bW4NCm1lcmdlZEFydCRuZXdXaWR0aDwtc2FwcGx5KG1lcmdlZEFydCRuZXdEaW0sICJbIiwgMikgJT4lIGFzLmRvdWJsZSgpICNFeHRyYWN0IFdpZHRoIGludG8gYSBuZXcgdGVtcG9yYXJ5IERpbWVuc2lvbiBjb2x1bW4NCm1lcmdlZEFydCRuZXdEZXB0aDwtc2FwcGx5KG1lcmdlZEFydCRuZXdEaW0sICJbIiwgMykgJT4lIGFzLmRvdWJsZSgpICNFeHRyYWN0IERlcHRoIGludG8gYSBuZXcgdGVtcG9yYXJ5IERpbWVuc2lvbiBjb2x1bW4NCg0KbWVyZ2VkQXJ0IDwtIG1lcmdlZEFydCAlPiUgbXV0YXRlKGBIZWlnaHQgKGNtKWAgPSBpZl9lbHNlKGlzLm5hKGBIZWlnaHQgKGNtKWApLCBuZXdIZWlnaHQsIGBIZWlnaHQgKGNtKWApLA0KICAgICAgICAgICAgICAgICAgICAgIGBXaWR0aCAoY20pYCA9IGlmX2Vsc2UoaXMubmEoYFdpZHRoIChjbSlgKSwgbmV3V2lkdGgsIGBXaWR0aCAoY20pYCksDQogICAgICAgICAgICAgICAgICAgICAgYERlcHRoIChjbSlgID0gaWZfZWxzZShpcy5uYShgRGVwdGggKGNtKWApLCBuZXdEZXB0aCwgYERlcHRoIChjbSlgKSkgJT4lIHNlbGVjdCgtbmV3SGVpZ2h0LCAtbmV3V2lkdGgsIC1uZXdEZXB0aCwgLURpbWVuc2lvbnNTdGFydCwgLURpbWVuc2lvbnNFbmQpICNyZXBsYWNlIHdpdGggdmFsdWVzIGluIG5ldyB0ZW1wIGNvbHVtbnMgd2hlcmV2ZXIgZGltZW5zaW9ucyBhcmUgTkFzIGFuZCByZW1vdmUgdGhlIHRlbXBvcmFyeSBEaW1lbnNpb24gY29sdW1ucw0KDQpwYXIobWZyb3c9YygxLCAyKSkNCm1lcmdlZEFydCRgSGVpZ2h0IChjbSlgICU+JSBoaXN0KCkgI0hhcyBhIGZldyBvdXRsaWVycyB3aXRoIHZlcnkgaGlnaCB2YWx1ZXMuIFVzZSBtZWRpYW4gZm9yIGltcHV0aW5nDQptZXJnZWRBcnQkYFdpZHRoIChjbSlgICU+JSBoaXN0KCkgI0hhcyBhIGZldyBvdXRsaWVycyB3aXRoIHZlcnkgaGlnaCB2YWx1ZXMuIFVzZSBtZWRpYW4gZm9yIGltcHV0aW5nDQoNCm1lcmdlZEFydFtpcy5uYShtZXJnZWRBcnQkYEhlaWdodCAoY20pYCksXSRgSGVpZ2h0IChjbSlgIDwtIG1lZGlhbihtZXJnZWRBcnQkYEhlaWdodCAoY20pYCwgbmEucm0gPSBUUlVFKQ0KbWVyZ2VkQXJ0W2lzLm5hKG1lcmdlZEFydCRgV2lkdGggKGNtKWApLF0kYFdpZHRoIChjbSlgIDwtIG1lZGlhbihtZXJnZWRBcnQkYFdpZHRoIChjbSlgLCBuYS5ybSA9IFRSVUUpDQoNCiNDaGVjayBmb3IgU3BlY2lhbCAoTkFOLCBJbmYsIC1JbmYpIHZhbHVlcw0KY2hlY2tTcGVjaWFsVmFsdWVzIDwtIGZ1bmN0aW9uKHgpDQp7DQppcy5pbmZpbml0ZSh4KSB8IGlzLm5hbih4KQ0KfQ0KDQptZXJnZWRBcnQgJT4lIHNlbGVjdF9pZigucHJlZGljYXRlID0gaXMubnVtZXJpYykgJT4lIHNhcHBseShmdW5jdGlvbih4KSBzdW0oY2hlY2tTcGVjaWFsVmFsdWVzKHgpKSkgI05vIHNwZWNpYWwgdmFsdWVzDQoNCmBgYA0KDQoNCiMjCVNjYW4gSUkgKE91dGxpZXJzKQ0KDQoxLiBJbnN0ZWFkIG9mIHN0YXJ0aW5nIHdpdGggdGhlIGhpc3QoKSBvciBib3hwbG90KCkgdG8gdmlzdWFsaXplIHRoZSBvdXRsaWVycywgaSB3ZW50IGFoZWFkIHdpdGggdGhlIHotc2NvcmUgYXBwcm9hY2ggdG8gaWRlbnRpZnkgdGhlIG91dGxpZXJzIG9uIGFsbCBudW1lcmljIGNvbHVtbnMgdXNpbmcgc2VsZWN0X2lmKGlzLm51bWVyaWMpLiBUaGUgaGVhZCgpIG9mIHRoZSB6U2NvcmVzIGRhdGFzZXQgaXMgcXVpY2tseSB2aXN1YWxpemVkLiBWYWx1ZXMgaGF2ZSBiZWVuIGNvbnZlcnRlZCB0byB6c2NvcmVzIGFzIGlkZW50aWZpZWQgZnJvbSB0aGUgYWJzKHpzY29yZXMpPjMgIA0KXCAgXCAgIFwgICBcICAgXCAgKi0gSGlzdG9ncmFtIGlzIHRoZW4gY2hlY2tlZCBmb3IgZWFjaCBjb2x1bW4gdG8gZW5zdXJlIG5vcm1hbCBkaXN0cmlidXRpb24gZm9yIHVzaW5nIHotc2NvcmVzLiBIZWlnaHQgYW5kIFdpZHRoIHNsaWdodGx5IHJlc2VtYmxlIG5vcm1hbCBkaXN0cmlidXRpb24gd2hpbGUgRGVwdGggZG9lc24ndC4gRnVydGhlciBhbmFseXNpcyBmb2xsb3dzKiAgDQoyLiBIZWlnaHQgc2VlbXMgdG8gaGF2ZSAyMjMgb3V0bGllcnMgYXMgaWRlbnRpZmllZCBieSB6U2NvcmVzIGZyb20gMTU3NC44Y20gdG8gMjE1LjljbS4gSG93ZXZlciBtb3N0IG9mIHRoZW0gYXJlIHNwZWNpZmljIGFydHdvcmtzIHdoaWNoIGhhdmUgbGFyZ2VyIGhlaWdodCB0aGFuIG1ham9yaXR5IG9mIHRoZW0uIFNPIHJlbW92aW5nIHRoZW0gd291bGQgY3JlYXRlIGEgYmlhc2VkIGRhdGFzZXQsIHNvIGNhcHBlZCBjYW4gYmUgdXNlZCBvbiBIZWlnaHQgY29sdW1uLiAgDQozLiBCZWZvcmUgdGhhdCwgY291bnRzIG9mIHRvcCAxMCBvdXRsaWVycyBpcyBhbHNvIHZpZXdlZC4gVGhlIGJveHBsb3Qgb2YgdGhlIGNvbHVtbiBpcyBhbHNvIHZpZXdlZC4gVG9wIG91dGxpZXJzIGZyb20gbGFyZ2VzdCB0byBzbWFsbGVzdCBpcyB2aWV3ZWQgKG9ubHkgdG9wIDEwIHNob3duIHVzaW5nIGhlYWQoKSBiZWNhdXNlIG9mIHRoZSBsaW1pdCkgIA0KNC4gQ2FwcGluZyBmdW5jdGlvbiBpcyBjcmVhdGVkIHRvIGNhcCB0byA1JSBhbmQgOTUlIHF1YW50aWxlcy4gQ2FwcGluZyBpcyBkb25lIGZvciBIZWlnaHQgY29sdW1uICANCjUuIFdpZHRoIGhhcyAyNTMgb3V0bGllcnMgZnJvbSAxNzA1Y20gdG8gMTY0LjljbS4gVGhlc2UgYWxsIGNvcnJlc3BvbmQgdG8gc3BlY2lmaWMgYXJ0d29ya3MgYW5kIGhlbmNlIGNhbiBiZSBzYWZlbHkgYXNzdW1lZCB0byBiZSBhY3R1YWwgZGF0YSBhbmQgbm90IGEgZG9jdW1lbnRpbmcgZXJyb3IuIENhcHBpbmcgaXMgY2FycmllZCBvdXQgb24gV2lkdGggY29sdW1uIHRvIGF2b2lkIGNyZWF0aW5nIGEgYmlhc2VkIGRhdGFzZXQgYnkgcmVtb3ZpbmcuIFRvcCAxMCBvdXRsaWVycyBhcmUgYWxzbyB2aWV3ZWQgaW4gdGhlIHByb2Nlc3MgKHNhbWUgYXMgaGVpZ2h0KSAgDQo2LiBEZXB0aCBoYXMgfjUwJSBtaXNzaW5nIHZhbHVlcywgc28gaXQgd291bGQgbm90IGJlIGhpZ2hseSByZWNvbW1lbmRlZCB0byB0cmVhdCB0aGUgZGF0YS4gRGVwdGggY29sdW1uIGluIHRoZSBkYXRhIGlzIHByb3ZpZGVkIHdoZXJlIHBhaW50aW5nIHdpdGggMyBkaW1lbnNpb25zIG5lZWQgdG8gYmUgZXhwbGljaXRseSBhbmFseXplZCBpZiBuZWVkZWQsIGFuZCBoZW5jZSBhcmUgbm90IGRlbGliZXJhdGVseSBjYXB0dXJlZCBmb3IgbWFueSBhcnR3b3JrcyB3aGlsZSBjcmVhdGluZyBkYXRhLiBBbHNvLCB6LXNjb3JlcyBhcmUgbm90IGFwcGxpY2FibGUgc2luY2UgaXQgZG9lc24ndCBldmVuIHJlbW90ZWx5IHJlc2VtYmxlIG5vcm1hbCBkaXN0cmlidXRpb24uIE15IGFzc3VtcHRpb24gaXMgdGhhdCB0aGUgY29sdW1uIHdvdWxkIG5vdCBmZWF0dXJlIGluIGFueSBtb2RlbGluZyBhY3Rpdml0eSBhbmQgaGVuY2UgaXMgbm90IGNhcHBlZCBvciB0cmVhdGVkIGF0IHRoZSBtb21lbnQgIA0KNy4gQm94cGxvdCBpcyBhbHNvIHBsb3R0ZWQgZm9yIGVhY2ggb2YgdGhlIGNvbHVtbnMgZm9yIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgZGlzdHJpYnV0aW9uIGFuZCBoZWxwIGluIHRyZWF0aW5nIHRoZSBvdXRsaWVycyAgDQo4LiBDb25zdGl0dWVudElELCBVTEFOIGFuZCBPYmplY3RJRCBhcmUgdW5pcXVlIGlkZW50aWZpZXJzIGFuZCBkbyBub3QgaGF2ZSBhIHF1YW50aXRhdGl2ZSBhc3BlY3QsIGhlbmNlIG91dGxpZXIgdHJlYXRtZW50IGlzIG5vdCBjYXJyaWVkIG91dCBmb3IgdGhlbS4gIA0KOS4gQWRkaXRpb25hbCBjb21tZW50cyBpbiBjb2RlIGNodW5rcyAgDQoNCmBgYHtyIE91dGxpZXJzLCBmaWcuaGVpZ2h0PTIuNX0NCiNvdXRsaWVyIGRldGVjdGlvbiBhbmQgdHJlYXRtZW50Og0KcGFyKG1mcm93ID0gKGMoMSwyKSkpDQptZXJnZWRBcnQkYEhlaWdodCAoY20pYCAlPiUgaGlzdCh4bGFiID0gIkhlaWdodCIsIGJyZWFrcyA9IDI1KSAjY2hlY2sgaWYgSGVpZ2h0IGlzIHJlbW90ZWx5IHNpbWlsYXIgdG8gbm9ybWFsIGRpc3RyaWJ1dGlvbiwgdG8gdXNlIHotc2NvcmVzLiBJdCBzbGlnaHRseSByZXNlbWJsZXMgbm9ybWFsIGRpc3RyaWJ1dGlvbiwgc28gd2Ugd2lsbCBnbyBhaGVhZCB3aXRoIHotc2NvcmVzDQp6U2NvcmVzPC1tZXJnZWRBcnQgJT4lIHNlbGVjdF9pZigucHJlZGljYXRlID0gaXMubnVtZXJpYykgJT4lIHNjb3Jlcyh0eXBlID0gInoiKSAjQ3JlYXRlIHogc2NvcmVzIHN1bW1hcnkgZm9yIGFsbCBudW1lcmljIHZhbHVlcy4gSW5kaXZpZHVhbCBjb2x1bW5zIGZvY3VzZWQgb24gbmV4dA0KelNjb3JlcyAlPiUgaGVhZCg1KQ0KDQojJ0hlaWdodCcgaGFzIDIyMyBvdXRsaWVycyBhcyBzaG93biB1c2luZyB6IHNjb3JlcyBmcm9tIDE1NzQuOGNtIHRvIDIxNS45Y20uDQojJ0hvd2V2ZXIgbW9zdCBhcmUgc3BlY2lmaWMgYXJ0d29ya3Mgc28gZXhjbHVkaW5nIHRoZW0gd291bGQgY3JlYXRlIGEgYmlhc2VkIGRhdGEuIHNvIHVzZSBjYXBwaW5nIHRvIHF1YXJ0aWxlDQptZXJnZWRBcnQkYEhlaWdodCAoY20pYCAlPiUgYm94cGxvdChsYXM9MiwgeGxhYiA9ICJIZWlnaHQiKSAjRXhwbG9yZSB0aGUgb3V0bGllcnMgdXNpbmcgYm94cGxvdA0KbWVyZ2VkQXJ0ICU+JSBjb3VudChgSGVpZ2h0IChjbSlgLCBzb3J0ID0gVFJVRSkgJT4lIGFycmFuZ2UoLWBIZWlnaHQgKGNtKWApICU+JSBoZWFkKDEwKSAjSWRlbnRpZnkgdGhlIHZhbHVlIGNvdW50IG9mIHRvcCAxMCBvdXRsaWVyIHZhbHVlcw0KekhlaWdodCA8LSBzY29yZXMobWVyZ2VkQXJ0JGBIZWlnaHQgKGNtKWAsIHR5cGU9InoiKQ0KDQptZXJnZWRBcnQkYEhlaWdodCAoY20pYFt3aGljaChhYnMoekhlaWdodCk+MyldICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHNldF9uYW1lcyhjKCJvdXRsX0hlaWdodCIpKSAlPiUNCiAgYXJyYW5nZSgtb3V0bF9IZWlnaHQpICU+JSBoZWFkKDEwKSAjdG9wIDEwIGxhcmdlc3Qgb3V0bGllciB2YWx1ZXMNCg0KY2FwIDwtIGZ1bmN0aW9uKHgpeyAjZnVuY3Rpb24gdG8gY2FwIHRvIG5lYXJlc3Qgbm9uLW91dGxpZXIgdmFsdWUNCiAgcXVhbnRpbGVzIDwtIHF1YW50aWxlKCB4LCBjKC4wNSwgMC4yNSwgMC43NSwgLjk1ICksIG5hLnJtID0gVFJVRSApDQogIHhbIHggPCBxdWFudGlsZXNbMl0gLSAxLjUqSVFSKHgsIG5hLnJtID0gVFJVRSkgXSA8LSBxdWFudGlsZXNbMV0NCiAgeFsgeCA+IHF1YW50aWxlc1szXSArIDEuNSpJUVIoeCwgbmEucm0gPSBUUlVFKSBdIDwtIHF1YW50aWxlc1s0XQ0KICB4fQ0KDQptZXJnZWRBcnQkYEhlaWdodCAoY20pYCA8LSBtZXJnZWRBcnQkYEhlaWdodCAoY20pYCAlPiUgY2FwKCkNCg0KI291dGxpZXJzIGluICdXaWR0aCcuIFRoZXJlIGFyZSAyNTMgb3V0bGllcnMgZnJvbSAxNzA1Y20gdG8gMTY0LjljbS4gQWxsIHNwZWNpZmljIGFydHdvcmtzIGV4Y2VwdCBmb3IgMy4NCiNDYXAgdGhlIG91dGxpZXJzIHRvIGF2b2lkIGJpYXNlZCBkYXRhDQptZXJnZWRBcnQkYFdpZHRoIChjbSlgICU+JSBoaXN0KHhsYWIgPSAiV2lkdGgiKSAjQ2hlY2sgZGlzdHJpYnV0aW9uIHRvIGVuc3VyZSB0aGF0IGl0IHJlbW90ZWx5IHJlc2VtYmxlcyBub3JtYWwgZGlzdHJpYnV0aW9uIHRvIGFwcGx5IHotc2NvcmVzDQptZXJnZWRBcnQkYFdpZHRoIChjbSlgICU+JSBib3hwbG90KGxhcz0yLCB4bGFiID0gIldpZHRoIikgI0V4cGxvcmUgdGhlIG91dGxpZXJzIHVzaW5nIGJveHBsb3QNCg0KeldpZHRoIDwtIHNjb3JlcyhtZXJnZWRBcnQkYFdpZHRoIChjbSlgLCB0eXBlPSJ6IikNCm1lcmdlZEFydCRgV2lkdGggKGNtKWBbd2hpY2goYWJzKHpXaWR0aCk+MyldICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHNldF9uYW1lcyhjKCJvdXRsX1dpZHRoIikpICU+JQ0KICBhcnJhbmdlKC1vdXRsX1dpZHRoKSAlPiUgaGVhZCgxMCkgI3RvcCAxMCBsYXJnZXN0IG91dGxpZXIgdmFsdWVzDQoNCm1lcmdlZEFydCRgV2lkdGggKGNtKWAgPC0gbWVyZ2VkQXJ0JGBXaWR0aCAoY20pYCAlPiUgY2FwKCkNCg0KI291dGxpZXJzIGluICdEZXB0aCcuIFRoaXMgY29sdW1uIGhhcyBtb3JlIHRoYW4gNTAlIE5BcyBhbmQgbW9zdCBvZiB0aGUgcmVtYWluaW5nIHZhbHVlcyBhcmUgMCAoYXMgDQojbW9zdCBvZiB0aGUgcGFpbnRpbmcgaGF2ZSBhIGRlcHRoIH4wKS4gVGhlcmUgaXMgbm8gYmVuZWZpdCBvZiB0cmVhdGluZyBvdXRsaWVycyBmb3IgdGhpcyBjb2x1bW4NCiNUaGlzIGNvbHVtbiBpcyBhdmFpbGFibGUgaW4gY2FzZSBhcnR3b3JrcyB3aXRoIDMgZGltZW5zaW9ucyBuZWVkIHRvIGJlIGFuYWx5emVkIGV4cGxpY2l0bHkNCg0KbWVyZ2VkQXJ0JGBEZXB0aCAoY20pYCAlPiUgaGlzdCh4bGFiID0gIkRlcHRoIikjTm8gb3V0bGllcnMgdXNpbmcgei1zY29yZXMgc2luY2UgdGhlIGRhdGEgaXMgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiANCm1lcmdlZEFydCRgRGVwdGggKGNtKWAgJT4lIGJveHBsb3QoeGxhYiA9ICJEZXB0aCIpICNVc2luZyBUdWtleSdzIGVtdGhvZCB0aGVyZSBhcmUgbWFueSBvdXRsaWVycyB0aGF0IGFyZSBkZXRlY3RlZC4gQnV0LCBzaW5jZSBtb3N0IG9mIHRoZSBkYXRhIGlzIGVpdGhlciBOQSBvciAwLCBvdXRsaWVyIGRldGVjdGlvbiB3b250IG1ha2Ugc2Vuc2UgZm9yIHRoaXMgY29sdW1uDQpgYGANCg0KDQojIwlUcmFuc2Zvcm0gDQoNCjEuIFRyYW5zZm9ybWF0aW9uIGlzIGRvbmUgYnkgY2hlY2tpbmcgbXVsdGlwbGUgdHJhbnNmb3JtIG9wZXJhdGlvbnMgb2YgYWxsIG51bWVyaWMgY29sdW1ucyB3aGljaCBhcmUgc3Vic2V0IGludG8gYSBzZXBhcmF0ZSBkYXRhc2V0IGZvciBjaGVja2luZyB0cmFuc2Zvcm1hdGlvbnMgYW5kIGV4cGxvcmUgdmlzdWFscyAgDQoyLiBudW1EYXRhIGlzIG11dGF0ZWQgdG8gYXBwbHkgbXVsdGlwbGUgdHJhbnNmb3JtYXRpb25zIG9uIGFsbCBjb2x1bW5zLCBuYW1lbHkgbG9nLCBsb2cxMCwgc3FydCwgc3F1YXJlLCByZWNpcHJvY2FsLCBCb3hDb3guIFJlbGV2YW50IGNvbHVtbnMgYXJlIGNyZWF0ZWQgYnkgYXBwcm9wcmlhdGUgbmFtaW5nLiAgIA0KXCAgIFwgICBcICAgXCAgIFwgICpUaGUgcmVxdWlyZW1lbnQgb2YgY3JlYXRpbmcgYSBuZXcgY29sdW1uIGluIHRoZSBhc3NpZ25tZW50IGlzIGFsc28gZG9uZSBoZXJlKg0KMy4gQWxsIHZhcmlhYmxlcyBhcmUgdmlld2VkIHVuZGVyIHRoZSBoaXN0b2dyYW0gbGVucy4gRHVlIHRvIHRoZSBwYWdlIGxpbWl0LCBvbmx5IDMgcHJpbWFyeSBjb2x1bW5zIGFyZSBkaXNwbGF5ZWQgaW4gdGhlIHJlcG9ydCAoSGVpZ2h0LCBXaWR0aCBhbmQgRGVwdGgpICANCjQuIE9ubHkgbG9nKERlcHRoKSBzZWVtIHRvIGhhdmUgdHJhbnNmb3JtZWQgdGhlIHZhcmlhYmxlIHRvd2FyZHMgYSBub3JtYWwgZGlzdHJpYnV0aW9uIGFuZCBpcyB2aWV3ZWQgd2l0aCBhIGhpc3RvZ3JhbS4gIA0KNS4gRGVwdGggaXMgbXV0YXRlZCBpbnRvIGxvZyhEZXB0aCkgIA0KNi4gT3RoZXIgdmFyaWFibGVzIGNhbiBiZSB0cmFuc2Zvcm1lZCBiYXNlZCBvbiB0aGVpciBpbnZvbHZlbWVudCBpbiBtb2RlbGluZyBhY3Rpdml0aWVzICANCjcuIEFkZGl0aW9uYWwgY29tbWVudHMgaW4gY29kZSBjaHVua3MgIA0KDQoNCmBgYHtyIFRyYW5zZm9ybWF0aW9uLCBmaWcua2VlcD0nbGFzdCcsIGZpZy5oZWlnaHQ9NX0NCg0KI1RyYW5zZm9ybWF0aW9uczogVGhlcmUgYXJlIDYgbnVtZXJpYyBjb2x1bW5zIGFuZCBhcmUgc3Vic2V0IHRvICdudW1EYXRhJyBkYXRhZnJhbWUNCiNmb3IgdHJhbnNmb3JtYXRpb24gYW5kIGNvbXBhcmluZyB0byBub3JtYWwgZGlzdHJpYnV0aW9uDQpudW1EYXRhIDwtIG1lcmdlZEFydCAlPiUgc2VsZWN0X2lmKC5wcmVkaWNhdGUgPSBpcy5udW1lcmljKSAjY3JlYXRlIG51bURhdGEgdGVtcG9yYXJpbHkgZm9yIGNoZWNraW5nIHRyYW5zZm9ybWF0aW9ucw0KDQojY3JlYXRlIGluY3JlbWVudGFsIGRhdGFmcmFtZSBhdHRlbXB0aW5nIDYgaW1wb3J0YW50IHRyYW5zZm9ybWF0aW9uIGZ1bmN0aW9ucyBvbiBhbGwgdmFyaWFibGVzDQpudW1EYXRhIDwtICANCiAgbnVtRGF0YSAlPiUgbXV0YXRlKGFjcm9zcyguY29scyA9IGMoMTo2KSwgLmZucyA9IGxvZywgLm5hbWVzID0gImxvZ197Y29sfSIpKSAlPiUNCiAgbXV0YXRlKGFjcm9zcyguY29scyA9IGMoMTo2KSwgLmZucyA9IGxvZzEwLCAubmFtZXMgPSAibG9nMTBfe2NvbH0iKSkgJT4lDQogIG11dGF0ZShhY3Jvc3MoLmNvbHMgPSBjKDE6NiksIC5mbnMgPSBzcXJ0LCAubmFtZXMgPSAic3FydF97Y29sfSIpKSAlPiUNCiAgbXV0YXRlKGFjcm9zcyguY29scyA9IGMoMTo2KSwgLmZucyA9IGZ1bmN0aW9uKHgpIHheMiwgLm5hbWVzID0gInNxX3tjb2x9IikpICU+JQ0KICBtdXRhdGUoYWNyb3NzKC5jb2xzID0gYygxOjYpLCAuZm5zID0gZnVuY3Rpb24oeCkgMS94LCAubmFtZXMgPSAicmVjaXBfe2NvbH0iKSkgJT4lDQogIG11dGF0ZShhY3Jvc3MoLmNvbHMgPSBjKDE6NiksIC5mbnMgPSBCb3hDb3gsIC5uYW1lcyA9ICJCb3hDb3hfe2NvbH0iLCBsYW1iZGE9ImF1dG8iKSkNCg0KI0FsbCB0cmFuc2Zvcm1lZCB2YXJpYWJsZXMgYXJlIHZpZXdlZCB1bmRlciBhIGhpc3RvZ3JhbSB0byBpZGVudGlmeSBjbG9zZW5lc3MgdG8gYSBub3JtYWwgZGlzdHJpYnV0aW9uLiBOb3QgZGlzcGxheWVkIGluZGl2aWR1YWxseSBkdWUgdG8gcGFnZSBsaW1pdCBvbiB0aGUgYXNzaWdubWVudCAoZmV3IGltcG9ydGFudCBvbmVzIGRpc3BsYXllZCBiZWxvdykNCiN2YXJpYWJsZXMgdGhhdCBzbGlnaHRseSByZXNlbWJsZSBub3JtYWwgZGlzdHJpYnV0aW9uIGFyZSBsb2dfRGVwdGguIFNPIHRyYW5zZm9ybSBEZXB0aCB0byBsb2dfRGVwdGggYW5kIHRoZW4gZHJvcCBEZXB0aA0KI090aGVyIHZhcmlhYmxlcyBkaWRuJ3Qgc2VlbSB0byByZXNlbWJsZSBub3JtYWwgZGlzdHJpYnV0aW9ucyBhZnRlciB0cmFuc2Zvcm1hdGlvbnMgYW5kIGFyZSBub3QgY2hhbmdlZCAoVGhpcyBtaWdodCBiZSBkdWUgdG8gdGhlICdub3QgY3VyYXRvciBhcHByb3ZlZCcgc3RhdHVzIG9mIG1hbnkgYXJ0d29ya3Mgc2luY2UgdGhlIG11c2V1bSBpcyBpbiB0aGUgcHJvY2VzcyBvZiBmaWxsaW5nIGluIG1pc3NpbmcgZGV0YWlscyBjdXJyZW50bHkpDQpwYXIobWZyb3cgPSBjKDMsMSkpDQpudW1EYXRhJGBIZWlnaHQgKGNtKWAgJT4lIGhpc3QoeGxhYiA9ICJIZWlnaHQiKSAjQ2hlY2sgb3JpZ2luYWwgSGVpZ2h0IGRpc3RyaWJ1dGlvbiBpbiBib3hwbG90DQpudW1EYXRhJGBXaWR0aCAoY20pYCAlPiUgaGlzdCh4bGFiID0gIldpZHRoIikgI0NoZWNrIG9yaWdpbmFsIFdpZHRoIGRpc3RyaWJ1dGlvbiBpbiBib3hwbG90DQpudW1EYXRhJGBEZXB0aCAoY20pYCAlPiUgaGlzdCh4bGFiID0gIkRlcHRoIikgI0NoZWNrIG9yaWdpbmFsIERlcHRoIGRpc3RyaWJ1dGlvbiBpbiBib3hwbG90DQpwYXIobWZyb3cgPSBjKDEsMSkpDQpudW1EYXRhJGBsb2cxMF9EZXB0aCAoY20pYCAlPiUgaGlzdCh4bGFiID0gIkxvZyBEZXB0aCIsIGJyZWFrcyA9IDI1KSAjQ2hlY2sgbG9nKERlcHRoKSBkaXN0cmlidXRpb24gaW4gYm94cGxvdA0KDQptZXJnZWRBcnQ8LSBtZXJnZWRBcnQgJT4lIG11dGF0ZShgRGVwdGggKGNtKWAgPSBsb2coYERlcHRoIChjbSlgKSkgI0NoYW5nZSBEZXB0aCB0byBsb2coRGVwdGgpIGFzIGl0IGlzIG11Y2ggY2xvc2VyIHRvIG5vcm1hbCBkaXN0cmlidXRpb24gKGFsdGhvdWdoIG5vdCBwZXJmZWN0KS4gVGhlIG90aGVycyBjYW4gYWxzbyBiZSB0cmFuc2Zvcm1lZCBiYXNlZCBvbiByZXF1aXJlZCBvZiBhbnkgZnVydGhlciBtb2RlbGluZyBhY3Rpdml0aWVzLiBBTHRlcm5hdGl2ZWx5IHNjYWxpbmcgb3IgY2VudGVyaW5nIGNhbiBiZSBpbXBsZW1lbnRlZCBmb3IgbW9kZWxpbmcuDQpgYGANCg0KUmVmZXJlbmNlcyBmb3IgdGhlIHByb2plY3Q6ICANCmh0dHBzOi8vZ2l0aHViLmNvbS9NdXNldW1vZk1vZGVybkFydC9jb2xsZWN0aW9uICANCmh0dHA6Ly9yYXJlLXBob2VuaXgtMTYxNjEwLmFwcHNwb3QuY29tL3NlY3VyZWQvaW5kZXguaHRtbCAgDQoNCjxicj4NCjxicj4NCg==