Required packages

library(forecast)
library(tidyr)
library(dplyr)
library(stringr)
library(outliers)
library(editrules)

Executive Summary

In this report, it shows step by step how to do data preprocessing before jumping into data modelling in an data science project. We use Adult dataset from UCI to implement this part. There are five main steps considered in this report. First of all, we should discover variable types and meaning of dataset. Secondly, we check whether dataset is tidy or not. Thirdly, we deal with issues related to typing errors such as white space, wrong grammar. In this step, we categorize some variables into groups so that it would be easier to build data model later on. The Fourth step checks whether missing values or inconsistent errors exists in the dataset. We verify the values of nummeric variables such Age, Capital Loss, Capital gain and Hours per week. Finally, we do transformation for Capital and Hours per week by using reciprocal and boxcox methods, respectively.

Data

The Adult dataset is used to build classifiers to predict whether an individual has income more than or less than 50K per year base on the 1994 US Cencus dataset. There are total 48842 records divided into two datasets “adult.data” and “adult.test”. Both datasets have 14 descriptive features and 1 target features. We will merge these two datasets into one for Data Preprocessing Steps.

Data Description:

Feature Name Description
Age Continous
Workclass Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked
fnlwgt Continous
Education Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters, 1st-4th, 10th, Doctorate, 5th-6th, Preschool.
Education-num Continuous
Marital-status Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse.
Occupation Tech-support, Craft-repair, Other-service, Sales, Exec-managerial, Prof-specialty, Handlers-cleaners, Machine-op-inspct, Adm-clerical, Farming-fishing, Transport-moving, Priv-house-serv, Protective-serv, Armed-Forces.
Relationship Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried.
Race White, Asian-Pac-Islander, Amer-Indian-Eskimo, Other, Black.
Sex Female, Male
Capital-loss Continuous
Capital-gain Continuous
Hours-per-week Continous
Native-country United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan, Greece, South, China, Cuba, Iran, Honduras, Philippines, Italy, Poland, Jamaica, Vietnam, Mexico, Portugal, Ireland, France, Dominican-Republic, Laos, Ecuador, Taiwan, Haiti, Columbia, Hungary, Guatemala, Nicaragua, Scotland, Thailand, Yugoslavia, El-Salvador, Trinadad&Tobago, Peru, Hong, Holand-Netherlands.
Income >50K , <=50K
attributes_name <- c("age", "workclass", "fnlwgt", "education", "education-num", "marital-status", "occupation", "relationship", "race","sex", "capital-gain", "capital-loss","hours-per-week", "native countries", "income")
adult_training <- read.delim("adult.data.txt", sep = ",", header = FALSE, col.names = attributes_name)
head(adult_training)
adult_test <- read.delim("adult.test.txt", sep=",", header = FALSE, col.names = attributes_name)
head(adult_test)

Obviously, adult_test dataset got wrong value in first row. So therefore we need to remove this line before merging.

# Remove first row in adult_test
adult_test <- adult_test[-1,]
# Merge two dataset into one
adult <- rbind(adult_training, adult_test)
head(adult)

Understand

# Check dimension of data
dim(adult)
[1] 48842    15

It shows that there are totally 48842 records and 15 attributes

# Summary of dataset
summary(adult)
     age                        workclass         fnlwgt                education     education.num                  marital.status 
 Length:48842        Private         :33906   Min.   :  12285    HS-grad     :15784   Min.   : 1.00    Married-civ-spouse   :22379  
 Class :character    Self-emp-not-inc: 3862   1st Qu.: 117551    Some-college:10878   1st Qu.: 9.00    Never-married        :16117  
 Mode  :character    Local-gov       : 3136   Median : 178145    Bachelors   : 8025   Median :10.00    Divorced             : 6633  
                     ?               : 2799   Mean   : 189664    Masters     : 2657   Mean   :10.08    Separated            : 1530  
                     State-gov       : 1981   3rd Qu.: 237642    Assoc-voc   : 2061   3rd Qu.:12.00    Widowed              : 1518  
                     Self-emp-inc    : 1695   Max.   :1490400    11th        : 1812   Max.   :16.00    Married-spouse-absent:  628  
                    (Other)          : 1463                     (Other)      : 7625                   (Other)               :   37  
            occupation             relationship                    race            sex         capital.gain    capital.loss    hours.per.week 
  Prof-specialty : 6172    Husband       :19716    Amer-Indian-Eskimo:  470    Female:16192   Min.   :    0   Min.   :   0.0   Min.   : 1.00  
  Craft-repair   : 6112    Not-in-family :12583    Asian-Pac-Islander: 1519    Male  :32650   1st Qu.:    0   1st Qu.:   0.0   1st Qu.:40.00  
  Exec-managerial: 6086    Other-relative: 1506    Black             : 4685          :    0   Median :    0   Median :   0.0   Median :40.00  
  Adm-clerical   : 5611    Own-child     : 7581    Other             :  406                   Mean   : 1079   Mean   :  87.5   Mean   :40.42  
  Sales          : 5504    Unmarried     : 5125    White             :41762                   3rd Qu.:    0   3rd Qu.:   0.0   3rd Qu.:45.00  
  Other-service  : 4923    Wife          : 2331                      :    0                   Max.   :99999   Max.   :4356.0   Max.   :99.00  
 (Other)         :14434                  :    0                                                                                               
       native.countries     income     
  United-States:43832    <=50K :24720  
  Mexico       :  951    >50K  : 7841  
  ?            :  857          :    0  
  Philippines  :  295    <=50K.:12435  
  Germany      :  206    >50K. : 3846  
  Puerto-Rico  :  184                  
 (Other)       : 2517                  
# Check type of variables:
lapply(adult, typeof)
$workclass
[1] "integer"

$education
[1] "integer"

$marital.status
[1] "integer"

$occupation
[1] "integer"

$relationship
[1] "integer"

$race
[1] "integer"

$sex
[1] "integer"

$native.countries
[1] "integer"

$income
[1] "integer"

$age
[1] "integer"

$fnlwgt
[1] "integer"

$capital.gain
[1] "integer"

$capital.loss
[1] "integer"

$hours.per.week
[1] "double"

$capital
[1] "integer"

$age_group
[1] "integer"

Type of variables such as Age, Workclass, Education, Marital.Status, Relationship,Occupation, Sex, Native Countries and Income look different compared with data description.

# Check class of variables:
lapply(adult, class)
$workclass
[1] "factor"

$education
[1] "factor"

$marital.status
[1] "factor"

$occupation
[1] "factor"

$relationship
[1] "factor"

$race
[1] "factor"

$sex
[1] "factor"

$native.countries
[1] "factor"

$income
[1] "factor"

$age
[1] "integer"

$fnlwgt
[1] "integer"

$capital.gain
[1] "integer"

$capital.loss
[1] "integer"

$hours.per.week
[1] "numeric"

$capital
[1] "integer"

$age_group
[1] "factor"
# Check levels of factor variables:
fac_cols <- sapply(adult, is.factor)
lapply(adult[, fac_cols], levels)
$workclass
 [1] " ?"                " Federal-gov"      " Local-gov"        " Never-worked"     " Private"          " Self-emp-inc"     " Self-emp-not-inc"
 [8] " State-gov"        " Without-pay"      ""                 

$education
 [1] " 10th"         " 11th"         " 12th"         " 1st-4th"      " 5th-6th"      " 7th-8th"      " 9th"          " Assoc-acdm"  
 [9] " Assoc-voc"    " Bachelors"    " Doctorate"    " HS-grad"      " Masters"      " Preschool"    " Prof-school"  " Some-college"
[17] ""             

$marital.status
[1] " Divorced"              " Married-AF-spouse"     " Married-civ-spouse"    " Married-spouse-absent" " Never-married"        
[6] " Separated"             " Widowed"               ""                      

$occupation
 [1] " ?"                 " Adm-clerical"      " Armed-Forces"      " Craft-repair"      " Exec-managerial"   " Farming-fishing"  
 [7] " Handlers-cleaners" " Machine-op-inspct" " Other-service"     " Priv-house-serv"   " Prof-specialty"    " Protective-serv"  
[13] " Sales"             " Tech-support"      " Transport-moving"  ""                  

$relationship
[1] " Husband"        " Not-in-family"  " Other-relative" " Own-child"      " Unmarried"      " Wife"           ""               

$race
[1] " Amer-Indian-Eskimo" " Asian-Pac-Islander" " Black"              " Other"              " White"              ""                   

$sex
[1] " Female" " Male"   ""       

$native.countries
 [1] " ?"                          " Cambodia"                   " Canada"                     " China"                     
 [5] " Columbia"                   " Cuba"                       " Dominican-Republic"         " Ecuador"                   
 [9] " El-Salvador"                " England"                    " France"                     " Germany"                   
[13] " Greece"                     " Guatemala"                  " Haiti"                      " Holand-Netherlands"        
[17] " Honduras"                   " Hong"                       " Hungary"                    " India"                     
[21] " Iran"                       " Ireland"                    " Italy"                      " Jamaica"                   
[25] " Japan"                      " Laos"                       " Mexico"                     " Nicaragua"                 
[29] " Outlying-US(Guam-USVI-etc)" " Peru"                       " Philippines"                " Poland"                    
[33] " Portugal"                   " Puerto-Rico"                " Scotland"                   " South"                     
[37] " Taiwan"                     " Thailand"                   " Trinadad&Tobago"            " United-States"             
[41] " Vietnam"                    " Yugoslavia"                 ""                           

$income
[1] " <=50K"  " >50K"   ""        " <=50K." " >50K." 

It is clearly to see that there are some typing errors in this dataset for factor variables.

Tidy & Manipulate Data I

Check if the data conforms the tidy data principles.

names(adult)
 [1] "age"              "workclass"        "fnlwgt"           "education"        "education.num"    "marital.status"   "occupation"      
 [8] "relationship"     "race"             "sex"              "capital.gain"     "capital.loss"     "hours.per.week"   "native.countries"
[15] "income"          
head(adult)

This dataset is considered as tidy dataset. However there is only one thing needed to be checked about the relationship between education and education.num variables

# Check relationship between education and education.num
adult %>% distinct(education, education.num)  

It is clearly that Education.Num is an ID number of education. Therefore, we can remove one of them. In this assignment, I will drop education.num

# drop education.num variable
adult$education.num <- NULL

Tidy & Manipulate Data II

Create/mutate at least one variable from the existing variables (minimum requirement #6). In addition to the R codes and outputs, explain everything that you do in this step..

# Create capital variable which is the difference betwwen capital-gain and capital-loss
adult <- adult %>% mutate(capital = capital.gain - capital.loss)

Clean Data

# List down Factor Columns in dataframe & Trim string in factor columns
fac_cols <- sapply(adult, is.factor)
adult <- data.frame(cbind(sapply(adult[,fac_cols], trimws, which="both"), adult[,!fac_cols]))
adult <- adult %>% mutate(workclass = ifelse(grepl(".gov$", str_trim(workclass)), "Gov", 
                                                 ifelse(grepl("^Self.",str_trim(workclass)),"Self-emp",
                                                        ifelse(grepl("^Private$", str_trim(workclass)),"Private", "Other"))))
adult$workclass <- as.factor(adult$workclass)
levels(adult$workclass)
[1] "Gov"      "Other"    "Private"  "Self-emp"
adult <- adult %>% mutate(education = ifelse(grepl(".th$|^Preschool$", (education)), "Before-Highschool",
                                                     ifelse(grepl("^Assoc.", (education)),"Associate",
                                                            ifelse(grepl("^Masters$|^Doctorate$|^Pro.",(education)), "Post-Graduate", 
                                                                   as.character((education))))))
adult$education <- as.factor(adult$education)
levels(adult$education)
[1] "Associate"         "Bachelors"         "Before-Highschool" "HS-grad"           "Post-Graduate"     "Some-college"     
adult <- adult %>% mutate(marital.status = ifelse(grepl("^Married.", marital.status), "Married", as.character(marital.status)))
adult$marital.status <- as.factor(adult$marital.status)
levels(adult$marital.status)
[1] "Divorced"      "Married"       "Never-married" "Separated"     "Widowed"      
adult <- adult %>% mutate(income = ifelse(grepl("^<=50K.$", income), "<=50K",
                                                   ifelse(grepl("^>50K.$", income),">50K", as.character(income))))
adult$income <- as.factor(adult$income)
levels(adult$income)
[1] "<=50K" ">50K" 
# Convert Age character into numeric because Age has character type as default in dataset.
adult$age <- as.integer(adult$age)
# Categorize Age into 4 groups
adult<- adult %>% mutate(age_group = ifelse(age <=30, "<=30",
                                               ifelse(age>30 & age <=45, "30-45",
                                                      ifelse(age>45 & age <=60,"45-60",
                                                             ">60"))))
adult$age_group <- as.factor(adult$age_group)
# Check levels result of Age after processing
levels(adult$age_group)
[1] "<=30"  ">60"   "30-45" "45-60"
adult<- adult %>% mutate(native.countries = ifelse(grepl("United.",native.countries), "USA", "Non-USA"))
adult$native.countries <- as.factor(adult$native.countries)
levels(adult$native.countries)
[1] "Non-USA" "USA"    
summary(adult)
    workclass                 education           marital.status            occupation            relationship                   race      
 Gov     : 6549   Associate        : 3662   Divorced     : 6633   Prof-specialty : 6172   Husband       :19716   Amer-Indian-Eskimo:  470  
 Other   : 2830   Bachelors        : 8025   Married      :23044   Craft-repair   : 6112   Not-in-family :12583   Asian-Pac-Islander: 1519  
 Private :33906   Before-Highschool: 6408   Never-married:16117   Exec-managerial: 6086   Other-relative: 1506   Black             : 4685  
 Self-emp: 5557   HS-grad          :15784   Separated    : 1530   Adm-clerical   : 5611   Own-child     : 7581   Other             :  406  
                  Post-Graduate    : 4085   Widowed      : 1518   Sales          : 5504   Unmarried     : 5125   White             :41762  
                  Some-college     :10878                         Other-service  : 4923   Wife          : 2331                             
                                                                  (Other)        :14434                                                    
     sex        native.countries   income           age            fnlwgt         capital.gain    capital.loss    hours.per.week 
 Female:16192   Non-USA: 5010    <=50K:37155   Min.   :17.00   Min.   :  12285   Min.   :    0   Min.   :   0.0   Min.   : 1.00  
 Male  :32650   USA    :43832    >50K :11687   1st Qu.:28.00   1st Qu.: 117551   1st Qu.:    0   1st Qu.:   0.0   1st Qu.:40.00  
                                               Median :37.00   Median : 178145   Median :    0   Median :   0.0   Median :40.00  
                                               Mean   :38.64   Mean   : 189664   Mean   : 1079   Mean   :  87.5   Mean   :40.42  
                                               3rd Qu.:48.00   3rd Qu.: 237642   3rd Qu.:    0   3rd Qu.:   0.0   3rd Qu.:45.00  
                                               Max.   :90.00   Max.   :1490400   Max.   :99999   Max.   :4356.0   Max.   :99.00  
                                                                                                                                 
    capital        age_group    
 Min.   :-4356.0   <=30 :15793  
 1st Qu.:    0.0   >60  : 3606  
 Median :    0.0   30-45:18505  
 Mean   :  991.6   45-60:10938  
 3rd Qu.:    0.0                
 Max.   :99999.0                
                                

Scan I

Scan the data for missing values, inconsistencies and obvious errors. In this step, you should fulfil the minimum requirement #7. In addition to the R codes and outputs, explain how you dealt with these values.

** Check missing values **

colnames(adult)[apply(is.na(adult),2,any)]
character(0)

There is no missing values in all variables

** Check Infinite vaules for nummeric variables

num_cols <- sapply(adult, is.numeric)
is.special <- function(x){
  if (is.numeric(x)) is.infinite(x)
}
head(sapply(adult[,num_cols], is.special))
       age fnlwgt capital.gain capital.loss hours.per.week capital
[1,] FALSE  FALSE        FALSE        FALSE          FALSE   FALSE
[2,] FALSE  FALSE        FALSE        FALSE          FALSE   FALSE
[3,] FALSE  FALSE        FALSE        FALSE          FALSE   FALSE
[4,] FALSE  FALSE        FALSE        FALSE          FALSE   FALSE
[5,] FALSE  FALSE        FALSE        FALSE          FALSE   FALSE
[6,] FALSE  FALSE        FALSE        FALSE          FALSE   FALSE

** Check inconsistence error for Age variable

Age_rule <- editset(c("age >15", "age <= 100"))
violate <- violatedEdits(Age_rule, adult)
summary(violate)
No violations detected, 0 checks evaluated to NA
NULL

There are no violation for Age

** Check inconsistence error for Capital Gain variable

cap_gain_rule <- editset("capital.gain >=0")
violateCapGain <- violatedEdits(cap_gain_rule, adult)
summary(violateCapGain)
No violations detected, 0 checks evaluated to NA
NULL

** Check inconsistence error for Capital Loss variable

cap_loss_rule <- editset("capital.loss >=0")
violateCapLoss <- violatedEdits(cap_loss_rule, adult)
summary(violateCapLoss)
No violations detected, 0 checks evaluated to NA
NULL

** Check inconsistence error for Hours.Per.Week variable

hour_rule <- editset(c("hours.per.week > 0", "hours.per.week <160"))
violateHours <- violatedEdits(hour_rule, adult)
summary(violateHours)
No violations detected, 0 checks evaluated to NA
NULL

Scan II

Scan the numeric data for outliers. In this step, we only consider outliers for variables including “Capital” (because it is the difference between captial gain and capital loss) and “Hours.per.week”. For “fnlwgt” variable, it stands for “Final Weight” defined by the US Census. For “Age” variable, because it does not violate rule which we did check in previous part and we did categorize Age into 4 groups. Hence we do not check outlier for “Age” and “fnlwgt”

Check outliers of Hours Per Week

# Draw Boxplot chart of this variable
boxplot(x = adult$hours.per.week, main = "Box Plot of Hours Per Week", ylab= "Hours per week")

# Using z-score to check outliers
z.scores.hours<- adult$hours.per.week %>% scores(type="z")
z.scores.hours %>% summary()
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-3.18142 -0.03409 -0.03409  0.00000  0.36942  4.72726 
length(which(abs(z.scores.hours)>3))
[1] 681

There are 681 outliers for this variable in dataset. To deal with this, we impute outliers with mean value

adult$hours.per.week[which(abs(z.scores.hours)>3)] <- mean(adult$hours.per.week, na.rm = TRUE)
# Using boxplot to see changes after imputing outliers
boxplot(x = adult$hours.per.week, main = "Box Plot of Hours Per Week After Imputing Outliers", ylab= "Hours per week")

Check outliers for Capital

boxplot(x = adult$capital, main = "Box Plot of Capital", ylab= "Capital")

Obviously, the value 99999 is as an outlier of capital. However, we skip this value because it can happen in reality for People’s Income.

Transform

Transform Capital variable

hist(adult$capital, main = "Histogram of Capital")

Looking at histogram of capital, it has right skew distribution. Therefore we use reciprocal tranformation to reduce the right skewness.

# Apply Reciprocal Transformation for Capital
capital_recip <- 1/adult$capital
hist(capital_recip)

Transform Hours.Per.Week variable

hist(adult$hours.per.week, main = "Histogram of Hours Per Week", xlab="Hours per week")

# Using BoxCox to do transformation for Hours.per.week
boxcox_hours <- BoxCox(adult$hours.per.week, lambda = "auto")
hist(boxcox_hours)



LS0tDQp0aXRsZTogIk1BVEgyMzQ5IFNlbWVzdGVyIDEsIDIwMTgiDQphdXRob3I6ICJIVVlOSCBBSSBMT0FOIC0gczM2NTU0NjEiDQpzdWJ0aXRsZTogQXNzaWdubWVudCAzDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQojIyBSZXF1aXJlZCBwYWNrYWdlcyANCg0KYGBge3IgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGZvcmVjYXN0KQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KG91dGxpZXJzKQ0KbGlicmFyeShlZGl0cnVsZXMpDQpgYGANCg0KDQojIyBFeGVjdXRpdmUgU3VtbWFyeSANCg0KSW4gdGhpcyByZXBvcnQsIGl0IHNob3dzIHN0ZXAgYnkgc3RlcCBob3cgdG8gZG8gZGF0YSBwcmVwcm9jZXNzaW5nIGJlZm9yZSBqdW1waW5nIGludG8gZGF0YSBtb2RlbGxpbmcgaW4gYW4gZGF0YSBzY2llbmNlIHByb2plY3QuIFdlIHVzZSAqKkFkdWx0KiogZGF0YXNldCBmcm9tIFtVQ0ldKGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9hZHVsdCkgdG8gaW1wbGVtZW50IHRoaXMgcGFydC4gVGhlcmUgYXJlIGZpdmUgbWFpbiBzdGVwcyBjb25zaWRlcmVkIGluIHRoaXMgcmVwb3J0LiBGaXJzdCBvZiBhbGwsIHdlIHNob3VsZCBkaXNjb3ZlciB2YXJpYWJsZSB0eXBlcyBhbmQgbWVhbmluZyBvZiBkYXRhc2V0LiBTZWNvbmRseSwgd2UgY2hlY2sgd2hldGhlciBkYXRhc2V0IGlzIHRpZHkgb3Igbm90LiBUaGlyZGx5LCB3ZSBkZWFsIHdpdGggaXNzdWVzIHJlbGF0ZWQgdG8gdHlwaW5nIGVycm9ycyBzdWNoIGFzIHdoaXRlIHNwYWNlLCB3cm9uZyBncmFtbWFyLiBJbiB0aGlzIHN0ZXAsIHdlIGNhdGVnb3JpemUgc29tZSB2YXJpYWJsZXMgaW50byBncm91cHMgc28gdGhhdCBpdCB3b3VsZCBiZSBlYXNpZXIgdG8gYnVpbGQgZGF0YSBtb2RlbCBsYXRlciBvbi4gVGhlIEZvdXJ0aCBzdGVwIGNoZWNrcyB3aGV0aGVyIG1pc3NpbmcgdmFsdWVzIG9yIGluY29uc2lzdGVudCBlcnJvcnMgZXhpc3RzIGluIHRoZSBkYXRhc2V0LiBXZSB2ZXJpZnkgdGhlIHZhbHVlcyBvZiBudW1tZXJpYyB2YXJpYWJsZXMgc3VjaCBBZ2UsIENhcGl0YWwgTG9zcywgQ2FwaXRhbCBnYWluIGFuZCBIb3VycyBwZXIgd2Vlay4gRmluYWxseSwgd2UgZG8gdHJhbnNmb3JtYXRpb24gZm9yIENhcGl0YWwgYW5kIEhvdXJzIHBlciB3ZWVrIGJ5IHVzaW5nIHJlY2lwcm9jYWwgYW5kIGJveGNveCBtZXRob2RzLCByZXNwZWN0aXZlbHkuIA0KDQojIyBEYXRhIA0KDQpUaGUgX18qQWR1bHQqX18gZGF0YXNldCBpcyB1c2VkIHRvIGJ1aWxkIGNsYXNzaWZpZXJzIHRvIHByZWRpY3Qgd2hldGhlciBhbiBpbmRpdmlkdWFsIGhhcyBpbmNvbWUgIG1vcmUgdGhhbiBvciBsZXNzIHRoYW4gNTBLIHBlciB5ZWFyIGJhc2Ugb24gdGhlIDE5OTQgVVMgQ2VuY3VzIGRhdGFzZXQuIFRoZXJlIGFyZSB0b3RhbCA0ODg0MiByZWNvcmRzIGRpdmlkZWQgaW50byB0d28gZGF0YXNldHMgX18iYWR1bHQuZGF0YSJfXyBhbmQgX18iYWR1bHQudGVzdCJfXy4gQm90aCBkYXRhc2V0cyBoYXZlIDE0IGRlc2NyaXB0aXZlIGZlYXR1cmVzIGFuZCAxIHRhcmdldCBmZWF0dXJlcy4gV2Ugd2lsbCBtZXJnZSB0aGVzZSB0d28gZGF0YXNldHMgaW50byBvbmUgZm9yIERhdGEgUHJlcHJvY2Vzc2luZyBTdGVwcy4NCg0KKipEYXRhIERlc2NyaXB0aW9uOioqDQoNCkZlYXR1cmUgTmFtZSAgfCBEZXNjcmlwdGlvbg0KLS0tLS0tLS0tLS0tLS18IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpBZ2UgICAgICAgICAgIHwgQ29udGlub3VzDQpXb3JrY2xhc3MgICAgIHwgUHJpdmF0ZSwgU2VsZi1lbXAtbm90LWluYywgU2VsZi1lbXAtaW5jLCBGZWRlcmFsLWdvdiwgTG9jYWwtZ292LCBTdGF0ZS1nb3YsIFdpdGhvdXQtcGF5LCBOZXZlci13b3JrZWQNCmZubHdndCAgICAgICAgfCBDb250aW5vdXMNCkVkdWNhdGlvbiAgICAgfCBCYWNoZWxvcnMsIFNvbWUtY29sbGVnZSwgMTF0aCwgSFMtZ3JhZCwgUHJvZi1zY2hvb2wsIEFzc29jLWFjZG0sIEFzc29jLXZvYywgOXRoLCA3dGgtOHRoLCAxMnRoLCBNYXN0ZXJzLCAxc3QtNHRoLCAxMHRoLCBEb2N0b3JhdGUsIDV0aC02dGgsIFByZXNjaG9vbC4NCkVkdWNhdGlvbi1udW0gfCBDb250aW51b3VzDQpNYXJpdGFsLXN0YXR1cyAgfCBNYXJyaWVkLWNpdi1zcG91c2UsIERpdm9yY2VkLCBOZXZlci1tYXJyaWVkLCBTZXBhcmF0ZWQsIFdpZG93ZWQsIE1hcnJpZWQtc3BvdXNlLWFic2VudCwgTWFycmllZC1BRi1zcG91c2UuIA0KT2NjdXBhdGlvbiAgICB8IFRlY2gtc3VwcG9ydCwgQ3JhZnQtcmVwYWlyLCBPdGhlci1zZXJ2aWNlLCBTYWxlcywgRXhlYy1tYW5hZ2VyaWFsLCBQcm9mLXNwZWNpYWx0eSwgSGFuZGxlcnMtY2xlYW5lcnMsIE1hY2hpbmUtb3AtaW5zcGN0LCBBZG0tY2xlcmljYWwsIEZhcm1pbmctZmlzaGluZywgVHJhbnNwb3J0LW1vdmluZywgUHJpdi1ob3VzZS1zZXJ2LCBQcm90ZWN0aXZlLXNlcnYsIEFybWVkLUZvcmNlcy4NClJlbGF0aW9uc2hpcCAgfCBXaWZlLCBPd24tY2hpbGQsIEh1c2JhbmQsIE5vdC1pbi1mYW1pbHksIE90aGVyLXJlbGF0aXZlLCBVbm1hcnJpZWQuIA0KUmFjZSAgICB8IFdoaXRlLCBBc2lhbi1QYWMtSXNsYW5kZXIsIEFtZXItSW5kaWFuLUVza2ltbywgT3RoZXIsIEJsYWNrLiANClNleCAgICAgfCBGZW1hbGUsIE1hbGUNCkNhcGl0YWwtbG9zcyB8IENvbnRpbnVvdXMNCkNhcGl0YWwtZ2FpbiAgfCBDb250aW51b3VzDQpIb3Vycy1wZXItd2VlayB8IENvbnRpbm91cw0KTmF0aXZlLWNvdW50cnkgIHwgVW5pdGVkLVN0YXRlcywgQ2FtYm9kaWEsIEVuZ2xhbmQsIFB1ZXJ0by1SaWNvLCBDYW5hZGEsIEdlcm1hbnksIE91dGx5aW5nLVVTKEd1YW0tVVNWSS1ldGMpLCBJbmRpYSwgSmFwYW4sIEdyZWVjZSwgU291dGgsIENoaW5hLCBDdWJhLCBJcmFuLCBIb25kdXJhcywgUGhpbGlwcGluZXMsIEl0YWx5LCBQb2xhbmQsIEphbWFpY2EsIFZpZXRuYW0sIE1leGljbywgUG9ydHVnYWwsIElyZWxhbmQsIEZyYW5jZSwgRG9taW5pY2FuLVJlcHVibGljLCBMYW9zLCBFY3VhZG9yLCBUYWl3YW4sIEhhaXRpLCBDb2x1bWJpYSwgSHVuZ2FyeSwgR3VhdGVtYWxhLCBOaWNhcmFndWEsIFNjb3RsYW5kLCBUaGFpbGFuZCwgWXVnb3NsYXZpYSwgRWwtU2FsdmFkb3IsIFRyaW5hZGFkJlRvYmFnbywgUGVydSwgSG9uZywgSG9sYW5kLU5ldGhlcmxhbmRzLg0KSW5jb21lICAgIHwgPjUwSyAsIDw9NTBLDQoNCg0KYGBge3J9DQphdHRyaWJ1dGVzX25hbWUgPC0gYygiYWdlIiwgIndvcmtjbGFzcyIsICJmbmx3Z3QiLCAiZWR1Y2F0aW9uIiwgImVkdWNhdGlvbi1udW0iLCAibWFyaXRhbC1zdGF0dXMiLCAib2NjdXBhdGlvbiIsICJyZWxhdGlvbnNoaXAiLCAicmFjZSIsInNleCIsICJjYXBpdGFsLWdhaW4iLCAiY2FwaXRhbC1sb3NzIiwiaG91cnMtcGVyLXdlZWsiLCAibmF0aXZlIGNvdW50cmllcyIsICJpbmNvbWUiKQ0KYWR1bHRfdHJhaW5pbmcgPC0gcmVhZC5kZWxpbSgiYWR1bHQuZGF0YS50eHQiLCBzZXAgPSAiLCIsIGhlYWRlciA9IEZBTFNFLCBjb2wubmFtZXMgPSBhdHRyaWJ1dGVzX25hbWUpDQpoZWFkKGFkdWx0X3RyYWluaW5nKQ0KYWR1bHRfdGVzdCA8LSByZWFkLmRlbGltKCJhZHVsdC50ZXN0LnR4dCIsIHNlcD0iLCIsIGhlYWRlciA9IEZBTFNFLCBjb2wubmFtZXMgPSBhdHRyaWJ1dGVzX25hbWUpDQpoZWFkKGFkdWx0X3Rlc3QpDQoNCmBgYA0KT2J2aW91c2x5LCBhZHVsdF90ZXN0IGRhdGFzZXQgZ290IHdyb25nIHZhbHVlIGluIGZpcnN0IHJvdy4gU28gdGhlcmVmb3JlIHdlIG5lZWQgdG8gcmVtb3ZlIHRoaXMgbGluZSBiZWZvcmUgbWVyZ2luZy4NCmBgYHtyfQ0KIyBSZW1vdmUgZmlyc3Qgcm93IGluIGFkdWx0X3Rlc3QNCmFkdWx0X3Rlc3QgPC0gYWR1bHRfdGVzdFstMSxdDQojIE1lcmdlIHR3byBkYXRhc2V0IGludG8gb25lDQphZHVsdCA8LSByYmluZChhZHVsdF90cmFpbmluZywgYWR1bHRfdGVzdCkNCmhlYWQoYWR1bHQpDQpgYGANCg0KIyMgVW5kZXJzdGFuZCANCg0KDQpgYGB7cn0NCiMgQ2hlY2sgZGltZW5zaW9uIG9mIGRhdGENCmRpbShhZHVsdCkNCmBgYA0KDQpJdCBzaG93cyB0aGF0IHRoZXJlIGFyZSB0b3RhbGx5IDQ4ODQyIHJlY29yZHMgYW5kIDE1IGF0dHJpYnV0ZXMNCg0KYGBge3J9DQojIFN1bW1hcnkgb2YgZGF0YXNldA0Kc3VtbWFyeShhZHVsdCkNCmBgYA0KDQpgYGB7cn0NCiMgQ2hlY2sgdHlwZSBvZiB2YXJpYWJsZXM6DQpsYXBwbHkoYWR1bHQsIHR5cGVvZikNCmBgYA0KVHlwZSBvZiB2YXJpYWJsZXMgc3VjaCBhcyBBZ2UsIFdvcmtjbGFzcywgRWR1Y2F0aW9uLCBNYXJpdGFsLlN0YXR1cywgUmVsYXRpb25zaGlwLE9jY3VwYXRpb24sIFNleCwgTmF0aXZlIENvdW50cmllcyBhbmQgSW5jb21lIGxvb2sgZGlmZmVyZW50IGNvbXBhcmVkIHdpdGggZGF0YSBkZXNjcmlwdGlvbi4NCmBgYHtyfQ0KIyBDaGVjayBjbGFzcyBvZiB2YXJpYWJsZXM6DQpsYXBwbHkoYWR1bHQsIGNsYXNzKQ0KYGBgDQoNCg0KYGBge3J9DQojIENoZWNrIGxldmVscyBvZiBmYWN0b3IgdmFyaWFibGVzOg0KZmFjX2NvbHMgPC0gc2FwcGx5KGFkdWx0LCBpcy5mYWN0b3IpDQpsYXBwbHkoYWR1bHRbLCBmYWNfY29sc10sIGxldmVscykNCg0KYGBgDQpJdCBpcyBjbGVhcmx5IHRvIHNlZSB0aGF0IHRoZXJlIGFyZSBzb21lIHR5cGluZyBlcnJvcnMgaW4gdGhpcyBkYXRhc2V0IGZvciBmYWN0b3IgdmFyaWFibGVzLg0KDQojIwlUaWR5ICYgTWFuaXB1bGF0ZSBEYXRhIEkgDQoNCkNoZWNrIGlmIHRoZSBkYXRhIGNvbmZvcm1zIHRoZSB0aWR5IGRhdGEgcHJpbmNpcGxlcy4NCg0KDQpgYGB7cn0NCm5hbWVzKGFkdWx0KQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChhZHVsdCkNCmBgYA0KVGhpcyBkYXRhc2V0IGlzIGNvbnNpZGVyZWQgYXMgdGlkeSBkYXRhc2V0LiBIb3dldmVyIHRoZXJlIGlzIG9ubHkgb25lIHRoaW5nIG5lZWRlZCB0byBiZSBjaGVja2VkIGFib3V0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBlZHVjYXRpb24gYW5kIGVkdWNhdGlvbi5udW0gdmFyaWFibGVzDQpgYGB7cn0NCiMgQ2hlY2sgcmVsYXRpb25zaGlwIGJldHdlZW4gZWR1Y2F0aW9uIGFuZCBlZHVjYXRpb24ubnVtDQphZHVsdCAlPiUgZGlzdGluY3QoZWR1Y2F0aW9uLCBlZHVjYXRpb24ubnVtKSAgDQpgYGANCkl0IGlzIGNsZWFybHkgdGhhdCBFZHVjYXRpb24uTnVtIGlzIGFuIElEIG51bWJlciBvZiBlZHVjYXRpb24uIFRoZXJlZm9yZSwgd2UgY2FuIHJlbW92ZSBvbmUgb2YgdGhlbS4gSW4gdGhpcyBhc3NpZ25tZW50LCBJIHdpbGwgZHJvcCBlZHVjYXRpb24ubnVtDQoNCmBgYHtyfQ0KIyBkcm9wIGVkdWNhdGlvbi5udW0gdmFyaWFibGUNCmFkdWx0JGVkdWNhdGlvbi5udW0gPC0gTlVMTA0KYGBgDQoNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSUkgDQoNCkNyZWF0ZS9tdXRhdGUgYXQgbGVhc3Qgb25lIHZhcmlhYmxlIGZyb20gdGhlIGV4aXN0aW5nIHZhcmlhYmxlcyAobWluaW11bSByZXF1aXJlbWVudCAjNikuIEluIGFkZGl0aW9uIHRvIHRoZSBSIGNvZGVzIGFuZCBvdXRwdXRzLCBleHBsYWluIGV2ZXJ5dGhpbmcgdGhhdCB5b3UgZG8gaW4gdGhpcyBzdGVwLi4NCg0KYGBge3J9DQojIENyZWF0ZSBjYXBpdGFsIHZhcmlhYmxlIHdoaWNoIGlzIHRoZSBkaWZmZXJlbmNlIGJldHd3ZW4gY2FwaXRhbC1nYWluIGFuZCBjYXBpdGFsLWxvc3MNCmFkdWx0IDwtIGFkdWx0ICU+JSBtdXRhdGUoY2FwaXRhbCA9IGNhcGl0YWwuZ2FpbiAtIGNhcGl0YWwubG9zcykNCmBgYA0KDQoqKkNsZWFuIERhdGEqKg0KDQoqIENsZWFyIFdoaXRlU3BhY2UgaW4gU3RyaW5nDQpgYGB7cn0NCiMgTGlzdCBkb3duIEZhY3RvciBDb2x1bW5zIGluIGRhdGFmcmFtZSAmIFRyaW0gc3RyaW5nIGluIGZhY3RvciBjb2x1bW5zDQpmYWNfY29scyA8LSBzYXBwbHkoYWR1bHQsIGlzLmZhY3RvcikNCmFkdWx0IDwtIGRhdGEuZnJhbWUoY2JpbmQoc2FwcGx5KGFkdWx0WyxmYWNfY29sc10sIHRyaW13cywgd2hpY2g9ImJvdGgiKSwgYWR1bHRbLCFmYWNfY29sc10pKQ0KYGBgDQoNCiogQ2xlYW4gIldvcmtjbGFzcyIgdmFyaWFibGUgYnkgY2F0ZWdvcml6aW5nIGl0IGludG8gNCBjYXRlZ29yaWVzOiBHb3YsIFNlbGYtZW1wLCBQcml2YXRlIGFuZCBPdGhlcg0KYGBge3J9DQphZHVsdCA8LSBhZHVsdCAlPiUgbXV0YXRlKHdvcmtjbGFzcyA9IGlmZWxzZShncmVwbCgiLmdvdiQiLCBzdHJfdHJpbSh3b3JrY2xhc3MpKSwgIkdvdiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgiXlNlbGYuIixzdHJfdHJpbSh3b3JrY2xhc3MpKSwiU2VsZi1lbXAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIl5Qcml2YXRlJCIsIHN0cl90cmltKHdvcmtjbGFzcykpLCJQcml2YXRlIiwgIk90aGVyIikpKSkNCmFkdWx0JHdvcmtjbGFzcyA8LSBhcy5mYWN0b3IoYWR1bHQkd29ya2NsYXNzKQ0KbGV2ZWxzKGFkdWx0JHdvcmtjbGFzcykNCmBgYA0KDQoqIENsZWFuICJFZHVjYXRpb24iIHZhcmlhYmxlIGJ5IGNhdGVnb3JpemluZyBpdCBpbnRvIGdyb3VwczogQmVmb3JlLUhpZ2hzY2hvb2wsIEFzc29jaWF0ZSwgUG9zdC1ncmFkdWF0ZSwgSFMtZ3JhZCwgU29tZS1jb2xsZWdlIGFuZCBCYWNoZWxvcnMNCmBgYHtyfQ0KYWR1bHQgPC0gYWR1bHQgJT4lIG11dGF0ZShlZHVjYXRpb24gPSBpZmVsc2UoZ3JlcGwoIi50aCR8XlByZXNjaG9vbCQiLCAoZWR1Y2F0aW9uKSksICJCZWZvcmUtSGlnaHNjaG9vbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgiXkFzc29jLiIsIChlZHVjYXRpb24pKSwiQXNzb2NpYXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgiXk1hc3RlcnMkfF5Eb2N0b3JhdGUkfF5Qcm8uIiwoZWR1Y2F0aW9uKSksICJQb3N0LUdyYWR1YXRlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKChlZHVjYXRpb24pKSkpKSkNCmFkdWx0JGVkdWNhdGlvbiA8LSBhcy5mYWN0b3IoYWR1bHQkZWR1Y2F0aW9uKQ0KbGV2ZWxzKGFkdWx0JGVkdWNhdGlvbikNCmBgYA0KDQoqIENsZWFuICJNYXJpdGFsIFN0YXR1cyIgdmFyaWFibGUNCmBgYHtyfQ0KYWR1bHQgPC0gYWR1bHQgJT4lIG11dGF0ZShtYXJpdGFsLnN0YXR1cyA9IGlmZWxzZShncmVwbCgiXk1hcnJpZWQuIiwgbWFyaXRhbC5zdGF0dXMpLCAiTWFycmllZCIsIGFzLmNoYXJhY3RlcihtYXJpdGFsLnN0YXR1cykpKQ0KYWR1bHQkbWFyaXRhbC5zdGF0dXMgPC0gYXMuZmFjdG9yKGFkdWx0JG1hcml0YWwuc3RhdHVzKQ0KbGV2ZWxzKGFkdWx0JG1hcml0YWwuc3RhdHVzKQ0KYGBgDQoqIENsZWFuICJJbmNvbWUgdmFyaWFibGUiDQpgYGB7cn0NCmFkdWx0IDwtIGFkdWx0ICU+JSBtdXRhdGUoaW5jb21lID0gaWZlbHNlKGdyZXBsKCJePD01MEsuJCIsIGluY29tZSksICI8PTUwSyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIl4+NTBLLiQiLCBpbmNvbWUpLCI+NTBLIiwgYXMuY2hhcmFjdGVyKGluY29tZSkpKSkNCmFkdWx0JGluY29tZSA8LSBhcy5mYWN0b3IoYWR1bHQkaW5jb21lKQ0KbGV2ZWxzKGFkdWx0JGluY29tZSkNCmBgYA0KKiBDYXRlZ29yaXplIEFnZSBpbnRvIDQgZ3JvdXBzIDogPD0zMCwgMzAtNDUsIDQ1LTYwIGFuZCA+NjANCmBgYHtyfQ0KIyBDb252ZXJ0IEFnZSBjaGFyYWN0ZXIgaW50byBudW1lcmljIGJlY2F1c2UgQWdlIGhhcyBjaGFyYWN0ZXIgdHlwZSBhcyBkZWZhdWx0IGluIGRhdGFzZXQuDQphZHVsdCRhZ2UgPC0gYXMuaW50ZWdlcihhZHVsdCRhZ2UpDQojIENhdGVnb3JpemUgQWdlIGludG8gNCBncm91cHMNCmFkdWx0PC0gYWR1bHQgJT4lIG11dGF0ZShhZ2VfZ3JvdXAgPSBpZmVsc2UoYWdlIDw9MzAsICI8PTMwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGFnZT4zMCAmIGFnZSA8PTQ1LCAiMzAtNDUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGFnZT40NSAmIGFnZSA8PTYwLCI0NS02MCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIj42MCIpKSkpDQphZHVsdCRhZ2VfZ3JvdXAgPC0gYXMuZmFjdG9yKGFkdWx0JGFnZV9ncm91cCkNCiMgQ2hlY2sgbGV2ZWxzIHJlc3VsdCBvZiBBZ2UgYWZ0ZXIgcHJvY2Vzc2luZw0KbGV2ZWxzKGFkdWx0JGFnZV9ncm91cCkNCmBgYA0KKiBDbGVhbiBOYXRpdmUgQ291bnRyaWVzIHZhcmlhYmxlIGJ5IGNhdGVnb3JpemluZyBpdCBpbnRvIHR3byBncm91cHMgOiBVUyBhbmQgTm9uLVVTDQpgYGB7cn0NCmFkdWx0PC0gYWR1bHQgJT4lIG11dGF0ZShuYXRpdmUuY291bnRyaWVzID0gaWZlbHNlKGdyZXBsKCJVbml0ZWQuIixuYXRpdmUuY291bnRyaWVzKSwgIlVTQSIsICJOb24tVVNBIikpDQphZHVsdCRuYXRpdmUuY291bnRyaWVzIDwtIGFzLmZhY3RvcihhZHVsdCRuYXRpdmUuY291bnRyaWVzKQ0KbGV2ZWxzKGFkdWx0JG5hdGl2ZS5jb3VudHJpZXMpDQpgYGANCg0KKiBDaGVjayBkYXRhIGFnYWluIGFmdGVyIGNsZWFuaW5nDQpgYGB7cn0NCnN1bW1hcnkoYWR1bHQpDQpgYGANCg0KDQojIwlTY2FuIEkgDQoNClNjYW4gdGhlIGRhdGEgZm9yIG1pc3NpbmcgdmFsdWVzLCBpbmNvbnNpc3RlbmNpZXMgYW5kIG9idmlvdXMgZXJyb3JzLiBJbiB0aGlzIHN0ZXAsIHlvdSBzaG91bGQgZnVsZmlsIHRoZSBtaW5pbXVtIHJlcXVpcmVtZW50ICM3LiBJbiBhZGRpdGlvbiB0byB0aGUgUiBjb2RlcyBhbmQgb3V0cHV0cywgZXhwbGFpbiBob3cgeW91IGRlYWx0IHdpdGggdGhlc2UgdmFsdWVzLg0KDQoqKiBDaGVjayBtaXNzaW5nIHZhbHVlcyAqKg0KDQpgYGB7cn0NCmNvbG5hbWVzKGFkdWx0KVthcHBseShpcy5uYShhZHVsdCksMixhbnkpXQ0KYGBgDQpUaGVyZSBpcyBubyBtaXNzaW5nIHZhbHVlcyBpbiBhbGwgdmFyaWFibGVzDQoNCioqIENoZWNrIEluZmluaXRlIHZhdWxlcyBmb3IgbnVtbWVyaWMgdmFyaWFibGVzDQpgYGB7cn0NCm51bV9jb2xzIDwtIHNhcHBseShhZHVsdCwgaXMubnVtZXJpYykNCmlzLnNwZWNpYWwgPC0gZnVuY3Rpb24oeCl7DQogIGlmIChpcy5udW1lcmljKHgpKSBpcy5pbmZpbml0ZSh4KQ0KfQ0KaGVhZChzYXBwbHkoYWR1bHRbLG51bV9jb2xzXSwgaXMuc3BlY2lhbCkpDQpgYGANCg0KKiogQ2hlY2sgaW5jb25zaXN0ZW5jZSBlcnJvciBmb3IgQWdlIHZhcmlhYmxlDQpgYGB7cn0NCkFnZV9ydWxlIDwtIGVkaXRzZXQoYygiYWdlID4xNSIsICJhZ2UgPD0gMTAwIikpDQp2aW9sYXRlIDwtIHZpb2xhdGVkRWRpdHMoQWdlX3J1bGUsIGFkdWx0KQ0Kc3VtbWFyeSh2aW9sYXRlKQ0KYGBgDQpUaGVyZSBhcmUgbm8gdmlvbGF0aW9uIGZvciBBZ2UNCg0KKiogQ2hlY2sgaW5jb25zaXN0ZW5jZSBlcnJvciBmb3IgQ2FwaXRhbCBHYWluIHZhcmlhYmxlDQpgYGB7cn0NCmNhcF9nYWluX3J1bGUgPC0gZWRpdHNldCgiY2FwaXRhbC5nYWluID49MCIpDQp2aW9sYXRlQ2FwR2FpbiA8LSB2aW9sYXRlZEVkaXRzKGNhcF9nYWluX3J1bGUsIGFkdWx0KQ0Kc3VtbWFyeSh2aW9sYXRlQ2FwR2FpbikNCmBgYA0KDQoqKiBDaGVjayBpbmNvbnNpc3RlbmNlIGVycm9yIGZvciBDYXBpdGFsIExvc3MgdmFyaWFibGUNCmBgYHtyfQ0KY2FwX2xvc3NfcnVsZSA8LSBlZGl0c2V0KCJjYXBpdGFsLmxvc3MgPj0wIikNCnZpb2xhdGVDYXBMb3NzIDwtIHZpb2xhdGVkRWRpdHMoY2FwX2xvc3NfcnVsZSwgYWR1bHQpDQpzdW1tYXJ5KHZpb2xhdGVDYXBMb3NzKQ0KYGBgDQoNCioqIENoZWNrIGluY29uc2lzdGVuY2UgZXJyb3IgZm9yIEhvdXJzLlBlci5XZWVrIHZhcmlhYmxlDQpgYGB7cn0NCmhvdXJfcnVsZSA8LSBlZGl0c2V0KGMoImhvdXJzLnBlci53ZWVrID4gMCIsICJob3Vycy5wZXIud2VlayA8MTYwIikpDQp2aW9sYXRlSG91cnMgPC0gdmlvbGF0ZWRFZGl0cyhob3VyX3J1bGUsIGFkdWx0KQ0Kc3VtbWFyeSh2aW9sYXRlSG91cnMpDQpgYGANCg0KDQojIwlTY2FuIElJDQoNClNjYW4gdGhlIG51bWVyaWMgZGF0YSBmb3Igb3V0bGllcnMuIEluIHRoaXMgc3RlcCwgd2Ugb25seSBjb25zaWRlciBvdXRsaWVycyBmb3IgdmFyaWFibGVzIGluY2x1ZGluZyAgIkNhcGl0YWwiIChiZWNhdXNlIGl0IGlzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gY2FwdGlhbCBnYWluIGFuZCBjYXBpdGFsIGxvc3MpIGFuZCAiSG91cnMucGVyLndlZWsiLiBGb3IgImZubHdndCIgdmFyaWFibGUsIGl0IHN0YW5kcyBmb3IgIkZpbmFsIFdlaWdodCIgZGVmaW5lZCBieSB0aGUgVVMgQ2Vuc3VzLiBGb3IgIkFnZSIgdmFyaWFibGUsIGJlY2F1c2UgaXQgZG9lcyBub3QgdmlvbGF0ZSBydWxlIHdoaWNoIHdlIGRpZCBjaGVjayBpbiBwcmV2aW91cyBwYXJ0IGFuZCB3ZSBkaWQgY2F0ZWdvcml6ZSBBZ2UgaW50byA0IGdyb3Vwcy4gSGVuY2Ugd2UgZG8gbm90IGNoZWNrIG91dGxpZXIgZm9yICJBZ2UiIGFuZCAiZm5sd2d0Ig0KDQoqKkNoZWNrIG91dGxpZXJzIG9mIEhvdXJzIFBlciBXZWVrKioNCmBgYHtyfQ0KIyBEcmF3IEJveHBsb3QgY2hhcnQgb2YgdGhpcyB2YXJpYWJsZQ0KYm94cGxvdCh4ID0gYWR1bHQkaG91cnMucGVyLndlZWssIG1haW4gPSAiQm94IFBsb3Qgb2YgSG91cnMgUGVyIFdlZWsiLCB5bGFiPSAiSG91cnMgcGVyIHdlZWsiKQ0KDQpgYGANCmBgYHtyfQ0KIyBVc2luZyB6LXNjb3JlIHRvIGNoZWNrIG91dGxpZXJzDQp6LnNjb3Jlcy5ob3VyczwtIGFkdWx0JGhvdXJzLnBlci53ZWVrICU+JSBzY29yZXModHlwZT0ieiIpDQp6LnNjb3Jlcy5ob3VycyAlPiUgc3VtbWFyeSgpDQpsZW5ndGgod2hpY2goYWJzKHouc2NvcmVzLmhvdXJzKT4zKSkNCmBgYA0KVGhlcmUgYXJlIDY4MSBvdXRsaWVycyBmb3IgdGhpcyB2YXJpYWJsZSBpbiBkYXRhc2V0LiBUbyBkZWFsIHdpdGggdGhpcywgd2UgaW1wdXRlIG91dGxpZXJzIHdpdGggbWVhbiB2YWx1ZQ0KYGBge3J9DQphZHVsdCRob3Vycy5wZXIud2Vla1t3aGljaChhYnMoei5zY29yZXMuaG91cnMpPjMpXSA8LSBtZWFuKGFkdWx0JGhvdXJzLnBlci53ZWVrLCBuYS5ybSA9IFRSVUUpDQoNCiMgVXNpbmcgYm94cGxvdCB0byBzZWUgY2hhbmdlcyBhZnRlciBpbXB1dGluZyBvdXRsaWVycw0KYm94cGxvdCh4ID0gYWR1bHQkaG91cnMucGVyLndlZWssIG1haW4gPSAiQm94IFBsb3Qgb2YgSG91cnMgUGVyIFdlZWsgQWZ0ZXIgSW1wdXRpbmcgT3V0bGllcnMiLCB5bGFiPSAiSG91cnMgcGVyIHdlZWsiKQ0KYGBgDQoqKkNoZWNrIG91dGxpZXJzIGZvciBDYXBpdGFsKioNCmBgYHtyfQ0KYm94cGxvdCh4ID0gYWR1bHQkY2FwaXRhbCwgbWFpbiA9ICJCb3ggUGxvdCBvZiBDYXBpdGFsIiwgeWxhYj0gIkNhcGl0YWwiKQ0KYGBgDQoNCk9idmlvdXNseSwgdGhlIHZhbHVlIDk5OTk5IGlzIGFzIGFuIG91dGxpZXIgb2YgY2FwaXRhbC4gSG93ZXZlciwgd2Ugc2tpcCB0aGlzIHZhbHVlIGJlY2F1c2UgaXQgY2FuIGhhcHBlbiBpbiByZWFsaXR5IGZvciBQZW9wbGUncyBJbmNvbWUuDQoNCiMjCVRyYW5zZm9ybSANCg0KKipUcmFuc2Zvcm0gQ2FwaXRhbCB2YXJpYWJsZSoqDQoNCmBgYHtyfQ0KaGlzdChhZHVsdCRjYXBpdGFsLCBtYWluID0gIkhpc3RvZ3JhbSBvZiBDYXBpdGFsIikNCmBgYA0KDQpMb29raW5nIGF0IGhpc3RvZ3JhbSBvZiBjYXBpdGFsLCBpdCBoYXMgcmlnaHQgc2tldyBkaXN0cmlidXRpb24uIFRoZXJlZm9yZSB3ZSB1c2UgcmVjaXByb2NhbCB0cmFuZm9ybWF0aW9uIHRvIHJlZHVjZSB0aGUgcmlnaHQgc2tld25lc3MuDQpgYGB7cn0NCiMgQXBwbHkgUmVjaXByb2NhbCBUcmFuc2Zvcm1hdGlvbiBmb3IgQ2FwaXRhbA0KY2FwaXRhbF9yZWNpcCA8LSAxL2FkdWx0JGNhcGl0YWwNCmhpc3QoY2FwaXRhbF9yZWNpcCkNCmBgYA0KDQoqKlRyYW5zZm9ybSBIb3Vycy5QZXIuV2VlayB2YXJpYWJsZSoqDQpgYGB7cn0NCmhpc3QoYWR1bHQkaG91cnMucGVyLndlZWssIG1haW4gPSAiSGlzdG9ncmFtIG9mIEhvdXJzIFBlciBXZWVrIiwgeGxhYj0iSG91cnMgcGVyIHdlZWsiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBVc2luZyBCb3hDb3ggdG8gZG8gdHJhbnNmb3JtYXRpb24gZm9yIEhvdXJzLnBlci53ZWVrDQpib3hjb3hfaG91cnMgPC0gQm94Q294KGFkdWx0JGhvdXJzLnBlci53ZWVrLCBsYW1iZGEgPSAiYXV0byIpDQpoaXN0KGJveGNveF9ob3VycykNCmBgYA0KDQo8YnI+DQo8YnI+DQo=