1 Setup & Configuration

  • The code below will load the libraries you will need for this tutorial. If you do not have one, please install with install.packages("pkg"):
options(warnPartialMatchArgs = FALSE)  # don't want these warnings
options(width = 100)
library(tibble)           # special type of data frame
library(magrittr)         # pipes
library(dplyr)            # data manipulation
library(ggplot2)          # pretty plots
library(tidyr)            # reshape data frames; mostly for ggplots
library(lubridate)        # working with dates/times
library(nycflights13)     # data set of flight times, departures, and arrivals
  • The data we will be using today is available via the nycflights13 package.
  • R environment:
    • Now is a good time to check that your version of R is up-to-date. If you are not running version R >= 3.2.3 it is time to update. Use a function from the devtools package to check your session info:
devtools::session_info()

2 What is Data Wrangling?

Data wrangling, sometimes referred to as data munging, is the process of transforming and mapping data from one “raw” data form into another format with the intent of making it more appropriate and valuable for a variety of downstream purposes such as analytics. A data wrangler is a person who performs these transformation operations (see wiki).

2.0.1 What’s this tutorial about?


3 Pipes (magrittr) Pipe

This is an idea that has been around for a long time, particularly in the Linux/Unix ecosystem, where it is denoted with the “|” symbol:

  • Complex tasks can be accomplished by stringing together many simple tools - syntax is important for efficiency / readability
  • The idea of pipes is that the result of the previous command is passed to the next command, which allows many tools to be strung together - from left to right.
  • In R, magrittr introduces the %>% pipe operator which runs the expression of the left and passes the result as the first argument to expression on the right.

You can think about the following sequence of actions:

1. find key
2. unlock car
3. start car
4. drive to school
5. park.

Expressed as a set of nested functions in R pseudocode this would look like:

park(drive(start_car(find("keys")), to="campus"))

Writing it out using pipes give it a more natural (and easier to read!) structure:

find("keys") %>%
    start_car() %>%
    drive(to="campus") %>%
    park()

4 Approaches

All of the following are fine, it comes down to personal preference (and IMHO, readability):

# Nested:
h( g( f(x), y = 1), z = 1 )

# Piped:
f(x) %>% g(y = 1) %>% h(z = 1)

# Intermediate:
res <- f(x)
res <- g(res, y = 1)
res <- h(res, z = 1)

But what about other arguments? Sometimes we want to send our results to an function argument other than first one or we want to use the previous result for multiple arguments. In these cases we can refer to the previous result using “.”.

data.frame(a = 1:3, b = 3:1) %>% lm(a ~ b, data = .)

Call:
lm(formula = a ~ b, data = .)

Coefficients:
(Intercept)            b  
          4           -1  
data.frame(a = 1:3, b = 3:1) %>% .[[1]]
[1] 1 2 3
data.frame(a = 1:3, b = 3:1) %>% .[[length(.)]]
[1] 3 2 1

5 Tibbles

5.1 Modern data frames

Hadley Wickham has a package that modifies data frames to be more modern, or as he calls them surly and lazy. That is they don’t change variable names or types, and don’t do partial matching; and they complain more (e.g. when a variable does not exist). This forces you to confront problems earlier, typically leading to cleaner, more expressive code. Tibbles also have an enhanced print() method which makes them easier to use with large datasets containing complex objects.

class(iris)
[1] "data.frame"
tbl_iris <- as.tibble(iris)
class(tbl_iris)
[1] "tbl_df"     "tbl"        "data.frame"

5.2 Printing

tbl_iris

5.3 Lazy tibbles

tbl_iris[1, ]
tbl_iris[, "Species"]

5.4 Lazy tibbles (factors)

tibble(x = 1:3, y = c("A", "B", "C"))   # respects input class by default; character
# Annoyingly, the default of `data.frame()` is stringsAsFactors = TRUE
data.frame(x = 1:3, y = c("A", "B", "C"), stringsAsFactors = TRUE)   # character -> factor class

5.5 Surly Tibbles

tbl_iris[, "Name"]      # errors out; invalid index
Error: Column `Name` not found
tbl_iris[["Name"]]      # same as above; different syntax
NULL
iris$Name               # returns NULL
NULL
tbl_iris$Name           # returns NULL (with explanation!)
Unknown or uninitialised column: 'Name'.
NULL
tbl_iris[160, ]         # exceeded index; returns 1 row data frame of NAs
head(iris$Spec)      # partial `$` match; not always a good thing!
[1] setosa setosa setosa setosa setosa setosa
Levels: setosa versicolor virginica
head(tbl_iris$Spec)  # partial `$` match complains with warning(), as it should!
Unknown or uninitialised column: 'Spec'.
NULL

6 dplyr

6.1 A Grammar of Data Manipulation

The dplyr package is based on the concepts of functions as verbs that manipulate data frames.

Single data frame functions / verbs:

  • filter(): pick rows matching criteria
  • slice(): pick rows using index(es)
  • select(): pick and/or reorder columns by name
  • pull(): grab a column as a vector
  • rename(): rename specific columns
  • arrange(): reorder rows
  • mutate(): add new variables
  • transmute(): create new data frame with variables
  • distinct(): filter for unique rows
  • sample_n() / sample_frac(): randomly sample rows
  • summarise(): reduce variables to values
  • … and many more

6.2 dplyr rules for functions

1. First argument is always a data frame
2. Subsequent arguments say what to do with that data frame
3. Always return a data frame
4. Do not modify in place
5. Performance via lazy evaluation

7 Example Data

We will demonstrate dplyr functionality using the nycflights13 data which contains data on flights departing from New York City. It was written by Hadley Wickham and includes airline on-time data for all flights departing NYC in 2013 as well as useful metadata on airlines, airports, weather, and planes (see ?nycflights13).

7.1 Structure of flights

flights %<>% as.tibble    # 'rebound' pipe; aka compound assignment
str(flights)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   336776 obs. of  19 variables:
 $ year          : int  2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
 $ month         : int  1 1 1 1 1 1 1 1 1 1 ...
 $ day           : int  1 1 1 1 1 1 1 1 1 1 ...
 $ dep_time      : int  517 533 542 544 554 554 555 557 557 558 ...
 $ sched_dep_time: int  515 529 540 545 600 558 600 600 600 600 ...
 $ dep_delay     : num  2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
 $ arr_time      : int  830 850 923 1004 812 740 913 709 838 753 ...
 $ sched_arr_time: int  819 830 850 1022 837 728 854 723 846 745 ...
 $ arr_delay     : num  11 20 33 -18 -25 12 19 -14 -8 8 ...
 $ carrier       : chr  "UA" "UA" "AA" "B6" ...
 $ flight        : int  1545 1714 1141 725 461 1696 507 5708 79 301 ...
 $ tailnum       : chr  "N14228" "N24211" "N619AA" "N804JB" ...
 $ origin        : chr  "EWR" "LGA" "JFK" "JFK" ...
 $ dest          : chr  "IAH" "IAH" "MIA" "BQN" ...
 $ air_time      : num  227 227 160 183 116 150 158 53 140 138 ...
 $ distance      : num  1400 1416 1089 1576 762 ...
 $ hour          : num  5 5 5 5 6 5 6 6 6 6 ...
 $ minute        : num  15 29 40 45 0 58 0 0 0 0 ...
 $ time_hour     : POSIXct, format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...

7.2 Look at full tibble data frame

flights

8 dplyr Verbs

8.1 March flights dplyr::filter

flights %>% filter(month == 3)

8.2 First week of March flights dplyr::filter

flights %>% filter(month == 3, day <= 7)

8.3 Flights to LAX or RDU in March dplyr::filter

flights %>% filter(dest == "LAX" | dest == "RDU", month==3)

8.4 First 10 flights dplyr::slice

flights %>% slice(1:10)

8.5 Last 5 flights dplyr::slice

flights %>% slice((n() - 5):n())

8.6 Select columns dplyr::select

flights %>% select(year, month, day)

8.7 Exclude columns dplyr::select

flights %>% select(-year, -month, -day)

8.8 Select ranges dplyr::select

flights %>% select(year:day)

8.9 Exclude ranges dplyr::select

flights %>% select(-(year:day))

8.10 Select matching dplyr::select

flights %>% select(contains("dep"),
                   contains("arr"))

flights %>% select(starts_with("dep"),
                   starts_with("arr"))

8.11 Reorder (Move) dplyr::select

flights %>% select(origin, everything())    # move `origin` to front
flights %>% select(-origin, everything())   # move `origin` to back
# These are alternatives to the above; which may seem impossible at first
# See below for why this works (`select()` vs `rename()`)
flights %>% select(origin, -origin)   # move `origin` to front
flights %>% select(-origin, origin)   # move `origin` to back

8.12 Using dplyr::pull

names(flights)
 [1] "year"           "month"          "day"            "dep_time"      
 [5] "sched_dep_time" "dep_delay"      "arr_time"       "sched_arr_time"
 [9] "arr_delay"      "carrier"        "flight"         "tailnum"       
[13] "origin"         "dest"           "air_time"       "distance"      
[17] "hour"           "minute"         "time_hour"     
flights %>% pull("year") %>% head()
[1] 2013 2013 2013 2013 2013 2013
flights %>% pull(1) %>% head()
[1] 2013 2013 2013 2013 2013 2013
flights %>% pull(-1) %>% head()
[1] "2013-01-01 05:00:00 EST" "2013-01-01 05:00:00 EST" "2013-01-01 05:00:00 EST"
[4] "2013-01-01 05:00:00 EST" "2013-01-01 06:00:00 EST" "2013-01-01 05:00:00 EST"

8.13 Other helpers:

  • starts_with()
  • ends_with()
  • contains()
  • matches()
  • num_range()
  • one_of()
  • everything()
  • last_col()
  • The above “helpers” can be found in the new-ish ?tidyselect package which supports the dplyr package.

8.14 Change column names dplyr::rename

flights %>% rename(tail_number = tailnum)

8.15 select() vs. rename()

You can do some interesting things with dplyr::select(), such as renaming and selecting simultaneously.

flights %>% select(tail_number = tailnum)

This might be confusing, but arguments are evaluated only at the end of the call, and in the order of the arguments (i.e. ...). So you can refer to variables that have been removed or renamed by their original names. This is best seen in an example:

# Rename tailnum -> tail_number
# Then select the new variable `tail_number`
# Then select all variables except `tailnum` (but referring to the original name!)
flights %>% select(tail_number = tailnum, -tailnum)   # no rows
# Select all variables except `tailnum`
# Then rename tailnum -> tail_number
# This 'adds back the variable with a new name
# And reorders it to the final column
flights %>% select(-tailnum, tail_number = tailnum)   # tailnum -> tail_number + move to end

8.16 Sort data dplyr::arrange

flights %>% filter(month == 3, day == 2) %>% arrange(origin, dest)

8.17 Descending order dplyr::arrange

flights %>% filter(month == 3, day == 2) %>%
  arrange(desc(origin), dest) %>% select(origin, dest, tailnum)
NA

8.18 Modify columns dplyr::mutate

flights %>%
   select(1:3) %>%
   mutate(date = paste(month, day, year, sep="/") %>% mdy())

8.19 Create new tibble from existing columns dplyr::transmute

flights %>%
   select(1:3) %>%
   transmute(date = paste(month, day, year, sep="/") %>% mdy())

8.20 Find unique rows dplyr::distinct

flights %>%
   select(origin, dest) %>%
   distinct() %>%
   arrange(origin, dest)

8.21 Sample rows dplyr::sample_n

flights %>% sample_n(10)

8.22 Sample rows dplyr::sample_frac

flights %>% sample_frac(0.001)

8.23 Summary via dplyr::summarise

flights %>% mutate(date = paste(month, day, year, sep = "/") %>% mdy()) %>%
   summarize(n(), min(date), max(date))

8.24 Set Groupings dplyr::group_by

# grouping parameters stored in attributes
flights %>% group_by(origin)    # doesn't appear any different
flights %>% class()             # pre-grouping is standard `tibble`
[1] "tbl_df"     "tbl"        "data.frame"
flights %>% group_by(origin) %>% class()  # returned object is a new class `grouped_df`
[1] "grouped_df" "tbl_df"     "tbl"        "data.frame"

8.25 Summarize groups with dplyr::group_by() + dplyr::summarise()

# the `lubridate` package (tidyverse) helps working with dates
flights %>%
  group_by(origin) %>%
  mutate(date = paste(month, day, year, sep = "/") %>% lubridate::mdy()) %>%
  summarize(n = n(), min = min(date), median = median(date), max = max(date))

9 Plotting

No data wrangling and/or data exploration is complete without at least some visual exploration of the data. Asking yourself: what do I have here? how is my data (particularly my class variable or response variable) distributed? Are there any missing values? Negative values?

par(mfrow = c(2, 2))                             # make 2x2 grid for plots
par(mgp = c(2, 0.75, 0), mar = c(3, 4, 3, 1))    # graphics settings; squeeze margins
lapply(names(iris)[-5], function(class)
   boxplot(split(iris[[class]], iris$Species), main = class, col = 1:3)) %>%
   invisible()   # pipe to invisible to suppress output

9.1 The same via

thm <- theme_bw() +
  theme(
    panel.background = element_rect(fill = "transparent", colour = NA),
    plot.background = element_rect(fill = "transparent", colour = NA),
    legend.position = "top",
    legend.background = element_rect(fill = "transparent", colour = NA),
    legend.key = element_rect(fill = "transparent", colour = NA),
    plot.title = element_text(hjust = 0.5)
  )
theme_set(thm)

iris %>%
  tidyr::gather(key = "Feature", value = "cm", -Species) %>%
  mutate(Feature = gsub("\\.", " ", Feature)) %>%
  ggplot(aes(y = cm, x = Species, fill = Species)) +
  geom_boxplot(color = "#1F3552", alpha = 0.75, size = 0.5) +
  scale_x_discrete(name = "Species") +
  ggtitle("Overall Title") +
  facet_wrap(~Feature, ncol = 2) +
  NULL

9.2 Pairwise plots

Very useful for visually inspecting variables for patterns of interest. You will typically need to know your outcome of interest a priori, i.e. supervised analysis, or you will not know how to color the points.

plot(iris[, -5], col=iris$Species)

Unfortunately there is no simple equivelant for pairwise plots in ggplot2, so we are forced to look at the variabels one at a time, or write our own wrapper function (homework??):

ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  geom_point(size = 2)


10 Exercises

1. Which destinations have the highest average delays?

2. How many flights to Los Angeles (LAX) did each of the legacy carriers (AA, UA, DL or US) have in May from JFK, and what was their average duration?

3. Which plane (check the tail number) flew out of each New York airport the most?

4. What was the shortest flight out of each airport in terms of distance? In terms of duration?

5. Which date should you fly on if you want to have the lowest possible average departure delay? What about arrival delay?

10.0.1 Plot with

6. Create a time series plot of each of the legacy carriers average departure delay by day and origin airport. It should look something like this:

10.0.2 Now Do It Yourself

7. You have probably had enough of New York City flight times. Now take a look at either the `mtcars` or `mtcars2` data set and execute many of the same commands above while exploring this new, different data set.

11 Merging Data

No data wrangling tutorial would be complete without a section on merging data. This will happen often. Data will come to you, perhaps from various sources, and you must combine the records (cases, samples, rows, etc.) based on a common indicator variable, usually some sort of key identifier variable (e.g. a name, a patient ID, etc.).

11.1 Data manipulation is not complete without merging information

Two table functions / verbs, all functions have the form f(a, b):

Function What it does
left_join Join matching rows from b to a, preserving all rows of a
right_join Join matching rows from a to b, preserving all rows of b
inner_join Join data, preserving only rows with keys in both a and b
full_join Join data, preserving all rows in both a and b
semi_join Subset rows in a that have a match in b
anti_join Subset rows in a that do not have a match in b

11.2 Joining Data

addr <- data.frame(name = c("Alice", "Bob",
                            "Carol", "dave", "Eve"),
                   email = c("alice@company.com",
                             "bob@company.com",
                             "carol@company.com",
                             "dave@company.com",
                             "eve@company.com"),
                   stringsAsFactors = FALSE)
addr
phone <- data.frame(name = c("Bob", "Carol",
                             "Eve", "Eve", "Frank"),
                    phone = c("919 555-1111",
                             "919 555-2222",
                             "919 555-3333",
                             "310 555-3333",
                             "919 555-4444"),
                    stringsAsFactors = FALSE)
phone

11.3 Outer Join

In dplyr:

full_join(addr, phone)
Joining, by = "name"

In base R:

merge(addr, phone, all = TRUE)

11.4 Inner Join

In dplyr:

inner_join(addr, phone)
Joining, by = "name"

In base R:

merge(addr, phone, all = FALSE)

11.5 Left Join

In dplyr:

left_join(addr, phone)
Joining, by = "name"

In base R:

merge(addr, phone, all.x = TRUE)

11.6 Right Join

In dplyr:

right_join(addr, phone)
Joining, by = "name"

In base R:

merge(addr, phone, all.y = TRUE)

11.7 Semi and Anti Joins

semi_join(addr, phone)   # semi
Joining, by = "name"
anti_join(addr, phone)   # anti
Joining, by = "name"

11.8 Special case: many-to-many relationships

addr2 <- data.frame(name = c("Alice", "Alice", "Bob", "Bob"),
                    email= c("alice@company.com",
                             "alice@gmail.com",
                             "bob@company.com",
                             "bob@hotmail.com"),
                    stringsAsFactors = FALSE)
phone2 <- data.frame(name = c("Alice", "Alice", "Bob", "Bob"),
                     phone = c("919 555-1111", "310 555-2222",
                               "919 555-3333", "310 555-3333"),
                     stringsAsFactors = FALSE)

In dplyr:

full_join(addr2, phone2, by = "name")

In base R:

merge(addr2, phone2)

11.9 Example: Enhancing NYC Flight Data

The nycflights13 package also contains additional information about:

Dataset Description
weather hourly meterological data for each airport
planes construction information about each plane
airports airport names and locations
airlines translation between two letter carrier codes and names

11.10 Weather and Flight Delays

Let’s take a quick look at the weather data, with an eye towards examining how it might affect things like flight departure delays.

weather

11.11 Join by?

Variable defintions in the weather dataset:

Variable Description
origin Weather station. Named origin to faciliate merging with flights data
year, month, day, hour Time of recording
temp, dewp Temperature and dewpoint in Fahrenheit
humid Relative humidity
wind_dir, wind_speed, wind_gust Wind direction (in degrees), speed (in mph), and gust speed (in mph)
precip Preciptation, in inches
pressure Sea level pressure in millibars
visib Visibility in miles

intersect(names(weather), names(flights))
[1] "origin"    "year"      "month"     "day"       "hour"      "time_hour"

11.12 Joining flights and weather

When joining these two data frames I will use an inner join (because I only want the flights that have the weather data, and I only want weather data when there was a flight)

Let’s make a ggplot of the delay time vs. visibility:

flightsw %>%
  ggplot(aes(x = visib, y = dep_delay)) +
  geom_jitter(alpha = 0.5) +    # shake up the points a bit for better viewing
  NULL

Let’s take a closer look at the dependent variable -> visibility:

flightsw %>%
  ggplot(aes(x = visib)) +
  geom_histogram(fill = "blue")

table(flightsw$visib)

     0   0.06   0.12   0.25    0.5   0.75      1   1.25    1.5   1.75      2    2.5 
   110     93    440   1358   1478    496   1474    208   1692    156   3199   2434 
     3      4      5      6      7      8      9     10 
  3624   2253   4809   6260   7347   7614  11436 278739 

Wow! The vast majority of flights are under clear skies with > 10 miles visibility. Good to know.

flightsw %>%
  ggplot(aes(x = visib, y = dep_delay)) +
  geom_jitter(shape = 21, color = "blue", fill = "gray",
              size = 1.5, width = 0.2, alpha = 0.5) +
  geom_smooth(color = "red") +
  NULL

Notice a few things:

  • There are many negative departure time values (early!)
  • The departure time distribution is heavily skewed towards zero
  • This makes sense since delays are much more likely than early departures, which means the many points overlap near zero.
  • These values stop us from plotting the y-axis on a log-scale (which might give us a better view of the data),
  • A boxplot (see here about boxplots) might give us a better representation of the data .
  • Let’s remove the negative departure times and replot as a boxplot:
flightsw %>%
  filter(dep_delay > 0)  %>%
  mutate(visib %<>% factor) %>%
  ggplot(aes(x = visib, y = dep_delay, group = visib)) +
  geom_boxplot(fill = "blue", color = "gray") +
  coord_trans(y = "log10") +
  xlab("Visibility") +
  ylab("Departure Delay") +
  NULL


12 More Exercises

  1. Check some of the other weather variables (e.g. temp, wind_speed, etc) and see if you can find any relationship between them and departure delay (dep_delay).

  2. Merge the flights data with the planes data set (pay attention to what columns are being used for the join). Are older planes more likely to be delayed? What about planes from Airbus vs Boeing vs Embraer?


13 Acknowledgments

Above materials are derived in part from the following sources:


Created on 2018-11-14 by the Rmarkdown package (v1.10) and R version 3.5.1 (2018-07-02).

LS0tCnRpdGxlOiAiRGF0YSBXcmFuZ2xpbmcgaW4gdGhlIFRpZHl2ZXJzZSIKYXV0aG9yOiAiU2ltb24gVGF2ZW5lciAmIFN0dSBGaWVsZCIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJWUgJUIgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBubwogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCnJhdGlvOiAnOToxNicKdGFibGVzOiB5ZXMKZm9udHNpemU6IDEycHQKLS0tCgoKCiMgU2V0dXAgJiBDb25maWd1cmF0aW9uCgoqIFRoZSBjb2RlIGJlbG93IHdpbGwgbG9hZCB0aGUgbGlicmFyaWVzIHlvdSB3aWxsIG5lZWQgZm9yIHRoaXMgdHV0b3JpYWwuCklmIHlvdSBkbyBub3QgaGF2ZSBvbmUsIHBsZWFzZSBpbnN0YWxsIHdpdGggYGluc3RhbGwucGFja2FnZXMoInBrZyIpYDoKYGBge3Igc2V0dXAsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpvcHRpb25zKHdhcm5QYXJ0aWFsTWF0Y2hBcmdzID0gRkFMU0UpICAjIGRvbid0IHdhbnQgdGhlc2Ugd2FybmluZ3MKb3B0aW9ucyh3aWR0aCA9IDEwMCkKbGlicmFyeSh0aWJibGUpICAgICAgICAgICAjIHNwZWNpYWwgdHlwZSBvZiBkYXRhIGZyYW1lCmxpYnJhcnkobWFncml0dHIpICAgICAgICAgIyBwaXBlcwpsaWJyYXJ5KGRwbHlyKSAgICAgICAgICAgICMgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShnZ3Bsb3QyKSAgICAgICAgICAjIHByZXR0eSBwbG90cwpsaWJyYXJ5KHRpZHlyKSAgICAgICAgICAgICMgcmVzaGFwZSBkYXRhIGZyYW1lczsgbW9zdGx5IGZvciBnZ3Bsb3RzCmxpYnJhcnkobHVicmlkYXRlKSAgICAgICAgIyB3b3JraW5nIHdpdGggZGF0ZXMvdGltZXMKbGlicmFyeShueWNmbGlnaHRzMTMpICAgICAjIGRhdGEgc2V0IG9mIGZsaWdodCB0aW1lcywgZGVwYXJ0dXJlcywgYW5kIGFycml2YWxzCmBgYAoKKiBUaGUgZGF0YSB3ZSB3aWxsIGJlIHVzaW5nIHRvZGF5IGlzIGF2YWlsYWJsZSB2aWEgdGhlIGBueWNmbGlnaHRzMTNgIHBhY2thZ2UuCiogUiBlbnZpcm9ubWVudDoKICAgICsgIE5vdyBpcyBhIGdvb2QgdGltZSB0byBjaGVjayB0aGF0IHlvdXIgdmVyc2lvbiBvZiBSIGlzIHVwLXRvLWRhdGUuIElmIHlvdSBhcmUgbm90IHJ1bm5pbmcgdmVyc2lvbiBSID49IDMuMi4zIGl0IGlzIHRpbWUgdG8gdXBkYXRlLiBVc2UgYSBmdW5jdGlvbiBmcm9tIHRoZSBgZGV2dG9vbHNgIHBhY2thZ2UgdG8gY2hlY2sgeW91ciBzZXNzaW9uIGluZm86CmBgYHtyLCBldmFsID0gRkFMU0V9CmRldnRvb2xzOjpzZXNzaW9uX2luZm8oKQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIFdoYXQgaXMgRGF0YSBXcmFuZ2xpbmc/CgoqKkRhdGEgd3JhbmdsaW5nKiosIHNvbWV0aW1lcyByZWZlcnJlZCB0byBhcyAqKmRhdGEgbXVuZ2luZyoqLCBpcyB0aGUgcHJvY2VzcyBvZiB0cmFuc2Zvcm1pbmcgYW5kICptYXBwaW5nKiBkYXRhIGZyb20gb25lICJyYXciIGRhdGEgZm9ybSBpbnRvIGFub3RoZXIgZm9ybWF0IHdpdGggdGhlIGludGVudCBvZiBtYWtpbmcgaXQgbW9yZSBhcHByb3ByaWF0ZSBhbmQgdmFsdWFibGUgZm9yIGEgdmFyaWV0eSBvZiBkb3duc3RyZWFtIHB1cnBvc2VzIHN1Y2ggYXMgYW5hbHl0aWNzLiBBIGRhdGEgd3JhbmdsZXIgaXMgYSBwZXJzb24gd2hvIHBlcmZvcm1zIHRoZXNlIHRyYW5zZm9ybWF0aW9uIG9wZXJhdGlvbnMgKHNlZSBbd2lraV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGF0YV93cmFuZ2xpbmcpKS4KCiMjIyBXaGF0J3MgdGhpcyB0dXRvcmlhbCBhYm91dD8KPGNlbnRlcj4KICA8aW1nIHNyYz0iaW1hZ2VzL2RhdGEtc2NpZW5jZS1leHBsb3JlLnBuZyI+CjwvY2VudGVyPgoKCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKCiMgUGlwZXMgKGBtYWdyaXR0cmApICA8aW1nIHNyYz0iaW1hZ2VzL21hZ3JpdHRyLnBuZyIgY2xhc3M9InRpdGxlLWhleCI+ICAhW1BpcGVdKGltYWdlcy9waXBlLmpwZWcpCgpUaGlzIGlzIGFuIGlkZWEgdGhhdCBoYXMgYmVlbiBhcm91bmQgZm9yIGEgbG9uZyB0aW1lLCBwYXJ0aWN1bGFybHkgaW4gdGhlIExpbnV4L1VuaXggZWNvc3lzdGVtLCB3aGVyZSBpdCBpcyBkZW5vdGVkIHdpdGggdGhlICJ8IiBzeW1ib2w6CgoqIENvbXBsZXggdGFza3MgY2FuIGJlIGFjY29tcGxpc2hlZCBieSBzdHJpbmdpbmcgdG9nZXRoZXIgbWFueSBzaW1wbGUgdG9vbHMgLSBzeW50YXggaXMgaW1wb3J0YW50IGZvciBlZmZpY2llbmN5IC8gcmVhZGFiaWxpdHkKKiBUaGUgaWRlYSBvZiBwaXBlcyBpcyB0aGF0IHRoZSByZXN1bHQgb2YgdGhlIHByZXZpb3VzIGNvbW1hbmQgaXMgcGFzc2VkIHRvIHRoZSBuZXh0IGNvbW1hbmQsIHdoaWNoIGFsbG93cyBtYW55IHRvb2xzIHRvIGJlIHN0cnVuZyB0b2dldGhlciAtIGZyb20gbGVmdCB0byByaWdodC4KKiBJbiBSLCBgbWFncml0dHJgIGludHJvZHVjZXMgdGhlIGAlPiVgIHBpcGUgb3BlcmF0b3Igd2hpY2ggcnVucyB0aGUgZXhwcmVzc2lvbiBvZiB0aGUgbGVmdCBhbmQgcGFzc2VzIHRoZSByZXN1bHQgYXMgdGhlICpmaXJzdCogYXJndW1lbnQgdG8gZXhwcmVzc2lvbiBvbiB0aGUgcmlnaHQuCgpZb3UgY2FuIHRoaW5rIGFib3V0IHRoZSBmb2xsb3dpbmcgc2VxdWVuY2Ugb2YgYWN0aW9uczoKCiAgICAxLiBmaW5kIGtleQogICAgMi4gdW5sb2NrIGNhcgogICAgMy4gc3RhcnQgY2FyCiAgICA0LiBkcml2ZSB0byBzY2hvb2wKICAgIDUuIHBhcmsuCgpFeHByZXNzZWQgYXMgYSBzZXQgb2YgbmVzdGVkIGZ1bmN0aW9ucyBpbiBSIGBwc2V1ZG9jb2RlYCB0aGlzIHdvdWxkIGxvb2sgbGlrZToKCmBgYHtyLCBldmFsID0gRkFMU0V9CnBhcmsoZHJpdmUoc3RhcnRfY2FyKGZpbmQoImtleXMiKSksIHRvPSJjYW1wdXMiKSkKYGBgCgpXcml0aW5nIGl0IG91dCB1c2luZyBwaXBlcyBnaXZlIGl0IGEgbW9yZSBuYXR1cmFsIChhbmQgZWFzaWVyIHRvIHJlYWQhKSBzdHJ1Y3R1cmU6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpmaW5kKCJrZXlzIikgJT4lCiAgICBzdGFydF9jYXIoKSAlPiUKICAgIGRyaXZlKHRvPSJjYW1wdXMiKSAlPiUKICAgIHBhcmsoKQpgYGAKCiMgQXBwcm9hY2hlcwoKQWxsIG9mIHRoZSBmb2xsb3dpbmcgYXJlIGZpbmUsIGl0IGNvbWVzIGRvd24gdG8gcGVyc29uYWwgcHJlZmVyZW5jZSAoYW5kIElNSE8sIHJlYWRhYmlsaXR5KToKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgTmVzdGVkOgpoKCBnKCBmKHgpLCB5ID0gMSksIHogPSAxICkKCiMgUGlwZWQ6CmYoeCkgJT4lIGcoeSA9IDEpICU+JSBoKHogPSAxKQoKIyBJbnRlcm1lZGlhdGU6CnJlcyA8LSBmKHgpCnJlcyA8LSBnKHJlcywgeSA9IDEpCnJlcyA8LSBoKHJlcywgeiA9IDEpCmBgYAoKCkJ1dCB3aGF0IGFib3V0IG90aGVyIGFyZ3VtZW50cz8gU29tZXRpbWVzIHdlIHdhbnQgdG8gc2VuZCBvdXIgcmVzdWx0cyB0byBhbiBmdW5jdGlvbiBhcmd1bWVudCBvdGhlciB0aGFuIGZpcnN0IG9uZSBvciB3ZSB3YW50IHRvIHVzZSB0aGUgcHJldmlvdXMgcmVzdWx0IGZvciBtdWx0aXBsZSBhcmd1bWVudHMuIEluIHRoZXNlIGNhc2VzIHdlIGNhbiByZWZlciB0byB0aGUgcHJldmlvdXMgcmVzdWx0IHVzaW5nICIuIi4KCmBgYHtyfQpkYXRhLmZyYW1lKGEgPSAxOjMsIGIgPSAzOjEpICU+JSBsbShhIH4gYiwgZGF0YSA9IC4pCmRhdGEuZnJhbWUoYSA9IDE6MywgYiA9IDM6MSkgJT4lIC5bWzFdXQpkYXRhLmZyYW1lKGEgPSAxOjMsIGIgPSAzOjEpICU+JSAuW1tsZW5ndGgoLildXQpgYGAKCgojIFRpYmJsZXMKIyMgTW9kZXJuIGRhdGEgZnJhbWVzCgpIYWRsZXkgV2lja2hhbSBoYXMgYSBwYWNrYWdlIHRoYXQgbW9kaWZpZXMgZGF0YSBmcmFtZXMgdG8gYmUgbW9yZSBtb2Rlcm4sCm9yIGFzIGhlIGNhbGxzIHRoZW0gKnN1cmx5KiBhbmQgKmxhenkqLiBUaGF0IGlzIHRoZXkgZG9uJ3QgY2hhbmdlIHZhcmlhYmxlCm5hbWVzIG9yIHR5cGVzLCBhbmQgZG9uJ3QgZG8gcGFydGlhbCBtYXRjaGluZzsgYW5kIHRoZXkgY29tcGxhaW4gbW9yZQooZS5nLiB3aGVuIGEgdmFyaWFibGUgZG9lcyBub3QgZXhpc3QpLiBUaGlzIGZvcmNlcyB5b3UgdG8gY29uZnJvbnQgcHJvYmxlbXMKZWFybGllciwgdHlwaWNhbGx5IGxlYWRpbmcgdG8gY2xlYW5lciwgbW9yZSBleHByZXNzaXZlIGNvZGUuClRpYmJsZXMgYWxzbyBoYXZlIGFuIGVuaGFuY2VkIGBwcmludCgpYCBtZXRob2Qgd2hpY2ggbWFrZXMgdGhlbSBlYXNpZXIgdG8KdXNlIHdpdGggbGFyZ2UgZGF0YXNldHMgY29udGFpbmluZyBjb21wbGV4IG9iamVjdHMuCgpgYGB7cn0KY2xhc3MoaXJpcykKdGJsX2lyaXMgPC0gYXMudGliYmxlKGlyaXMpCmNsYXNzKHRibF9pcmlzKQpgYGAKCiMjIFByaW50aW5nCmBgYHtyfQp0YmxfaXJpcwpgYGAKCiMjIExhenkgdGliYmxlcwpgYGB7cn0KdGJsX2lyaXNbMSwgXQp0YmxfaXJpc1ssICJTcGVjaWVzIl0KYGBgCgoKIyMgTGF6eSB0aWJibGVzIChmYWN0b3JzKQpgYGB7cn0KdGliYmxlKHggPSAxOjMsIHkgPSBjKCJBIiwgIkIiLCAiQyIpKSAgICMgcmVzcGVjdHMgaW5wdXQgY2xhc3MgYnkgZGVmYXVsdDsgY2hhcmFjdGVyCiMgQW5ub3lpbmdseSwgdGhlIGRlZmF1bHQgb2YgYGRhdGEuZnJhbWUoKWAgaXMgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUKZGF0YS5mcmFtZSh4ID0gMTozLCB5ID0gYygiQSIsICJCIiwgIkMiKSwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpICAgIyBjaGFyYWN0ZXIgLT4gZmFjdG9yIGNsYXNzCmBgYAoKCiMjIFN1cmx5IFRpYmJsZXMKYGBge3IgQmFkVGliYmxlLCBlcnJvciA9IFRSVUV9CnRibF9pcmlzWywgIk5hbWUiXSAgICAgICMgZXJyb3JzIG91dDsgaW52YWxpZCBpbmRleAp0YmxfaXJpc1tbIk5hbWUiXV0gICAgICAjIHNhbWUgYXMgYWJvdmU7IGRpZmZlcmVudCBzeW50YXgKYGBgCgpgYGB7ciBCYWRUaWJibGUyfQppcmlzJE5hbWUgICAgICAgICAgICAgICAjIHJldHVybnMgTlVMTAp0YmxfaXJpcyROYW1lICAgICAgICAgICAjIHJldHVybnMgTlVMTCAod2l0aCBleHBsYW5hdGlvbiEpCmBgYAoKYGBge3IgQmFkVGliYmxlM30KdGJsX2lyaXNbMTYwLCBdICAgICAgICAgIyBleGNlZWRlZCBpbmRleDsgcmV0dXJucyAxIHJvdyBkYXRhIGZyYW1lIG9mIE5BcwpgYGAKCmBgYHtyIEJhZFRpYmJsZTR9CmhlYWQoaXJpcyRTcGVjKSAgICAgICMgcGFydGlhbCBgJGAgbWF0Y2g7IG5vdCBhbHdheXMgYSBnb29kIHRoaW5nIQpoZWFkKHRibF9pcmlzJFNwZWMpICAjIHBhcnRpYWwgYCRgIG1hdGNoIGNvbXBsYWlucyB3aXRoIHdhcm5pbmcoKSwgYXMgaXQgc2hvdWxkIQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIGBkcGx5cmAgPGltZyBzcmM9ImltYWdlcy9kcGx5ci5wbmciIGNsYXNzPSJ0aXRsZS1oZXgiPgojIyBBIEdyYW1tYXIgb2YgRGF0YSBNYW5pcHVsYXRpb24KClRoZSBgZHBseXJgIHBhY2thZ2UgaXMgYmFzZWQgb24gdGhlIGNvbmNlcHRzIG9mIGZ1bmN0aW9ucyBhcyB2ZXJicyB0aGF0IG1hbmlwdWxhdGUgZGF0YSBmcmFtZXMuCgpTaW5nbGUgZGF0YSBmcmFtZSBmdW5jdGlvbnMgLyB2ZXJiczoKCiogYGZpbHRlcigpYDogcGljayByb3dzIG1hdGNoaW5nIGNyaXRlcmlhCiogYHNsaWNlKClgOiBwaWNrIHJvd3MgdXNpbmcgaW5kZXgoZXMpCiogYHNlbGVjdCgpYDogcGljayBhbmQvb3IgcmVvcmRlciBjb2x1bW5zIGJ5IG5hbWUKKiBgcHVsbCgpYDogZ3JhYiBhIGNvbHVtbiBhcyBhIHZlY3RvcgoqIGByZW5hbWUoKWA6IHJlbmFtZSBzcGVjaWZpYyBjb2x1bW5zCiogYGFycmFuZ2UoKWA6IHJlb3JkZXIgcm93cwoqIGBtdXRhdGUoKWA6IGFkZCBuZXcgdmFyaWFibGVzCiogYHRyYW5zbXV0ZSgpYDogY3JlYXRlIG5ldyBkYXRhIGZyYW1lIHdpdGggdmFyaWFibGVzCiogYGRpc3RpbmN0KClgOiBmaWx0ZXIgZm9yIHVuaXF1ZSByb3dzCiogYHNhbXBsZV9uKCkgLyBzYW1wbGVfZnJhYygpYDogcmFuZG9tbHkgc2FtcGxlIHJvd3MKKiBgc3VtbWFyaXNlKClgOiByZWR1Y2UgdmFyaWFibGVzIHRvIHZhbHVlcwoqIC4uLiBhbmQgbWFueSBtb3JlCgoKIyMgZHBseXIgcnVsZXMgZm9yIGZ1bmN0aW9ucwoKICAgIDEuIEZpcnN0IGFyZ3VtZW50IGlzIGFsd2F5cyBhIGRhdGEgZnJhbWUKICAgIDIuIFN1YnNlcXVlbnQgYXJndW1lbnRzIHNheSB3aGF0IHRvIGRvIHdpdGggdGhhdCBkYXRhIGZyYW1lCiAgICAzLiBBbHdheXMgcmV0dXJuIGEgZGF0YSBmcmFtZQogICAgNC4gRG8gbm90IG1vZGlmeSBpbiBwbGFjZQogICAgNS4gUGVyZm9ybWFuY2UgdmlhIGxhenkgZXZhbHVhdGlvbgoKCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCiMgRXhhbXBsZSBEYXRhCldlIHdpbGwgZGVtb25zdHJhdGUgZHBseXIgZnVuY3Rpb25hbGl0eSB1c2luZyB0aGUgYG55Y2ZsaWdodHMxM2AgZGF0YSB3aGljaCBjb250YWlucyBkYXRhIG9uIGZsaWdodHMgZGVwYXJ0aW5nIGZyb20gTmV3IFlvcmsgQ2l0eS4gSXQgd2FzIHdyaXR0ZW4gYnkgSGFkbGV5IFdpY2toYW0gYW5kIGluY2x1ZGVzIGFpcmxpbmUgb24tdGltZSBkYXRhIGZvciBhbGwgZmxpZ2h0cyBkZXBhcnRpbmcgTllDIGluIDIwMTMgYXMgd2VsbCBhcyB1c2VmdWwgKm1ldGFkYXRhKiBvbiBhaXJsaW5lcywgYWlycG9ydHMsIHdlYXRoZXIsIGFuZCBwbGFuZXMgKHNlZSBgP255Y2ZsaWdodHMxM2ApLgoKIyMgU3RydWN0dXJlIG9mIGBmbGlnaHRzYAoKYGBge3IgZmxpZ2h0c19zdHVjdHVyZX0KZmxpZ2h0cyAlPD4lIGFzLnRpYmJsZSAgICAjICdyZWJvdW5kJyBwaXBlOyBha2EgY29tcG91bmQgYXNzaWdubWVudApzdHIoZmxpZ2h0cykKYGBgCgoKIyMgTG9vayBhdCBmdWxsIGB0aWJibGVgIGRhdGEgZnJhbWUKYGBge3IgZmxpZ2h0c190aWJibGV9CmZsaWdodHMKYGBgCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCiMgYGRwbHlyYCBWZXJicwojIyBNYXJjaCBmbGlnaHRzIGBkcGx5cjo6ZmlsdGVyYApgYGB7cn0KZmxpZ2h0cyAlPiUgZmlsdGVyKG1vbnRoID09IDMpCmBgYAoKIyMgRmlyc3Qgd2VlayBvZiBNYXJjaCBmbGlnaHRzIGBkcGx5cjo6ZmlsdGVyYApgYGB7cn0KZmxpZ2h0cyAlPiUgZmlsdGVyKG1vbnRoID09IDMsIGRheSA8PSA3KQpgYGAKCiMjIEZsaWdodHMgdG8gTEFYIG9yIFJEVSBpbiBNYXJjaCBgZHBseXI6OmZpbHRlcmAKYGBge3J9CmZsaWdodHMgJT4lIGZpbHRlcihkZXN0ID09ICJMQVgiIHwgZGVzdCA9PSAiUkRVIiwgbW9udGg9PTMpCmBgYAoKIyMgRmlyc3QgMTAgZmxpZ2h0cyBgZHBseXI6OnNsaWNlYApgYGB7cn0KZmxpZ2h0cyAlPiUgc2xpY2UoMToxMCkKYGBgCgojIyBMYXN0IDUgZmxpZ2h0cyBgZHBseXI6OnNsaWNlYApgYGB7cn0KZmxpZ2h0cyAlPiUgc2xpY2UoKG4oKSAtIDUpOm4oKSkKYGBgCgojIyBTZWxlY3QgY29sdW1ucyBgZHBseXI6OnNlbGVjdGAKYGBge3J9CmZsaWdodHMgJT4lIHNlbGVjdCh5ZWFyLCBtb250aCwgZGF5KQpgYGAKCiMjIEV4Y2x1ZGUgY29sdW1ucyBgZHBseXI6OnNlbGVjdGAKYGBge3J9CmZsaWdodHMgJT4lIHNlbGVjdCgteWVhciwgLW1vbnRoLCAtZGF5KQpgYGAKCiMjIFNlbGVjdCByYW5nZXMgYGRwbHlyOjpzZWxlY3RgCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3QoeWVhcjpkYXkpCmBgYAoKIyMgRXhjbHVkZSByYW5nZXMgYGRwbHlyOjpzZWxlY3RgCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3QoLSh5ZWFyOmRheSkpCmBgYAoKIyMgU2VsZWN0IG1hdGNoaW5nIGBkcGx5cjo6c2VsZWN0YApgYGB7cn0KZmxpZ2h0cyAlPiUgc2VsZWN0KGNvbnRhaW5zKCJkZXAiKSwKICAgICAgICAgICAgICAgICAgIGNvbnRhaW5zKCJhcnIiKSkKCmZsaWdodHMgJT4lIHNlbGVjdChzdGFydHNfd2l0aCgiZGVwIiksCiAgICAgICAgICAgICAgICAgICBzdGFydHNfd2l0aCgiYXJyIikpCmBgYAoKIyMgUmVvcmRlciAoTW92ZSkgYGRwbHlyOjpzZWxlY3RgCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3Qob3JpZ2luLCBldmVyeXRoaW5nKCkpICAgICMgbW92ZSBgb3JpZ2luYCB0byBmcm9udApmbGlnaHRzICU+JSBzZWxlY3QoLW9yaWdpbiwgZXZlcnl0aGluZygpKSAgICMgbW92ZSBgb3JpZ2luYCB0byBiYWNrCmBgYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgVGhlc2UgYXJlIGFsdGVybmF0aXZlcyB0byB0aGUgYWJvdmU7IHdoaWNoIG1heSBzZWVtIGltcG9zc2libGUgYXQgZmlyc3QKIyBTZWUgYmVsb3cgZm9yIHdoeSB0aGlzIHdvcmtzIChgc2VsZWN0KClgIHZzIGByZW5hbWUoKWApCmZsaWdodHMgJT4lIHNlbGVjdChvcmlnaW4sIC1vcmlnaW4pICAgIyBtb3ZlIGBvcmlnaW5gIHRvIGZyb250CmZsaWdodHMgJT4lIHNlbGVjdCgtb3JpZ2luLCBvcmlnaW4pICAgIyBtb3ZlIGBvcmlnaW5gIHRvIGJhY2sKYGBgCgoKIyMgVXNpbmcgYGRwbHlyOjpwdWxsYApgYGB7ciBwdWxsfQpuYW1lcyhmbGlnaHRzKQpmbGlnaHRzICU+JSBwdWxsKCJ5ZWFyIikgJT4lIGhlYWQoKQpmbGlnaHRzICU+JSBwdWxsKDEpICU+JSBoZWFkKCkKZmxpZ2h0cyAlPiUgcHVsbCgtMSkgJT4lIGhlYWQoKQpgYGAKCgojIyBPdGhlciBoZWxwZXJzOgogICAtIGBzdGFydHNfd2l0aCgpYAogICAtIGBlbmRzX3dpdGgoKWAKICAgLSBgY29udGFpbnMoKWAKICAgLSBgbWF0Y2hlcygpYAogICAtIGBudW1fcmFuZ2UoKWAKICAgLSBgb25lX29mKClgCiAgIC0gYGV2ZXJ5dGhpbmcoKWAKICAgLSBgbGFzdF9jb2woKWAKICAgLSBUaGUgYWJvdmUgImhlbHBlcnMiIGNhbiBiZSBmb3VuZCBpbiB0aGUgbmV3LWlzaCBgP3RpZHlzZWxlY3RgIHBhY2thZ2UKICAgICB3aGljaCBzdXBwb3J0cyB0aGUgYGRwbHlyYCBwYWNrYWdlLgoKCgojIyBDaGFuZ2UgY29sdW1uIG5hbWVzIGBkcGx5cjo6cmVuYW1lYApgYGB7cn0KZmxpZ2h0cyAlPiUgcmVuYW1lKHRhaWxfbnVtYmVyID0gdGFpbG51bSkKYGBgCgoKCgojIyBgc2VsZWN0KClgIHZzLiBgcmVuYW1lKClgCllvdSBjYW4gZG8gc29tZSBpbnRlcmVzdGluZyB0aGluZ3Mgd2l0aCBgZHBseXI6OnNlbGVjdCgpYCwgc3VjaCBhcyByZW5hbWluZwoqYW5kKiBzZWxlY3Rpbmcgc2ltdWx0YW5lb3VzbHkuCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3QodGFpbF9udW1iZXIgPSB0YWlsbnVtKQpgYGAKCgpUaGlzIG1pZ2h0IGJlIGNvbmZ1c2luZywgYnV0IGFyZ3VtZW50cyBhcmUgZXZhbHVhdGVkIG9ubHkgYXQgdGhlIGVuZCBvZiB0aGUKY2FsbCwgYW5kIGluIHRoZSBvcmRlciBvZiB0aGUgYXJndW1lbnRzIChpLmUuIGAuLi5gKS4gU28geW91IGNhbiByZWZlciB0byAKdmFyaWFibGVzIHRoYXQgaGF2ZSBiZWVuIHJlbW92ZWQgb3IgcmVuYW1lZCBieSB0aGVpciBvcmlnaW5hbCBuYW1lcy4KVGhpcyBpcyBiZXN0IHNlZW4gaW4gYW4gZXhhbXBsZToKCmBgYHtyfQojIFJlbmFtZSB0YWlsbnVtIC0+IHRhaWxfbnVtYmVyCiMgVGhlbiBzZWxlY3QgdGhlIG5ldyB2YXJpYWJsZSBgdGFpbF9udW1iZXJgCiMgVGhlbiBzZWxlY3QgYWxsIHZhcmlhYmxlcyBleGNlcHQgYHRhaWxudW1gIChidXQgcmVmZXJyaW5nIHRvIHRoZSBvcmlnaW5hbCBuYW1lISkKZmxpZ2h0cyAlPiUgc2VsZWN0KHRhaWxfbnVtYmVyID0gdGFpbG51bSwgLXRhaWxudW0pICAgIyBubyByb3dzCiMgU2VsZWN0IGFsbCB2YXJpYWJsZXMgZXhjZXB0IGB0YWlsbnVtYAojIFRoZW4gcmVuYW1lIHRhaWxudW0gLT4gdGFpbF9udW1iZXIKIyBUaGlzICdhZGRzIGJhY2sgdGhlIHZhcmlhYmxlIHdpdGggYSBuZXcgbmFtZQojIEFuZCByZW9yZGVycyBpdCB0byB0aGUgZmluYWwgY29sdW1uCmZsaWdodHMgJT4lIHNlbGVjdCgtdGFpbG51bSwgdGFpbF9udW1iZXIgPSB0YWlsbnVtKSAgICMgdGFpbG51bSAtPiB0YWlsX251bWJlciArIG1vdmUgdG8gZW5kCmBgYAoKCgojIyBTb3J0IGRhdGEgYGRwbHlyOjphcnJhbmdlYApgYGB7cn0KZmxpZ2h0cyAlPiUgZmlsdGVyKG1vbnRoID09IDMsIGRheSA9PSAyKSAlPiUgYXJyYW5nZShvcmlnaW4sIGRlc3QpCmBgYAoKCiMjIERlc2NlbmRpbmcgb3JkZXIgYGRwbHlyOjphcnJhbmdlYApgYGB7cn0KZmxpZ2h0cyAlPiUgZmlsdGVyKG1vbnRoID09IDMsIGRheSA9PSAyKSAlPiUKICBhcnJhbmdlKGRlc2Mob3JpZ2luKSwgZGVzdCkgJT4lIHNlbGVjdChvcmlnaW4sIGRlc3QsIHRhaWxudW0pCgpgYGAKCiMjIE1vZGlmeSBjb2x1bW5zIGBkcGx5cjo6bXV0YXRlYApgYGB7cn0KZmxpZ2h0cyAlPiUKICAgc2VsZWN0KDE6MykgJT4lCiAgIG11dGF0ZShkYXRlID0gcGFzdGUobW9udGgsIGRheSwgeWVhciwgc2VwPSIvIikgJT4lIG1keSgpKQpgYGAKCiMjIENyZWF0ZSBuZXcgdGliYmxlIGZyb20gZXhpc3RpbmcgY29sdW1ucyBgZHBseXI6OnRyYW5zbXV0ZWAKYGBge3J9CmZsaWdodHMgJT4lCiAgIHNlbGVjdCgxOjMpICU+JQogICB0cmFuc211dGUoZGF0ZSA9IHBhc3RlKG1vbnRoLCBkYXksIHllYXIsIHNlcD0iLyIpICU+JSBtZHkoKSkKYGBgCgojIyBGaW5kIHVuaXF1ZSByb3dzIGBkcGx5cjo6ZGlzdGluY3RgCmBgYHtyfQpmbGlnaHRzICU+JQogICBzZWxlY3Qob3JpZ2luLCBkZXN0KSAlPiUKICAgZGlzdGluY3QoKSAlPiUKICAgYXJyYW5nZShvcmlnaW4sIGRlc3QpCmBgYAoKIyMgU2FtcGxlIHJvd3MgYGRwbHlyOjpzYW1wbGVfbmAKYGBge3J9CmZsaWdodHMgJT4lIHNhbXBsZV9uKDEwKQpgYGAKCiMjIFNhbXBsZSByb3dzIGBkcGx5cjo6c2FtcGxlX2ZyYWNgCmBgYHtyfQpmbGlnaHRzICU+JSBzYW1wbGVfZnJhYygwLjAwMSkKYGBgCgojIyBTdW1tYXJ5IHZpYSBgZHBseXI6OnN1bW1hcmlzZWAKYGBge3J9CmZsaWdodHMgJT4lIG11dGF0ZShkYXRlID0gcGFzdGUobW9udGgsIGRheSwgeWVhciwgc2VwID0gIi8iKSAlPiUgbWR5KCkpICU+JQogICBzdW1tYXJpemUobigpLCBtaW4oZGF0ZSksIG1heChkYXRlKSkKYGBgCgojIyBTZXQgR3JvdXBpbmdzIGBkcGx5cjo6Z3JvdXBfYnlgCmBgYHtyfQojIGdyb3VwaW5nIHBhcmFtZXRlcnMgc3RvcmVkIGluIGF0dHJpYnV0ZXMKZmxpZ2h0cyAlPiUgZ3JvdXBfYnkob3JpZ2luKSAgICAjIGRvZXNuJ3QgYXBwZWFyIGFueSBkaWZmZXJlbnQKZmxpZ2h0cyAlPiUgY2xhc3MoKSAgICAgICAgICAgICAjIHByZS1ncm91cGluZyBpcyBzdGFuZGFyZCBgdGliYmxlYApmbGlnaHRzICU+JSBncm91cF9ieShvcmlnaW4pICU+JSBjbGFzcygpICAjIHJldHVybmVkIG9iamVjdCBpcyBhIG5ldyBjbGFzcyBgZ3JvdXBlZF9kZmAKYGBgCgojIyBTdW1tYXJpemUgZ3JvdXBzIHdpdGggYGRwbHlyOjpncm91cF9ieSgpYCArIGBkcGx5cjo6c3VtbWFyaXNlKClgCmBgYHtyfQojIHRoZSBgbHVicmlkYXRlYCBwYWNrYWdlICh0aWR5dmVyc2UpIGhlbHBzIHdvcmtpbmcgd2l0aCBkYXRlcwpmbGlnaHRzICU+JQogIGdyb3VwX2J5KG9yaWdpbikgJT4lCiAgbXV0YXRlKGRhdGUgPSBwYXN0ZShtb250aCwgZGF5LCB5ZWFyLCBzZXAgPSAiLyIpICU+JSBsdWJyaWRhdGU6Om1keSgpKSAlPiUKICBzdW1tYXJpemUobiA9IG4oKSwgbWluID0gbWluKGRhdGUpLCBtZWRpYW4gPSBtZWRpYW4oZGF0ZSksIG1heCA9IG1heChkYXRlKSkKYGBgCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIFBsb3R0aW5nCgpObyBkYXRhIHdyYW5nbGluZyBhbmQvb3IgZGF0YSBleHBsb3JhdGlvbiBpcyBjb21wbGV0ZSB3aXRob3V0IGF0IGxlYXN0IHNvbWUgdmlzdWFsIGV4cGxvcmF0aW9uIG9mIHRoZSBkYXRhLiBBc2tpbmcgeW91cnNlbGY6IHdoYXQgZG8gSSBoYXZlIGhlcmU/IGhvdyBpcyBteSBkYXRhIChwYXJ0aWN1bGFybHkgbXkgY2xhc3MgdmFyaWFibGUgb3IgcmVzcG9uc2UgdmFyaWFibGUpIGRpc3RyaWJ1dGVkPyBBcmUgdGhlcmUgYW55IG1pc3NpbmcgdmFsdWVzPyBOZWdhdGl2ZSB2YWx1ZXM/CgpgYGB7cn0KcGFyKG1mcm93ID0gYygyLCAyKSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbWFrZSAyeDIgZ3JpZCBmb3IgcGxvdHMKcGFyKG1ncCA9IGMoMiwgMC43NSwgMCksIG1hciA9IGMoMywgNCwgMywgMSkpICAgICMgZ3JhcGhpY3Mgc2V0dGluZ3M7IHNxdWVlemUgbWFyZ2lucwpsYXBwbHkobmFtZXMoaXJpcylbLTVdLCBmdW5jdGlvbihjbGFzcykKICAgYm94cGxvdChzcGxpdChpcmlzW1tjbGFzc11dLCBpcmlzJFNwZWNpZXMpLCBtYWluID0gY2xhc3MsIGNvbCA9IDE6MykpICU+JQogICBpbnZpc2libGUoKSAgICMgcGlwZSB0byBpbnZpc2libGUgdG8gc3VwcHJlc3Mgb3V0cHV0CmBgYAoKIyMgVGhlIHNhbWUgdmlhIDxpbWcgc3JjPSJpbWFnZXMvZ2dwbG90Mi5wbmciIGNsYXNzPSJ0aXRsZS1oZXgiPgpgYGB7cn0KdGhtIDwtIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICBsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KQogICkKdGhlbWVfc2V0KHRobSkKCmlyaXMgJT4lCiAgdGlkeXI6OmdhdGhlcihrZXkgPSAiRmVhdHVyZSIsIHZhbHVlID0gImNtIiwgLVNwZWNpZXMpICU+JQogIG11dGF0ZShGZWF0dXJlID0gZ3N1YigiXFwuIiwgIiAiLCBGZWF0dXJlKSkgJT4lCiAgZ2dwbG90KGFlcyh5ID0gY20sIHggPSBTcGVjaWVzLCBmaWxsID0gU3BlY2llcykpICsKICBnZW9tX2JveHBsb3QoY29sb3IgPSAiIzFGMzU1MiIsIGFscGhhID0gMC43NSwgc2l6ZSA9IDAuNSkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTcGVjaWVzIikgKwogIGdndGl0bGUoIk92ZXJhbGwgVGl0bGUiKSArCiAgZmFjZXRfd3JhcCh+RmVhdHVyZSwgbmNvbCA9IDIpICsKICBOVUxMCmBgYAoKCiMjIFBhaXJ3aXNlIHBsb3RzCgpWZXJ5IHVzZWZ1bCBmb3IgdmlzdWFsbHkgaW5zcGVjdGluZyB2YXJpYWJsZXMgZm9yIHBhdHRlcm5zIG9mIGludGVyZXN0LiBZb3Ugd2lsbCB0eXBpY2FsbHkgbmVlZCB0byBrbm93IHlvdXIgb3V0Y29tZSBvZiBpbnRlcmVzdCAqYSBwcmlvcmkqLCBpLmUuIHN1cGVydmlzZWQgYW5hbHlzaXMsIG9yIHlvdSB3aWxsIG5vdCBrbm93IGhvdyB0byBjb2xvciB0aGUgcG9pbnRzLgoKYGBge3J9CnBsb3QoaXJpc1ssIC01XSwgY29sPWlyaXMkU3BlY2llcykKYGBgCgpVbmZvcnR1bmF0ZWx5IHRoZXJlIGlzIG5vIHNpbXBsZSBlcXVpdmVsYW50IGZvciBwYWlyd2lzZSBwbG90cyBpbiBgZ2dwbG90MmAsIHNvIHdlIGFyZSBmb3JjZWQgdG8gbG9vayBhdCB0aGUgdmFyaWFiZWxzIG9uZSBhdCBhIHRpbWUsIG9yIHdyaXRlIG91ciBvd24gd3JhcHBlciBmdW5jdGlvbiAoaG9tZXdvcms/Pyk6CgpgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gU2VwYWwuV2lkdGgsIGNvbG9yID0gU3BlY2llcykpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKQpgYGAKCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIEV4ZXJjaXNlcwoKICAgIDEuIFdoaWNoIGRlc3RpbmF0aW9ucyBoYXZlIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgZGVsYXlzPwoKICAgIDIuIEhvdyBtYW55IGZsaWdodHMgdG8gTG9zIEFuZ2VsZXMgKExBWCkgZGlkIGVhY2ggb2YgdGhlIGxlZ2FjeSBjYXJyaWVycyAoQUEsIFVBLCBETCBvciBVUykgaGF2ZSBpbiBNYXkgZnJvbSBKRkssIGFuZCB3aGF0IHdhcyB0aGVpciBhdmVyYWdlIGR1cmF0aW9uPwoKICAgIDMuIFdoaWNoIHBsYW5lIChjaGVjayB0aGUgdGFpbCBudW1iZXIpIGZsZXcgb3V0IG9mIGVhY2ggTmV3IFlvcmsgYWlycG9ydCB0aGUgbW9zdD8KCiAgICA0LiBXaGF0IHdhcyB0aGUgc2hvcnRlc3QgZmxpZ2h0IG91dCBvZiBlYWNoIGFpcnBvcnQgaW4gdGVybXMgb2YgZGlzdGFuY2U/IEluIHRlcm1zIG9mIGR1cmF0aW9uPwoKICAgIDUuIFdoaWNoIGRhdGUgc2hvdWxkIHlvdSBmbHkgb24gaWYgeW91IHdhbnQgdG8gaGF2ZSB0aGUgbG93ZXN0IHBvc3NpYmxlIGF2ZXJhZ2UgZGVwYXJ0dXJlIGRlbGF5PyBXaGF0IGFib3V0IGFycml2YWwgZGVsYXk/CgoKIyMjIFBsb3Qgd2l0aCA8aW1nIHNyYz0iaW1hZ2VzL2dncGxvdDIucG5nIiBjbGFzcz0idGl0bGUtaGV4Ij4KCiAgICA2LiBDcmVhdGUgYSB0aW1lIHNlcmllcyBwbG90IG9mIGVhY2ggb2YgdGhlIGxlZ2FjeSBjYXJyaWVycyBhdmVyYWdlIGRlcGFydHVyZSBkZWxheSBieSBkYXkgYW5kIG9yaWdpbiBhaXJwb3J0LiBJdCBzaG91bGQgbG9vayBzb21ldGhpbmcgbGlrZSB0aGlzOgoKYGBge3IgZmxpZ2h0c19nZ3Bsb3QsIGVjaG8gPSBGQUxTRX0KZmxpZ2h0cyAlPiUKICBmaWx0ZXIoY2FycmllciA9PSBjKCJBQSIsICJVQSIsICJETCIsICJVUyIpKSAlPiUKICBncm91cF9ieShjYXJyaWVyLCBkYXksIG9yaWdpbikgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCksIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpICU+JQogIGdncGxvdChhZXMoeCA9IGRheSwgeSA9IGRlbGF5KSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZmFjZXRfd3JhcChjYXJyaWVyIH4gb3JpZ2luKQpgYGAKCiMjIyBOb3cgRG8gSXQgWW91cnNlbGYKCiAgICA3LiBZb3UgaGF2ZSBwcm9iYWJseSBoYWQgZW5vdWdoIG9mIE5ldyBZb3JrIENpdHkgZmxpZ2h0IHRpbWVzLiBOb3cgdGFrZSBhIGxvb2sgYXQgZWl0aGVyIHRoZSBgbXRjYXJzYCBvciBgbXRjYXJzMmAgZGF0YSBzZXQgYW5kIGV4ZWN1dGUgbWFueSBvZiB0aGUgc2FtZSBjb21tYW5kcyBhYm92ZSB3aGlsZSBleHBsb3JpbmcgdGhpcyBuZXcsIGRpZmZlcmVudCBkYXRhIHNldC4KCgoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKCgojIE1lcmdpbmcgRGF0YQoKTm8gZGF0YSB3cmFuZ2xpbmcgdHV0b3JpYWwgd291bGQgYmUgY29tcGxldGUgd2l0aG91dCBhIHNlY3Rpb24gb24gbWVyZ2luZyBkYXRhLiBUaGlzIHdpbGwgaGFwcGVuIG9mdGVuLiBEYXRhIHdpbGwgY29tZSB0byB5b3UsIHBlcmhhcHMgZnJvbSB2YXJpb3VzIHNvdXJjZXMsIGFuZCB5b3UgbXVzdCBjb21iaW5lIHRoZSByZWNvcmRzIChjYXNlcywgc2FtcGxlcywgcm93cywgZXRjLikgYmFzZWQgb24gYSBjb21tb24gaW5kaWNhdG9yIHZhcmlhYmxlLCB1c3VhbGx5IHNvbWUgc29ydCBvZiAqa2V5KiBpZGVudGlmaWVyIHZhcmlhYmxlIChlLmcuIGEgbmFtZSwgYSBwYXRpZW50IElELCBldGMuKS4KCiMjIERhdGEgbWFuaXB1bGF0aW9uIGlzIG5vdCBjb21wbGV0ZSB3aXRob3V0IG1lcmdpbmcgaW5mb3JtYXRpb24KClR3byB0YWJsZSBmdW5jdGlvbnMgLyB2ZXJicywgYWxsIGZ1bmN0aW9ucyBoYXZlIHRoZSBmb3JtIGBmKGEsIGIpYDoKCnwgICBGdW5jdGlvbiAgICB8ICAgV2hhdCBpdCBkb2VzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAtLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfAp8IGBsZWZ0X2pvaW5gICAgfCBKb2luIG1hdGNoaW5nIHJvd3MgZnJvbSBgYmAgdG8gYGFgLCBwcmVzZXJ2aW5nIGFsbCByb3dzIG9mIGBhYCB8CnwgYHJpZ2h0X2pvaW5gICB8IEpvaW4gbWF0Y2hpbmcgcm93cyBmcm9tIGBhYCB0byBgYmAsIHByZXNlcnZpbmcgYWxsIHJvd3Mgb2YgYGJgIHwKfCBgaW5uZXJfam9pbmAgIHwgSm9pbiBkYXRhLCBwcmVzZXJ2aW5nIG9ubHkgcm93cyB3aXRoIGtleXMgaW4gYm90aCBgYWAgYW5kIGBiYCAgfAp8IGBmdWxsX2pvaW5gICAgfCBKb2luIGRhdGEsIHByZXNlcnZpbmcgYWxsIHJvd3MgaW4gYm90aCBgYWAgYW5kIGBiYCAgICAgICAgICAgICB8CnwgYHNlbWlfam9pbmAgICB8IFN1YnNldCByb3dzIGluIGBhYCB0aGF0IGhhdmUgYSBtYXRjaCBpbiBgYmAgICAgICAgICAgICAgICAgICAgIHwKfCBgYW50aV9qb2luYCAgIHwgU3Vic2V0IHJvd3MgaW4gYGFgIHRoYXQgZG8gbm90IGhhdmUgYSBtYXRjaCBpbiBgYmAgICAgICAgICAgICAgfAoKIyMgSm9pbmluZyBEYXRhCgpgYGB7ciBhZGRyfQphZGRyIDwtIGRhdGEuZnJhbWUobmFtZSA9IGMoIkFsaWNlIiwgIkJvYiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2Fyb2wiLCAiZGF2ZSIsICJFdmUiKSwKICAgICAgICAgICAgICAgICAgIGVtYWlsID0gYygiYWxpY2VAY29tcGFueS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJib2JAY29tcGFueS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYXJvbEBjb21wYW55LmNvbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRhdmVAY29tcGFueS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJldmVAY29tcGFueS5jb20iKSwKICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYWRkcgpgYGAKCmBgYHtyIHBob25lfQpwaG9uZSA8LSBkYXRhLmZyYW1lKG5hbWUgPSBjKCJCb2IiLCAiQ2Fyb2wiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFdmUiLCAiRXZlIiwgIkZyYW5rIiksCiAgICAgICAgICAgICAgICAgICAgcGhvbmUgPSBjKCI5MTkgNTU1LTExMTEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICI5MTkgNTU1LTIyMjIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICI5MTkgNTU1LTMzMzMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICIzMTAgNTU1LTMzMzMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICI5MTkgNTU1LTQ0NDQiKSwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnBob25lCmBgYAoKCiMjIE91dGVyIEpvaW4KCkluIGBkcGx5cmA6CmBgYHtyfQpmdWxsX2pvaW4oYWRkciwgcGhvbmUpCmBgYAoKSW4gYGJhc2UgUmA6CmBgYHtyfQptZXJnZShhZGRyLCBwaG9uZSwgYWxsID0gVFJVRSkKYGBgCgojIyBJbm5lciBKb2luCgpJbiBgZHBseXJgOgpgYGB7cn0KaW5uZXJfam9pbihhZGRyLCBwaG9uZSkKYGBgCgpJbiBgYmFzZSBSYDoKYGBge3J9Cm1lcmdlKGFkZHIsIHBob25lLCBhbGwgPSBGQUxTRSkKYGBgCgojIyBMZWZ0IEpvaW4KCkluIGBkcGx5cmA6CmBgYHtyfQpsZWZ0X2pvaW4oYWRkciwgcGhvbmUpCmBgYAoKSW4gYGJhc2UgUmA6CmBgYHtyfQptZXJnZShhZGRyLCBwaG9uZSwgYWxsLnggPSBUUlVFKQpgYGAKCiMjIFJpZ2h0IEpvaW4KCkluIGBkcGx5cmA6CmBgYHtyfQpyaWdodF9qb2luKGFkZHIsIHBob25lKQpgYGAKCkluIGBiYXNlIFJgOgpgYGB7cn0KbWVyZ2UoYWRkciwgcGhvbmUsIGFsbC55ID0gVFJVRSkKYGBgCgoKIyMgU2VtaSBhbmQgQW50aSBKb2lucwoKYGBge3Igc2VtaX0Kc2VtaV9qb2luKGFkZHIsIHBob25lKSAgICMgc2VtaQpgYGAKCmBgYHtyIGFudGl9CmFudGlfam9pbihhZGRyLCBwaG9uZSkgICAjIGFudGkKYGBgCgoKIyMgU3BlY2lhbCBjYXNlOiBtYW55LXRvLW1hbnkgcmVsYXRpb25zaGlwcwoKYGBge3J9CmFkZHIyIDwtIGRhdGEuZnJhbWUobmFtZSA9IGMoIkFsaWNlIiwgIkFsaWNlIiwgIkJvYiIsICJCb2IiKSwKICAgICAgICAgICAgICAgICAgICBlbWFpbD0gYygiYWxpY2VAY29tcGFueS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhbGljZUBnbWFpbC5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJib2JAY29tcGFueS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJib2JAaG90bWFpbC5jb20iKSwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKYGBge3J9CnBob25lMiA8LSBkYXRhLmZyYW1lKG5hbWUgPSBjKCJBbGljZSIsICJBbGljZSIsICJCb2IiLCAiQm9iIiksCiAgICAgICAgICAgICAgICAgICAgIHBob25lID0gYygiOTE5IDU1NS0xMTExIiwgIjMxMCA1NTUtMjIyMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiOTE5IDU1NS0zMzMzIiwgIjMxMCA1NTUtMzMzMyIpLAogICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKSW4gYGRwbHlyYDoKYGBge3J9CmZ1bGxfam9pbihhZGRyMiwgcGhvbmUyLCBieSA9ICJuYW1lIikKYGBgCgpJbiBgYmFzZSBSYDoKYGBge3J9Cm1lcmdlKGFkZHIyLCBwaG9uZTIpCmBgYAoKCiMjIEV4YW1wbGU6IEVuaGFuY2luZyBOWUMgRmxpZ2h0IERhdGEKClRoZSBgbnljZmxpZ2h0czEzYCBwYWNrYWdlIGFsc28gY29udGFpbnMgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dDoKCnwgICBEYXRhc2V0ICAgICB8ICAgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8CnwgYHdlYXRoZXJgICAgICB8IGhvdXJseSBtZXRlcm9sb2dpY2FsIGRhdGEgZm9yIGVhY2ggYWlycG9ydCAgICAgICAgICAgICB8CnwgYHBsYW5lc2AgICAgICB8IGNvbnN0cnVjdGlvbiBpbmZvcm1hdGlvbiBhYm91dCBlYWNoIHBsYW5lICAgICAgICAgICAgICB8CnwgYGFpcnBvcnRzYCAgICB8IGFpcnBvcnQgbmFtZXMgYW5kIGxvY2F0aW9ucyAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYGFpcmxpbmVzYCAgICB8IHRyYW5zbGF0aW9uIGJldHdlZW4gdHdvIGxldHRlciBjYXJyaWVyIGNvZGVzIGFuZCBuYW1lcyB8CgoKIyMgV2VhdGhlciBhbmQgRmxpZ2h0IERlbGF5cwoKTGV0J3MgdGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlIHdlYXRoZXIgZGF0YSwgd2l0aCBhbiBleWUgdG93YXJkcyBleGFtaW5pbmcgaG93IGl0IG1pZ2h0IGFmZmVjdCB0aGluZ3MgbGlrZSBmbGlnaHQgZGVwYXJ0dXJlIGRlbGF5cy4KCmBgYHtyfQp3ZWF0aGVyCmBgYAoKCiMjIEpvaW4gYnk/CgpWYXJpYWJsZSBkZWZpbnRpb25zIGluIHRoZSBgd2VhdGhlcmAgZGF0YXNldDoKCnwgICBWYXJpYWJsZSAgICB8ICAgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8CnwgYG9yaWdpbmAgICAgICB8IFdlYXRoZXIgc3RhdGlvbi4gTmFtZWQgb3JpZ2luIHRvIGZhY2lsaWF0ZSBtZXJnaW5nIHdpdGggZmxpZ2h0cyBkYXRhIHwKfCBgeWVhcmAsIGBtb250aGAsIGBkYXlgLCBgaG91cmAgfCBUaW1lIG9mIHJlY29yZGluZyAgICAgICAgIHwKfCBgdGVtcGAsIGBkZXdwYCB8IFRlbXBlcmF0dXJlIGFuZCBkZXdwb2ludCBpbiBGYWhyZW5oZWl0ICAgIHwKfCBgaHVtaWRgICAgICAgfCBSZWxhdGl2ZSBodW1pZGl0eSAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBgd2luZF9kaXJgLCBgd2luZF9zcGVlZGAsIGB3aW5kX2d1c3RgIHwgV2luZCBkaXJlY3Rpb24gKGluIGRlZ3JlZXMpLCBzcGVlZCAoaW4gbXBoKSwgYW5kIGd1c3Qgc3BlZWQgKGluIG1waCkgfAp8IGBwcmVjaXBgICAgICB8IFByZWNpcHRhdGlvbiwgaW4gaW5jaGVzICAgICAgICAgICAgICAgICAgICAgfAp8IGBwcmVzc3VyZWAgICB8IFNlYSBsZXZlbCBwcmVzc3VyZSBpbiBtaWxsaWJhcnMgICAgICAgICAgICAgfAp8IGB2aXNpYmAgICAgICB8IFZpc2liaWxpdHkgaW4gbWlsZXMgICAgICAgICAgICAgICAgICAgICAgICAgfAoKCgo8IS0tClZhcmlhYmxlIGRlZmludGlvbnMgaW4gdGhlIGBmbGlnaHRzYCBkYXRhc2V0OgoKfCAgIFZhcmlhYmxlICAgICB8ICAgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfAp8IGB0YWlsbnVtYCAgICAgIHwgVGFpbCBudW1iZXIgICAgICAgICAgICAgICAgICAgICAgIHwKfCBgeWVhcmAgICAgICAgICB8IFllYXIgbWFudWZhY3R1cmVkICAgICAgICAgICAgICAgICB8CnwgYHR5cGVgICAgICAgICAgfCBUeXBlIG9mIHBsYW5lICAgICAgICAgICAgICAgICAgICAgfAp8IGBtYW51ZmFjdHVyZXJgIHwgTWFudWZhY3R1cmVyICAgICAgICAgICAgICAgICAgICAgIHwKfCBgbW9kZWxgICAgICAgICB8IE1vZGVsICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYGVuZ2luZXNgICAgICAgfCBOdW1iZXIgb2YgZW5naW5lcyAgICAgICAgICAgICAgICAgfAp8IGBzZWF0c2AgICAgICAgIHwgTnVtYmVyIG9mIHNlYXRzICAgICAgICAgICAgICAgICAgIHwKfCBgc3BlZWRgICAgICAgICB8IEF2ZXJhZ2UgY3J1aXNpbmcgc3BlZWQgaW4gbXBoICAgICB8CnwgYGVuZ2luZWAgICAgICAgfCBUeXBlIG9mIGVuZ2luZSAgICAgICAgICAgICAgICAgICAgfAotLT4KCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCmBgYHtyfQppbnRlcnNlY3QobmFtZXMod2VhdGhlciksIG5hbWVzKGZsaWdodHMpKQpgYGAKCgoKCiMjIEpvaW5pbmcgZmxpZ2h0cyBhbmQgd2VhdGhlcgoKV2hlbiBqb2luaW5nIHRoZXNlIHR3byBkYXRhIGZyYW1lcyBJIHdpbGwgdXNlIGFuIGlubmVyIGpvaW4gKGJlY2F1c2UgSSBvbmx5IHdhbnQgdGhlIGZsaWdodHMgdGhhdCBoYXZlIHRoZSB3ZWF0aGVyIGRhdGEsIGFuZCBJIG9ubHkgd2FudCB3ZWF0aGVyIGRhdGEgd2hlbiB0aGVyZSB3YXMgYSBmbGlnaHQpCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQooZmxpZ2h0c3cgPC0gaW5uZXJfam9pbihmbGlnaHRzLCB3ZWF0aGVyKSkKYGBgCgpMZXQncyBtYWtlIGEgYGdncGxvdGAgb2YgdGhlIGRlbGF5IHRpbWUgdnMuIHZpc2liaWxpdHk6CgpgYGB7cn0KZmxpZ2h0c3cgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdmlzaWIsIHkgPSBkZXBfZGVsYXkpKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjUpICsgICAgIyBzaGFrZSB1cCB0aGUgcG9pbnRzIGEgYml0IGZvciBiZXR0ZXIgdmlld2luZwogIE5VTEwKYGBgCgoKCkxldCdzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIC0+IHZpc2liaWxpdHk6CgpgYGB7cn0KZmxpZ2h0c3cgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdmlzaWIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJibHVlIikKdGFibGUoZmxpZ2h0c3ckdmlzaWIpCmBgYAoKCldvdyEgVGhlIHZhc3QgbWFqb3JpdHkgb2YgZmxpZ2h0cyBhcmUgdW5kZXIgY2xlYXIgc2tpZXMgd2l0aCBgPiAxMGAgbWlsZXMgdmlzaWJpbGl0eS4gR29vZCB0byBrbm93LgoKYGBge3J9CmZsaWdodHN3ICU+JQogIGdncGxvdChhZXMoeCA9IHZpc2liLCB5ID0gZGVwX2RlbGF5KSkgKwogIGdlb21faml0dGVyKHNoYXBlID0gMjEsIGNvbG9yID0gImJsdWUiLCBmaWxsID0gImdyYXkiLAogICAgICAgICAgICAgIHNpemUgPSAxLjUsIHdpZHRoID0gMC4yLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fc21vb3RoKGNvbG9yID0gInJlZCIpICsKICBOVUxMCmBgYAoKCk5vdGljZSBhIGZldyB0aGluZ3M6CgogICogVGhlcmUgYXJlIG1hbnkgKm5lZ2F0aXZlKiBkZXBhcnR1cmUgdGltZSB2YWx1ZXMgKGVhcmx5ISkKICAqIFRoZSBkZXBhcnR1cmUgdGltZSBkaXN0cmlidXRpb24gaXMgaGVhdmlseSBza2V3ZWQgdG93YXJkcyB6ZXJvIAogICogVGhpcyBtYWtlcyBzZW5zZSBzaW5jZSBkZWxheXMgYXJlIG11Y2ggbW9yZSBsaWtlbHkgdGhhbiBlYXJseSBkZXBhcnR1cmVzLAogICAgd2hpY2ggbWVhbnMgdGhlICoqbWFueSoqIHBvaW50cyBvdmVybGFwIG5lYXIgemVyby4KICAqIFRoZXNlIHZhbHVlcyBzdG9wIHVzIGZyb20gcGxvdHRpbmcgdGhlIHktYXhpcyBvbiBhIGxvZy1zY2FsZSAod2hpY2ggbWlnaHQgCiAgICBnaXZlIHVzIGEgYmV0dGVyIHZpZXcgb2YgdGhlIGRhdGEpLCAKICAqIEEgYm94cGxvdCAoc2VlIFtoZXJlXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vdW5kZXJzdGFuZGluZy1ib3hwbG90cy01ZTJkZjdiY2JkNTEpIAogICAgYWJvdXQgYm94cGxvdHMpIG1pZ2h0IGdpdmUgdXMgYSBiZXR0ZXIgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRhdGEgLgogICogTGV0J3MgcmVtb3ZlIHRoZSBuZWdhdGl2ZSBkZXBhcnR1cmUgdGltZXMgYW5kIHJlcGxvdCBhcyBhICpib3hwbG90KjoKCmBgYHtyfQpmbGlnaHRzdyAlPiUKICBmaWx0ZXIoZGVwX2RlbGF5ID4gMCkgICU+JQogIG11dGF0ZSh2aXNpYiAlPD4lIGZhY3RvcikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdmlzaWIsIHkgPSBkZXBfZGVsYXksIGdyb3VwID0gdmlzaWIpKSArCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAiYmx1ZSIsIGNvbG9yID0gImdyYXkiKSArCiAgY29vcmRfdHJhbnMoeSA9ICJsb2cxMCIpICsKICB4bGFiKCJWaXNpYmlsaXR5IikgKwogIHlsYWIoIkRlcGFydHVyZSBEZWxheSIpICsKICBOVUxMCmBgYAoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIE1vcmUgRXhlcmNpc2VzCgoxLiBDaGVjayBzb21lIG9mIHRoZSBvdGhlciB3ZWF0aGVyIHZhcmlhYmxlcyAoZS5nLiBgdGVtcGAsIGB3aW5kX3NwZWVkYCwgYGV0Y2ApCmFuZCBzZWUgaWYgeW91IGNhbiBmaW5kIGFueSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVtIGFuZCBkZXBhcnR1cmUKZGVsYXkgKGBkZXBfZGVsYXlgKS4KCjIuIE1lcmdlIHRoZSBgZmxpZ2h0c2AgZGF0YSB3aXRoIHRoZSBgcGxhbmVzYCBkYXRhIHNldCAocGF5IGF0dGVudGlvbiB0bwp3aGF0IGNvbHVtbnMgYXJlIGJlaW5nIHVzZWQgZm9yIHRoZSBqb2luKS4gQXJlIG9sZGVyIHBsYW5lcyBtb3JlIGxpa2VseQp0byBiZSBkZWxheWVkPyBXaGF0IGFib3V0IHBsYW5lcyBmcm9tIEFpcmJ1cyB2cyBCb2VpbmcgdnMgRW1icmFlcj8KCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIEFja25vd2xlZGdtZW50cwoKQWJvdmUgbWF0ZXJpYWxzIGFyZSBkZXJpdmVkIGluIHBhcnQgZnJvbSB0aGUgZm9sbG93aW5nIHNvdXJjZXM6CgoqIFtSU3R1ZGlvIERhdGEgV3JhbmdsaW5nIENoZWF0IFNoZWV0XShodHRwOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAyL2RhdGEtd3JhbmdsaW5nLWNoZWF0c2hlZXQucGRmKQoKKiBQYWNrYWdlIFZpZ25ldHRlczoKICAgICsgW2RwbHlyXShodHRwOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kcGx5ci92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpCiAgICArIFt0aWR5cl0oaHR0cDovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdGlkeXIvdmlnbmV0dGVzL3RpZHktZGF0YS5odG1sKQoKKiBIYWRsZXkgV2lja2hhbSdzIG5ldyBib29rOgogICAgKyBbUiBmb3IgRGF0YSBTY2llbmNlXShodHRwOi8vcjRkcy5oYWQuY28ubnovKQoKPGltZyBzcmM9ImltYWdlcy9yNGRzLmpwZyIgd2lkdGg9MjAwLCBoZWlnaHQ9NDAwPgoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkNyZWF0ZWQgb24gYHIgU3lzLkRhdGUoKWAgYnkgdGhlIFtSbWFya2Rvd24gcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vcm1hcmtkb3duKSAodmByIHV0aWxzOjpwYWNrYWdlVmVyc2lvbigicm1hcmtkb3duIilgKSBhbmQgYHIgUi52ZXJzaW9uJHZlcnNpb24uc3RyaW5nYC4K