# load libraries
library(readr)
library(dplyr)
1.Introduction
A huge part of data science involves acquiring raw data and getting
it into a form ready for analysis. Some have estimated that data
scientists spend 80% of their time cleaning and manipulating data, and
only 20% of their time actually analyzing it or building models from
it.
When we receive raw data, we have to do a number of things before
we’re ready to analyze it, possibly including:
1.diagnosing the “tidiness” of the data — how much data cleaning we
will have to do
2.reshaping the data — getting the right rows and columns for
effective analysis
3.combining multiple files
4.changing the types of values — how we fix a column where numerical
values are stored as Preview: Docs Loading link description strings, for
example
5.dropping or filling missing values - how we deal with data that is
incomplete or missing
6.manipulating strings to represent the data better
We will go through the techniques data scientists use to accomplish
these goals by looking at some “unclean” datasets and trying to get them
into a good, clean state. Along the way we will use the powerful
tidyverse packages dplyr and tidyr to get our data squeaky clean!
Instuctions
We have provided an example of data representing exam scores from
1000 students in an online math class.
These data frames, which you can view in the rendered notebook, are
hard to work with. They’re separated into multiple tables, and the
values don’t lend themselves well to analysis. Try to think about how
you would plot the exam score average against the age of the students in
the class. This would not be easy!
In the next exercises, we’ll transform this data so that performing a
task like that visualization would be simple.
# load data frame
students_1 <- read_csv('exams_0.csv')
students_2 <- read_csv('exams_1.csv')
# inspect data frame
head(students_1)
head(students_2)
2.Diagnose the Data
We often describe data that is easy to analyze and visualize as “tidy
data”. What does it mean to have tidy data?
For data to be tidy, it must have:
1.Each variable as a separate column
2.Each row as a separate observation
The first step of diagnosing whether or not a dataset is tidy is
using base R and dplyr functions to explore and probe the dataset.
You’ve seen most of the functions we often use to diagnose a dataset
for cleaning. Some of the most useful ones are:
1.head() — display the first 6 rows of the table
2.summary() — display the summary statistics of the table
3.colnames() — display the column names of the table
# read CSVs
grocery_1 <- read_csv('grocery_1.csv')
grocery_2 <- read_csv('grocery_2.csv')
Instructions
1.Provided in notebook.Rmd are two data frames, grocery_1 and
grocery_2.
Begin by viewing the head() of both grocery_1 and grocery_2.
# inspect data frames
head(grocery_1)
head(grocery_2)
summary(grocery_1)
Grocery Item Cake Recipe Pancake Recipe Cookie Recipe
Length:3 Min. :1.000 Min. :1.0 Min. :1.000
Class :character 1st Qu.:1.500 1st Qu.:1.5 1st Qu.:1.000
Mode :character Median :2.000 Median :2.0 Median :1.000
Mean :1.667 Mean :2.0 Mean :1.333
3rd Qu.:2.000 3rd Qu.:2.5 3rd Qu.:1.500
Max. :2.000 Max. :3.0 Max. :2.000
summary(grocery_2)
Grocery Item Recipe Number
Length:9 Length:9 Min. :1.000
Class :character Class :character 1st Qu.:1.000
Mode :character Mode :character Median :2.000
Mean :1.667
3rd Qu.:2.000
Max. :3.000
colnames(grocery_1)
[1] "Grocery Item" "Cake Recipe" "Pancake Recipe" "Cookie Recipe"
colnames(grocery_2)
[1] "Grocery Item" "Recipe" "Number"
2.Explore the data frames using the other functions listed.
Which data frame is “clean”, tidy, and ready for analysis? Create a
variable named clean_data_frame and assign it the value 1 if grocery_1
is a clean and tidy data frame or 2 if grocery_2 is a clean and tidy
data frame.
# clean data frame
clean_data_frame <- 2
3.Dealing with Multiple Files
Often, you have the same data separated out into multiple files.
Let’s say that you have a ton of files following the filename
structure: ‘file_1.csv’, ‘file_2.csv’, ‘file_3.csv’, and so on. The
power of dplyr and tidyr is mainly in being able to manipulate large
amounts of structured data, so you want to be able to get all of the
relevant information into one table so that you can analyze the
aggregate data.
You can combine the base R functions list.files() and lapply() with
readr and dplyr to organize this data better, as shown below: ” files
<- list.files(pattern = “file_.*csv”) df_list <-
lapply(files,read_csv) df <- bind_rows(df_list) ” 1.The first line
uses list.files() and a regular expression, a sequence of characters
describing a pattern of text that should be matched, to find any file in
the current directory that starts with ‘file_’ and has an extension of
csv, storing the name of each file in a vector files
2.The second line uses lapply() to read each file in files into a
data frame with read_csv(), storing the data frames in df_list
3.The third line then concatenates all of those data frames together
with dplyr’s bind_rows() function
Instructions
1.You have 10 different files containing 100 students each. These
files follow the naming structure:
exams_0.csv exams_1.csv … up to exams_9.csv
You are going to read each file into an individual data frame and
then combine all of the entries into one data frame.
First, create a variable called student_files and set it equal to the
list.files() of all of the CSV files we want to import.
# list files
student_files <- list.files(pattern = "exams_.*csv")
student_files
[1] "exams_0.csv" "exams_1.csv" "exams_2.csv" "exams_3.csv" "exams_4.csv" "exams_5.csv"
[7] "exams_6.csv" "exams_7.csv" "exams_8.csv" "exams_9.csv"
2.Read each file in student_files into a data frame using lapply()
and save the result to df_list.
# read files
df_list <- lapply(student_files, read_csv)
df_list
[[1]]
[[2]]
[[3]]
[[4]]
[[5]]
[[6]]
[[7]]
[[8]]
[[9]]
[[10]]
NA
3.Concatenate all of the data frames in df_list into one data frame
called students.
# concatenate data frames
students <- bind_rows(df_list)
students
4.Inspect students. Save the number of rows in students to
nrow_students. Did you get all of them
# number of rows in students
nrow_students <- nrow(students)
print(nrow_students)
[1] 1000
4.Reshaping your Data
Since we want
1.Each variable as a separate column
2.Each row as a separate observation
We would want to reshape a table like:
Account Checking Savings
“12456543” 8500 8900
“12283942” 6410 8020
“12839485” 78000 92000
Into a table that looks more like:
Account Account Type Amount
“12456543” “Checking” 8500
“12456543” “Savings” 8900
“12283942” “Checking” 6410
“12283942” “Savings” 8020
“12839485” “Checking” 78000
“12839485” “Savings” 920000
We can use tidyr’s gather() function to do this transformation.
gather() takes a data frame and the columns to unpack:
library(tidyr)
” df %>% gather(‘Checking’,‘Savings’,key=‘Account
Type’,value=‘Amount’) ” The arguments you provide are:
1.df: the data frame you want to gather, which can be piped into
gather()
2.Checking and Savings: the columns of the old data frame that you
want to turn into variables
3.key: what to call the column of the new data frame that stores the
variables
4.value: what to call the column of the new data frame that stores
the values
Instructions
1.The students data frame from the previous exercise has been loaded
into the notebook for you. Save the columns names to original_col_names
and print it.
# original column names
original_col_names <- colnames(students)
original_col_names
[1] "id" "full_name" "gender_age" "fractions" "probability" "grade"
2.There is a column for the scores on the fractions exam, and a
column for the scores on the probability exam.
We want to make each row an observation, so we want to transform this
table to look like:
full_name exam score gender_age grade
“First Student” “fractions” score% … …
“First Student” “probability” score% … …
“Second Student” “fractions” score% … …
“Second Student” “probability” score% … …
Use gather to create a new table (still called students) that follows
this structure. Then view the head() of students.
# gather columns
students <- students %>%
gather('fractions', 'probability', key = 'exam', value = 'score')
head(students)
3.Save the columns names of the updated students data frame to
gathered_col_names and print it.
# updated column names
gathered_col_names <- colnames(students)
gathered_col_names
[1] "id" "full_name" "gender_age" "grade" "exam" "score"
4.The dplyr function count() takes a data frame and a column as
arguments and returns a table with counts of the unique values in the
named column.
Find the count of each unique value in the exam column. Save the
result to exam_counts and view exam_counts.
# unique value counts of exam
exam_counts <- students %>%
count(exam)
exam_counts
5.Dealing with Duplicates
Often we see duplicated rows of data in the data frames we are
working with. This could happen due to errors in data collection or in
saving and loading the data.
To check for duplicates, we can use the base R function duplicated(),
which will return a logical vector telling us which rows are duplicate
rows.
Let’s say we have a data frame fruits that represents this table:
item price calories
“banana” “$1” 105
“apple” “$0.75” 95
“apple” “$0.75” 95
“peach” “$3” 55
“peach” “$4” 55
“clementine” “$2.5” 35
If we call fruits %>% duplicated(), we would get the following
vector:
“>> [1] FALSE FALSE TRUE FALSE FALSE FALSE”
We can see that the third row, which represents an “apple” with price
“$0.75” and 95 calories, is a duplicate row. Every value in this row is
the same as in another row (the previous row).
We can use the dplyr distinct() function to remove all rows of a data
frame that are duplicates of another row.
If we call fruits %>% distinct(), we would get the table:
item price calories
“banana” “$1” 105
“apple” “$0.75” 95
“peach” “$3” 55
“peach” “$4” 55
“clementine” “$2.5” 35
The “apple” row was deleted because it was exactly the same as
another row. But the two “peach” rows remain because there is a
difference in the price column.
If we wanted to remove every row with a duplicate value in the item
column, we could specify a subset:
“fruits %>% distinct(item,.keep_all=TRUE)”
By default, this keeps the first occurrence of the duplicate:
item price calories
“banana” “$1” 105
“apple” “$0.75” 95
“peach” “$3” 55
“clementine” “$2.5” 35
Make sure that the columns you drop duplicates from are specifically
the ones where duplicates don’t belong. You wouldn’t want to drop
duplicates with the price column as a subset, for example, because it’s
okay if multiple items cost the same amount!
Instructions
1.The students data frame has a column id that is neither unique nor
required for our analysis. Drop the id column from the data frame and
save the result to students. View the head() of students.
# drop id column
students <- students %>%
select(-id)
head(students)
- It seems like in the data collection process, some rows may have
been recorded twice. Use the duplicated() function on the students data
frame to make a vector object called duplicates.
# find duplicated rows
duplicates <- students %>%
duplicated()
head(duplicates)
[1] FALSE FALSE FALSE FALSE FALSE FALSE
3.table() is a base R function that takes any R object as an argument
and returns a table with the counts of each unique value in the
object.
Pipe the result from the previous checkpoint into table() to see how
many rows are exact duplicates. Make sure to save the result to
duplicate_counts, and view duplicate_counts.
# count duplicated rows
duplicate_counts <- duplicates %>%
table()
duplicate_counts
.
FALSE TRUE
1976 24
4.Get rid of the duplicate rows in the students data frame and save
this new data frame as unique_students.
# remove duplicated rows, keep only unique rows
unique_students <- students %>%
distinct()
unique_students
5.Use the duplicated() function again to make an object called
updated_duplicates after dropping the duplicates. Pipe the result into
table() to see if any duplicates remain, and view updated_duplicates.
Are there any TRUEs left?
# find and count duplicated rows in updated data frame
updated_duplicates <- unique_students %>%
duplicated() %>%
table()
updated_duplicates
.
FALSE
1976
6.Splitting By Index
In trying to get clean data, we want to make sure each column
represents one type of measurement. Often, multiple measurements are
recorded in the same column, and we want to separate these out so that
we can do individual analysis on each variable.
Let’s say we have a column “birthday” with data formatted in MMDDYYYY
format. In other words, “11011993” represents a birthday of November 1,
1993. We want to split this data into day, month, and year so that we
can use these columns as separate features.
In this case, we know the exact structure of these strings. The first
two characters will always correspond to the month, the second two to
the day, and the rest of the string will always correspond to year. We
can easily break the data into three separate columns by splitting the
strings into substrings using str_sub(), a helpful function from the
stringr package:
// Create the ‘month’ column df %>% mutate(month =
str_sub(birthday,1,2))
// Create the ‘day’ column df %>% mutate(day =
str_sub(birthday,3,4))
// Create the ‘year’ column df %>% mutate(year =
str_sub(birthday,5))
1.The first command takes the characters starting at index 1 and
ending at index 2 of each value in the birthday column and puts it into
a month column.
2.The second command takes the characters starting at index 3 and
ending at index 4 of each value in the birthday column and puts it into
a day column.
3.The third command takes the characters starting at index 5 and
ending at the end of the value in the birthday column and puts it into a
year column.
This would transform a table like:
id birthday
1011 “12241989”
1112 “10311966”
1113 “01052011”
into a table like:
id birthday month day year
1011 “12241989” “12” “24” “1989”
1112 “10311966” “10” “31” “1966”
1113 “01052011” “01” “05” “2011”
We will practice changing string columns into numerical columns (like
converting “10” to 10) in a future exercise.
Instructions
1.Print out the columns of the students data frame.
# print columns of students
print(colnames(students))
[1] "full_name" "gender_age" "grade" "exam" "score"
2.The column gender_age sounds like it contains both gender and
age!
View the head() of students to see what kind of data gender_age
contains.
# view head of students
head(students)
3.It looks like the first character of the values in gender_age
contains the gender, while the rest of the string contains the age.
Let’s separate out the gender data into a new column called gender. Save
the result to students, and view the head().
# add gender and age columns
library(stringr)
students <- students %>%
mutate(gender = str_sub(gender_age, 1, 1))
head(students)
4.Now, separate out the age data into a new column called age. Save
the updated data frame to students, and view the head().
# add gender and age columns
students <- students %>%
mutate(age = str_sub(gender_age, 2))
head(students)
5.Now, we don’t need that gender_age column anymore. Drop gender_age
from students, and save the result to students. View the head() of
students.
# drop gender_age column
students <- students %>%
select(-gender_age)
head(students)
7.Splitting By Character
Let’s say we have a column called “type” with data entries in the
format “admin_US” or “user_Kenya”, as shown in the table below.
id type
1011 “user_Kenya”
1112 “admin_US”
1113 “moderator_UK”
Just like we saw before, this column actually contains two types of
data. One seems to be the user type (with values like “admin” or “user”)
and one seems to be the country this user is in (with values like “US”
or “Kenya”).
We can no longer just split along the first 4 characters because
admin and user are of different lengths. Instead, we know that we want
to split along the “”. We can thus use the tidyr function separate()
to split this column into two, separate columns: ” Create the
‘user_type’ and ‘country’ columns //df %>% //
separate(type,c(‘user_type’,‘country’),’’)”
1.type is the column to split
2.c(‘user_type’,‘country’) is a vector with the names of the two new
columns
3.’_’ is the character to split on
This would transform the table above into a table like:
id user_type country
1011 “user” “Kenya”
1112 “admin” “US”
1113 “moderator” “UK”
Instructions
1.View the head() of students. Notice that the students’ names are
stored in a column called full_name.
# view the head of students
head(students)
2.Separate the full_name column into two new columns, first_name and
last_name, by splitting on the ’ ’ character .
Provide as an extra argument to the separate() function extra
=‘merge’. This will ensure that middle names or two-word last names will
all end up in the last_name column.
Save the result to students, and view the head().
# separate the full_name column
students <- students %>%
separate('full_name', c('first_name', 'last_name'), ' ', extra ='merge')
head(students)
8.Looking at Data Types
Each column of a data frame can hold items of the same data type. The
data types that R uses are: character, numeric (real or decimal),
integer, logical, or complex. Often, we want to convert between types so
that we can do better analysis. If a numerical category like “num_users”
is stored as a vector of characters instead of numerics, for example, it
makes it more difficult to do something like make a line graph of users
over time.
To see the types of each column of a data frame, we can use:
str(df)
str() displays the internal structure of an R object. Calling str()
with a data frame as an argument will return a variety of information,
including the data types. For a data frame like this:
item price calories
“banana” “$1” 105
“apple” “$0.75” 95
“peach” “$3” 55
“clementine” “$2.5” 35
the data types would be:
// #> $ item: chr
// #> $ price: chr
// #> $ calories: num
We can see that the price column is made up of characters, which will
probably make our analysis of price more difficult. We’ll look at how to
convert columns into numeric values in the next few exercises.
Instructions
1.Let’s inspect the data types in the students table.
Print out the structure of students.
# print structure of students
str(students)
tibble [2,000 × 7] (S3: tbl_df/tbl/data.frame)
$ first_name: chr [1:2000] "Moses" "Timofei" "Silvain" "Lezley" ...
$ last_name : chr [1:2000] "Kirckman" "Strowan" "Poll" "Pinxton" ...
$ grade : num [1:2000] 11 11 9 11 11 11 9 11 12 11 ...
$ exam : chr [1:2000] "fractions" "fractions" "fractions" "fractions" ...
$ score : chr [1:2000] "69%" "63%" "69%" NA ...
$ gender : chr [1:2000] "M" "M" "M" "M" ...
$ age : chr [1:2000] "14" "18" "18" "18" ...
2.If we wanted to make a scatterplot of age vs average exam score,
would we be able to do it with this type of data?
Paste the following code in the last code block to try and print out
the mean of the score column of students.
” students %>% summarise(mean_score = mean(score)) ”
What warning do you see?
# mean of age column
students %>%
summarise(mean_score = mean(score))
9.String Parsing
Sometimes we need to modify strings in our data frames to help us
transform them into more meaningful metrics. For example, in our fruits
table from before:
item price calories
“banana” “$1” 105
“apple” “$0.75” 95
“peach” “$3” 55
“peach” “$4” 55
“clementine” “$2.5” 35
We can see that the ‘price’ column is actually composed of character
strings representing dollar amounts. This column could be much better
represented as numeric, so that we could take the mean, calculate other
aggregate statistics, or compare different fruits to one another in
terms of price.
First, we can use a regular expression, a sequence of characters that
describe a pattern of text to be matched, to remove all of the dollar
signs. The base R function gsub() will remove the $ from the price
column, replacing the symbol with an empty string ’’:
“fruit %>% mutate(price=gsub(‘\$’,’’,price))”
Then, we can use the base R function as.numeric() to convert
character strings containing numerical values to numeric:
“fruit %>% mutate(price = as.numeric(price))”
Now, we have a data frame that looks like: item price calories
“banana” 1 105
“apple” 0.75 95
“peach” 3 55
“peach” 4 55
“clementine” 2.5 35
Instructions
1.We saw in the last exercise that finding the mean of the score
column is hard to do when the data is stored as characters and not
numbers.
View the head() of students to take a look at the values in the score
column.
# view head of students
head(students)
2.Remove the ‘%’ symbol from the score column, and save the resulting
data frame to students. View students.
# remove % from score column
students <- students %>%
mutate(score = gsub('%', '',score))
students
3.Convert the score column to a numerical type using the as.numeric()
function. Save this new data frame to students, and view
# change score column to numeric
students <- students %>%
mutate(score = as.numeric(score))
students
10.Review
Great! We have looked at a number of different methods we may use to
get data into the format we want for analysis.
Specifically, we have covered:
1.diagnosing the “tidiness” of data
2.combining multiple files
3.reshaping data
4.changing the types of values
5.manipulating strings to represent data better
You can use these methods to transform your datasets to be clean and
easy to work with!
Instructions
1.The students data frame is nearly cleaned and ready for analysis!
There’s one more change that can be made to the ages of the students to
help describe and visualize the data. What could that change be? What is
the ideal data type for the ages column?
Make the change to age and save the resulting data frame to
students.
# update age column
students <- students %>%
mutate(age = as.numeric(age));
students
LS0tDQp0aXRsZTogIkRhdGEgQ2xlYW5pbmcgaW4gUiINCmF1dGhvcjogIkFubmFiZWwgS3VvIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJVktJW0tJWQgJUg6JU0nKWAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgbG9hZCBsaWJyYXJpZXMNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQoNCiMgMS5JbnRyb2R1Y3Rpb24NCg0KQSBodWdlIHBhcnQgb2YgZGF0YSBzY2llbmNlIGludm9sdmVzIGFjcXVpcmluZyByYXcgZGF0YSBhbmQgZ2V0dGluZyBpdCBpbnRvIGEgZm9ybSByZWFkeSBmb3IgYW5hbHlzaXMuIFNvbWUgaGF2ZSBlc3RpbWF0ZWQgdGhhdCBkYXRhIHNjaWVudGlzdHMgc3BlbmQgODAlIG9mIHRoZWlyIHRpbWUgY2xlYW5pbmcgYW5kIG1hbmlwdWxhdGluZyBkYXRhLCBhbmQgb25seSAyMCUgb2YgdGhlaXIgdGltZSBhY3R1YWxseSBhbmFseXppbmcgaXQgb3IgYnVpbGRpbmcgbW9kZWxzIGZyb20gaXQuDQoNCldoZW4gd2UgcmVjZWl2ZSByYXcgZGF0YSwgd2UgaGF2ZSB0byBkbyBhIG51bWJlciBvZiB0aGluZ3MgYmVmb3JlIHdl4oCZcmUgcmVhZHkgdG8gYW5hbHl6ZSBpdCwgcG9zc2libHkgaW5jbHVkaW5nOg0KDQoxLmRpYWdub3NpbmcgdGhlIOKAnHRpZGluZXNz4oCdIG9mIHRoZSBkYXRhIOKAlCBob3cgbXVjaCBkYXRhIGNsZWFuaW5nIHdlIHdpbGwgaGF2ZSB0byBkbw0KDQoyLnJlc2hhcGluZyB0aGUgZGF0YSDigJQgZ2V0dGluZyB0aGUgcmlnaHQgcm93cyBhbmQgY29sdW1ucyBmb3IgZWZmZWN0aXZlIGFuYWx5c2lzDQoNCjMuY29tYmluaW5nIG11bHRpcGxlIGZpbGVzDQoNCjQuY2hhbmdpbmcgdGhlIHR5cGVzIG9mIHZhbHVlcyDigJQgaG93IHdlIGZpeCBhIGNvbHVtbiB3aGVyZSBudW1lcmljYWwgdmFsdWVzIGFyZSBzdG9yZWQgYXMgUHJldmlldzogRG9jcyBMb2FkaW5nIGxpbmsgZGVzY3JpcHRpb24gc3RyaW5ncywgZm9yIGV4YW1wbGUNCg0KNS5kcm9wcGluZyBvciBmaWxsaW5nIG1pc3NpbmcgdmFsdWVzIC0gaG93IHdlIGRlYWwgd2l0aCBkYXRhIHRoYXQgaXMgaW5jb21wbGV0ZSBvciBtaXNzaW5nDQoNCjYubWFuaXB1bGF0aW5nIHN0cmluZ3MgdG8gcmVwcmVzZW50IHRoZSBkYXRhIGJldHRlcg0KDQpXZSB3aWxsIGdvIHRocm91Z2ggdGhlIHRlY2huaXF1ZXMgZGF0YSBzY2llbnRpc3RzIHVzZSB0byBhY2NvbXBsaXNoIHRoZXNlIGdvYWxzIGJ5IGxvb2tpbmcgYXQgc29tZSDigJx1bmNsZWFu4oCdIGRhdGFzZXRzIGFuZCB0cnlpbmcgdG8gZ2V0IHRoZW0gaW50byBhIGdvb2QsIGNsZWFuIHN0YXRlLiBBbG9uZyB0aGUgd2F5IHdlIHdpbGwgdXNlIHRoZSBwb3dlcmZ1bCB0aWR5dmVyc2UgcGFja2FnZXMgZHBseXIgYW5kIHRpZHlyIHRvIGdldCBvdXIgZGF0YSBzcXVlYWt5IGNsZWFuIQ0KDQojIyBJbnN0dWN0aW9ucw0KDQpXZSBoYXZlIHByb3ZpZGVkIGFuIGV4YW1wbGUgb2YgZGF0YSByZXByZXNlbnRpbmcgZXhhbSBzY29yZXMgZnJvbSAxMDAwIHN0dWRlbnRzIGluIGFuIG9ubGluZSBtYXRoIGNsYXNzLg0KDQpUaGVzZSBkYXRhIGZyYW1lcywgd2hpY2ggeW91IGNhbiB2aWV3IGluIHRoZSByZW5kZXJlZCBub3RlYm9vaywgYXJlIGhhcmQgdG8gd29yayB3aXRoLiBUaGV54oCZcmUgc2VwYXJhdGVkIGludG8gbXVsdGlwbGUgdGFibGVzLCBhbmQgdGhlIHZhbHVlcyBkb27igJl0IGxlbmQgdGhlbXNlbHZlcyB3ZWxsIHRvIGFuYWx5c2lzLiBUcnkgdG8gdGhpbmsgYWJvdXQgaG93IHlvdSB3b3VsZCBwbG90IHRoZSBleGFtIHNjb3JlIGF2ZXJhZ2UgYWdhaW5zdCB0aGUgYWdlIG9mIHRoZSBzdHVkZW50cyBpbiB0aGUgY2xhc3MuIFRoaXMgd291bGQgbm90IGJlIGVhc3khDQoNCkluIHRoZSBuZXh0IGV4ZXJjaXNlcywgd2XigJlsbCB0cmFuc2Zvcm0gdGhpcyBkYXRhIHNvIHRoYXQgcGVyZm9ybWluZyBhIHRhc2sgbGlrZSB0aGF0IHZpc3VhbGl6YXRpb24gd291bGQgYmUgc2ltcGxlLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KIyBsb2FkIGRhdGEgZnJhbWUNCnN0dWRlbnRzXzEgPC0gcmVhZF9jc3YoJ2V4YW1zXzAuY3N2JykNCnN0dWRlbnRzXzIgPC0gcmVhZF9jc3YoJ2V4YW1zXzEuY3N2JykNCmBgYA0KDQpgYGB7cn0NCiMgaW5zcGVjdCBkYXRhIGZyYW1lDQpoZWFkKHN0dWRlbnRzXzEpDQpoZWFkKHN0dWRlbnRzXzIpDQpgYGANCg0KIyAyLkRpYWdub3NlIHRoZSBEYXRhDQoNCldlIG9mdGVuIGRlc2NyaWJlIGRhdGEgdGhhdCBpcyBlYXN5IHRvIGFuYWx5emUgYW5kIHZpc3VhbGl6ZSBhcyDigJx0aWR5IGRhdGHigJ0uIFdoYXQgZG9lcyBpdCBtZWFuIHRvIGhhdmUgdGlkeSBkYXRhPw0KDQpGb3IgZGF0YSB0byBiZSB0aWR5LCBpdCBtdXN0IGhhdmU6DQoNCjEuRWFjaCB2YXJpYWJsZSBhcyBhIHNlcGFyYXRlIGNvbHVtbg0KDQoyLkVhY2ggcm93IGFzIGEgc2VwYXJhdGUgb2JzZXJ2YXRpb24NCg0KVGhlIGZpcnN0IHN0ZXAgb2YgZGlhZ25vc2luZyB3aGV0aGVyIG9yIG5vdCBhIGRhdGFzZXQgaXMgdGlkeSBpcyB1c2luZyBiYXNlIFIgYW5kIGRwbHlyIGZ1bmN0aW9ucyB0byBleHBsb3JlIGFuZCBwcm9iZSB0aGUgZGF0YXNldC4NCg0KWW914oCZdmUgc2VlbiBtb3N0IG9mIHRoZSBmdW5jdGlvbnMgd2Ugb2Z0ZW4gdXNlIHRvIGRpYWdub3NlIGEgZGF0YXNldCBmb3IgY2xlYW5pbmcuIFNvbWUgb2YgdGhlIG1vc3QgdXNlZnVsIG9uZXMgYXJlOg0KDQoxLmhlYWQoKSDigJQgZGlzcGxheSB0aGUgZmlyc3QgNiByb3dzIG9mIHRoZSB0YWJsZQ0KDQoyLnN1bW1hcnkoKSDigJQgZGlzcGxheSB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzIG9mIHRoZSB0YWJsZQ0KDQozLmNvbG5hbWVzKCkg4oCUIGRpc3BsYXkgdGhlIGNvbHVtbiBuYW1lcyBvZiB0aGUgdGFibGUNCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCiMgcmVhZCBDU1ZzDQpncm9jZXJ5XzEgPC0gcmVhZF9jc3YoJ2dyb2NlcnlfMS5jc3YnKQ0KZ3JvY2VyeV8yIDwtIHJlYWRfY3N2KCdncm9jZXJ5XzIuY3N2JykNCmBgYA0KDQojIyBJbnN0cnVjdGlvbnMNCg0KMS5Qcm92aWRlZCBpbiBub3RlYm9vay5SbWQgYXJlIHR3byBkYXRhIGZyYW1lcywgZ3JvY2VyeV8xIGFuZCBncm9jZXJ5XzIuDQoNCkJlZ2luIGJ5IHZpZXdpbmcgdGhlIGhlYWQoKSBvZiBib3RoIGdyb2NlcnlfMSBhbmQgZ3JvY2VyeV8yLg0KDQpgYGB7cn0NCiMgaW5zcGVjdCBkYXRhIGZyYW1lcw0KaGVhZChncm9jZXJ5XzEpDQpoZWFkKGdyb2NlcnlfMikNCnN1bW1hcnkoZ3JvY2VyeV8xKQ0Kc3VtbWFyeShncm9jZXJ5XzIpDQpjb2xuYW1lcyhncm9jZXJ5XzEpDQpjb2xuYW1lcyhncm9jZXJ5XzIpDQpgYGANCg0KMi5FeHBsb3JlIHRoZSBkYXRhIGZyYW1lcyB1c2luZyB0aGUgb3RoZXIgZnVuY3Rpb25zIGxpc3RlZC4NCg0KV2hpY2ggZGF0YSBmcmFtZSBpcyDigJxjbGVhbuKAnSwgdGlkeSwgYW5kIHJlYWR5IGZvciBhbmFseXNpcz8gQ3JlYXRlIGEgdmFyaWFibGUgbmFtZWQgY2xlYW5fZGF0YV9mcmFtZSBhbmQgYXNzaWduIGl0IHRoZSB2YWx1ZSAxIGlmIGdyb2NlcnlfMSBpcyBhIGNsZWFuIGFuZCB0aWR5IGRhdGEgZnJhbWUgb3IgMiBpZiBncm9jZXJ5XzIgaXMgYSBjbGVhbiBhbmQgdGlkeSBkYXRhIGZyYW1lLg0KDQpgYGB7cn0NCiMgY2xlYW4gZGF0YSBmcmFtZQ0KY2xlYW5fZGF0YV9mcmFtZSA8LSAyDQpgYGANCg0KIyAzLkRlYWxpbmcgd2l0aCBNdWx0aXBsZSBGaWxlcw0KT2Z0ZW4sIHlvdSBoYXZlIHRoZSBzYW1lIGRhdGEgc2VwYXJhdGVkIG91dCBpbnRvIG11bHRpcGxlIGZpbGVzLg0KDQpMZXTigJlzIHNheSB0aGF0IHlvdSBoYXZlIGEgdG9uIG9mIGZpbGVzIGZvbGxvd2luZyB0aGUgZmlsZW5hbWUgc3RydWN0dXJlOiAnZmlsZV8xLmNzdicsICdmaWxlXzIuY3N2JywgJ2ZpbGVfMy5jc3YnLCBhbmQgc28gb24uIFRoZSBwb3dlciBvZiBkcGx5ciBhbmQgdGlkeXIgaXMgbWFpbmx5IGluIGJlaW5nIGFibGUgdG8gbWFuaXB1bGF0ZSBsYXJnZSBhbW91bnRzIG9mIHN0cnVjdHVyZWQgZGF0YSwgc28geW91IHdhbnQgdG8gYmUgYWJsZSB0byBnZXQgYWxsIG9mIHRoZSByZWxldmFudCBpbmZvcm1hdGlvbiBpbnRvIG9uZSB0YWJsZSBzbyB0aGF0IHlvdSBjYW4gYW5hbHl6ZSB0aGUgYWdncmVnYXRlIGRhdGEuDQoNCllvdSBjYW4gY29tYmluZSB0aGUgYmFzZSBSIGZ1bmN0aW9ucyBsaXN0LmZpbGVzKCkgYW5kIGxhcHBseSgpIHdpdGggcmVhZHIgYW5kIGRwbHlyIHRvIG9yZ2FuaXplIHRoaXMgZGF0YSBiZXR0ZXIsIGFzIHNob3duIGJlbG93Og0KIg0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXR0ZXJuID0gImZpbGVfLipjc3YiKQ0KZGZfbGlzdCA8LSBsYXBwbHkoZmlsZXMscmVhZF9jc3YpDQpkZiA8LSBiaW5kX3Jvd3MoZGZfbGlzdCkNCiINCjEuVGhlIGZpcnN0IGxpbmUgdXNlcyBsaXN0LmZpbGVzKCkgYW5kIGEgcmVndWxhciBleHByZXNzaW9uLCBhIHNlcXVlbmNlIG9mIGNoYXJhY3RlcnMgZGVzY3JpYmluZyBhIHBhdHRlcm4gb2YgdGV4dCB0aGF0IHNob3VsZCBiZSBtYXRjaGVkLCB0byBmaW5kIGFueSBmaWxlIGluIHRoZSBjdXJyZW50IGRpcmVjdG9yeSB0aGF0IHN0YXJ0cyB3aXRoICdmaWxlXycgYW5kIGhhcyBhbiBleHRlbnNpb24gb2YgY3N2LCBzdG9yaW5nIHRoZSBuYW1lIG9mIGVhY2ggZmlsZSBpbiBhIHZlY3RvciBmaWxlcw0KDQoyLlRoZSBzZWNvbmQgbGluZSB1c2VzIGxhcHBseSgpIHRvIHJlYWQgZWFjaCBmaWxlIGluIGZpbGVzIGludG8gYSBkYXRhIGZyYW1lIHdpdGggcmVhZF9jc3YoKSwgc3RvcmluZyB0aGUgDQpkYXRhIGZyYW1lcyBpbiBkZl9saXN0DQoNCjMuVGhlIHRoaXJkIGxpbmUgdGhlbiBjb25jYXRlbmF0ZXMgYWxsIG9mIHRob3NlIGRhdGEgZnJhbWVzIHRvZ2V0aGVyIHdpdGggZHBseXLigJlzIGJpbmRfcm93cygpIGZ1bmN0aW9uDQoNCiMjIEluc3RydWN0aW9ucw0KMS5Zb3UgaGF2ZSAxMCBkaWZmZXJlbnQgZmlsZXMgY29udGFpbmluZyAxMDAgc3R1ZGVudHMgZWFjaC4gVGhlc2UgZmlsZXMgZm9sbG93IHRoZSBuYW1pbmcgc3RydWN0dXJlOg0KDQpleGFtc18wLmNzdg0KZXhhbXNfMS5jc3YNCuKApiB1cCB0byBleGFtc185LmNzdg0KDQpZb3UgYXJlIGdvaW5nIHRvIHJlYWQgZWFjaCBmaWxlIGludG8gYW4gaW5kaXZpZHVhbCBkYXRhIGZyYW1lIGFuZCB0aGVuIGNvbWJpbmUgYWxsIG9mIHRoZSBlbnRyaWVzIGludG8gb25lIGRhdGEgZnJhbWUuDQoNCkZpcnN0LCBjcmVhdGUgYSB2YXJpYWJsZSBjYWxsZWQgc3R1ZGVudF9maWxlcyBhbmQgc2V0IGl0IGVxdWFsIHRvIHRoZSBsaXN0LmZpbGVzKCkgb2YgYWxsIG9mIHRoZSBDU1YgZmlsZXMgd2Ugd2FudCB0byBpbXBvcnQuDQpgYGB7cn0NCiMgbGlzdCBmaWxlcw0Kc3R1ZGVudF9maWxlcyA8LSBsaXN0LmZpbGVzKHBhdHRlcm4gPSAiZXhhbXNfLipjc3YiKQ0Kc3R1ZGVudF9maWxlcw0KYGBgDQoyLlJlYWQgZWFjaCBmaWxlIGluIHN0dWRlbnRfZmlsZXMgaW50byBhIGRhdGEgZnJhbWUgdXNpbmcgbGFwcGx5KCkgYW5kIHNhdmUgdGhlIHJlc3VsdCB0byBkZl9saXN0Lg0KYGBge3J9DQojIHJlYWQgZmlsZXMNCmRmX2xpc3QgPC0gbGFwcGx5KHN0dWRlbnRfZmlsZXMsIHJlYWRfY3N2KQ0KZGZfbGlzdA0KYGBgDQozLkNvbmNhdGVuYXRlIGFsbCBvZiB0aGUgZGF0YSBmcmFtZXMgaW4gZGZfbGlzdCBpbnRvIG9uZSBkYXRhIGZyYW1lIGNhbGxlZCBzdHVkZW50cy4NCmBgYHtyfQ0KIyBjb25jYXRlbmF0ZSBkYXRhIGZyYW1lcw0Kc3R1ZGVudHMgPC0gYmluZF9yb3dzKGRmX2xpc3QpDQpzdHVkZW50cw0KYGBgDQo0Lkluc3BlY3Qgc3R1ZGVudHMuIFNhdmUgdGhlIG51bWJlciBvZiByb3dzIGluIHN0dWRlbnRzIHRvIG5yb3dfc3R1ZGVudHMuIERpZCB5b3UgZ2V0IGFsbCBvZiB0aGVtDQpgYGB7cn0NCiMgbnVtYmVyIG9mIHJvd3MgaW4gc3R1ZGVudHMNCm5yb3dfc3R1ZGVudHMgPC0gbnJvdyhzdHVkZW50cykNCnByaW50KG5yb3dfc3R1ZGVudHMpDQpgYGANCiMgNC5SZXNoYXBpbmcgeW91ciBEYXRhDQpTaW5jZSB3ZSB3YW50DQoNCjEuRWFjaCB2YXJpYWJsZSBhcyBhIHNlcGFyYXRlIGNvbHVtbg0KDQoyLkVhY2ggcm93IGFzIGEgc2VwYXJhdGUgb2JzZXJ2YXRpb24NCg0KV2Ugd291bGQgd2FudCB0byByZXNoYXBlIGEgdGFibGUgbGlrZToNCg0KQWNjb3VudAkgICAgQ2hlY2tpbmcJU2F2aW5ncw0KDQrigJwxMjQ1NjU0M+KAnQkgODUwMAkgICAgICAgODkwMA0KDQrigJwxMjI4Mzk0MuKAnQkgNjQxMAkgICAgICAgODAyMA0KDQrigJwxMjgzOTQ4NeKAnQk3ODAwMAkgICAgICA5MjAwMA0KDQpJbnRvIGEgdGFibGUgdGhhdCBsb29rcyBtb3JlIGxpa2U6DQoNCkFjY291bnQJICAgIEFjY291bnQgVHlwZQkgIEFtb3VudA0KDQrigJwxMjQ1NjU0M+KAnQnigJxDaGVja2luZ+KAnQkgICAgICA4NTAwDQoNCuKAnDEyNDU2NTQz4oCdCeKAnFNhdmluZ3PigJ0JICAgICAgICA4OTAwDQoNCuKAnDEyMjgzOTQy4oCdCeKAnENoZWNraW5n4oCdCSAgICAgIDY0MTANCg0K4oCcMTIyODM5NDLigJ0J4oCcU2F2aW5nc+KAnQkgICAgICAgIDgwMjANCg0K4oCcMTI4Mzk0ODXigJ0J4oCcQ2hlY2tpbmfigJ0JICAgICA3ODAwMA0KDQrigJwxMjgzOTQ4NeKAnQnigJxTYXZpbmdz4oCdCSAgICAgIDkyMDAwMA0KDQoNCldlIGNhbiB1c2UgdGlkeXLigJlzIGdhdGhlcigpIGZ1bmN0aW9uIHRvIGRvIHRoaXMgdHJhbnNmb3JtYXRpb24uIGdhdGhlcigpIHRha2VzIGEgZGF0YSBmcmFtZSBhbmQgdGhlIGNvbHVtbnMgdG8gdW5wYWNrOg0KYGBge3J9DQpsaWJyYXJ5KHRpZHlyKQ0KYGBgDQoNCiINCmRmICU+JQ0KICBnYXRoZXIoJ0NoZWNraW5nJywnU2F2aW5ncycsa2V5PSdBY2NvdW50IFR5cGUnLHZhbHVlPSdBbW91bnQnKQ0KIg0KVGhlIGFyZ3VtZW50cyB5b3UgcHJvdmlkZSBhcmU6DQoNCjEuZGY6IHRoZSBkYXRhIGZyYW1lIHlvdSB3YW50IHRvIGdhdGhlciwgd2hpY2ggY2FuIGJlIHBpcGVkIGludG8gZ2F0aGVyKCkNCg0KMi5DaGVja2luZyBhbmQgU2F2aW5nczogdGhlIGNvbHVtbnMgb2YgdGhlIG9sZCBkYXRhIGZyYW1lIHRoYXQgeW91IHdhbnQgdG8gdHVybiBpbnRvIHZhcmlhYmxlcw0KDQozLmtleTogd2hhdCB0byBjYWxsIHRoZSBjb2x1bW4gb2YgdGhlIG5ldyBkYXRhIGZyYW1lIHRoYXQgc3RvcmVzIHRoZSB2YXJpYWJsZXMNCg0KNC52YWx1ZTogd2hhdCB0byBjYWxsIHRoZSBjb2x1bW4gb2YgdGhlIG5ldyBkYXRhIGZyYW1lIHRoYXQgc3RvcmVzIHRoZSB2YWx1ZXMNCg0KIyMgSW5zdHJ1Y3Rpb25zDQoxLlRoZSBzdHVkZW50cyBkYXRhIGZyYW1lIGZyb20gdGhlIHByZXZpb3VzIGV4ZXJjaXNlIGhhcyBiZWVuIGxvYWRlZCBpbnRvIHRoZSBub3RlYm9vayBmb3IgeW91LiBTYXZlIHRoZSBjb2x1bW5zIG5hbWVzIHRvIG9yaWdpbmFsX2NvbF9uYW1lcyBhbmQgcHJpbnQgaXQuDQpgYGB7cn0NCiMgb3JpZ2luYWwgY29sdW1uIG5hbWVzDQpvcmlnaW5hbF9jb2xfbmFtZXMgPC0gY29sbmFtZXMoc3R1ZGVudHMpDQpvcmlnaW5hbF9jb2xfbmFtZXMNCmBgYA0KMi5UaGVyZSBpcyBhIGNvbHVtbiBmb3IgdGhlIHNjb3JlcyBvbiB0aGUgZnJhY3Rpb25zIGV4YW0sIGFuZCBhIGNvbHVtbiBmb3IgdGhlIHNjb3JlcyBvbiB0aGUgcHJvYmFiaWxpdHkgZXhhbS4NCg0KV2Ugd2FudCB0byBtYWtlIGVhY2ggcm93IGFuIG9ic2VydmF0aW9uLCBzbyB3ZSB3YW50IHRvIHRyYW5zZm9ybSB0aGlzIHRhYmxlIHRvIGxvb2sgbGlrZToNCg0KZnVsbF9uYW1lCSAgICAgICAgZXhhbQkgICAgc2NvcmUJIGdlbmRlcl9hZ2UJZ3JhZGUNCg0K4oCcRmlyc3QgU3R1ZGVudOKAnQnigJxmcmFjdGlvbnPigJ0Jc2NvcmUlCSAgICDigKYJICAgICAg4oCmDQoNCuKAnEZpcnN0IFN0dWRlbnTigJ0J4oCccHJvYmFiaWxpdHnigJ0Jc2NvcmUlICAJ4oCmCSAgICAgIOKApg0KDQrigJxTZWNvbmQgU3R1ZGVudOKAnQnigJxmcmFjdGlvbnPigJ0Jc2NvcmUlCSAg4oCmCSAgICAgIOKApg0KDQrigJxTZWNvbmQgU3R1ZGVudOKAnQnigJxwcm9iYWJpbGl0eeKAnQlzY29yZSUJ4oCmCSAgICAgIOKApg0KDQoNClVzZSBnYXRoZXIgdG8gY3JlYXRlIGEgbmV3IHRhYmxlIChzdGlsbCBjYWxsZWQgc3R1ZGVudHMpIHRoYXQgZm9sbG93cyB0aGlzIHN0cnVjdHVyZS4gVGhlbiB2aWV3IHRoZSBoZWFkKCkgb2Ygc3R1ZGVudHMuDQoNCmBgYHtyfQ0KIyBnYXRoZXIgY29sdW1ucw0Kc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lDQogIGdhdGhlcignZnJhY3Rpb25zJywgJ3Byb2JhYmlsaXR5Jywga2V5ID0gJ2V4YW0nLCB2YWx1ZSA9ICdzY29yZScpDQpoZWFkKHN0dWRlbnRzKQ0KYGBgDQozLlNhdmUgdGhlIGNvbHVtbnMgbmFtZXMgb2YgdGhlIHVwZGF0ZWQgc3R1ZGVudHMgZGF0YSBmcmFtZSB0byBnYXRoZXJlZF9jb2xfbmFtZXMgYW5kIHByaW50IGl0Lg0KYGBge3J9DQojIHVwZGF0ZWQgY29sdW1uIG5hbWVzDQpnYXRoZXJlZF9jb2xfbmFtZXMgPC0gY29sbmFtZXMoc3R1ZGVudHMpDQpnYXRoZXJlZF9jb2xfbmFtZXMNCmBgYA0KNC5UaGUgZHBseXIgZnVuY3Rpb24gY291bnQoKSB0YWtlcyBhIGRhdGEgZnJhbWUgYW5kIGEgY29sdW1uIGFzIGFyZ3VtZW50cyBhbmQgcmV0dXJucyBhIHRhYmxlIHdpdGggY291bnRzIG9mIHRoZSB1bmlxdWUgdmFsdWVzIGluIHRoZSBuYW1lZCBjb2x1bW4uDQoNCkZpbmQgdGhlIGNvdW50IG9mIGVhY2ggdW5pcXVlIHZhbHVlIGluIHRoZSBleGFtIGNvbHVtbi4gU2F2ZSB0aGUgcmVzdWx0IHRvIGV4YW1fY291bnRzIGFuZCB2aWV3IGV4YW1fY291bnRzLg0KYGBge3J9DQojIHVuaXF1ZSB2YWx1ZSBjb3VudHMgb2YgZXhhbQ0KZXhhbV9jb3VudHMgPC0gc3R1ZGVudHMgJT4lDQogIGNvdW50KGV4YW0pDQpleGFtX2NvdW50cw0KYGBgDQojIDUuRGVhbGluZyB3aXRoIER1cGxpY2F0ZXMNCk9mdGVuIHdlIHNlZSBkdXBsaWNhdGVkIHJvd3Mgb2YgZGF0YSBpbiB0aGUgZGF0YSBmcmFtZXMgd2UgYXJlIHdvcmtpbmcgd2l0aC4gVGhpcyBjb3VsZCBoYXBwZW4gZHVlIHRvIGVycm9ycyBpbiBkYXRhIGNvbGxlY3Rpb24gb3IgaW4gc2F2aW5nIGFuZCBsb2FkaW5nIHRoZSBkYXRhLg0KDQpUbyBjaGVjayBmb3IgZHVwbGljYXRlcywgd2UgY2FuIHVzZSB0aGUgYmFzZSBSIGZ1bmN0aW9uIGR1cGxpY2F0ZWQoKSwgd2hpY2ggd2lsbCByZXR1cm4gYSBsb2dpY2FsIHZlY3RvciB0ZWxsaW5nIHVzIHdoaWNoIHJvd3MgYXJlIGR1cGxpY2F0ZSByb3dzLg0KDQpMZXTigJlzIHNheSB3ZSBoYXZlIGEgZGF0YSBmcmFtZSBmcnVpdHMgdGhhdCByZXByZXNlbnRzIHRoaXMgdGFibGU6DQoNCml0ZW0JICAgICAgICAgcHJpY2UJY2Fsb3JpZXMNCg0K4oCcYmFuYW5h4oCdCSAgICAgIOKAnCQx4oCdCSAgICAgMTA1DQoNCuKAnGFwcGxl4oCdCSAgICAg4oCcJDAuNzXigJ0JICAgICAgOTUNCg0K4oCcYXBwbGXigJ0JICAgICDigJwkMC43NeKAnQkgICAgICA5NQ0KDQrigJxwZWFjaOKAnQkgICAgICAgIOKAnCQz4oCdCSAgICAgIDU1DQoNCuKAnHBlYWNo4oCdCSAgICAgICAg4oCcJDTigJ0JICAgICAgNTUNCg0K4oCcY2xlbWVudGluZeKAnQnigJwkMi414oCdCSAgICAgIDM1DQoNCklmIHdlIGNhbGwgZnJ1aXRzICU+JSBkdXBsaWNhdGVkKCksIHdlIHdvdWxkIGdldCB0aGUgZm9sbG93aW5nIHZlY3RvcjoNCg0KIj4+IFsxXSBGQUxTRSBGQUxTRSBUUlVFIEZBTFNFIEZBTFNFIEZBTFNFIg0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlIHRoaXJkIHJvdywgd2hpY2ggcmVwcmVzZW50cyBhbiAiYXBwbGUiIHdpdGggcHJpY2UgIiQwLjc1IiBhbmQgOTUgY2Fsb3JpZXMsIGlzIGEgZHVwbGljYXRlIHJvdy4gRXZlcnkgdmFsdWUgaW4gdGhpcyByb3cgaXMgdGhlIHNhbWUgYXMgaW4gYW5vdGhlciByb3cgKHRoZSBwcmV2aW91cyByb3cpLg0KDQpXZSBjYW4gdXNlIHRoZSBkcGx5ciBkaXN0aW5jdCgpIGZ1bmN0aW9uIHRvIHJlbW92ZSBhbGwgcm93cyBvZiBhIGRhdGEgZnJhbWUgdGhhdCBhcmUgZHVwbGljYXRlcyBvZiBhbm90aGVyIHJvdy4NCg0KSWYgd2UgY2FsbCBmcnVpdHMgJT4lIGRpc3RpbmN0KCksIHdlIHdvdWxkIGdldCB0aGUgdGFibGU6DQoNCml0ZW0JICAgICAgICAgcHJpY2UJIGNhbG9yaWVzDQoNCuKAnGJhbmFuYeKAnQkgICAgICDigJwkMeKAnQkgICAgICAxMDUNCg0K4oCcYXBwbGXigJ0JICAgICDigJwkMC43NeKAnQkgICAgICAgOTUNCg0K4oCccGVhY2jigJ0JICAgICAgICDigJwkM+KAnQkgICAgICAgNTUNCg0K4oCccGVhY2jigJ0JICAgICAgICDigJwkNOKAnQkgICAgICAgNTUNCg0K4oCcY2xlbWVudGluZeKAnQnigJwkMi414oCdCSAgICAgICAzNQ0KDQpUaGUgImFwcGxlIiByb3cgd2FzIGRlbGV0ZWQgYmVjYXVzZSBpdCB3YXMgZXhhY3RseSB0aGUgc2FtZSBhcyBhbm90aGVyIHJvdy4gQnV0IHRoZSB0d28gInBlYWNoIiByb3dzIHJlbWFpbiBiZWNhdXNlIHRoZXJlIGlzIGEgZGlmZmVyZW5jZSBpbiB0aGUgcHJpY2UgY29sdW1uLg0KDQpJZiB3ZSB3YW50ZWQgdG8gcmVtb3ZlIGV2ZXJ5IHJvdyB3aXRoIGEgZHVwbGljYXRlIHZhbHVlIGluIHRoZSBpdGVtIGNvbHVtbiwgd2UgY291bGQgc3BlY2lmeSBhIHN1YnNldDoNCg0KImZydWl0cyAlPiUNCiAgZGlzdGluY3QoaXRlbSwua2VlcF9hbGw9VFJVRSkiDQoNCkJ5IGRlZmF1bHQsIHRoaXMga2VlcHMgdGhlIGZpcnN0IG9jY3VycmVuY2Ugb2YgdGhlIGR1cGxpY2F0ZToNCg0KaXRlbQkgICAgICAgcHJpY2UJICAgY2Fsb3JpZXMNCg0K4oCcYmFuYW5h4oCdCSAgIOKAnCQx4oCdCSAgICAgMTA1DQoNCuKAnGFwcGxl4oCdCSAgICAg4oCcJDAuNzXigJ0JIDk1DQoNCuKAnHBlYWNo4oCdCSAgICAg4oCcJDPigJ0JICAgICA1NQ0KDQrigJxjbGVtZW50aW5l4oCdIOKAnCQyLjXigJ0JICAgMzUNCg0KTWFrZSBzdXJlIHRoYXQgdGhlIGNvbHVtbnMgeW91IGRyb3AgZHVwbGljYXRlcyBmcm9tIGFyZSBzcGVjaWZpY2FsbHkgdGhlIG9uZXMgd2hlcmUgZHVwbGljYXRlcyBkb27igJl0IGJlbG9uZy4gWW91IHdvdWxkbuKAmXQgd2FudCB0byBkcm9wIGR1cGxpY2F0ZXMgd2l0aCB0aGUgcHJpY2UgY29sdW1uIGFzIGEgc3Vic2V0LCBmb3IgZXhhbXBsZSwgYmVjYXVzZSBpdOKAmXMgb2theSBpZiBtdWx0aXBsZSBpdGVtcyBjb3N0IHRoZSBzYW1lIGFtb3VudCENCg0KIyMgSW5zdHJ1Y3Rpb25zDQoxLlRoZSBzdHVkZW50cyBkYXRhIGZyYW1lIGhhcyBhIGNvbHVtbiBpZCB0aGF0IGlzIG5laXRoZXIgdW5pcXVlIG5vciByZXF1aXJlZCBmb3Igb3VyIGFuYWx5c2lzLiBEcm9wIHRoZSBpZCBjb2x1bW4gZnJvbSB0aGUgZGF0YSBmcmFtZSBhbmQgc2F2ZSB0aGUgcmVzdWx0IHRvIHN0dWRlbnRzLiBWaWV3IHRoZSBoZWFkKCkgb2Ygc3R1ZGVudHMuDQpgYGB7cn0NCiMgZHJvcCBpZCBjb2x1bW4NCnN0dWRlbnRzIDwtIHN0dWRlbnRzICU+JQ0KICBzZWxlY3QoLWlkKQ0KaGVhZChzdHVkZW50cykNCmBgYA0KMi4NCkl0IHNlZW1zIGxpa2UgaW4gdGhlIGRhdGEgY29sbGVjdGlvbiBwcm9jZXNzLCBzb21lIHJvd3MgbWF5IGhhdmUgYmVlbiByZWNvcmRlZCB0d2ljZS4gVXNlIHRoZSBkdXBsaWNhdGVkKCkgZnVuY3Rpb24gb24gdGhlIHN0dWRlbnRzIGRhdGEgZnJhbWUgdG8gbWFrZSBhIHZlY3RvciBvYmplY3QgY2FsbGVkIGR1cGxpY2F0ZXMuDQpgYGB7cn0NCiMgZmluZCBkdXBsaWNhdGVkIHJvd3MNCmR1cGxpY2F0ZXMgPC0gc3R1ZGVudHMgJT4lDQogIGR1cGxpY2F0ZWQoKQ0KaGVhZChkdXBsaWNhdGVzKQ0KYGBgDQozLnRhYmxlKCkgaXMgYSBiYXNlIFIgZnVuY3Rpb24gdGhhdCB0YWtlcyBhbnkgUiBvYmplY3QgYXMgYW4gYXJndW1lbnQgYW5kIHJldHVybnMgYSB0YWJsZSB3aXRoIHRoZSBjb3VudHMgb2YgZWFjaCB1bmlxdWUgdmFsdWUgaW4gdGhlIG9iamVjdC4NCg0KUGlwZSB0aGUgcmVzdWx0IGZyb20gdGhlIHByZXZpb3VzIGNoZWNrcG9pbnQgaW50byB0YWJsZSgpIHRvIHNlZSBob3cgbWFueSByb3dzIGFyZSBleGFjdCBkdXBsaWNhdGVzLiBNYWtlIHN1cmUgdG8gc2F2ZSB0aGUgcmVzdWx0IHRvIGR1cGxpY2F0ZV9jb3VudHMsIGFuZCB2aWV3IGR1cGxpY2F0ZV9jb3VudHMuDQpgYGB7cn0NCiMgY291bnQgZHVwbGljYXRlZCByb3dzDQpkdXBsaWNhdGVfY291bnRzIDwtIGR1cGxpY2F0ZXMgJT4lDQogIHRhYmxlKCkNCmR1cGxpY2F0ZV9jb3VudHMNCmBgYA0KDQo0LkdldCByaWQgb2YgdGhlIGR1cGxpY2F0ZSByb3dzIGluIHRoZSBzdHVkZW50cyBkYXRhIGZyYW1lIGFuZCBzYXZlIHRoaXMgbmV3IGRhdGEgZnJhbWUgYXMgdW5pcXVlX3N0dWRlbnRzLg0KYGBge3J9DQojIHJlbW92ZSBkdXBsaWNhdGVkIHJvd3MsIGtlZXAgb25seSB1bmlxdWUgcm93cw0KdW5pcXVlX3N0dWRlbnRzIDwtIHN0dWRlbnRzICU+JQ0KICBkaXN0aW5jdCgpDQp1bmlxdWVfc3R1ZGVudHMNCmBgYA0KNS5Vc2UgdGhlIGR1cGxpY2F0ZWQoKSBmdW5jdGlvbiBhZ2FpbiB0byBtYWtlIGFuIG9iamVjdCBjYWxsZWQgdXBkYXRlZF9kdXBsaWNhdGVzIGFmdGVyIGRyb3BwaW5nIHRoZSBkdXBsaWNhdGVzLiBQaXBlIHRoZSByZXN1bHQgaW50byB0YWJsZSgpIHRvIHNlZSBpZiBhbnkgZHVwbGljYXRlcyByZW1haW4sIGFuZCB2aWV3IHVwZGF0ZWRfZHVwbGljYXRlcy4gQXJlIHRoZXJlIGFueSBUUlVFcyBsZWZ0Pw0KYGBge3J9DQojIGZpbmQgYW5kIGNvdW50IGR1cGxpY2F0ZWQgcm93cyBpbiB1cGRhdGVkIGRhdGEgZnJhbWUNCnVwZGF0ZWRfZHVwbGljYXRlcyA8LSB1bmlxdWVfc3R1ZGVudHMgJT4lDQogIGR1cGxpY2F0ZWQoKSAlPiUNCiAgdGFibGUoKQ0KdXBkYXRlZF9kdXBsaWNhdGVzDQpgYGANCiMgNi5TcGxpdHRpbmcgQnkgSW5kZXgNCkluIHRyeWluZyB0byBnZXQgY2xlYW4gZGF0YSwgd2Ugd2FudCB0byBtYWtlIHN1cmUgZWFjaCBjb2x1bW4gcmVwcmVzZW50cyBvbmUgdHlwZSBvZiBtZWFzdXJlbWVudC4gT2Z0ZW4sIG11bHRpcGxlIG1lYXN1cmVtZW50cyBhcmUgcmVjb3JkZWQgaW4gdGhlIHNhbWUgY29sdW1uLCBhbmQgd2Ugd2FudCB0byBzZXBhcmF0ZSB0aGVzZSBvdXQgc28gdGhhdCB3ZSBjYW4gZG8gaW5kaXZpZHVhbCBhbmFseXNpcyBvbiBlYWNoIHZhcmlhYmxlLg0KDQpMZXTigJlzIHNheSB3ZSBoYXZlIGEgY29sdW1uIOKAnGJpcnRoZGF54oCdIHdpdGggZGF0YSBmb3JtYXR0ZWQgaW4gTU1ERFlZWVkgZm9ybWF0LiBJbiBvdGhlciB3b3Jkcywg4oCcMTEwMTE5OTPigJ0gcmVwcmVzZW50cyBhIGJpcnRoZGF5IG9mIE5vdmVtYmVyIDEsIDE5OTMuIFdlIHdhbnQgdG8gc3BsaXQgdGhpcyBkYXRhIGludG8gZGF5LCBtb250aCwgYW5kIHllYXIgc28gdGhhdCB3ZSBjYW4gdXNlIHRoZXNlIGNvbHVtbnMgYXMgc2VwYXJhdGUgZmVhdHVyZXMuDQoNCkluIHRoaXMgY2FzZSwgd2Uga25vdyB0aGUgZXhhY3Qgc3RydWN0dXJlIG9mIHRoZXNlIHN0cmluZ3MuIFRoZSBmaXJzdCB0d28gY2hhcmFjdGVycyB3aWxsIGFsd2F5cyBjb3JyZXNwb25kIHRvIHRoZSBtb250aCwgdGhlIHNlY29uZCB0d28gdG8gdGhlIGRheSwgYW5kIHRoZSByZXN0IG9mIHRoZSBzdHJpbmcgd2lsbCBhbHdheXMgY29ycmVzcG9uZCB0byB5ZWFyLiBXZSBjYW4gZWFzaWx5IGJyZWFrIHRoZSBkYXRhIGludG8gdGhyZWUgc2VwYXJhdGUgY29sdW1ucyBieSBzcGxpdHRpbmcgdGhlIHN0cmluZ3MgaW50byBzdWJzdHJpbmdzIHVzaW5nIHN0cl9zdWIoKSwgYSBoZWxwZnVsIGZ1bmN0aW9uIGZyb20gdGhlIHN0cmluZ3IgcGFja2FnZToNCg0KLy8gQ3JlYXRlIHRoZSAnbW9udGgnIGNvbHVtbg0KZGYgJT4lDQogIG11dGF0ZShtb250aCA9IHN0cl9zdWIoYmlydGhkYXksMSwyKSkNCg0KLy8gQ3JlYXRlIHRoZSAnZGF5JyBjb2x1bW4NCmRmICU+JQ0KICBtdXRhdGUoZGF5ID0gc3RyX3N1YihiaXJ0aGRheSwzLDQpKQ0KDQovLyBDcmVhdGUgdGhlICd5ZWFyJyBjb2x1bW4NCmRmICU+JQ0KICBtdXRhdGUoeWVhciA9IHN0cl9zdWIoYmlydGhkYXksNSkpDQogIA0KMS5UaGUgZmlyc3QgY29tbWFuZCB0YWtlcyB0aGUgY2hhcmFjdGVycyBzdGFydGluZyBhdCBpbmRleCAxIGFuZCBlbmRpbmcgYXQgaW5kZXggMiBvZiBlYWNoIHZhbHVlIGluIHRoZSBiaXJ0aGRheSBjb2x1bW4gYW5kIHB1dHMgaXQgaW50byBhIG1vbnRoIGNvbHVtbi4NCg0KMi5UaGUgc2Vjb25kIGNvbW1hbmQgdGFrZXMgdGhlIGNoYXJhY3RlcnMgc3RhcnRpbmcgYXQgaW5kZXggMyBhbmQgZW5kaW5nIGF0IGluZGV4IDQgb2YgZWFjaCB2YWx1ZSBpbiB0aGUgYmlydGhkYXkgY29sdW1uIGFuZCBwdXRzIGl0IGludG8gYSBkYXkgY29sdW1uLg0KDQozLlRoZSB0aGlyZCBjb21tYW5kIHRha2VzIHRoZSBjaGFyYWN0ZXJzIHN0YXJ0aW5nIGF0IGluZGV4IDUgYW5kIGVuZGluZyBhdCB0aGUgZW5kIG9mIHRoZSB2YWx1ZSBpbiB0aGUgYmlydGhkYXkgY29sdW1uIGFuZCBwdXRzIGl0IGludG8gYSB5ZWFyIGNvbHVtbi4NCg0KVGhpcyB3b3VsZCB0cmFuc2Zvcm0gYSB0YWJsZSBsaWtlOg0KDQppZAkgIGJpcnRoZGF5DQoNCjEwMTEJ4oCcMTIyNDE5ODnigJ0NCg0KMTExMgnigJwxMDMxMTk2NuKAnQ0KDQoxMTEzCeKAnDAxMDUyMDEx4oCdDQoNCmludG8gYSB0YWJsZSBsaWtlOg0KDQppZAkgIGJpcnRoZGF5CSAgbW9udGgJZGF5CSAgeWVhcg0KDQoxMDExCeKAnDEyMjQxOTg54oCdCeKAnDEy4oCdCeKAnDI04oCdCeKAnDE5ODnigJ0NCg0KMTExMgnigJwxMDMxMTk2NuKAnQnigJwxMOKAnQnigJwzMeKAnQnigJwxOTY24oCdDQoNCjExMTMJ4oCcMDEwNTIwMTHigJ0J4oCcMDHigJ0J4oCcMDXigJ0J4oCcMjAxMeKAnQ0KDQpXZSB3aWxsIHByYWN0aWNlIGNoYW5naW5nIHN0cmluZyBjb2x1bW5zIGludG8gbnVtZXJpY2FsIGNvbHVtbnMgKGxpa2UgY29udmVydGluZyAiMTAiIHRvIDEwKSBpbiBhIGZ1dHVyZSBleGVyY2lzZS4NCg0KIyMgSW5zdHJ1Y3Rpb25zDQoxLlByaW50IG91dCB0aGUgY29sdW1ucyBvZiB0aGUgc3R1ZGVudHMgZGF0YSBmcmFtZS4NCmBgYHtyfQ0KIyBwcmludCBjb2x1bW5zIG9mIHN0dWRlbnRzDQpwcmludChjb2xuYW1lcyhzdHVkZW50cykpDQpgYGANCjIuVGhlIGNvbHVtbiBnZW5kZXJfYWdlIHNvdW5kcyBsaWtlIGl0IGNvbnRhaW5zIGJvdGggZ2VuZGVyIGFuZCBhZ2UhDQoNClZpZXcgdGhlIGhlYWQoKSBvZiBzdHVkZW50cyB0byBzZWUgd2hhdCBraW5kIG9mIGRhdGEgZ2VuZGVyX2FnZSBjb250YWlucy4NCmBgYHtyfQ0KIyB2aWV3IGhlYWQgb2Ygc3R1ZGVudHMNCmhlYWQoc3R1ZGVudHMpDQpgYGANCjMuSXQgbG9va3MgbGlrZSB0aGUgZmlyc3QgY2hhcmFjdGVyIG9mIHRoZSB2YWx1ZXMgaW4gZ2VuZGVyX2FnZSBjb250YWlucyB0aGUgZ2VuZGVyLCB3aGlsZSB0aGUgcmVzdCBvZiB0aGUgc3RyaW5nIGNvbnRhaW5zIHRoZSBhZ2UuIExldOKAmXMgc2VwYXJhdGUgb3V0IHRoZSBnZW5kZXIgZGF0YSBpbnRvIGEgbmV3IGNvbHVtbiBjYWxsZWQgZ2VuZGVyLiBTYXZlIHRoZSByZXN1bHQgdG8gc3R1ZGVudHMsIGFuZCB2aWV3IHRoZSBoZWFkKCkuDQpgYGB7cn0NCiMgYWRkIGdlbmRlciBhbmQgYWdlIGNvbHVtbnMNCmxpYnJhcnkoc3RyaW5ncikNCnN0dWRlbnRzIDwtIHN0dWRlbnRzICU+JQ0KICBtdXRhdGUoZ2VuZGVyID0gc3RyX3N1YihnZW5kZXJfYWdlLCAxLCAxKSkNCmhlYWQoc3R1ZGVudHMpDQpgYGANCjQuTm93LCBzZXBhcmF0ZSBvdXQgdGhlIGFnZSBkYXRhIGludG8gYSBuZXcgY29sdW1uIGNhbGxlZCBhZ2UuIFNhdmUgdGhlIHVwZGF0ZWQgZGF0YSBmcmFtZSB0byBzdHVkZW50cywgYW5kIHZpZXcgdGhlIGhlYWQoKS4NCmBgYHtyfQ0KIyBhZGQgZ2VuZGVyIGFuZCBhZ2UgY29sdW1ucw0Kc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lDQogIG11dGF0ZShhZ2UgPSBzdHJfc3ViKGdlbmRlcl9hZ2UsIDIpKQ0KaGVhZChzdHVkZW50cykNCmBgYA0KNS5Ob3csIHdlIGRvbuKAmXQgbmVlZCB0aGF0IGdlbmRlcl9hZ2UgY29sdW1uIGFueW1vcmUuIERyb3AgZ2VuZGVyX2FnZSBmcm9tIHN0dWRlbnRzLCBhbmQgc2F2ZSB0aGUgcmVzdWx0IHRvIHN0dWRlbnRzLiBWaWV3IHRoZSBoZWFkKCkgb2Ygc3R1ZGVudHMuDQpgYGB7cn0NCiMgZHJvcCBnZW5kZXJfYWdlIGNvbHVtbg0Kc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lDQogIHNlbGVjdCgtZ2VuZGVyX2FnZSkNCmhlYWQoc3R1ZGVudHMpDQpgYGANCiMgNy5TcGxpdHRpbmcgQnkgQ2hhcmFjdGVyDQpMZXTigJlzIHNheSB3ZSBoYXZlIGEgY29sdW1uIGNhbGxlZCAidHlwZSIgd2l0aCBkYXRhIGVudHJpZXMgaW4gdGhlIGZvcm1hdCAiYWRtaW5fVVMiIG9yICJ1c2VyX0tlbnlhIiwgYXMgc2hvd24gaW4gdGhlIHRhYmxlIGJlbG93Lg0KDQppZAkgIHR5cGUNCg0KMTAxMQnigJx1c2VyX0tlbnlh4oCdDQoNCjExMTIJ4oCcYWRtaW5fVVPigJ0NCg0KMTExMwnigJxtb2RlcmF0b3JfVUvigJ0NCg0KSnVzdCBsaWtlIHdlIHNhdyBiZWZvcmUsIHRoaXMgY29sdW1uIGFjdHVhbGx5IGNvbnRhaW5zIHR3byB0eXBlcyBvZiBkYXRhLiBPbmUgc2VlbXMgdG8gYmUgdGhlIHVzZXIgdHlwZSAod2l0aCB2YWx1ZXMgbGlrZSDigJxhZG1pbuKAnSBvciDigJx1c2Vy4oCdKSBhbmQgb25lIHNlZW1zIHRvIGJlIHRoZSBjb3VudHJ5IHRoaXMgdXNlciBpcyBpbiAod2l0aCB2YWx1ZXMgbGlrZSDigJxVU+KAnSBvciDigJxLZW55YeKAnSkuDQoNCldlIGNhbiBubyBsb25nZXIganVzdCBzcGxpdCBhbG9uZyB0aGUgZmlyc3QgNCBjaGFyYWN0ZXJzIGJlY2F1c2UgYWRtaW4gYW5kIHVzZXIgYXJlIG9mIGRpZmZlcmVudCBsZW5ndGhzLiBJbnN0ZWFkLCB3ZSBrbm93IHRoYXQgd2Ugd2FudCB0byBzcGxpdCBhbG9uZyB0aGUgIl8iLiBXZSBjYW4gdGh1cyB1c2UgdGhlIHRpZHlyIGZ1bmN0aW9uIHNlcGFyYXRlKCkgdG8gc3BsaXQgdGhpcyBjb2x1bW4gaW50byB0d28sIHNlcGFyYXRlIGNvbHVtbnM6DQoiDQpDcmVhdGUgdGhlICd1c2VyX3R5cGUnIGFuZCAnY291bnRyeScgY29sdW1ucw0KLy9kZiAlPiUNCi8vICBzZXBhcmF0ZSh0eXBlLGMoJ3VzZXJfdHlwZScsJ2NvdW50cnknKSwnXycpDQoiDQoNCjEudHlwZSBpcyB0aGUgY29sdW1uIHRvIHNwbGl0DQoNCjIuYygndXNlcl90eXBlJywnY291bnRyeScpIGlzIGEgdmVjdG9yIHdpdGggdGhlIG5hbWVzIG9mIHRoZSB0d28gbmV3IGNvbHVtbnMNCg0KMy4nXycgaXMgdGhlIGNoYXJhY3RlciB0byBzcGxpdCBvbg0KDQpUaGlzIHdvdWxkIHRyYW5zZm9ybSB0aGUgdGFibGUgYWJvdmUgaW50byBhIHRhYmxlIGxpa2U6DQoNCmlkCSAgdXNlcl90eXBlCSAgIGNvdW50cnkNCg0KMTAxMQnigJx1c2Vy4oCdCSAgICAg4oCcS2VueWHigJ0NCg0KMTExMgnigJxhZG1pbuKAnQkgICAgIOKAnFVT4oCdDQoNCjExMTMJ4oCcbW9kZXJhdG9y4oCdCSDigJxVS+KAnQ0KDQojIyBJbnN0cnVjdGlvbnMNCjEuVmlldyB0aGUgaGVhZCgpIG9mIHN0dWRlbnRzLiBOb3RpY2UgdGhhdCB0aGUgc3R1ZGVudHPigJkgbmFtZXMgYXJlIHN0b3JlZCBpbiBhIGNvbHVtbiBjYWxsZWQgZnVsbF9uYW1lLg0KYGBge3J9DQojIHZpZXcgdGhlIGhlYWQgb2Ygc3R1ZGVudHMNCmhlYWQoc3R1ZGVudHMpDQpgYGANCjIuU2VwYXJhdGUgdGhlIGZ1bGxfbmFtZSBjb2x1bW4gaW50byB0d28gbmV3IGNvbHVtbnMsIGZpcnN0X25hbWUgYW5kIGxhc3RfbmFtZSwgYnkgc3BsaXR0aW5nIG9uIHRoZSAnICcgY2hhcmFjdGVyIC4NCg0KUHJvdmlkZSBhcyBhbiBleHRyYSBhcmd1bWVudCB0byB0aGUgc2VwYXJhdGUoKSBmdW5jdGlvbiBleHRyYSA9J21lcmdlJy4gVGhpcyB3aWxsIGVuc3VyZSB0aGF0IG1pZGRsZSBuYW1lcyBvciB0d28td29yZCBsYXN0IG5hbWVzIHdpbGwgYWxsIGVuZCB1cCBpbiB0aGUgbGFzdF9uYW1lIGNvbHVtbi4NCg0KU2F2ZSB0aGUgcmVzdWx0IHRvIHN0dWRlbnRzLCBhbmQgdmlldyB0aGUgaGVhZCgpLg0KYGBge3J9DQojIHNlcGFyYXRlIHRoZSBmdWxsX25hbWUgY29sdW1uDQpzdHVkZW50cyA8LSBzdHVkZW50cyAlPiUNCiAgc2VwYXJhdGUoJ2Z1bGxfbmFtZScsIGMoJ2ZpcnN0X25hbWUnLCAnbGFzdF9uYW1lJyksICcgJywgZXh0cmEgPSdtZXJnZScpDQpoZWFkKHN0dWRlbnRzKQ0KYGBgDQojIDguTG9va2luZyBhdCBEYXRhIFR5cGVzDQpFYWNoIGNvbHVtbiBvZiBhIGRhdGEgZnJhbWUgY2FuIGhvbGQgaXRlbXMgb2YgdGhlIHNhbWUgZGF0YSB0eXBlLiBUaGUgZGF0YSB0eXBlcyB0aGF0IFIgdXNlcyBhcmU6IGNoYXJhY3RlciwgbnVtZXJpYyAocmVhbCBvciBkZWNpbWFsKSwgaW50ZWdlciwgbG9naWNhbCwgb3IgY29tcGxleC4gT2Z0ZW4sIHdlIHdhbnQgdG8gY29udmVydCBiZXR3ZWVuIHR5cGVzIHNvIHRoYXQgd2UgY2FuIGRvIGJldHRlciBhbmFseXNpcy4gSWYgYSBudW1lcmljYWwgY2F0ZWdvcnkgbGlrZSAibnVtX3VzZXJzIiBpcyBzdG9yZWQgYXMgYSB2ZWN0b3Igb2YgY2hhcmFjdGVycyBpbnN0ZWFkIG9mIG51bWVyaWNzLCBmb3IgZXhhbXBsZSwgaXQgbWFrZXMgaXQgbW9yZSBkaWZmaWN1bHQgdG8gZG8gc29tZXRoaW5nIGxpa2UgbWFrZSBhIGxpbmUgZ3JhcGggb2YgdXNlcnMgb3ZlciB0aW1lLg0KDQpUbyBzZWUgdGhlIHR5cGVzIG9mIGVhY2ggY29sdW1uIG9mIGEgZGF0YSBmcmFtZSwgd2UgY2FuIHVzZToNCg0Kc3RyKGRmKQ0KDQpzdHIoKSBkaXNwbGF5cyB0aGUgaW50ZXJuYWwgc3RydWN0dXJlIG9mIGFuIFIgb2JqZWN0LiBDYWxsaW5nIHN0cigpIHdpdGggYSBkYXRhIGZyYW1lIGFzIGFuIGFyZ3VtZW50IHdpbGwgcmV0dXJuIGEgdmFyaWV0eSBvZiBpbmZvcm1hdGlvbiwgaW5jbHVkaW5nIHRoZSBkYXRhIHR5cGVzLiBGb3IgYSBkYXRhIGZyYW1lIGxpa2UgdGhpczoNCg0KaXRlbQkgICAgICAgICBwcmljZQkgICBjYWxvcmllcw0KDQrigJxiYW5hbmHigJ0JICAgICDigJwkMeKAnQkgICAgIDEwNQ0KDQrigJxhcHBsZeKAnQkgICAgICAg4oCcJDAuNzXigJ0JIDk1DQoNCuKAnHBlYWNo4oCdCSAgICAgICDigJwkM+KAnQkgICAgIDU1DQoNCuKAnGNsZW1lbnRpbmXigJ0JIOKAnCQyLjXigJ0JICAgMzUNCg0KdGhlIGRhdGEgdHlwZXMgd291bGQgYmU6DQoNCi8vICAjPiAkIGl0ZW06ICAgICAgICBjaHINCg0KLy8gICM+ICQgcHJpY2U6ICAgICAgIGNocg0KDQovLyAgIz4gJCBjYWxvcmllczogICAgbnVtDQoNCldlIGNhbiBzZWUgdGhhdCB0aGUgcHJpY2UgY29sdW1uIGlzIG1hZGUgdXAgb2YgY2hhcmFjdGVycywgd2hpY2ggd2lsbCBwcm9iYWJseSBtYWtlIG91ciBhbmFseXNpcyBvZiBwcmljZSBtb3JlIGRpZmZpY3VsdC4gV2XigJlsbCBsb29rIGF0IGhvdyB0byBjb252ZXJ0IGNvbHVtbnMgaW50byBudW1lcmljIHZhbHVlcyBpbiB0aGUgbmV4dCBmZXcgZXhlcmNpc2VzLg0KDQojIyBJbnN0cnVjdGlvbnMNCjEuTGV04oCZcyBpbnNwZWN0IHRoZSBkYXRhIHR5cGVzIGluIHRoZSBzdHVkZW50cyB0YWJsZS4NCg0KUHJpbnQgb3V0IHRoZSBzdHJ1Y3R1cmUgb2Ygc3R1ZGVudHMuDQpgYGB7cn0NCiMgcHJpbnQgc3RydWN0dXJlIG9mIHN0dWRlbnRzDQpzdHIoc3R1ZGVudHMpDQpgYGANCjIuSWYgd2Ugd2FudGVkIHRvIG1ha2UgYSBzY2F0dGVycGxvdCBvZiBhZ2UgdnMgYXZlcmFnZSBleGFtIHNjb3JlLCB3b3VsZCB3ZSBiZSBhYmxlIHRvIGRvIGl0IHdpdGggdGhpcyB0eXBlIG9mIGRhdGE/DQoNClBhc3RlIHRoZSBmb2xsb3dpbmcgY29kZSBpbiB0aGUgbGFzdCBjb2RlIGJsb2NrIHRvIHRyeSBhbmQgcHJpbnQgb3V0IHRoZSBtZWFuIG9mIHRoZSBzY29yZSBjb2x1bW4gb2Ygc3R1ZGVudHMuDQoNCiIgc3R1ZGVudHMgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9zY29yZSA9IG1lYW4oc2NvcmUpKSAiDQoNCldoYXQgd2FybmluZyBkbyB5b3Ugc2VlPw0KYGBge3J9DQojIG1lYW4gb2YgYWdlIGNvbHVtbg0Kc3R1ZGVudHMgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9zY29yZSA9IG1lYW4oc2NvcmUpKQ0KYGBgDQoNCiMgOS5TdHJpbmcgUGFyc2luZw0KU29tZXRpbWVzIHdlIG5lZWQgdG8gbW9kaWZ5IHN0cmluZ3MgaW4gb3VyIGRhdGEgZnJhbWVzIHRvIGhlbHAgdXMgdHJhbnNmb3JtIHRoZW0gaW50byBtb3JlIG1lYW5pbmdmdWwgbWV0cmljcy4gRm9yIGV4YW1wbGUsIGluIG91ciBmcnVpdHMgdGFibGUgZnJvbSBiZWZvcmU6DQoNCml0ZW0JICAgICAgICAgcHJpY2UJICBjYWxvcmllcw0KDQrigJxiYW5hbmHigJ0JICAgICDigJwkMeKAnQkgICAgMTA1DQoNCuKAnGFwcGxl4oCdCSAgICAgICDigJwkMC43NeKAnQk5NQ0KDQrigJxwZWFjaOKAnQkgICAgICAg4oCcJDPigJ0JICAgIDU1DQoNCuKAnHBlYWNo4oCdCSAgICAgICDigJwkNOKAnQkgICAgNTUNCg0K4oCcY2xlbWVudGluZeKAnQkg4oCcJDIuNeKAnQkgIDM1DQoNCldlIGNhbiBzZWUgdGhhdCB0aGUgJ3ByaWNlJyBjb2x1bW4gaXMgYWN0dWFsbHkgY29tcG9zZWQgb2YgY2hhcmFjdGVyIHN0cmluZ3MgcmVwcmVzZW50aW5nIGRvbGxhciBhbW91bnRzLiBUaGlzIGNvbHVtbiBjb3VsZCBiZSBtdWNoIGJldHRlciByZXByZXNlbnRlZCBhcyBudW1lcmljLCBzbyB0aGF0IHdlIGNvdWxkIHRha2UgdGhlIG1lYW4sIGNhbGN1bGF0ZSBvdGhlciBhZ2dyZWdhdGUgc3RhdGlzdGljcywgb3IgY29tcGFyZSBkaWZmZXJlbnQgZnJ1aXRzIHRvIG9uZSBhbm90aGVyIGluIHRlcm1zIG9mIHByaWNlLg0KDQpGaXJzdCwgd2UgY2FuIHVzZSBhIHJlZ3VsYXIgZXhwcmVzc2lvbiwgYSBzZXF1ZW5jZSBvZiBjaGFyYWN0ZXJzIHRoYXQgZGVzY3JpYmUgYSBwYXR0ZXJuIG9mIHRleHQgdG8gYmUgbWF0Y2hlZCwgdG8gcmVtb3ZlIGFsbCBvZiB0aGUgZG9sbGFyIHNpZ25zLiBUaGUgYmFzZSBSIGZ1bmN0aW9uIGdzdWIoKSB3aWxsIHJlbW92ZSB0aGUgJCBmcm9tIHRoZSBwcmljZSBjb2x1bW4sIHJlcGxhY2luZyB0aGUgc3ltYm9sIHdpdGggYW4gZW1wdHkgc3RyaW5nICcnOg0KDQoiZnJ1aXQgJT4lDQogIG11dGF0ZShwcmljZT1nc3ViKCdcXCQnLCcnLHByaWNlKSkiDQoNClRoZW4sIHdlIGNhbiB1c2UgdGhlIGJhc2UgUiBmdW5jdGlvbiBhcy5udW1lcmljKCkgdG8gY29udmVydCBjaGFyYWN0ZXIgc3RyaW5ncyBjb250YWluaW5nIG51bWVyaWNhbCB2YWx1ZXMgdG8gbnVtZXJpYzoNCg0KImZydWl0ICU+JQ0KICBtdXRhdGUocHJpY2UgPSBhcy5udW1lcmljKHByaWNlKSkiDQoNCk5vdywgd2UgaGF2ZSBhIGRhdGEgZnJhbWUgdGhhdCBsb29rcyBsaWtlOg0KaXRlbQkgICAgICAgIHByaWNlCWNhbG9yaWVzDQoNCuKAnGJhbmFuYeKAnQkgICAgMQkgICAgMTA1DQoNCuKAnGFwcGxl4oCdCSAgICAgIDAuNzUJOTUNCg0K4oCccGVhY2jigJ0JICAgICAgMwkgICAgNTUNCg0K4oCccGVhY2jigJ0JICAgICAgNAkgICAgNTUNCg0K4oCcY2xlbWVudGluZeKAnQkyLjUJICAzNQ0KDQoNCiMjIEluc3RydWN0aW9ucw0KMS5XZSBzYXcgaW4gdGhlIGxhc3QgZXhlcmNpc2UgdGhhdCBmaW5kaW5nIHRoZSBtZWFuIG9mIHRoZSBzY29yZSBjb2x1bW4gaXMgaGFyZCB0byBkbyB3aGVuIHRoZSBkYXRhIGlzIHN0b3JlZCBhcyBjaGFyYWN0ZXJzIGFuZCBub3QgbnVtYmVycy4NCg0KVmlldyB0aGUgaGVhZCgpIG9mIHN0dWRlbnRzIHRvIHRha2UgYSBsb29rIGF0IHRoZSB2YWx1ZXMgaW4gdGhlIHNjb3JlIGNvbHVtbi4NCmBgYHtyfQ0KIyB2aWV3IGhlYWQgb2Ygc3R1ZGVudHMNCmhlYWQoc3R1ZGVudHMpDQpgYGANCjIuUmVtb3ZlIHRoZSAnJScgc3ltYm9sIGZyb20gdGhlIHNjb3JlIGNvbHVtbiwgYW5kIHNhdmUgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIHRvIHN0dWRlbnRzLiBWaWV3IHN0dWRlbnRzLg0KYGBge3J9DQojIHJlbW92ZSAlIGZyb20gc2NvcmUgY29sdW1uDQpzdHVkZW50cyA8LSBzdHVkZW50cyAlPiUNCiAgbXV0YXRlKHNjb3JlID0gZ3N1YignJScsICcnLHNjb3JlKSkNCnN0dWRlbnRzDQpgYGANCjMuQ29udmVydCB0aGUgc2NvcmUgY29sdW1uIHRvIGEgbnVtZXJpY2FsIHR5cGUgdXNpbmcgdGhlIGFzLm51bWVyaWMoKSBmdW5jdGlvbi4gU2F2ZSB0aGlzIG5ldyBkYXRhIGZyYW1lIHRvIHN0dWRlbnRzLCBhbmQgdmlldyANCmBgYHtyfQ0KIyBjaGFuZ2Ugc2NvcmUgY29sdW1uIHRvIG51bWVyaWMNCnN0dWRlbnRzIDwtIHN0dWRlbnRzICU+JQ0KICBtdXRhdGUoc2NvcmUgPSBhcy5udW1lcmljKHNjb3JlKSkNCnN0dWRlbnRzDQpgYGANCiMgMTAuUmV2aWV3DQpHcmVhdCEgV2UgaGF2ZSBsb29rZWQgYXQgYSBudW1iZXIgb2YgZGlmZmVyZW50IG1ldGhvZHMgd2UgbWF5IHVzZSB0byBnZXQgZGF0YSBpbnRvIHRoZSBmb3JtYXQgd2Ugd2FudCBmb3IgYW5hbHlzaXMuDQoNClNwZWNpZmljYWxseSwgd2UgaGF2ZSBjb3ZlcmVkOg0KDQoxLmRpYWdub3NpbmcgdGhlIOKAnHRpZGluZXNz4oCdIG9mIGRhdGENCg0KMi5jb21iaW5pbmcgbXVsdGlwbGUgZmlsZXMNCg0KMy5yZXNoYXBpbmcgZGF0YQ0KDQo0LmNoYW5naW5nIHRoZSB0eXBlcyBvZiB2YWx1ZXMNCg0KNS5tYW5pcHVsYXRpbmcgc3RyaW5ncyB0byByZXByZXNlbnQgZGF0YSBiZXR0ZXINCg0KWW91IGNhbiB1c2UgdGhlc2UgbWV0aG9kcyB0byB0cmFuc2Zvcm0geW91ciBkYXRhc2V0cyB0byBiZSBjbGVhbiBhbmQgZWFzeSB0byB3b3JrIHdpdGghDQoNCiMjIEluc3RydWN0aW9ucw0KMS5UaGUgc3R1ZGVudHMgZGF0YSBmcmFtZSBpcyBuZWFybHkgY2xlYW5lZCBhbmQgcmVhZHkgZm9yIGFuYWx5c2lzISBUaGVyZeKAmXMgb25lIG1vcmUgY2hhbmdlIHRoYXQgY2FuIGJlIG1hZGUgdG8gdGhlIGFnZXMgb2YgdGhlIHN0dWRlbnRzIHRvIGhlbHAgZGVzY3JpYmUgYW5kIHZpc3VhbGl6ZSB0aGUgZGF0YS4gV2hhdCBjb3VsZCB0aGF0IGNoYW5nZSBiZT8gV2hhdCBpcyB0aGUgaWRlYWwgZGF0YSB0eXBlIGZvciB0aGUgYWdlcyBjb2x1bW4/DQoNCk1ha2UgdGhlIGNoYW5nZSB0byBhZ2UgYW5kIHNhdmUgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIHRvIHN0dWRlbnRzLg0KYGBge3J9DQojIHVwZGF0ZSBhZ2UgY29sdW1uDQpzdHVkZW50cyA8LSBzdHVkZW50cyAlPiUNCiAgbXV0YXRlKGFnZSA9IGFzLm51bWVyaWMoYWdlKSk7DQpzdHVkZW50cw0KYGBgDQo=