Welcome to your off-platform project focused on Data Manipulation in R! The goal of this project is to get experience working with multiple .csv files in RStudio. We’ll be creating several data frames and manipulating them to try to find new insights.

In this project we will be looking at data associated with the Coronavirus pandemic from 2020. While this topic isn’t your typical lighthearted Codecademy assignment, we felt like it is a excellent way to use your new R skills to understand the world around you. This assignment was written on March 23rd, 2020 — since you will be taking this assignment in the future, we’re hoping that this can be an interesting historical analysis rather than an ongoing issue.

Collect and Investigate the Data

We’ve provided three datasets named corona_confirmed.csv, corona_recovered.csv and corona_deaths.csv. For the entirety of this project, we’ll be using these datasets. You can find more recent versions of this data at Johns Hopkins’ data repository on GitHub.

This is a good opportunity to practice downloading data from GitHub or other repositories. We’re using files found in the csse_covid_19_time_series folder. In addition, we recommend looking at some of the projects linked on the GitHub page — that can help give you a better sense of what type of information you can extract from this data.

For now, load these three .csv files into three separate dataframes. Note that we used the read_csv() function from the readr library. If you load the csv files using a different method, you might have slightly different column names.

Inspect the dataframes to get a sense of how the data is stored (we recommend using head()).

library(dplyr)
library(readr)

# Load the data
confirmed <- read_csv("corona_confirmed.csv")
deaths <- read_csv("corona_deaths.csv")
recovered <- read.csv("corona_recovered.csv")

# Inspect the data
head(confirmed)
head(deaths)
head(recovered)

Looking At March 22nd, 2020

The format of these three data frames are all the same — each row contains information about the number of cases in a certain province, state, or country. Every column (other than the columns containing the latitude, longitude, and country name) represents a date. We have data starting on January 1st, 2020 and ending on March 22nd, 2020 (the day this assignment was written).

Let’s warm up by finding the total number of confirmed cases on March 22nd, 2020. To do this, we’ll use both the select() and sum() functions. When using the select() function, make sure to put the name of the column in backticks (usually found in the top left corner of your keyboard)! This usually isn’t necessary, but since the name of the column contains the / character, we need backticks! Make sure to do this whenever you’re working with these columns.

# Sum the total number of cases on March 22nd
(total_cases <- confirmed %>% 
  select(`3/22/20`) %>% 
  sum())
[1] 335955

Filter By Values

Let’s start to filter the data a bit more. Give these three tasks a try:

You may want to save these results to variables as they will be useful later!

# Filter for countries in the northern hemisphere
(total_cases_north_hemisphere <- confirmed %>% 
  filter(Lat > 0) %>% 
  select(`3/22/20`) %>% 
  sum())
[1] 329794
# Filter for Australia cases
(total_cases_australia <- confirmed %>% 
  filter(`Country/Region` == "Australia") %>% 
  select(`3/22/20`) %>% 
  sum())
[1] 1314
# Display percentages
total_cases_north_hemisphere / total_cases * 100
[1] 98.16612
total_cases_australia / total_cases * 100
[1] 0.3911238

Group By Country

Notice that some countries have multiple rows of data. This happens when a country has information about specific states or provinces. While this information might be useful, it makes it a bit tricky to see the total number of cases by country.

We were able to get the total number of cases in Australia by using filter(), but what if we wanted to do this for every country? It would be a pain to have to type every country name into a filter function. Use group_by() to create a new data frame containing one row for every Country/Region.

Every column of those new rows should have the sum of the total number of cases for that country for every day. We can use the summarize_at() function to do this. summarize_at() takes two parameters:

  1. A vector of the columns you want to summarize. In this case we want every column other than Lat, Long, and Province/State. summarize_at() only works with numbers, so we’ll want to remove the column containing country names. We also don’t need to add the latitudes and longitudes. Remember, you can use the - symbol to list columns you want to exclude. Also don’t forget to use backticks around columns with / symbols. For example -`Province/State` will exclude that column. These small details get complicated!
  2. The function that you want to apply to the column. In this case, sum.

After creating this new data frame inspect it. To confirm you did the group_by() correctly, find the row for Australia and confirm the number of cases on March 22nd matches your results from the previous step

# Group by countries
(countries_confirmed <- confirmed %>% 
  group_by(`Country/Region`) %>% 
  summarize_at(vars(-`Province/State`:-Long), sum))
# Filter the grouped data set for Australia
countries_confirmed %>% 
  filter(`Country/Region` == "Australia") %>% 
  select(`3/22/20`)

Investigating The Recovered Dataset

Do the same process of grouping by country using the recovered dataset. What percentage of the cases in the US have recovered on March 22nd?

# Group by countries
(countries_recovered <- recovered %>% 
  group_by(`Country.Region`) %>% 
  summarize_at(vars(-`Province.State`:-Long), sum))

total_us <- countries_confirmed %>% filter(`Country/Region` == 'US') %>% select(`3/22/20`)
recovered_us <- countries_recovered %>% filter(`Country.Region` == 'US') %>% select(`X3.22.20`)
recovered_us /total_us * 100

You may see some surprising results — are there really zero recovered cases in the US? Let’s take a closer look at the US row in the recovered table. Either view the table in RStudio’s interface, or print the row in the cell block below. What do you notice about the values as you scroll to March 22nd?

# Filter to inspect the US row
recovered_grouped %>% 
  filter(`Country.Region` == "US")

It seems like the number of recovered cases is steadily increasing to 17, until March 18th, when it suddenly drops back to 0. This is surprising, and not what we expected! Perhaps we’re misunderstanding the format of the data, or perhaps there’s an error in reporting the data.

What’s the best course of action at this point? One strategy is to investigate the source of the data to truly understand what it is reporting. When we went back to Johns Hopkins’ repository, we found a note saying that the data had moved into a different file. It might be best to use that file!

If we wanted to use the current file, instead of reporting the number of confirmed and recovered cases in the US on March 22nd, we could report the maximum number of confirmed and recovered cases. Let’s give that a shot using the max() function. Make sure to also use select() to remove the Country/Region column — we can’t take the max of a row if it contains a string.

# Find the maximum number of confirmed and recovered cases
max_recovered <- countries_recovered %>%
  filter(`Country.Region` == "US") %>%
  select(-Country.Region) %>%
  max()
    
max_confirmed <- countries_confirmed %>%
  filter(`Country/Region` == "US") %>%
  select(-`Country/Region`) %>%
  max()

max_recovered / max_confirmed * 100
[1] 0.05109401

Transposing Data Frames

The code that you just wrote in the last prompt is a little ugly — we first have to select a particular row by the value in a particular column, remove that column from the row, and then find the maximum value of the remaining values. It would be much easier if we rotated the data frame so rows represented dates and columns represented countries. Try drawing a sketch of what this rotated data frame might look like!

We could then find the maximum value of a country by simply selecting the appropriate column and finding the maximum value in that column. Let’s try that! The t() function will transpose the data frame so all of the columns become rows. t() unfortunately returns a matrix instead of a data frame. If you pipe the result of t() through the as.data.frame() function, your result will be the transposed data frame. Try transposing one of your data frames by piping it through both t() and as.data.frame() and call head() to investigate it.

# Transpose the data frame
transposed_confirmed <- countries_confirmed %>% t() %>% as.data.frame()
transposed_confirmed %>% head()

Great! We’re almost there. But take a look at the column names. We want the column names to be the countries, but instead, the country names are in the first row. We need to figure out how to change the first row of a data frame into column names. Challenge yourself to try to figure this out on your own in the following code block. We’ll walk you through our solution in the following section.

colnames(transposed_confirmed) <- countries_confirmed$`Country/Region`
head(transposed_confirmed)

As usual, we used Google to help solve this problem. Learning how to properly search for answers to your programming questions is an essential skill. In this case, we specifically searched “r data frame set first row as column names”, which led us to a Stack Overflow page that suggested using the janitor library. That’s a cute name for a library — this janitor library will help us clean up our data frame.

First, we installed and loaded the janitor package. Then we passed our transposed data frame into the row_to_names() function and used the parameter row_number = 1.

library(janitor)

# Make the first row the column titles
transposed_confirmed <- transposed_confirmed %>% row_to_names(row_number = 1)
transposed_confirmed %>% head()

We’re getting closer. If you printed the head of the data frame you just created, you might have noticed that the columns are now of type <fctr>, or factor. This was one of the side effects of rotating the data frame. We want to turn all of these columns back into doubles.

To do so, pass your data frame to the apply() function. apply() takes two parameters in addition to the data set that you’re using. The MARGIN parameter should be set to 2 — this tells apply() to work with columns rather than rows.

The second parameter should be the function you want to apply to all columns. In this case, we want as.numeric.

Finally, after calling apply(), pipe the result through as.data.frame() to ensure we’re still working with a data frame. Print out the head of your result to see what the data now looks like. Look at the data type of the columns now!

# Transform the columns to numeric values
transposed_confirmed <- apply(transposed_confirmed, 2, as.numeric) %>% as.data.frame()
head(transposed_confirmed)

Nice work - we got there! Let’s see what our hard work allows us to do. Let’s once again find the maximum number of cases reported in the US. To do so, simply select the US column and pipe the result to the max() function. This should be the same value that we found before we rotate the data frame.

# Find the maximum number of confirmed cases in the US
transposed_confirmed %>% 
  select(US) %>% 
  max()
[1] 33272

Extra Credit: Visualization

We put a lot of work into rotating that data frame, and you may be wondering if it was worth it. In this section, we’re going to make some basic graphs that demonstrate the value of rotating the data frame.

You may not have been introduced to visualization yet. If this is unfamiliar to you, feel free to skip this section — in the following section we offer some ideas for more data manipulation challenges using this data set. That being said, if visualization is unfamiliar to you, you can use this as an opportunity to do some independent learning! Challenge yourself to do some research to create the following graph.

Let’s build a line graph showing the number of confirmed cases over time for a particular country. To do this, we’ll first need to add a new column to our dataset to represent the date (notice that when we rotated our data frame, we lost the column containing the date). The first day in our dataset was January 22nd. Let’s represent that as day 1. January 23rd would then be day 2, and so on. We want to add a column named date that contains the numbers 1 through the total number of rows in the dataset. Use mutate() to do this. nrow() will help find the number of rows in the dataset. Print out the head of the date column to ensure this worked.

# Add the date column
transposed_confirmed <- transposed_confirmed %>% mutate(date = c(1:nrow(transposed_confirmed)))
head(select(transposed_confirmed, date))
transposed_confirmed

Great! Our date column is all set. Let’s now graph the number of cases in Italy over the days in our dataset. We’ll use the ggplot2 library to do this. Use a combination of ggplot(), aes(), and geom_line() to make our graph! Once again, if you’re unfamiliar with graphing, try to do some online research to learn how to do this!

library(ggplot2)
# Create a line graph with date on the X axis and number of cases in Italy on the Y axis
p <- ggplot(transposed_confirmed, aes(x = date, y = Italy)) +
  geom_line()
p

That line of code is pretty concise. That’s one of the reasons why we wanted to rotate the data frame. Having a column containing only the confirmed cases from a particular country made this graph relatively simple to create.

Finally, let’s do a bit of work to add a title, and to fix the names of the axes. There are a few different ways to do this, but we used the labs() function which takes x, y and title parameters.

# Add a proper title, x label, and y label
p + labs(x = "Number of days since January 22nd, 2020", y = "Number of confirmed cases", title = "Confirmed cases of Wuhan Virus in Italy")

In the next section we’ll give you ideas on ways to continue visualizing this data.

Further Work

Great work! In this project you have truly refined your data manipulation skills using a real data set. In doing so, we hope that you’ve seen some of the difficulties you need to work through when working with data. Your data set might have annoying column names. Data may be missing or incorrect. You might need to reshape the data or change its type. Gaining the confidence to master these challenges unique to your dataset is an important part of being a data scientist.

If you’d like to continue working with this data, here are some challenges:

LS0tDQp0aXRsZTogIkludmVzdGlnYXRpbmcgdGhlIENvcm9uYXZpcnVzIChhLmsuYS4gV3VoYW4gVmlydXMpIFBhbmRlbWljIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KV2VsY29tZSB0byB5b3VyIG9mZi1wbGF0Zm9ybSBwcm9qZWN0IGZvY3VzZWQgb24gRGF0YSBNYW5pcHVsYXRpb24gaW4gUiEgVGhlIGdvYWwgb2YgdGhpcyBwcm9qZWN0IGlzIHRvIGdldCBleHBlcmllbmNlIHdvcmtpbmcgd2l0aCBtdWx0aXBsZSBgLmNzdmAgZmlsZXMgaW4gUlN0dWRpby4gV2UnbGwgYmUgY3JlYXRpbmcgc2V2ZXJhbCBkYXRhIGZyYW1lcyBhbmQgbWFuaXB1bGF0aW5nIHRoZW0gdG8gdHJ5IHRvIGZpbmQgbmV3IGluc2lnaHRzLg0KDQpJbiB0aGlzIHByb2plY3Qgd2Ugd2lsbCBiZSBsb29raW5nIGF0IGRhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBDb3JvbmF2aXJ1cyBwYW5kZW1pYyBmcm9tIDIwMjAuIFdoaWxlIHRoaXMgdG9waWMgaXNuJ3QgeW91ciB0eXBpY2FsIGxpZ2h0aGVhcnRlZCBDb2RlY2FkZW15IGFzc2lnbm1lbnQsIHdlIGZlbHQgbGlrZSBpdCBpcyBhIGV4Y2VsbGVudCB3YXkgdG8gdXNlIHlvdXIgbmV3IFIgc2tpbGxzIHRvIHVuZGVyc3RhbmQgdGhlIHdvcmxkIGFyb3VuZCB5b3UuIFRoaXMgYXNzaWdubWVudCB3YXMgd3JpdHRlbiBvbiBNYXJjaCAyM3JkLCAyMDIwICZtZGFzaDsgc2luY2UgeW91IHdpbGwgYmUgdGFraW5nIHRoaXMgYXNzaWdubWVudCBpbiB0aGUgZnV0dXJlLCB3ZeKAmXJlIGhvcGluZyB0aGF0IHRoaXMgY2FuIGJlIGFuIGludGVyZXN0aW5nIGhpc3RvcmljYWwgYW5hbHlzaXMgcmF0aGVyIHRoYW4gYW4gb25nb2luZyBpc3N1ZS4NCg0KIyBDb2xsZWN0IGFuZCBJbnZlc3RpZ2F0ZSB0aGUgRGF0YQ0KDQpXZeKAmXZlIHByb3ZpZGVkIHRocmVlIGRhdGFzZXRzIG5hbWVkIGBjb3JvbmFfY29uZmlybWVkLmNzdmAsIGBjb3JvbmFfcmVjb3ZlcmVkLmNzdmAgYW5kIGBjb3JvbmFfZGVhdGhzLmNzdmAuIEZvciB0aGUgZW50aXJldHkgb2YgdGhpcyBwcm9qZWN0LCB3ZeKAmWxsIGJlIHVzaW5nIHRoZXNlIGRhdGFzZXRzLiBZb3UgY2FuIGZpbmQgbW9yZSByZWNlbnQgdmVyc2lvbnMgb2YgdGhpcyBkYXRhIGF0IEpvaG5zIEhvcGtpbnPigJkgZGF0YSByZXBvc2l0b3J5IG9uIFtHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9DU1NFR0lTYW5kRGF0YS9DT1ZJRC0xOSkuDQoNClRoaXMgaXMgYSBnb29kIG9wcG9ydHVuaXR5IHRvIHByYWN0aWNlIGRvd25sb2FkaW5nIGRhdGEgZnJvbSBHaXRIdWIgb3Igb3RoZXIgcmVwb3NpdG9yaWVzLiBXZeKAmXJlIHVzaW5nIGZpbGVzIGZvdW5kIGluIHRoZSBgY3NzZV9jb3ZpZF8xOV90aW1lX3Nlcmllc2AgZm9sZGVyLiBJbiBhZGRpdGlvbiwgd2UgcmVjb21tZW5kIGxvb2tpbmcgYXQgc29tZSBvZiB0aGUgcHJvamVjdHMgbGlua2VkIG9uIHRoZSBHaXRIdWIgcGFnZSAmbWRhc2g7IHRoYXQgY2FuIGhlbHAgZ2l2ZSB5b3UgYSBiZXR0ZXIgc2Vuc2Ugb2Ygd2hhdCB0eXBlIG9mIGluZm9ybWF0aW9uIHlvdSBjYW4gZXh0cmFjdCBmcm9tIHRoaXMgZGF0YS4NCg0KRm9yIG5vdywgbG9hZCB0aGVzZSB0aHJlZSBgLmNzdmAgZmlsZXMgaW50byB0aHJlZSBzZXBhcmF0ZSBkYXRhZnJhbWVzLiBOb3RlIHRoYXQgd2UgdXNlZCB0aGUgYHJlYWRfY3N2KClgIGZ1bmN0aW9uIGZyb20gdGhlIGByZWFkcmAgbGlicmFyeS4gSWYgeW91IGxvYWQgdGhlIGNzdiBmaWxlcyB1c2luZyBhIGRpZmZlcmVudCBtZXRob2QsIHlvdSBtaWdodCBoYXZlIHNsaWdodGx5IGRpZmZlcmVudCBjb2x1bW4gbmFtZXMuDQoNCkluc3BlY3QgdGhlIGRhdGFmcmFtZXMgdG8gZ2V0IGEgc2Vuc2Ugb2YgaG93IHRoZSBkYXRhIGlzIHN0b3JlZCAod2UgcmVjb21tZW5kIHVzaW5nIGBoZWFkKClgKS4NCg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocmVhZHIpDQoNCiMgTG9hZCB0aGUgZGF0YQ0KY29uZmlybWVkIDwtIHJlYWRfY3N2KCJjb3JvbmFfY29uZmlybWVkLmNzdiIpDQpkZWF0aHMgPC0gcmVhZF9jc3YoImNvcm9uYV9kZWF0aHMuY3N2IikNCnJlY292ZXJlZCA8LSByZWFkLmNzdigiY29yb25hX3JlY292ZXJlZC5jc3YiKQ0KDQojIEluc3BlY3QgdGhlIGRhdGENCmhlYWQoY29uZmlybWVkKQ0KaGVhZChkZWF0aHMpDQpoZWFkKHJlY292ZXJlZCkNCmBgYA0KDQojIExvb2tpbmcgQXQgTWFyY2ggMjJuZCwgMjAyMA0KDQpUaGUgZm9ybWF0IG9mIHRoZXNlIHRocmVlIGRhdGEgZnJhbWVzIGFyZSBhbGwgdGhlIHNhbWUgJm1kYXNoOyBlYWNoIHJvdyBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgbnVtYmVyIG9mIGNhc2VzIGluIGEgY2VydGFpbiBwcm92aW5jZSwgc3RhdGUsIG9yIGNvdW50cnkuIEV2ZXJ5IGNvbHVtbiAob3RoZXIgdGhhbiB0aGUgY29sdW1ucyBjb250YWluaW5nIHRoZSBsYXRpdHVkZSwgbG9uZ2l0dWRlLCBhbmQgY291bnRyeSBuYW1lKSByZXByZXNlbnRzIGEgZGF0ZS4gV2UgaGF2ZSBkYXRhIHN0YXJ0aW5nIG9uIEphbnVhcnkgMXN0LCAyMDIwIGFuZCBlbmRpbmcgb24gTWFyY2ggMjJuZCwgMjAyMCAodGhlIGRheSB0aGlzIGFzc2lnbm1lbnQgd2FzIHdyaXR0ZW4pLg0KDQpMZXQncyB3YXJtIHVwIGJ5IGZpbmRpbmcgdGhlIHRvdGFsIG51bWJlciBvZiBjb25maXJtZWQgY2FzZXMgb24gTWFyY2ggMjJuZCwgMjAyMC4gVG8gZG8gdGhpcywgd2UnbGwgdXNlIGJvdGggdGhlIGBzZWxlY3QoKWAgYW5kIGBzdW0oKWAgZnVuY3Rpb25zLiBXaGVuIHVzaW5nIHRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uLCBtYWtlIHN1cmUgdG8gcHV0IHRoZSBuYW1lIG9mIHRoZSBjb2x1bW4gaW4gYmFja3RpY2tzICh1c3VhbGx5IGZvdW5kIGluIHRoZSB0b3AgbGVmdCBjb3JuZXIgb2YgeW91ciBrZXlib2FyZCkhIFRoaXMgdXN1YWxseSBpc24ndCBuZWNlc3NhcnksIGJ1dCBzaW5jZSB0aGUgbmFtZSBvZiB0aGUgY29sdW1uIGNvbnRhaW5zIHRoZSBgL2AgY2hhcmFjdGVyLCB3ZSBuZWVkIGJhY2t0aWNrcyEgTWFrZSBzdXJlIHRvIGRvIHRoaXMgd2hlbmV2ZXIgeW91J3JlIHdvcmtpbmcgd2l0aCB0aGVzZSBjb2x1bW5zLg0KDQpgYGB7cn0NCiMgU3VtIHRoZSB0b3RhbCBudW1iZXIgb2YgY2FzZXMgb24gTWFyY2ggMjJuZA0KKHRvdGFsX2Nhc2VzIDwtIGNvbmZpcm1lZCAlPiUgDQogIHNlbGVjdChgMy8yMi8yMGApICU+JSANCiAgc3VtKCkpDQpgYGANCg0KIyBGaWx0ZXIgQnkgVmFsdWVzDQoNCkxldCdzIHN0YXJ0IHRvIGZpbHRlciB0aGUgZGF0YSBhIGJpdCBtb3JlLiBHaXZlIHRoZXNlIHRocmVlIHRhc2tzIGEgdHJ5Og0KDQoqIEhvdyBtYW55IGNvbmZpcm1lZCBjYXNlcyBhcmUgdGhlcmUgaW4gY291bnRyaWVzIG9uIE1hcmNoIDIybmQgdGhhdCBhcmUgbm9ydGggb2YgdGhlIGVxdWF0b3I/IChJZiBhIGNvdW50cnkgaXMgbm9ydGggb2YgdGhlIGVxdWF0b3IsIGl0cyBsYXRpdHVkZSBpcyBncmVhdGVyIHRoYW4gYDBgKQ0KKiBIb3cgbWFueSBjb25maXJtZWQgY2FzZXMgYXJlIHRoZXJlIGluIE1hcmNoIDIybmQgaW4gQXVzdHJhbGlhPw0KKiBDYW4geW91IHJlcG9ydCBib3RoIG9mIHRob3NlIHZhbHVlcyBhcyBhIHBlcmNlbnRhZ2Ugb2YgdGhlIHRvdGFsIG51bWJlciBvZiBjYXNlcyBpbiB0aGUgd29ybGQgb24gTWFyY2ggMjJuZD8NCg0KWW91IG1heSB3YW50IHRvIHNhdmUgdGhlc2UgcmVzdWx0cyB0byB2YXJpYWJsZXMgYXMgdGhleSB3aWxsIGJlIHVzZWZ1bCBsYXRlciENCg0KYGBge3J9DQojIEZpbHRlciBmb3IgY291bnRyaWVzIGluIHRoZSBub3J0aGVybiBoZW1pc3BoZXJlDQoodG90YWxfY2FzZXNfbm9ydGhfaGVtaXNwaGVyZSA8LSBjb25maXJtZWQgJT4lIA0KICBmaWx0ZXIoTGF0ID4gMCkgJT4lIA0KICBzZWxlY3QoYDMvMjIvMjBgKSAlPiUgDQogIHN1bSgpKQ0KDQojIEZpbHRlciBmb3IgQXVzdHJhbGlhIGNhc2VzDQoodG90YWxfY2FzZXNfYXVzdHJhbGlhIDwtIGNvbmZpcm1lZCAlPiUgDQogIGZpbHRlcihgQ291bnRyeS9SZWdpb25gID09ICJBdXN0cmFsaWEiKSAlPiUgDQogIHNlbGVjdChgMy8yMi8yMGApICU+JSANCiAgc3VtKCkpDQojIERpc3BsYXkgcGVyY2VudGFnZXMNCnRvdGFsX2Nhc2VzX25vcnRoX2hlbWlzcGhlcmUgLyB0b3RhbF9jYXNlcyAqIDEwMA0KdG90YWxfY2FzZXNfYXVzdHJhbGlhIC8gdG90YWxfY2FzZXMgKiAxMDANCg0KYGBgDQoNCiMgR3JvdXAgQnkgQ291bnRyeQ0KDQpOb3RpY2UgdGhhdCBzb21lIGNvdW50cmllcyBoYXZlIG11bHRpcGxlIHJvd3Mgb2YgZGF0YS4gVGhpcyBoYXBwZW5zIHdoZW4gYSBjb3VudHJ5IGhhcyBpbmZvcm1hdGlvbiBhYm91dCBzcGVjaWZpYyBzdGF0ZXMgb3IgcHJvdmluY2VzLiBXaGlsZSB0aGlzIGluZm9ybWF0aW9uIG1pZ2h0IGJlIHVzZWZ1bCwgaXQgbWFrZXMgaXQgYSBiaXQgdHJpY2t5IHRvIHNlZSB0aGUgdG90YWwgbnVtYmVyIG9mIGNhc2VzIGJ5IGNvdW50cnkuDQoNCldlIHdlcmUgYWJsZSB0byBnZXQgdGhlIHRvdGFsIG51bWJlciBvZiBjYXNlcyBpbiBBdXN0cmFsaWEgYnkgdXNpbmcgYGZpbHRlcigpYCwgYnV0IHdoYXQgaWYgd2Ugd2FudGVkIHRvIGRvIHRoaXMgZm9yIGV2ZXJ5IGNvdW50cnk/IEl0IHdvdWxkIGJlIGEgcGFpbiB0byBoYXZlIHRvIHR5cGUgZXZlcnkgY291bnRyeSBuYW1lIGludG8gYSBmaWx0ZXIgZnVuY3Rpb24uIFVzZSBgZ3JvdXBfYnkoKWAgdG8gY3JlYXRlIGEgbmV3IGRhdGEgZnJhbWUgY29udGFpbmluZyBvbmUgcm93IGZvciBldmVyeSBgQ291bnRyeS9SZWdpb25gLiANCg0KRXZlcnkgY29sdW1uIG9mIHRob3NlIG5ldyByb3dzIHNob3VsZCBoYXZlIHRoZSBzdW0gb2YgdGhlIHRvdGFsIG51bWJlciBvZiBjYXNlcyBmb3IgdGhhdCBjb3VudHJ5IGZvciBldmVyeSBkYXkuIFdlIGNhbiB1c2UgdGhlIFtgc3VtbWFyaXplX2F0KClgXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N1bW1hcmlzZV9hbGwuaHRtbCkgZnVuY3Rpb24gdG8gZG8gdGhpcy4gYHN1bW1hcml6ZV9hdCgpYCB0YWtlcyB0d28gcGFyYW1ldGVyczoNCg0KMS4gQSB2ZWN0b3Igb2YgdGhlIGNvbHVtbnMgeW91IHdhbnQgdG8gc3VtbWFyaXplLiBJbiB0aGlzIGNhc2Ugd2Ugd2FudCBldmVyeSBjb2x1bW4gb3RoZXIgdGhhbiBgTGF0YCwgYExvbmdgLCBhbmQgYFByb3ZpbmNlL1N0YXRlYC4gYHN1bW1hcml6ZV9hdCgpYCBvbmx5IHdvcmtzIHdpdGggbnVtYmVycywgc28gd2UnbGwgd2FudCB0byByZW1vdmUgdGhlIGNvbHVtbiBjb250YWluaW5nIGNvdW50cnkgbmFtZXMuIFdlIGFsc28gZG9uJ3QgbmVlZCB0byBhZGQgdGhlIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcy4gUmVtZW1iZXIsIHlvdSBjYW4gdXNlIHRoZSBgLWAgc3ltYm9sIHRvIGxpc3QgY29sdW1ucyB5b3Ugd2FudCB0byBleGNsdWRlLiBBbHNvIGRvbid0IGZvcmdldCB0byB1c2UgYmFja3RpY2tzIGFyb3VuZCBjb2x1bW5zIHdpdGggYC9gIHN5bWJvbHMuIEZvciBleGFtcGxlIC1cYFByb3ZpbmNlL1N0YXRlXGAgd2lsbCBleGNsdWRlIHRoYXQgY29sdW1uLiBUaGVzZSBzbWFsbCBkZXRhaWxzIGdldCBjb21wbGljYXRlZCENCjIuIFRoZSBmdW5jdGlvbiB0aGF0IHlvdSB3YW50IHRvIGFwcGx5IHRvIHRoZSBjb2x1bW4uIEluIHRoaXMgY2FzZSwgYHN1bWAuDQoNCkFmdGVyIGNyZWF0aW5nIHRoaXMgbmV3IGRhdGEgZnJhbWUgaW5zcGVjdCBpdC4gVG8gY29uZmlybSB5b3UgZGlkIHRoZSBgZ3JvdXBfYnkoKWAgY29ycmVjdGx5LCBmaW5kIHRoZSByb3cgZm9yIEF1c3RyYWxpYSBhbmQgY29uZmlybSB0aGUgbnVtYmVyIG9mIGNhc2VzIG9uIE1hcmNoIDIybmQgbWF0Y2hlcyB5b3VyIHJlc3VsdHMgZnJvbSB0aGUgcHJldmlvdXMgc3RlcA0KDQpgYGB7cn0NCiMgR3JvdXAgYnkgY291bnRyaWVzDQooY291bnRyaWVzX2NvbmZpcm1lZCA8LSBjb25maXJtZWQgJT4lIA0KICBncm91cF9ieShgQ291bnRyeS9SZWdpb25gKSAlPiUgDQogIHN1bW1hcml6ZV9hdCh2YXJzKC1gUHJvdmluY2UvU3RhdGVgOi1Mb25nKSwgc3VtKSkNCiMgRmlsdGVyIHRoZSBncm91cGVkIGRhdGEgc2V0IGZvciBBdXN0cmFsaWENCmNvdW50cmllc19jb25maXJtZWQgJT4lIA0KICBmaWx0ZXIoYENvdW50cnkvUmVnaW9uYCA9PSAiQXVzdHJhbGlhIikgJT4lIA0KICBzZWxlY3QoYDMvMjIvMjBgKQ0KYGBgDQoNCiMgSW52ZXN0aWdhdGluZyBUaGUgUmVjb3ZlcmVkIERhdGFzZXQNCg0KRG8gdGhlIHNhbWUgcHJvY2VzcyBvZiBncm91cGluZyBieSBjb3VudHJ5IHVzaW5nIHRoZSByZWNvdmVyZWQgZGF0YXNldC4gV2hhdCBwZXJjZW50YWdlIG9mIHRoZSBjYXNlcyBpbiB0aGUgYFVTYCBoYXZlIHJlY292ZXJlZCBvbiBNYXJjaCAyMm5kPw0KDQpgYGB7cn0NCiMgR3JvdXAgYnkgY291bnRyaWVzDQooY291bnRyaWVzX3JlY292ZXJlZCA8LSByZWNvdmVyZWQgJT4lIA0KICBncm91cF9ieShgQ291bnRyeS5SZWdpb25gKSAlPiUgDQogIHN1bW1hcml6ZV9hdCh2YXJzKC1gUHJvdmluY2UuU3RhdGVgOi1Mb25nKSwgc3VtKSkNCg0KdG90YWxfdXMgPC0gY291bnRyaWVzX2NvbmZpcm1lZCAlPiUgZmlsdGVyKGBDb3VudHJ5L1JlZ2lvbmAgPT0gJ1VTJykgJT4lIHNlbGVjdChgMy8yMi8yMGApDQpyZWNvdmVyZWRfdXMgPC0gY291bnRyaWVzX3JlY292ZXJlZCAlPiUgZmlsdGVyKGBDb3VudHJ5LlJlZ2lvbmAgPT0gJ1VTJykgJT4lIHNlbGVjdChgWDMuMjIuMjBgKQ0KcmVjb3ZlcmVkX3VzIC90b3RhbF91cyAqIDEwMA0KYGBgDQoNCllvdSBtYXkgc2VlIHNvbWUgc3VycHJpc2luZyByZXN1bHRzICZtZGFzaDsgYXJlIHRoZXJlIHJlYWxseSB6ZXJvIHJlY292ZXJlZCBjYXNlcyBpbiB0aGUgVVM/IExldCdzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgYFVTYCByb3cgaW4gdGhlIGByZWNvdmVyZWRgIHRhYmxlLiBFaXRoZXIgdmlldyB0aGUgdGFibGUgaW4gUlN0dWRpbydzIGludGVyZmFjZSwgb3IgcHJpbnQgdGhlIHJvdyBpbiB0aGUgY2VsbCBibG9jayBiZWxvdy4gV2hhdCBkbyB5b3Ugbm90aWNlIGFib3V0IHRoZSB2YWx1ZXMgYXMgeW91IHNjcm9sbCB0byBNYXJjaCAyMm5kPw0KDQpgYGB7cn0NCiMgRmlsdGVyIHRvIGluc3BlY3QgdGhlIFVTIHJvdw0KY291bnRyaWVzX3JlY292ZXJlZCAlPiUgDQogIGZpbHRlcihgQ291bnRyeS5SZWdpb25gID09ICJVUyIpDQpgYGANCg0KSXQgc2VlbXMgbGlrZSB0aGUgbnVtYmVyIG9mIHJlY292ZXJlZCBjYXNlcyBpcyBzdGVhZGlseSBpbmNyZWFzaW5nIHRvIDE3LCB1bnRpbCBNYXJjaCAxOHRoLCB3aGVuIGl0IHN1ZGRlbmx5IGRyb3BzIGJhY2sgdG8gMC4gVGhpcyBpcyBzdXJwcmlzaW5nLCBhbmQgbm90IHdoYXQgd2UgZXhwZWN0ZWQhIFBlcmhhcHMgd2UncmUgbWlzdW5kZXJzdGFuZGluZyB0aGUgZm9ybWF0IG9mIHRoZSBkYXRhLCBvciBwZXJoYXBzIHRoZXJlJ3MgYW4gZXJyb3IgaW4gcmVwb3J0aW5nIHRoZSBkYXRhLg0KDQpXaGF0J3MgdGhlIGJlc3QgY291cnNlIG9mIGFjdGlvbiBhdCB0aGlzIHBvaW50PyBPbmUgc3RyYXRlZ3kgaXMgdG8gaW52ZXN0aWdhdGUgdGhlIHNvdXJjZSBvZiB0aGUgZGF0YSB0byB0cnVseSB1bmRlcnN0YW5kIHdoYXQgaXQgaXMgcmVwb3J0aW5nLiBXaGVuIHdlIHdlbnQgYmFjayB0byBbSm9obnMgSG9wa2lucycgcmVwb3NpdG9yeV0oaHR0cHM6Ly9naXRodWIuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L3RyZWUvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzKSwgd2UgZm91bmQgYSBub3RlIHNheWluZyB0aGF0IHRoZSBkYXRhIGhhZCBtb3ZlZCBpbnRvIGEgZGlmZmVyZW50IGZpbGUuIEl0IG1pZ2h0IGJlIGJlc3QgdG8gdXNlIHRoYXQgZmlsZSENCg0KSWYgd2Ugd2FudGVkIHRvIHVzZSB0aGUgY3VycmVudCBmaWxlLCBpbnN0ZWFkIG9mIHJlcG9ydGluZyB0aGUgbnVtYmVyIG9mIGNvbmZpcm1lZCBhbmQgcmVjb3ZlcmVkIGNhc2VzIGluIHRoZSBVUyBvbiBNYXJjaCAyMm5kLCB3ZSBjb3VsZCByZXBvcnQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmZpcm1lZCBhbmQgcmVjb3ZlcmVkIGNhc2VzLiBMZXQncyBnaXZlIHRoYXQgYSBzaG90IHVzaW5nIHRoZSBgbWF4KClgIGZ1bmN0aW9uLiBNYWtlIHN1cmUgdG8gYWxzbyB1c2UgYHNlbGVjdCgpYCB0byByZW1vdmUgdGhlIGBDb3VudHJ5L1JlZ2lvbmAgY29sdW1uICZtZGFzaDsgd2UgY2FuJ3QgdGFrZSB0aGUgbWF4IG9mIGEgcm93IGlmIGl0IGNvbnRhaW5zIGEgc3RyaW5nLg0KYGBge3J9DQojIEZpbmQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmZpcm1lZCBhbmQgcmVjb3ZlcmVkIGNhc2VzDQptYXhfcmVjb3ZlcmVkIDwtIGNvdW50cmllc19yZWNvdmVyZWQgJT4lDQogIGZpbHRlcihgQ291bnRyeS5SZWdpb25gID09ICJVUyIpICU+JQ0KICBzZWxlY3QoLUNvdW50cnkuUmVnaW9uKSAlPiUNCiAgbWF4KCkNCiAgICANCm1heF9jb25maXJtZWQgPC0gY291bnRyaWVzX2NvbmZpcm1lZCAlPiUNCiAgZmlsdGVyKGBDb3VudHJ5L1JlZ2lvbmAgPT0gIlVTIikgJT4lDQogIHNlbGVjdCgtYENvdW50cnkvUmVnaW9uYCkgJT4lDQogIG1heCgpDQoNCm1heF9yZWNvdmVyZWQgLyBtYXhfY29uZmlybWVkICogMTAwDQpgYGANCg0KIyBUcmFuc3Bvc2luZyBEYXRhIEZyYW1lcw0KDQpUaGUgY29kZSB0aGF0IHlvdSBqdXN0IHdyb3RlIGluIHRoZSBsYXN0IHByb21wdCBpcyBhIGxpdHRsZSB1Z2x5ICZtZGFzaDsgd2UgZmlyc3QgaGF2ZSB0byBzZWxlY3QgYSBwYXJ0aWN1bGFyIHJvdyBieSB0aGUgdmFsdWUgaW4gYSBwYXJ0aWN1bGFyIGNvbHVtbiwgcmVtb3ZlIHRoYXQgY29sdW1uIGZyb20gdGhlIHJvdywgYW5kIHRoZW4gZmluZCB0aGUgbWF4aW11bSB2YWx1ZSBvZiB0aGUgcmVtYWluaW5nIHZhbHVlcy4gSXQgd291bGQgYmUgbXVjaCBlYXNpZXIgaWYgd2Ugcm90YXRlZCB0aGUgZGF0YSBmcmFtZSBzbyByb3dzIHJlcHJlc2VudGVkIGRhdGVzIGFuZCBjb2x1bW5zIHJlcHJlc2VudGVkIGNvdW50cmllcy4gVHJ5IGRyYXdpbmcgYSBza2V0Y2ggb2Ygd2hhdCB0aGlzIHJvdGF0ZWQgZGF0YSBmcmFtZSBtaWdodCBsb29rIGxpa2UhDQoNCldlIGNvdWxkIHRoZW4gZmluZCB0aGUgbWF4aW11bSB2YWx1ZSBvZiBhIGNvdW50cnkgYnkgc2ltcGx5IHNlbGVjdGluZyB0aGUgYXBwcm9wcmlhdGUgY29sdW1uIGFuZCBmaW5kaW5nIHRoZSBtYXhpbXVtIHZhbHVlIGluIHRoYXQgY29sdW1uLiBMZXQncyB0cnkgdGhhdCEgVGhlIGB0KClgIGZ1bmN0aW9uIHdpbGwgdHJhbnNwb3NlIHRoZSBkYXRhIGZyYW1lIHNvIGFsbCBvZiB0aGUgY29sdW1ucyBiZWNvbWUgcm93cy4gYHQoKWAgdW5mb3J0dW5hdGVseSByZXR1cm5zIGEgbWF0cml4IGluc3RlYWQgb2YgYSBkYXRhIGZyYW1lLiBJZiB5b3UgcGlwZSB0aGUgcmVzdWx0IG9mIGB0KClgIHRocm91Z2ggdGhlIGBhcy5kYXRhLmZyYW1lKClgIGZ1bmN0aW9uLCB5b3VyIHJlc3VsdCB3aWxsIGJlIHRoZSB0cmFuc3Bvc2VkIGRhdGEgZnJhbWUuIFRyeSB0cmFuc3Bvc2luZyBvbmUgb2YgeW91ciBkYXRhIGZyYW1lcyBieSBwaXBpbmcgaXQgdGhyb3VnaCBib3RoIGB0KClgIGFuZCBgYXMuZGF0YS5mcmFtZSgpYCBhbmQgY2FsbCBgaGVhZCgpYCB0byBpbnZlc3RpZ2F0ZSBpdC4NCg0KYGBge3J9DQojIFRyYW5zcG9zZSB0aGUgZGF0YSBmcmFtZQ0KdHJhbnNwb3NlZF9jb25maXJtZWQgPC0gY291bnRyaWVzX2NvbmZpcm1lZCAlPiUgdCgpICU+JSBhcy5kYXRhLmZyYW1lKCkNCnRyYW5zcG9zZWRfY29uZmlybWVkICU+JSBoZWFkKCkNCmBgYA0KDQoNCkdyZWF0ISBXZSdyZSBhbG1vc3QgdGhlcmUuIEJ1dCB0YWtlIGEgbG9vayBhdCB0aGUgY29sdW1uIG5hbWVzLiBXZSB3YW50IHRoZSBjb2x1bW4gbmFtZXMgdG8gYmUgdGhlIGNvdW50cmllcywgYnV0IGluc3RlYWQsIHRoZSBjb3VudHJ5IG5hbWVzIGFyZSBpbiB0aGUgZmlyc3Qgcm93LiBXZSBuZWVkIHRvIGZpZ3VyZSBvdXQgaG93IHRvIGNoYW5nZSB0aGUgZmlyc3Qgcm93IG9mIGEgZGF0YSBmcmFtZSBpbnRvIGNvbHVtbiBuYW1lcy4gQ2hhbGxlbmdlIHlvdXJzZWxmIHRvIHRyeSB0byBmaWd1cmUgdGhpcyBvdXQgb24geW91ciBvd24gaW4gdGhlIGZvbGxvd2luZyBjb2RlIGJsb2NrLiBXZSdsbCB3YWxrIHlvdSB0aHJvdWdoIG91ciBzb2x1dGlvbiBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb24uDQoNCmBgYHtyfQ0KY29sbmFtZXModHJhbnNwb3NlZF9jb25maXJtZWQpIDwtIGNvdW50cmllc19jb25maXJtZWQkYENvdW50cnkvUmVnaW9uYA0KaGVhZCh0cmFuc3Bvc2VkX2NvbmZpcm1lZCkNCmBgYA0KDQpBcyB1c3VhbCwgd2UgdXNlZCBHb29nbGUgdG8gaGVscCBzb2x2ZSB0aGlzIHByb2JsZW0uIExlYXJuaW5nIGhvdyB0byBwcm9wZXJseSBzZWFyY2ggZm9yIGFuc3dlcnMgdG8geW91ciBwcm9ncmFtbWluZyBxdWVzdGlvbnMgaXMgYW4gZXNzZW50aWFsIHNraWxsLiBJbiB0aGlzIGNhc2UsICB3ZSBzcGVjaWZpY2FsbHkgc2VhcmNoZWQgInIgZGF0YSBmcmFtZSBzZXQgZmlyc3Qgcm93IGFzIGNvbHVtbiBuYW1lcyIsIHdoaWNoIGxlZCB1cyB0byBhIFtTdGFjayBPdmVyZmxvdyBwYWdlXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zMjA1NDM2OC91c2UtZmlyc3Qtcm93LWRhdGEtYXMtY29sdW1uLW5hbWVzLWluLXIpIHRoYXQgc3VnZ2VzdGVkIHVzaW5nIHRoZSBgamFuaXRvcmAgbGlicmFyeS4gVGhhdCdzIGEgY3V0ZSBuYW1lIGZvciBhIGxpYnJhcnkgJm1kYXNoOyB0aGlzIGBqYW5pdG9yYCBsaWJyYXJ5IHdpbGwgaGVscCB1cyBjbGVhbiB1cCBvdXIgZGF0YSBmcmFtZS4NCg0KRmlyc3QsIHdlIGluc3RhbGxlZCBhbmQgbG9hZGVkIHRoZSBgamFuaXRvcmAgcGFja2FnZS4gVGhlbiB3ZSBwYXNzZWQgb3VyIHRyYW5zcG9zZWQgZGF0YSBmcmFtZSBpbnRvIHRoZSBgcm93X3RvX25hbWVzKClgIGZ1bmN0aW9uIGFuZCB1c2VkIHRoZSBwYXJhbWV0ZXIgYHJvd19udW1iZXIgPSAxYC4NCg0KYGBge3J9DQpsaWJyYXJ5KGphbml0b3IpDQoNCiMgTWFrZSB0aGUgZmlyc3Qgcm93IHRoZSBjb2x1bW4gdGl0bGVzDQp0cmFuc3Bvc2VkX2NvbmZpcm1lZCA8LSB0cmFuc3Bvc2VkX2NvbmZpcm1lZCAlPiUgcm93X3RvX25hbWVzKHJvd19udW1iZXIgPSAxKQ0KdHJhbnNwb3NlZF9jb25maXJtZWQgJT4lIGhlYWQoKQ0KYGBgDQoNCldlJ3JlIGdldHRpbmcgY2xvc2VyLiBJZiB5b3UgcHJpbnRlZCB0aGUgaGVhZCBvZiB0aGUgZGF0YSBmcmFtZSB5b3UganVzdCBjcmVhdGVkLCB5b3UgbWlnaHQgaGF2ZSBub3RpY2VkIHRoYXQgdGhlIGNvbHVtbnMgYXJlIG5vdyBvZiB0eXBlIGA8ZmN0cj5gLCBvciBmYWN0b3IuIFRoaXMgd2FzIG9uZSBvZiB0aGUgc2lkZSBlZmZlY3RzIG9mIHJvdGF0aW5nIHRoZSBkYXRhIGZyYW1lLiBXZSB3YW50IHRvIHR1cm4gYWxsIG9mIHRoZXNlIGNvbHVtbnMgYmFjayBpbnRvIGRvdWJsZXMuDQoNClRvIGRvIHNvLCBwYXNzIHlvdXIgZGF0YSBmcmFtZSB0byB0aGUgW2BhcHBseSgpYF0oaHR0cHM6Ly93d3cuZ3VydTk5LmNvbS9yLWFwcGx5LXNhcHBseS10YXBwbHkuaHRtbCkgZnVuY3Rpb24uIGBhcHBseSgpYCB0YWtlcyB0d28gcGFyYW1ldGVycyBpbiBhZGRpdGlvbiB0byB0aGUgZGF0YSBzZXQgdGhhdCB5b3UncmUgdXNpbmcuIFRoZSBgTUFSR0lOYCBwYXJhbWV0ZXIgc2hvdWxkIGJlIHNldCB0byBgMmAgJm1kYXNoOyB0aGlzIHRlbGxzIGBhcHBseSgpYCB0byB3b3JrIHdpdGggY29sdW1ucyByYXRoZXIgdGhhbiByb3dzLg0KDQpUaGUgc2Vjb25kIHBhcmFtZXRlciBzaG91bGQgYmUgdGhlIGZ1bmN0aW9uIHlvdSB3YW50IHRvIGFwcGx5IHRvIGFsbCBjb2x1bW5zLiBJbiB0aGlzIGNhc2UsIHdlIHdhbnQgYGFzLm51bWVyaWNgLg0KDQpGaW5hbGx5LCBhZnRlciBjYWxsaW5nIGBhcHBseSgpYCwgcGlwZSB0aGUgcmVzdWx0IHRocm91Z2ggYGFzLmRhdGEuZnJhbWUoKWAgdG8gZW5zdXJlIHdlJ3JlIHN0aWxsIHdvcmtpbmcgd2l0aCBhIGRhdGEgZnJhbWUuIFByaW50IG91dCB0aGUgaGVhZCBvZiB5b3VyIHJlc3VsdCB0byBzZWUgd2hhdCB0aGUgZGF0YSBub3cgbG9va3MgbGlrZS4gTG9vayBhdCB0aGUgZGF0YSB0eXBlIG9mIHRoZSBjb2x1bW5zIG5vdyENCg0KYGBge3J9DQojIFRyYW5zZm9ybSB0aGUgY29sdW1ucyB0byBudW1lcmljIHZhbHVlcw0KdHJhbnNwb3NlZF9jb25maXJtZWQgPC0gYXBwbHkodHJhbnNwb3NlZF9jb25maXJtZWQsIDIsIGFzLm51bWVyaWMpICU+JSBhcy5kYXRhLmZyYW1lKCkNCmhlYWQodHJhbnNwb3NlZF9jb25maXJtZWQpDQpgYGANCg0KTmljZSB3b3JrIC0gd2UgZ290IHRoZXJlISBMZXQncyBzZWUgd2hhdCBvdXIgaGFyZCB3b3JrIGFsbG93cyB1cyB0byBkby4gTGV0J3Mgb25jZSBhZ2FpbiBmaW5kIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjYXNlcyByZXBvcnRlZCBpbiB0aGUgYFVTYC4gVG8gZG8gc28sIHNpbXBseSBzZWxlY3QgdGhlIGBVU2AgY29sdW1uIGFuZCBwaXBlIHRoZSByZXN1bHQgdG8gdGhlIGBtYXgoKWAgZnVuY3Rpb24uIFRoaXMgc2hvdWxkIGJlIHRoZSBzYW1lIHZhbHVlIHRoYXQgd2UgZm91bmQgYmVmb3JlIHdlIHJvdGF0ZSB0aGUgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQojIEZpbmQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBpbiB0aGUgVVMNCnRyYW5zcG9zZWRfY29uZmlybWVkICU+JSANCiAgc2VsZWN0KFVTKSAlPiUgDQogIG1heCgpDQpgYGANCg0KIyBFeHRyYSBDcmVkaXQ6IFZpc3VhbGl6YXRpb24NCg0KV2UgcHV0IGEgbG90IG9mIHdvcmsgaW50byByb3RhdGluZyB0aGF0IGRhdGEgZnJhbWUsIGFuZCB5b3UgbWF5IGJlIHdvbmRlcmluZyBpZiBpdCB3YXMgd29ydGggaXQuIEluIHRoaXMgc2VjdGlvbiwgd2UncmUgZ29pbmcgdG8gbWFrZSBzb21lIGJhc2ljIGdyYXBocyB0aGF0IGRlbW9uc3RyYXRlIHRoZSB2YWx1ZSBvZiByb3RhdGluZyB0aGUgZGF0YSBmcmFtZS4NCg0KWW91IG1heSBub3QgaGF2ZSBiZWVuIGludHJvZHVjZWQgdG8gdmlzdWFsaXphdGlvbiB5ZXQuIElmIHRoaXMgaXMgdW5mYW1pbGlhciB0byB5b3UsIGZlZWwgZnJlZSB0byBza2lwIHRoaXMgc2VjdGlvbiAmbWRhc2g7IGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbiB3ZSBvZmZlciBzb21lIGlkZWFzIGZvciBtb3JlIGRhdGEgbWFuaXB1bGF0aW9uIGNoYWxsZW5nZXMgdXNpbmcgdGhpcyBkYXRhIHNldC4gVGhhdCBiZWluZyBzYWlkLCBpZiB2aXN1YWxpemF0aW9uIGlzIHVuZmFtaWxpYXIgdG8geW91LCB5b3UgY2FuIHVzZSB0aGlzIGFzIGFuIG9wcG9ydHVuaXR5IHRvIGRvIHNvbWUgaW5kZXBlbmRlbnQgbGVhcm5pbmchIENoYWxsZW5nZSB5b3Vyc2VsZiB0byBkbyBzb21lIHJlc2VhcmNoIHRvIGNyZWF0ZSB0aGUgZm9sbG93aW5nIGdyYXBoLg0KDQpMZXQncyBidWlsZCBhIGxpbmUgZ3JhcGggc2hvd2luZyB0aGUgbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBvdmVyIHRpbWUgZm9yIGEgcGFydGljdWxhciBjb3VudHJ5LiBUbyBkbyB0aGlzLCB3ZSdsbCBmaXJzdCBuZWVkIHRvIGFkZCBhIG5ldyBjb2x1bW4gdG8gb3VyIGRhdGFzZXQgdG8gcmVwcmVzZW50IHRoZSBkYXRlIChub3RpY2UgdGhhdCB3aGVuIHdlIHJvdGF0ZWQgb3VyIGRhdGEgZnJhbWUsIHdlIGxvc3QgdGhlIGNvbHVtbiBjb250YWluaW5nIHRoZSBkYXRlKS4gVGhlIGZpcnN0IGRheSBpbiBvdXIgZGF0YXNldCB3YXMgSmFudWFyeSAyMm5kLiBMZXQncyByZXByZXNlbnQgdGhhdCBhcyBkYXkgYDFgLiBKYW51YXJ5IDIzcmQgd291bGQgdGhlbiBiZSBkYXkgYDJgLCBhbmQgc28gb24uIFdlIHdhbnQgdG8gYWRkIGEgY29sdW1uIG5hbWVkIGBkYXRlYCB0aGF0IGNvbnRhaW5zIHRoZSBudW1iZXJzIGAxYCB0aHJvdWdoIHRoZSB0b3RhbCBudW1iZXIgb2Ygcm93cyBpbiB0aGUgZGF0YXNldC4gVXNlIGBtdXRhdGUoKWAgdG8gZG8gdGhpcy4gYG5yb3coKWAgd2lsbCBoZWxwIGZpbmQgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSBkYXRhc2V0LiBQcmludCBvdXQgdGhlIGhlYWQgb2YgdGhlIGBkYXRlYCBjb2x1bW4gdG8gZW5zdXJlIHRoaXMgd29ya2VkLg0KDQpgYGB7cn0NCiMgQWRkIHRoZSBkYXRlIGNvbHVtbg0KdHJhbnNwb3NlZF9jb25maXJtZWQgPC0gdHJhbnNwb3NlZF9jb25maXJtZWQgJT4lIG11dGF0ZShkYXRlID0gYygxOm5yb3codHJhbnNwb3NlZF9jb25maXJtZWQpKSkNCmhlYWQoc2VsZWN0KHRyYW5zcG9zZWRfY29uZmlybWVkLCBkYXRlKSkNCnRyYW5zcG9zZWRfY29uZmlybWVkDQpgYGANCg0KR3JlYXQhIE91ciBkYXRlIGNvbHVtbiBpcyBhbGwgc2V0LiBMZXQncyBub3cgZ3JhcGggdGhlIG51bWJlciBvZiBjYXNlcyBpbiBJdGFseSBvdmVyIHRoZSBkYXlzIGluIG91ciBkYXRhc2V0LiBXZSdsbCB1c2UgdGhlIGBnZ3Bsb3QyYCBsaWJyYXJ5IHRvIGRvIHRoaXMuIFVzZSBhIGNvbWJpbmF0aW9uIG9mIGBnZ3Bsb3QoKWAsIGBhZXMoKWAsIGFuZCBgZ2VvbV9saW5lKClgIHRvIG1ha2Ugb3VyIGdyYXBoISBPbmNlIGFnYWluLCBpZiB5b3UncmUgdW5mYW1pbGlhciB3aXRoIGdyYXBoaW5nLCB0cnkgdG8gZG8gc29tZSBvbmxpbmUgcmVzZWFyY2ggdG8gbGVhcm4gaG93IHRvIGRvIHRoaXMhDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KIyBDcmVhdGUgYSBsaW5lIGdyYXBoIHdpdGggZGF0ZSBvbiB0aGUgWCBheGlzIGFuZCBudW1iZXIgb2YgY2FzZXMgaW4gSXRhbHkgb24gdGhlIFkgYXhpcw0KcCA8LSBnZ3Bsb3QodHJhbnNwb3NlZF9jb25maXJtZWQsIGFlcyh4ID0gZGF0ZSwgeSA9IEl0YWx5KSkgKw0KICBnZW9tX2xpbmUoKQ0KcA0KYGBgDQoNClRoYXQgbGluZSBvZiBjb2RlIGlzIHByZXR0eSBjb25jaXNlLiBUaGF0J3Mgb25lIG9mIHRoZSByZWFzb25zIHdoeSB3ZSB3YW50ZWQgdG8gcm90YXRlIHRoZSBkYXRhIGZyYW1lLiBIYXZpbmcgYSBjb2x1bW4gY29udGFpbmluZyBvbmx5IHRoZSBjb25maXJtZWQgY2FzZXMgZnJvbSBhIHBhcnRpY3VsYXIgY291bnRyeSBtYWRlIHRoaXMgZ3JhcGggcmVsYXRpdmVseSBzaW1wbGUgdG8gY3JlYXRlLg0KDQpGaW5hbGx5LCBsZXQncyBkbyBhIGJpdCBvZiB3b3JrIHRvIGFkZCBhIHRpdGxlLCBhbmQgdG8gZml4IHRoZSBuYW1lcyBvZiB0aGUgYXhlcy4gVGhlcmUgYXJlIGEgZmV3IGRpZmZlcmVudCB3YXlzIHRvIGRvIHRoaXMsIGJ1dCB3ZSB1c2VkIHRoZSBgbGFicygpYCBmdW5jdGlvbiB3aGljaCB0YWtlcyBgeGAsIGB5YCBhbmQgYHRpdGxlYCBwYXJhbWV0ZXJzLg0KDQpgYGB7cn0NCiMgQWRkIGEgcHJvcGVyIHRpdGxlLCB4IGxhYmVsLCBhbmQgeSBsYWJlbA0KcCArIGxhYnMoeCA9ICJOdW1iZXIgb2YgZGF5cyBzaW5jZSBKYW51YXJ5IDIybmQsIDIwMjAiLCB5ID0gIk51bWJlciBvZiBjb25maXJtZWQgY2FzZXMiLCB0aXRsZSA9ICJDb25maXJtZWQgY2FzZXMgb2YgV3VoYW4gVmlydXMgaW4gSXRhbHkiKQ0KYGBgDQoNCkluIHRoZSBuZXh0IHNlY3Rpb24gd2UnbGwgZ2l2ZSB5b3UgaWRlYXMgb24gd2F5cyB0byBjb250aW51ZSB2aXN1YWxpemluZyB0aGlzIGRhdGEuDQoNCiMgRnVydGhlciBXb3JrDQoNCkdyZWF0IHdvcmshIEluIHRoaXMgcHJvamVjdCB5b3UgaGF2ZSB0cnVseSByZWZpbmVkIHlvdXIgZGF0YSBtYW5pcHVsYXRpb24gc2tpbGxzIHVzaW5nIGEgcmVhbCBkYXRhIHNldC4gSW4gZG9pbmcgc28sIHdlIGhvcGUgdGhhdCB5b3UndmUgc2VlbiBzb21lIG9mIHRoZSBkaWZmaWN1bHRpZXMgeW91IG5lZWQgdG8gd29yayB0aHJvdWdoIHdoZW4gd29ya2luZyB3aXRoIGRhdGEuIFlvdXIgZGF0YSBzZXQgbWlnaHQgaGF2ZSBhbm5veWluZyBjb2x1bW4gbmFtZXMuIERhdGEgbWF5IGJlIG1pc3Npbmcgb3IgaW5jb3JyZWN0LiBZb3UgbWlnaHQgbmVlZCB0byByZXNoYXBlIHRoZSBkYXRhIG9yIGNoYW5nZSBpdHMgdHlwZS4gR2FpbmluZyB0aGUgY29uZmlkZW5jZSB0byBtYXN0ZXIgdGhlc2UgY2hhbGxlbmdlcyB1bmlxdWUgdG8geW91ciBkYXRhc2V0IGlzIGFuIGltcG9ydGFudCBwYXJ0IG9mIGJlaW5nIGEgZGF0YSBzY2llbnRpc3QuDQoNCklmIHlvdSdkIGxpa2UgdG8gY29udGludWUgd29ya2luZyB3aXRoIHRoaXMgZGF0YSwgaGVyZSBhcmUgc29tZSBjaGFsbGVuZ2VzOg0KDQotIEZpbmQgdGhlIHJhdGUgb2YgY2hhbmdlLiBJbnN0ZWFkIG9mIHJlcG9ydGluZyB0aGUgdG90YWwgbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBpbiBhIGNvdW50cnksIHJlcG9ydCBob3cgbWFueSBfbmV3XyBjYXNlcyB0aGVyZSB3ZXJlIHRoYXQgZGF5LiBXaGljaCBjb3VudHJpZXMgYXJlIHNsb3dpbmcgdGhlaXIgcmF0ZSBvZiBpbmZlY3Rpb24/DQotIEdyYXBoIG11bHRpcGxlIGNvdW50cmllcyBvbiBhIHNpbmdsZSBncmFwaC4gTGV0J3MgdmlzdWFsbHkgY29tcGFyZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGNvdW50cmllcy4NCi0gQ3JlYXRlIGEgW3NpZGUgYnkgc2lkZSBiYXIgcGxvdF0oaHR0cHM6Ly9ycHVicy5jb20vZHZkdW5uZS9nZ3Bsb3RfdHdvX2JhcnMpIHRoYXQgc2hvd3MgdGhlIG51bWJlciBvZiBjb25maXJtZWQgY2FzZXMsIHJlY292ZXJpZXMsIGFuZCBkZWF0aHMgZXZlcnkgZGF5IGZvciBhIHBhcnRpY3VsYXIgY291bnRyeS4NCi0gRmluZCBtb3JlIGRhdGEuIE5vdyB0aGF0IHlvdSd2ZSBnb3R0ZW4gY29tZm9ydGFibGUgd2l0aCB0aGlzIGRhdGFzZXQsIHRyeSBmaW5kaW5nIGFub3RoZXIgdGhhdCBjYW4gZXhwYW5kIHlvdXIgdW5kZXJzdGFuZGluZyBvZiB0aGlzIHBhbmRlbWljLiBbVGhlIE5ldyBZb3JrIFRpbWVzIEdpdEh1YiBwYWdlXShodHRwczovL2dpdGh1Yi5jb20vbnl0aW1lcy9jb3ZpZC0xOS1kYXRhKSBoYXMgYSBkYXRhc2V0IGNvbnRhaW5pbmcgaW5mb3JtYXRpb24gYWJvdXQgc3BlY2lmaWMgY291bnRpZXMgaW4gdGhlIFVTLiBUcnlpbmcgdG8gZm9ybWF0IHRoaXMgZGF0YXNldCB0byBmaW5kIG1vcmUgdXNlZnVsIGluZm9ybWF0aW9uIHdvdWxkIGJlIGEgZmFudGFzdGljIGV4ZXJjaXNlIQ0KDQo=