Packages Used

library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)

Reading in and Cleaning “Flatfiles”

I think when most new R users (and most R users in general) think of datasets, they think in terms of a spreadsheet format. This conceptual layout is often referred to as a flatfile, as it is a two dimensional layout without hierarchical relationships to other data, as is the case in a relational database. We will only work with this spreadsheet-style data in this workshop.

When a lot of people think ‘spreadsheet’, they think a .xlsx file. The .xlsx format works very well for some types of data like budgets or class grades, but it really is not suitable for larger datasets. How attractive does opening an .xlsx file with several millions of rows and a hundred columns sound? And some of Excel’s ‘smart’ features can have very stupid consequences. (Check out this nightmare.)

In R, it is more common to use plain text files. These could have the extensions .csv or just .txt. Either way they are just text. Copy and paste the text below into a plain text editor (You can do this in RStudio, too), and save it in your project/working directory as “MTcars.txt”.

"mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear", "carb"
"Mazda RX4", 21, 6, 160, 110, 3.9, 2.62, 16.46, 0, 1, 4, 4
"Mazda RX4 Wag", 21, 6, 160, 110, 3.9, 2.875, 17.02, 0, 1, 4, 4
"Datsun 710", 22.8, 4, 108, 93, 3.85, 2.32, 18.61, 1, 1, 4, 1
"Hornet 4 Drive", 21.4, 6, 258, 110, 3.08, 3.215, 19.44, 1, 0, 3, 1
"Hornet Sportabout", 18.7, 8, 360, 175, 3.15, 3.44, 17.02, 0, 0, 3, 2
"Valiant", 18.1, 6, 225, 105, 2.76, 3.46, 20.22, 1, 0, 3, 1
"Duster 360", 14.3, 8, 360, 245, 3.21, 3.57, 15.84, 0, 0, 3, 4
"Merc 240D", 24.4, 4, 146.7, 62, 3.69, 3.19, 20, 1, 0, 4, 2
"Merc 230", 22.8, 4, 140.8, 95, 3.92, 3.15, 22.9, 1, 0, 4, 2
"Merc 280", 19.2, 6, 167.6, 123, 3.92, 3.44, 18.3, 1, 0, 4, 4
"Merc 280C", 17.8, 6, 167.6, 123, 3.92, 3.44, 18.9, 1, 0, 4, 4
"Merc 450SE", 16.4, 8, 275.8, 180, 3.07, 4.07, 17.4, 0, 0, 3, 3
"Merc 450SL", 17.3, 8, 275.8, 180, 3.07, 3.73, 17.6, 0, 0, 3, 3
"Merc 450SLC", 15.2, 8, 275.8, 180, 3.07, 3.78, 18, 0, 0, 3, 3
"Cadillac Fleetwood", 10.4, 8, 472, 205, 2.93, 5.25, 17.98, 0, 0, 3, 4
"Lincoln Continental", 10.4, 8, 460, 215, 3, 5.424, 17.82, 0, 0, 3, 4
"Chrysler Imperial", 14.7, 8, 440, 230, 3.23, 5.345, 17.42, 0, 0, 3, 4
"Fiat 128", 32.4, 4, 78.7, 66, 4.08, 2.2, 19.47, 1, 1, 4, 1
"Honda Civic", 30.4, 4, 75.7, 52, 4.93, 1.615, 18.52, 1, 1, 4, 2
"Toyota Corolla", 33.9, 4, 71.1, 65, 4.22, 1.835, 19.9, 1, 1, 4, 1
"Toyota Corona", 21.5, 4, 120.1, 97, 3.7, 2.465, 20.01, 1, 0, 3, 1
"Dodge Challenger", 15.5, 8, 318, 150, 2.76, 3.52, 16.87, 0, 0, 3, 2
"AMC Javelin", 15.2, 8, 304, 150, 3.15, 3.435, 17.3, 0, 0, 3, 2
"Camaro Z28", 13.3, 8, 350, 245, 3.73, 3.84, 15.41, 0, 0, 3, 4
"Pontiac Firebird", 19.2, 8, 400, 175, 3.08, 3.845, 17.05, 0, 0, 3, 2
"Fiat X1-9", 27.3, 4, 79, 66, 4.08, 1.935, 18.9, 1, 1, 4, 1
"Porsche 914-2", 26, 4, 120.3, 91, 4.43, 2.14, 16.7, 0, 1, 5, 2
"Lotus Europa", 30.4, 4, 95.1, 113, 3.77, 1.513, 16.9, 1, 1, 5, 2
"Ford Pantera L", 15.8, 8, 351, 264, 4.22, 3.17, 14.5, 0, 1, 5, 4
"Ferrari Dino", 19.7, 6, 145, 175, 3.62, 2.77, 15.5, 0, 1, 5, 6
"Maserati Bora", 15, 8, 301, 335, 3.54, 3.57, 14.6, 0, 1, 5, 8
"Volvo 142E", 21.4, 4, 121, 109, 4.11, 2.78, 18.6, 1, 1, 4, 2

Now run this code.

MT <- read.table("MTcars.txt", sep = ",", header = TRUE) 
# The 'sep =' could be anything, but whitespace (" "), semicolon, and Tab ("\t") are also common.
head(MT)

Because we knew that our values were comma-separated, we could have just used read.csv() which is just the read.table() function with the separator pre-set. (If you did not know previously, or have not guessed yet, the .csv extension means “Comma-Separated Values”.)

MT2 <- read.csv("MTcars.txt") # We could change the .txt to .csv, but it's unnecessary.
identical(MT, MT2)

We can see that read.csv() is just a wrapper function for read.table() by entering read.csv (note the lack of parantheses).

read.csv

read.table() has many more options/arguments as you can see from the output above. We can find out what these arguments are for any function by entering ?<function_name>.

?read.table

Most of these arguments have defaults and/or are optional so they do not need to be explicitly entered. See that the defaults for strip.white and blank.lines.skip are FALSE and TRUE, respectively.

Note also that at the top of the Help page it reads ‘read.table {utils}’. This means that the function read.table() is part of the ‘utils’ package. There are several packages that are part of every R install. The most important of which are ‘base’, ‘utils’, ‘stats’, and ‘graphics’. Additional packages can be installed from CRAN using the command install.packages("<package_name>"), or clicking the Packages tab in RStudio. Packages still need to be loaded to be used, however. This is done using the library(<package_name>) (quotes optional).

The reason we are talking about packages here is that RStudio likes to use the ‘readr’ package to read in flatfiles. Let’s use the ‘Import Dataset’ button on the Environment pane. Choose ‘From CSV’ and then ‘Browse…’. Select and open the ‘MTcars.txt’ file.

RStudio gives us a preview of what is going to be read in. The readr package is an excellent package… but here, we have a problem. Where read.csv() worked perfectly, read_csv() is not going to. What are some possible solutions?

library(readr)
MTcars <- read_csv("<filepath>/MTcars.txt")
head(MTcars)
str(MTcars)

Running str() on both MTcars and MT (or MT2), we can see there are some differences (besides our row names work around). What are they?

We had problems because of row names. Row names were a bad feature. They should be an additional data column instead. Packages like readr and readxl are more consistent and often much faster than some of the base functionality in R. That said, I use both ‘read.csv()’ and ‘read_csv()’ to read in data. Loading and using different packages is a necessary thing to do.

Selecting and Subsetting Data

So we have a dataset MTcars. This dataset is tiny. We could probably get a reasonable grasp of the values that are contain within simply by eyeballing them. Our ability to do this rapidly diminishes as the dataset grows, however. To look at specific values we need to be able to find them in our dataset. The most basic way is to select a row and column numerically. Let’s look at the model column of MTcars.

MTcars[1,1] # This selects the first row and first column of MTcars
MTcars[3,1] # The third value from the first column.
MTcars[1:3,1] # The first three.
MTcars[c(3, 6, 10, 20:23, 28),1] # A specific selection
MTcars[seq(from = 1, to = nrow(MTcars), by = 2),1] # Can we tell what this did?

We can do the same with columns.

MTcars[1,2] # The MPG value for the Mazda RX4

Does this return a single value? What happens when we str() it. Hold this thought.

We can get all the values in the mpg column just by leaving rows blank. (This works for rows as well, of course).

head(MTcars[,2])

We have another way of selecting values, and this way will just return the value. We use the dollar sign $ which we can think of as meaning ‘select’.

head(MTcars$mpg)

Notice the difference in the output. This is a vector of values, not a dataframe (or tibble).

MTcars$mpg[1] # This gives us the first value in the MPG column.
str(MTcars$mpg[1])

With this knowledge we can get all sorts of info about our columns.

mean(MTcars$hp, na.rm = TRUE) # The extra argument here removes NAs from the data. We don't have any, but I want you to see it.
sd(MTcars$mpg) # Standard Deviation
min(MTcars$qsec) # Seconds for a quarter mile. The MT stands for 'Motor Trend' magazine, after all.
range(MTcars$qsec) 
plot(MTcars$hp, MTcars$qsec) # Yep, more horsepower correlates with faster times
# cor(MTcars$hp, MTcars$qsec) # Gives a negative correlation of -0.7082234

What if we only wanted to compare the tested cars that had manual transmissions. The column am means ‘automatic/manual’, with ‘automatic’ assigned the value zero and manual to the value 1. We could make a separate dataset. Here we have some options.

Subset with a Logical Vector

MTcars_man1 <- MTcars[MTcars$am == 1, ] # Note that we use a double "="
head(MTcars_man1)

Let’s think about what this did. MTcars$am == 1 creates a logical vector of TRUE and FALSE. For all of the TRUE values, a row is kept in the new dataframe.

Subset with the Function subset()

MTcars_man2 <- subset(MTcars, am == 1)
identical(MTcars_man1, MTcars_man2)

Two methods; same result.

Subset with filter() A third method is to use the dplr package’s filter().

library(dplyr)
MTcars_man3 <- filter(MTcars, am == 1)
identical(MTcars_man1, MTcars_man3)

Really?!

table(MTcars_man1 == MTcars_man3)

If we run str() on MTcars_man3 we see that there is some additional metadata in the dataframe. However, if we use the table() function, we can see that all the individual values in each cell are the same. So, the dataframes are the same.

Now we can make a plot with just the manual transmission cars.

plot(MTcars_man1$hp, MTcars_man1$qsec)
# cor(MTcars_man1$hp, MTcars_man1$qsec) # An even stronger correlation here: -0.8494566

We do not need to make entirely new dataframes to get simple statistics. For example, we can get the mean of hp simply by subsetting within the function call.

mean(MTcars$hp[MTcars$am == 1], na.rm = T)
# mean(MTcars$hp[MTcars$am == 1], na.rm = T) == mean(MTcars_man1$hp, na.rm = T) # [1] TRUE

We can even put in a number of logical expressions. Can you figure out what happens in these function calls?

mean(MTcars$hp[MTcars$am == 1 & MTcars$cyl > 4], na.rm = T)
median(MTcars$hp[MTcars$am == 0 & MTcars$mpg <= 16], na.rm = T)
range(MTcars$hp[MTcars$disp >= median(MTcars$disp) | MTcars$cyl >= 8], na.rm = T) # I'm using two criteria to select my 'big' engine cars.

Some of this code becomes very difficult even to read let alone to construct oneself. We never need to do things all in one line. If ran range(MTcars$hp[MTcars$disp >= median(MTcars$disp) | MTcars$cyl >= 8], na.rm = T) over multiple lines starting from the most embedded function call, how could we break this down more simply?

I’ll start us out.

aa <- median(MTcars$disp)
bb <- 
cc <- 
dd <- 
ee <- 
range(ee, na.rm = T) # Needs to output: [1] 105 335

Tidy Data

Let’s read in some YEG Open Data (that I’ve messed with), and take a look at it.

LeisureAtt <- read.csv("LeisureAtt.csv") # Note we didn't use readr's read_csv()
head(LeisureAtt)

This is the complete data set. What do you think of the layout? In some ways this data is fine (in fact it could even be better than the format we are about to learn). However, it violates the principles of ‘tidy data’. Think about how this data should look to plot it most easily. If we have an x and a y axis to plot what would they be, and can we match a single variable to x and another to y? If we wanted to perform a single operation, like rounding to the nearest hundred, to all the important values (the attendance at each observation) how could the data be laid out to make this (and everything else we do to our data) easier?

The principles of tidy data were described by Hadley Wickham and you can learn more about them here. The main principle of tidy data is that each variable is column and each observation is a row. Does the data above follow this principle? Even though the data above is easy to look at, and would probably how you would format the data in a presentation, it is not tidy.

names(LeisureAtt) <- gsub("X", "", names(LeisureAtt)) # First let's get rid of that pesky "X"
library(tidyr) # A package with tidying functions like gather(), and its reverse, spread()
LeisureAtt <- gather(LeisureAtt, YEAR, ATTENDANCE, 2:7)
head(LeisureAtt, 3)

Did this do what we want? Any problems? Let’s take a look at a simple plot. (We’ll cover plotting later).

library(ggplot2) # function ggplot()
ggplot(LeisureAtt, aes(x = MONTH, y = ATTENDANCE)) + geom_bar(stat = "identity") + theme(axis.text.x  = element_text(angle=60, hjust = 1, vjust=1, size=8)) + facet_wrap(~ YEAR, ncol = 3) 

Of course, we can fix this by changing the type of factor we have for MONTH.

str(LeisureAtt$MONTH) # If we had used read_csv() this would be a character vector, but we wouldn't have had the "X" on the year names.
levels(LeisureAtt$MONTH)
LeisureAtt$MONTH <- factor(LeisureAtt$MONTH, levels = toupper(month.name)) # Note about the toupper(month.name)
levels(LeisureAtt$MONTH)
head(LeisureAtt)

Still looks the same, but if we plot it again, we see that we get what we want.

ggplot(LeisureAtt, aes(x = MONTH, y = ATTENDANCE)) + geom_bar(stat = "identity") + theme(axis.text.x  = element_text(angle=60, hjust = 1, vjust=1, size=8)) + facet_wrap(~ YEAR, ncol = 3) 

Because our data was tidy(ish), plotting is much simpler. The data for x and y are consolidated into separate columns. This might be harder for us to look at, but when we start dealing with larger datasets, ‘looking’ is not going to benefit us rather we will want to use plots or functions like range(), summary(), mean(), summary(), etc, to get a sense of what our dataset contains. These operations are much easier to run, and good for us conceptually, when variables are represented in single columns.

Reading in Files from the Web

Edmonton has a great open data portal, which you can find here: https://data.edmonton.ca/. Let’s take a look at what the Leisure Centre Attendance dataset looked like when I found it.

LeisureAttend <- read.csv(url("https://dashboard.edmonton.ca/api/views/iaa7-x8kk/rows.csv"))
head(LeisureAttend, 5)[,c(2:5,7)] # I'm omitting some columns for aesthetic reasons

This dataset, unlike the one I created mostly conforms with the principles of tidy data, but there is something else odd here. What is it and what can we do about it? Without thinking of functions, what are some way we could modify this dataframe?

str(LeisureAttend)
range(LeisureAttend$MONTHLY_ATTENDANCE)

LeisureAttend <- filter(LeisureAttend, MONTHLY_ATTENDANCE != 0)
ggplot(LeisureAttend, aes(x = REPORT_PERIOD, y = MONTHLY_ATTENDANCE)) + geom_bar(stat = "identity") + theme(axis.text.x  = element_text(angle=60, hjust = 1, vjust=1, size=8))

We will do more with this data when we get to plotting.

Renaming Values and Dealing with Factors

What we are about to do is, in this case, completely unnecessary, but is useful to know. In the MTcars dataset some of the values are not very intuitive. Transmission type is denoted by a numeric value where it might be easier to read if those values were abbreviations like ‘auto’ and ‘man’. (I always forget what vs means, - I think it is the cylinder arrangement inline cylinders or not.) Let’s change the numeric values in MTcars$am to strings ‘auto’ and ‘man’. And let’s do this a slightly safer way.

xx <- MTcars$am
xx[xx == 0] <- "auto"
xx[MTcars$am == 1] <- "man" # This is a bit odd to do, but I did it to illustrate a point. Please ask, why?
MTcars$amChar <- xx # What did this do?
rm(xx)
identical((MTcars$am == 0), (MTcars$amChar == "auto"))
table((MTcars$am == 0) == (MTcars$amChar == "auto"))

What if we wanted to create factor variable for engine size called eng_size, where engines with a displacement one standard deviation above than the mean are classed “big”, below “small”, and within “avr” for ‘average’? We could do this with a ‘for’ loop and some ‘if/else’ statements like below.

xx <- character()
for (i in 1:nrow(MTcars)) {
    if (MTcars$disp[i] > (mean(MTcars$disp) + sd(MTcars$disp))) {
        xx[i] <- "big"
    } else if (MTcars$disp[i] < (mean(MTcars$disp) - sd(MTcars$disp))) {
        xx[i] <- "small"
    } else {
        xx[i] <- "avr"
    }
}
MTcars$eng_size <- factor(xx, levels = c("small", "avr", "big"), ordered = TRUE)
str(MTcars$eng_size)

It is good to know about how to write ‘for’, ‘while’ and ‘if’ statements when you learn about programming in general, but I want to teach you how to use R. The way to use R properly is to avoid using these whenever possible. An experienced R programmer would know there is a function that does this for you (one that I should have, but did not know about for the first two years I used R). It is called ifelse().

# ifelse("Is this TRUE?", "Yes, so = ", "No, so = ")
xx <- ifelse(MTcars$disp > (mean(MTcars$disp) + sd(MTcars$disp)), "big", ifelse(MTcars$disp < (mean(MTcars$disp) - sd(MTcars$disp)), "small", "avr"))
xx <- factor(xx, levels = c("small", "avr", "big"), ordered = TRUE)
identical(xx, MTcars$eng_size)

Not only is ifelse() easier to write and read (i.e., elegant), it is also much, much, much, much faster than using a ‘for’ loop. R is not a fast programming language by any means. While ifelse() runs within R, many functions like those in the dplyr package actually run outside of R, in this case in C++. Finding a specific function in another R package can sometimes turn an hour of computational time into seconds, literally. (You can wrap a function call in system.time() to compare computation speeds).

When we changed the zeros to “auto”, the vector xx was changed into a character vector. If we try to do this with factors we will have trouble.

xx <- MTcars$eng_size
xx[xx == "big"] <- "large"

The solution here is to use a line of code like this:

xx <- MTcars$eng_size
levels(xx)[levels(xx) == "big"] <- "large"

Of course, we could also just turn xx into a character vector and modify it that way too.

Factors also have another annoying habit. They will not go away even after they are removed. Let’s pretend we only want the summer months from ’LeisureAttend`.

LeisAttSummer <- filter(LeisureAttend, MONTH %in% c("JULY", "AUGUST", "SEPTEMBER"))
str(LeisAttSummer$MONTH)

We still have a factor with 12 levels. The solution is to use droplevels(). We could have just added this onto the tail of the original filter() call.

LeisAttSummer <- droplevels(filter(LeisureAttend, MONTH %in% c("JULY", "AUGUST", "SEPTEMBER")))
str(LeisAttSummer$MONTH)

The Pipe: %>%

The pipe was an innovation originally from the magrittr package, but is now frequently used in many packages especially dplyr and tidyr. Code can be conceptually hard to read as it typically becomes more and more embedded, meaning that the first thing your code is doing is usually in the centre within the innermost “()”. With the pipe operator, you can mostly think of your code to mean “Take these data, and then (%>%), and then (%>%)…”. How would we re-write the code above without the pipe?

LeisAttSummer <- 

Next: Plotting Your Data

LS0tCnRpdGxlOiAnREMgMjogUmVhZGluZyBJbiBhbmQgQ2xlYW5pbmcgRGF0YScKYXV0aG9yOiAiQnJpYW4gUnVzayIKZGF0ZTogIk5vdmVtYmVyIDE1dGggJiAxNnRoLCAyMDE3IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCiMjIyMgUGFja2FnZXMgVXNlZAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeShyZWFkcikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpgYGAKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShyZWFkcikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCgojIyBSZWFkaW5nIGluIGFuZCBDbGVhbmluZyAiRmxhdGZpbGVzIgoKSSB0aGluayB3aGVuIG1vc3QgbmV3IFIgdXNlcnMgKGFuZCBtb3N0IFIgdXNlcnMgaW4gZ2VuZXJhbCkgdGhpbmsgb2YgZGF0YXNldHMsIHRoZXkgdGhpbmsgaW4gdGVybXMgb2YgYSBzcHJlYWRzaGVldCBmb3JtYXQuIFRoaXMgY29uY2VwdHVhbCBsYXlvdXQgaXMgb2Z0ZW4gcmVmZXJyZWQgdG8gYXMgYSBmbGF0ZmlsZSwgYXMgaXQgaXMgYSB0d28gZGltZW5zaW9uYWwgbGF5b3V0IHdpdGhvdXQgaGllcmFyY2hpY2FsIHJlbGF0aW9uc2hpcHMgdG8gb3RoZXIgZGF0YSwgYXMgaXMgdGhlIGNhc2UgaW4gYSByZWxhdGlvbmFsIGRhdGFiYXNlLiBXZSB3aWxsIG9ubHkgd29yayB3aXRoIHRoaXMgc3ByZWFkc2hlZXQtc3R5bGUgZGF0YSBpbiB0aGlzIHdvcmtzaG9wLgoKV2hlbiBhIGxvdCBvZiBwZW9wbGUgdGhpbmsgJ3NwcmVhZHNoZWV0JywgdGhleSB0aGluayBhIC54bHN4IGZpbGUuIFRoZSAueGxzeCBmb3JtYXQgd29ya3MgdmVyeSB3ZWxsIGZvciBzb21lIHR5cGVzIG9mIGRhdGEgbGlrZSBidWRnZXRzIG9yIGNsYXNzIGdyYWRlcywgYnV0IGl0IHJlYWxseSBpcyBub3Qgc3VpdGFibGUgZm9yIGxhcmdlciBkYXRhc2V0cy4gSG93IGF0dHJhY3RpdmUgZG9lcyBvcGVuaW5nIGFuIC54bHN4IGZpbGUgd2l0aCBzZXZlcmFsIG1pbGxpb25zIG9mIHJvd3MgYW5kIGEgaHVuZHJlZCBjb2x1bW5zIHNvdW5kPyBBbmQgc29tZSBvZiBFeGNlbCdzICdzbWFydCcgZmVhdHVyZXMgY2FuIGhhdmUgdmVyeSBzdHVwaWQgY29uc2VxdWVuY2VzLiAoW0NoZWNrIG91dCB0aGlzIG5pZ2h0bWFyZV0oaHR0cDovL3d3dy5zY2llbmNlbWFnLm9yZy9uZXdzL3NpZnRlci9vbmUtZml2ZS1nZW5ldGljcy1wYXBlcnMtY29udGFpbnMtZXJyb3JzLXRoYW5rcy1taWNyb3NvZnQtZXhjZWwpLikKCkluIFIsIGl0IGlzIG1vcmUgY29tbW9uIHRvIHVzZSBwbGFpbiB0ZXh0IGZpbGVzLiBUaGVzZSBjb3VsZCBoYXZlIHRoZSBleHRlbnNpb25zIC5jc3Ygb3IganVzdCAudHh0LiBFaXRoZXIgd2F5IHRoZXkgYXJlIGp1c3QgdGV4dC4gQ29weSBhbmQgcGFzdGUgdGhlIHRleHQgYmVsb3cgaW50byBhIHBsYWluIHRleHQgZWRpdG9yIChZb3UgY2FuIGRvIHRoaXMgaW4gUlN0dWRpbywgdG9vKSwgYW5kIHNhdmUgaXQgaW4geW91ciBwcm9qZWN0L3dvcmtpbmcgZGlyZWN0b3J5IGFzICJNVGNhcnMudHh0Ii4KCmBgYHtyLCBldmFsID0gRkFMU0V9CiJtcGciLCAiY3lsIiwgImRpc3AiLCAiaHAiLCAiZHJhdCIsICJ3dCIsICJxc2VjIiwgInZzIiwgImFtIiwgImdlYXIiLCAiY2FyYiIKIk1hemRhIFJYNCIsIDIxLCA2LCAxNjAsIDExMCwgMy45LCAyLjYyLCAxNi40NiwgMCwgMSwgNCwgNAoiTWF6ZGEgUlg0IFdhZyIsIDIxLCA2LCAxNjAsIDExMCwgMy45LCAyLjg3NSwgMTcuMDIsIDAsIDEsIDQsIDQKIkRhdHN1biA3MTAiLCAyMi44LCA0LCAxMDgsIDkzLCAzLjg1LCAyLjMyLCAxOC42MSwgMSwgMSwgNCwgMQoiSG9ybmV0IDQgRHJpdmUiLCAyMS40LCA2LCAyNTgsIDExMCwgMy4wOCwgMy4yMTUsIDE5LjQ0LCAxLCAwLCAzLCAxCiJIb3JuZXQgU3BvcnRhYm91dCIsIDE4LjcsIDgsIDM2MCwgMTc1LCAzLjE1LCAzLjQ0LCAxNy4wMiwgMCwgMCwgMywgMgoiVmFsaWFudCIsIDE4LjEsIDYsIDIyNSwgMTA1LCAyLjc2LCAzLjQ2LCAyMC4yMiwgMSwgMCwgMywgMQoiRHVzdGVyIDM2MCIsIDE0LjMsIDgsIDM2MCwgMjQ1LCAzLjIxLCAzLjU3LCAxNS44NCwgMCwgMCwgMywgNAoiTWVyYyAyNDBEIiwgMjQuNCwgNCwgMTQ2LjcsIDYyLCAzLjY5LCAzLjE5LCAyMCwgMSwgMCwgNCwgMgoiTWVyYyAyMzAiLCAyMi44LCA0LCAxNDAuOCwgOTUsIDMuOTIsIDMuMTUsIDIyLjksIDEsIDAsIDQsIDIKIk1lcmMgMjgwIiwgMTkuMiwgNiwgMTY3LjYsIDEyMywgMy45MiwgMy40NCwgMTguMywgMSwgMCwgNCwgNAoiTWVyYyAyODBDIiwgMTcuOCwgNiwgMTY3LjYsIDEyMywgMy45MiwgMy40NCwgMTguOSwgMSwgMCwgNCwgNAoiTWVyYyA0NTBTRSIsIDE2LjQsIDgsIDI3NS44LCAxODAsIDMuMDcsIDQuMDcsIDE3LjQsIDAsIDAsIDMsIDMKIk1lcmMgNDUwU0wiLCAxNy4zLCA4LCAyNzUuOCwgMTgwLCAzLjA3LCAzLjczLCAxNy42LCAwLCAwLCAzLCAzCiJNZXJjIDQ1MFNMQyIsIDE1LjIsIDgsIDI3NS44LCAxODAsIDMuMDcsIDMuNzgsIDE4LCAwLCAwLCAzLCAzCiJDYWRpbGxhYyBGbGVldHdvb2QiLCAxMC40LCA4LCA0NzIsIDIwNSwgMi45MywgNS4yNSwgMTcuOTgsIDAsIDAsIDMsIDQKIkxpbmNvbG4gQ29udGluZW50YWwiLCAxMC40LCA4LCA0NjAsIDIxNSwgMywgNS40MjQsIDE3LjgyLCAwLCAwLCAzLCA0CiJDaHJ5c2xlciBJbXBlcmlhbCIsIDE0LjcsIDgsIDQ0MCwgMjMwLCAzLjIzLCA1LjM0NSwgMTcuNDIsIDAsIDAsIDMsIDQKIkZpYXQgMTI4IiwgMzIuNCwgNCwgNzguNywgNjYsIDQuMDgsIDIuMiwgMTkuNDcsIDEsIDEsIDQsIDEKIkhvbmRhIENpdmljIiwgMzAuNCwgNCwgNzUuNywgNTIsIDQuOTMsIDEuNjE1LCAxOC41MiwgMSwgMSwgNCwgMgoiVG95b3RhIENvcm9sbGEiLCAzMy45LCA0LCA3MS4xLCA2NSwgNC4yMiwgMS44MzUsIDE5LjksIDEsIDEsIDQsIDEKIlRveW90YSBDb3JvbmEiLCAyMS41LCA0LCAxMjAuMSwgOTcsIDMuNywgMi40NjUsIDIwLjAxLCAxLCAwLCAzLCAxCiJEb2RnZSBDaGFsbGVuZ2VyIiwgMTUuNSwgOCwgMzE4LCAxNTAsIDIuNzYsIDMuNTIsIDE2Ljg3LCAwLCAwLCAzLCAyCiJBTUMgSmF2ZWxpbiIsIDE1LjIsIDgsIDMwNCwgMTUwLCAzLjE1LCAzLjQzNSwgMTcuMywgMCwgMCwgMywgMgoiQ2FtYXJvIFoyOCIsIDEzLjMsIDgsIDM1MCwgMjQ1LCAzLjczLCAzLjg0LCAxNS40MSwgMCwgMCwgMywgNAoiUG9udGlhYyBGaXJlYmlyZCIsIDE5LjIsIDgsIDQwMCwgMTc1LCAzLjA4LCAzLjg0NSwgMTcuMDUsIDAsIDAsIDMsIDIKIkZpYXQgWDEtOSIsIDI3LjMsIDQsIDc5LCA2NiwgNC4wOCwgMS45MzUsIDE4LjksIDEsIDEsIDQsIDEKIlBvcnNjaGUgOTE0LTIiLCAyNiwgNCwgMTIwLjMsIDkxLCA0LjQzLCAyLjE0LCAxNi43LCAwLCAxLCA1LCAyCiJMb3R1cyBFdXJvcGEiLCAzMC40LCA0LCA5NS4xLCAxMTMsIDMuNzcsIDEuNTEzLCAxNi45LCAxLCAxLCA1LCAyCiJGb3JkIFBhbnRlcmEgTCIsIDE1LjgsIDgsIDM1MSwgMjY0LCA0LjIyLCAzLjE3LCAxNC41LCAwLCAxLCA1LCA0CiJGZXJyYXJpIERpbm8iLCAxOS43LCA2LCAxNDUsIDE3NSwgMy42MiwgMi43NywgMTUuNSwgMCwgMSwgNSwgNgoiTWFzZXJhdGkgQm9yYSIsIDE1LCA4LCAzMDEsIDMzNSwgMy41NCwgMy41NywgMTQuNiwgMCwgMSwgNSwgOAoiVm9sdm8gMTQyRSIsIDIxLjQsIDQsIDEyMSwgMTA5LCA0LjExLCAyLjc4LCAxOC42LCAxLCAxLCA0LCAyCmBgYAoKTm93IHJ1biB0aGlzIGNvZGUuCmBgYHtyfQpNVCA8LSByZWFkLnRhYmxlKCJNVGNhcnMudHh0Iiwgc2VwID0gIiwiLCBoZWFkZXIgPSBUUlVFKSAKIyBUaGUgJ3NlcCA9JyBjb3VsZCBiZSBhbnl0aGluZywgYnV0IHdoaXRlc3BhY2UgKCIgIiksIHNlbWljb2xvbiwgYW5kIFRhYiAoIlx0IikgYXJlIGFsc28gY29tbW9uLgpoZWFkKE1UKQpgYGAKCkJlY2F1c2Ugd2Uga25ldyB0aGF0IG91ciB2YWx1ZXMgd2VyZSBjb21tYS1zZXBhcmF0ZWQsIHdlIGNvdWxkIGhhdmUganVzdCB1c2VkIGByZWFkLmNzdigpYCB3aGljaCBpcyBqdXN0IHRoZSBgcmVhZC50YWJsZSgpYCBmdW5jdGlvbiB3aXRoIHRoZSBzZXBhcmF0b3IgcHJlLXNldC4gKElmIHlvdSBkaWQgbm90IGtub3cgcHJldmlvdXNseSwgb3IgaGF2ZSBub3QgZ3Vlc3NlZCB5ZXQsIHRoZSAuY3N2IGV4dGVuc2lvbiBtZWFucyAiQ29tbWEtU2VwYXJhdGVkIFZhbHVlcyIuKQpgYGB7cn0KTVQyIDwtIHJlYWQuY3N2KCJNVGNhcnMudHh0IikgIyBXZSBjb3VsZCBjaGFuZ2UgdGhlIC50eHQgdG8gLmNzdiwgYnV0IGl0J3MgdW5uZWNlc3NhcnkuCmlkZW50aWNhbChNVCwgTVQyKQpgYGAKCldlIGNhbiBzZWUgdGhhdCBgcmVhZC5jc3YoKWAgaXMganVzdCBhIHdyYXBwZXIgZnVuY3Rpb24gZm9yIGByZWFkLnRhYmxlKClgIGJ5IGVudGVyaW5nIGByZWFkLmNzdmAgKG5vdGUgdGhlIGxhY2sgb2YgcGFyYW50aGVzZXMpLgpgYGB7cn0KcmVhZC5jc3YKYGBgCgpgcmVhZC50YWJsZSgpYCBoYXMgbWFueSBtb3JlIG9wdGlvbnMvYXJndW1lbnRzIGFzIHlvdSBjYW4gc2VlIGZyb20gdGhlIG91dHB1dCBhYm92ZS4gV2UgY2FuIGZpbmQgb3V0IHdoYXQgdGhlc2UgYXJndW1lbnRzIGFyZSBmb3IgYW55IGZ1bmN0aW9uIGJ5IGVudGVyaW5nIGA/PGZ1bmN0aW9uX25hbWU+YC4KYGBge3IsIGV2YWwgPSBGQUxTRX0KP3JlYWQudGFibGUKYGBgCgpNb3N0IG9mIHRoZXNlIGFyZ3VtZW50cyBoYXZlIGRlZmF1bHRzIGFuZC9vciBhcmUgb3B0aW9uYWwgc28gdGhleSBkbyBub3QgbmVlZCB0byBiZSBleHBsaWNpdGx5IGVudGVyZWQuIFNlZSB0aGF0IHRoZSBkZWZhdWx0cyBmb3IgYHN0cmlwLndoaXRlYCBhbmQgYGJsYW5rLmxpbmVzLnNraXBgIGFyZSBgRkFMU0VgIGFuZCBgVFJVRWAsIHJlc3BlY3RpdmVseS4gCgpOb3RlIGFsc28gdGhhdCBhdCB0aGUgdG9wIG9mIHRoZSBIZWxwIHBhZ2UgaXQgcmVhZHMgJ3JlYWQudGFibGUge3V0aWxzfScuIFRoaXMgbWVhbnMgdGhhdCB0aGUgZnVuY3Rpb24gYHJlYWQudGFibGUoKWAgaXMgcGFydCBvZiB0aGUgJ3V0aWxzJyBwYWNrYWdlLiBUaGVyZSBhcmUgc2V2ZXJhbCBwYWNrYWdlcyB0aGF0IGFyZSBwYXJ0IG9mIGV2ZXJ5IFIgaW5zdGFsbC4gVGhlIG1vc3QgaW1wb3J0YW50IG9mIHdoaWNoIGFyZSAnYmFzZScsICd1dGlscycsICdzdGF0cycsIGFuZCAnZ3JhcGhpY3MnLiBBZGRpdGlvbmFsIHBhY2thZ2VzIGNhbiBiZSBpbnN0YWxsZWQgZnJvbSBDUkFOIHVzaW5nIHRoZSBjb21tYW5kIGBpbnN0YWxsLnBhY2thZ2VzKCI8cGFja2FnZV9uYW1lPiIpYCwgb3IgY2xpY2tpbmcgdGhlIFBhY2thZ2VzIHRhYiBpbiBSU3R1ZGlvLiBQYWNrYWdlcyBzdGlsbCBuZWVkIHRvIGJlIGxvYWRlZCB0byBiZSB1c2VkLCBob3dldmVyLiBUaGlzIGlzIGRvbmUgdXNpbmcgdGhlIGBsaWJyYXJ5KDxwYWNrYWdlX25hbWU+KWAgKHF1b3RlcyBvcHRpb25hbCkuCgpUaGUgcmVhc29uIHdlIGFyZSB0YWxraW5nIGFib3V0IHBhY2thZ2VzIGhlcmUgaXMgdGhhdCBSU3R1ZGlvIGxpa2VzIHRvIHVzZSB0aGUgJ3JlYWRyJyBwYWNrYWdlIHRvIHJlYWQgaW4gZmxhdGZpbGVzLiBMZXQncyB1c2UgdGhlICdJbXBvcnQgRGF0YXNldCcgYnV0dG9uIG9uIHRoZSBFbnZpcm9ubWVudCBwYW5lLiBDaG9vc2UgJ0Zyb20gQ1NWJyBhbmQgdGhlbiAnQnJvd3NlLi4uJy4gU2VsZWN0IGFuZCBvcGVuIHRoZSAnTVRjYXJzLnR4dCcgZmlsZS4gCgpSU3R1ZGlvIGdpdmVzIHVzIGEgcHJldmlldyBvZiB3aGF0IGlzIGdvaW5nIHRvIGJlIHJlYWQgaW4uIFRoZSByZWFkciBwYWNrYWdlIGlzIGFuIGV4Y2VsbGVudCBwYWNrYWdlLi4uIGJ1dCBoZXJlLCB3ZSBoYXZlIGEgcHJvYmxlbS4gV2hlcmUgYHJlYWQuY3N2KClgIHdvcmtlZCBwZXJmZWN0bHksIGByZWFkX2NzdigpYCBpcyBub3QgZ29pbmcgdG8uIFdoYXQgYXJlIHNvbWUgcG9zc2libGUgc29sdXRpb25zPwoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShyZWFkcikKTVRjYXJzIDwtIHJlYWRfY3N2KCI8ZmlsZXBhdGg+L01UY2Fycy50eHQiKQpoZWFkKE1UY2FycykKc3RyKE1UY2FycykKYGBgCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpNVGNhcnMgPC0gcmVhZF9jc3YoIk1UY2Fycy50eHQiLCBjb2xfbmFtZXMgPSBGQUxTRSwgc2tpcCA9IDEpCk1UY2Fyc05hbWVzIDwtIGFzLmNoYXJhY3RlcihyZWFkX2NzdigiTVRjYXJzLnR4dCIsIGNvbF9uYW1lcyA9IEZBTFNFLCBuX21heCA9IDEpKQpNVGNhcnNOYW1lcyA8LSBnc3ViKHBhdHRlcm4gPSAiXCIiLCByZXBsYWNlbWVudCA9ICIiLCBNVGNhcnNOYW1lcykgIyBOb3RlIHRoZSBvcmRlciBvZiB0aGUgYXJndW1lbnRzCk1UY2Fyc05hbWVzIDwtIGMoIm1vZGVsIiwgTVRjYXJzTmFtZXMpCm5hbWVzKE1UY2FycykgPC0gTVRjYXJzTmFtZXMKYGBgCgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KIyBsaWJyYXJ5KHJlYWRyKQojIE1UY2FycyA8LSByZWFkX2NzdigiTVRjYXJzLnR4dCIpCmhlYWQoTVRjYXJzKQpzdHIoTVRjYXJzKQpgYGAKUnVubmluZyBgc3RyKClgIG9uIGJvdGggYE1UY2Fyc2AgYW5kIGBNVGAgKG9yIGBNVDJgKSwgd2UgY2FuIHNlZSB0aGVyZSBhcmUgc29tZSBkaWZmZXJlbmNlcyAoYmVzaWRlcyBvdXIgcm93IG5hbWVzIHdvcmsgYXJvdW5kKS4gV2hhdCBhcmUgdGhleT8KCldlIGhhZCBwcm9ibGVtcyBiZWNhdXNlIG9mIHJvdyBuYW1lcy4gUm93IG5hbWVzIHdlcmUgYSBiYWQgZmVhdHVyZS4gVGhleSBzaG91bGQgYmUgYW4gYWRkaXRpb25hbCBkYXRhIGNvbHVtbiBpbnN0ZWFkLiBQYWNrYWdlcyBsaWtlIHJlYWRyIGFuZCByZWFkeGwgYXJlIG1vcmUgY29uc2lzdGVudCBhbmQgb2Z0ZW4gbXVjaCBmYXN0ZXIgdGhhbiBzb21lIG9mIHRoZSBiYXNlIGZ1bmN0aW9uYWxpdHkgaW4gUi4gVGhhdCBzYWlkLCBJIHVzZSBib3RoICdyZWFkLmNzdigpJyBhbmQgJ3JlYWRfY3N2KCknIHRvIHJlYWQgaW4gZGF0YS4gTG9hZGluZyBhbmQgdXNpbmcgZGlmZmVyZW50IHBhY2thZ2VzIGlzIGEgbmVjZXNzYXJ5IHRoaW5nIHRvIGRvLgoKIyMjIyBTZWxlY3RpbmcgYW5kIFN1YnNldHRpbmcgRGF0YQoKU28gd2UgaGF2ZSBhIGRhdGFzZXQgYE1UY2Fyc2AuIFRoaXMgZGF0YXNldCBpcyB0aW55LiBXZSBjb3VsZCBwcm9iYWJseSBnZXQgYSByZWFzb25hYmxlIGdyYXNwIG9mIHRoZSB2YWx1ZXMgdGhhdCBhcmUgY29udGFpbiB3aXRoaW4gc2ltcGx5IGJ5IGV5ZWJhbGxpbmcgdGhlbS4gT3VyIGFiaWxpdHkgdG8gZG8gdGhpcyByYXBpZGx5IGRpbWluaXNoZXMgYXMgdGhlIGRhdGFzZXQgZ3Jvd3MsIGhvd2V2ZXIuIFRvIGxvb2sgYXQgc3BlY2lmaWMgdmFsdWVzIHdlIG5lZWQgdG8gYmUgYWJsZSB0byBmaW5kIHRoZW0gaW4gb3VyIGRhdGFzZXQuIFRoZSBtb3N0IGJhc2ljIHdheSBpcyB0byBzZWxlY3QgYSByb3cgYW5kIGNvbHVtbiBudW1lcmljYWxseS4gTGV0J3MgbG9vayBhdCB0aGUgYG1vZGVsYCBjb2x1bW4gb2YgYE1UY2Fyc2AuCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KTVRjYXJzWzEsMV0gIyBUaGlzIHNlbGVjdHMgdGhlIGZpcnN0IHJvdyBhbmQgZmlyc3QgY29sdW1uIG9mIE1UY2FycwpNVGNhcnNbMywxXSAjIFRoZSB0aGlyZCB2YWx1ZSBmcm9tIHRoZSBmaXJzdCBjb2x1bW4uCk1UY2Fyc1sxOjMsMV0gIyBUaGUgZmlyc3QgdGhyZWUuCk1UY2Fyc1tjKDMsIDYsIDEwLCAyMDoyMywgMjgpLDFdICMgQSBzcGVjaWZpYyBzZWxlY3Rpb24KTVRjYXJzW3NlcShmcm9tID0gMSwgdG8gPSBucm93KE1UY2FycyksIGJ5ID0gMiksMV0gIyBDYW4gd2UgdGVsbCB3aGF0IHRoaXMgZGlkPwpgYGAKCldlIGNhbiBkbyB0aGUgc2FtZSB3aXRoIGNvbHVtbnMuCgpgYGB7cn0KTVRjYXJzWzEsMl0gIyBUaGUgTVBHIHZhbHVlIGZvciB0aGUgTWF6ZGEgUlg0CmBgYApEb2VzIHRoaXMgcmV0dXJuIGEgc2luZ2xlIHZhbHVlPyBXaGF0IGhhcHBlbnMgd2hlbiB3ZSBgc3RyKClgIGl0LiBIb2xkIHRoaXMgdGhvdWdodC4KCldlIGNhbiBnZXQgYWxsIHRoZSB2YWx1ZXMgaW4gdGhlIGBtcGdgIGNvbHVtbiBqdXN0IGJ5IGxlYXZpbmcgcm93cyBibGFuay4gKFRoaXMgd29ya3MgZm9yIHJvd3MgYXMgd2VsbCwgb2YgY291cnNlKS4KYGBge3J9CmhlYWQoTVRjYXJzWywyXSkKYGBgCgpXZSBoYXZlIGFub3RoZXIgd2F5IG9mIHNlbGVjdGluZyB2YWx1ZXMsIGFuZCB0aGlzIHdheSB3aWxsIGp1c3QgcmV0dXJuIHRoZSB2YWx1ZS4gV2UgdXNlIHRoZSBkb2xsYXIgc2lnbiBgJGAgd2hpY2ggd2UgY2FuIHRoaW5rIG9mIGFzIG1lYW5pbmcgJ3NlbGVjdCcuCgpgYGB7cn0KaGVhZChNVGNhcnMkbXBnKQpgYGAKTm90aWNlIHRoZSBkaWZmZXJlbmNlIGluIHRoZSBvdXRwdXQuIFRoaXMgaXMgYSB2ZWN0b3Igb2YgdmFsdWVzLCBub3QgYSBkYXRhZnJhbWUgKG9yIHRpYmJsZSkuCgpgYGB7cn0KTVRjYXJzJG1wZ1sxXSAjIFRoaXMgZ2l2ZXMgdXMgdGhlIGZpcnN0IHZhbHVlIGluIHRoZSBNUEcgY29sdW1uLgpzdHIoTVRjYXJzJG1wZ1sxXSkKYGBgCgpXaXRoIHRoaXMga25vd2xlZGdlIHdlIGNhbiBnZXQgYWxsIHNvcnRzIG9mIGluZm8gYWJvdXQgb3VyIGNvbHVtbnMuCmBgYHtyfQptZWFuKE1UY2FycyRocCwgbmEucm0gPSBUUlVFKSAjIFRoZSBleHRyYSBhcmd1bWVudCBoZXJlIHJlbW92ZXMgTkFzIGZyb20gdGhlIGRhdGEuIFdlIGRvbid0IGhhdmUgYW55LCBidXQgSSB3YW50IHlvdSB0byBzZWUgaXQuCnNkKE1UY2FycyRtcGcpICMgU3RhbmRhcmQgRGV2aWF0aW9uCm1pbihNVGNhcnMkcXNlYykgIyBTZWNvbmRzIGZvciBhIHF1YXJ0ZXIgbWlsZS4gVGhlIE1UIHN0YW5kcyBmb3IgJ01vdG9yIFRyZW5kJyBtYWdhemluZSwgYWZ0ZXIgYWxsLgpyYW5nZShNVGNhcnMkcXNlYykgCnBsb3QoTVRjYXJzJGhwLCBNVGNhcnMkcXNlYykgIyBZZXAsIG1vcmUgaG9yc2Vwb3dlciBjb3JyZWxhdGVzIHdpdGggZmFzdGVyIHRpbWVzCiMgY29yKE1UY2FycyRocCwgTVRjYXJzJHFzZWMpICMgR2l2ZXMgYSBuZWdhdGl2ZSBjb3JyZWxhdGlvbiBvZiAtMC43MDgyMjM0CmBgYAoKV2hhdCBpZiB3ZSBvbmx5IHdhbnRlZCB0byBjb21wYXJlIHRoZSB0ZXN0ZWQgY2FycyB0aGF0IGhhZCBtYW51YWwgdHJhbnNtaXNzaW9ucy4gVGhlIGNvbHVtbiBgYW1gIG1lYW5zICdhdXRvbWF0aWMvbWFudWFsJywgd2l0aCAnYXV0b21hdGljJyBhc3NpZ25lZCB0aGUgdmFsdWUgemVybyBhbmQgbWFudWFsIHRvIHRoZSB2YWx1ZSAxLiBXZSBjb3VsZCBtYWtlIGEgc2VwYXJhdGUgZGF0YXNldC4gSGVyZSB3ZSBoYXZlIHNvbWUgb3B0aW9ucy4KCl9fU3Vic2V0IHdpdGggYSBMb2dpY2FsIFZlY3Rvcl9fCmBgYHtyfQpNVGNhcnNfbWFuMSA8LSBNVGNhcnNbTVRjYXJzJGFtID09IDEsIF0gIyBOb3RlIHRoYXQgd2UgdXNlIGEgZG91YmxlICI9IgpoZWFkKE1UY2Fyc19tYW4xKQpgYGAKTGV0J3MgdGhpbmsgYWJvdXQgd2hhdCB0aGlzIGRpZC4gYE1UY2FycyRhbSA9PSAxYCBjcmVhdGVzIGEgbG9naWNhbCB2ZWN0b3Igb2YgYFRSVUVgIGFuZCBgRkFMU0VgLiBGb3IgYWxsIG9mIHRoZSBgVFJVRWAgdmFsdWVzLCBhIHJvdyBpcyBrZXB0IGluIHRoZSBuZXcgZGF0YWZyYW1lLgoKX19TdWJzZXQgd2l0aCB0aGUgRnVuY3Rpb24gYHN1YnNldCgpYF9fCmBgYHtyfQpNVGNhcnNfbWFuMiA8LSBzdWJzZXQoTVRjYXJzLCBhbSA9PSAxKQppZGVudGljYWwoTVRjYXJzX21hbjEsIE1UY2Fyc19tYW4yKQpgYGAKVHdvIG1ldGhvZHM7IHNhbWUgcmVzdWx0LgoKX19TdWJzZXQgd2l0aCBgZmlsdGVyKClgX18KQSB0aGlyZCBtZXRob2QgaXMgdG8gdXNlIHRoZSBgZHBscmAgcGFja2FnZSdzIGBmaWx0ZXIoKWAuCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpNVGNhcnNfbWFuMyA8LSBmaWx0ZXIoTVRjYXJzLCBhbSA9PSAxKQppZGVudGljYWwoTVRjYXJzX21hbjEsIE1UY2Fyc19tYW4zKQpgYGAKUmVhbGx5PyEKYGBge3J9CnRhYmxlKE1UY2Fyc19tYW4xID09IE1UY2Fyc19tYW4zKQpgYGAKSWYgd2UgcnVuIGBzdHIoKWAgb24gYE1UY2Fyc19tYW4zYCB3ZSBzZWUgdGhhdCB0aGVyZSBpcyBzb21lIGFkZGl0aW9uYWwgbWV0YWRhdGEgaW4gdGhlIGRhdGFmcmFtZS4gSG93ZXZlciwgaWYgd2UgdXNlIHRoZSBgdGFibGUoKWAgZnVuY3Rpb24sIHdlIGNhbiBzZWUgdGhhdCBhbGwgdGhlIGluZGl2aWR1YWwgdmFsdWVzIGluIGVhY2ggY2VsbCBhcmUgdGhlIHNhbWUuIFNvLCB0aGUgZGF0YWZyYW1lcyBhcmUgdGhlIHNhbWUuCgpOb3cgd2UgY2FuIG1ha2UgYSBwbG90IHdpdGgganVzdCB0aGUgbWFudWFsIHRyYW5zbWlzc2lvbiBjYXJzLgpgYGB7cn0KcGxvdChNVGNhcnNfbWFuMSRocCwgTVRjYXJzX21hbjEkcXNlYykKIyBjb3IoTVRjYXJzX21hbjEkaHAsIE1UY2Fyc19tYW4xJHFzZWMpICMgQW4gZXZlbiBzdHJvbmdlciBjb3JyZWxhdGlvbiBoZXJlOiAtMC44NDk0NTY2CmBgYAoKCldlIGRvIG5vdCBuZWVkIHRvIG1ha2UgZW50aXJlbHkgbmV3IGRhdGFmcmFtZXMgdG8gZ2V0IHNpbXBsZSBzdGF0aXN0aWNzLiBGb3IgZXhhbXBsZSwgd2UgY2FuIGdldCB0aGUgbWVhbiBvZiBgaHBgIHNpbXBseSBieSBzdWJzZXR0aW5nIHdpdGhpbiB0aGUgZnVuY3Rpb24gY2FsbC4KYGBge3J9Cm1lYW4oTVRjYXJzJGhwW01UY2FycyRhbSA9PSAxXSwgbmEucm0gPSBUKQojIG1lYW4oTVRjYXJzJGhwW01UY2FycyRhbSA9PSAxXSwgbmEucm0gPSBUKSA9PSBtZWFuKE1UY2Fyc19tYW4xJGhwLCBuYS5ybSA9IFQpICMgWzFdIFRSVUUKYGBgCgpXZSBjYW4gZXZlbiBwdXQgaW4gYSBudW1iZXIgb2YgbG9naWNhbCBleHByZXNzaW9ucy4gQ2FuIHlvdSBmaWd1cmUgb3V0IHdoYXQgaGFwcGVucyBpbiB0aGVzZSBmdW5jdGlvbiBjYWxscz8KYGBge3J9Cm1lYW4oTVRjYXJzJGhwW01UY2FycyRhbSA9PSAxICYgTVRjYXJzJGN5bCA+IDRdLCBuYS5ybSA9IFQpCm1lZGlhbihNVGNhcnMkaHBbTVRjYXJzJGFtID09IDAgJiBNVGNhcnMkbXBnIDw9IDE2XSwgbmEucm0gPSBUKQpyYW5nZShNVGNhcnMkaHBbTVRjYXJzJGRpc3AgPj0gbWVkaWFuKE1UY2FycyRkaXNwKSB8IE1UY2FycyRjeWwgPj0gOF0sIG5hLnJtID0gVCkgIyBJJ20gdXNpbmcgdHdvIGNyaXRlcmlhIHRvIHNlbGVjdCBteSAnYmlnJyBlbmdpbmUgY2Fycy4KYGBgCgpTb21lIG9mIHRoaXMgY29kZSBiZWNvbWVzIHZlcnkgZGlmZmljdWx0IGV2ZW4gdG8gcmVhZCBsZXQgYWxvbmUgdG8gY29uc3RydWN0IG9uZXNlbGYuIFdlIG5ldmVyIG5lZWQgdG8gZG8gdGhpbmdzIGFsbCBpbiBvbmUgbGluZS4gSWYgcmFuIGByYW5nZShNVGNhcnMkaHBbTVRjYXJzJGRpc3AgPj0gbWVkaWFuKE1UY2FycyRkaXNwKSB8IE1UY2FycyRjeWwgPj0gOF0sIG5hLnJtID0gVClgIG92ZXIgbXVsdGlwbGUgbGluZXMgc3RhcnRpbmcgZnJvbSB0aGUgbW9zdCBlbWJlZGRlZCBmdW5jdGlvbiBjYWxsLCBob3cgY291bGQgd2UgYnJlYWsgdGhpcyBkb3duIG1vcmUgc2ltcGx5PwoKSSdsbCBzdGFydCB1cyBvdXQuCmBgYHtyLCBldmFsPUZBTFNFfQphYSA8LSBtZWRpYW4oTVRjYXJzJGRpc3ApCmJiIDwtIApjYyA8LSAKZGQgPC0gCmVlIDwtIApyYW5nZShlZSwgbmEucm0gPSBUKSAjIE5lZWRzIHRvIG91dHB1dDogWzFdIDEwNSAzMzUKYGBgCgojIyMjIFRpZHkgRGF0YQoKTGV0J3MgcmVhZCBpbiBzb21lIFlFRyBPcGVuIERhdGEgKHRoYXQgSSd2ZSBtZXNzZWQgd2l0aCksIGFuZCB0YWtlIGEgbG9vayBhdCBpdC4KCmBgYHtyfQpMZWlzdXJlQXR0IDwtIHJlYWQuY3N2KCJMZWlzdXJlQXR0LmNzdiIpICMgTm90ZSB3ZSBkaWRuJ3QgdXNlIHJlYWRyJ3MgcmVhZF9jc3YoKQpoZWFkKExlaXN1cmVBdHQpCmBgYAoKVGhpcyBpcyB0aGUgY29tcGxldGUgZGF0YSBzZXQuIFdoYXQgZG8geW91IHRoaW5rIG9mIHRoZSBsYXlvdXQ/IEluIHNvbWUgd2F5cyB0aGlzIGRhdGEgaXMgZmluZSAoaW4gZmFjdCBpdCBjb3VsZCBldmVuIGJlIGJldHRlciB0aGFuIHRoZSBmb3JtYXQgd2UgYXJlIGFib3V0IHRvIGxlYXJuKS4gSG93ZXZlciwgaXQgdmlvbGF0ZXMgdGhlIHByaW5jaXBsZXMgb2YgJ3RpZHkgZGF0YScuIFRoaW5rIGFib3V0IGhvdyB0aGlzIGRhdGEgc2hvdWxkIGxvb2sgdG8gcGxvdCBpdCBtb3N0IGVhc2lseS4gSWYgd2UgaGF2ZSBhbiBgeGAgYW5kIGEgYHlgIGF4aXMgdG8gcGxvdCB3aGF0IHdvdWxkIHRoZXkgYmUsIGFuZCBjYW4gd2UgbWF0Y2ggYSBzaW5nbGUgdmFyaWFibGUgdG8gYHhgIGFuZCBhbm90aGVyIHRvIGB5YD8gSWYgd2Ugd2FudGVkIHRvIHBlcmZvcm0gYSBzaW5nbGUgb3BlcmF0aW9uLCBsaWtlIHJvdW5kaW5nIHRvIHRoZSBuZWFyZXN0IGh1bmRyZWQsIHRvIGFsbCB0aGUgaW1wb3J0YW50IHZhbHVlcyAodGhlIGF0dGVuZGFuY2UgYXQgZWFjaCBvYnNlcnZhdGlvbikgaG93IGNvdWxkIHRoZSBkYXRhIGJlIGxhaWQgb3V0IHRvIG1ha2UgdGhpcyAoYW5kIGV2ZXJ5dGhpbmcgZWxzZSB3ZSBkbyB0byBvdXIgZGF0YSkgZWFzaWVyPwoKVGhlIHByaW5jaXBsZXMgb2YgdGlkeSBkYXRhIHdlcmUgZGVzY3JpYmVkIGJ5IEhhZGxleSBXaWNraGFtIGFuZCB5b3UgY2FuIGxlYXJuIG1vcmUgYWJvdXQgdGhlbSBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RpZHlyL3ZpZ25ldHRlcy90aWR5LWRhdGEuaHRtbCkuIFRoZSBtYWluIHByaW5jaXBsZSBvZiB0aWR5IGRhdGEgaXMgdGhhdCBlYWNoIHZhcmlhYmxlIGlzIGNvbHVtbiBhbmQgZWFjaCBvYnNlcnZhdGlvbiBpcyBhIHJvdy4gRG9lcyB0aGUgZGF0YSBhYm92ZSBmb2xsb3cgdGhpcyBwcmluY2lwbGU/IEV2ZW4gdGhvdWdoIHRoZSBkYXRhIGFib3ZlIGlzIGVhc3kgdG8gbG9vayBhdCwgYW5kIHdvdWxkIHByb2JhYmx5IGhvdyB5b3Ugd291bGQgZm9ybWF0IHRoZSBkYXRhIGluIGEgcHJlc2VudGF0aW9uLCBpdCBpcyBub3QgdGlkeS4KCmBgYHtyfQpuYW1lcyhMZWlzdXJlQXR0KSA8LSBnc3ViKCJYIiwgIiIsIG5hbWVzKExlaXN1cmVBdHQpKSAjIEZpcnN0IGxldCdzIGdldCByaWQgb2YgdGhhdCBwZXNreSAiWCIKbGlicmFyeSh0aWR5cikgIyBBIHBhY2thZ2Ugd2l0aCB0aWR5aW5nIGZ1bmN0aW9ucyBsaWtlIGdhdGhlcigpLCBhbmQgaXRzIHJldmVyc2UsIHNwcmVhZCgpCkxlaXN1cmVBdHQgPC0gZ2F0aGVyKExlaXN1cmVBdHQsIFlFQVIsIEFUVEVOREFOQ0UsIDI6NykKaGVhZChMZWlzdXJlQXR0LCAzKQpgYGAKRGlkIHRoaXMgZG8gd2hhdCB3ZSB3YW50PyBBbnkgcHJvYmxlbXM/IExldCdzIHRha2UgYSBsb29rIGF0IGEgc2ltcGxlIHBsb3QuIChXZSdsbCBjb3ZlciBwbG90dGluZyBsYXRlcikuCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKSAjIGZ1bmN0aW9uIGdncGxvdCgpCmdncGxvdChMZWlzdXJlQXR0LCBhZXMoeCA9IE1PTlRILCB5ID0gQVRURU5EQU5DRSkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgdGhlbWUoYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0KGFuZ2xlPTYwLCBoanVzdCA9IDEsIHZqdXN0PTEsIHNpemU9OCkpICsgZmFjZXRfd3JhcCh+IFlFQVIsIG5jb2wgPSAzKSAKYGBgCk9mIGNvdXJzZSwgd2UgY2FuIGZpeCB0aGlzIGJ5IGNoYW5naW5nIHRoZSB0eXBlIG9mIGZhY3RvciB3ZSBoYXZlIGZvciBgTU9OVEhgLgoKYGBge3J9CnN0cihMZWlzdXJlQXR0JE1PTlRIKSAjIElmIHdlIGhhZCB1c2VkIHJlYWRfY3N2KCkgdGhpcyB3b3VsZCBiZSBhIGNoYXJhY3RlciB2ZWN0b3IsIGJ1dCB3ZSB3b3VsZG4ndCBoYXZlIGhhZCB0aGUgIlgiIG9uIHRoZSB5ZWFyIG5hbWVzLgpsZXZlbHMoTGVpc3VyZUF0dCRNT05USCkKTGVpc3VyZUF0dCRNT05USCA8LSBmYWN0b3IoTGVpc3VyZUF0dCRNT05USCwgbGV2ZWxzID0gdG91cHBlcihtb250aC5uYW1lKSkgIyBOb3RlIGFib3V0IHRoZSB0b3VwcGVyKG1vbnRoLm5hbWUpCmxldmVscyhMZWlzdXJlQXR0JE1PTlRIKQpgYGAKYGBge3J9CmhlYWQoTGVpc3VyZUF0dCkKYGBgClN0aWxsIGxvb2tzIHRoZSBzYW1lLCBidXQgaWYgd2UgcGxvdCBpdCBhZ2Fpbiwgd2Ugc2VlIHRoYXQgd2UgZ2V0IHdoYXQgd2Ugd2FudC4KCmBgYHtyfQpnZ3Bsb3QoTGVpc3VyZUF0dCwgYWVzKHggPSBNT05USCwgeSA9IEFUVEVOREFOQ0UpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIHRoZW1lKGF4aXMudGV4dC54ICA9IGVsZW1lbnRfdGV4dChhbmdsZT02MCwgaGp1c3QgPSAxLCB2anVzdD0xLCBzaXplPTgpKSArIGZhY2V0X3dyYXAofiBZRUFSLCBuY29sID0gMykgCmBgYAoKQmVjYXVzZSBvdXIgZGF0YSB3YXMgdGlkeShpc2gpLCBwbG90dGluZyBpcyBtdWNoIHNpbXBsZXIuIFRoZSBkYXRhIGZvciBgeGAgYW5kIGB5YCBhcmUgY29uc29saWRhdGVkIGludG8gc2VwYXJhdGUgY29sdW1ucy4gVGhpcyBtaWdodCBiZSBoYXJkZXIgZm9yIHVzIHRvIGxvb2sgYXQsIGJ1dCB3aGVuIHdlIHN0YXJ0IGRlYWxpbmcgd2l0aCBsYXJnZXIgZGF0YXNldHMsICdsb29raW5nJyBpcyBub3QgZ29pbmcgdG8gYmVuZWZpdCB1cyByYXRoZXIgd2Ugd2lsbCB3YW50IHRvIHVzZSBwbG90cyBvciBmdW5jdGlvbnMgbGlrZSBgcmFuZ2UoKWAsIGBzdW1tYXJ5KClgLCBgbWVhbigpYCwgYHN1bW1hcnkoKWAsIGV0YywgdG8gZ2V0IGEgc2Vuc2Ugb2Ygd2hhdCBvdXIgZGF0YXNldCBjb250YWlucy4gVGhlc2Ugb3BlcmF0aW9ucyBhcmUgbXVjaCBlYXNpZXIgdG8gcnVuLCBhbmQgZ29vZCBmb3IgdXMgY29uY2VwdHVhbGx5LCB3aGVuIHZhcmlhYmxlcyBhcmUgcmVwcmVzZW50ZWQgaW4gc2luZ2xlIGNvbHVtbnMuCgojIyMjIFJlYWRpbmcgaW4gRmlsZXMgZnJvbSB0aGUgV2ViCgpFZG1vbnRvbiBoYXMgYSBncmVhdCBvcGVuIGRhdGEgcG9ydGFsLCB3aGljaCB5b3UgY2FuIGZpbmQgaGVyZTogPGh0dHBzOi8vZGF0YS5lZG1vbnRvbi5jYS8+LiBMZXQncyB0YWtlIGEgbG9vayBhdCB3aGF0IHRoZSBMZWlzdXJlIENlbnRyZSBBdHRlbmRhbmNlIGRhdGFzZXQgbG9va2VkIGxpa2Ugd2hlbiBJIGZvdW5kIGl0LgoKCmBgYHtyfQpMZWlzdXJlQXR0ZW5kIDwtIHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9kYXNoYm9hcmQuZWRtb250b24uY2EvYXBpL3ZpZXdzL2lhYTcteDhray9yb3dzLmNzdiIpKQpoZWFkKExlaXN1cmVBdHRlbmQsIDUpWyxjKDI6NSw3KV0gIyBJJ20gb21pdHRpbmcgc29tZSBjb2x1bW5zIGZvciBhZXN0aGV0aWMgcmVhc29ucwpgYGAKClRoaXMgZGF0YXNldCwgdW5saWtlIHRoZSBvbmUgSSBjcmVhdGVkIF9tb3N0bHlfIGNvbmZvcm1zIHdpdGggdGhlIHByaW5jaXBsZXMgb2YgdGlkeSBkYXRhLCBidXQgdGhlcmUgaXMgc29tZXRoaW5nIGVsc2Ugb2RkIGhlcmUuIFdoYXQgaXMgaXQgYW5kIHdoYXQgY2FuIHdlIGRvIGFib3V0IGl0PyBXaXRob3V0IHRoaW5raW5nIG9mIGZ1bmN0aW9ucywgd2hhdCBhcmUgc29tZSB3YXkgd2UgY291bGQgbW9kaWZ5IHRoaXMgZGF0YWZyYW1lPwoKYGBge3J9CnN0cihMZWlzdXJlQXR0ZW5kKQpyYW5nZShMZWlzdXJlQXR0ZW5kJE1PTlRITFlfQVRURU5EQU5DRSkKCkxlaXN1cmVBdHRlbmQgPC0gZmlsdGVyKExlaXN1cmVBdHRlbmQsIE1PTlRITFlfQVRURU5EQU5DRSAhPSAwKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoTGVpc3VyZUF0dGVuZCwgYWVzKHggPSBSRVBPUlRfUEVSSU9ELCB5ID0gTU9OVEhMWV9BVFRFTkRBTkNFKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZShheGlzLnRleHQueCAgPSBlbGVtZW50X3RleHQoYW5nbGU9NjAsIGhqdXN0ID0gMSwgdmp1c3Q9MSwgc2l6ZT04KSkKYGBgCgpXZSB3aWxsIGRvIG1vcmUgd2l0aCB0aGlzIGRhdGEgd2hlbiB3ZSBnZXQgdG8gcGxvdHRpbmcuCgoKIyMjIyBSZW5hbWluZyBWYWx1ZXMgYW5kIERlYWxpbmcgd2l0aCBGYWN0b3JzCgpXaGF0IHdlIGFyZSBhYm91dCB0byBkbyBpcywgaW4gdGhpcyBjYXNlLCBjb21wbGV0ZWx5IHVubmVjZXNzYXJ5LCBidXQgaXMgdXNlZnVsIHRvIGtub3cuIEluIHRoZSBgTVRjYXJzYCBkYXRhc2V0IHNvbWUgb2YgdGhlIHZhbHVlcyBhcmUgbm90IHZlcnkgaW50dWl0aXZlLiBUcmFuc21pc3Npb24gdHlwZSBpcyBkZW5vdGVkIGJ5IGEgbnVtZXJpYyB2YWx1ZSB3aGVyZSBpdCBtaWdodCBiZSBlYXNpZXIgdG8gcmVhZCBpZiB0aG9zZSB2YWx1ZXMgd2VyZSBhYmJyZXZpYXRpb25zIGxpa2UgJ2F1dG8nIGFuZCAnbWFuJy4gKEkgYWx3YXlzIGZvcmdldCB3aGF0IGB2c2AgbWVhbnMsIC0gSSB0aGluayBpdCBpcyB0aGUgY3lsaW5kZXIgYXJyYW5nZW1lbnQgaW5saW5lIGN5bGluZGVycyBvciBub3QuKSBMZXQncyBjaGFuZ2UgdGhlIG51bWVyaWMgdmFsdWVzIGluIGBNVGNhcnMkYW1gIHRvIHN0cmluZ3MgJ2F1dG8nIGFuZCAnbWFuJy4gQW5kIGxldCdzIGRvIHRoaXMgYSBzbGlnaHRseSBzYWZlciB3YXkuCgpgYGB7cn0KeHggPC0gTVRjYXJzJGFtCnh4W3h4ID09IDBdIDwtICJhdXRvIgp4eFtNVGNhcnMkYW0gPT0gMV0gPC0gIm1hbiIgIyBUaGlzIGlzIGEgYml0IG9kZCB0byBkbywgYnV0IEkgZGlkIGl0IHRvIGlsbHVzdHJhdGUgYSBwb2ludC4gUGxlYXNlIGFzaywgd2h5PwpNVGNhcnMkYW1DaGFyIDwtIHh4ICMgV2hhdCBkaWQgdGhpcyBkbz8Kcm0oeHgpCmlkZW50aWNhbCgoTVRjYXJzJGFtID09IDApLCAoTVRjYXJzJGFtQ2hhciA9PSAiYXV0byIpKQp0YWJsZSgoTVRjYXJzJGFtID09IDApID09IChNVGNhcnMkYW1DaGFyID09ICJhdXRvIikpCmBgYAoKV2hhdCBpZiB3ZSB3YW50ZWQgdG8gY3JlYXRlIGZhY3RvciB2YXJpYWJsZSBmb3IgZW5naW5lIHNpemUgY2FsbGVkIGBlbmdfc2l6ZWAsIHdoZXJlIGVuZ2luZXMgd2l0aCBhIGRpc3BsYWNlbWVudCBvbmUgc3RhbmRhcmQgZGV2aWF0aW9uIGFib3ZlIHRoYW4gdGhlIG1lYW4gYXJlIGNsYXNzZWQgImJpZyIsIGJlbG93ICJzbWFsbCIsIGFuZCB3aXRoaW4gImF2ciIgZm9yICdhdmVyYWdlJz8gV2UgX2NvdWxkXyBkbyB0aGlzIHdpdGggYSAnZm9yJyBsb29wIGFuZCBzb21lICdpZi9lbHNlJyBzdGF0ZW1lbnRzIGxpa2UgYmVsb3cuCgpgYGB7cn0KeHggPC0gY2hhcmFjdGVyKCkKZm9yIChpIGluIDE6bnJvdyhNVGNhcnMpKSB7CglpZiAoTVRjYXJzJGRpc3BbaV0gPiAobWVhbihNVGNhcnMkZGlzcCkgKyBzZChNVGNhcnMkZGlzcCkpKSB7CgkJeHhbaV0gPC0gImJpZyIKCX0gZWxzZSBpZiAoTVRjYXJzJGRpc3BbaV0gPCAobWVhbihNVGNhcnMkZGlzcCkgLSBzZChNVGNhcnMkZGlzcCkpKSB7CgkJeHhbaV0gPC0gInNtYWxsIgoJfSBlbHNlIHsKCQl4eFtpXSA8LSAiYXZyIgoJfQp9Ck1UY2FycyRlbmdfc2l6ZSA8LSBmYWN0b3IoeHgsIGxldmVscyA9IGMoInNtYWxsIiwgImF2ciIsICJiaWciKSwgb3JkZXJlZCA9IFRSVUUpCnN0cihNVGNhcnMkZW5nX3NpemUpCmBgYApJdCBpcyBnb29kIHRvIGtub3cgYWJvdXQgaG93IHRvIHdyaXRlICdmb3InLCAnd2hpbGUnIGFuZCAnaWYnIHN0YXRlbWVudHMgd2hlbiB5b3UgbGVhcm4gYWJvdXQgcHJvZ3JhbW1pbmcgaW4gZ2VuZXJhbCwgYnV0IEkgd2FudCB0byB0ZWFjaCB5b3UgaG93IHRvIHVzZSBSLiBUaGUgd2F5IHRvIHVzZSBSIHByb3Blcmx5IGlzIHRvIGF2b2lkIHVzaW5nIHRoZXNlIHdoZW5ldmVyIHBvc3NpYmxlLiBBbiBleHBlcmllbmNlZCBSIHByb2dyYW1tZXIgd291bGQga25vdyB0aGVyZSBpcyBhIGZ1bmN0aW9uIHRoYXQgZG9lcyB0aGlzIGZvciB5b3UgKG9uZSB0aGF0IEkgc2hvdWxkIGhhdmUsIGJ1dCBkaWQgbm90IGtub3cgYWJvdXQgZm9yIHRoZSBmaXJzdCB0d28geWVhcnMgSSB1c2VkIFIpLiBJdCBpcyBjYWxsZWQgYGlmZWxzZSgpYC4KCmBgYHtyfQojIGlmZWxzZSgiSXMgdGhpcyBUUlVFPyIsICJZZXMsIHNvID0gIiwgIk5vLCBzbyA9ICIpCnh4IDwtIGlmZWxzZShNVGNhcnMkZGlzcCA+IChtZWFuKE1UY2FycyRkaXNwKSArIHNkKE1UY2FycyRkaXNwKSksICJiaWciLCBpZmVsc2UoTVRjYXJzJGRpc3AgPCAobWVhbihNVGNhcnMkZGlzcCkgLSBzZChNVGNhcnMkZGlzcCkpLCAic21hbGwiLCAiYXZyIikpCnh4IDwtIGZhY3Rvcih4eCwgbGV2ZWxzID0gYygic21hbGwiLCAiYXZyIiwgImJpZyIpLCBvcmRlcmVkID0gVFJVRSkKaWRlbnRpY2FsKHh4LCBNVGNhcnMkZW5nX3NpemUpCmBgYApOb3Qgb25seSBpcyBgaWZlbHNlKClgIGVhc2llciB0byB3cml0ZSBhbmQgcmVhZCAoaS5lLiwgZWxlZ2FudCksIGl0IGlzIGFsc28gbXVjaCwgbXVjaCwgbXVjaCwgbXVjaCBmYXN0ZXIgdGhhbiB1c2luZyBhICdmb3InIGxvb3AuIFIgaXMgbm90IGEgX2Zhc3RfIHByb2dyYW1taW5nIGxhbmd1YWdlIGJ5IGFueSBtZWFucy4gV2hpbGUgYGlmZWxzZSgpYCBydW5zIHdpdGhpbiBSLCBtYW55IGZ1bmN0aW9ucyBsaWtlIHRob3NlIGluIHRoZSBgZHBseXJgIHBhY2thZ2UgYWN0dWFsbHkgcnVuIG91dHNpZGUgb2YgUiwgaW4gdGhpcyBjYXNlIGluIEMrKy4gRmluZGluZyBhIHNwZWNpZmljIGZ1bmN0aW9uIGluIGFub3RoZXIgUiBwYWNrYWdlIGNhbiBzb21ldGltZXMgdHVybiBhbiBob3VyIG9mIGNvbXB1dGF0aW9uYWwgdGltZSBpbnRvIHNlY29uZHMsIGxpdGVyYWxseS4gKFlvdSBjYW4gd3JhcCBhIGZ1bmN0aW9uIGNhbGwgaW4gYHN5c3RlbS50aW1lKClgIHRvIGNvbXBhcmUgY29tcHV0YXRpb24gc3BlZWRzKS4KCldoZW4gd2UgY2hhbmdlZCB0aGUgemVyb3MgdG8gImF1dG8iLCB0aGUgdmVjdG9yIGB4eGAgd2FzIGNoYW5nZWQgaW50byBhIGNoYXJhY3RlciB2ZWN0b3IuIElmIHdlIHRyeSB0byBkbyB0aGlzIHdpdGggZmFjdG9ycyB3ZSB3aWxsIGhhdmUgdHJvdWJsZS4KYGBge3J9Cnh4IDwtIE1UY2FycyRlbmdfc2l6ZQp4eFt4eCA9PSAiYmlnIl0gPC0gImxhcmdlIgpgYGAKVGhlIHNvbHV0aW9uIGhlcmUgaXMgdG8gdXNlIGEgbGluZSBvZiBjb2RlIGxpa2UgdGhpczoKYGBge3J9Cnh4IDwtIE1UY2FycyRlbmdfc2l6ZQpsZXZlbHMoeHgpW2xldmVscyh4eCkgPT0gImJpZyJdIDwtICJsYXJnZSIKYGBgCk9mIGNvdXJzZSwgd2UgY291bGQgYWxzbyBqdXN0IHR1cm4gYHh4YCBpbnRvIGEgY2hhcmFjdGVyIHZlY3RvciBhbmQgbW9kaWZ5IGl0IHRoYXQgd2F5IHRvby4KCkZhY3RvcnMgYWxzbyBoYXZlIGFub3RoZXIgYW5ub3lpbmcgaGFiaXQuIFRoZXkgd2lsbCBub3QgZ28gYXdheSBldmVuIGFmdGVyIHRoZXkgYXJlIHJlbW92ZWQuIExldCdzIHByZXRlbmQgd2Ugb25seSB3YW50IHRoZSBzdW1tZXIgbW9udGhzIGZyb20gJ0xlaXN1cmVBdHRlbmRgLgpgYGB7cn0KTGVpc0F0dFN1bW1lciA8LSBmaWx0ZXIoTGVpc3VyZUF0dGVuZCwgTU9OVEggJWluJSBjKCJKVUxZIiwgIkFVR1VTVCIsICJTRVBURU1CRVIiKSkKc3RyKExlaXNBdHRTdW1tZXIkTU9OVEgpCmBgYApXZSBzdGlsbCBoYXZlIGEgZmFjdG9yIHdpdGggMTIgbGV2ZWxzLiBUaGUgc29sdXRpb24gaXMgdG8gdXNlIGBkcm9wbGV2ZWxzKClgLiBXZSBjb3VsZCBoYXZlIGp1c3QgYWRkZWQgdGhpcyBvbnRvIHRoZSB0YWlsIG9mIHRoZSBvcmlnaW5hbCBgZmlsdGVyKClgIGNhbGwuCmBgYHtyfQpMZWlzQXR0U3VtbWVyIDwtIGRyb3BsZXZlbHMoZmlsdGVyKExlaXN1cmVBdHRlbmQsIE1PTlRIICVpbiUgYygiSlVMWSIsICJBVUdVU1QiLCAiU0VQVEVNQkVSIikpKQpzdHIoTGVpc0F0dFN1bW1lciRNT05USCkKYGBgCgojIyMjIFRoZSBQaXBlOiBgJT4lYAoKVGhlIHBpcGUgd2FzIGFuIGlubm92YXRpb24gb3JpZ2luYWxseSBmcm9tIHRoZSBgbWFncml0dHJgIHBhY2thZ2UsIGJ1dCBpcyBub3cgZnJlcXVlbnRseSB1c2VkIGluIG1hbnkgcGFja2FnZXMgZXNwZWNpYWxseSBgZHBseXJgIGFuZCBgdGlkeXJgLiBDb2RlIGNhbiBiZSBjb25jZXB0dWFsbHkgaGFyZCB0byByZWFkIGFzIGl0IHR5cGljYWxseSBiZWNvbWVzIG1vcmUgYW5kIG1vcmUgZW1iZWRkZWQsIG1lYW5pbmcgdGhhdCB0aGUgZmlyc3QgdGhpbmcgeW91ciBjb2RlIGlzIGRvaW5nIGlzIHVzdWFsbHkgaW4gdGhlIGNlbnRyZSB3aXRoaW4gdGhlIGlubmVybW9zdCAiKCkiLiBXaXRoIHRoZSBwaXBlIG9wZXJhdG9yLCB5b3UgY2FuIG1vc3RseSB0aGluayBvZiB5b3VyIGNvZGUgdG8gbWVhbiAiVGFrZSB0aGVzZSBkYXRhLCBhbmQgdGhlbiAoJT4lKSwgYW5kIHRoZW4gKCU+JSkuLi4gIi4gSG93IHdvdWxkIHdlIHJlLXdyaXRlIHRoZSBjb2RlIGFib3ZlIHdpdGhvdXQgdGhlIHBpcGU/CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpMZWlzQXR0U3VtbWVyIDwtIApgYGAKCiMjIE5leHQ6IFBsb3R0aW5nIFlvdXIgRGF0YQoKCgo=