1.Introduction
In this lesson you will learn about aggregates in R using dplyr. An
aggregate statistic is a way of creating a single number that describes
a group of numbers. Common aggregate statistics include mean, median,
and standard deviation.
Additionally, you will learn how you can group data into different
subsets based on column values. This can help narrow the focus of a
summary statistic to a subset of a dataset. R you ready to get
started?
Instructions
In the workspace to the right we have loaded data from ShoeFly.com, a
fictional e-commerce shoe store. The data includes information regarding
customer orders as well as the source of page visits to ShoeFly.com’s
website.
Review the code and the output. What calculations are made, and on
which subsets of the data do they occur?
Throughout the rest of this lesson you will dig into ShoeFly.com’s
data to learn more about its customer’s orders and how users are
discovering the website. Proceed to the next exercise to get
started!
# load packages
library(readr)
library(dplyr)
# load data
orders <- read_csv("orders.csv")
page_visits <- read_csv("page_visits.csv")
# inspect data frames
orders
page_visits
# average price of order
average_price <- orders %>%
summarize(mean_price = mean(price, na.rm = TRUE))
average_price
# page visits by UTM source
click_source <- page_visits %>%
group_by(utm_source) %>%
summarize(count = n())
click_source
# page visits by UTM source and month
click_source_by_month <- page_visits %>%
group_by(utm_source,month) %>%
summarize(count = n())
click_source_by_month
2.Calculating Column Statistics
In this exercise, you will learn how to combine all of the values
from a column for a single calculation. This can be done with the help
of the dplyr function summarize(), which returns a new data frame
containing the desired calculation.
Some examples of this type of calculation include:
1.The data frame customers contains the names and ages of all of your
customers. You want to find the median age:
customers <- data.frame(
name = c("Alice", "Bob", "Claire", "Daniel", "Elaine", "Frank", "Grace"),
age = c(23, 25, 31, 35, 35, 46, 62)
)
customers %>%
select(age)
#c(23, 25, 31, 35, 35, 46, 62)
customers %>%
summarize(median_age = median(age))
#35
2.The data frame shipments contains address information for all
shipments that you’ve sent out in the past year. You want to know how
many different states you have shipped to.
shipments <- data.frame(
states = c('CA', 'CA', 'CA', 'CA', 'NY', 'NY', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ')
)
shipments %>%
summarize(n_distinct_states = n_distinct(states))
#3
3.The data frame inventory contains a list of types of t-shirts that
your company makes. You want to know the standard deviation of the
prices of your inventory.
inventory <- data.frame(
price = c(31, 23, 30, 27, 30, 22, 27, 22, 39, 27, 36)
)
inventory %>%
summarize(sd_price = sd(price))
The general syntax for these calculations is:
df %>% summarize(var_name = command(column_name))
1.df is the data frame you are working with
2.summarize is a dplyr function that reduces multiple values to a
single value
3.var_name is the name you assign to the column that stores the
results of the summary function in the returned data frame
4.command is the summary function that is applied to the column by
summarize()
5.column_name is the name of the column of df that is being
summarized
The following table includes common summary functions that can be
given as an argument to summarize():
knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates1.png")

Instructions
1.ShoeFly.com has a new batch of orders stored in the data frame
orders. Inspect the first 10 rows of the data frame using head().
# load order data
orders <- read_csv("orders.csv")
# inspect orders here:
head(orders, 10)
2.Our finance department wants to know the price of the most
expensive pair of shoes purchased. Save your answer to the variable
most_expensive.
# define most_expensive here:
most_expensive <- orders %>%
summarize(most_expensive = max(price))
most_expensive
3.Woah, wait a minute! Take a look at the output of the code you just
ran. The result for the most expensive pair of shoes is coming back as
NA. Why is this happening?
If you scroll up in the rendered notebook to where orders.csv is
loaded, you can see a warning about row 99 of the file. There is a
missing column of information! It appears that the price for row 99 was
not in the file, and this is causing your maximum value calculation to
return NA.
Add the following as an additional argument to max() so that it
removes all missing values before computing the maximum value. ” na.rm =
TRUE ”
# define most_expensive here:
most_expensive <- orders %>%
summarize(most_expensive = max(price, na.rm = TRUE))
most_expensive
4.Our fashion department wants to know how many different colors of
shoes we are selling. Save your answer to the variable num_colors.
# define num_colors here:
num_colors <- orders %>%
summarize(num_colors = n_distinct(shoe_color, na.rm = TRUE))
num_colors
3.Calculating Aggregate Functions I
When we have a bunch of data, we often want to calculate aggregate
statistics (mean, standard deviation, median, percentiles, etc.) over
certain subsets of the data.
Suppose we have a grade book with columns student, assignment_name,
and grade:
knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates2.png")

We want to get an average grade for each student across all
assignments. We can do this using the helpful dplyr function
group_by().
For this example, we’d use the following piece of code:
” grades <- df %>% group_by(student) %>%
summarize(mean_grade = mean(grade)) ”
The output might look something like this:
knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates3.png")

In general, we use the following syntax to calculate aggregates:
” df %>% group_by(column_1) %>% summarize(aggregate_name =
command(column_2)) ”
1.column_1 (student in our example) is the column that we want to
group_by()
2.column_2 (grade in our example) is the column that we want to apply
command(), a summary function, to using summarize()
3.aggregate_name is the name assigned to the calculated aggregate
In addition to the summary functions discussed in the last exercise
(mean(), median(), sd(), var(), min(), max(), IQR() and n_distinct()),
another helpful summary function, especially for grouped data, is n().
n() will return the count of the rows within a group, and does not
require a column as an argument. To get the count of the rows in each
group of students from our example:
” grades <- df %>% group_by(student) %>% summarize(count =
n()) ”
Instructions
- Let’s return to our orders data from ShoeFly.com.
In the previous exercise, our finance department wanted to know the
most expensive shoe that we sold.
Now, they want to know the price of the most expensive shoe for each
shoe_type (i.e., the price of the most expensive boot, the price of the
most expensive ballet flat, etc.). Name the column that shows the most
expensive shoe prices max_price.
Save your answer to the variable pricey_shoes, and view it.
# define pricey_shoes here:
pricey_shoes <- orders %>%
group_by(shoe_type) %>%
summarize(max_price = max(price, na.rm = TRUE))
pricey_shoes
2.The inventory team wants to know how many of each shoe_type has
been sold so they can forecast inventory for the future.
Save your answer to the variable shoes_sold, and view it.
# define shoes_sold here:
shoes_sold <- orders %>%
group_by(shoe_type) %>%
summarize(count = n())
shoes_sold
4.Calculating Aggregate Functions II
Sometimes, we want to group by more than one column. We can do this
by passing multiple column names as arguments to the group_by
function.
Imagine that we run a chain of stores and have data about the number
of sales at different locations on different days:
knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates4.png")

We suspect that sales are different at different locations on
different days of the week. In order to test this hypothesis, we could
calculate the average sales for each store on each day of the week
across multiple months. The code would look like this:
” df %>% group_by(location,day_of_week) %>%
summarize(mean_total_sales = mean(total_sales)) ”
And the results might look something like this:
knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates5.png")

Instructions
1.At ShoeFly.com, our Purchasing team thinks that certain
shoe_type/shoe_color combinations are particularly popular this year
(for example, blue ballet flats are all the rage in Paris).
Find the total number of shoes of each shoe_type/shoe_color
combination purchased using group_by, summarize() and n(). Name the
aggregate count column count. Save your result to the variable
shoe_counts, and view it.
# define shoe_counts here:
shoe_counts <- orders %>%
group_by(shoe_type, shoe_color) %>%
summarize(count = n())
shoe_counts
2.The Marketing team wants to better understand the different price
levels of the kinds of shoes that have been sold on the website, in
particular looking at shoe_type/shoe_material combinations.
Find the mean price of each shoe_type/shoe_material combination
purchased using group_by, summarize() and mean(). Assign the name
mean_price to the calculated aggregate. Save your result to the variable
shoe_prices, and view it.
Don’t forget to include na.rm = TRUE as an argument in the summary
function that you call!
# define shoe_prices here:
shoe_prices <- orders %>%
group_by(shoe_type, shoe_material) %>%
summarize(mean_price = mean(price, na.rm = TRUE))
shoe_prices
5.Combining Grouping with Filters
While group_by() is most often used with summarize() to calculate
summary statistics, it can also be used with the dplyr function filter()
to filter rows of a data frame based on per-group metrics.
Suppose you work at an educational technology company that offers
online courses and collects user data in an enrollments data frame:
# 建立資料框
enrollments <- data.frame(
user_id = c(1234, 1234, 4567, 4567),
course = c("learn_r", "learn_python", "learn_r", "learn_python"),
quiz_score = c(80, 95, 90, 55)
)
enrollments
You want to identify all the enrollments in difficult courses, which
you define as courses with an average quiz_score less than 80. To filter
the data frame to just these rows:
enrollments %>%
group_by(course) %>%
filter(mean(quiz_score) < 80)
1.group_by() groups the data frame by course into two groups: learn-r
and learn-python
2.filter() will keep all the rows of the data frame whose per-group
(per-course) average quiz_score is less than 80
Rather than filtering rows by the individual column values, the rows
will be filtered by their group value since a summary function is
used!
1.The average quiz_score for the learn-r course is 85, so all the
rows of enrollments with a value of learn-r in the course column are
filtered out.
2.The average quiz_score for the learn-python course is 75, so all
the rows of enrollments with a value of learn-python in the course
column remain.
Instructions
1.Your boss at ShoeFly.com wants to gain a better insight into the
orders of the most popular shoe_types.
Group orders by shoe_type and filter to only include orders with a
shoe_type that has been ordered more than 7 times. Save the result to
most_pop_orders, and view it.
You can include any of the summary functions as part of an argument
to filter(), including n()!
# define most_pop_orders here:
most_pop_orders <- orders %>%
group_by(shoe_type) %>%
filter((count = n()) > 7)
most_pop_orders
6.Combining Grouping with Mutate
group_by() can also be used with the dplyr function mutate() to add
columns to a data frame that involve per-group metrics.
Consider the same educational technology company’s enrollments table
from the previous exercise:
# 建立資料框
enrollments <- data.frame(
user_id = c(1234, 1234, 4567, 4567),
course = c("learn_r", "learn_python", "learn_r", "learn_python"),
quiz_score = c(80, 95, 90, 55)
)
enrollments
You want to add a new column to the data frame that stores the
difference between a row’s quiz_score and the average quiz_score for
that row’s course. To add the column:
enrollments %>%
group_by(course) %>%
mutate(diff_from_course_mean = quiz_score - mean(quiz_score))
NA
1.group_by() groups the data frame by course into two groups: learn-r
and learn-python
2.mutate() will add a new column diff_from_course_mean which is
calculated as the difference between a row’s individual quiz_score and
the mean(quiz_score) for that row’s group (course)
1.The average quiz_score for the learn-r course is 85, so
diff_from_course_mean is calculated as quiz_score - 85 for all the rows
of enrollments with a value of learn-r in the course column.
2.The average quiz_score for the learn-python course is 75, so
diff_from_course_mean is calculated as quiz_score - 75 for all the rows
of enrollments with a value of learn-python in the course column.
Instructions
1.You want to be able to tell how expensive each order is compared to
the average price of orders with the same shoe_type.
Group orders by shoe_type and create a new column named
diff_from_shoe_type_mean that stores the difference in price between an
orders price and the average price of orders with the same shoe_type.
Save the result to diff_from_mean, and view it.
Don’t forget to include na.rm = TRUE as an argument in the summary
function you call!
# define diff_from_mean here:
diff_from_mean <- orders %>%
group_by(shoe_type) %>%
mutate(diff_from_shoe_type_mean = price - mean(price, na.rm = TRUE))
diff_from_mean
7.Review
This lesson introduced you to aggregates in R using dplyr. You
learned:
1.How to calculate summary statistics with summarize()
2.How to perform aggregate statistics over individual rows with the
same value or values using group_by()
Instructions
1.Let’s examine some more data from ShoeFly.com. This time, in
addition to the orders data, we’ll be looking at data about user visits
to the website, stored in the page_visits data frame. Inspect the
columns of the data frames using the rendered notebook.
Find the average price of an order in the orders data frame using
summarize() and the mean() summary function. Save the resulting data
frame to a variable named average_price and view it.
Don’t forget to include na.rm = TRUE as an argument in the call to
mean()!
# load data
orders <- read_csv("orders.csv")
page_visits <- read_csv("page_visits.csv")
# inspect data frames
head(orders)
head(page_visits)
# define average_price here:
average_price <- orders %>%
summarize(avg_price = mean(price, na.rm = TRUE))
average_price
2.In the page_visits data frame, the column utm_source contains
information about how users got to ShoeFly’s homepage. For instance, if
utm_source = Facebook, then the user came to ShoeFly by clicking on an
ad on Facebook.com.
Use a group_by statement to calculate how many visits came from each
of the different sources. Save your answer to the variable click_source,
and view it.
# define click_source here:
click_source <- page_visits %>%
group_by(utm_source) %>%
summarize(count = n())
click_source
3.Our Marketing department thinks that the traffic to our site has
been changing over the past few months. Use group_by to calculate the
number of visits to our site from each utm_source for each month. Save
your answer to the variable click_source_by_month, and view it.
# define click_source_by_month here:
click_source_by_month <- page_visits %>%
group_by(utm_source, month) %>%
summarize(count = n())
click_source_by_month
LS0tDQp0aXRsZTogIkFnZ3JlZ2F0ZXMgaW4gUiINCmF1dGhvcjogIkFubmFiZWwgS3VvIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJVktJW0tJWQgJUg6JU0nKWAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyAxLkludHJvZHVjdGlvbg0KDQpJbiB0aGlzIGxlc3NvbiB5b3Ugd2lsbCBsZWFybiBhYm91dCBhZ2dyZWdhdGVzIGluIFIgdXNpbmcgZHBseXIuIEFuIGFnZ3JlZ2F0ZSBzdGF0aXN0aWMgaXMgYSB3YXkgb2YgY3JlYXRpbmcgYSBzaW5nbGUgbnVtYmVyIHRoYXQgZGVzY3JpYmVzIGEgZ3JvdXAgb2YgbnVtYmVycy4gQ29tbW9uIGFnZ3JlZ2F0ZSBzdGF0aXN0aWNzIGluY2x1ZGUgbWVhbiwgbWVkaWFuLCBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uLg0KDQpBZGRpdGlvbmFsbHksIHlvdSB3aWxsIGxlYXJuIGhvdyB5b3UgY2FuIGdyb3VwIGRhdGEgaW50byBkaWZmZXJlbnQgc3Vic2V0cyBiYXNlZCBvbiBjb2x1bW4gdmFsdWVzLiBUaGlzIGNhbiBoZWxwIG5hcnJvdyB0aGUgZm9jdXMgb2YgYSBzdW1tYXJ5IHN0YXRpc3RpYyB0byBhIHN1YnNldCBvZiBhIGRhdGFzZXQuIFIgeW91IHJlYWR5IHRvIGdldCBzdGFydGVkPw0KDQojIyBJbnN0cnVjdGlvbnMNCg0KSW4gdGhlIHdvcmtzcGFjZSB0byB0aGUgcmlnaHQgd2UgaGF2ZSBsb2FkZWQgZGF0YSBmcm9tIFNob2VGbHkuY29tLCBhIGZpY3Rpb25hbCBlLWNvbW1lcmNlIHNob2Ugc3RvcmUuIFRoZSBkYXRhIGluY2x1ZGVzIGluZm9ybWF0aW9uIHJlZ2FyZGluZyBjdXN0b21lciBvcmRlcnMgYXMgd2VsbCBhcyB0aGUgc291cmNlIG9mIHBhZ2UgdmlzaXRzIHRvIFNob2VGbHkuY29t4oCZcyB3ZWJzaXRlLg0KDQpSZXZpZXcgdGhlIGNvZGUgYW5kIHRoZSBvdXRwdXQuIFdoYXQgY2FsY3VsYXRpb25zIGFyZSBtYWRlLCBhbmQgb24gd2hpY2ggc3Vic2V0cyBvZiB0aGUgZGF0YSBkbyB0aGV5IG9jY3VyPw0KDQpUaHJvdWdob3V0IHRoZSByZXN0IG9mIHRoaXMgbGVzc29uIHlvdSB3aWxsIGRpZyBpbnRvIFNob2VGbHkuY29t4oCZcyBkYXRhIHRvIGxlYXJuIG1vcmUgYWJvdXQgaXRzIGN1c3RvbWVy4oCZcyBvcmRlcnMgYW5kIGhvdyB1c2VycyBhcmUgZGlzY292ZXJpbmcgdGhlIHdlYnNpdGUuIFByb2NlZWQgdG8gdGhlIG5leHQgZXhlcmNpc2UgdG8gZ2V0IHN0YXJ0ZWQhDQoNCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRX0NCiMgbG9hZCBwYWNrYWdlcw0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZHBseXIpDQpgYGANCg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KIyBsb2FkIGRhdGENCm9yZGVycyA8LSByZWFkX2Nzdigib3JkZXJzLmNzdiIpDQpwYWdlX3Zpc2l0cyA8LSByZWFkX2NzdigicGFnZV92aXNpdHMuY3N2IikNCmBgYA0KDQpgYGB7cn0NCiMgaW5zcGVjdCBkYXRhIGZyYW1lcw0Kb3JkZXJzDQpwYWdlX3Zpc2l0cw0KYGBgDQoNCmBgYHtyfQ0KIyBhdmVyYWdlIHByaWNlIG9mIG9yZGVyDQphdmVyYWdlX3ByaWNlIDwtIG9yZGVycyAlPiUgDQogIHN1bW1hcml6ZShtZWFuX3ByaWNlID0gbWVhbihwcmljZSwgbmEucm0gPSBUUlVFKSkNCmF2ZXJhZ2VfcHJpY2UNCmBgYA0KDQpgYGB7cn0NCiMgcGFnZSB2aXNpdHMgYnkgVVRNIHNvdXJjZQ0KY2xpY2tfc291cmNlIDwtIHBhZ2VfdmlzaXRzICU+JQ0KICBncm91cF9ieSh1dG1fc291cmNlKSAlPiUNCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKQ0KY2xpY2tfc291cmNlDQpgYGANCg0KYGBge3J9DQojIHBhZ2UgdmlzaXRzIGJ5IFVUTSBzb3VyY2UgYW5kIG1vbnRoDQpjbGlja19zb3VyY2VfYnlfbW9udGggPC0gcGFnZV92aXNpdHMgJT4lDQogIGdyb3VwX2J5KHV0bV9zb3VyY2UsbW9udGgpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpDQpjbGlja19zb3VyY2VfYnlfbW9udGgNCmBgYA0KDQojIDIuQ2FsY3VsYXRpbmcgQ29sdW1uIFN0YXRpc3RpY3MNCg0KSW4gdGhpcyBleGVyY2lzZSwgeW91IHdpbGwgbGVhcm4gaG93IHRvIGNvbWJpbmUgYWxsIG9mIHRoZSB2YWx1ZXMgZnJvbSBhIGNvbHVtbiBmb3IgYSBzaW5nbGUgY2FsY3VsYXRpb24uIFRoaXMgY2FuIGJlIGRvbmUgd2l0aCB0aGUgaGVscCBvZiB0aGUgZHBseXIgZnVuY3Rpb24gc3VtbWFyaXplKCksIHdoaWNoIHJldHVybnMgYSBuZXcgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSBkZXNpcmVkIGNhbGN1bGF0aW9uLg0KDQpTb21lIGV4YW1wbGVzIG9mIHRoaXMgdHlwZSBvZiBjYWxjdWxhdGlvbiBpbmNsdWRlOg0KDQoxLlRoZSBkYXRhIGZyYW1lIGN1c3RvbWVycyBjb250YWlucyB0aGUgbmFtZXMgYW5kIGFnZXMgb2YgYWxsIG9mIHlvdXIgY3VzdG9tZXJzLiBZb3Ugd2FudCB0byBmaW5kIHRoZSBtZWRpYW4gYWdlOg0KDQpgYGB7cn0NCmN1c3RvbWVycyA8LSBkYXRhLmZyYW1lKA0KICBuYW1lID0gYygiQWxpY2UiLCAiQm9iIiwgIkNsYWlyZSIsICJEYW5pZWwiLCAiRWxhaW5lIiwgIkZyYW5rIiwgIkdyYWNlIiksDQogIGFnZSA9IGMoMjMsIDI1LCAzMSwgMzUsIDM1LCA0NiwgNjIpDQopDQpgYGANCg0KYGBge3J9DQpjdXN0b21lcnMgJT4lDQogIHNlbGVjdChhZ2UpDQojYygyMywgMjUsIDMxLCAzNSwgMzUsIDQ2LCA2MikNCmN1c3RvbWVycyAlPiUNCiAgc3VtbWFyaXplKG1lZGlhbl9hZ2UgPSBtZWRpYW4oYWdlKSkNCiMzNQ0KYGBgDQoNCg0KMi5UaGUgZGF0YSBmcmFtZSBzaGlwbWVudHMgY29udGFpbnMgYWRkcmVzcyBpbmZvcm1hdGlvbiBmb3IgYWxsIHNoaXBtZW50cyB0aGF0IHlvdeKAmXZlIHNlbnQgb3V0IGluIHRoZSBwYXN0IHllYXIuIFlvdSB3YW50IHRvIGtub3cgaG93IG1hbnkgZGlmZmVyZW50IHN0YXRlcyB5b3UgaGF2ZSBzaGlwcGVkIHRvLg0KDQpgYGB7cn0NCnNoaXBtZW50cyA8LSBkYXRhLmZyYW1lKA0KICBzdGF0ZXMgPSBjKCdDQScsICdDQScsICdDQScsICdDQScsICdOWScsICdOWScsICdOSicsICdOSicsICdOSicsICdOSicsICdOSicsICdOSicsICdOSicpDQopDQoNCmBgYA0KDQpgYGB7cn0NCnNoaXBtZW50cyAlPiUNCiAgc3VtbWFyaXplKG5fZGlzdGluY3Rfc3RhdGVzID0gbl9kaXN0aW5jdChzdGF0ZXMpKQ0KIzMNCmBgYA0KDQozLlRoZSBkYXRhIGZyYW1lIGludmVudG9yeSBjb250YWlucyBhIGxpc3Qgb2YgdHlwZXMgb2YgdC1zaGlydHMgdGhhdCB5b3VyIGNvbXBhbnkgbWFrZXMuIFlvdSB3YW50IHRvIGtub3cgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgcHJpY2VzIG9mIHlvdXIgaW52ZW50b3J5Lg0KDQpgYGB7cn0NCmludmVudG9yeSA8LSBkYXRhLmZyYW1lKA0KICBwcmljZSA9IGMoMzEsIDIzLCAzMCwgMjcsIDMwLCAyMiwgMjcsIDIyLCAzOSwgMjcsIDM2KQ0KKQ0KYGBgDQoNCmBgYHtyfQ0KaW52ZW50b3J5ICU+JQ0KICBzdW1tYXJpemUoc2RfcHJpY2UgPSBzZChwcmljZSkpDQpgYGANCg0KVGhlIGdlbmVyYWwgc3ludGF4IGZvciB0aGVzZSBjYWxjdWxhdGlvbnMgaXM6DQoNCmRmICU+JQ0KICBzdW1tYXJpemUodmFyX25hbWUgPSBjb21tYW5kKGNvbHVtbl9uYW1lKSkNCg0KMS5kZiBpcyB0aGUgZGF0YSBmcmFtZSB5b3UgYXJlIHdvcmtpbmcgd2l0aA0KDQoyLnN1bW1hcml6ZSBpcyBhIGRwbHlyIGZ1bmN0aW9uIHRoYXQgcmVkdWNlcyBtdWx0aXBsZSB2YWx1ZXMgdG8gYSBzaW5nbGUgdmFsdWUNCg0KMy52YXJfbmFtZSBpcyB0aGUgbmFtZSB5b3UgYXNzaWduIHRvIHRoZSBjb2x1bW4gdGhhdCBzdG9yZXMgdGhlIHJlc3VsdHMgb2YgdGhlIHN1bW1hcnkgZnVuY3Rpb24gaW4gdGhlIHJldHVybmVkIGRhdGEgZnJhbWUNCg0KNC5jb21tYW5kIGlzIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHRoYXQgaXMgYXBwbGllZCB0byB0aGUgY29sdW1uIGJ5IHN1bW1hcml6ZSgpDQoNCjUuY29sdW1uX25hbWUgaXMgdGhlIG5hbWUgb2YgdGhlIGNvbHVtbiBvZiBkZiB0aGF0IGlzIGJlaW5nIHN1bW1hcml6ZWQNCg0KVGhlIGZvbGxvd2luZyB0YWJsZSBpbmNsdWRlcyBjb21tb24gc3VtbWFyeSBmdW5jdGlvbnMgdGhhdCBjYW4gYmUgZ2l2ZW4gYXMgYW4gYXJndW1lbnQgdG8gc3VtbWFyaXplKCk6DQoNCmBgYHtyIEFnZ3JlZ2F0ZXMxLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMva3VvYW4vRGVza3RvcC9SIENvZGUvQWdncmVnYXRlczEucG5nIikNCmBgYA0KDQojIyBJbnN0cnVjdGlvbnMNCg0KMS5TaG9lRmx5LmNvbSBoYXMgYSBuZXcgYmF0Y2ggb2Ygb3JkZXJzIHN0b3JlZCBpbiB0aGUgZGF0YSBmcmFtZSBvcmRlcnMuIEluc3BlY3QgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIGRhdGEgZnJhbWUgdXNpbmcgaGVhZCgpLg0KDQpgYGB7ciBtZXNzYWdlID0gRkFMU0V9DQojIGxvYWQgb3JkZXIgZGF0YQ0Kb3JkZXJzIDwtIHJlYWRfY3N2KCJvcmRlcnMuY3N2IikNCg0KIyBpbnNwZWN0IG9yZGVycyBoZXJlOg0KaGVhZChvcmRlcnMsIDEwKQ0KYGBgDQoNCjIuT3VyIGZpbmFuY2UgZGVwYXJ0bWVudCB3YW50cyB0byBrbm93IHRoZSBwcmljZSBvZiB0aGUgbW9zdCBleHBlbnNpdmUgcGFpciBvZiBzaG9lcyBwdXJjaGFzZWQuIFNhdmUgeW91ciBhbnN3ZXIgdG8gdGhlIHZhcmlhYmxlIG1vc3RfZXhwZW5zaXZlLg0KDQpgYGB7cn0NCiMgZGVmaW5lIG1vc3RfZXhwZW5zaXZlIGhlcmU6DQptb3N0X2V4cGVuc2l2ZSA8LSBvcmRlcnMgJT4lDQogIHN1bW1hcml6ZShtb3N0X2V4cGVuc2l2ZSA9ICBtYXgocHJpY2UpKQ0KbW9zdF9leHBlbnNpdmUNCmBgYA0KDQozLldvYWgsIHdhaXQgYSBtaW51dGUhIFRha2UgYSBsb29rIGF0IHRoZSBvdXRwdXQgb2YgdGhlIGNvZGUgeW91IGp1c3QgcmFuLiBUaGUgcmVzdWx0IGZvciB0aGUgbW9zdCBleHBlbnNpdmUgcGFpciBvZiBzaG9lcyBpcyBjb21pbmcgYmFjayBhcyBOQS4gV2h5IGlzIHRoaXMgaGFwcGVuaW5nPw0KDQpJZiB5b3Ugc2Nyb2xsIHVwIGluIHRoZSByZW5kZXJlZCBub3RlYm9vayB0byB3aGVyZSBvcmRlcnMuY3N2IGlzIGxvYWRlZCwgeW91IGNhbiBzZWUgYSB3YXJuaW5nIGFib3V0IHJvdyA5OSBvZiB0aGUgZmlsZS4gVGhlcmUgaXMgYSBtaXNzaW5nIGNvbHVtbiBvZiBpbmZvcm1hdGlvbiEgSXQgYXBwZWFycyB0aGF0IHRoZSBwcmljZSBmb3Igcm93IDk5IHdhcyBub3QgaW4gdGhlIGZpbGUsIGFuZCB0aGlzIGlzIGNhdXNpbmcgeW91ciBtYXhpbXVtIHZhbHVlIGNhbGN1bGF0aW9uIHRvIHJldHVybiBOQS4NCg0KQWRkIHRoZSBmb2xsb3dpbmcgYXMgYW4gYWRkaXRpb25hbCBhcmd1bWVudCB0byBtYXgoKSBzbyB0aGF0IGl0IHJlbW92ZXMgYWxsIG1pc3NpbmcgdmFsdWVzIGJlZm9yZSBjb21wdXRpbmcgdGhlIG1heGltdW0gdmFsdWUuDQoiDQpuYS5ybSA9IFRSVUUNCiINCg0KYGBge3J9DQojIGRlZmluZSBtb3N0X2V4cGVuc2l2ZSBoZXJlOg0KbW9zdF9leHBlbnNpdmUgPC0gb3JkZXJzICU+JQ0KICBzdW1tYXJpemUobW9zdF9leHBlbnNpdmUgPSAgbWF4KHByaWNlLCBuYS5ybSA9IFRSVUUpKQ0KbW9zdF9leHBlbnNpdmUNCmBgYA0KDQo0Lk91ciBmYXNoaW9uIGRlcGFydG1lbnQgd2FudHMgdG8ga25vdyBob3cgbWFueSBkaWZmZXJlbnQgY29sb3JzIG9mIHNob2VzIHdlIGFyZSBzZWxsaW5nLiBTYXZlIHlvdXIgYW5zd2VyIHRvIHRoZSB2YXJpYWJsZSBudW1fY29sb3JzLg0KDQpgYGB7cn0NCiMgZGVmaW5lIG51bV9jb2xvcnMgaGVyZToNCm51bV9jb2xvcnMgPC0gb3JkZXJzICU+JQ0KICBzdW1tYXJpemUobnVtX2NvbG9ycyA9IG5fZGlzdGluY3Qoc2hvZV9jb2xvciwgbmEucm0gPSBUUlVFKSkNCm51bV9jb2xvcnMgDQpgYGANCg0KIyAzLkNhbGN1bGF0aW5nIEFnZ3JlZ2F0ZSBGdW5jdGlvbnMgSQ0KDQpXaGVuIHdlIGhhdmUgYSBidW5jaCBvZiBkYXRhLCB3ZSBvZnRlbiB3YW50IHRvIGNhbGN1bGF0ZSBhZ2dyZWdhdGUgc3RhdGlzdGljcyAobWVhbiwgc3RhbmRhcmQgZGV2aWF0aW9uLCBtZWRpYW4sIHBlcmNlbnRpbGVzLCBldGMuKSBvdmVyIGNlcnRhaW4gc3Vic2V0cyBvZiB0aGUgZGF0YS4NCg0KU3VwcG9zZSB3ZSBoYXZlIGEgZ3JhZGUgYm9vayB3aXRoIGNvbHVtbnMgc3R1ZGVudCwgYXNzaWdubWVudF9uYW1lLCBhbmQgZ3JhZGU6DQoNCmBgYHtyIEFnZ3JlZ2F0ZXMyLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMva3VvYW4vRGVza3RvcC9SIENvZGUvQWdncmVnYXRlczIucG5nIikNCmBgYA0KDQpXZSB3YW50IHRvIGdldCBhbiBhdmVyYWdlIGdyYWRlIGZvciBlYWNoIHN0dWRlbnQgYWNyb3NzIGFsbCBhc3NpZ25tZW50cy4gV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGhlbHBmdWwgZHBseXIgZnVuY3Rpb24gZ3JvdXBfYnkoKS4NCg0KRm9yIHRoaXMgZXhhbXBsZSwgd2XigJlkIHVzZSB0aGUgZm9sbG93aW5nIHBpZWNlIG9mIGNvZGU6DQoNCiINCmdyYWRlcyA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoc3R1ZGVudCkgJT4lDQogIHN1bW1hcml6ZShtZWFuX2dyYWRlID0gbWVhbihncmFkZSkpDQoiDQoNCg0KVGhlIG91dHB1dCBtaWdodCBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6DQoNCmBgYHtyIEFnZ3JlZ2F0ZXMzLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMva3VvYW4vRGVza3RvcC9SIENvZGUvQWdncmVnYXRlczMucG5nIikNCmBgYA0KDQoNCkluIGdlbmVyYWwsIHdlIHVzZSB0aGUgZm9sbG93aW5nIHN5bnRheCB0byBjYWxjdWxhdGUgYWdncmVnYXRlczoNCg0KIg0KZGYgJT4lDQogIGdyb3VwX2J5KGNvbHVtbl8xKSAlPiUNCiAgc3VtbWFyaXplKGFnZ3JlZ2F0ZV9uYW1lID0gY29tbWFuZChjb2x1bW5fMikpDQoiDQoNCjEuY29sdW1uXzEgKHN0dWRlbnQgaW4gb3VyIGV4YW1wbGUpIGlzIHRoZSBjb2x1bW4gdGhhdCB3ZSB3YW50IHRvIGdyb3VwX2J5KCkNCg0KMi5jb2x1bW5fMiAoZ3JhZGUgaW4gb3VyIGV4YW1wbGUpIGlzIHRoZSBjb2x1bW4gdGhhdCB3ZSB3YW50IHRvIGFwcGx5IGNvbW1hbmQoKSwgYSBzdW1tYXJ5IGZ1bmN0aW9uLCB0byB1c2luZyBzdW1tYXJpemUoKQ0KDQozLmFnZ3JlZ2F0ZV9uYW1lIGlzIHRoZSBuYW1lIGFzc2lnbmVkIHRvIHRoZSBjYWxjdWxhdGVkIGFnZ3JlZ2F0ZQ0KDQpJbiBhZGRpdGlvbiB0byB0aGUgc3VtbWFyeSBmdW5jdGlvbnMgZGlzY3Vzc2VkIGluIHRoZSBsYXN0IGV4ZXJjaXNlIChtZWFuKCksIG1lZGlhbigpLCBzZCgpLCB2YXIoKSwgbWluKCksIG1heCgpLCBJUVIoKSBhbmQgbl9kaXN0aW5jdCgpKSwgYW5vdGhlciBoZWxwZnVsIHN1bW1hcnkgZnVuY3Rpb24sIGVzcGVjaWFsbHkgZm9yIGdyb3VwZWQgZGF0YSwgaXMgbigpLiBuKCkgd2lsbCByZXR1cm4gdGhlIGNvdW50IG9mIHRoZSByb3dzIHdpdGhpbiBhIGdyb3VwLCBhbmQgZG9lcyBub3QgcmVxdWlyZSBhIGNvbHVtbiBhcyBhbiBhcmd1bWVudC4gVG8gZ2V0IHRoZSBjb3VudCBvZiB0aGUgcm93cyBpbiBlYWNoIGdyb3VwIG9mIHN0dWRlbnRzIGZyb20gb3VyIGV4YW1wbGU6DQoNCiINCmdyYWRlcyA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoc3R1ZGVudCkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCiINCg0KIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuDQpMZXTigJlzIHJldHVybiB0byBvdXIgb3JkZXJzIGRhdGEgZnJvbSBTaG9lRmx5LmNvbS4NCg0KSW4gdGhlIHByZXZpb3VzIGV4ZXJjaXNlLCBvdXIgZmluYW5jZSBkZXBhcnRtZW50IHdhbnRlZCB0byBrbm93IHRoZSBtb3N0IGV4cGVuc2l2ZSBzaG9lIHRoYXQgd2Ugc29sZC4NCg0KTm93LCB0aGV5IHdhbnQgdG8ga25vdyB0aGUgcHJpY2Ugb2YgdGhlIG1vc3QgZXhwZW5zaXZlIHNob2UgZm9yIGVhY2ggc2hvZV90eXBlIChpLmUuLCB0aGUgcHJpY2Ugb2YgdGhlIG1vc3QgZXhwZW5zaXZlIGJvb3QsIHRoZSBwcmljZSBvZiB0aGUgbW9zdCBleHBlbnNpdmUgYmFsbGV0IGZsYXQsIGV0Yy4pLiBOYW1lIHRoZSBjb2x1bW4gdGhhdCBzaG93cyB0aGUgbW9zdCBleHBlbnNpdmUgc2hvZSBwcmljZXMgbWF4X3ByaWNlLg0KDQpTYXZlIHlvdXIgYW5zd2VyIHRvIHRoZSB2YXJpYWJsZSBwcmljZXlfc2hvZXMsIGFuZCB2aWV3IGl0Lg0KDQpgYGB7cn0NCiMgZGVmaW5lIHByaWNleV9zaG9lcyBoZXJlOg0KcHJpY2V5X3Nob2VzIDwtIG9yZGVycyAlPiUNCiAgZ3JvdXBfYnkoc2hvZV90eXBlKSAlPiUNCiAgc3VtbWFyaXplKG1heF9wcmljZSA9IG1heChwcmljZSwgbmEucm0gPSBUUlVFKSkNCg0KcHJpY2V5X3Nob2VzDQpgYGANCg0KMi5UaGUgaW52ZW50b3J5IHRlYW0gd2FudHMgdG8ga25vdyBob3cgbWFueSBvZiBlYWNoIHNob2VfdHlwZSBoYXMgYmVlbiBzb2xkIHNvIHRoZXkgY2FuIGZvcmVjYXN0IGludmVudG9yeSBmb3IgdGhlIGZ1dHVyZS4NCg0KU2F2ZSB5b3VyIGFuc3dlciB0byB0aGUgdmFyaWFibGUgc2hvZXNfc29sZCwgYW5kIHZpZXcgaXQuDQoNCmBgYHtyfQ0KIyBkZWZpbmUgc2hvZXNfc29sZCBoZXJlOg0Kc2hvZXNfc29sZCA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KHNob2VfdHlwZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCg0Kc2hvZXNfc29sZA0KYGBgDQoNCiMgNC5DYWxjdWxhdGluZyBBZ2dyZWdhdGUgRnVuY3Rpb25zIElJDQoNClNvbWV0aW1lcywgd2Ugd2FudCB0byBncm91cCBieSBtb3JlIHRoYW4gb25lIGNvbHVtbi4gV2UgY2FuIGRvIHRoaXMgYnkgcGFzc2luZyBtdWx0aXBsZSBjb2x1bW4gbmFtZXMgYXMgYXJndW1lbnRzIHRvIHRoZSBncm91cF9ieSBmdW5jdGlvbi4NCg0KSW1hZ2luZSB0aGF0IHdlIHJ1biBhIGNoYWluIG9mIHN0b3JlcyBhbmQgaGF2ZSBkYXRhIGFib3V0IHRoZSBudW1iZXIgb2Ygc2FsZXMgYXQgZGlmZmVyZW50IGxvY2F0aW9ucyBvbiBkaWZmZXJlbnQgZGF5czoNCg0KDQpgYGB7ciBBZ2dyZWdhdGVzNCwgb3V0LndpZHRoPSI2MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL2t1b2FuL0Rlc2t0b3AvUiBDb2RlL0FnZ3JlZ2F0ZXM0LnBuZyIpDQpgYGANCg0KV2Ugc3VzcGVjdCB0aGF0IHNhbGVzIGFyZSBkaWZmZXJlbnQgYXQgZGlmZmVyZW50IGxvY2F0aW9ucyBvbiBkaWZmZXJlbnQgZGF5cyBvZiB0aGUgd2Vlay4gSW4gb3JkZXIgdG8gdGVzdCB0aGlzIGh5cG90aGVzaXMsIHdlIGNvdWxkIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBzYWxlcyBmb3IgZWFjaCBzdG9yZSBvbiBlYWNoIGRheSBvZiB0aGUgd2VlayBhY3Jvc3MgbXVsdGlwbGUgbW9udGhzLiBUaGUgY29kZSB3b3VsZCBsb29rIGxpa2UgdGhpczoNCg0KIg0KZGYgJT4lDQogIGdyb3VwX2J5KGxvY2F0aW9uLGRheV9vZl93ZWVrKSAlPiUNCiAgc3VtbWFyaXplKG1lYW5fdG90YWxfc2FsZXMgPSBtZWFuKHRvdGFsX3NhbGVzKSkNCiINCg0KQW5kIHRoZSByZXN1bHRzIG1pZ2h0IGxvb2sgc29tZXRoaW5nIGxpa2UgdGhpczoNCg0KYGBge3IgQWdncmVnYXRlczUsIG91dC53aWR0aD0iNjAlIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9rdW9hbi9EZXNrdG9wL1IgQ29kZS9BZ2dyZWdhdGVzNS5wbmciKQ0KYGBgDQoNCiMjIEluc3RydWN0aW9ucw0KMS5BdCBTaG9lRmx5LmNvbSwgb3VyIFB1cmNoYXNpbmcgdGVhbSB0aGlua3MgdGhhdCBjZXJ0YWluIHNob2VfdHlwZS9zaG9lX2NvbG9yIGNvbWJpbmF0aW9ucyBhcmUgcGFydGljdWxhcmx5IHBvcHVsYXIgdGhpcyB5ZWFyIChmb3IgZXhhbXBsZSwgYmx1ZSBiYWxsZXQgZmxhdHMgYXJlIGFsbCB0aGUgcmFnZSBpbiBQYXJpcykuDQoNCkZpbmQgdGhlIHRvdGFsIG51bWJlciBvZiBzaG9lcyBvZiBlYWNoIHNob2VfdHlwZS9zaG9lX2NvbG9yIGNvbWJpbmF0aW9uIHB1cmNoYXNlZCB1c2luZyBncm91cF9ieSwgc3VtbWFyaXplKCkgYW5kIG4oKS4gTmFtZSB0aGUgYWdncmVnYXRlIGNvdW50IGNvbHVtbiBjb3VudC4gU2F2ZSB5b3VyIHJlc3VsdCB0byB0aGUgdmFyaWFibGUgc2hvZV9jb3VudHMsIGFuZCB2aWV3IGl0Lg0KDQpgYGB7cn0NCiMgZGVmaW5lIHNob2VfY291bnRzIGhlcmU6DQpzaG9lX2NvdW50cyA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KHNob2VfdHlwZSwgc2hvZV9jb2xvcikgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCg0Kc2hvZV9jb3VudHMNCmBgYA0KDQoyLlRoZSBNYXJrZXRpbmcgdGVhbSB3YW50cyB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgZGlmZmVyZW50IHByaWNlIGxldmVscyBvZiB0aGUga2luZHMgb2Ygc2hvZXMgdGhhdCBoYXZlIGJlZW4gc29sZCBvbiB0aGUgd2Vic2l0ZSwgaW4gcGFydGljdWxhciBsb29raW5nIGF0IHNob2VfdHlwZS9zaG9lX21hdGVyaWFsIGNvbWJpbmF0aW9ucy4NCg0KRmluZCB0aGUgbWVhbiBwcmljZSBvZiBlYWNoIHNob2VfdHlwZS9zaG9lX21hdGVyaWFsIGNvbWJpbmF0aW9uIHB1cmNoYXNlZCB1c2luZyBncm91cF9ieSwgc3VtbWFyaXplKCkgYW5kIG1lYW4oKS4gQXNzaWduIHRoZSBuYW1lIG1lYW5fcHJpY2UgdG8gdGhlIGNhbGN1bGF0ZWQgYWdncmVnYXRlLiBTYXZlIHlvdXIgcmVzdWx0IHRvIHRoZSB2YXJpYWJsZSBzaG9lX3ByaWNlcywgYW5kIHZpZXcgaXQuDQoNCkRvbuKAmXQgZm9yZ2V0IHRvIGluY2x1ZGUgbmEucm0gPSBUUlVFIGFzIGFuIGFyZ3VtZW50IGluIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHRoYXQgeW91IGNhbGwhDQoNCmBgYHtyfQ0KIyBkZWZpbmUgc2hvZV9wcmljZXMgaGVyZToNCnNob2VfcHJpY2VzIDwtIG9yZGVycyAlPiUNCiAgZ3JvdXBfYnkoc2hvZV90eXBlLCBzaG9lX21hdGVyaWFsKSAlPiUNCiAgc3VtbWFyaXplKG1lYW5fcHJpY2UgPSBtZWFuKHByaWNlLCBuYS5ybSA9IFRSVUUpKQ0KDQpzaG9lX3ByaWNlcw0KYGBgDQoNCiMgNS5Db21iaW5pbmcgR3JvdXBpbmcgd2l0aCBGaWx0ZXJzDQoNCldoaWxlIGdyb3VwX2J5KCkgaXMgbW9zdCBvZnRlbiB1c2VkIHdpdGggc3VtbWFyaXplKCkgdG8gY2FsY3VsYXRlIHN1bW1hcnkgc3RhdGlzdGljcywgaXQgY2FuIGFsc28gYmUgdXNlZCB3aXRoIHRoZSBkcGx5ciBmdW5jdGlvbiBmaWx0ZXIoKSB0byBmaWx0ZXIgcm93cyBvZiBhIGRhdGEgZnJhbWUgYmFzZWQgb24gcGVyLWdyb3VwIG1ldHJpY3MuDQoNClN1cHBvc2UgeW91IHdvcmsgYXQgYW4gZWR1Y2F0aW9uYWwgdGVjaG5vbG9neSBjb21wYW55IHRoYXQgb2ZmZXJzIG9ubGluZSBjb3Vyc2VzIGFuZCBjb2xsZWN0cyB1c2VyIGRhdGEgaW4gYW4gZW5yb2xsbWVudHMgZGF0YSBmcmFtZToNCmBgYHtyfQ0KIyDlu7rnq4vos4fmlpnmoYYNCmVucm9sbG1lbnRzIDwtIGRhdGEuZnJhbWUoDQogIHVzZXJfaWQgPSBjKDEyMzQsIDEyMzQsIDQ1NjcsIDQ1NjcpLA0KICBjb3Vyc2UgPSBjKCJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIsICJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIpLA0KICBxdWl6X3Njb3JlID0gYyg4MCwgOTUsIDkwLCA1NSkNCikNCg0KZW5yb2xsbWVudHMNCmBgYA0KDQpZb3Ugd2FudCB0byBpZGVudGlmeSBhbGwgdGhlIGVucm9sbG1lbnRzIGluIGRpZmZpY3VsdCBjb3Vyc2VzLCB3aGljaCB5b3UgZGVmaW5lIGFzIGNvdXJzZXMgd2l0aCBhbiBhdmVyYWdlIHF1aXpfc2NvcmUgbGVzcyB0aGFuIDgwLiBUbyBmaWx0ZXIgdGhlIGRhdGEgZnJhbWUgdG8ganVzdCB0aGVzZSByb3dzOg0KYGBge3J9DQplbnJvbGxtZW50cyAlPiUNCiAgZ3JvdXBfYnkoY291cnNlKSAlPiUNCiAgZmlsdGVyKG1lYW4ocXVpel9zY29yZSkgPCA4MCkNCmBgYA0KDQoxLmdyb3VwX2J5KCkgZ3JvdXBzIHRoZSBkYXRhIGZyYW1lIGJ5IGNvdXJzZSBpbnRvIHR3byBncm91cHM6IGxlYXJuLXIgYW5kIGxlYXJuLXB5dGhvbg0KDQoyLmZpbHRlcigpIHdpbGwga2VlcCBhbGwgdGhlIHJvd3Mgb2YgdGhlIGRhdGEgZnJhbWUgd2hvc2UgcGVyLWdyb3VwIChwZXItY291cnNlKSBhdmVyYWdlIHF1aXpfc2NvcmUgaXMgbGVzcyB0aGFuIDgwDQoNClJhdGhlciB0aGFuIGZpbHRlcmluZyByb3dzIGJ5IHRoZSBpbmRpdmlkdWFsIGNvbHVtbiB2YWx1ZXMsIHRoZSByb3dzIHdpbGwgYmUgZmlsdGVyZWQgYnkgdGhlaXIgZ3JvdXAgdmFsdWUgc2luY2UgYSBzdW1tYXJ5IGZ1bmN0aW9uIGlzIHVzZWQhIA0KDQoxLlRoZSBhdmVyYWdlIHF1aXpfc2NvcmUgZm9yIHRoZSBsZWFybi1yIGNvdXJzZSBpcyA4NSwgc28gYWxsIHRoZSByb3dzIG9mIGVucm9sbG1lbnRzIHdpdGggYSB2YWx1ZSBvZiBsZWFybi1yIGluIHRoZSBjb3Vyc2UgY29sdW1uIGFyZSBmaWx0ZXJlZCBvdXQuDQoNCjIuVGhlIGF2ZXJhZ2UgcXVpel9zY29yZSBmb3IgdGhlIGxlYXJuLXB5dGhvbiBjb3Vyc2UgaXMgNzUsIHNvIGFsbCB0aGUgcm93cyBvZiBlbnJvbGxtZW50cyB3aXRoIGEgdmFsdWUgb2YgbGVhcm4tcHl0aG9uIGluIHRoZSBjb3Vyc2UgY29sdW1uIHJlbWFpbi4NCg0KIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuWW91ciBib3NzIGF0IFNob2VGbHkuY29tIHdhbnRzIHRvIGdhaW4gYSBiZXR0ZXIgaW5zaWdodCBpbnRvIHRoZSBvcmRlcnMgb2YgdGhlIG1vc3QgcG9wdWxhciBzaG9lX3R5cGVzLg0KDQpHcm91cCBvcmRlcnMgYnkgc2hvZV90eXBlIGFuZCBmaWx0ZXIgdG8gb25seSBpbmNsdWRlIG9yZGVycyB3aXRoIGEgc2hvZV90eXBlIHRoYXQgaGFzIGJlZW4gb3JkZXJlZCBtb3JlIHRoYW4gNyB0aW1lcy4gU2F2ZSB0aGUgcmVzdWx0IHRvIG1vc3RfcG9wX29yZGVycywgYW5kIHZpZXcgaXQuDQoNCllvdSBjYW4gaW5jbHVkZSBhbnkgb2YgdGhlIHN1bW1hcnkgZnVuY3Rpb25zIGFzIHBhcnQgb2YgYW4gYXJndW1lbnQgdG8gZmlsdGVyKCksIGluY2x1ZGluZyBuKCkhDQoNCmBgYHtyfQ0KIyBkZWZpbmUgbW9zdF9wb3Bfb3JkZXJzIGhlcmU6DQptb3N0X3BvcF9vcmRlcnMgPC0gb3JkZXJzICU+JQ0KICBncm91cF9ieShzaG9lX3R5cGUpICU+JQ0KICBmaWx0ZXIoKGNvdW50ID0gbigpKSA+IDcpDQoNCm1vc3RfcG9wX29yZGVycw0KYGBgDQoNCiMgNi5Db21iaW5pbmcgR3JvdXBpbmcgd2l0aCBNdXRhdGUNCg0KZ3JvdXBfYnkoKSBjYW4gYWxzbyBiZSB1c2VkIHdpdGggdGhlIGRwbHlyIGZ1bmN0aW9uIG11dGF0ZSgpIHRvIGFkZCBjb2x1bW5zIHRvIGEgZGF0YSBmcmFtZSB0aGF0IGludm9sdmUgcGVyLWdyb3VwIG1ldHJpY3MuDQoNCkNvbnNpZGVyIHRoZSBzYW1lIGVkdWNhdGlvbmFsIHRlY2hub2xvZ3kgY29tcGFueeKAmXMgZW5yb2xsbWVudHMgdGFibGUgZnJvbSB0aGUgcHJldmlvdXMgZXhlcmNpc2U6DQoNCmBgYHtyfQ0KIyDlu7rnq4vos4fmlpnmoYYNCmVucm9sbG1lbnRzIDwtIGRhdGEuZnJhbWUoDQogIHVzZXJfaWQgPSBjKDEyMzQsIDEyMzQsIDQ1NjcsIDQ1NjcpLA0KICBjb3Vyc2UgPSBjKCJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIsICJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIpLA0KICBxdWl6X3Njb3JlID0gYyg4MCwgOTUsIDkwLCA1NSkNCikNCg0KZW5yb2xsbWVudHMNCmBgYA0KDQpZb3Ugd2FudCB0byBhZGQgYSBuZXcgY29sdW1uIHRvIHRoZSBkYXRhIGZyYW1lIHRoYXQgc3RvcmVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYSByb3figJlzIHF1aXpfc2NvcmUgYW5kIHRoZSBhdmVyYWdlIHF1aXpfc2NvcmUgZm9yIHRoYXQgcm934oCZcyBjb3Vyc2UuIFRvIGFkZCB0aGUgY29sdW1uOg0KDQpgYGB7cn0NCmVucm9sbG1lbnRzICU+JSANCiAgZ3JvdXBfYnkoY291cnNlKSAlPiUgDQogIG11dGF0ZShkaWZmX2Zyb21fY291cnNlX21lYW4gPSBxdWl6X3Njb3JlIC0gbWVhbihxdWl6X3Njb3JlKSkNCg0KYGBgDQoNCjEuZ3JvdXBfYnkoKSBncm91cHMgdGhlIGRhdGEgZnJhbWUgYnkgY291cnNlIGludG8gdHdvIGdyb3VwczogbGVhcm4tciBhbmQgbGVhcm4tcHl0aG9uDQoNCjIubXV0YXRlKCkgd2lsbCBhZGQgYSBuZXcgY29sdW1uIGRpZmZfZnJvbV9jb3Vyc2VfbWVhbiB3aGljaCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYSByb3figJlzIGluZGl2aWR1YWwgcXVpel9zY29yZSBhbmQgdGhlIG1lYW4ocXVpel9zY29yZSkgZm9yIHRoYXQgcm934oCZcyBncm91cCAoY291cnNlKQ0KDQoNCjEuVGhlIGF2ZXJhZ2UgcXVpel9zY29yZSBmb3IgdGhlIGxlYXJuLXIgY291cnNlIGlzIDg1LCBzbyBkaWZmX2Zyb21fY291cnNlX21lYW4gaXMgY2FsY3VsYXRlZCBhcyBxdWl6X3Njb3JlIC0gODUgZm9yIGFsbCB0aGUgcm93cyBvZiBlbnJvbGxtZW50cyB3aXRoIGEgdmFsdWUgb2YgbGVhcm4tciBpbiB0aGUgY291cnNlIGNvbHVtbi4NCg0KMi5UaGUgYXZlcmFnZSBxdWl6X3Njb3JlIGZvciB0aGUgbGVhcm4tcHl0aG9uIGNvdXJzZSBpcyA3NSwgc28gZGlmZl9mcm9tX2NvdXJzZV9tZWFuIGlzIGNhbGN1bGF0ZWQgYXMgcXVpel9zY29yZSAtIDc1IGZvciBhbGwgdGhlIHJvd3Mgb2YgZW5yb2xsbWVudHMgd2l0aCBhIHZhbHVlIG9mIGxlYXJuLXB5dGhvbiBpbiB0aGUgY291cnNlIGNvbHVtbi4NCg0KIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuWW91IHdhbnQgdG8gYmUgYWJsZSB0byB0ZWxsIGhvdyBleHBlbnNpdmUgZWFjaCBvcmRlciBpcyBjb21wYXJlZCB0byB0aGUgYXZlcmFnZSBwcmljZSBvZiBvcmRlcnMgd2l0aCB0aGUgc2FtZSBzaG9lX3R5cGUuDQoNCkdyb3VwIG9yZGVycyBieSBzaG9lX3R5cGUgYW5kIGNyZWF0ZSBhIG5ldyBjb2x1bW4gbmFtZWQgZGlmZl9mcm9tX3Nob2VfdHlwZV9tZWFuIHRoYXQgc3RvcmVzIHRoZSBkaWZmZXJlbmNlIGluIHByaWNlIGJldHdlZW4gYW4gb3JkZXJzIHByaWNlIGFuZCB0aGUgYXZlcmFnZSBwcmljZSBvZiBvcmRlcnMgd2l0aCB0aGUgc2FtZSBzaG9lX3R5cGUuIFNhdmUgdGhlIHJlc3VsdCB0byBkaWZmX2Zyb21fbWVhbiwgYW5kIHZpZXcgaXQuDQoNCkRvbuKAmXQgZm9yZ2V0IHRvIGluY2x1ZGUgbmEucm0gPSBUUlVFIGFzIGFuIGFyZ3VtZW50IGluIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHlvdSBjYWxsIQ0KDQpgYGB7cn0NCiMgZGVmaW5lIGRpZmZfZnJvbV9tZWFuIGhlcmU6DQpkaWZmX2Zyb21fbWVhbiA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KHNob2VfdHlwZSkgJT4lDQogIG11dGF0ZShkaWZmX2Zyb21fc2hvZV90eXBlX21lYW4gPSBwcmljZSAtIG1lYW4ocHJpY2UsIG5hLnJtID0gVFJVRSkpDQoNCmRpZmZfZnJvbV9tZWFuDQpgYGANCg0KIyA3LlJldmlldw0KDQpUaGlzIGxlc3NvbiBpbnRyb2R1Y2VkIHlvdSB0byBhZ2dyZWdhdGVzIGluIFIgdXNpbmcgZHBseXIuIFlvdSBsZWFybmVkOg0KDQoxLkhvdyB0byBjYWxjdWxhdGUgc3VtbWFyeSBzdGF0aXN0aWNzIHdpdGggc3VtbWFyaXplKCkNCg0KMi5Ib3cgdG8gcGVyZm9ybSBhZ2dyZWdhdGUgc3RhdGlzdGljcyBvdmVyIGluZGl2aWR1YWwgcm93cyB3aXRoIHRoZSBzYW1lIHZhbHVlIG9yIHZhbHVlcyB1c2luZyBncm91cF9ieSgpDQoNCiMjIEluc3RydWN0aW9ucw0KMS5MZXTigJlzIGV4YW1pbmUgc29tZSBtb3JlIGRhdGEgZnJvbSBTaG9lRmx5LmNvbS4gVGhpcyB0aW1lLCBpbiBhZGRpdGlvbiB0byB0aGUgb3JkZXJzIGRhdGEsIHdl4oCZbGwgYmUgbG9va2luZyBhdCBkYXRhIGFib3V0IHVzZXIgdmlzaXRzIHRvIHRoZSB3ZWJzaXRlLCBzdG9yZWQgaW4gdGhlIHBhZ2VfdmlzaXRzIGRhdGEgZnJhbWUuIEluc3BlY3QgdGhlIGNvbHVtbnMgb2YgdGhlIGRhdGEgZnJhbWVzIHVzaW5nIHRoZSByZW5kZXJlZCBub3RlYm9vay4NCg0KRmluZCB0aGUgYXZlcmFnZSBwcmljZSBvZiBhbiBvcmRlciBpbiB0aGUgb3JkZXJzIGRhdGEgZnJhbWUgdXNpbmcgc3VtbWFyaXplKCkgYW5kIHRoZSBtZWFuKCkgc3VtbWFyeSBmdW5jdGlvbi4gU2F2ZSB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgdG8gYSB2YXJpYWJsZSBuYW1lZCBhdmVyYWdlX3ByaWNlIGFuZCB2aWV3IGl0Lg0KDQpEb27igJl0IGZvcmdldCB0byBpbmNsdWRlIG5hLnJtID0gVFJVRSBhcyBhbiBhcmd1bWVudCBpbiB0aGUgY2FsbCB0byBtZWFuKCkhDQoNCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRX0NCiMgbG9hZCBkYXRhDQpvcmRlcnMgPC0gcmVhZF9jc3YoIm9yZGVycy5jc3YiKQ0KcGFnZV92aXNpdHMgPC0gcmVhZF9jc3YoInBhZ2VfdmlzaXRzLmNzdiIpDQpgYGANCg0KYGBge3J9DQojIGluc3BlY3QgZGF0YSBmcmFtZXMNCmhlYWQob3JkZXJzKQ0KaGVhZChwYWdlX3Zpc2l0cykNCmBgYA0KDQpgYGB7cn0NCiMgZGVmaW5lIGF2ZXJhZ2VfcHJpY2UgaGVyZToNCmF2ZXJhZ2VfcHJpY2UgPC0gb3JkZXJzICU+JQ0KICBzdW1tYXJpemUoYXZnX3ByaWNlID0gbWVhbihwcmljZSwgbmEucm0gPSBUUlVFKSkNCg0KYXZlcmFnZV9wcmljZQ0KYGBgDQoNCjIuSW4gdGhlIHBhZ2VfdmlzaXRzIGRhdGEgZnJhbWUsIHRoZSBjb2x1bW4gdXRtX3NvdXJjZSBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCBob3cgdXNlcnMgZ290IHRvIFNob2VGbHnigJlzIGhvbWVwYWdlLiBGb3IgaW5zdGFuY2UsIGlmIHV0bV9zb3VyY2UgPSBGYWNlYm9vaywgdGhlbiB0aGUgdXNlciBjYW1lIHRvIFNob2VGbHkgYnkgY2xpY2tpbmcgb24gYW4gYWQgb24gRmFjZWJvb2suY29tLg0KDQpVc2UgYSBncm91cF9ieSBzdGF0ZW1lbnQgdG8gY2FsY3VsYXRlIGhvdyBtYW55IHZpc2l0cyBjYW1lIGZyb20gZWFjaCBvZiB0aGUgZGlmZmVyZW50IHNvdXJjZXMuIFNhdmUgeW91ciBhbnN3ZXIgdG8gdGhlIHZhcmlhYmxlIGNsaWNrX3NvdXJjZSwgYW5kIHZpZXcgaXQuDQoNCmBgYHtyfQ0KIyBkZWZpbmUgY2xpY2tfc291cmNlIGhlcmU6DQpjbGlja19zb3VyY2UgPC0gcGFnZV92aXNpdHMgJT4lDQogIGdyb3VwX2J5KHV0bV9zb3VyY2UpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpDQoNCmNsaWNrX3NvdXJjZQ0KYGBgDQoNCjMuT3VyIE1hcmtldGluZyBkZXBhcnRtZW50IHRoaW5rcyB0aGF0IHRoZSB0cmFmZmljIHRvIG91ciBzaXRlIGhhcyBiZWVuIGNoYW5naW5nIG92ZXIgdGhlIHBhc3QgZmV3IG1vbnRocy4gVXNlIGdyb3VwX2J5IHRvIGNhbGN1bGF0ZSB0aGUgbnVtYmVyIG9mIHZpc2l0cyB0byBvdXIgc2l0ZSBmcm9tIGVhY2ggdXRtX3NvdXJjZSBmb3IgZWFjaCBtb250aC4gU2F2ZSB5b3VyIGFuc3dlciB0byB0aGUgdmFyaWFibGUgY2xpY2tfc291cmNlX2J5X21vbnRoLCBhbmQgdmlldyBpdC4NCg0KYGBge3J9DQojIGRlZmluZSBjbGlja19zb3VyY2VfYnlfbW9udGggaGVyZToNCmNsaWNrX3NvdXJjZV9ieV9tb250aCA8LSBwYWdlX3Zpc2l0cyAlPiUNCiAgZ3JvdXBfYnkodXRtX3NvdXJjZSwgbW9udGgpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpDQoNCmNsaWNrX3NvdXJjZV9ieV9tb250aA0KYGBg