0.1 Introduction

As part of our project, we are tasked to answer the question “What are the most valued data science skills?” by working as a team, deciding what data to collect and how to collect it, use relational database and set of normalized tables and data exploration and analysis. Our team members are as follows;

  • Anil Akyildirim

  • Nicholas Chung

  • Jai Jeffryes

  • Tamiko Jenkins

  • Joe Rovalino

  • Sie Siong Wong

As part of project management tools, we have used Slack Private channel and Skype for Project Communication, Github for Project tracking, documentation and code collaboration, and Amazon Relational Database Service for data integration. All of our supporting code and data are on the GitHub repo, which documents branches and commits from our team.

0.2 Load Data

0.2.1 Data Collection

We have reviewed and discussed different data types such as current job requirements around data scientists from job postings such as indeed.com or monster.com and articles around top data scientists skills in websites such as towardsdatascience and knuggets. Our approach built on the assumption that data scientists with jobs have the skills most valued by employers. We collected skills from employed data scientists.

we were inspired by the research of Jeff Hale whose article on data science skills appeared on the website, Medium.

We discussed different methods of collecting the data and further how we can store it. As a result, we decided to work with usefull data within linkedin.com. We compared our findings from LinkedIn data to Mr. Hale’s 2018 findings.

0.2.2 Load JSON files

# load all JSON
filenames <- list.files("data/profiles", pattern="*.json", full.names=TRUE) # this should give you a character vector, with each file name represented by an entry
example_file <- lapply(filenames[1], function(x) jsonlite::fromJSON(txt = x)) # a list in which each element is one of your original JSON files
example_file
## [[1]]
## [[1]]$profileAlternative
## [[1]]$profileAlternative$name
## [1] "Aditi Sharma"
## 
## [[1]]$profileAlternative$headline
## [1] "Data Scientist at Square"
## 
## [[1]]$profileAlternative$location
## [1] "San Francisco Bay Area"
## 
## [[1]]$profileAlternative$connections
## [1] "500+"
## 
## [[1]]$profileAlternative$summary
## [1] "Data Scientist with 3+ years of experience leveraging data to solve business problems in a results-driven environment. I thrive in a challenging environment and desire to continuously improve.\n\nCurrently working on recommender systems and pricing prediction using NLP/deep learning/RNN-LSTM."
## 
## 
## [[1]]$aboutAlternative
## [[1]]$aboutAlternative$text
## [1] "Data Scientist with 3+ years of experience leveraging data to solve business problems in a results-driven environment. I thrive in a challenging environment and desire to continuously improve.\n\nCurrently working on recommender systems and pricing prediction using NLP/deep learning/RNN-LSTM."
## 
## 
## [[1]]$positions
## list()
## 
## [[1]]$educations
##                                       title                         degree
## 1               University of San Francisco         Master of Science - MS
## 2 National Institute of Technology Warangal Bachelor of Technology (BTech)
##   date1 date2
## 1  2018  2019
## 2  2011  2015
## 
## [[1]]$skills
##                                 title count
## 1                              Python    11
## 2                                   R     9
## 3                                 C++     7
## 4                     Data Structures     5
## 5                          Statistics     2
## 6                    Machine Learning     2
## 7                            Research     1
## 8                        Optimization     1
## 9                       Data Analysis     1
## 10                Distributed Systems     1
## 11                         Algorithms  <NA>
## 12       Artificial Intelligence (AI)  <NA>
## 13                    Computer Vision  <NA>
## 14                 Financial Analysis  <NA>
## 15                 Regression Testing  <NA>
## 16 Neuro-Linguistic Programming (NLP)  <NA>
## 17                                SQL     2
## 18                             python     1
## 19          Amazon Web Services (AWS)     1
## 20                            MongoDB     1
## 21                           selenium  <NA>
## 22                             Matlab  <NA>
## 23                            PyTorch  <NA>
## 24                            pytorch  <NA>
## 25                                AWS  <NA>
## 26                            PySpark  <NA>
## 27                              NoSQL  <NA>
## 28                              MySQL  <NA>
## 29                      Deep Learning     2
## 30                Recommender Systems     1
## 31                Predictive Modeling     1
## 32                  Linear Regression     1
## 33                   Data Acquisition  <NA>
## 34          Exploratory Data Analysis  <NA>
## 35                       Scikit-Learn  <NA>
## 36                     Data Analytics  <NA>
## 37                Chatbot Development  <NA>
## 38                      Random Forest  <NA>
## 39                              CPLEX  <NA>
## 40                    Vehicle Routing  <NA>
## 41                                EDA  <NA>
## 42                        A/B Testing  <NA>
## 43                                NLP  <NA>
## 44  Natural Language Processing (NLP)  <NA>
## 
## [[1]]$recommendations
## [[1]]$recommendations$givenCount
## [1] "0"
## 
## [[1]]$recommendations$receivedCount
## [1] "0"
## 
## [[1]]$recommendations$given
## list()
## 
## [[1]]$recommendations$received
## list()
## 
## 
## [[1]]$accomplishments
##   count
## 1     1
##                                                                                    items
## 1 Scalable Motor Movement Recognition from Electroencephalography using Machine Learning
## 
## [[1]]$peopleAlsoViewed
## list()
## 
## [[1]]$volunteerExperience
## list()
## 
## [[1]]$profile
## [[1]]$profile$name
## [1] "Aditi Sharma"
## 
## [[1]]$profile$headline
## [1] "Data Scientist at Square"
## 
## [[1]]$profile$location
## [1] "San Francisco Bay Area"
## 
## [[1]]$profile$connections
## [1] "500+"
## 
## [[1]]$profile$summary
## [1] "Data Scientist with 3+ years of experience leveraging data to solve business problems in a results-driven environment. I thrive in a challenging environment and desire to continuously improve.\n\nCurrently working on recommender systems and pricing prediction using NLP/deep learning/RNN-LSTM."

0.3 Structure Data

0.3.1 Bind fromJSON results

# apply fromJSON to read in all of the json files
# create the column (variable) title, headline, which will be populated with json file identifying information
# extract the skills data which contains the variables title and counts
# bind the results together as a data frame named r_df
r_df <- dplyr::bind_rows(sapply(filenames, function(x) fromJSON(x, flatten=TRUE)$skills), .id="headline")

0.3.2 Extract Headlines

# apply fromJSON to read in all of the json files
# extract the headline variable from the profile data, saving each file name as the variable title
# save the mapping as  data frame headlines 
headlines <- sapply(filenames, function(x) fromJSON(x, flatten=TRUE)$profile$headline, USE.NAMES = TRUE)

0.3.3 Map Headlines to Skills

#  apply a look up of the variable title specifying the filename 
#  and add the headline value from the headlines  data frame
#  to the headlines variable in  data frame r_df

r_df$headline <- sapply(r_df$headline, function(x) headlines[x])

0.4 Format Data

0.4.1 Name and convert variables and data

df <- r_df
# TODO: conform names to db
# TODO: Fix naming call
# TODO: find out which naming convention we're using
# display data frame r_df
head(df)
##                   headline            title count
## 1 Data Scientist at Square           Python    11
## 2 Data Scientist at Square                R     9
## 3 Data Scientist at Square              C++     7
## 4 Data Scientist at Square  Data Structures     5
## 5 Data Scientist at Square       Statistics     2
## 6 Data Scientist at Square Machine Learning     2
# name r_df
names(df) <- c("title", "skills", "count")
#names(v) <- c("headline", "skills", "linkedin")
class(df)
## [1] "data.frame"
# create numeric values in Linkedin counts column
# coerce any nulls to na's
sapply(df, class)
##       title      skills       count 
## "character" "character" "character"
df$count <- as.numeric(df$count)
sapply(df, class)
##       title      skills       count 
## "character" "character"   "numeric"

0.4.2 Remove NA’s from numeric column

# count all rows
# 4822
# view a subset of rows with na's mixed with complete rows
nrow(df)
## [1] 4822
df[11:20,]
##                       title                             skills count
## 11 Data Scientist at Square                         Algorithms    NA
## 12 Data Scientist at Square       Artificial Intelligence (AI)    NA
## 13 Data Scientist at Square                    Computer Vision    NA
## 14 Data Scientist at Square                 Financial Analysis    NA
## 15 Data Scientist at Square                 Regression Testing    NA
## 16 Data Scientist at Square Neuro-Linguistic Programming (NLP)    NA
## 17 Data Scientist at Square                                SQL     2
## 18 Data Scientist at Square                             python     1
## 19 Data Scientist at Square          Amazon Web Services (AWS)     1
## 20 Data Scientist at Square                            MongoDB     1
# filter for any rows with na
# count all rows with na's
# 942
# view a subset of rows with only na's
df_na <- df %>% filter_all(any_vars(is.na(.)))
nrow(df_na)
## [1] 942
df[1:20,]
##                       title                             skills count
## 1  Data Scientist at Square                             Python    11
## 2  Data Scientist at Square                                  R     9
## 3  Data Scientist at Square                                C++     7
## 4  Data Scientist at Square                    Data Structures     5
## 5  Data Scientist at Square                         Statistics     2
## 6  Data Scientist at Square                   Machine Learning     2
## 7  Data Scientist at Square                           Research     1
## 8  Data Scientist at Square                       Optimization     1
## 9  Data Scientist at Square                      Data Analysis     1
## 10 Data Scientist at Square                Distributed Systems     1
## 11 Data Scientist at Square                         Algorithms    NA
## 12 Data Scientist at Square       Artificial Intelligence (AI)    NA
## 13 Data Scientist at Square                    Computer Vision    NA
## 14 Data Scientist at Square                 Financial Analysis    NA
## 15 Data Scientist at Square                 Regression Testing    NA
## 16 Data Scientist at Square Neuro-Linguistic Programming (NLP)    NA
## 17 Data Scientist at Square                                SQL     2
## 18 Data Scientist at Square                             python     1
## 19 Data Scientist at Square          Amazon Web Services (AWS)     1
## 20 Data Scientist at Square                            MongoDB     1
# omit any rows with na's
# save rows without na's as a data frame names df
# count the data frame 
# 4822-942 = 3880
df <- na.omit(df)
head(df)
##                      title           skills count
## 1 Data Scientist at Square           Python    11
## 2 Data Scientist at Square                R     9
## 3 Data Scientist at Square              C++     7
## 4 Data Scientist at Square  Data Structures     5
## 5 Data Scientist at Square       Statistics     2
## 6 Data Scientist at Square Machine Learning     2
nrow(df)
## [1] 3880

0.5 Store Data

0.5.1 Prepare data values for storage

# TODO: 1) I think we just need a names () on the column missing the column name 
# Done
# TODO: 2) I have to research how to remove unicode 
# suggest
# rs <- dbSendQuery(con, 'SET NAMES utf8')
# ALTER TABLE mytable CONVERT TO CHARACTER SET utf8mb4;

# TODO: 3)remove termination of comma
# TODO: can we try this: texts(df) <- iconv(texts(df, from = "UTF-8", to = "ASCII", sub = "")
# https://cran.r-project.org/web/packages/quanteda/quanteda.pdf
# stripped all the Unicode characters 
# stripped all the commas from the fields for skill and title

# Example of dirty data obtained with \d,"[^"]+,
# line 256: "396","Python",31,"Data Scientist at Conde Nast • MS in Data Science, Columbia University • IIIT-H Alumnus • Marathoner"

#x <- c("396","Python",31,"Data Scientist at Conde Nast • MS in Data Science, Columbia University • IIIT-H Alumnus • Marathoner")[4]
#validUTF8(x)
#validUTF8(xx)
#xi <- stringi::stri_enc_toascii(x)
#xi <- iconv(xi, "latin1", "ASCII", sub='')


# Joe
#library("quanteda")
#dfs <-df
#texts(dfs) <- iconv(texts(dfs, from = "UTF-8", to = "ASCII", sub = ""))

#Convert Latin characters to UTF-8
#convert_for_sql <- function(x) {
#  Encoding(x) <- "latin1"
#  x <-  iconv(x, "latin1", "UTF-8", sub='')
#  x <- stringr::str_replace(x,",","")
#  Encoding(x) <- "UTF-8"
#  return(x)
#}
df_f <- df
# Remove non-ASCII character codes
test <- df_f[256,]
df_f$skills <- sapply(df_f$skills, function(x) gsub('[^\x20-\x7E]', '', x))
df_f$title <- sapply(df_f$title, function(x) gsub('[^\x20-\x7E]', '', x))
df_f$skills <- sapply(df_f$skills, function(x) gsub('[@]', 'at', x))
df_f$title <- sapply(df_f$title, function(x) gsub('[@]', 'at', x))
df_f$skills <- sapply(df_f$skills, function(x) gsub('[\\|\\(\\),]', '', x))
df_f$title <- sapply(df_f$title, function(x) gsub('[\\|\\(\\),]', '', x))
Encoding(df_f$skills) <- "UTF-8"
Encoding(df_f$title) <- "UTF-8"
head(df_f)
##                      title           skills count
## 1 Data Scientist at Square           Python    11
## 2 Data Scientist at Square                R     9
## 3 Data Scientist at Square              C++     7
## 4 Data Scientist at Square  Data Structures     5
## 5 Data Scientist at Square       Statistics     2
## 6 Data Scientist at Square Machine Learning     2
test
##                                                                                                    title
## 397 Data Scientist at Conde Nast • MS in Data Science, Columbia University • IIIT-H Alumnus • Marathoner
##     skills count
## 397  MySQL    20
df_f[256,]
##                                                                                                title
## 397 Data Scientist at Conde Nast  MS in Data Science Columbia University  IIIT-H Alumnus  Marathoner
##     skills count
## 397  MySQL    20

0.5.2 Prepare data format for storage

# TODO: follow df.csv convention

# Add rownames (indices) as a skill id
# to final dataframe to prepare for 
# SQL-based storage and to provide option to
# remove automatic row names from write csv
# Remove depr function
#df_csv <- add_rownames(df, var = "skill_id")

# NB: these are the original row id's based on R records
# to generate skill_ids without skips for na's removed
# use a seq
df_csv <- tibble::rownames_to_column(df_f, var = "skill_id")
df_csv$skill_id <- as.numeric(df_csv$skill_id)
head(df_csv)
##   skill_id                    title           skills count
## 1        1 Data Scientist at Square           Python    11
## 2        2 Data Scientist at Square                R     9
## 3        3 Data Scientist at Square              C++     7
## 4        4 Data Scientist at Square  Data Structures     5
## 5        5 Data Scientist at Square       Statistics     2
## 6        6 Data Scientist at Square Machine Learning     2
# TODO: follow df.csv convention
# Rearrange column order with dplyr select
df_csv <- dplyr::select(df_csv, skill_id, skills, count, title)

head(df_csv)
##   skill_id           skills count                    title
## 1        1           Python    11 Data Scientist at Square
## 2        2                R     9 Data Scientist at Square
## 3        3              C++     7 Data Scientist at Square
## 4        4  Data Structures     5 Data Scientist at Square
## 5        5       Statistics     2 Data Scientist at Square
## 6        6 Machine Learning     2 Data Scientist at Square

0.5.3 Write to csv

# write csv and upload to our mysql database
# Encoding(df_csv)
write.csv(df_csv, "results/df_alt.csv", row.names=FALSE, fileEncoding="UTF-8")

0.6 Load Data

0.6.1 Load the data from the database

# load the data in the database and look at 2018 Linkedin Data
user_name <- 'anil'
user_password <- "redy2rok"
database <- 'prj3'
host_name <- 'msds607.ckxhi71v1dqf.us-east-1.rds.amazonaws.com'

#connecting to the MySQL database

myDb <- dbConnect(RMariaDB::MariaDB(), user=user_name, password=user_password, dbname=database, host=host_name)
myDb
## <MariaDBConnection>
##   Host:    msds607.ckxhi71v1dqf.us-east-1.rds.amazonaws.com
##   Server:  5.7.22-log
##   Client:  5.5.1

0.6.2 View tables

#list of tables we have
dbListTables(myDb)
##  [1] "agg_linkedin"            "df"                     
##  [3] "df_bak"                  "ds_general_skills_clean"
##  [5] "dsmain"                  "footable"               
##  [7] "just_skills"             "payscale_data"          
##  [9] "rawdata"                 "sample_linkedin_tall"   
## [11] "sample_linkedin_wide"    "skills_raw"

0.7 Exploring the Data

0.7.1 View 2018 Data

# lets load 2018 Linkedin Data
df <- dbGetQuery(myDb, "select * from df")
head(df)
##   skill_id           skills count                    title
## 1        1           Python    11 Data Scientist at Square
## 2        2                R     9 Data Scientist at Square
## 3        3              C++     7 Data Scientist at Square
## 4        4  Data Structures     5 Data Scientist at Square
## 5        5       Statistics     2 Data Scientist at Square
## 6        6 Machine Learning     2 Data Scientist at Square

0.7.2 View 2019 Data

# There are more than 145 skills, clean to data similar to 2018 data
df <- subset(df, select = c(skills, count))
colnames(df) <- c("Skills", "Linkedin")
head(df)
##             Skills Linkedin
## 1           Python       11
## 2                R        9
## 3              C++        7
## 4  Data Structures        5
## 5       Statistics        2
## 6 Machine Learning        2
# there are skills that is listed more than once. finding those
n_occur <- data.frame(table(df$Skills))
head(n_occur[n_occur$Freq > 1,])
##                 Var1 Freq
## 4        A/B Testing    3
## 8             Access    4
## 9         Accounting    2
## 11 Actuarial Science    5
## 14   Adobe Photoshop    4
## 15       Advertising    3
# we need to add the count of the duplicate skills rows 

df <- aggregate(Linkedin ~ Skills, dat=df, FUN=sum)
head(df)
##          Skills Linkedin
## 1          .NET        9
## 2   3D Modeling        1
## 3 8051 Assembly        3
## 4   A/B Testing        4
## 5        Abaqus       11
## 6  Ableton Live        1
# data is collected and ready to be analyzed at this point
summary(df)
##     Skills             Linkedin   
##  Length:929         Min.   :   1  
##  Class :character   1st Qu.:   2  
##  Mode  :character   Median :   5  
##                     Mean   :  33  
##                     3rd Qu.:  17  
##                     Max.   :2196
str(df)
## 'data.frame':    929 obs. of  2 variables:
##  $ Skills  : chr  ".NET" "3D Modeling" "8051 Assembly" "A/B Testing" ...
##  $ Linkedin:integer64 9 1 3 4 11 1 1 23 ...
df$Skills <- as.character(df$Skills)
df$Linkedin <- as.numeric(df$Linkedin)

0.8 Visualizing the Data

0.8.1 Improvements

The first exploratory pass is crowded. We’ll filter the data in the next pass.

#We have 1157 observations (skills) that data science roles use in linkedin
#let's see the distribution

theme_set(theme_classic())

ggplot(df, aes(x=Skills, y=Linkedin))+
  geom_bar(stat="identity", width = 0.5, fill=("tomato2"))+
             theme(axis.text.x = element_text(angle = 65, vjust=0.6))

# we have way too many skills so let's only focus on the ones that has significant count.

df <- filter(df, Linkedin >100)
head(df)
##           Skills Linkedin
## 1     Algorithms      317
## 2       Analysis      148
## 3      Analytics      560
## 4       Big Data      214
## 5 Bioinformatics      106
## 6      Bloomberg      107

0.8.2 Visualization

# we narrowed it down to 57 skills. Let's see how distribution looks like.

theme_set(theme_classic())

ggplot(df, aes(x=Skills, y=Linkedin))+
  geom_bar(stat="identity", width = 0.5, fill=("tomato2"))+
             theme(axis.text.x = element_text(angle = 65, vjust=0.6))

# let's narrow it down further. 
df <- filter(df, Linkedin > 200)
head(df)
##              Skills Linkedin
## 1        Algorithms      317
## 2         Analytics      560
## 3          Big Data      214
## 4 Business Analysis      207
## 5                 C      456
## 6               C++      498
theme_set(theme_classic())

ggplot(df, aes(x=reorder(Skills, Linkedin, fun=max), y=Linkedin))+
  geom_bar(stat="identity", width = 0.5, fill=("tomato2"))+
  labs(title="2019 Data Science Skills Distribution",
       x="Data Science Skills",
       y="Count in Linkedin")+
  theme(axis.text.x = element_text(angle = 65, vjust=0.6))

0.8.3 Analyze the Data

The Data Science skills Distribution chart for 2019 shows us the most frequent data science skills that people use for their Linkedin Profiles. The results show us that, Data Analysis; as part of General Data Skills, is the most commonly used skill within Data Scientists in Linkedin. The top three programming languages used within the profiles are R, Python and SQL. Statistics and Machine Learning are in 5th and 6th place in that order. If we consider Machine Learning and Statistics, as part of General Data Science Skills and Programming Languages as part of Technical Data Science Skills, we can conclude that

** Top three General Data Science Skills are Data Analysis, Statistics and Machine Learning.**

** Top three Technical Data Science Skills are R, Python and SQL.

# count for top three General and Technical Data Science Skills

data_analysis <- filter(df, df$Skills=="Data Analysis")
machine_learning <- filter(df, df$Skills=="Machine Learning")
statistics <- filter(df, df$Skills=="Statistics")
python <- filter(df, df$Skills=="Python")
r <- filter(df, df$Skills=="R")
sql <- filter(df, df$Skills=="SQL")
data_analysis
##          Skills Linkedin
## 1 Data Analysis     2196
machine_learning
##             Skills Linkedin
## 1 Machine Learning      979
statistics
##       Skills Linkedin
## 1 Statistics     1036
python
##   Skills Linkedin
## 1 Python     1539
r
##   Skills Linkedin
## 1      R     1864
sql
##   Skills Linkedin
## 1    SQL     1237

We can also look at the Linkedin Data Set from Jeff Hale and see if they follow the same pattern.

# lets load 2018 Linkedin Data from Jeff Hale.
skills_2018 <- dbGetQuery(myDb, "select * from ds_general_skills_clean")
skills_2018$LinkedIn <- as.numeric(skills_2018$LinkedIn) # little cleanup
head(skills_2018)
##            Keyword LinkedIn Indeed SimplyHired Monster
## 1 machine learning     5701   3439        2561    2340
## 2         analysis     5168   3500        2668    3306
## 3       statistics     4893   2992        2308    2399
## 4 computer science     4517   2739        2093    1900
## 5    communication     3404   2344        1791    2053
## 6      mathematics     2605   1961        1497    1815
# analyze briefly to see if there are differences
theme_set(theme_classic())

ggplot(skills_2018, aes(x=reorder(Keyword, LinkedIn, fun=max),y=LinkedIn))+
  geom_bar(stat="identity", width = 0.5, fill=("tomato2"))+
  labs(title="2018 Data Science Skills Distribution",
       x="Data Science Skills",
       y="Count in Linkedin",
       caption = "Source: Jeff Hale 2018 Data Skills Analysis")+
  theme(axis.text.x = element_text(angle = 65, vjust=0.6))

With the assumption of computer science covering the Programming Lanaguages, we can see that the data science skills distribution for 2018 is similar to our Data Science Skills Distribution for 2018. Machine Learning , Data Analysis and Statistics leading the top General Data Science Skills. The only difference we see is that Machine Learning is slightly more used Data Science Skill than Data Analysis.

0.9 Conclusions

The six data science skills most valued by employers in 2019 appear to be the following.

General Data Science Skills:

1- Data Analysis => 2196

2- Machine Learning => 979

3- Statistics => 1036

Technical Data Science Skills

1- R => 1864

2- Python => 1539

3- SQL => 1237

Our approach differed from Mr. Hale’s. He investigated programming languages as a separate research question. Our approach commingles them. Therefore, though our high-ranking skills list includes the languages R, Python, and SQL, nothing is to be concluded from their absence from Hale’s list. What we see in common are the skills of analysis, statistics, and machine learning. We believe the data tell a compelling story about investment in these disciplines.

LS0tDQp0aXRsZTogIlRpZHkgYW5kIGNsZWFuIExpbmtlZGluIFNjcmFwZWQgRGF0YSINCmF1dGhvcjogQW5pbCBBa3lpbGRpcmltLCBOaWNob2xhcyBDaHVuZywgSmFpIEplZmZyeWVzLCBUYW1pa28gSmVua2lucywgSm9lIFJvdmFsaW5vLA0KICBTaWUgU2lvbmcgV29uZw0KZGF0ZTogIjEwLzE5LzIwMTkiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQotLS0NCg0KIyMgSW50cm9kdWN0aW9uDQoNCkFzIHBhcnQgb2Ygb3VyIHByb2plY3QsIHdlIGFyZSB0YXNrZWQgdG8gYW5zd2VyIHRoZSBxdWVzdGlvbiAiV2hhdCBhcmUgdGhlIG1vc3QgdmFsdWVkIGRhdGEgc2NpZW5jZSBza2lsbHM/IiBieSB3b3JraW5nIGFzIGEgdGVhbSwgZGVjaWRpbmcgd2hhdCBkYXRhIHRvIGNvbGxlY3QgYW5kIGhvdyB0byBjb2xsZWN0IGl0LCB1c2UgcmVsYXRpb25hbCBkYXRhYmFzZSBhbmQgc2V0IG9mIG5vcm1hbGl6ZWQgdGFibGVzIGFuZCBkYXRhIGV4cGxvcmF0aW9uIGFuZCBhbmFseXNpcy4gT3VyIHRlYW0gbWVtYmVycyBhcmUgYXMgZm9sbG93czsNCg0KLSBBbmlsIEFreWlsZGlyaW0NCg0KLSBOaWNob2xhcyBDaHVuZw0KDQotIEphaSBKZWZmcnllcw0KDQotIFRhbWlrbyBKZW5raW5zDQoNCi0gSm9lIFJvdmFsaW5vDQoNCi0gU2llIFNpb25nIFdvbmcNCg0KQXMgcGFydCBvZiBwcm9qZWN0IG1hbmFnZW1lbnQgdG9vbHMsIHdlIGhhdmUgdXNlZCBTbGFjayBQcml2YXRlIGNoYW5uZWwgYW5kIFNreXBlIGZvciBQcm9qZWN0IENvbW11bmljYXRpb24sIEdpdGh1YiBmb3IgUHJvamVjdCB0cmFja2luZywgZG9jdW1lbnRhdGlvbiBhbmQgY29kZSBjb2xsYWJvcmF0aW9uLCBhbmQgQW1hem9uIFJlbGF0aW9uYWwgRGF0YWJhc2UgU2VydmljZSBmb3IgZGF0YSBpbnRlZ3JhdGlvbi4gQWxsIG9mIG91ciBzdXBwb3J0aW5nIGNvZGUgYW5kIGRhdGEgYXJlIG9uIHRoZSBHaXRIdWIgcmVwbywgd2hpY2ggZG9jdW1lbnRzIGJyYW5jaGVzIGFuZCBjb21taXRzIGZyb20gb3VyIHRlYW0uDQoNCi0gR2l0SHViOiBbaHR0cHM6Ly9naXRodWIuY29tL3Bub2phaS9kc2tpbGxdKGh0dHBzOi8vZ2l0aHViLmNvbS9wbm9qYWkvZHNraWxsKQ0KLSBBbWF6b24gUmVsYXRpb25hbCBEYXRhYmFzZSBTZXJ2aWNlOiBbbXNkczYwNy5ja3hoaTcxdjFkcWYudXMtZWFzdC0xLnJkcy5hbWF6b25hd3MuY29tXShtc2RzNjA3LmNreGhpNzF2MWRxZi51cy1lYXN0LTEucmRzLmFtYXpvbmF3cy5jb20pDQoNCg0KIyMgTG9hZCBEYXRhIHsudGFic2V0fQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkNCg0KIyBsb2FkIHBhY2thZ2VzDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShqc29ubGl0ZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoUk1hcmlhREIpDQpgYGANCg0KIyMjIERhdGEgQ29sbGVjdGlvbiANCg0KV2UgaGF2ZSByZXZpZXdlZCBhbmQgZGlzY3Vzc2VkIGRpZmZlcmVudCBkYXRhIHR5cGVzIHN1Y2ggYXMgY3VycmVudCBqb2IgcmVxdWlyZW1lbnRzIGFyb3VuZCBkYXRhIHNjaWVudGlzdHMgZnJvbSBqb2IgcG9zdGluZ3Mgc3VjaCBhcyBpbmRlZWQuY29tIG9yIG1vbnN0ZXIuY29tIGFuZCBhcnRpY2xlcyBhcm91bmQgdG9wIGRhdGEgc2NpZW50aXN0cyBza2lsbHMgaW4gd2Vic2l0ZXMgc3VjaCBhcyB0b3dhcmRzZGF0YXNjaWVuY2UgYW5kIGtudWdnZXRzLiBPdXIgYXBwcm9hY2ggYnVpbHQgb24gdGhlIGFzc3VtcHRpb24gdGhhdCBkYXRhIHNjaWVudGlzdHMgd2l0aCBqb2JzIGhhdmUgdGhlIHNraWxscyBtb3N0IHZhbHVlZCBieSBlbXBsb3llcnMuIFdlIGNvbGxlY3RlZCBza2lsbHMgZnJvbSBlbXBsb3llZCBkYXRhIHNjaWVudGlzdHMuDQoNCndlIHdlcmUgaW5zcGlyZWQgYnkgdGhlIHJlc2VhcmNoIG9mIEplZmYgSGFsZSB3aG9zZSBhcnRpY2xlIG9uIGRhdGEgc2NpZW5jZSBza2lsbHMgYXBwZWFyZWQgb24gdGhlIHdlYnNpdGUsIE1lZGl1bS4NCg0KLSBbaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3RoZS1tb3N0LWluLWRlbWFuZC1za2lsbHMtZm9yLWRhdGEtc2NpZW50aXN0cy00YTRhOGRiODk2ZGJdKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS90aGUtbW9zdC1pbi1kZW1hbmQtc2tpbGxzLWZvci1kYXRhLXNjaWVudGlzdHMtNGE0YThkYjg5NmRiKS4NCg0KV2UgZGlzY3Vzc2VkIGRpZmZlcmVudCBtZXRob2RzIG9mIGNvbGxlY3RpbmcgdGhlIGRhdGEgYW5kIGZ1cnRoZXIgaG93IHdlIGNhbiBzdG9yZSBpdC4gQXMgYSByZXN1bHQsIHdlIGRlY2lkZWQgdG8gd29yayB3aXRoIHVzZWZ1bGwgZGF0YSB3aXRoaW4gbGlua2VkaW4uY29tLiBXZSBjb21wYXJlZCBvdXIgZmluZGluZ3MgZnJvbSBMaW5rZWRJbiBkYXRhIHRvIE1yLiBIYWxlJ3MgMjAxOCBmaW5kaW5ncy4NCg0KDQojIyMgTG9hZCBKU09OIGZpbGVzDQpgYGB7cn0NCiMgbG9hZCBhbGwgSlNPTg0KZmlsZW5hbWVzIDwtIGxpc3QuZmlsZXMoImRhdGEvcHJvZmlsZXMiLCBwYXR0ZXJuPSIqLmpzb24iLCBmdWxsLm5hbWVzPVRSVUUpICMgdGhpcyBzaG91bGQgZ2l2ZSB5b3UgYSBjaGFyYWN0ZXIgdmVjdG9yLCB3aXRoIGVhY2ggZmlsZSBuYW1lIHJlcHJlc2VudGVkIGJ5IGFuIGVudHJ5DQpleGFtcGxlX2ZpbGUgPC0gbGFwcGx5KGZpbGVuYW1lc1sxXSwgZnVuY3Rpb24oeCkganNvbmxpdGU6OmZyb21KU09OKHR4dCA9IHgpKSAjIGEgbGlzdCBpbiB3aGljaCBlYWNoIGVsZW1lbnQgaXMgb25lIG9mIHlvdXIgb3JpZ2luYWwgSlNPTiBmaWxlcw0KZXhhbXBsZV9maWxlDQpgYGANCg0KIyMgU3RydWN0dXJlIERhdGEgey50YWJzZXR9DQoNCiMjIyBCaW5kIGZyb21KU09OIHJlc3VsdHMNCmBgYHtyfQ0KIyBhcHBseSBmcm9tSlNPTiB0byByZWFkIGluIGFsbCBvZiB0aGUganNvbiBmaWxlcw0KIyBjcmVhdGUgdGhlIGNvbHVtbiAodmFyaWFibGUpIHRpdGxlLCBoZWFkbGluZSwgd2hpY2ggd2lsbCBiZSBwb3B1bGF0ZWQgd2l0aCBqc29uIGZpbGUgaWRlbnRpZnlpbmcgaW5mb3JtYXRpb24NCiMgZXh0cmFjdCB0aGUgc2tpbGxzIGRhdGEgd2hpY2ggY29udGFpbnMgdGhlIHZhcmlhYmxlcyB0aXRsZSBhbmQgY291bnRzDQojIGJpbmQgdGhlIHJlc3VsdHMgdG9nZXRoZXIgYXMgYSBkYXRhIGZyYW1lIG5hbWVkIHJfZGYNCnJfZGYgPC0gZHBseXI6OmJpbmRfcm93cyhzYXBwbHkoZmlsZW5hbWVzLCBmdW5jdGlvbih4KSBmcm9tSlNPTih4LCBmbGF0dGVuPVRSVUUpJHNraWxscyksIC5pZD0iaGVhZGxpbmUiKQ0KYGBgDQoNCg0KIyMjIEV4dHJhY3QgSGVhZGxpbmVzDQpgYGB7cn0NCiMgYXBwbHkgZnJvbUpTT04gdG8gcmVhZCBpbiBhbGwgb2YgdGhlIGpzb24gZmlsZXMNCiMgZXh0cmFjdCB0aGUgaGVhZGxpbmUgdmFyaWFibGUgZnJvbSB0aGUgcHJvZmlsZSBkYXRhLCBzYXZpbmcgZWFjaCBmaWxlIG5hbWUgYXMgdGhlIHZhcmlhYmxlIHRpdGxlDQojIHNhdmUgdGhlIG1hcHBpbmcgYXMgIGRhdGEgZnJhbWUgaGVhZGxpbmVzIA0KaGVhZGxpbmVzIDwtIHNhcHBseShmaWxlbmFtZXMsIGZ1bmN0aW9uKHgpIGZyb21KU09OKHgsIGZsYXR0ZW49VFJVRSkkcHJvZmlsZSRoZWFkbGluZSwgVVNFLk5BTUVTID0gVFJVRSkNCmBgYA0KDQoNCiMjIyBNYXAgSGVhZGxpbmVzIHRvIFNraWxscw0KYGBge3J9DQojICBhcHBseSBhIGxvb2sgdXAgb2YgdGhlIHZhcmlhYmxlIHRpdGxlIHNwZWNpZnlpbmcgdGhlIGZpbGVuYW1lIA0KIyAgYW5kIGFkZCB0aGUgaGVhZGxpbmUgdmFsdWUgZnJvbSB0aGUgaGVhZGxpbmVzICBkYXRhIGZyYW1lDQojICB0byB0aGUgaGVhZGxpbmVzIHZhcmlhYmxlIGluICBkYXRhIGZyYW1lIHJfZGYNCg0Kcl9kZiRoZWFkbGluZSA8LSBzYXBwbHkocl9kZiRoZWFkbGluZSwgZnVuY3Rpb24oeCkgaGVhZGxpbmVzW3hdKQ0KDQpgYGANCg0KIyMgRm9ybWF0IERhdGEgey50YWJzZXR9DQoNCiMjIyBOYW1lIGFuZCBjb252ZXJ0IHZhcmlhYmxlcyBhbmQgZGF0YQ0KYGBge3J9DQpkZiA8LSByX2RmDQojIFRPRE86IGNvbmZvcm0gbmFtZXMgdG8gZGINCiMgVE9ETzogRml4IG5hbWluZyBjYWxsDQojIFRPRE86IGZpbmQgb3V0IHdoaWNoIG5hbWluZyBjb252ZW50aW9uIHdlJ3JlIHVzaW5nDQojIGRpc3BsYXkgZGF0YSBmcmFtZSByX2RmDQpoZWFkKGRmKQ0KIyBuYW1lIHJfZGYNCm5hbWVzKGRmKSA8LSBjKCJ0aXRsZSIsICJza2lsbHMiLCAiY291bnQiKQ0KI25hbWVzKHYpIDwtIGMoImhlYWRsaW5lIiwgInNraWxscyIsICJsaW5rZWRpbiIpDQpjbGFzcyhkZikNCiMgY3JlYXRlIG51bWVyaWMgdmFsdWVzIGluIExpbmtlZGluIGNvdW50cyBjb2x1bW4NCiMgY29lcmNlIGFueSBudWxscyB0byBuYSdzDQpzYXBwbHkoZGYsIGNsYXNzKQ0KZGYkY291bnQgPC0gYXMubnVtZXJpYyhkZiRjb3VudCkNCnNhcHBseShkZiwgY2xhc3MpDQpgYGANCg0KDQojIyMgUmVtb3ZlIE5BJ3MgZnJvbSBudW1lcmljIGNvbHVtbg0KYGBge3J9DQojIGNvdW50IGFsbCByb3dzDQojIDQ4MjINCiMgdmlldyBhIHN1YnNldCBvZiByb3dzIHdpdGggbmEncyBtaXhlZCB3aXRoIGNvbXBsZXRlIHJvd3MNCm5yb3coZGYpDQpkZlsxMToyMCxdDQoNCiMgZmlsdGVyIGZvciBhbnkgcm93cyB3aXRoIG5hDQojIGNvdW50IGFsbCByb3dzIHdpdGggbmEncw0KIyA5NDINCiMgdmlldyBhIHN1YnNldCBvZiByb3dzIHdpdGggb25seSBuYSdzDQpkZl9uYSA8LSBkZiAlPiUgZmlsdGVyX2FsbChhbnlfdmFycyhpcy5uYSguKSkpDQpucm93KGRmX25hKQ0KZGZbMToyMCxdDQoNCiMgb21pdCBhbnkgcm93cyB3aXRoIG5hJ3MNCiMgc2F2ZSByb3dzIHdpdGhvdXQgbmEncyBhcyBhIGRhdGEgZnJhbWUgbmFtZXMgZGYNCiMgY291bnQgdGhlIGRhdGEgZnJhbWUgDQojIDQ4MjItOTQyID0gMzg4MA0KZGYgPC0gbmEub21pdChkZikNCmhlYWQoZGYpDQpucm93KGRmKQ0KYGBgDQoNCg0KIyMgU3RvcmUgRGF0YSB7LnRhYnNldH0NCg0KIyMjIFByZXBhcmUgZGF0YSB2YWx1ZXMgZm9yIHN0b3JhZ2UNCmBgYHtyfQ0KDQojIFRPRE86IDEpIEkgdGhpbmsgd2UganVzdCBuZWVkIGEgbmFtZXMgKCkgb24gdGhlIGNvbHVtbiBtaXNzaW5nIHRoZSBjb2x1bW4gbmFtZSANCiMgRG9uZQ0KIyBUT0RPOiAyKSBJIGhhdmUgdG8gcmVzZWFyY2ggaG93IHRvIHJlbW92ZSB1bmljb2RlIA0KIyBzdWdnZXN0DQojIHJzIDwtIGRiU2VuZFF1ZXJ5KGNvbiwgJ1NFVCBOQU1FUyB1dGY4JykNCiMgQUxURVIgVEFCTEUgbXl0YWJsZSBDT05WRVJUIFRPIENIQVJBQ1RFUiBTRVQgdXRmOG1iNDsNCg0KIyBUT0RPOiAzKXJlbW92ZSB0ZXJtaW5hdGlvbiBvZiBjb21tYQ0KIyBUT0RPOiBjYW4gd2UgdHJ5IHRoaXM6IHRleHRzKGRmKSA8LSBpY29udih0ZXh0cyhkZiwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJIiwgc3ViID0gIiIpDQojIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9xdWFudGVkYS9xdWFudGVkYS5wZGYNCiMgc3RyaXBwZWQgYWxsIHRoZSBVbmljb2RlIGNoYXJhY3RlcnMgDQojIHN0cmlwcGVkIGFsbCB0aGUgY29tbWFzIGZyb20gdGhlIGZpZWxkcyBmb3Igc2tpbGwgYW5kIHRpdGxlDQoNCiMgRXhhbXBsZSBvZiBkaXJ0eSBkYXRhIG9idGFpbmVkIHdpdGggXGQsIlteIl0rLA0KIyBsaW5lIDI1NjogIjM5NiIsIlB5dGhvbiIsMzEsIkRhdGEgU2NpZW50aXN0IGF0IENvbmRlIE5hc3Qg4oCiIE1TIGluIERhdGEgU2NpZW5jZSwgQ29sdW1iaWEgVW5pdmVyc2l0eSDigKIgSUlJVC1IIEFsdW1udXMg4oCiIE1hcmF0aG9uZXIiDQoNCiN4IDwtIGMoIjM5NiIsIlB5dGhvbiIsMzEsIkRhdGEgU2NpZW50aXN0IGF0IENvbmRlIE5hc3Qg4oCiIE1TIGluIERhdGEgU2NpZW5jZSwgQ29sdW1iaWEgVW5pdmVyc2l0eSDigKIgSUlJVC1IIEFsdW1udXMg4oCiIE1hcmF0aG9uZXIiKVs0XQ0KI3ZhbGlkVVRGOCh4KQ0KI3ZhbGlkVVRGOCh4eCkNCiN4aSA8LSBzdHJpbmdpOjpzdHJpX2VuY190b2FzY2lpKHgpDQojeGkgPC0gaWNvbnYoeGksICJsYXRpbjEiLCAiQVNDSUkiLCBzdWI9JycpDQoNCg0KIyBKb2UNCiNsaWJyYXJ5KCJxdWFudGVkYSIpDQojZGZzIDwtZGYNCiN0ZXh0cyhkZnMpIDwtIGljb252KHRleHRzKGRmcywgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJIiwgc3ViID0gIiIpKQ0KDQojQ29udmVydCBMYXRpbiBjaGFyYWN0ZXJzIHRvIFVURi04DQojY29udmVydF9mb3Jfc3FsIDwtIGZ1bmN0aW9uKHgpIHsNCiMgIEVuY29kaW5nKHgpIDwtICJsYXRpbjEiDQojICB4IDwtICBpY29udih4LCAibGF0aW4xIiwgIlVURi04Iiwgc3ViPScnKQ0KIyAgeCA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZSh4LCIsIiwiIikNCiMgIEVuY29kaW5nKHgpIDwtICJVVEYtOCINCiMgIHJldHVybih4KQ0KI30NCmRmX2YgPC0gZGYNCiMgUmVtb3ZlIG5vbi1BU0NJSSBjaGFyYWN0ZXIgY29kZXMNCnRlc3QgPC0gZGZfZlsyNTYsXQ0KZGZfZiRza2lsbHMgPC0gc2FwcGx5KGRmX2Ykc2tpbGxzLCBmdW5jdGlvbih4KSBnc3ViKCdbXlx4MjAtXHg3RV0nLCAnJywgeCkpDQpkZl9mJHRpdGxlIDwtIHNhcHBseShkZl9mJHRpdGxlLCBmdW5jdGlvbih4KSBnc3ViKCdbXlx4MjAtXHg3RV0nLCAnJywgeCkpDQpkZl9mJHNraWxscyA8LSBzYXBwbHkoZGZfZiRza2lsbHMsIGZ1bmN0aW9uKHgpIGdzdWIoJ1tAXScsICdhdCcsIHgpKQ0KZGZfZiR0aXRsZSA8LSBzYXBwbHkoZGZfZiR0aXRsZSwgZnVuY3Rpb24oeCkgZ3N1YignW0BdJywgJ2F0JywgeCkpDQpkZl9mJHNraWxscyA8LSBzYXBwbHkoZGZfZiRza2lsbHMsIGZ1bmN0aW9uKHgpIGdzdWIoJ1tcXHxcXChcXCksXScsICcnLCB4KSkNCmRmX2YkdGl0bGUgPC0gc2FwcGx5KGRmX2YkdGl0bGUsIGZ1bmN0aW9uKHgpIGdzdWIoJ1tcXHxcXChcXCksXScsICcnLCB4KSkNCkVuY29kaW5nKGRmX2Ykc2tpbGxzKSA8LSAiVVRGLTgiDQpFbmNvZGluZyhkZl9mJHRpdGxlKSA8LSAiVVRGLTgiDQpoZWFkKGRmX2YpDQp0ZXN0DQpkZl9mWzI1NixdDQpgYGANCg0KDQojIyMgUHJlcGFyZSBkYXRhIGZvcm1hdCBmb3Igc3RvcmFnZQ0KYGBge3J9DQoNCiMgVE9ETzogZm9sbG93IGRmLmNzdiBjb252ZW50aW9uDQoNCiMgQWRkIHJvd25hbWVzIChpbmRpY2VzKSBhcyBhIHNraWxsIGlkDQojIHRvIGZpbmFsIGRhdGFmcmFtZSB0byBwcmVwYXJlIGZvciANCiMgU1FMLWJhc2VkIHN0b3JhZ2UgYW5kIHRvIHByb3ZpZGUgb3B0aW9uIHRvDQojIHJlbW92ZSBhdXRvbWF0aWMgcm93IG5hbWVzIGZyb20gd3JpdGUgY3N2DQojIFJlbW92ZSBkZXByIGZ1bmN0aW9uDQojZGZfY3N2IDwtIGFkZF9yb3duYW1lcyhkZiwgdmFyID0gInNraWxsX2lkIikNCg0KIyBOQjogdGhlc2UgYXJlIHRoZSBvcmlnaW5hbCByb3cgaWQncyBiYXNlZCBvbiBSIHJlY29yZHMNCiMgdG8gZ2VuZXJhdGUgc2tpbGxfaWRzIHdpdGhvdXQgc2tpcHMgZm9yIG5hJ3MgcmVtb3ZlZA0KIyB1c2UgYSBzZXENCmRmX2NzdiA8LSB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbihkZl9mLCB2YXIgPSAic2tpbGxfaWQiKQ0KZGZfY3N2JHNraWxsX2lkIDwtIGFzLm51bWVyaWMoZGZfY3N2JHNraWxsX2lkKQ0KaGVhZChkZl9jc3YpDQoNCiMgVE9ETzogZm9sbG93IGRmLmNzdiBjb252ZW50aW9uDQojIFJlYXJyYW5nZSBjb2x1bW4gb3JkZXIgd2l0aCBkcGx5ciBzZWxlY3QNCmRmX2NzdiA8LSBkcGx5cjo6c2VsZWN0KGRmX2Nzdiwgc2tpbGxfaWQsIHNraWxscywgY291bnQsIHRpdGxlKQ0KDQpoZWFkKGRmX2NzdikNCg0KYGBgDQoNCg0KDQojIyMgV3JpdGUgdG8gY3N2DQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgd3JpdGUgY3N2IGFuZCB1cGxvYWQgdG8gb3VyIG15c3FsIGRhdGFiYXNlDQojIEVuY29kaW5nKGRmX2NzdikNCndyaXRlLmNzdihkZl9jc3YsICJyZXN1bHRzL2RmX2FsdC5jc3YiLCByb3cubmFtZXM9RkFMU0UsIGZpbGVFbmNvZGluZz0iVVRGLTgiKQ0KDQpgYGANCg0KDQoNCiMjIExvYWQgRGF0YSB7LnRhYnNldH0NCg0KDQojIyMgTG9hZCB0aGUgZGF0YSBmcm9tIHRoZSBkYXRhYmFzZQ0KYGBge3J9DQojIGxvYWQgdGhlIGRhdGEgaW4gdGhlIGRhdGFiYXNlIGFuZCBsb29rIGF0IDIwMTggTGlua2VkaW4gRGF0YQ0KdXNlcl9uYW1lIDwtICdhbmlsJw0KdXNlcl9wYXNzd29yZCA8LSAicmVkeTJyb2siDQpkYXRhYmFzZSA8LSAncHJqMycNCmhvc3RfbmFtZSA8LSAnbXNkczYwNy5ja3hoaTcxdjFkcWYudXMtZWFzdC0xLnJkcy5hbWF6b25hd3MuY29tJw0KDQojY29ubmVjdGluZyB0byB0aGUgTXlTUUwgZGF0YWJhc2UNCg0KbXlEYiA8LSBkYkNvbm5lY3QoUk1hcmlhREI6Ok1hcmlhREIoKSwgdXNlcj11c2VyX25hbWUsIHBhc3N3b3JkPXVzZXJfcGFzc3dvcmQsIGRibmFtZT1kYXRhYmFzZSwgaG9zdD1ob3N0X25hbWUpDQpteURiDQpgYGANCg0KIyMjIFZpZXcgdGFibGVzDQoNCmBgYHtyfQ0KI2xpc3Qgb2YgdGFibGVzIHdlIGhhdmUNCmRiTGlzdFRhYmxlcyhteURiKQ0KDQpgYGANCg0KDQoNCg0KIyMgRXhwbG9yaW5nIHRoZSBEYXRhICB7LnRhYnNldH0NCg0KIyMjIFZpZXcgMjAxOCBEYXRhDQpgYGB7cn0NCiMgbGV0cyBsb2FkIDIwMTggTGlua2VkaW4gRGF0YQ0KZGYgPC0gZGJHZXRRdWVyeShteURiLCAic2VsZWN0ICogZnJvbSBkZiIpDQpoZWFkKGRmKQ0KDQoNCmBgYA0KDQojIyMgVmlldyAyMDE5IERhdGENCg0KYGBge3J9DQojIFRoZXJlIGFyZSBtb3JlIHRoYW4gMTQ1IHNraWxscywgY2xlYW4gdG8gZGF0YSBzaW1pbGFyIHRvIDIwMTggZGF0YQ0KZGYgPC0gc3Vic2V0KGRmLCBzZWxlY3QgPSBjKHNraWxscywgY291bnQpKQ0KY29sbmFtZXMoZGYpIDwtIGMoIlNraWxscyIsICJMaW5rZWRpbiIpDQpoZWFkKGRmKQ0KDQpgYGANCg0KYGBge3J9DQojIHRoZXJlIGFyZSBza2lsbHMgdGhhdCBpcyBsaXN0ZWQgbW9yZSB0aGFuIG9uY2UuIGZpbmRpbmcgdGhvc2UNCm5fb2NjdXIgPC0gZGF0YS5mcmFtZSh0YWJsZShkZiRTa2lsbHMpKQ0KaGVhZChuX29jY3VyW25fb2NjdXIkRnJlcSA+IDEsXSkNCg0KYGBgDQoNCmBgYHtyfQ0KIyB3ZSBuZWVkIHRvIGFkZCB0aGUgY291bnQgb2YgdGhlIGR1cGxpY2F0ZSBza2lsbHMgcm93cyANCg0KZGYgPC0gYWdncmVnYXRlKExpbmtlZGluIH4gU2tpbGxzLCBkYXQ9ZGYsIEZVTj1zdW0pDQpoZWFkKGRmKQ0KDQoNCmBgYA0KDQpgYGB7cn0NCiMgZGF0YSBpcyBjb2xsZWN0ZWQgYW5kIHJlYWR5IHRvIGJlIGFuYWx5emVkIGF0IHRoaXMgcG9pbnQNCnN1bW1hcnkoZGYpDQpzdHIoZGYpDQoNCmBgYA0KDQpgYGB7cn0NCg0KZGYkU2tpbGxzIDwtIGFzLmNoYXJhY3RlcihkZiRTa2lsbHMpDQpkZiRMaW5rZWRpbiA8LSBhcy5udW1lcmljKGRmJExpbmtlZGluKQ0KDQpgYGANCg0KDQoNCiMjIFZpc3VhbGl6aW5nIHRoZSBEYXRhIA0KDQojIyMgSW1wcm92ZW1lbnRzDQoNClRoZSBmaXJzdCBleHBsb3JhdG9yeSBwYXNzIGlzIGNyb3dkZWQuIFdlJ2xsIGZpbHRlciB0aGUgZGF0YSBpbiB0aGUgbmV4dCBwYXNzLg0KDQpgYGB7cn0NCg0KI1dlIGhhdmUgMTE1NyBvYnNlcnZhdGlvbnMgKHNraWxscykgdGhhdCBkYXRhIHNjaWVuY2Ugcm9sZXMgdXNlIGluIGxpbmtlZGluDQojbGV0J3Mgc2VlIHRoZSBkaXN0cmlidXRpb24NCg0KdGhlbWVfc2V0KHRoZW1lX2NsYXNzaWMoKSkNCg0KZ2dwbG90KGRmLCBhZXMoeD1Ta2lsbHMsIHk9TGlua2VkaW4pKSsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCB3aWR0aCA9IDAuNSwgZmlsbD0oInRvbWF0bzIiKSkrDQogICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2NSwgdmp1c3Q9MC42KSkNCg0KDQpgYGANCg0KYGBge3J9DQojIHdlIGhhdmUgd2F5IHRvbyBtYW55IHNraWxscyBzbyBsZXQncyBvbmx5IGZvY3VzIG9uIHRoZSBvbmVzIHRoYXQgaGFzIHNpZ25pZmljYW50IGNvdW50Lg0KDQpkZiA8LSBmaWx0ZXIoZGYsIExpbmtlZGluID4xMDApDQpoZWFkKGRmKQ0KDQpgYGANCg0KIyMjIFZpc3VhbGl6YXRpb24NCmBgYHtyfQ0KIyB3ZSBuYXJyb3dlZCBpdCBkb3duIHRvIDU3IHNraWxscy4gTGV0J3Mgc2VlIGhvdyBkaXN0cmlidXRpb24gbG9va3MgbGlrZS4NCg0KdGhlbWVfc2V0KHRoZW1lX2NsYXNzaWMoKSkNCg0KZ2dwbG90KGRmLCBhZXMoeD1Ta2lsbHMsIHk9TGlua2VkaW4pKSsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCB3aWR0aCA9IDAuNSwgZmlsbD0oInRvbWF0bzIiKSkrDQogICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2NSwgdmp1c3Q9MC42KSkNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBsZXQncyBuYXJyb3cgaXQgZG93biBmdXJ0aGVyLiANCmRmIDwtIGZpbHRlcihkZiwgTGlua2VkaW4gPiAyMDApDQpoZWFkKGRmKQ0KDQpgYGANCg0KDQpgYGB7cn0NCg0KdGhlbWVfc2V0KHRoZW1lX2NsYXNzaWMoKSkNCg0KZ2dwbG90KGRmLCBhZXMoeD1yZW9yZGVyKFNraWxscywgTGlua2VkaW4sIGZ1bj1tYXgpLCB5PUxpbmtlZGluKSkrDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5Iiwgd2lkdGggPSAwLjUsIGZpbGw9KCJ0b21hdG8yIikpKw0KICBsYWJzKHRpdGxlPSIyMDE5IERhdGEgU2NpZW5jZSBTa2lsbHMgRGlzdHJpYnV0aW9uIiwNCiAgICAgICB4PSJEYXRhIFNjaWVuY2UgU2tpbGxzIiwNCiAgICAgICB5PSJDb3VudCBpbiBMaW5rZWRpbiIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDY1LCB2anVzdD0wLjYpKQ0KDQoNCmBgYA0KDQoNCiMjIyBBbmFseXplIHRoZSBEYXRhIA0KDQpUaGUgRGF0YSBTY2llbmNlIHNraWxscyBEaXN0cmlidXRpb24gY2hhcnQgZm9yIDIwMTkgc2hvd3MgdXMgdGhlIG1vc3QgZnJlcXVlbnQgZGF0YSBzY2llbmNlIHNraWxscyB0aGF0IHBlb3BsZSB1c2UgZm9yIHRoZWlyIExpbmtlZGluIFByb2ZpbGVzLiBUaGUgcmVzdWx0cyBzaG93IHVzIHRoYXQsIERhdGEgQW5hbHlzaXM7IGFzIHBhcnQgb2YgR2VuZXJhbCBEYXRhIFNraWxscywgaXMgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBza2lsbCB3aXRoaW4gRGF0YSBTY2llbnRpc3RzIGluIExpbmtlZGluLiBUaGUgdG9wIHRocmVlIHByb2dyYW1taW5nIGxhbmd1YWdlcyB1c2VkIHdpdGhpbiB0aGUgcHJvZmlsZXMgYXJlIFIsIFB5dGhvbiBhbmQgU1FMLiBTdGF0aXN0aWNzIGFuZCBNYWNoaW5lIExlYXJuaW5nIGFyZSBpbiA1dGggYW5kIDZ0aCBwbGFjZSBpbiB0aGF0IG9yZGVyLiBJZiB3ZSBjb25zaWRlciBNYWNoaW5lIExlYXJuaW5nIGFuZCBTdGF0aXN0aWNzLCBhcyBwYXJ0IG9mIEdlbmVyYWwgRGF0YSBTY2llbmNlIFNraWxscyBhbmQgUHJvZ3JhbW1pbmcgTGFuZ3VhZ2VzIGFzIHBhcnQgb2YgVGVjaG5pY2FsIERhdGEgU2NpZW5jZSBTa2lsbHMsIHdlIGNhbiBjb25jbHVkZSB0aGF0IA0KDQoqKiBUb3AgdGhyZWUgR2VuZXJhbCBEYXRhIFNjaWVuY2UgU2tpbGxzIGFyZSBEYXRhIEFuYWx5c2lzLCBTdGF0aXN0aWNzIGFuZCBNYWNoaW5lIExlYXJuaW5nLioqDQoNCioqIFRvcCB0aHJlZSBUZWNobmljYWwgRGF0YSBTY2llbmNlIFNraWxscyBhcmUgUiwgUHl0aG9uIGFuZCBTUUwuIA0KDQpgYGB7cn0NCiMgY291bnQgZm9yIHRvcCB0aHJlZSBHZW5lcmFsIGFuZCBUZWNobmljYWwgRGF0YSBTY2llbmNlIFNraWxscw0KDQpkYXRhX2FuYWx5c2lzIDwtIGZpbHRlcihkZiwgZGYkU2tpbGxzPT0iRGF0YSBBbmFseXNpcyIpDQptYWNoaW5lX2xlYXJuaW5nIDwtIGZpbHRlcihkZiwgZGYkU2tpbGxzPT0iTWFjaGluZSBMZWFybmluZyIpDQpzdGF0aXN0aWNzIDwtIGZpbHRlcihkZiwgZGYkU2tpbGxzPT0iU3RhdGlzdGljcyIpDQpweXRob24gPC0gZmlsdGVyKGRmLCBkZiRTa2lsbHM9PSJQeXRob24iKQ0KciA8LSBmaWx0ZXIoZGYsIGRmJFNraWxscz09IlIiKQ0Kc3FsIDwtIGZpbHRlcihkZiwgZGYkU2tpbGxzPT0iU1FMIikNCmRhdGFfYW5hbHlzaXMNCm1hY2hpbmVfbGVhcm5pbmcNCnN0YXRpc3RpY3MNCnB5dGhvbg0Kcg0Kc3FsDQoNCmBgYA0KDQoNCg0KV2UgY2FuIGFsc28gbG9vayBhdCB0aGUgTGlua2VkaW4gRGF0YSBTZXQgZnJvbSBKZWZmIEhhbGUgYW5kIHNlZSBpZiB0aGV5IGZvbGxvdyB0aGUgc2FtZSBwYXR0ZXJuLg0KDQpgYGB7cn0NCiMgbGV0cyBsb2FkIDIwMTggTGlua2VkaW4gRGF0YSBmcm9tIEplZmYgSGFsZS4NCnNraWxsc18yMDE4IDwtIGRiR2V0UXVlcnkobXlEYiwgInNlbGVjdCAqIGZyb20gZHNfZ2VuZXJhbF9za2lsbHNfY2xlYW4iKQ0Kc2tpbGxzXzIwMTgkTGlua2VkSW4gPC0gYXMubnVtZXJpYyhza2lsbHNfMjAxOCRMaW5rZWRJbikgIyBsaXR0bGUgY2xlYW51cA0KaGVhZChza2lsbHNfMjAxOCkNCg0KDQpgYGANCg0KDQpgYGB7cn0NCiMgYW5hbHl6ZSBicmllZmx5IHRvIHNlZSBpZiB0aGVyZSBhcmUgZGlmZmVyZW5jZXMNCnRoZW1lX3NldCh0aGVtZV9jbGFzc2ljKCkpDQoNCmdncGxvdChza2lsbHNfMjAxOCwgYWVzKHg9cmVvcmRlcihLZXl3b3JkLCBMaW5rZWRJbiwgZnVuPW1heCkseT1MaW5rZWRJbikpKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHdpZHRoID0gMC41LCBmaWxsPSgidG9tYXRvMiIpKSsNCiAgbGFicyh0aXRsZT0iMjAxOCBEYXRhIFNjaWVuY2UgU2tpbGxzIERpc3RyaWJ1dGlvbiIsDQogICAgICAgeD0iRGF0YSBTY2llbmNlIFNraWxscyIsDQogICAgICAgeT0iQ291bnQgaW4gTGlua2VkaW4iLA0KICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBKZWZmIEhhbGUgMjAxOCBEYXRhIFNraWxscyBBbmFseXNpcyIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDY1LCB2anVzdD0wLjYpKQ0KDQpgYGANCg0KV2l0aCB0aGUgYXNzdW1wdGlvbiBvZiBjb21wdXRlciBzY2llbmNlIGNvdmVyaW5nIHRoZSBQcm9ncmFtbWluZyBMYW5hZ3VhZ2VzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGRhdGEgc2NpZW5jZSBza2lsbHMgZGlzdHJpYnV0aW9uIGZvciAyMDE4IGlzIHNpbWlsYXIgdG8gb3VyIERhdGEgU2NpZW5jZSBTa2lsbHMgRGlzdHJpYnV0aW9uIGZvciAyMDE4LiBNYWNoaW5lIExlYXJuaW5nICwgRGF0YSBBbmFseXNpcyBhbmQgU3RhdGlzdGljcyBsZWFkaW5nIHRoZSB0b3AgR2VuZXJhbCBEYXRhIFNjaWVuY2UgU2tpbGxzLiBUaGUgb25seSBkaWZmZXJlbmNlIHdlIHNlZSBpcyB0aGF0IE1hY2hpbmUgTGVhcm5pbmcgaXMgc2xpZ2h0bHkgbW9yZSB1c2VkIERhdGEgU2NpZW5jZSBTa2lsbCB0aGFuIERhdGEgQW5hbHlzaXMuDQoNCg0KIyMgQ29uY2x1c2lvbnMNCg0KVGhlIHNpeCBkYXRhIHNjaWVuY2Ugc2tpbGxzIG1vc3QgdmFsdWVkIGJ5IGVtcGxveWVycyBpbiAyMDE5IGFwcGVhciB0byBiZSB0aGUgZm9sbG93aW5nLg0KDQpHZW5lcmFsIERhdGEgU2NpZW5jZSBTa2lsbHM6DQoNCjEtIERhdGEgQW5hbHlzaXMgPT4gMjE5Ng0KDQoyLSBNYWNoaW5lIExlYXJuaW5nID0+IDk3OQ0KDQozLSBTdGF0aXN0aWNzID0+IDEwMzYNCg0KVGVjaG5pY2FsIERhdGEgU2NpZW5jZSBTa2lsbHMNCg0KMS0gUiA9PiAxODY0DQoNCjItIFB5dGhvbiA9PiAxNTM5DQoNCjMtIFNRTCA9PiAxMjM3DQoNCk91ciBhcHByb2FjaCBkaWZmZXJlZCBmcm9tIE1yLiBIYWxlJ3MuIEhlIGludmVzdGlnYXRlZCBwcm9ncmFtbWluZyBsYW5ndWFnZXMgYXMgYSBzZXBhcmF0ZSByZXNlYXJjaCBxdWVzdGlvbi4gT3VyIGFwcHJvYWNoIGNvbW1pbmdsZXMgdGhlbS4gVGhlcmVmb3JlLCB0aG91Z2ggb3VyIGhpZ2gtcmFua2luZyBza2lsbHMgbGlzdCBpbmNsdWRlcyB0aGUgbGFuZ3VhZ2VzIFIsIFB5dGhvbiwgYW5kIFNRTCwgbm90aGluZyBpcyB0byBiZSBjb25jbHVkZWQgZnJvbSB0aGVpciBhYnNlbmNlIGZyb20gSGFsZSdzIGxpc3QuIFdoYXQgd2Ugc2VlIGluIGNvbW1vbiBhcmUgdGhlIHNraWxscyBvZiBhbmFseXNpcywgc3RhdGlzdGljcywgYW5kIG1hY2hpbmUgbGVhcm5pbmcuIFdlIGJlbGlldmUgdGhlIGRhdGEgdGVsbCBhIGNvbXBlbGxpbmcgc3RvcnkgYWJvdXQgaW52ZXN0bWVudCBpbiB0aGVzZSBkaXNjaXBsaW5lcy4NCg0KDQoNCg==