Required packages
library(readxl)
library(tidyr)
library(dplyr)
library(outliers)
library(infotheo)
Executive Summary
The objective of this report was to make a tidy dataset of countries with two indicators:
- Employment rates by education levels: the percentage of employed 25-64 year-olds by all 25-64 year-olds with the same education level
- Educational attainment: the highest level of education achieved as a percentage to the total population of 25-64 year-olds
Meanwhile, since income and educational attainment may be correlated, I added gross national income (GNI) and classified countries into different income levels. To my own interest, the working hour was also added to complement this classification. I adopted a combination of income level and the length of working hours to group countries, such as “High income & Short hours” and “Low income & Long hours”. To accomplish this, I obtained multiple datasets with the variables needed and joined them into a tidy one. Necessary scanning steps were taken to deal with missing values and to identify the outliers. For the classification, I created new variables of each country’s average GNI and working hours over three years and used the binning method to group the countries.
Data
Member countries of OECD (The Organisation for Economic Co-operation and Development) were investigated. I looked at three education levels for both employment rate and educational attainment: below upper secondary, upper secondary, and tertiary. The measures cover the latest three years available: 2014 to 2016. Five datasets were generated for further process. I introduced the main variables in each dataset below:
- GNI: Location(country code), Time(year), Value(gross national income: US dollars per capita); it is a CSV file downloaded from: data.oecd.org/natincome/gross-national-income.htm
- hours: Location(country code), Time(year), Value(average annual working hours per worker); it is a CSV file downloaded from: data.oecd.org/emp/hours-worked.htm
- employment: Location(country code), Subject(education level), Time(year),Value(employment rate); it is a CSV file downloaded from: data.oecd.org/emp/employment-by-education-level.htm
- education: Location(country code), Subject(education level), Time(year),Value(educational attainment); it is a CSV file downloaded from: data.oecd.org/eduatt/adult-education-level.htm
- code: Code value(country code), Definition(country’s full name); it is an EXCEL file downloaded from: www.dnb.com/content/dam/english/dnb-solutions/sales-and-marketing/iso_3digit_alpha_country_codes.xls
setwd("~/Desktop/DP Assignment 3")
GNI<-read.csv("GNI.csv",stringsAsFactors = FALSE)
GNI[1:3, ]
hours<-read.csv("hours worked.csv",stringsAsFactors = FALSE)
hours[1:3, ]
employment<-read.csv("employment.csv",stringsAsFactors = FALSE)
employment[1:3, ]
education<-read.csv("EducationLevel.csv",stringsAsFactors = FALSE)
education[1:3, ]
code<-read_excel("code.xls",skip=1)
code[1:3, ]
- I opened a new folder called “DP Assignment 3” on Desktop and saved the five datasets into it. This folder was set as the working directory using “setwd()”.
- CSV files were imported using Base R functions. I set “stringsAsFactors = FALSE” to read the variables in their original forms.
- The EXCEL file was imported using “read_excel()”.
- In order to save space for my report, I displayed only the first three rows of each dataset.
Understand
str(GNI)
'data.frame': 137 obs. of 8 variables:
$ LOCATION : chr "AUS" "AUS" "AUS" "AUT" ...
$ INDICATOR : chr "GNI" "GNI" "GNI" "GNI" ...
$ SUBJECT : chr "TOT" "TOT" "TOT" "TOT" ...
$ MEASURE : chr "USD_CAP" "USD_CAP" "USD_CAP" "USD_CAP" ...
$ FREQUENCY : chr "A" "A" "A" "A" ...
$ TIME : int 2014 2015 2016 2014 2015 2016 2014 2015 2016 2014 ...
$ Value : num 45953 45710 46885 48866 49594 ...
$ Flag.Codes: chr "" "" "" "" ...
str(hours)
'data.frame': 112 obs. of 8 variables:
$ LOCATION : chr "AUS" "AUS" "AUS" "AUT" ...
$ INDICATOR : chr "HRWKD" "HRWKD" "HRWKD" "HRWKD" ...
$ SUBJECT : chr "TOT" "TOT" "TOT" "TOT" ...
$ MEASURE : chr "HR_WKD" "HR_WKD" "HR_WKD" "HR_WKD" ...
$ FREQUENCY : chr "A" "A" "A" "A" ...
$ TIME : int 2014 2015 2016 2014 2015 2016 2014 2015 2014 2015 ...
$ Value : int 1681 1682 1669 1628 1608 1601 1556 1551 1704 1707 ...
$ Flag.Codes: logi NA NA NA NA NA NA ...
str(employment)
'data.frame': 363 obs. of 8 variables:
$ LOCATION : chr "NZL" "NZL" "NZL" "GBR" ...
$ INDICATOR : chr "EMPEDU" "EMPEDU" "EMPEDU" "EMPEDU" ...
$ SUBJECT : Factor w/ 3 levels "Below_upper_secondary",..: NA NA NA NA NA NA NA NA NA NA ...
$ MEASURE : chr "PC_25_64" "PC_25_64" "PC_25_64" "PC_25_64" ...
$ FREQUENCY : chr "A" "A" "A" "A" ...
$ TIME : int 2014 2015 2016 2014 2015 2016 2014 2015 2016 2014 ...
$ Value : num 80.3 81.3 82.1 84.6 85.4 ...
$ Flag.Codes: logi NA NA NA NA NA NA ...
str(education)
'data.frame': 363 obs. of 8 variables:
$ LOCATION : chr "AUS" "AUS" "AUS" "AUS" ...
$ INDICATOR : chr "EDUADULT" "EDUADULT" "EDUADULT" "EDUADULT" ...
$ SUBJECT : Factor w/ 3 levels "Below_upper_secondary",..: NA NA NA NA NA NA NA NA NA NA ...
$ MEASURE : chr "PC_25_64" "PC_25_64" "PC_25_64" "PC_25_64" ...
$ FREQUENCY : chr "A" "A" "A" "A" ...
$ TIME : int 2014 2015 2016 2014 2015 2016 2014 2015 2016 2014 ...
$ Value : num 22.9 21 20.1 41.9 42.9 ...
$ Flag.Codes: logi NA NA NA NA NA NA ...
str(code)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 239 obs. of 2 variables:
$ Code Value: chr "AFG" "ALA" "ALB" "DZA" ...
$ Definition: chr "Afghanistan" "Aland Islands" "Albania" "Algeria" ...
employment<-employment %>% mutate(SUBJECT=factor(SUBJECT,levels=c("BUPPSRY","UPPSRY_NTRY","TRY"),labels =c("Below_upper_secondary","Upper_secondary","Tertiary")))
education<-education %>% mutate(SUBJECT=factor(SUBJECT,levels=c("BUPPSRY","UPPSRY","TRY"),labels=c("Below_upper_secondary","Upper_secondary","Tertiary")))
I inspected the structure of the data frame and the type of variables using “str()”. There were character, numeric and logical values. Since there was no factors in my datasets, I converted the character variables “Subject” to factors and labelled them using “mutate()” and “factor()”. “Subject” includes three education levels that are suitable for a convertion to factors.
Tidy & Manipulate Data I
GNInew<-GNI %>% select(-INDICATOR,-SUBJECT,-MEASURE,-FREQUENCY,-Flag.Codes) %>% unite(Location_Year,LOCATION,TIME)
colnames(GNInew)[2]<-"GNI"
hoursnew<-hours%>%select(-INDICATOR,-SUBJECT,-MEASURE,-FREQUENCY,-Flag.Codes) %>% unite(Location_Year,LOCATION,TIME)
colnames(hoursnew)[2]<-"HRWKD"
employmentnew<-employment %>% select(-INDICATOR,-MEASURE,-FREQUENCY,-Flag.Codes) %>% unite(Location_Year_Subject, LOCATION, TIME,SUBJECT)
colnames(employmentnew)[2]<-"EMP"
educationnew<-education %>% select(-INDICATOR,-MEASURE,-FREQUENCY,-Flag.Codes) %>% unite(Location_Year_Subject,LOCATION,TIME,SUBJECT)
colnames(educationnew)[2]<-"EDU"
df1<-full_join(educationnew,employmentnew,by="Location_Year_Subject")
df1<-df1 %>% separate(1,into=c("Location","Year","Education_level"),extra = "merge",fill="left") %>% unite(Location_Year, Location, Year)
df1[1:3, ]#an example of df1
df2<-right_join(GNInew,hoursnew,by="Location_Year")
df2[1:3, ]#an example of df2
df<-left_join(df2,df1,by="Location_Year")
df<-separate(df,1,into=c("Country_code","Year"))
colnames(code)[1]<-"Country_code"
df<-right_join(code,df,by="Country_code")
df
I joined five datasets into one tidy dataset named “df”. To accomplish this, I took the below steps:
I deleted redundant variables to keep only the variables I need using “select()” for each dataset.
For GNI and hours, I combined two variables “Location” and “Time” using “unite()” and set it as a unique variable to match for joining purpose. I renamed “Value” to the corresponding indicators “GNI” and “HRWKD”.
For employment and education, I combined “Location”, “Time” and “Subject” using “unite()” and set it as a unique variable to match for joining purpose. I renamed “Value” to the corresponding indicators “EMP” and “EDU”.
I joined employmentnew and educationnew by “Location_Year_Subject” into a new dataset df1 using “full_join()” to keep all the values from both datasets. Then I separated the variable “Location_Year_Subject” into two variables “Location_Year” and “Education_level” for further joining purpose.
I joined matching rows from GNInew to hoursnew by “Location_Year” into a new dataset df2 using “right_join()” so that the variale “HRWKD” was placed to the right and all values from hoursnew were kept because hoursnew only contained countries that are OECD member countries.
I joined matching rows from df1 to df2 by “Location_Year” into the final combined dataset df using “left_join()” to keep all values of df2. Then I separated the variable “Location_Year” into “Country_code” and “Year”. At last, I joined matching rows from code to df by “Country_code” using “right_join()” so that I could have a new column “Definition” to check the full name of each country code.
Tidy & Manipulate Data II
freq<-data.frame(table(df$Country_code,df$Year))
freq<-spread(freq,key = Var2, value = Freq)
freq[1:3, ]#an example of freq
which(freq$`2014`==0)
integer(0)
which(freq$`2015`==0)
integer(0)
which(freq$`2016`==0)
[1] 3 5
freq$Var1[3:5]
[1] BEL CAN CHE
38 Levels: AUS AUT BEL CAN CHE CHL CRI CZE DEU DNK ESP EST FIN FRA GBR GRC HUN IRL ISL ISR ITA JPN KOR LTU LUX ... USA
dffreq1<-df[!(df$`Country_code`=="BEL"), ]
dfnew<-dffreq1[!(dffreq1$`Country_code`=="CHE"), ]
dfnew<-dfnew %>% group_by(`Country_code`) %>% mutate(Mean_GNI=mean(unique(GNI)),Mean_hours=mean(unique(HRWKD)))
dfnew
After joining the datasets, I assumed all countries have both GNI and working hours values available for three years, 2014 to 2016. I checked my assumption using “table()” to see the frequency of a country under each year. I converted the table to a dataframe for easy checking. Normally, each country should appear three times (for three education levels in a year) or at least once under each year. I found that two countries “BEL” and “CHE” do not match with this criteria. They have no rows for year 2016. It was because that these two countries have no “HRKWD” values in year 2016 or they have no values for both “GNI” and “HRKWD” in year 2016. If they have “HRKWD” values in year 2016, their 2016 rows should apprear because in the above joining process I kept all rows of the working hours dataset.
To classify countries based on a combination of their income level and working hours, I need to calculate both the average GNI and working hours over three years 2014-2016 for each country. Therefore I excluded these two countries in my report. Then I created two new columns of mean values using “group_by()” and “mutate()”. I added “unique()” in this function to ensure the value of each year only appears once in the calculation to get the correct mean value.
Scan I
colSums(is.na(dfnew))
Country_code Definition Year GNI HRWKD Education_level EDU
0 3 0 0 0 7 7
EMP Mean_GNI Mean_hours
7 0 0
which(is.na(dfnew$Definition))
[1] 275 276 277
head(dfnew[275:277, ])
dfnew1<-dfnew[!(dfnew$`Country_code`=="OECD"), ]
colSums((is.na(dfnew1)))
Country_code Definition Year GNI HRWKD Education_level EDU
0 0 0 0 0 4 4
EMP Mean_GNI Mean_hours
4 0 0
which(is.na(dfnew1$`Education_level`))==which(is.na(dfnew1$EDU))
[1] TRUE TRUE TRUE TRUE
which(is.na(dfnew1$`Education_level`))==which(is.na(dfnew1$EMP))
[1] TRUE TRUE TRUE TRUE
which(is.na(dfnew1$`Education_level`))
[1] 106 236 240 265
dfnew1[c(106,236,240,265), ]
#CHL
r1<-dfnew1[237:239, ]
dfnew2 <- rbind(dfnew1[1:236,],r1,r1,dfnew1[-(1:236),])
c1<-c("2014",21903.17218,1990)
dfnew2[237:239,3:5]<-rbind(c1,c1,c1)
c2<-c("2016",22355.9843,1974)
dfnew2[243:245,3:5]<-rbind(c2,c2,c2)
dfnew3<-dfnew2[-c(246,236), ]
dfnew3 %>% filter(Country_code=="CHL")
#IRL and RUS
r3<-dfnew3[106, ]
dfnew4<-rbind(dfnew3[1:106, ],r3,r3,dfnew3[-(1:106), ])
r4<-dfnew4[271, ]
dfnew5<-rbind(dfnew4[1:271, ],r4,r4,dfnew4[-(1:271), ])
c3<-c("Below_upper_secondary","Tertiary","Upper_secondary")
dfnew5[106:108,6]<-c3
dfnew5[271:273,6]<-c3
dfnew6<-dfnew5%>%group_by(`Country_code`,`Education_level`)%>% mutate(EDU=ifelse(is.na(EDU),mean(EDU,na.rm=TRUE),EDU)) %>% mutate(EMP=ifelse(is.na(EMP),mean(EMP,na.rm=TRUE),EMP))
dfnew6 %>% filter(Country_code=="IRL")
dfnew6 %>% filter(Country_code=="RUS")
Missing Value: I checked the number of missing values in each column. The three missing values in variable “Definition” are “OECD”, which represents an average value for all OECD countries. I deleted these three rows because I want to study each country separately. For all the other missing values in “Education_level”,“EMP” and “EDU”, I checked that they were in the same rows. Three countries included missing values: Ireland, Chile and Russia. Chile only had employment rate and educational attainment available in year 2015. I copied its “EMP” and “EDU” values for each education level of year 2015 to year 2014 and 2016. For Ireland and Russia, they had employment rate and educational attainment availble in year 2014 and 2015. I imputed the average values of employment rate and educational attainment for each education level in year 2014 and year 2015 to year 2016.
dfnew6$GNI<-as.numeric(dfnew6$GNI)
dfnew6$HRWKD<-as.integer(dfnew6$HRWKD)
dfnew6$Education_level<-as.factor(dfnew6$Education_level)
m<-cbind(dfnew6$GNI,dfnew6$HRWKD,dfnew6$EDU,dfnew6$EMP,dfnew6$Mean_GNI,dfnew6$Mean_hours) #with a reason
sum(is.nan(m))
[1] 0
sum(is.infinite(m))
[1] 0
Special Value: I checked the type of variables again and found that “GNI”,“HRWKD” and “Education_level” have become characters. It was probably due to the above “unite()”, “separate()” and “rbind()” process that forced all variables into one type. “Education_level” was converted to factors. “GNI” and “HRWKD” were converted to numeric values for further checking on special values. Functions “is.nan()” and “is.infinite()” only work for vectorial input. Therefore I combined all numeric values into one matrix. It turned out that there were no special values.
sum(dfnew6$GNI<0)
[1] 0
sum(dfnew6$HRWKD<0)
[1] 0
sum(dfnew6$EDU<=0 |dfnew6$EDU>=100)
[1] 0
sum(dfnew6$EMP<=0 |dfnew6$EMP>=100)
[1] 0
sum(dfnew6$Mean_GNI<0)
[1] 0
sum(dfnew6$Mean_hours<0)
[1] 0
checkEDU100<-dfnew6 %>% group_by(`Country_code`,Year) %>% mutate(sumEDU=sum(EDU))
which(round(checkEDU100$sumEDU,digits = 0)!=100)
[1] 118 119 120
dfnew6[118:120,]
Inconsistency:
- All values of “GNI”,“HRWKD”,“Mean_GNI” and “Mean_hours” should be greater than zero. After checking, the dataset obeys this rule.
- All values of “EDU” and “EMP” are percentages, therefore they should be within 0-100. After checking, the dataset obeys this rule.
- For each year in every country, the sum of “EDU” of three education levels should be 100. The educational attainment (“EDU”) represents the percentage of the 25-64 year-olds who achieved Tertiary/Upper secondary/Below upper secondary as the highest education level. These three education levels cover all the situtations. After checking, Japan does not fulfil the requirments because it only has the values of “EDU” for Tertiary level. I did not apply the same method to check “EMP” because the employment rate represents the percentage of employed 25-64 year-olds by all 25-64 year-olds with the same education level. They cannot be added up based on different population.
Scan II
z.scores<-dfnew6$GNI %>%scores(type="z")
z.scores %>% summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
-1.8250 -0.7893 -0.1215 0.0000 0.7900 2.4540
which(abs(z.scores)>3)
integer(0)
z.scores<-dfnew6$HRWKD %>%scores(type="z")
z.scores %>% summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
-1.89600 -0.57890 -0.03397 0.00000 0.57730 2.33100
which(abs(z.scores)>3)
integer(0)
z.scores<-dfnew6$Mean_GNI %>%scores(type="z")
z.scores %>% summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
-1.7920 -0.7724 -0.1372 0.0000 0.8405 2.3340
which(abs(z.scores)>3)
integer(0)
z.scores<-dfnew6$Mean_hours %>%scores(type="z")
z.scores %>% summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
-1.88700 -0.56860 -0.04827 0.00000 0.53370 2.30200
which(abs(z.scores)>3)
integer(0)
length(boxplot(dfnew6$EDU ~ dfnew6$Education_level, main="Educational attainment by Education level", ylab = "Education level", xlab = "Educational attainment")$out)
[1] 9

length(boxplot(dfnew6$EMP ~ dfnew6$Education_level, main="Employment rate by Education level", ylab = "Education level", xlab = "Employment rate")$out)
[1] 7

#identify the 9 outliers in educational attainment above the upper fence for "Below_upper_secondary" level
outlierEDU<-dfnew6 %>% filter(Education_level=="Below_upper_secondary") %>% select(-EMP)
outlierEDU<-outlierEDU[order(outlierEDU$EDU,decreasing=TRUE), ]
outlierEDU[1:9, ]
#identify the 1 outliers in employment rate above the upper fence for "Tertiary" level
outlierEMP1<-dfnew6 %>% filter(Education_level=="Tertiary") %>% select(-EDU)
outlierEMP1up<-outlierEMP1[order(outlierEMP1$EMP,decreasing=TRUE), ]
outlierEMP1up[1, ]
#identify the 3 outliers in employment rate below the lower fence for "Tertiary" level
outlierEMP1low<-outlierEMP1[order(outlierEMP1$EMP), ]
outlierEMP1low[1:3, ]
#identify the 3 outlier in employment rate below the lower fence for "Upper_secondary" level
outlierEMP2<-dfnew6 %>% filter(Education_level=="Upper_secondary") %>% select(-EDU)
outlierEMP2<-outlierEMP2[order(outlierEMP2$EMP), ]
outlierEMP2[1:3, ]
- For “GNI”,“HRWKD”,“Mean_GNI” and “Mean_hours”, I applied z-score method to identify outliers. After checking, there were no suggested outliers for them. It may imply that the values of gross national income and working hours for these countries remain in a reasonable range. No country stands out regarding gross national income or working hours.
- For the employment rate (“EMP”) and educational attainment (“EDU”), I applied bivariate box plots so that I can detect outliers by education levels. 9 outliers were identified in the educational attainment for “Below_upper_secondary” level. 4 outliers were identified in the employment rate for “Tertiary” level, with 1 above the upper fence and 3 below the lower fence. 3 outliers were identified in the employment rate for “Upper_secondary” level. I decided not to deal with these outliers. I compared the data on a global scale. It is reasonable for different countries to have different conditions on their education and employment.
LS0tCnRpdGxlOiAiTUFUSDIzNDkgU2VtZXN0ZXIgMSwgMjAxOCIKYXV0aG9yOiAiWWlmYW4gRkVORyBzMzcwNDQ3NiIKc3VidGl0bGU6IEFzc2lnbm1lbnQgMwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBSZXF1aXJlZCBwYWNrYWdlcyAKCmBgYHtyfQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeSh0aWR5cikKbGlicmFyeShkcGx5cikKbGlicmFyeShvdXRsaWVycykKbGlicmFyeShpbmZvdGhlbykKYGBgCgojIyBFeGVjdXRpdmUgU3VtbWFyeSAKClRoZSBvYmplY3RpdmUgb2YgdGhpcyByZXBvcnQgd2FzIHRvIG1ha2UgYSB0aWR5IGRhdGFzZXQgb2YgY291bnRyaWVzIHdpdGggdHdvIGluZGljYXRvcnM6IAoKKiBFbXBsb3ltZW50IHJhdGVzIGJ5IGVkdWNhdGlvbiBsZXZlbHM6IHRoZSBwZXJjZW50YWdlIG9mIGVtcGxveWVkIDI1LTY0IHllYXItb2xkcyBieSBhbGwgMjUtNjQgeWVhci1vbGRzIHdpdGggdGhlIHNhbWUgZWR1Y2F0aW9uIGxldmVsCiogRWR1Y2F0aW9uYWwgYXR0YWlubWVudDogdGhlIGhpZ2hlc3QgbGV2ZWwgb2YgZWR1Y2F0aW9uIGFjaGlldmVkIGFzIGEgcGVyY2VudGFnZSB0byB0aGUgdG90YWwgcG9wdWxhdGlvbiBvZiAyNS02NCB5ZWFyLW9sZHMKCk1lYW53aGlsZSwgc2luY2UgaW5jb21lIGFuZCBlZHVjYXRpb25hbCBhdHRhaW5tZW50IG1heSBiZSBjb3JyZWxhdGVkLCBJIGFkZGVkIGdyb3NzIG5hdGlvbmFsIGluY29tZSAoR05JKSBhbmQgY2xhc3NpZmllZCBjb3VudHJpZXMgaW50byBkaWZmZXJlbnQgaW5jb21lIGxldmVscy4gVG8gbXkgb3duIGludGVyZXN0LCB0aGUgd29ya2luZyBob3VyIHdhcyBhbHNvIGFkZGVkIHRvIGNvbXBsZW1lbnQgdGhpcyBjbGFzc2lmaWNhdGlvbi4gSSBhZG9wdGVkIGEgY29tYmluYXRpb24gb2YgaW5jb21lIGxldmVsIGFuZCB0aGUgbGVuZ3RoIG9mIHdvcmtpbmcgaG91cnMgdG8gZ3JvdXAgY291bnRyaWVzLCBzdWNoIGFzICJIaWdoIGluY29tZSAmIFNob3J0IGhvdXJzIiBhbmQgIkxvdyBpbmNvbWUgJiBMb25nIGhvdXJzIi4gVG8gYWNjb21wbGlzaCB0aGlzLCBJIG9idGFpbmVkIG11bHRpcGxlIGRhdGFzZXRzIHdpdGggdGhlIHZhcmlhYmxlcyBuZWVkZWQgYW5kIGpvaW5lZCB0aGVtIGludG8gYSB0aWR5IG9uZS4gTmVjZXNzYXJ5IHNjYW5uaW5nIHN0ZXBzIHdlcmUgdGFrZW4gdG8gZGVhbCB3aXRoIG1pc3NpbmcgdmFsdWVzIGFuZCB0byBpZGVudGlmeSB0aGUgb3V0bGllcnMuIEZvciB0aGUgY2xhc3NpZmljYXRpb24sIEkgY3JlYXRlZCBuZXcgdmFyaWFibGVzIG9mIGVhY2ggY291bnRyeSdzIGF2ZXJhZ2UgR05JIGFuZCB3b3JraW5nIGhvdXJzIG92ZXIgdGhyZWUgeWVhcnMgYW5kIHVzZWQgdGhlIGJpbm5pbmcgbWV0aG9kIHRvIGdyb3VwIHRoZSBjb3VudHJpZXMuIAoKIyMgRGF0YQoKTWVtYmVyIGNvdW50cmllcyBvZiBPRUNEIChUaGUgT3JnYW5pc2F0aW9uIGZvciBFY29ub21pYyBDby1vcGVyYXRpb24gYW5kIERldmVsb3BtZW50KSB3ZXJlIGludmVzdGlnYXRlZC4gSSBsb29rZWQgYXQgdGhyZWUgZWR1Y2F0aW9uIGxldmVscyBmb3IgYm90aCBlbXBsb3ltZW50IHJhdGUgYW5kIGVkdWNhdGlvbmFsIGF0dGFpbm1lbnQ6IGJlbG93IHVwcGVyIHNlY29uZGFyeSwgdXBwZXIgc2Vjb25kYXJ5LCBhbmQgdGVydGlhcnkuIFRoZSBtZWFzdXJlcyBjb3ZlciB0aGUgbGF0ZXN0IHRocmVlIHllYXJzIGF2YWlsYWJsZTogMjAxNCB0byAyMDE2LiBGaXZlIGRhdGFzZXRzIHdlcmUgZ2VuZXJhdGVkIGZvciBmdXJ0aGVyIHByb2Nlc3MuIEkgaW50cm9kdWNlZCB0aGUgbWFpbiB2YXJpYWJsZXMgaW4gZWFjaCBkYXRhc2V0IGJlbG93OgoKKiBHTkk6IExvY2F0aW9uKGNvdW50cnkgY29kZSksIFRpbWUoeWVhciksIFZhbHVlKGdyb3NzIG5hdGlvbmFsIGluY29tZTogVVMgZG9sbGFycyBwZXIgY2FwaXRhKTsgaXQgaXMgYSBDU1YgZmlsZSBkb3dubG9hZGVkIGZyb206IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4qZGF0YS5vZWNkLm9yZy9uYXRpbmNvbWUvZ3Jvc3MtbmF0aW9uYWwtaW5jb21lLmh0bSo8L3NwYW4+CiogaG91cnM6IExvY2F0aW9uKGNvdW50cnkgY29kZSksIFRpbWUoeWVhciksIFZhbHVlKGF2ZXJhZ2UgYW5udWFsIHdvcmtpbmcgaG91cnMgcGVyIHdvcmtlcik7IGl0IGlzIGEgQ1NWIGZpbGUgZG93bmxvYWRlZCBmcm9tOiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+KmRhdGEub2VjZC5vcmcvZW1wL2hvdXJzLXdvcmtlZC5odG0qPC9zcGFuPgoqIGVtcGxveW1lbnQ6IExvY2F0aW9uKGNvdW50cnkgY29kZSksIFN1YmplY3QoZWR1Y2F0aW9uIGxldmVsKSwgVGltZSh5ZWFyKSxWYWx1ZShlbXBsb3ltZW50IHJhdGUpOyBpdCBpcyBhIENTViBmaWxlIGRvd25sb2FkZWQgZnJvbTogPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPipkYXRhLm9lY2Qub3JnL2VtcC9lbXBsb3ltZW50LWJ5LWVkdWNhdGlvbi1sZXZlbC5odG0qPC9zcGFuPgoqIGVkdWNhdGlvbjogTG9jYXRpb24oY291bnRyeSBjb2RlKSwgU3ViamVjdChlZHVjYXRpb24gbGV2ZWwpLCBUaW1lKHllYXIpLFZhbHVlKGVkdWNhdGlvbmFsIGF0dGFpbm1lbnQpOyBpdCBpcyBhIENTViBmaWxlIGRvd25sb2FkZWQgZnJvbTogPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPipkYXRhLm9lY2Qub3JnL2VkdWF0dC9hZHVsdC1lZHVjYXRpb24tbGV2ZWwuaHRtKjwvc3Bhbj4KKiBjb2RlOiBDb2RlIHZhbHVlKGNvdW50cnkgY29kZSksIERlZmluaXRpb24oY291bnRyeSdzIGZ1bGwgbmFtZSk7IGl0IGlzIGFuIEVYQ0VMIGZpbGUgZG93bmxvYWRlZCBmcm9tOiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+Knd3dy5kbmIuY29tL2NvbnRlbnQvZGFtL2VuZ2xpc2gvZG5iLXNvbHV0aW9ucy9zYWxlcy1hbmQtbWFya2V0aW5nL2lzb18zZGlnaXRfYWxwaGFfY291bnRyeV9jb2Rlcy54bHMqPC9zcGFuPgoKYGBge3J9CnNldHdkKCJ+L0Rlc2t0b3AvRFAgQXNzaWdubWVudCAzIikKR05JPC1yZWFkLmNzdigiR05JLmNzdiIsc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpHTklbMTozLCBdCmhvdXJzPC1yZWFkLmNzdigiaG91cnMgd29ya2VkLmNzdiIsc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpob3Vyc1sxOjMsIF0KZW1wbG95bWVudDwtcmVhZC5jc3YoImVtcGxveW1lbnQuY3N2IixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmVtcGxveW1lbnRbMTozLCBdCmVkdWNhdGlvbjwtcmVhZC5jc3YoIkVkdWNhdGlvbkxldmVsLmNzdiIsc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQplZHVjYXRpb25bMTozLCBdCmNvZGU8LXJlYWRfZXhjZWwoImNvZGUueGxzIixza2lwPTEpCmNvZGVbMTozLCBdCmBgYAoxLiBJIG9wZW5lZCBhIG5ldyBmb2xkZXIgY2FsbGVkICJEUCBBc3NpZ25tZW50IDMiIG9uIERlc2t0b3AgYW5kIHNhdmVkIHRoZSBmaXZlIGRhdGFzZXRzIGludG8gaXQuIFRoaXMgZm9sZGVyIHdhcyBzZXQgYXMgdGhlIHdvcmtpbmcgZGlyZWN0b3J5IHVzaW5nICJzZXR3ZCgpIi4KMi4gQ1NWIGZpbGVzIHdlcmUgaW1wb3J0ZWQgdXNpbmcgQmFzZSBSIGZ1bmN0aW9ucy4gSSBzZXQgInN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSIgdG8gcmVhZCB0aGUgdmFyaWFibGVzIGluIHRoZWlyIG9yaWdpbmFsIGZvcm1zLgozLiBUaGUgRVhDRUwgZmlsZSB3YXMgaW1wb3J0ZWQgdXNpbmcgInJlYWRfZXhjZWwoKSIuCjQuIEluIG9yZGVyIHRvIHNhdmUgc3BhY2UgZm9yIG15IHJlcG9ydCwgSSBkaXNwbGF5ZWQgb25seSB0aGUgZmlyc3QgdGhyZWUgcm93cyBvZiBlYWNoIGRhdGFzZXQuCgojIyBVbmRlcnN0YW5kIAoKYGBge3J9CnN0cihHTkkpCnN0cihob3VycykKc3RyKGVtcGxveW1lbnQpCnN0cihlZHVjYXRpb24pCnN0cihjb2RlKQplbXBsb3ltZW50PC1lbXBsb3ltZW50ICU+JSBtdXRhdGUoU1VCSkVDVD1mYWN0b3IoU1VCSkVDVCxsZXZlbHM9YygiQlVQUFNSWSIsIlVQUFNSWV9OVFJZIiwiVFJZIiksbGFiZWxzID1jKCJCZWxvd191cHBlcl9zZWNvbmRhcnkiLCJVcHBlcl9zZWNvbmRhcnkiLCJUZXJ0aWFyeSIpKSkKZWR1Y2F0aW9uPC1lZHVjYXRpb24gJT4lIG11dGF0ZShTVUJKRUNUPWZhY3RvcihTVUJKRUNULGxldmVscz1jKCJCVVBQU1JZIiwiVVBQU1JZIiwiVFJZIiksbGFiZWxzPWMoIkJlbG93X3VwcGVyX3NlY29uZGFyeSIsIlVwcGVyX3NlY29uZGFyeSIsIlRlcnRpYXJ5IikpKQpgYGAKSSBpbnNwZWN0ZWQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSBmcmFtZSBhbmQgdGhlIHR5cGUgb2YgdmFyaWFibGVzIHVzaW5nICJzdHIoKSIuIFRoZXJlIHdlcmUgY2hhcmFjdGVyLCBudW1lcmljIGFuZCBsb2dpY2FsIHZhbHVlcy4gU2luY2UgdGhlcmUgd2FzIG5vIGZhY3RvcnMgaW4gbXkgZGF0YXNldHMsIEkgY29udmVydGVkIHRoZSBjaGFyYWN0ZXIgdmFyaWFibGVzICJTdWJqZWN0IiB0byBmYWN0b3JzIGFuZCBsYWJlbGxlZCB0aGVtIHVzaW5nICJtdXRhdGUoKSIgYW5kICJmYWN0b3IoKSIuICJTdWJqZWN0IiBpbmNsdWRlcyB0aHJlZSBlZHVjYXRpb24gbGV2ZWxzIHRoYXQgYXJlIHN1aXRhYmxlIGZvciBhIGNvbnZlcnRpb24gdG8gZmFjdG9ycy4KCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSAKCmBgYHtyfQpHTkluZXc8LUdOSSAlPiUgc2VsZWN0KC1JTkRJQ0FUT1IsLVNVQkpFQ1QsLU1FQVNVUkUsLUZSRVFVRU5DWSwtRmxhZy5Db2RlcykgJT4lIHVuaXRlKExvY2F0aW9uX1llYXIsTE9DQVRJT04sVElNRSkKY29sbmFtZXMoR05JbmV3KVsyXTwtIkdOSSIKCmhvdXJzbmV3PC1ob3VycyU+JXNlbGVjdCgtSU5ESUNBVE9SLC1TVUJKRUNULC1NRUFTVVJFLC1GUkVRVUVOQ1ksLUZsYWcuQ29kZXMpICU+JSB1bml0ZShMb2NhdGlvbl9ZZWFyLExPQ0FUSU9OLFRJTUUpCmNvbG5hbWVzKGhvdXJzbmV3KVsyXTwtIkhSV0tEIgoKZW1wbG95bWVudG5ldzwtZW1wbG95bWVudCAlPiUgc2VsZWN0KC1JTkRJQ0FUT1IsLU1FQVNVUkUsLUZSRVFVRU5DWSwtRmxhZy5Db2RlcykgJT4lIHVuaXRlKExvY2F0aW9uX1llYXJfU3ViamVjdCwgTE9DQVRJT04sIFRJTUUsU1VCSkVDVCkKY29sbmFtZXMoZW1wbG95bWVudG5ldylbMl08LSJFTVAiCgplZHVjYXRpb25uZXc8LWVkdWNhdGlvbiAlPiUgc2VsZWN0KC1JTkRJQ0FUT1IsLU1FQVNVUkUsLUZSRVFVRU5DWSwtRmxhZy5Db2RlcykgJT4lIHVuaXRlKExvY2F0aW9uX1llYXJfU3ViamVjdCxMT0NBVElPTixUSU1FLFNVQkpFQ1QpCmNvbG5hbWVzKGVkdWNhdGlvbm5ldylbMl08LSJFRFUiCgpkZjE8LWZ1bGxfam9pbihlZHVjYXRpb25uZXcsZW1wbG95bWVudG5ldyxieT0iTG9jYXRpb25fWWVhcl9TdWJqZWN0IikKZGYxPC1kZjEgJT4lIHNlcGFyYXRlKDEsaW50bz1jKCJMb2NhdGlvbiIsIlllYXIiLCJFZHVjYXRpb25fbGV2ZWwiKSxleHRyYSA9ICJtZXJnZSIsZmlsbD0ibGVmdCIpICU+JSB1bml0ZShMb2NhdGlvbl9ZZWFyLCBMb2NhdGlvbiwgWWVhcikKZGYxWzE6MywgXSNhbiBleGFtcGxlIG9mIGRmMQoKZGYyPC1yaWdodF9qb2luKEdOSW5ldyxob3Vyc25ldyxieT0iTG9jYXRpb25fWWVhciIpCmRmMlsxOjMsIF0jYW4gZXhhbXBsZSBvZiBkZjIKCmRmPC1sZWZ0X2pvaW4oZGYyLGRmMSxieT0iTG9jYXRpb25fWWVhciIpCmRmPC1zZXBhcmF0ZShkZiwxLGludG89YygiQ291bnRyeV9jb2RlIiwiWWVhciIpKQpjb2xuYW1lcyhjb2RlKVsxXTwtIkNvdW50cnlfY29kZSIKZGY8LXJpZ2h0X2pvaW4oY29kZSxkZixieT0iQ291bnRyeV9jb2RlIikKZGYKYGBgCkkgam9pbmVkIGZpdmUgZGF0YXNldHMgaW50byBvbmUgdGlkeSBkYXRhc2V0IG5hbWVkICJkZiIuIFRvIGFjY29tcGxpc2ggdGhpcywgSSB0b29rIHRoZSBiZWxvdyBzdGVwczoKCjEuIEkgZGVsZXRlZCByZWR1bmRhbnQgdmFyaWFibGVzIHRvIGtlZXAgb25seSB0aGUgdmFyaWFibGVzIEkgbmVlZCB1c2luZyAic2VsZWN0KCkiIGZvciBlYWNoIGRhdGFzZXQuIAoKMi4gRm9yIEdOSSBhbmQgaG91cnMsIEkgY29tYmluZWQgdHdvIHZhcmlhYmxlcyAiTG9jYXRpb24iIGFuZCAiVGltZSIgdXNpbmcgInVuaXRlKCkiIGFuZCBzZXQgaXQgYXMgYSB1bmlxdWUgdmFyaWFibGUgdG8gbWF0Y2ggZm9yIGpvaW5pbmcgcHVycG9zZS4gSSByZW5hbWVkICJWYWx1ZSIgdG8gdGhlIGNvcnJlc3BvbmRpbmcgaW5kaWNhdG9ycyAiR05JIiBhbmQgIkhSV0tEIi4KCjMuIEZvciBlbXBsb3ltZW50IGFuZCBlZHVjYXRpb24sIEkgY29tYmluZWQgIkxvY2F0aW9uIiwgIlRpbWUiIGFuZCAiU3ViamVjdCIgdXNpbmcgInVuaXRlKCkiIGFuZCBzZXQgaXQgYXMgYSB1bmlxdWUgdmFyaWFibGUgdG8gbWF0Y2ggZm9yIGpvaW5pbmcgcHVycG9zZS4gSSByZW5hbWVkICJWYWx1ZSIgdG8gdGhlIGNvcnJlc3BvbmRpbmcgaW5kaWNhdG9ycyAiRU1QIiBhbmQgIkVEVSIuCgozLiBJIGpvaW5lZCBlbXBsb3ltZW50bmV3IGFuZCBlZHVjYXRpb25uZXcgYnkgIkxvY2F0aW9uX1llYXJfU3ViamVjdCIgaW50byBhIG5ldyBkYXRhc2V0IGRmMSB1c2luZyAiZnVsbF9qb2luKCkiIHRvIGtlZXAgYWxsIHRoZSB2YWx1ZXMgZnJvbSBib3RoIGRhdGFzZXRzLiBUaGVuIEkgc2VwYXJhdGVkIHRoZSB2YXJpYWJsZSAiTG9jYXRpb25fWWVhcl9TdWJqZWN0IiBpbnRvIHR3byB2YXJpYWJsZXMgIkxvY2F0aW9uX1llYXIiIGFuZCAiRWR1Y2F0aW9uX2xldmVsIiBmb3IgZnVydGhlciBqb2luaW5nIHB1cnBvc2UuIAoKNC4gSSBqb2luZWQgbWF0Y2hpbmcgcm93cyBmcm9tIEdOSW5ldyB0byBob3Vyc25ldyBieSAiTG9jYXRpb25fWWVhciIgaW50byBhIG5ldyBkYXRhc2V0IGRmMiB1c2luZyAicmlnaHRfam9pbigpIiBzbyB0aGF0IHRoZSB2YXJpYWxlICJIUldLRCIgd2FzIHBsYWNlZCB0byB0aGUgcmlnaHQgYW5kIGFsbCB2YWx1ZXMgZnJvbSBob3Vyc25ldyB3ZXJlIGtlcHQgYmVjYXVzZSBob3Vyc25ldyBvbmx5IGNvbnRhaW5lZCBjb3VudHJpZXMgdGhhdCBhcmUgT0VDRCBtZW1iZXIgY291bnRyaWVzLgoKNS4gSSBqb2luZWQgbWF0Y2hpbmcgcm93cyBmcm9tIGRmMSB0byBkZjIgYnkgIkxvY2F0aW9uX1llYXIiIGludG8gdGhlIGZpbmFsIGNvbWJpbmVkIGRhdGFzZXQgZGYgdXNpbmcgImxlZnRfam9pbigpIiB0byBrZWVwIGFsbCB2YWx1ZXMgb2YgZGYyLiBUaGVuIEkgc2VwYXJhdGVkIHRoZSB2YXJpYWJsZSAiTG9jYXRpb25fWWVhciIgaW50byAiQ291bnRyeV9jb2RlIiBhbmQgIlllYXIiLiBBdCBsYXN0LCBJIGpvaW5lZCBtYXRjaGluZyByb3dzIGZyb20gY29kZSB0byBkZiBieSAiQ291bnRyeV9jb2RlIiB1c2luZyAicmlnaHRfam9pbigpIiBzbyB0aGF0IEkgY291bGQgaGF2ZSBhIG5ldyBjb2x1bW4gIkRlZmluaXRpb24iIHRvIGNoZWNrIHRoZSBmdWxsIG5hbWUgb2YgZWFjaCBjb3VudHJ5IGNvZGUuCgojIwlUaWR5ICYgTWFuaXB1bGF0ZSBEYXRhIElJIAoKYGBge3J9CmZyZXE8LWRhdGEuZnJhbWUodGFibGUoZGYkQ291bnRyeV9jb2RlLGRmJFllYXIpKQpmcmVxPC1zcHJlYWQoZnJlcSxrZXkgPSBWYXIyLCB2YWx1ZSA9IEZyZXEpCmZyZXFbMTozLCBdI2FuIGV4YW1wbGUgb2YgZnJlcQp3aGljaChmcmVxJGAyMDE0YD09MCkKd2hpY2goZnJlcSRgMjAxNWA9PTApCndoaWNoKGZyZXEkYDIwMTZgPT0wKQpmcmVxJFZhcjFbMzo1XQpkZmZyZXExPC1kZlshKGRmJGBDb3VudHJ5X2NvZGVgPT0iQkVMIiksIF0KZGZuZXc8LWRmZnJlcTFbIShkZmZyZXExJGBDb3VudHJ5X2NvZGVgPT0iQ0hFIiksIF0KZGZuZXc8LWRmbmV3ICU+JSBncm91cF9ieShgQ291bnRyeV9jb2RlYCkgJT4lIG11dGF0ZShNZWFuX0dOST1tZWFuKHVuaXF1ZShHTkkpKSxNZWFuX2hvdXJzPW1lYW4odW5pcXVlKEhSV0tEKSkpCmRmbmV3CmBgYApBZnRlciBqb2luaW5nIHRoZSBkYXRhc2V0cywgSSBhc3N1bWVkIGFsbCBjb3VudHJpZXMgaGF2ZSBib3RoIEdOSSBhbmQgd29ya2luZyBob3VycyB2YWx1ZXMgYXZhaWxhYmxlIGZvciB0aHJlZSB5ZWFycywgMjAxNCB0byAyMDE2LiBJIGNoZWNrZWQgbXkgYXNzdW1wdGlvbiB1c2luZyAidGFibGUoKSIgdG8gc2VlIHRoZSBmcmVxdWVuY3kgb2YgYSBjb3VudHJ5IHVuZGVyIGVhY2ggeWVhci4gSSBjb252ZXJ0ZWQgdGhlIHRhYmxlIHRvIGEgZGF0YWZyYW1lIGZvciBlYXN5IGNoZWNraW5nLiBOb3JtYWxseSwgZWFjaCBjb3VudHJ5IHNob3VsZCBhcHBlYXIgdGhyZWUgdGltZXMgKGZvciB0aHJlZSBlZHVjYXRpb24gbGV2ZWxzIGluIGEgeWVhcikgb3IgYXQgbGVhc3Qgb25jZSB1bmRlciBlYWNoIHllYXIuIEkgZm91bmQgdGhhdCB0d28gY291bnRyaWVzICJCRUwiIGFuZCAiQ0hFIiBkbyBub3QgbWF0Y2ggd2l0aCB0aGlzIGNyaXRlcmlhLiBUaGV5IGhhdmUgbm8gcm93cyBmb3IgeWVhciAyMDE2LiBJdCB3YXMgYmVjYXVzZSB0aGF0IHRoZXNlIHR3byBjb3VudHJpZXMgaGF2ZSBubyAiSFJLV0QiIHZhbHVlcyBpbiB5ZWFyIDIwMTYgb3IgdGhleSBoYXZlIG5vIHZhbHVlcyBmb3IgYm90aCAiR05JIiBhbmQgIkhSS1dEIiBpbiB5ZWFyIDIwMTYuIElmIHRoZXkgaGF2ZSAiSFJLV0QiIHZhbHVlcyBpbiB5ZWFyIDIwMTYsIHRoZWlyIDIwMTYgcm93cyBzaG91bGQgYXBwcmVhciBiZWNhdXNlIGluIHRoZSBhYm92ZSBqb2luaW5nIHByb2Nlc3MgSSBrZXB0IGFsbCByb3dzIG9mIHRoZSB3b3JraW5nIGhvdXJzIGRhdGFzZXQuIAoKVG8gY2xhc3NpZnkgY291bnRyaWVzIGJhc2VkIG9uIGEgY29tYmluYXRpb24gb2YgdGhlaXIgaW5jb21lIGxldmVsIGFuZCB3b3JraW5nIGhvdXJzLCBJIG5lZWQgdG8gY2FsY3VsYXRlIGJvdGggdGhlIGF2ZXJhZ2UgR05JIGFuZCB3b3JraW5nIGhvdXJzIG92ZXIgdGhyZWUgeWVhcnMgMjAxNC0yMDE2IGZvciBlYWNoIGNvdW50cnkuIFRoZXJlZm9yZSBJIGV4Y2x1ZGVkIHRoZXNlIHR3byBjb3VudHJpZXMgaW4gbXkgcmVwb3J0LiBUaGVuIEkgY3JlYXRlZCB0d28gbmV3IGNvbHVtbnMgb2YgbWVhbiB2YWx1ZXMgdXNpbmcgImdyb3VwX2J5KCkiIGFuZCAibXV0YXRlKCkiLiBJIGFkZGVkICJ1bmlxdWUoKSIgaW4gdGhpcyBmdW5jdGlvbiB0byBlbnN1cmUgdGhlIHZhbHVlIG9mIGVhY2ggeWVhciBvbmx5IGFwcGVhcnMgb25jZSBpbiB0aGUgY2FsY3VsYXRpb24gdG8gZ2V0IHRoZSBjb3JyZWN0IG1lYW4gdmFsdWUuIAoKIyMJU2NhbiBJIApgYGB7cn0KY29sU3Vtcyhpcy5uYShkZm5ldykpCndoaWNoKGlzLm5hKGRmbmV3JERlZmluaXRpb24pKQpoZWFkKGRmbmV3WzI3NToyNzcsIF0pCmRmbmV3MTwtZGZuZXdbIShkZm5ldyRgQ291bnRyeV9jb2RlYD09Ik9FQ0QiKSwgXQoKY29sU3VtcygoaXMubmEoZGZuZXcxKSkpCndoaWNoKGlzLm5hKGRmbmV3MSRgRWR1Y2F0aW9uX2xldmVsYCkpPT13aGljaChpcy5uYShkZm5ldzEkRURVKSkKd2hpY2goaXMubmEoZGZuZXcxJGBFZHVjYXRpb25fbGV2ZWxgKSk9PXdoaWNoKGlzLm5hKGRmbmV3MSRFTVApKQp3aGljaChpcy5uYShkZm5ldzEkYEVkdWNhdGlvbl9sZXZlbGApKQpkZm5ldzFbYygxMDYsMjM2LDI0MCwyNjUpLCBdCgojQ0hMCnIxPC1kZm5ldzFbMjM3OjIzOSwgXQpkZm5ldzIgPC0gcmJpbmQoZGZuZXcxWzE6MjM2LF0scjEscjEsZGZuZXcxWy0oMToyMzYpLF0pCmMxPC1jKCIyMDE0IiwyMTkwMy4xNzIxOCwxOTkwKQpkZm5ldzJbMjM3OjIzOSwzOjVdPC1yYmluZChjMSxjMSxjMSkKYzI8LWMoIjIwMTYiLDIyMzU1Ljk4NDMsMTk3NCkKZGZuZXcyWzI0MzoyNDUsMzo1XTwtcmJpbmQoYzIsYzIsYzIpCmRmbmV3MzwtZGZuZXcyWy1jKDI0NiwyMzYpLCBdCmRmbmV3MyAlPiUgZmlsdGVyKENvdW50cnlfY29kZT09IkNITCIpCgoKI0lSTCBhbmQgUlVTCnIzPC1kZm5ldzNbMTA2LCBdCmRmbmV3NDwtcmJpbmQoZGZuZXczWzE6MTA2LCBdLHIzLHIzLGRmbmV3M1stKDE6MTA2KSwgXSkKcjQ8LWRmbmV3NFsyNzEsIF0KZGZuZXc1PC1yYmluZChkZm5ldzRbMToyNzEsIF0scjQscjQsZGZuZXc0Wy0oMToyNzEpLCBdKQpjMzwtYygiQmVsb3dfdXBwZXJfc2Vjb25kYXJ5IiwiVGVydGlhcnkiLCJVcHBlcl9zZWNvbmRhcnkiKQpkZm5ldzVbMTA2OjEwOCw2XTwtYzMKZGZuZXc1WzI3MToyNzMsNl08LWMzCmRmbmV3NjwtZGZuZXc1JT4lZ3JvdXBfYnkoYENvdW50cnlfY29kZWAsYEVkdWNhdGlvbl9sZXZlbGApJT4lIG11dGF0ZShFRFU9aWZlbHNlKGlzLm5hKEVEVSksbWVhbihFRFUsbmEucm09VFJVRSksRURVKSkgJT4lIG11dGF0ZShFTVA9aWZlbHNlKGlzLm5hKEVNUCksbWVhbihFTVAsbmEucm09VFJVRSksRU1QKSkKZGZuZXc2ICU+JSBmaWx0ZXIoQ291bnRyeV9jb2RlPT0iSVJMIikKZGZuZXc2ICU+JSBmaWx0ZXIoQ291bnRyeV9jb2RlPT0iUlVTIikKYGBgCioqTWlzc2luZyBWYWx1ZToqKgpJIGNoZWNrZWQgdGhlIG51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyBpbiBlYWNoIGNvbHVtbi4gVGhlIHRocmVlIG1pc3NpbmcgdmFsdWVzIGluIHZhcmlhYmxlICJEZWZpbml0aW9uIiBhcmUgIk9FQ0QiLCB3aGljaCByZXByZXNlbnRzIGFuIGF2ZXJhZ2UgdmFsdWUgZm9yIGFsbCBPRUNEIGNvdW50cmllcy4gSSBkZWxldGVkIHRoZXNlIHRocmVlIHJvd3MgYmVjYXVzZSBJIHdhbnQgdG8gc3R1ZHkgZWFjaCBjb3VudHJ5IHNlcGFyYXRlbHkuIEZvciBhbGwgdGhlIG90aGVyIG1pc3NpbmcgdmFsdWVzIGluICJFZHVjYXRpb25fbGV2ZWwiLCJFTVAiIGFuZCAiRURVIiwgSSBjaGVja2VkIHRoYXQgdGhleSB3ZXJlIGluIHRoZSBzYW1lIHJvd3MuIFRocmVlIGNvdW50cmllcyBpbmNsdWRlZCBtaXNzaW5nIHZhbHVlczogSXJlbGFuZCwgQ2hpbGUgYW5kIFJ1c3NpYS4gQ2hpbGUgb25seSBoYWQgZW1wbG95bWVudCByYXRlIGFuZCBlZHVjYXRpb25hbCBhdHRhaW5tZW50IGF2YWlsYWJsZSBpbiB5ZWFyIDIwMTUuIEkgY29waWVkIGl0cyAiRU1QIiBhbmQgIkVEVSIgdmFsdWVzIGZvciBlYWNoIGVkdWNhdGlvbiBsZXZlbCBvZiB5ZWFyIDIwMTUgdG8geWVhciAyMDE0IGFuZCAyMDE2LiBGb3IgSXJlbGFuZCBhbmQgUnVzc2lhLCB0aGV5IGhhZCBlbXBsb3ltZW50IHJhdGUgYW5kIGVkdWNhdGlvbmFsIGF0dGFpbm1lbnQgYXZhaWxibGUgaW4geWVhciAyMDE0IGFuZCAyMDE1LiBJIGltcHV0ZWQgdGhlIGF2ZXJhZ2UgdmFsdWVzIG9mIGVtcGxveW1lbnQgcmF0ZSBhbmQgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCBmb3IgZWFjaCBlZHVjYXRpb24gbGV2ZWwgaW4geWVhciAyMDE0IGFuZCB5ZWFyIDIwMTUgdG8geWVhciAyMDE2LgoKYGBge3J9CmRmbmV3NiRHTkk8LWFzLm51bWVyaWMoZGZuZXc2JEdOSSkKZGZuZXc2JEhSV0tEPC1hcy5pbnRlZ2VyKGRmbmV3NiRIUldLRCkKZGZuZXc2JEVkdWNhdGlvbl9sZXZlbDwtYXMuZmFjdG9yKGRmbmV3NiRFZHVjYXRpb25fbGV2ZWwpCm08LWNiaW5kKGRmbmV3NiRHTkksZGZuZXc2JEhSV0tELGRmbmV3NiRFRFUsZGZuZXc2JEVNUCxkZm5ldzYkTWVhbl9HTkksZGZuZXc2JE1lYW5faG91cnMpICN3aXRoIGEgcmVhc29uCnN1bShpcy5uYW4obSkpCnN1bShpcy5pbmZpbml0ZShtKSkgCmBgYAoqKlNwZWNpYWwgVmFsdWU6KioKSSBjaGVja2VkIHRoZSB0eXBlIG9mIHZhcmlhYmxlcyBhZ2FpbiBhbmQgZm91bmQgdGhhdCAiR05JIiwiSFJXS0QiIGFuZCAiRWR1Y2F0aW9uX2xldmVsIiBoYXZlIGJlY29tZSBjaGFyYWN0ZXJzLiBJdCB3YXMgcHJvYmFibHkgZHVlIHRvIHRoZSBhYm92ZSAidW5pdGUoKSIsICJzZXBhcmF0ZSgpIiBhbmQgInJiaW5kKCkiIHByb2Nlc3MgdGhhdCBmb3JjZWQgYWxsIHZhcmlhYmxlcyBpbnRvIG9uZSB0eXBlLiAiRWR1Y2F0aW9uX2xldmVsIiB3YXMgY29udmVydGVkIHRvIGZhY3RvcnMuICJHTkkiIGFuZCAiSFJXS0QiIHdlcmUgY29udmVydGVkIHRvIG51bWVyaWMgdmFsdWVzIGZvciBmdXJ0aGVyIGNoZWNraW5nIG9uIHNwZWNpYWwgdmFsdWVzLiBGdW5jdGlvbnMgImlzLm5hbigpIiBhbmQgImlzLmluZmluaXRlKCkiIG9ubHkgd29yayBmb3IgdmVjdG9yaWFsIGlucHV0LiBUaGVyZWZvcmUgSSBjb21iaW5lZCBhbGwgbnVtZXJpYyB2YWx1ZXMgaW50byBvbmUgbWF0cml4LiBJdCB0dXJuZWQgb3V0IHRoYXQgdGhlcmUgd2VyZSBubyBzcGVjaWFsIHZhbHVlcy4KCmBgYHtyfQpzdW0oZGZuZXc2JEdOSTwwKQpzdW0oZGZuZXc2JEhSV0tEPDApCnN1bShkZm5ldzYkRURVPD0wIHxkZm5ldzYkRURVPj0xMDApCnN1bShkZm5ldzYkRU1QPD0wIHxkZm5ldzYkRU1QPj0xMDApCnN1bShkZm5ldzYkTWVhbl9HTkk8MCkKc3VtKGRmbmV3NiRNZWFuX2hvdXJzPDApCmNoZWNrRURVMTAwPC1kZm5ldzYgJT4lIGdyb3VwX2J5KGBDb3VudHJ5X2NvZGVgLFllYXIpICU+JSBtdXRhdGUoc3VtRURVPXN1bShFRFUpKQp3aGljaChyb3VuZChjaGVja0VEVTEwMCRzdW1FRFUsZGlnaXRzID0gMCkhPTEwMCkKZGZuZXc2WzExODoxMjAsXQpgYGAKKipJbmNvbnNpc3RlbmN5OioqCgoqIEFsbCB2YWx1ZXMgb2YgIkdOSSIsIkhSV0tEIiwiTWVhbl9HTkkiIGFuZCAiTWVhbl9ob3VycyIgc2hvdWxkIGJlIGdyZWF0ZXIgdGhhbiB6ZXJvLiBBZnRlciBjaGVja2luZywgdGhlIGRhdGFzZXQgb2JleXMgdGhpcyBydWxlLgoqIEFsbCB2YWx1ZXMgb2YgIkVEVSIgYW5kICJFTVAiIGFyZSBwZXJjZW50YWdlcywgdGhlcmVmb3JlIHRoZXkgc2hvdWxkIGJlIHdpdGhpbiAwLTEwMC4gQWZ0ZXIgY2hlY2tpbmcsIHRoZSBkYXRhc2V0IG9iZXlzIHRoaXMgcnVsZS4KKiBGb3IgZWFjaCB5ZWFyIGluIGV2ZXJ5IGNvdW50cnksIHRoZSBzdW0gb2YgIkVEVSIgb2YgdGhyZWUgZWR1Y2F0aW9uIGxldmVscyBzaG91bGQgYmUgMTAwLiBUaGUgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCAoIkVEVSIpIHJlcHJlc2VudHMgdGhlIHBlcmNlbnRhZ2Ugb2YgdGhlIDI1LTY0IHllYXItb2xkcyB3aG8gYWNoaWV2ZWQgVGVydGlhcnkvVXBwZXIgc2Vjb25kYXJ5L0JlbG93IHVwcGVyIHNlY29uZGFyeSBhcyB0aGUgaGlnaGVzdCBlZHVjYXRpb24gbGV2ZWwuIFRoZXNlIHRocmVlIGVkdWNhdGlvbiBsZXZlbHMgY292ZXIgYWxsIHRoZSBzaXR1dGF0aW9ucy4gQWZ0ZXIgY2hlY2tpbmcsIEphcGFuIGRvZXMgbm90IGZ1bGZpbCB0aGUgcmVxdWlybWVudHMgYmVjYXVzZSBpdCBvbmx5IGhhcyB0aGUgdmFsdWVzIG9mICJFRFUiIGZvciBUZXJ0aWFyeSBsZXZlbC4gSSBkaWQgbm90IGFwcGx5IHRoZSBzYW1lIG1ldGhvZCB0byBjaGVjayAiRU1QIiBiZWNhdXNlIHRoZSBlbXBsb3ltZW50IHJhdGUgcmVwcmVzZW50cyB0aGUgcGVyY2VudGFnZSBvZiBlbXBsb3llZCAyNS02NCB5ZWFyLW9sZHMgYnkgYWxsIDI1LTY0IHllYXItb2xkcyB3aXRoIHRoZSBzYW1lIGVkdWNhdGlvbiBsZXZlbC4gVGhleSBjYW5ub3QgYmUgYWRkZWQgdXAgYmFzZWQgb24gZGlmZmVyZW50IHBvcHVsYXRpb24uCgojIwlTY2FuIElJCgpgYGB7cn0Kei5zY29yZXM8LWRmbmV3NiRHTkkgJT4lc2NvcmVzKHR5cGU9InoiKQp6LnNjb3JlcyAlPiUgc3VtbWFyeSgpCndoaWNoKGFicyh6LnNjb3Jlcyk+MykKei5zY29yZXM8LWRmbmV3NiRIUldLRCAlPiVzY29yZXModHlwZT0ieiIpCnouc2NvcmVzICU+JSBzdW1tYXJ5KCkKd2hpY2goYWJzKHouc2NvcmVzKT4zKQp6LnNjb3JlczwtZGZuZXc2JE1lYW5fR05JICU+JXNjb3Jlcyh0eXBlPSJ6IikKei5zY29yZXMgJT4lIHN1bW1hcnkoKQp3aGljaChhYnMoei5zY29yZXMpPjMpCnouc2NvcmVzPC1kZm5ldzYkTWVhbl9ob3VycyAlPiVzY29yZXModHlwZT0ieiIpCnouc2NvcmVzICU+JSBzdW1tYXJ5KCkKd2hpY2goYWJzKHouc2NvcmVzKT4zKQpsZW5ndGgoYm94cGxvdChkZm5ldzYkRURVIH4gZGZuZXc2JEVkdWNhdGlvbl9sZXZlbCwgbWFpbj0iRWR1Y2F0aW9uYWwgYXR0YWlubWVudCBieSBFZHVjYXRpb24gbGV2ZWwiLCB5bGFiID0gIkVkdWNhdGlvbiBsZXZlbCIsIHhsYWIgPSAiRWR1Y2F0aW9uYWwgYXR0YWlubWVudCIpJG91dCkKbGVuZ3RoKGJveHBsb3QoZGZuZXc2JEVNUCB+IGRmbmV3NiRFZHVjYXRpb25fbGV2ZWwsIG1haW49IkVtcGxveW1lbnQgcmF0ZSBieSBFZHVjYXRpb24gbGV2ZWwiLCB5bGFiID0gIkVkdWNhdGlvbiBsZXZlbCIsIHhsYWIgPSAiRW1wbG95bWVudCByYXRlIikkb3V0KQojaWRlbnRpZnkgdGhlIDkgb3V0bGllcnMgaW4gZWR1Y2F0aW9uYWwgYXR0YWlubWVudCBhYm92ZSB0aGUgdXBwZXIgZmVuY2UgZm9yICJCZWxvd191cHBlcl9zZWNvbmRhcnkiIGxldmVsCm91dGxpZXJFRFU8LWRmbmV3NiAlPiUgZmlsdGVyKEVkdWNhdGlvbl9sZXZlbD09IkJlbG93X3VwcGVyX3NlY29uZGFyeSIpICU+JSBzZWxlY3QoLUVNUCkKb3V0bGllckVEVTwtb3V0bGllckVEVVtvcmRlcihvdXRsaWVyRURVJEVEVSxkZWNyZWFzaW5nPVRSVUUpLCBdCm91dGxpZXJFRFVbMTo5LCBdCiNpZGVudGlmeSB0aGUgMSBvdXRsaWVycyBpbiBlbXBsb3ltZW50IHJhdGUgYWJvdmUgdGhlIHVwcGVyIGZlbmNlIGZvciAiVGVydGlhcnkiIGxldmVsCm91dGxpZXJFTVAxPC1kZm5ldzYgJT4lIGZpbHRlcihFZHVjYXRpb25fbGV2ZWw9PSJUZXJ0aWFyeSIpICU+JSBzZWxlY3QoLUVEVSkKb3V0bGllckVNUDF1cDwtb3V0bGllckVNUDFbb3JkZXIob3V0bGllckVNUDEkRU1QLGRlY3JlYXNpbmc9VFJVRSksIF0Kb3V0bGllckVNUDF1cFsxLCBdCiNpZGVudGlmeSB0aGUgMyBvdXRsaWVycyBpbiBlbXBsb3ltZW50IHJhdGUgYmVsb3cgdGhlIGxvd2VyIGZlbmNlIGZvciAiVGVydGlhcnkiIGxldmVsCm91dGxpZXJFTVAxbG93PC1vdXRsaWVyRU1QMVtvcmRlcihvdXRsaWVyRU1QMSRFTVApLCBdCm91dGxpZXJFTVAxbG93WzE6MywgXQojaWRlbnRpZnkgdGhlIDMgb3V0bGllciBpbiBlbXBsb3ltZW50IHJhdGUgYmVsb3cgdGhlIGxvd2VyIGZlbmNlIGZvciAiVXBwZXJfc2Vjb25kYXJ5IiBsZXZlbApvdXRsaWVyRU1QMjwtZGZuZXc2ICU+JSBmaWx0ZXIoRWR1Y2F0aW9uX2xldmVsPT0iVXBwZXJfc2Vjb25kYXJ5IikgJT4lIHNlbGVjdCgtRURVKQpvdXRsaWVyRU1QMjwtb3V0bGllckVNUDJbb3JkZXIob3V0bGllckVNUDIkRU1QKSwgXQpvdXRsaWVyRU1QMlsxOjMsIF0KYGBgCiogRm9yICJHTkkiLCJIUldLRCIsIk1lYW5fR05JIiBhbmQgIk1lYW5faG91cnMiLCBJIGFwcGxpZWQgei1zY29yZSBtZXRob2QgdG8gaWRlbnRpZnkgb3V0bGllcnMuIEFmdGVyIGNoZWNraW5nLCB0aGVyZSB3ZXJlIG5vIHN1Z2dlc3RlZCBvdXRsaWVycyBmb3IgdGhlbS4gSXQgbWF5IGltcGx5IHRoYXQgdGhlIHZhbHVlcyBvZiBncm9zcyBuYXRpb25hbCBpbmNvbWUgYW5kIHdvcmtpbmcgaG91cnMgZm9yIHRoZXNlIGNvdW50cmllcyByZW1haW4gaW4gYSByZWFzb25hYmxlIHJhbmdlLiBObyBjb3VudHJ5IHN0YW5kcyBvdXQgcmVnYXJkaW5nIGdyb3NzIG5hdGlvbmFsIGluY29tZSBvciB3b3JraW5nIGhvdXJzLgoqIEZvciB0aGUgZW1wbG95bWVudCByYXRlICgiRU1QIikgYW5kIGVkdWNhdGlvbmFsIGF0dGFpbm1lbnQgKCJFRFUiKSwgSSBhcHBsaWVkIGJpdmFyaWF0ZSBib3ggcGxvdHMgc28gdGhhdCBJIGNhbiBkZXRlY3Qgb3V0bGllcnMgYnkgZWR1Y2F0aW9uIGxldmVscy4gOSBvdXRsaWVycyB3ZXJlIGlkZW50aWZpZWQgaW4gdGhlIGVkdWNhdGlvbmFsIGF0dGFpbm1lbnQgZm9yICJCZWxvd191cHBlcl9zZWNvbmRhcnkiIGxldmVsLiA0IG91dGxpZXJzIHdlcmUgaWRlbnRpZmllZCBpbiB0aGUgZW1wbG95bWVudCByYXRlIGZvciAiVGVydGlhcnkiIGxldmVsLCB3aXRoIDEgYWJvdmUgdGhlIHVwcGVyIGZlbmNlIGFuZCAzIGJlbG93IHRoZSBsb3dlciBmZW5jZS4gMyBvdXRsaWVycyB3ZXJlIGlkZW50aWZpZWQgaW4gdGhlIGVtcGxveW1lbnQgcmF0ZSBmb3IgIlVwcGVyX3NlY29uZGFyeSIgbGV2ZWwuIEkgZGVjaWRlZCBub3QgdG8gZGVhbCB3aXRoIHRoZXNlIG91dGxpZXJzLiBJIGNvbXBhcmVkIHRoZSBkYXRhIG9uIGEgZ2xvYmFsIHNjYWxlLiBJdCBpcyByZWFzb25hYmxlIGZvciBkaWZmZXJlbnQgY291bnRyaWVzIHRvIGhhdmUgZGlmZmVyZW50IGNvbmRpdGlvbnMgb24gdGhlaXIgZWR1Y2F0aW9uIGFuZCBlbXBsb3ltZW50LiAKCiMjCVRyYW5zZm9ybSAKYGBge3J9CmJpbl9HTkk8LWRpc2NyZXRpemUoZGZuZXc2JE1lYW5fR05JLCBkaXNjID0gImVxdWFsd2lkdGgiLG5iaW5zPTMpCmRmbmV3NzwtYmluZF9jb2xzKGRmbmV3NixiaW5fR05JKQpiaW5faG91cnM8LWRpc2NyZXRpemUoZGZuZXc3JE1lYW5faG91cnMsZGlzYz0iZXF1YWx3aWR0aCIsIG5iaW5zPTMpCmRmbmV3ODwtYmluZF9jb2xzKGRmbmV3NyxiaW5faG91cnMpCgpkZm5ldzg8LSBkZm5ldzggJT4lIG11dGF0ZShYPWZhY3RvcihYLGxldmVscz1jKDEsMiwzKSxsYWJlbHMgPWMoIkxvd19pbmNvbWUiLCJNaWRkbGVfaW5jb21lIiwiSGlnaF9pbmNvbWUiKSkpICU+JSBtdXRhdGUoWDE9ZmFjdG9yKFgxLGxldmVscz1jKDEsMiwzKSxsYWJlbHM9YygiU2hvcnRfaG91cnMiLCJNZWRpdW1faG91cnMiLCJMb25nX2hvdXJzIikpKSAlPiUgdW5pdGUoQ2xhc3MsWCxYMSxzZXAgPSAiJiIpCmRmbmV3OApDb3VudHJ5X2NsYXNzPC1kZm5ldzggJT4lIHVuZ3JvdXAoKSAlPiUgZGlzdGluY3QoQ291bnRyeV9jb2RlLENsYXNzKQpDb3VudHJ5X2NsYXNzW29yZGVyKENvdW50cnlfY2xhc3MkQ2xhc3MpLCBdCmBgYAoqIEkgYXBwbGllZCBiaW5uaW5nIG1ldGhvZCB0byBkaXZpZGUgIk1lYW5fR05JIiBhbmQgIk1lYW5faG91cnMiIGZvciBzZXR0aW5nIHVwIHRoZSBzY2FsZSBvZiB0aGUgY2xhc3NpZmljYXRpb24uIFRoZSB2YXJpYWJsZXMgd2VyZSBkaXZpZGVkIGludG8gMyBpbnRlcnZhbHMgb2YgZXF1YWwgd2lkdGggdXNpbmcgImRpc2NyZXRpemUoKSIuIFRoZW4gSSBhZGRlZCB0aGUgYmluaW5nIGNvbHVtbnMgdG8gbXkgZGF0YXNldCB1c2luZyAiYmluZF9jb2xzKCkiLiBJIGNvbnZlcnRlZCB0aGUgYmluaW5pbmcgY29sdW1ucyB0byBmYWN0b3JzIGFuZCBsYWJlbGxlZCB0aGUgdmFsdWVzIHVzaW5nICJtdXRhdGUoKSIgYW5kICJmYWN0b3IoKSIuIFRoZW4gSSBjb21iaW5lZCB0aGUgdHdvIGJpbmluZyBjb2x1bW5zIGludG8gb25lIHZhcmlhYmxlICJDbGFzcyIuIAoqIEkgY3JlYXRlZCBhIG5ldyBkYXRhc2V0IENvdW50cnlfY2xhc3MgZm9yIGEgZWFzeSByZWZlcmVuY2Ugb24gdGhlIGNsYXNzaWZpY2F0aW9uLiBJIHVzZWQgInVuZ3JvdXAoKSIgdG8gZHJvcCBlYWNoIGNvdW50cnkncyBncm91cGluZyB3aXRoIHRoZSB0aHJlZSBlZHVjYXRpb24gbGV2ZWxzLiBUaGVuIEkgdXNlZCAiZGlzdGluY3QoKSIgdG8gcmVtb3ZlIGR1cGxpY2F0ZWQgcm93cyBiYXNlZCBvbiAiQ291bnRyeV9jb2RlIi4KCjxicj4KPGJyPgo=