Outline

Today, we’re going to be covering two really important concepts

  1. Piping
  2. Split-Apply-Combine

Piping was introduced to R with the magrittr package (get it? ), and over the past 4 years has dramatically altered how programming in R works. Split-Apply-Combine is a data analysis technique that you may have already used, but is streamlined in the dplyr and purrr packages.

Setup

~2 minute setup

Create and save your R notebook for today. Clear the workspace in case there’s anything left over from last time. Load the important packages for today’s work:

install.packages("broom")

library(lsa2017)
library(tidyverse)
library(magrittr)
library(broom)

Also, download and unzip the file called joe_vowel_files.zip from Canvas.


Piping

This is the new functor we’ll be using today:

%>%

We’ll pronounce that “pipe.”

So far, every time we’ve used a function, like nrow(), we’ve passed it its arguments by putting them inside the parentheses:

nrow(iy_ah)
[1] 44818

What %>% does is take everything to its left, and injects it into the first argument of the function to its right.

iy_ah %>% nrow()
[1] 44818

We’ll pronounce this “piping iy_ah into nrow().”

This might not seem so earth shattering right away, but it really shines when you want to apply a function to the output of the previous function. Let’s say you wanted to check how many rows of “iy” data there is. Using normal functional application, you’d have to do this:

nrow(filter(iy_ah, plt_vclass == "iy"))
[1] 23699

Here, the first function you apply is the most deeply embedded, and the last function you apply is the outermost. Some syntacticians feel very comfortable with bracket notation like this, but it rapidly gets unreadable, and mis-matching closing parentheses is a very common kind of error. Here’s how these same exact operations look like with the pipes.

iy_ah %>%
  filter(plt_vclass == "iy") %>%
  nrow()
[1] 23699

This is much more readable, and the functions are visually ordered in the same order they’re applied.

The benefit of piping becomes most apparent if we revisit our data cleaning pipeline from our last meeting. Here’s iy_ah wide again.

iy_ah_wide

Last time, we made this data wide to long by creating intermediate data frames. However, we could do it all in a single shot by nesting functions like so:

spread(
  separate(
    gather(iy_ah_wide, 
           key = "vowel_formant", 
           value = "hz", 
           ah_F1:iy_F2), 
    "vowel_formant", 
    into = c("vowel", "formant"), 
    sep = "_"), 
  formant, 
  hz)

But good luck writing that twice, or sharing your code with anyone. Notice how the additional arguments for spread() are maximally far away from the actual call to the function at the top?

Piping makes it much better.

iy_ah_wide %>%
  gather(key = "vowel_formant", value = "hz", ah_F1:iy_F2) %>%
  separate("vowel_formant", into  = c("vowel", "formant"), sep = "_") %>%
  spread(formant, hz)

Another nice thing about these chains is that they can be built up incrementally. That is, just the first two lines will work:

iy_ah_wide %>%
  gather(key = "vowel_formant", value = "hz", ah_F1:iy_F2)

And so will just the fist three:

iy_ah_wide %>%
  gather(key = "vowel_formant", value = "hz", ah_F1:iy_F2) %>%
  separate("vowel_formant", into  = c("vowel", "formant"), sep = "_")

The same is not true of the embedded functional application, because it depends on matching parentheses between the beginning and end.

Idiom

I am going to recommend that you put a new line after every pipe, %>%, and indent the following line with two spaces when chaining together functions.

~5 minute activity

Re-create the fruit data frame:

fruit <- data.frame(person = c("Oakley", "Charlie"),
                 apples_bought = c(5, 3),
                 apples_ate = c(1, 2),
                 oranges_bought = c(5, 4),
                 oranges_ate = c(3, 3))

Using pipes, recreate the gather, separate and spread workflow from the last meeting to produce this table (you can double check the code from last time).

person fruit ate bought
Charlie apples 2 3
Charlie oranges 3 4
Oakley apples 1 5
Oakley oranges 3 5

Split-Apply-Comine

When doing data analysis, you’re going to find yourself doing these following steps a lot:

  1. Splitting the data up into subsets.
  2. Applying some kind of function to those subsets.
  3. Combining the results back together

Here is some fake data from an experiment of speakers’ fundamental frequency. Go ahead and run this code:

  pitch <- data_frame(speaker = rep(c("Oakley", "Charlie", "Azaria"), 
                                    times =  c(3,3,2)),
                       F0 = c(124, 109, 125, 103, 121, 123, 181, 206))
speaker F0
Oakley 124
Oakley 109
Oakley 125
Charlie 103
Charlie 121
Charlie 123
Azaria 181
Azaria 206

One thing we might want to know is what the average F0 is for each speaker. Here’s how we do that, conceptually, with the Split-Apply-Combine workflow.

Split the data up

First, split the data up into subsets based on the “speaker” column:

speaker F0
Azaria 181
Azaria 206
speaker F0
Charlie 103
Charlie 121
Charlie 123
speaker F0
Oakley 124
Oakley 109
Oakley 125


Apply some function to the data

In each subset, calculate the average F.

speaker mean_F0
Azaria 193.5
speaker mean_F0
Charlie 115.6667
speaker mean_F0
Oakley 119.3333


Combine the result

Combine these results into a new table.

speaker mean_F0
Azaria 193.5000
Charlie 115.6667
Oakley 119.3333

Split-Apply-Combine in dplyr

The dplyr package was written specifically to streamline the Split-Apply-Combine workflow. All dplyr “verbs” take a data frame as input, does something to them, and returns another data frame. Let’s start with the summarise() function.

summarise(data,
new_col1 = summary_function1,
new_col2 = summary_function2)

  • data
    • The data frame you want to summarise
  • summary functions
    • This isn’t a named argument. Here, you give summarise() a function that can take many values as input, and returns a single value as output.

For example, let’s calculate the mean F0 across all of the data in the pitch data frame.

pitch %>%
  summarise(mean_F0 = mean(F0))

This doesn’t look like much, because it’s just a one row data frame with one column called mean_F0 with one value, which is the mean of all of the F0 measurements from each speaker.

It’s also possible to do multiple summaries at once by just adding more summary functions to the summarise() verb.

pitch %>%
  summarise(mean_F0 = mean(F0),
            median_F0 = median(F0),
            sd_F0 = sd(F0),
            mad_F0 = mad(F0),
            range_F0 = max(F0)-min(F0),
            ndata = length(speaker))

The summarise() function is what we want to apply to our data, but for the Split-Apply-Combine workflow, we want to split the data according to the speaker, apply the summarise() function to each subset, then combine the results back together.

To split up the data, we first pass the pitch data frame to group_by().

group_by(data, groupA, groupB)

  • data
    • The data frame you want to apply Split-Apply-Combine to.
  • groupings
    • These are the names of columns in the data frame that you want to split the data up by. group_by() will create subsets of every unique combination of values in these columns.

When you apply group_by() to a data frame by itself, nothing really special happens:

pitch %>% 
  group_by(speaker)

But when you apply summarise() after group_by(), you get the mean F0 for each speaker:

pitch %>% 
  group_by(speaker) %>%
  summarise(mean_F0 = mean(F0))

Here’s what just happened.

  1. group_by() to split the data frame up according to the unique values of speaker.
  2. summarise() calculated the mean F0 within each subset.
  3. They were then automatically combined back together.

~5 minute activity

Just to refresh your memories, the iy_ah data frame contains all data from the vowels /i:/ and /ɑ/ from the PNC. The column idstring is a unique ID for each speaker, and plt_vclass codes the vowel class.

Calculate the average F1 and F2 for /i:/ and /ɑ/ for each speaker.

Additional dplyr verbs

Here are a few more dplyr verbs that you can string together.

verb description
filter() We’ve already used this to subset dataframes.
summarise() This takes a data frame, and outputs a new data frame based on the summary you asked for
mutate() This takes a data frame, and adds additional columns based on the formula you give it
select() This takes a data frame, and returns only the columns you ask for
arrange() Reorders the rows of the data frame

The one function here that requires some additional instruction is mutate().

mutate(data, new_col = value)

  • data
    • the data that you want to mutate
  • new columns
    • Here, you name new columns, and provide some kind of value, usually using some other function.

The simplest and least useful use of mutate() adds a new column with a single value:

pitch %>%
  mutate(jawn = "foo")

However, a much more useful use of mutate() creates transformed columns. For example, sometimes when working with pitch, you might estimate pitch differences in terms of semitones. Every doubling of pitch is 12 semitones. It’s the case that if you calculate the difference between the log2 transform of any two numbers that are doubles of eachother, you get 1.

# 1 octave differences
log2(6) - log2(3)
[1] 1
log2(18) - log2(9)
[1] 1
# 2 octave differences
log2(12) - log2(3)
[1] 2

If you wanted to estimate, for each speaker, their pitch range in terms of semitones, step 1 would be to convert their F0 in Hz into log2(Hz).

pitch %>%
  mutate(log2_F0 = log2(F0))

Then, for every speaker, subtract their max log2_F0 from their min log2_F0.

pitch %>%
  mutate(log2_F0 = log2(F0)) %>%
  group_by(speaker)%>%
  summarise(octave_range = max(log2_F0) - min(log2_F0),
            semitone_range = octave_range * 12)

~5 minute activity

Try the following:

pitch %>%
  mutate(number = n())

What happened? What do you think n() does?

Now try this:

pitch %>%
  group_by(speaker)%>%
  mutate(number = n())

What’s different this time?

Split-Apply-Combine for other data types

Data frames are not the only kind of data that you might want to use the Split-Apply-Combine workflow on. For example, the joe_vowel_files directory (that you unzipped earlier) contains one .csv for each vowel class from when I was an interviewer once. This is an unconventional data organization scheme, but not unheard of. For example, you might have a separate file of vowel data for each speaker.

We can use a variant of the Split-Apply-Combine workflow to load and combine all of these files at once.

~2 minute activity

This part may require some assistance. The Sys.glob() function searchers for all files that match the pattern you specify. The command below says “List all files that end in csv that are in the ../data/joe_vowel_files/ directory”. The preceeding .. in the path means that it should start the search from one directory above where the R notebook is saved. If you have set up your course directory as recommended, this should work. If you don’t get output that looks like this, I’ll come around and try to figure out why.

Sys.glob("../data/joe_vowel_files/*csv")
 [1] "../data/joe_vowel_files/*hr.csv"
 [2] "../data/joe_vowel_files/Tuw.csv"
 [3] "../data/joe_vowel_files/ae.csv" 
 [4] "../data/joe_vowel_files/ahr.csv"
 [5] "../data/joe_vowel_files/aw.csv" 
 [6] "../data/joe_vowel_files/ay.csv" 
 [7] "../data/joe_vowel_files/ay0.csv"
 [8] "../data/joe_vowel_files/e.csv"  
 [9] "../data/joe_vowel_files/ey.csv" 
[10] "../data/joe_vowel_files/eyF.csv"
[11] "../data/joe_vowel_files/i.csv"  
[12] "../data/joe_vowel_files/iy.csv" 
[13] "../data/joe_vowel_files/iyF.csv"
[14] "../data/joe_vowel_files/iyr.csv"
[15] "../data/joe_vowel_files/o.csv"  
[16] "../data/joe_vowel_files/oh.csv" 
[17] "../data/joe_vowel_files/ow.csv" 
[18] "../data/joe_vowel_files/owF.csv"
[19] "../data/joe_vowel_files/owr.csv"
[20] "../data/joe_vowel_files/uh.csv" 
[21] "../data/joe_vowel_files/uw.csv" 

Assign this vector of file names to the variable vowel_files.

vowel_files <- Sys.glob("../data/joe_vowel_files/*csv")

If we apply read.csv() to the first vowel file name, it will read it in as a data frame.

read.csv(vowel_files[1])

But there are 21 vowel files, and what we don’t want to do is write out something like vowel_2 <- read_csv(vowel_files[2]) 21 times! Even copy-pasting will involve errors.

To Split-Apply-Combine read_csv() to the vector of file names vowel_files, we’ll use map().

map(list, function, …)

  • list
    • the list of things that you want to split up
  • function
    • the function that you want to apply to every item in the list
  • ...
    • any extra arguments you need to pass to the function.

It’s easiest to see this at work:

all_vowels <- map(vowel_files, read.csv)
all_vowels[1]
[[1]]
NA
all_vowels[21]
[[1]]
NA

Here’s what has happened:

  • map() applied read.csv() to each file name.
  • read.csv() read each file in as a dataframe.
  • map() returned a list of dataframes that read.csv() produced.

There’s one last step we need combine all of these dataframes together. bind_rows() will combine all of these dataframes into one big dataframe.

vowels_df <- bind_rows(all_vowels)
head(vowels_df)

Of course, this can be chained together with pipes:

vowel_files %>%
  map(read.csv)%>%
  bind_rows()
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector

Recursive Data Frames

There is one last use of map() that is really useful, but might be a bit mind-bending for right now, but will hopefully start to make more sense as we work through the course, or when you re-read these notes. I wouldn’t necessarilly file this under “required core R competency”, but it is really very useful.

Let’s say we were interested in doing a correlation test between F1 and F2 for all of my vowels (this isn’t an especially interesting thing to do, it’s just for illustration). We could do that like so:

cor.test(vowels_df$F1, vowels_df$F2)

    Pearson's product-moment correlation

data:  vowels_df$F1 and vowels_df$F2
t = -1.9254, df = 327, p-value = 0.05505
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.211579986  0.002279927
sample estimates:
       cor 
-0.1058742 

What if we wanted to do a correlation test, not on all of my vowels together, but within each vowel? Step 1 would be to generalize the line that ran cor.test() above into its own function.

#' @name vowel_cor_test
#' @description conducts a correlation test on the data frame (df) using the columns F1 and F2
vowel_cor_test <- function(df){
  cor.test(df$F1, df$F2)
}
vowel_cor_test(vowels_df)

    Pearson's product-moment correlation

data:  df$F1 and df$F2
t = -1.9254, df = 327, p-value = 0.05505
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.211579986  0.002279927
sample estimates:
       cor 
-0.1058742 

The vowel_cor_test() function we just created is what we want to apply. Next we need to figure out how to split up the data, which we’rem going to do by creating a recursive dataframe:

vowels_nested <- vowels_df %>%
                    group_by(plt_vclass)%>%
                    filter(n() > 5)%>%
                    nest()
vowels_nested

This is the mind bending part. In vowels_nested, the first column is the vowel class, for which there is one row each. In the new data column, the value in each cell is actually a whole data frame.

vowels_nested$data[1]
[[1]]
NA

This means we can now use map() to apply the vowel_cor_test() function to each of these data frames.

vowels_test <- vowels_nested %>%
                  mutate(cor = map(data, vowel_cor_test))
vowels_test

Each item in the cor column is now a correlation test result.

vowels_test$cor[1]
[[1]]

    Pearson's product-moment correlation

data:  df$F1 and df$F2
t = -0.39131, df = 23, p-value = 0.6992
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.4616203  0.3242266
sample estimates:
        cor 
-0.08132266 

We’ve successfully fit a correlation test for every vowel, but the results are kind of trapped away in these strange print outs. We can liberate them with the function tidy() from the broom package. tidy() will convert many statistical tests like this into a dataframe representation.

tidy(vowels_test$cor[[1]])

We actually need to map the tidy() function to the cor column for this to work:

vowels_results <- vowels_test %>%
                    mutate(cor_df = map(cor, tidy))%>%
                    unnest(cor_df)
vowels_results %>%
  arrange(-abs(estimate))

And, as usual, this whole procedure could be chained together into one big analysis.

vowels_df %>%
  group_by(plt_vclass)%>%
  filter(n() > 5)%>%
  nest() %>%
  mutate(cor = map(data, vowel_cor_test),
         cor_df = map(cor, tidy))%>%
  unnest(cor_df)%>%
  arrange(-abs(estimate))
LS0tCnRpdGxlOiAiU3BsaXQtQXBwbHktQ29tYmluZSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogbm9uZQogICAgY3NzOiAuLi9jdXN0b20uY3NzCiAgICB0aGVtZTogZmxhdGx5CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogMwotLS0KCgojIE91dGxpbmUKClRvZGF5LCB3ZSdyZSBnb2luZyB0byBiZSBjb3ZlcmluZyB0d28gcmVhbGx5IGltcG9ydGFudCBjb25jZXB0cwoKMS4gUGlwaW5nCjIuIFNwbGl0LUFwcGx5LUNvbWJpbmUKClBpcGluZyB3YXMgaW50cm9kdWNlZCB0byBSIHdpdGggdGhlIGBtYWdyaXR0cmAgcGFja2FnZSAoZ2V0IGl0PyApLCBhbmQgb3ZlciB0aGUgcGFzdCA0IHllYXJzIGhhcyBkcmFtYXRpY2FsbHkgYWx0ZXJlZCBob3cgcHJvZ3JhbW1pbmcgaW4gUiB3b3Jrcy4gClNwbGl0LUFwcGx5LUNvbWJpbmUgaXMgYSBkYXRhIGFuYWx5c2lzIHRlY2huaXF1ZSB0aGF0IHlvdSBtYXkgaGF2ZSBhbHJlYWR5IHVzZWQsIGJ1dCBpcyBzdHJlYW1saW5lZCBpbiB0aGUgYGRwbHlyYCBhbmQgYHB1cnJyYCBwYWNrYWdlcy4KCgoKIyMgU2V0dXAKPGRpdiBjbGFzcyA9ICJicmVhayBib3giPgo8c3BhbiBjbGFzcyA9ICdiaWctbGFiZWwnPn4yIG1pbnV0ZSBzZXR1cDwvc3Bhbj4KCkNyZWF0ZSBhbmQgc2F2ZSB5b3VyIFIgbm90ZWJvb2sgZm9yIHRvZGF5LiBDbGVhciB0aGUgd29ya3NwYWNlIGluIGNhc2UgdGhlcmUncyBhbnl0aGluZyBsZWZ0IG92ZXIgZnJvbSBsYXN0IHRpbWUuIExvYWQgdGhlIGltcG9ydGFudCBwYWNrYWdlcyBmb3IgdG9kYXkncyB3b3JrOgoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoImJyb29tIikKCmxpYnJhcnkobHNhMjAxNykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoYnJvb20pCmBgYAoKQWxzbywgZG93bmxvYWQgYW5kIHVuemlwIHRoZSBmaWxlIGNhbGxlZCBgam9lX3Zvd2VsX2ZpbGVzLnppcGAgZnJvbSBDYW52YXMuCgo8L2Rpdj4KCjxoci8+CgojIFBpcGluZwoKVGhpcyBpcyB0aGUgbmV3IGZ1bmN0b3Igd2UnbGwgYmUgdXNpbmcgdG9kYXk6Cgo8ZGl2IGNsYXNzID0gImlsbHVzdHJhdGUiPgo8c3BhbiBjbGFzcyA9ICJwb3AiPiU+JTwvc3Bhbj4KPC9kaXY+CgpXZSdsbCBwcm9ub3VuY2UgdGhhdCAicGlwZS4iCgpTbyBmYXIsIGV2ZXJ5IHRpbWUgd2UndmUgdXNlZCBhIGZ1bmN0aW9uLCBsaWtlIGBucm93KClgLCB3ZSd2ZSBwYXNzZWQgaXQgaXRzIGFyZ3VtZW50cyBieSBwdXR0aW5nIHRoZW0gaW5zaWRlIHRoZSBwYXJlbnRoZXNlczoKCmBgYHtyfQpucm93KGl5X2FoKQpgYGAKCldoYXQgYCU+JWAgZG9lcyBpcyB0YWtlIGV2ZXJ5dGhpbmcgdG8gaXRzIGxlZnQsIGFuZCBpbmplY3RzIGl0IGludG8gdGhlIGZpcnN0IGFyZ3VtZW50IG9mIHRoZSBmdW5jdGlvbiB0byBpdHMgcmlnaHQuCgpgYGB7cn0KaXlfYWggJT4lIG5yb3coKQpgYGAKCldlJ2xsIHByb25vdW5jZSB0aGlzICJwaXBpbmcgYGl5X2FoYCBpbnRvIGBucm93KClgLiIKClRoaXMgbWlnaHQgbm90IHNlZW0gc28gZWFydGggc2hhdHRlcmluZyByaWdodCBhd2F5LCBidXQgaXQgcmVhbGx5IHNoaW5lcyB3aGVuIHlvdSB3YW50IHRvIGFwcGx5IGEgZnVuY3Rpb24gdG8gdGhlIG91dHB1dCBvZiB0aGUgcHJldmlvdXMgZnVuY3Rpb24uIExldCdzIHNheSB5b3Ugd2FudGVkIHRvIGNoZWNrIGhvdyBtYW55IHJvd3Mgb2YgCiJpeSIgZGF0YSB0aGVyZSBpcy4gVXNpbmcgbm9ybWFsIGZ1bmN0aW9uYWwgYXBwbGljYXRpb24sIHlvdSdkIGhhdmUgdG8gZG8gdGhpczoKCmBgYHtyfQpucm93KGZpbHRlcihpeV9haCwgcGx0X3ZjbGFzcyA9PSAiaXkiKSkKYGBgCgpIZXJlLCB0aGUgZmlyc3QgZnVuY3Rpb24geW91IGFwcGx5IGlzIHRoZSBtb3N0IGRlZXBseSBlbWJlZGRlZCwgYW5kIHRoZSBsYXN0IGZ1bmN0aW9uIHlvdSBhcHBseSBpcyB0aGUgb3V0ZXJtb3N0LiBTb21lIHN5bnRhY3RpY2lhbnMgZmVlbCB2ZXJ5IGNvbWZvcnRhYmxlIHdpdGggYnJhY2tldCBub3RhdGlvbiBsaWtlIHRoaXMsIGJ1dCBpdCByYXBpZGx5IGdldHMgdW5yZWFkYWJsZSwgYW5kIG1pcy1tYXRjaGluZyBjbG9zaW5nIHBhcmVudGhlc2VzIGlzIGEgdmVyeSBjb21tb24ga2luZCBvZiBlcnJvci4gSGVyZSdzIGhvdyB0aGVzZSBzYW1lIGV4YWN0IG9wZXJhdGlvbnMgbG9vayBsaWtlIHdpdGggdGhlIHBpcGVzLgpgYGB7cn0KaXlfYWggJT4lCiAgZmlsdGVyKHBsdF92Y2xhc3MgPT0gIml5IikgJT4lCiAgbnJvdygpCmBgYAoKVGhpcyBpcyBtdWNoIG1vcmUgcmVhZGFibGUsIGFuZCB0aGUgZnVuY3Rpb25zIGFyZSB2aXN1YWxseSBvcmRlcmVkIGluIHRoZSBzYW1lIG9yZGVyIHRoZXkncmUgYXBwbGllZC4KCgoKClRoZSBiZW5lZml0IG9mIHBpcGluZyBiZWNvbWVzIG1vc3QgYXBwYXJlbnQgaWYgd2UgcmV2aXNpdCBvdXIgZGF0YSBjbGVhbmluZyBwaXBlbGluZSBmcm9tIG91ciBsYXN0IG1lZXRpbmcuIEhlcmUncyBgaXlfYWhgIHdpZGUgYWdhaW4uCgpgYGB7cn0KaXlfYWhfd2lkZQpgYGAKCkxhc3QgdGltZSwgd2UgbWFkZSB0aGlzIGRhdGEgd2lkZSB0byBsb25nIGJ5IGNyZWF0aW5nIGludGVybWVkaWF0ZSBkYXRhIGZyYW1lcy4gSG93ZXZlciwgd2UgY291bGQgZG8gaXQgYWxsIGluIGEgc2luZ2xlIHNob3QgYnkgbmVzdGluZyBmdW5jdGlvbnMgbGlrZSBzbzoKCmBgYHtyfQpzcHJlYWQoCiAgc2VwYXJhdGUoCiAgICBnYXRoZXIoaXlfYWhfd2lkZSwgCiAgICAgICAgICAga2V5ID0gInZvd2VsX2Zvcm1hbnQiLCAKICAgICAgICAgICB2YWx1ZSA9ICJoeiIsIAogICAgICAgICAgIGFoX0YxOml5X0YyKSwgCiAgICAidm93ZWxfZm9ybWFudCIsIAogICAgaW50byA9IGMoInZvd2VsIiwgImZvcm1hbnQiKSwgCiAgICBzZXAgPSAiXyIpLCAKICBmb3JtYW50LCAKICBoeikKYGBgCgpCdXQgZ29vZCBsdWNrIHdyaXRpbmcgdGhhdCB0d2ljZSwgb3Igc2hhcmluZyB5b3VyIGNvZGUgd2l0aCBhbnlvbmUuIE5vdGljZSBob3cgdGhlIGFkZGl0aW9uYWwgYXJndW1lbnRzIGZvciBgc3ByZWFkKClgIGFyZSBtYXhpbWFsbHkgZmFyIGF3YXkgZnJvbSB0aGUgYWN0dWFsIGNhbGwgdG8gdGhlIGZ1bmN0aW9uIGF0IHRoZSB0b3A/CgpQaXBpbmcgbWFrZXMgaXQgbXVjaCBiZXR0ZXIuCgpgYGB7cn0KaXlfYWhfd2lkZSAlPiUKICBnYXRoZXIoa2V5ID0gInZvd2VsX2Zvcm1hbnQiLCB2YWx1ZSA9ICJoeiIsIGFoX0YxOml5X0YyKSAlPiUKICBzZXBhcmF0ZSgidm93ZWxfZm9ybWFudCIsIGludG8gID0gYygidm93ZWwiLCAiZm9ybWFudCIpLCBzZXAgPSAiXyIpICU+JQogIHNwcmVhZChmb3JtYW50LCBoeikKYGBgCgpBbm90aGVyIG5pY2UgdGhpbmcgYWJvdXQgdGhlc2UgY2hhaW5zIGlzIHRoYXQgdGhleSBjYW4gYmUgYnVpbHQgdXAgaW5jcmVtZW50YWxseS4gVGhhdCBpcywganVzdCB0aGUgZmlyc3QgdHdvIGxpbmVzIHdpbGwgd29yazoKCmBgYHtyfQppeV9haF93aWRlICU+JQogIGdhdGhlcihrZXkgPSAidm93ZWxfZm9ybWFudCIsIHZhbHVlID0gImh6IiwgYWhfRjE6aXlfRjIpCmBgYAoKQW5kIHNvIHdpbGwganVzdCB0aGUgZmlzdCB0aHJlZToKCmBgYHtyfQppeV9haF93aWRlICU+JQogIGdhdGhlcihrZXkgPSAidm93ZWxfZm9ybWFudCIsIHZhbHVlID0gImh6IiwgYWhfRjE6aXlfRjIpICU+JQogIHNlcGFyYXRlKCJ2b3dlbF9mb3JtYW50IiwgaW50byAgPSBjKCJ2b3dlbCIsICJmb3JtYW50IiksIHNlcCA9ICJfIikKYGBgCgpUaGUgc2FtZSBpcyAqKm5vdCoqIHRydWUgb2YgdGhlIGVtYmVkZGVkIGZ1bmN0aW9uYWwgYXBwbGljYXRpb24sIGJlY2F1c2UgaXQgZGVwZW5kcyBvbiBtYXRjaGluZyBwYXJlbnRoZXNlcyBiZXR3ZWVuIHRoZSBiZWdpbm5pbmcgYW5kIGVuZC4KCgoKCjxkaXYgY2xhc3MgPSAiYm94IGlkaW9tIj4KPHNwYW4gY2xhc3MgPSAibGFiZWwiPklkaW9tPC9zcGFuPgoKSSBhbSBnb2luZyB0byByZWNvbW1lbmQgdGhhdCB5b3UgcHV0IGEgbmV3IGxpbmUgYWZ0ZXIgZXZlcnkgcGlwZSwgYCU+JWAsIGFuZCBpbmRlbnQgdGhlIGZvbGxvd2luZyBsaW5lIHdpdGggdHdvIHNwYWNlcyB3aGVuIGNoYWluaW5nIHRvZ2V0aGVyIGZ1bmN0aW9ucy4KPC9kaXY+Cgo8ZGl2IGNsYXNzID0gImJveCBicmVhayI+CjxzcGFuIGNsYXNzID0gImJpZy1sYWJlbCI+fjUgbWludXRlIGFjdGl2aXR5PC9zcGFuPgoKUmUtY3JlYXRlIHRoZSBgZnJ1aXRgIGRhdGEgZnJhbWU6CgpgYGB7cn0KZnJ1aXQgPC0gZGF0YS5mcmFtZShwZXJzb24gPSBjKCJPYWtsZXkiLCAiQ2hhcmxpZSIpLAogICAgICAgICAgICAgICAgIGFwcGxlc19ib3VnaHQgPSBjKDUsIDMpLAogICAgICAgICAgICAgICAgIGFwcGxlc19hdGUgPSBjKDEsIDIpLAogICAgICAgICAgICAgICAgIG9yYW5nZXNfYm91Z2h0ID0gYyg1LCA0KSwKICAgICAgICAgICAgICAgICBvcmFuZ2VzX2F0ZSA9IGMoMywgMykpCmBgYAoKVXNpbmcgcGlwZXMsIHJlY3JlYXRlIHRoZSBgZ2F0aGVyYCwgYHNlcGFyYXRlYCBhbmQgYHNwcmVhZGAgd29ya2Zsb3cgZnJvbSB0aGUgbGFzdCBtZWV0aW5nIHRvIHByb2R1Y2UgdGhpcyB0YWJsZSAoeW91IGNhbiBkb3VibGUgY2hlY2sgdGhlIGNvZGUgZnJvbSBsYXN0IHRpbWUpLgoKPGRpdiBzdHlsZSA9ICJ3aWR0aDo1MCU7IG1hcmdpbjphdXRvO2JhY2tncm91bmQtY29sb3I6ICNERUY3RjUiPgpgYGB7ciBlY2hvID0gRiwgcmVzdWx0cyA9ICdhc2lzJ30KbGlicmFyeShrbml0cikKCmZydWl0ICU+JQogIGdhdGhlcihrZXksIHZhbHVlLCAyOjUpJT4lCiAgc2VwYXJhdGUoa2V5LCBpbnRvID0gYygiZnJ1aXQiLCAiYmVoYXZpb3IiKSwgc2VwID0gIl8iKSU+JQogIHNwcmVhZChiZWhhdmlvciwgdmFsdWUpJT4lCiAga2FibGUoKQpgYGAKPC9kaXY+CgoKPC9kaXY+CgojIFNwbGl0LUFwcGx5LUNvbWluZQoKV2hlbiBkb2luZyBkYXRhIGFuYWx5c2lzLCB5b3UncmUgZ29pbmcgdG8gZmluZCB5b3Vyc2VsZiBkb2luZyB0aGVzZSBmb2xsb3dpbmcgc3RlcHMgYSBsb3Q6CgoxLiBTcGxpdHRpbmcgdGhlIGRhdGEgdXAgaW50byBzdWJzZXRzLgoyLiBBcHBseWluZyBzb21lIGtpbmQgb2YgZnVuY3Rpb24gdG8gdGhvc2Ugc3Vic2V0cy4KMy4gQ29tYmluaW5nIHRoZSByZXN1bHRzIGJhY2sgdG9nZXRoZXIKCgpIZXJlIGlzIHNvbWUgZmFrZSBkYXRhIGZyb20gYW4gZXhwZXJpbWVudCBvZiBzcGVha2VycycgZnVuZGFtZW50YWwgZnJlcXVlbmN5LiBHbyBhaGVhZCBhbmQgcnVuIHRoaXMgY29kZToKCmBgYHtyfQogIHBpdGNoIDwtIGRhdGFfZnJhbWUoc3BlYWtlciA9IHJlcChjKCJPYWtsZXkiLCAiQ2hhcmxpZSIsICJBemFyaWEiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gIGMoMywzLDIpKSwKICAgICAgICAgICAgICAgICAgICAgICBGMCA9IGMoMTI0LCAxMDksIDEyNSwgMTAzLCAxMjEsIDEyMywgMTgxLCAyMDYpKQpgYGAKCjxkaXYgc3R5bGU9IndpZHRoOjI1JSI+CgpgYGB7ciBlY2hvID0gRiwgcmVzdWx0cyA9ICdhc2lzJ30KICBwaXRjaCAlPiUgCiAgICBrYWJsZSgpCmBgYAoKPC9kaXY+CgpPbmUgdGhpbmcgd2UgbWlnaHQgd2FudCB0byBrbm93IGlzIHdoYXQgdGhlIGF2ZXJhZ2UgRjAgaXMgZm9yIGVhY2ggc3BlYWtlci4gSGVyZSdzIGhvdyB3ZSBkbyB0aGF0LCBjb25jZXB0dWFsbHksIHdpdGggdGhlIFNwbGl0LUFwcGx5LUNvbWJpbmUgd29ya2Zsb3cuCgojIyBTcGxpdCB0aGUgZGF0YSB1cAoKRmlyc3QsIHNwbGl0IHRoZSBkYXRhIHVwIGludG8gc3Vic2V0cyBiYXNlZCBvbiB0aGUgInNwZWFrZXIiIGNvbHVtbjoKCjxkaXYgc3R5bGU9IndpZHRoOjEwMCU7ZmxvYXQ6bGVmdDsiPgpgYGB7ciBlY2hvID0gRiwgcmVzdWx0cz0nYXNpcyd9CmZvcihpIGluIHNvcnQodW5pcXVlKHBpdGNoJHNwZWFrZXIpKSl7CiAgY2F0KCI8ZGl2IHN0eWxlPSd3aWR0aDozMCU7ZmxvYXQ6bGVmdDttYXJnaW46MTBweDsnPlxuXG4iKQogIGZvbyA8LSBrYWJsZShzdWJzZXQocGl0Y2gsIHNwZWFrZXIgPT0gaSkpCiAgZm9yKGsgaW4gZm9vKXsKICAgIGNhdChrKQogICAgY2F0KCJcbiIpCiAgfQogIGNhdCgiXG4iKQogIGNhdCgiPC9kaXY+XG4iKQp9CmNhdCgiPC9icj4iKQpgYGAKPC9kaXY+CgojIyBBcHBseSBzb21lIGZ1bmN0aW9uIHRvIHRoZSBkYXRhCgpJbiBlYWNoIHN1YnNldCwgY2FsY3VsYXRlIHRoZSBhdmVyYWdlIEYuCgo8ZGl2IHN0eWxlPSJ3aWR0aDoxMDAlO2Zsb2F0OmxlZnQ7Ij4KYGBge3IgZWNobyA9IEYsIHJlc3VsdHM9J2FzaXMnfQpmb3IoaSBpbiBzb3J0KHVuaXF1ZShwaXRjaCRzcGVha2VyKSkpewogIGNhdCgiPGRpdiBzdHlsZT0nd2lkdGg6MjUlO2Zsb2F0OmxlZnQ7bWFyZ2luOjEwcHg7Jz5cblxuIikKICBmb28gPC0gcGl0Y2ggJT4lCiAgICBmaWx0ZXIoc3BlYWtlciA9PSBpKSU+JQogICAgZ3JvdXBfYnkoc3BlYWtlciklPiUKICAgIHN1bW1hcmlzZShtZWFuX0YwID0gbWVhbihGMCkpJT4lCiAgICBrYWJsZSgpCiAgZm9yKGsgaW4gZm9vKXsKICAgIGNhdChrKQogICAgY2F0KCJcbiIpCiAgfQogIGNhdCgiXG4iKQogIGNhdCgiPC9kaXY+XG4iKQp9CmNhdCgiPC9icj4iKQoKYGBgCjwvZGl2PgoKCiMjIENvbWJpbmUgdGhlIHJlc3VsdAoKQ29tYmluZSB0aGVzZSByZXN1bHRzIGludG8gYSBuZXcgdGFibGUuCgo8ZGl2IHN0eWxlPSJ3aWR0aDoyNSUiPgoKYGBge3IgZWNobyA9IEYsIHJlc3VsdHMgPSAnYXNpcyd9CiAgcGl0Y2ggJT4lIAogICAgZ3JvdXBfYnkoc3BlYWtlcikgJT4lCiAgICBzdW1tYXJpc2UobWVhbl9GMCA9IG1lYW4oRjApKSU+JQogICAga2FibGUoKQpgYGAKCjwvZGl2PgoKCiMjIFNwbGl0LUFwcGx5LUNvbWJpbmUgaW4gYGRwbHlyYAoKVGhlIGBkcGx5cmAgcGFja2FnZSB3YXMgd3JpdHRlbiBzcGVjaWZpY2FsbHkgdG8gc3RyZWFtbGluZSB0aGUgU3BsaXQtQXBwbHktQ29tYmluZSB3b3JrZmxvdy4gQWxsIGBkcGx5cmAgInZlcmJzIiB0YWtlIGEgZGF0YSBmcmFtZSBhcyBpbnB1dCwgZG9lcyBzb21ldGhpbmcgdG8gdGhlbSwgYW5kIHJldHVybnMgYW5vdGhlciBkYXRhIGZyYW1lLiBMZXQncyBzdGFydCB3aXRoIHRoZSBgc3VtbWFyaXNlKClgIGZ1bmN0aW9uLgoKPGRpdiBjbGFzcyA9ICJpbGx1c3RyYXRlIj4Kc3VtbWFyaXNlKDxzcGFuIGNsYXNzID0gInBvcCI+ZGF0YTwvc3Bhbj4sIDxici8+PHNwYW4gY2xhc3MgPSAicG9wIj5uZXdfY29sMSA9IHN1bW1hcnlfZnVuY3Rpb24xPC9zcGFuPiw8YnIgLz4gPHNwYW4gY2xhc3MgPSAicG9wIj5uZXdfY29sMiA9IHN1bW1hcnlfZnVuY3Rpb24yPC9zcGFuPikKPC9kaXY+CgotIGBkYXRhYCAKICAgIC0gVGhlIGRhdGEgZnJhbWUgeW91IHdhbnQgdG8gc3VtbWFyaXNlCi0gc3VtbWFyeSBmdW5jdGlvbnMKICAgIC0gVGhpcyBpc24ndCBhIG5hbWVkIGFyZ3VtZW50LiBIZXJlLCB5b3UgZ2l2ZSBgc3VtbWFyaXNlKClgIGEgZnVuY3Rpb24gdGhhdCBjYW4gdGFrZSBtYW55IHZhbHVlcyBhcyBpbnB1dCwgYW5kIHJldHVybnMgYSBzaW5nbGUgdmFsdWUgYXMgb3V0cHV0LgogICAgCkZvciBleGFtcGxlLCBsZXQncyBjYWxjdWxhdGUgdGhlIG1lYW4gRjAgYWNyb3NzIGFsbCBvZiB0aGUgZGF0YSBpbiB0aGUgYHBpdGNoYCBkYXRhIGZyYW1lLgoKCmBgYHtyfQpwaXRjaCAlPiUKICBzdW1tYXJpc2UobWVhbl9GMCA9IG1lYW4oRjApKQpgYGAKClRoaXMgZG9lc24ndCBsb29rIGxpa2UgbXVjaCwgYmVjYXVzZSBpdCdzIGp1c3QgYSBvbmUgcm93IGRhdGEgZnJhbWUgd2l0aCBvbmUgY29sdW1uIGNhbGxlZCBgbWVhbl9GMGAgd2l0aCBvbmUgdmFsdWUsIHdoaWNoIGlzIHRoZSBtZWFuIG9mIGFsbCBvZiB0aGUgRjAgbWVhc3VyZW1lbnRzIGZyb20gZWFjaCBzcGVha2VyLgoKSXQncyBhbHNvIHBvc3NpYmxlIHRvIGRvIG11bHRpcGxlIHN1bW1hcmllcyBhdCBvbmNlIGJ5IGp1c3QgYWRkaW5nIG1vcmUgc3VtbWFyeSBmdW5jdGlvbnMgdG8gdGhlIGBzdW1tYXJpc2UoKWAgdmVyYi4KCgoKYGBge3J9CnBpdGNoICU+JQogIHN1bW1hcmlzZShtZWFuX0YwID0gbWVhbihGMCksCiAgICAgICAgICAgIG1lZGlhbl9GMCA9IG1lZGlhbihGMCksCiAgICAgICAgICAgIHNkX0YwID0gc2QoRjApLAogICAgICAgICAgICBtYWRfRjAgPSBtYWQoRjApLAogICAgICAgICAgICByYW5nZV9GMCA9IG1heChGMCktbWluKEYwKSwKICAgICAgICAgICAgbmRhdGEgPSBsZW5ndGgoc3BlYWtlcikpCmBgYAoKVGhlIGBzdW1tYXJpc2UoKWAgZnVuY3Rpb24gaXMgd2hhdCB3ZSB3YW50IHRvICphcHBseSogdG8gb3VyIGRhdGEsIGJ1dCBmb3IgdGhlIFNwbGl0LUFwcGx5LUNvbWJpbmUgd29ya2Zsb3csIHdlIHdhbnQgdG8gKnNwbGl0KiB0aGUgZGF0YSBhY2NvcmRpbmcgdG8gdGhlIHNwZWFrZXIsICphcHBseSogdGhlIGBzdW1tYXJpc2UoKWAgZnVuY3Rpb24gdG8gZWFjaCBzdWJzZXQsIHRoZW4gKmNvbWJpbmUqIHRoZSByZXN1bHRzIGJhY2sgdG9nZXRoZXIuIAoKVG8gc3BsaXQgdXAgdGhlIGRhdGEsIHdlIGZpcnN0IHBhc3MgdGhlIGBwaXRjaGAgZGF0YSBmcmFtZSB0byBgZ3JvdXBfYnkoKWAuCgo8ZGl2IGNsYXNzPSAiaWxsdXN0cmF0ZSI+Cmdyb3VwX2J5KDxzcGFuIGNsYXNzID0gInBvcCI+ZGF0YTwvc3Bhbj4sIDxzcGFuIGNsYXNzID0gInBvcCI+Z3JvdXBBPC9zcGFuPiwgPHNwYW4gY2xhc3MgPSAicG9wIj5ncm91cEI8L3NwYW4+KQo8L2Rpdj4KCi0gYGRhdGFgCiAgICAtIFRoZSBkYXRhIGZyYW1lIHlvdSB3YW50IHRvIGFwcGx5IFNwbGl0LUFwcGx5LUNvbWJpbmUgdG8uCi0gZ3JvdXBpbmdzCiAgICAtIFRoZXNlIGFyZSB0aGUgbmFtZXMgb2YgY29sdW1ucyBpbiB0aGUgZGF0YSBmcmFtZSB0aGF0IHlvdSB3YW50IHRvIHNwbGl0IHRoZSBkYXRhIHVwIGJ5LiBgZ3JvdXBfYnkoKWAgd2lsbCBjcmVhdGUgc3Vic2V0cyBvZiBldmVyeSB1bmlxdWUgY29tYmluYXRpb24gb2YgdmFsdWVzIGluIHRoZXNlIGNvbHVtbnMuCiAgICAKV2hlbiB5b3UgYXBwbHkgYGdyb3VwX2J5KClgIHRvIGEgZGF0YSBmcmFtZSBieSBpdHNlbGYsIG5vdGhpbmcgcmVhbGx5IHNwZWNpYWwgaGFwcGVuczoKCmBgYHtyfQpwaXRjaCAlPiUgCiAgZ3JvdXBfYnkoc3BlYWtlcikKYGBgCgpCdXQgd2hlbiB5b3UgYXBwbHkgYHN1bW1hcmlzZSgpYCBhZnRlciBgZ3JvdXBfYnkoKWAsIHlvdSBnZXQgdGhlIG1lYW4gRjAgZm9yIGVhY2ggc3BlYWtlcjoKCmBgYHtyfQpwaXRjaCAlPiUgCiAgZ3JvdXBfYnkoc3BlYWtlcikgJT4lCiAgc3VtbWFyaXNlKG1lYW5fRjAgPSBtZWFuKEYwKSkKYGBgCgpIZXJlJ3Mgd2hhdCBqdXN0IGhhcHBlbmVkLgoKMS4gYGdyb3VwX2J5KClgIHRvIHNwbGl0IHRoZSBkYXRhIGZyYW1lIHVwIGFjY29yZGluZyB0byB0aGUgdW5pcXVlIHZhbHVlcyBvZiBgc3BlYWtlcmAuCjIuIGBzdW1tYXJpc2UoKWAgY2FsY3VsYXRlZCB0aGUgbWVhbiBGMCB3aXRoaW4gZWFjaCBzdWJzZXQuCjMuIFRoZXkgd2VyZSB0aGVuIGF1dG9tYXRpY2FsbHkgY29tYmluZWQgYmFjayB0b2dldGhlci4KCgo8ZGl2IGNsYXNzID0gImJveCBicmVhayI+CjxzcGFuIGNsYXNzID0gImJpZy1sYWJlbCI+fjUgbWludXRlIGFjdGl2aXR5PC9zcGFuPgoKSnVzdCB0byByZWZyZXNoIHlvdXIgbWVtb3JpZXMsIHRoZSBgaXlfYWhgIGRhdGEgZnJhbWUgY29udGFpbnMgYWxsIGRhdGEgZnJvbSB0aGUgdm93ZWxzIC9pOi8gYW5kIC/JkS8gZnJvbSB0aGUgUE5DLiBUaGUgY29sdW1uIGBpZHN0cmluZ2AgaXMgYSB1bmlxdWUgSUQgZm9yIGVhY2ggc3BlYWtlciwgYW5kIGBwbHRfdmNsYXNzYCBjb2RlcyB0aGUgdm93ZWwgY2xhc3MuCgpDYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgRjEgYW5kIEYyIGZvciAvaTovIGFuZCAvyZEvIGZvciBlYWNoIHNwZWFrZXIuCgo8L2Rpdj4KCgojIyMgQWRkaXRpb25hbCBgZHBseXJgIHZlcmJzCgpIZXJlIGFyZSBhIGZldyBtb3JlIGBkcGx5cmAgdmVyYnMgdGhhdCB5b3UgY2FuIHN0cmluZyB0b2dldGhlci4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnZlcmIgICAgICAgICAgIGRlc2NyaXB0aW9uICAgICAgICAgICAgICAgCi0tLS0tLS0tLS0tLS0gIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiBgZmlsdGVyKClgICAgICBXZSd2ZSBhbHJlYWR5IHVzZWQgdGhpcyB0byBzdWJzZXQgZGF0YWZyYW1lcy4gCgpgc3VtbWFyaXNlKClgICAgIFRoaXMgdGFrZXMgYSBkYXRhIGZyYW1lLCBhbmQgb3V0cHV0cyBhIG5ldwogICAgICAgICAgICAgICAgIGRhdGEgZnJhbWUgYmFzZWQgb24gdGhlIHN1bW1hcnkgeW91IGFza2VkIGZvcgoKYG11dGF0ZSgpYCAgICAgIFRoaXMgdGFrZXMgYSBkYXRhIGZyYW1lLCBhbmQgYWRkcyBhZGRpdGlvbmFsIAogICAgICAgICAgICAgICAgY29sdW1ucyBiYXNlZCBvbiB0aGUgZm9ybXVsYSB5b3UgZ2l2ZSBpdAoKYHNlbGVjdCgpYCAgICAgIFRoaXMgdGFrZXMgYSBkYXRhIGZyYW1lLCBhbmQgcmV0dXJucyBvbmx5IHRoZSBjb2x1bW5zIHlvdSBhc2sgZm9yIAoKYGFycmFuZ2UoKWAgICAgIFJlb3JkZXJzIHRoZSByb3dzIG9mIHRoZSBkYXRhIGZyYW1lIAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KClRoZSBvbmUgZnVuY3Rpb24gaGVyZSB0aGF0IHJlcXVpcmVzIHNvbWUgYWRkaXRpb25hbCBpbnN0cnVjdGlvbiBpcyBgbXV0YXRlKClgLgoKPGRpdiBjbGFzcyA9ICJpbGx1c3RyYXRlIj4KbXV0YXRlKDxzcGFuIGNsYXNzID0gInBvcCI+ZGF0YTwvc3Bhbj4sIDxzcGFuIGNsYXNzID0gInBvcCI+bmV3X2NvbCA9IHZhbHVlPC9zcGFuPikKPC9kaXY+CgotIGBkYXRhYAogICAgLSB0aGUgZGF0YSB0aGF0IHlvdSB3YW50IHRvIG11dGF0ZQotIG5ldyBjb2x1bW5zCiAgICAtIEhlcmUsIHlvdSBuYW1lIG5ldyBjb2x1bW5zLCBhbmQgcHJvdmlkZSBzb21lIGtpbmQgb2YgdmFsdWUsIHVzdWFsbHkgdXNpbmcgc29tZSBvdGhlciBmdW5jdGlvbi4KICAgClRoZSBzaW1wbGVzdCBhbmQgbGVhc3QgdXNlZnVsIHVzZSBvZiBgbXV0YXRlKClgIGFkZHMgYSBuZXcgY29sdW1uIHdpdGggYSBzaW5nbGUgdmFsdWU6CgpgYGB7cn0KcGl0Y2ggJT4lCiAgbXV0YXRlKGphd24gPSAiZm9vIikKYGBgCgpIb3dldmVyLCBhIG11Y2ggbW9yZSB1c2VmdWwgdXNlIG9mIGBtdXRhdGUoKWAgY3JlYXRlcyB0cmFuc2Zvcm1lZCBjb2x1bW5zLiBGb3IgZXhhbXBsZSwgc29tZXRpbWVzIHdoZW4gd29ya2luZyB3aXRoIHBpdGNoLCB5b3UgbWlnaHQgZXN0aW1hdGUgcGl0Y2ggZGlmZmVyZW5jZXMgaW4gdGVybXMgb2Ygc2VtaXRvbmVzLiBFdmVyeSBkb3VibGluZyBvZiBwaXRjaCBpcyAxMiBzZW1pdG9uZXMuIEl0J3MgdGhlIGNhc2UgdGhhdCBpZiB5b3UgY2FsY3VsYXRlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGxvZzIgdHJhbnNmb3JtIG9mIGFueSB0d28gbnVtYmVycyB0aGF0IGFyZSBkb3VibGVzIG9mIGVhY2hvdGhlciwgeW91IGdldCAxLgoKYGBge3J9CiMgMSBvY3RhdmUgZGlmZmVyZW5jZXMKbG9nMig2KSAtIGxvZzIoMykKbG9nMigxOCkgLSBsb2cyKDkpCgojIDIgb2N0YXZlIGRpZmZlcmVuY2VzCmxvZzIoMTIpIC0gbG9nMigzKQpgYGAKCklmIHlvdSB3YW50ZWQgdG8gZXN0aW1hdGUsIGZvciBlYWNoIHNwZWFrZXIsIHRoZWlyIHBpdGNoIHJhbmdlIGluIHRlcm1zIG9mIHNlbWl0b25lcywgc3RlcCAxIHdvdWxkIGJlIHRvIGNvbnZlcnQgdGhlaXIgRjAgaW4gSHogaW50byBsb2cyKEh6KS4KCmBgYHtyfQpwaXRjaCAlPiUKICBtdXRhdGUobG9nMl9GMCA9IGxvZzIoRjApKQpgYGAKClRoZW4sIGZvciBldmVyeSBzcGVha2VyLCBzdWJ0cmFjdCB0aGVpciBtYXggYGxvZzJfRjBgIGZyb20gdGhlaXIgbWluIGBsb2cyX0YwYC4gCgpgYGB7cn0KcGl0Y2ggJT4lCiAgbXV0YXRlKGxvZzJfRjAgPSBsb2cyKEYwKSkgJT4lCiAgZ3JvdXBfYnkoc3BlYWtlciklPiUKICBzdW1tYXJpc2Uob2N0YXZlX3JhbmdlID0gbWF4KGxvZzJfRjApIC0gbWluKGxvZzJfRjApLAogICAgICAgICAgICBzZW1pdG9uZV9yYW5nZSA9IG9jdGF2ZV9yYW5nZSAqIDEyKQpgYGAKCjxkaXYgY2xhc3MgPSAiYm94IGJyZWFrIj4KPHNwYW4gY2xhc3MgPSAiYmlnLWxhYmVsIj5+NSBtaW51dGUgYWN0aXZpdHk8L3NwYW4+CgpUcnkgdGhlIGZvbGxvd2luZzoKCmBgYHtyfQpwaXRjaCAlPiUKICBtdXRhdGUobnVtYmVyID0gbigpKQpgYGAKCldoYXQgaGFwcGVuZWQ/IFdoYXQgZG8geW91IHRoaW5rIGBuKClgIGRvZXM/CgpOb3cgdHJ5IHRoaXM6CmBgYHtyfQpwaXRjaCAlPiUKICBncm91cF9ieShzcGVha2VyKSU+JQogIG11dGF0ZShudW1iZXIgPSBuKCkpCmBgYAoKV2hhdCdzIGRpZmZlcmVudCB0aGlzIHRpbWU/Cgo8L2Rpdj4KCgoKIyBTcGxpdC1BcHBseS1Db21iaW5lIGZvciBvdGhlciBkYXRhIHR5cGVzCgpEYXRhIGZyYW1lcyBhcmUgbm90IHRoZSBvbmx5IGtpbmQgb2YgZGF0YSB0aGF0IHlvdSBtaWdodCB3YW50IHRvIHVzZSB0aGUgU3BsaXQtQXBwbHktQ29tYmluZSB3b3JrZmxvdyBvbi4gRm9yIGV4YW1wbGUsIHRoZSBgam9lX3Zvd2VsX2ZpbGVzYCBkaXJlY3RvcnkgKHRoYXQgeW91IHVuemlwcGVkIGVhcmxpZXIpIGNvbnRhaW5zIG9uZSAuY3N2IGZvciBlYWNoIHZvd2VsIGNsYXNzIGZyb20gd2hlbiBJIHdhcyBhbiBpbnRlcnZpZXdlciBvbmNlLiBUaGlzIGlzIGFuIHVuY29udmVudGlvbmFsIGRhdGEgb3JnYW5pemF0aW9uIHNjaGVtZSwgYnV0IG5vdCB1bmhlYXJkIG9mLiBGb3IgZXhhbXBsZSwgeW91IG1pZ2h0IGhhdmUgYSBzZXBhcmF0ZSBmaWxlIG9mIHZvd2VsIGRhdGEgZm9yIGVhY2ggc3BlYWtlci4KCldlIGNhbiB1c2UgYSB2YXJpYW50IG9mIHRoZSBTcGxpdC1BcHBseS1Db21iaW5lIHdvcmtmbG93IHRvIGxvYWQgYW5kIGNvbWJpbmUgKmFsbCogb2YgdGhlc2UgZmlsZXMgYXQgb25jZS4KCjxkaXYgY2xhc3MgPSAiYnJlYWsgYm94Ij4KCjxzcGFuIGNsYXNzPSAiYmlnLWxhYmVsIj5+MiBtaW51dGUgYWN0aXZpdHk8L3NwYW4+CgpUaGlzIHBhcnQgbWF5IHJlcXVpcmUgc29tZSBhc3Npc3RhbmNlLiBUaGUgYFN5cy5nbG9iKClgIGZ1bmN0aW9uIHNlYXJjaGVycyBmb3IgYWxsIGZpbGVzIHRoYXQgbWF0Y2ggdGhlIHBhdHRlcm4geW91IHNwZWNpZnkuIFRoZSBjb21tYW5kIGJlbG93IHNheXMgIkxpc3QgYWxsIGZpbGVzIHRoYXQgZW5kIGluIGBjc3ZgIHRoYXQgYXJlIGluIHRoZSBgLi4vZGF0YS9qb2Vfdm93ZWxfZmlsZXMvYCBkaXJlY3RvcnkiLiBUaGUgcHJlY2VlZGluZyBgLi5gIGluIHRoZSBwYXRoIG1lYW5zIHRoYXQgaXQgc2hvdWxkIHN0YXJ0IHRoZSBzZWFyY2ggZnJvbSBvbmUgZGlyZWN0b3J5IGFib3ZlIHdoZXJlIHRoZSBSIG5vdGVib29rIGlzIHNhdmVkLiBJZiB5b3UgaGF2ZSBzZXQgdXAgeW91ciBjb3Vyc2UgZGlyZWN0b3J5IGFzIHJlY29tbWVuZGVkLCB0aGlzIHNob3VsZCB3b3JrLiBJZiB5b3UgZG9uJ3QgZ2V0IG91dHB1dCB0aGF0IGxvb2tzIGxpa2UgdGhpcywgSSdsbCBjb21lIGFyb3VuZCBhbmQgdHJ5IHRvIGZpZ3VyZSBvdXQgd2h5LgoKYGBge3J9ClN5cy5nbG9iKCIuLi9kYXRhL2pvZV92b3dlbF9maWxlcy8qY3N2IikKYGBgCgo8L2Rpdj4KCkFzc2lnbiB0aGlzIHZlY3RvciBvZiBmaWxlIG5hbWVzIHRvIHRoZSB2YXJpYWJsZSBgdm93ZWxfZmlsZXNgLgoKYGBge3J9CnZvd2VsX2ZpbGVzIDwtIFN5cy5nbG9iKCIuLi9kYXRhL2pvZV92b3dlbF9maWxlcy8qY3N2IikKYGBgCgoKSWYgd2UgYXBwbHkgYHJlYWQuY3N2KClgIHRvIHRoZSBmaXJzdCB2b3dlbCBmaWxlIG5hbWUsIGl0IHdpbGwgcmVhZCBpdCBpbiBhcyBhIGRhdGEgZnJhbWUuCgpgYGB7cn0KcmVhZC5jc3Yodm93ZWxfZmlsZXNbMV0pCmBgYAoKQnV0IHRoZXJlIGFyZSAyMSB2b3dlbCBmaWxlcywgYW5kIHdoYXQgd2UgZG9uJ3Qgd2FudCB0byBkbyBpcyB3cml0ZSBvdXQgc29tZXRoaW5nIGxpa2UgYHZvd2VsXzIgPC0gcmVhZF9jc3Yodm93ZWxfZmlsZXNbMl0pYCAyMSB0aW1lcyEgRXZlbiBjb3B5LXBhc3Rpbmcgd2lsbCBpbnZvbHZlIGVycm9ycy4gCgpUbyBTcGxpdC1BcHBseS1Db21iaW5lIGByZWFkX2NzdigpYCB0byB0aGUgdmVjdG9yIG9mIGZpbGUgbmFtZXMgYHZvd2VsX2ZpbGVzYCwgd2UnbGwgdXNlIGBtYXAoKWAuCgo8ZGl2IGNsYXNzID0gImlsbHVzdHJhdGUiPgptYXAoPHNwYW4gY2xhc3MgPSAicG9wIj5saXN0PC9zcGFuPiwgPHNwYW4gY2xhc3MgPSAicG9wIj5mdW5jdGlvbjwvc3Bhbj4sIC4uLikKPC9kaXY+CgotIGBsaXN0YAogICAgLSB0aGUgbGlzdCBvZiB0aGluZ3MgdGhhdCB5b3Ugd2FudCB0byBzcGxpdCB1cAotIGBmdW5jdGlvbmAKICAgIC0gdGhlIGZ1bmN0aW9uIHRoYXQgeW91IHdhbnQgdG8gYXBwbHkgdG8gZXZlcnkgaXRlbSBpbiB0aGUgbGlzdAotIGAuLi5gCiAgICAtIGFueSBleHRyYSBhcmd1bWVudHMgeW91IG5lZWQgdG8gcGFzcyB0byB0aGUgZnVuY3Rpb24uCiAgICAKSXQncyBlYXNpZXN0IHRvIHNlZSB0aGlzIGF0IHdvcms6CgpgYGB7cn0KYWxsX3Zvd2VscyA8LSBtYXAodm93ZWxfZmlsZXMsIHJlYWQuY3N2KQpgYGAKCmBgYHtyfQphbGxfdm93ZWxzWzFdCmBgYApgYGB7cn0KYWxsX3Zvd2Vsc1syMV0KYGBgCgoKSGVyZSdzIHdoYXQgaGFzIGhhcHBlbmVkOgoKLSBgbWFwKClgIGFwcGxpZWQgYHJlYWQuY3N2KClgIHRvIGVhY2ggZmlsZSBuYW1lLgotIGByZWFkLmNzdigpYCByZWFkIGVhY2ggZmlsZSBpbiBhcyBhIGRhdGFmcmFtZS4KLSBgbWFwKClgIHJldHVybmVkIGEgbGlzdCBvZiBkYXRhZnJhbWVzIHRoYXQgYHJlYWQuY3N2KClgIHByb2R1Y2VkLgoKVGhlcmUncyBvbmUgbGFzdCBzdGVwIHdlIG5lZWQgY29tYmluZSBhbGwgb2YgdGhlc2UgZGF0YWZyYW1lcyB0b2dldGhlci4gYGJpbmRfcm93cygpYCB3aWxsIGNvbWJpbmUgYWxsIG9mIHRoZXNlIGRhdGFmcmFtZXMgaW50byBvbmUgYmlnIGRhdGFmcmFtZS4KCmBgYHtyfQp2b3dlbHNfZGYgPC0gYmluZF9yb3dzKGFsbF92b3dlbHMpCmBgYAoKCmBgYHtyfQpoZWFkKHZvd2Vsc19kZikKYGBgCgpPZiBjb3Vyc2UsIHRoaXMgY2FuIGJlIGNoYWluZWQgdG9nZXRoZXIgd2l0aCBwaXBlczoKCmBgYHtyfQp2b3dlbF9maWxlcyAlPiUKICBtYXAocmVhZC5jc3YpJT4lCiAgYmluZF9yb3dzKCkKYGBgCgoKIyMgUmVjdXJzaXZlIERhdGEgRnJhbWVzCgpUaGVyZSBpcyBvbmUgbGFzdCB1c2Ugb2YgYG1hcCgpYCB0aGF0IGlzIHJlYWxseSB1c2VmdWwsIGJ1dCBtaWdodCBiZSBhIGJpdCBtaW5kLWJlbmRpbmcgZm9yIHJpZ2h0IG5vdywgYnV0IHdpbGwgaG9wZWZ1bGx5IHN0YXJ0IHRvIG1ha2UgbW9yZSBzZW5zZSBhcyB3ZSB3b3JrIHRocm91Z2ggdGhlIGNvdXJzZSwgb3Igd2hlbiB5b3UgcmUtcmVhZCB0aGVzZSBub3Rlcy4gSSB3b3VsZG4ndCBuZWNlc3NhcmlsbHkgZmlsZSB0aGlzIHVuZGVyICJyZXF1aXJlZCBjb3JlIFIgY29tcGV0ZW5jeSIsIGJ1dCBpdCBpcyByZWFsbHkgdmVyeSB1c2VmdWwuCgpMZXQncyBzYXkgd2Ugd2VyZSBpbnRlcmVzdGVkIGluIGRvaW5nIGEgY29ycmVsYXRpb24gdGVzdCBiZXR3ZWVuIEYxIGFuZCBGMiBmb3IgYWxsIG9mIG15IHZvd2VscyAodGhpcyBpc24ndCBhbiBlc3BlY2lhbGx5IGludGVyZXN0aW5nIHRoaW5nIHRvIGRvLCBpdCdzIGp1c3QgZm9yIGlsbHVzdHJhdGlvbikuIFdlIGNvdWxkIGRvIHRoYXQgbGlrZSBzbzoKCmBgYHtyfQpjb3IudGVzdCh2b3dlbHNfZGYkRjEsIHZvd2Vsc19kZiRGMikKYGBgCgpXaGF0IGlmIHdlIHdhbnRlZCB0byBkbyBhIGNvcnJlbGF0aW9uIHRlc3QsIG5vdCBvbiBhbGwgb2YgbXkgdm93ZWxzIHRvZ2V0aGVyLCBidXQgd2l0aGluIGVhY2ggdm93ZWw/IFN0ZXAgMSB3b3VsZCBiZSB0byBnZW5lcmFsaXplIHRoZSBsaW5lIHRoYXQgcmFuIGBjb3IudGVzdCgpYCBhYm92ZSBpbnRvIGl0cyBvd24gZnVuY3Rpb24uCgpgYGB7cn0KIycgQG5hbWUgdm93ZWxfY29yX3Rlc3QKIycgQGRlc2NyaXB0aW9uIGNvbmR1Y3RzIGEgY29ycmVsYXRpb24gdGVzdCBvbiB0aGUgZGF0YSBmcmFtZSAoZGYpIHVzaW5nIHRoZSBjb2x1bW5zIEYxIGFuZCBGMgp2b3dlbF9jb3JfdGVzdCA8LSBmdW5jdGlvbihkZil7CiAgY29yLnRlc3QoZGYkRjEsIGRmJEYyKQp9CmBgYAoKYGBge3J9CnZvd2VsX2Nvcl90ZXN0KHZvd2Vsc19kZikKYGBgCgpUaGUgYHZvd2VsX2Nvcl90ZXN0KClgIGZ1bmN0aW9uIHdlIGp1c3QgY3JlYXRlZCBpcyB3aGF0IHdlIHdhbnQgdG8gKmFwcGx5Ki4gTmV4dCB3ZSBuZWVkIHRvIGZpZ3VyZSBvdXQgaG93IHRvICpzcGxpdCogdXAgdGhlIGRhdGEsIHdoaWNoIHdlJ3JlbSBnb2luZyB0byBkbyBieSBjcmVhdGluZyBhIHJlY3Vyc2l2ZSBkYXRhZnJhbWU6CgpgYGB7cn0Kdm93ZWxzX25lc3RlZCA8LSB2b3dlbHNfZGYgJT4lCiAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkocGx0X3ZjbGFzcyklPiUKICAgICAgICAgICAgICAgICAgICBmaWx0ZXIobigpID4gNSklPiUKICAgICAgICAgICAgICAgICAgICBuZXN0KCkKdm93ZWxzX25lc3RlZApgYGAKClRoaXMgaXMgdGhlIG1pbmQgYmVuZGluZyBwYXJ0LiBJbiBgdm93ZWxzX25lc3RlZGAsIHRoZSBmaXJzdCBjb2x1bW4gaXMgdGhlIHZvd2VsIGNsYXNzLCBmb3Igd2hpY2ggdGhlcmUgaXMgb25lIHJvdyBlYWNoLiBJbiB0aGUgbmV3IGBkYXRhYCBjb2x1bW4sIHRoZSB2YWx1ZSBpbiBlYWNoIGNlbGwgaXMgYWN0dWFsbHkgYSB3aG9sZSBkYXRhIGZyYW1lLgoKYGBge3J9CnZvd2Vsc19uZXN0ZWQkZGF0YVsxXQpgYGAKClRoaXMgbWVhbnMgd2UgY2FuIG5vdyB1c2UgYG1hcCgpYCB0byBhcHBseSB0aGUgYHZvd2VsX2Nvcl90ZXN0KClgIGZ1bmN0aW9uIHRvIGVhY2ggb2YgdGhlc2UgZGF0YSBmcmFtZXMuCgpgYGB7cn0Kdm93ZWxzX3Rlc3QgPC0gdm93ZWxzX25lc3RlZCAlPiUKICAgICAgICAgICAgICAgICAgbXV0YXRlKGNvciA9IG1hcChkYXRhLCB2b3dlbF9jb3JfdGVzdCkpCnZvd2Vsc190ZXN0CmBgYAoKRWFjaCBpdGVtIGluIHRoZSBgY29yYCBjb2x1bW4gaXMgbm93IGEgY29ycmVsYXRpb24gdGVzdCByZXN1bHQuCgpgYGB7cn0Kdm93ZWxzX3Rlc3QkY29yWzFdCmBgYAoKV2UndmUgc3VjY2Vzc2Z1bGx5IGZpdCBhIGNvcnJlbGF0aW9uIHRlc3QgZm9yIGV2ZXJ5IHZvd2VsLCBidXQgdGhlIHJlc3VsdHMgYXJlIGtpbmQgb2YgdHJhcHBlZCBhd2F5IGluIHRoZXNlIHN0cmFuZ2UgcHJpbnQgb3V0cy4gV2UgY2FuIGxpYmVyYXRlIHRoZW0gd2l0aCB0aGUgZnVuY3Rpb24gYHRpZHkoKWAgZnJvbSB0aGUgYGJyb29tYCBwYWNrYWdlLiBgdGlkeSgpYCB3aWxsIGNvbnZlcnQgbWFueSBzdGF0aXN0aWNhbCB0ZXN0cyBsaWtlIHRoaXMgaW50byBhIGRhdGFmcmFtZSByZXByZXNlbnRhdGlvbi4KCmBgYHtyfQp0aWR5KHZvd2Vsc190ZXN0JGNvcltbMV1dKQpgYGAKCldlIGFjdHVhbGx5IG5lZWQgdG8gbWFwIHRoZSBgdGlkeSgpYCBmdW5jdGlvbiB0byB0aGUgYGNvcmAgY29sdW1uIGZvciB0aGlzIHRvIHdvcms6CgpgYGB7cn0Kdm93ZWxzX3Jlc3VsdHMgPC0gdm93ZWxzX3Rlc3QgJT4lCiAgICAgICAgICAgICAgICAgICAgbXV0YXRlKGNvcl9kZiA9IG1hcChjb3IsIHRpZHkpKSU+JQogICAgICAgICAgICAgICAgICAgIHVubmVzdChjb3JfZGYpCnZvd2Vsc19yZXN1bHRzICU+JQogIGFycmFuZ2UoLWFicyhlc3RpbWF0ZSkpCmBgYAoKQW5kLCBhcyB1c3VhbCwgdGhpcyB3aG9sZSBwcm9jZWR1cmUgY291bGQgYmUgY2hhaW5lZCB0b2dldGhlciBpbnRvIG9uZSBiaWcgYW5hbHlzaXMuCgoKYGBge3J9CnZvd2Vsc19kZiAlPiUKICBncm91cF9ieShwbHRfdmNsYXNzKSU+JQogIGZpbHRlcihuKCkgPiA1KSU+JQogIG5lc3QoKSAlPiUKICBtdXRhdGUoY29yID0gbWFwKGRhdGEsIHZvd2VsX2Nvcl90ZXN0KSwKICAgICAgICAgY29yX2RmID0gbWFwKGNvciwgdGlkeSkpJT4lCiAgdW5uZXN0KGNvcl9kZiklPiUKICBhcnJhbmdlKC1hYnMoZXN0aW1hdGUpKQpgYGAKCgoK