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:
- How many confirmed cases are there in countries on March 22nd that are north of the equator? (If a country is north of the equator, its latitude is greater than
0
)
- How many confirmed cases are there in March 22nd in Australia?
- Can you report both of those values as a percentage of the total number of cases in the world on March 22nd?
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:
- 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!
- 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
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:
- Find the rate of change. Instead of reporting the total number of confirmed cases in a country, report how many new cases there were that day. Which countries are slowing their rate of infection?
- Graph multiple countries on a single graph. Let’s visually compare the difference between countries.
- Create a side by side bar plot that shows the number of confirmed cases, recoveries, and deaths every day for a particular country.
- Find more data. Now that you’ve gotten comfortable with this dataset, try finding another that can expand your understanding of this pandemic. The New York Times GitHub page has a dataset containing information about specific counties in the US. Trying to format this dataset to find more useful information would be a fantastic exercise!
LS0tDQp0aXRsZTogIkludmVzdGlnYXRpbmcgdGhlIENvcm9uYXZpcnVzIChhLmsuYS4gV3VoYW4gVmlydXMpIFBhbmRlbWljIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KV2VsY29tZSB0byB5b3VyIG9mZi1wbGF0Zm9ybSBwcm9qZWN0IGZvY3VzZWQgb24gRGF0YSBNYW5pcHVsYXRpb24gaW4gUiEgVGhlIGdvYWwgb2YgdGhpcyBwcm9qZWN0IGlzIHRvIGdldCBleHBlcmllbmNlIHdvcmtpbmcgd2l0aCBtdWx0aXBsZSBgLmNzdmAgZmlsZXMgaW4gUlN0dWRpby4gV2UnbGwgYmUgY3JlYXRpbmcgc2V2ZXJhbCBkYXRhIGZyYW1lcyBhbmQgbWFuaXB1bGF0aW5nIHRoZW0gdG8gdHJ5IHRvIGZpbmQgbmV3IGluc2lnaHRzLg0KDQpJbiB0aGlzIHByb2plY3Qgd2Ugd2lsbCBiZSBsb29raW5nIGF0IGRhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBDb3JvbmF2aXJ1cyBwYW5kZW1pYyBmcm9tIDIwMjAuIFdoaWxlIHRoaXMgdG9waWMgaXNuJ3QgeW91ciB0eXBpY2FsIGxpZ2h0aGVhcnRlZCBDb2RlY2FkZW15IGFzc2lnbm1lbnQsIHdlIGZlbHQgbGlrZSBpdCBpcyBhIGV4Y2VsbGVudCB3YXkgdG8gdXNlIHlvdXIgbmV3IFIgc2tpbGxzIHRvIHVuZGVyc3RhbmQgdGhlIHdvcmxkIGFyb3VuZCB5b3UuIFRoaXMgYXNzaWdubWVudCB3YXMgd3JpdHRlbiBvbiBNYXJjaCAyM3JkLCAyMDIwICZtZGFzaDsgc2luY2UgeW91IHdpbGwgYmUgdGFraW5nIHRoaXMgYXNzaWdubWVudCBpbiB0aGUgZnV0dXJlLCB3ZeKAmXJlIGhvcGluZyB0aGF0IHRoaXMgY2FuIGJlIGFuIGludGVyZXN0aW5nIGhpc3RvcmljYWwgYW5hbHlzaXMgcmF0aGVyIHRoYW4gYW4gb25nb2luZyBpc3N1ZS4NCg0KIyBDb2xsZWN0IGFuZCBJbnZlc3RpZ2F0ZSB0aGUgRGF0YQ0KDQpXZeKAmXZlIHByb3ZpZGVkIHRocmVlIGRhdGFzZXRzIG5hbWVkIGBjb3JvbmFfY29uZmlybWVkLmNzdmAsIGBjb3JvbmFfcmVjb3ZlcmVkLmNzdmAgYW5kIGBjb3JvbmFfZGVhdGhzLmNzdmAuIEZvciB0aGUgZW50aXJldHkgb2YgdGhpcyBwcm9qZWN0LCB3ZeKAmWxsIGJlIHVzaW5nIHRoZXNlIGRhdGFzZXRzLiBZb3UgY2FuIGZpbmQgbW9yZSByZWNlbnQgdmVyc2lvbnMgb2YgdGhpcyBkYXRhIGF0IEpvaG5zIEhvcGtpbnPigJkgZGF0YSByZXBvc2l0b3J5IG9uIFtHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9DU1NFR0lTYW5kRGF0YS9DT1ZJRC0xOSkuDQoNClRoaXMgaXMgYSBnb29kIG9wcG9ydHVuaXR5IHRvIHByYWN0aWNlIGRvd25sb2FkaW5nIGRhdGEgZnJvbSBHaXRIdWIgb3Igb3RoZXIgcmVwb3NpdG9yaWVzLiBXZeKAmXJlIHVzaW5nIGZpbGVzIGZvdW5kIGluIHRoZSBgY3NzZV9jb3ZpZF8xOV90aW1lX3Nlcmllc2AgZm9sZGVyLiBJbiBhZGRpdGlvbiwgd2UgcmVjb21tZW5kIGxvb2tpbmcgYXQgc29tZSBvZiB0aGUgcHJvamVjdHMgbGlua2VkIG9uIHRoZSBHaXRIdWIgcGFnZSAmbWRhc2g7IHRoYXQgY2FuIGhlbHAgZ2l2ZSB5b3UgYSBiZXR0ZXIgc2Vuc2Ugb2Ygd2hhdCB0eXBlIG9mIGluZm9ybWF0aW9uIHlvdSBjYW4gZXh0cmFjdCBmcm9tIHRoaXMgZGF0YS4NCg0KRm9yIG5vdywgbG9hZCB0aGVzZSB0aHJlZSBgLmNzdmAgZmlsZXMgaW50byB0aHJlZSBzZXBhcmF0ZSBkYXRhZnJhbWVzLiBOb3RlIHRoYXQgd2UgdXNlZCB0aGUgYHJlYWRfY3N2KClgIGZ1bmN0aW9uIGZyb20gdGhlIGByZWFkcmAgbGlicmFyeS4gSWYgeW91IGxvYWQgdGhlIGNzdiBmaWxlcyB1c2luZyBhIGRpZmZlcmVudCBtZXRob2QsIHlvdSBtaWdodCBoYXZlIHNsaWdodGx5IGRpZmZlcmVudCBjb2x1bW4gbmFtZXMuDQoNCkluc3BlY3QgdGhlIGRhdGFmcmFtZXMgdG8gZ2V0IGEgc2Vuc2Ugb2YgaG93IHRoZSBkYXRhIGlzIHN0b3JlZCAod2UgcmVjb21tZW5kIHVzaW5nIGBoZWFkKClgKS4NCg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocmVhZHIpDQoNCiMgTG9hZCB0aGUgZGF0YQ0KY29uZmlybWVkIDwtIHJlYWRfY3N2KCJjb3JvbmFfY29uZmlybWVkLmNzdiIpDQpkZWF0aHMgPC0gcmVhZF9jc3YoImNvcm9uYV9kZWF0aHMuY3N2IikNCnJlY292ZXJlZCA8LSByZWFkLmNzdigiY29yb25hX3JlY292ZXJlZC5jc3YiKQ0KDQojIEluc3BlY3QgdGhlIGRhdGENCmhlYWQoY29uZmlybWVkKQ0KaGVhZChkZWF0aHMpDQpoZWFkKHJlY292ZXJlZCkNCmBgYA0KDQojIExvb2tpbmcgQXQgTWFyY2ggMjJuZCwgMjAyMA0KDQpUaGUgZm9ybWF0IG9mIHRoZXNlIHRocmVlIGRhdGEgZnJhbWVzIGFyZSBhbGwgdGhlIHNhbWUgJm1kYXNoOyBlYWNoIHJvdyBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgbnVtYmVyIG9mIGNhc2VzIGluIGEgY2VydGFpbiBwcm92aW5jZSwgc3RhdGUsIG9yIGNvdW50cnkuIEV2ZXJ5IGNvbHVtbiAob3RoZXIgdGhhbiB0aGUgY29sdW1ucyBjb250YWluaW5nIHRoZSBsYXRpdHVkZSwgbG9uZ2l0dWRlLCBhbmQgY291bnRyeSBuYW1lKSByZXByZXNlbnRzIGEgZGF0ZS4gV2UgaGF2ZSBkYXRhIHN0YXJ0aW5nIG9uIEphbnVhcnkgMXN0LCAyMDIwIGFuZCBlbmRpbmcgb24gTWFyY2ggMjJuZCwgMjAyMCAodGhlIGRheSB0aGlzIGFzc2lnbm1lbnQgd2FzIHdyaXR0ZW4pLg0KDQpMZXQncyB3YXJtIHVwIGJ5IGZpbmRpbmcgdGhlIHRvdGFsIG51bWJlciBvZiBjb25maXJtZWQgY2FzZXMgb24gTWFyY2ggMjJuZCwgMjAyMC4gVG8gZG8gdGhpcywgd2UnbGwgdXNlIGJvdGggdGhlIGBzZWxlY3QoKWAgYW5kIGBzdW0oKWAgZnVuY3Rpb25zLiBXaGVuIHVzaW5nIHRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uLCBtYWtlIHN1cmUgdG8gcHV0IHRoZSBuYW1lIG9mIHRoZSBjb2x1bW4gaW4gYmFja3RpY2tzICh1c3VhbGx5IGZvdW5kIGluIHRoZSB0b3AgbGVmdCBjb3JuZXIgb2YgeW91ciBrZXlib2FyZCkhIFRoaXMgdXN1YWxseSBpc24ndCBuZWNlc3NhcnksIGJ1dCBzaW5jZSB0aGUgbmFtZSBvZiB0aGUgY29sdW1uIGNvbnRhaW5zIHRoZSBgL2AgY2hhcmFjdGVyLCB3ZSBuZWVkIGJhY2t0aWNrcyEgTWFrZSBzdXJlIHRvIGRvIHRoaXMgd2hlbmV2ZXIgeW91J3JlIHdvcmtpbmcgd2l0aCB0aGVzZSBjb2x1bW5zLg0KDQpgYGB7cn0NCiMgU3VtIHRoZSB0b3RhbCBudW1iZXIgb2YgY2FzZXMgb24gTWFyY2ggMjJuZA0KKHRvdGFsX2Nhc2VzIDwtIGNvbmZpcm1lZCAlPiUgDQogIHNlbGVjdChgMy8yMi8yMGApICU+JSANCiAgc3VtKCkpDQpgYGANCg0KIyBGaWx0ZXIgQnkgVmFsdWVzDQoNCkxldCdzIHN0YXJ0IHRvIGZpbHRlciB0aGUgZGF0YSBhIGJpdCBtb3JlLiBHaXZlIHRoZXNlIHRocmVlIHRhc2tzIGEgdHJ5Og0KDQoqIEhvdyBtYW55IGNvbmZpcm1lZCBjYXNlcyBhcmUgdGhlcmUgaW4gY291bnRyaWVzIG9uIE1hcmNoIDIybmQgdGhhdCBhcmUgbm9ydGggb2YgdGhlIGVxdWF0b3I/IChJZiBhIGNvdW50cnkgaXMgbm9ydGggb2YgdGhlIGVxdWF0b3IsIGl0cyBsYXRpdHVkZSBpcyBncmVhdGVyIHRoYW4gYDBgKQ0KKiBIb3cgbWFueSBjb25maXJtZWQgY2FzZXMgYXJlIHRoZXJlIGluIE1hcmNoIDIybmQgaW4gQXVzdHJhbGlhPw0KKiBDYW4geW91IHJlcG9ydCBib3RoIG9mIHRob3NlIHZhbHVlcyBhcyBhIHBlcmNlbnRhZ2Ugb2YgdGhlIHRvdGFsIG51bWJlciBvZiBjYXNlcyBpbiB0aGUgd29ybGQgb24gTWFyY2ggMjJuZD8NCg0KWW91IG1heSB3YW50IHRvIHNhdmUgdGhlc2UgcmVzdWx0cyB0byB2YXJpYWJsZXMgYXMgdGhleSB3aWxsIGJlIHVzZWZ1bCBsYXRlciENCg0KYGBge3J9DQojIEZpbHRlciBmb3IgY291bnRyaWVzIGluIHRoZSBub3J0aGVybiBoZW1pc3BoZXJlDQoodG90YWxfY2FzZXNfbm9ydGhfaGVtaXNwaGVyZSA8LSBjb25maXJtZWQgJT4lIA0KICBmaWx0ZXIoTGF0ID4gMCkgJT4lIA0KICBzZWxlY3QoYDMvMjIvMjBgKSAlPiUgDQogIHN1bSgpKQ0KDQojIEZpbHRlciBmb3IgQXVzdHJhbGlhIGNhc2VzDQoodG90YWxfY2FzZXNfYXVzdHJhbGlhIDwtIGNvbmZpcm1lZCAlPiUgDQogIGZpbHRlcihgQ291bnRyeS9SZWdpb25gID09ICJBdXN0cmFsaWEiKSAlPiUgDQogIHNlbGVjdChgMy8yMi8yMGApICU+JSANCiAgc3VtKCkpDQojIERpc3BsYXkgcGVyY2VudGFnZXMNCnRvdGFsX2Nhc2VzX25vcnRoX2hlbWlzcGhlcmUgLyB0b3RhbF9jYXNlcyAqIDEwMA0KdG90YWxfY2FzZXNfYXVzdHJhbGlhIC8gdG90YWxfY2FzZXMgKiAxMDANCg0KYGBgDQoNCiMgR3JvdXAgQnkgQ291bnRyeQ0KDQpOb3RpY2UgdGhhdCBzb21lIGNvdW50cmllcyBoYXZlIG11bHRpcGxlIHJvd3Mgb2YgZGF0YS4gVGhpcyBoYXBwZW5zIHdoZW4gYSBjb3VudHJ5IGhhcyBpbmZvcm1hdGlvbiBhYm91dCBzcGVjaWZpYyBzdGF0ZXMgb3IgcHJvdmluY2VzLiBXaGlsZSB0aGlzIGluZm9ybWF0aW9uIG1pZ2h0IGJlIHVzZWZ1bCwgaXQgbWFrZXMgaXQgYSBiaXQgdHJpY2t5IHRvIHNlZSB0aGUgdG90YWwgbnVtYmVyIG9mIGNhc2VzIGJ5IGNvdW50cnkuDQoNCldlIHdlcmUgYWJsZSB0byBnZXQgdGhlIHRvdGFsIG51bWJlciBvZiBjYXNlcyBpbiBBdXN0cmFsaWEgYnkgdXNpbmcgYGZpbHRlcigpYCwgYnV0IHdoYXQgaWYgd2Ugd2FudGVkIHRvIGRvIHRoaXMgZm9yIGV2ZXJ5IGNvdW50cnk/IEl0IHdvdWxkIGJlIGEgcGFpbiB0byBoYXZlIHRvIHR5cGUgZXZlcnkgY291bnRyeSBuYW1lIGludG8gYSBmaWx0ZXIgZnVuY3Rpb24uIFVzZSBgZ3JvdXBfYnkoKWAgdG8gY3JlYXRlIGEgbmV3IGRhdGEgZnJhbWUgY29udGFpbmluZyBvbmUgcm93IGZvciBldmVyeSBgQ291bnRyeS9SZWdpb25gLiANCg0KRXZlcnkgY29sdW1uIG9mIHRob3NlIG5ldyByb3dzIHNob3VsZCBoYXZlIHRoZSBzdW0gb2YgdGhlIHRvdGFsIG51bWJlciBvZiBjYXNlcyBmb3IgdGhhdCBjb3VudHJ5IGZvciBldmVyeSBkYXkuIFdlIGNhbiB1c2UgdGhlIFtgc3VtbWFyaXplX2F0KClgXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N1bW1hcmlzZV9hbGwuaHRtbCkgZnVuY3Rpb24gdG8gZG8gdGhpcy4gYHN1bW1hcml6ZV9hdCgpYCB0YWtlcyB0d28gcGFyYW1ldGVyczoNCg0KMS4gQSB2ZWN0b3Igb2YgdGhlIGNvbHVtbnMgeW91IHdhbnQgdG8gc3VtbWFyaXplLiBJbiB0aGlzIGNhc2Ugd2Ugd2FudCBldmVyeSBjb2x1bW4gb3RoZXIgdGhhbiBgTGF0YCwgYExvbmdgLCBhbmQgYFByb3ZpbmNlL1N0YXRlYC4gYHN1bW1hcml6ZV9hdCgpYCBvbmx5IHdvcmtzIHdpdGggbnVtYmVycywgc28gd2UnbGwgd2FudCB0byByZW1vdmUgdGhlIGNvbHVtbiBjb250YWluaW5nIGNvdW50cnkgbmFtZXMuIFdlIGFsc28gZG9uJ3QgbmVlZCB0byBhZGQgdGhlIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcy4gUmVtZW1iZXIsIHlvdSBjYW4gdXNlIHRoZSBgLWAgc3ltYm9sIHRvIGxpc3QgY29sdW1ucyB5b3Ugd2FudCB0byBleGNsdWRlLiBBbHNvIGRvbid0IGZvcmdldCB0byB1c2UgYmFja3RpY2tzIGFyb3VuZCBjb2x1bW5zIHdpdGggYC9gIHN5bWJvbHMuIEZvciBleGFtcGxlIC1cYFByb3ZpbmNlL1N0YXRlXGAgd2lsbCBleGNsdWRlIHRoYXQgY29sdW1uLiBUaGVzZSBzbWFsbCBkZXRhaWxzIGdldCBjb21wbGljYXRlZCENCjIuIFRoZSBmdW5jdGlvbiB0aGF0IHlvdSB3YW50IHRvIGFwcGx5IHRvIHRoZSBjb2x1bW4uIEluIHRoaXMgY2FzZSwgYHN1bWAuDQoNCkFmdGVyIGNyZWF0aW5nIHRoaXMgbmV3IGRhdGEgZnJhbWUgaW5zcGVjdCBpdC4gVG8gY29uZmlybSB5b3UgZGlkIHRoZSBgZ3JvdXBfYnkoKWAgY29ycmVjdGx5LCBmaW5kIHRoZSByb3cgZm9yIEF1c3RyYWxpYSBhbmQgY29uZmlybSB0aGUgbnVtYmVyIG9mIGNhc2VzIG9uIE1hcmNoIDIybmQgbWF0Y2hlcyB5b3VyIHJlc3VsdHMgZnJvbSB0aGUgcHJldmlvdXMgc3RlcA0KDQpgYGB7cn0NCiMgR3JvdXAgYnkgY291bnRyaWVzDQooY291bnRyaWVzX2NvbmZpcm1lZCA8LSBjb25maXJtZWQgJT4lIA0KICBncm91cF9ieShgQ291bnRyeS9SZWdpb25gKSAlPiUgDQogIHN1bW1hcml6ZV9hdCh2YXJzKC1gUHJvdmluY2UvU3RhdGVgOi1Mb25nKSwgc3VtKSkNCiMgRmlsdGVyIHRoZSBncm91cGVkIGRhdGEgc2V0IGZvciBBdXN0cmFsaWENCmNvdW50cmllc19jb25maXJtZWQgJT4lIA0KICBmaWx0ZXIoYENvdW50cnkvUmVnaW9uYCA9PSAiQXVzdHJhbGlhIikgJT4lIA0KICBzZWxlY3QoYDMvMjIvMjBgKQ0KYGBgDQoNCiMgSW52ZXN0aWdhdGluZyBUaGUgUmVjb3ZlcmVkIERhdGFzZXQNCg0KRG8gdGhlIHNhbWUgcHJvY2VzcyBvZiBncm91cGluZyBieSBjb3VudHJ5IHVzaW5nIHRoZSByZWNvdmVyZWQgZGF0YXNldC4gV2hhdCBwZXJjZW50YWdlIG9mIHRoZSBjYXNlcyBpbiB0aGUgYFVTYCBoYXZlIHJlY292ZXJlZCBvbiBNYXJjaCAyMm5kPw0KDQpgYGB7cn0NCiMgR3JvdXAgYnkgY291bnRyaWVzDQooY291bnRyaWVzX3JlY292ZXJlZCA8LSByZWNvdmVyZWQgJT4lIA0KICBncm91cF9ieShgQ291bnRyeS5SZWdpb25gKSAlPiUgDQogIHN1bW1hcml6ZV9hdCh2YXJzKC1gUHJvdmluY2UuU3RhdGVgOi1Mb25nKSwgc3VtKSkNCg0KdG90YWxfdXMgPC0gY291bnRyaWVzX2NvbmZpcm1lZCAlPiUgZmlsdGVyKGBDb3VudHJ5L1JlZ2lvbmAgPT0gJ1VTJykgJT4lIHNlbGVjdChgMy8yMi8yMGApDQpyZWNvdmVyZWRfdXMgPC0gY291bnRyaWVzX3JlY292ZXJlZCAlPiUgZmlsdGVyKGBDb3VudHJ5LlJlZ2lvbmAgPT0gJ1VTJykgJT4lIHNlbGVjdChgWDMuMjIuMjBgKQ0KcmVjb3ZlcmVkX3VzIC90b3RhbF91cyAqIDEwMA0KYGBgDQoNCllvdSBtYXkgc2VlIHNvbWUgc3VycHJpc2luZyByZXN1bHRzICZtZGFzaDsgYXJlIHRoZXJlIHJlYWxseSB6ZXJvIHJlY292ZXJlZCBjYXNlcyBpbiB0aGUgVVM/IExldCdzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgYFVTYCByb3cgaW4gdGhlIGByZWNvdmVyZWRgIHRhYmxlLiBFaXRoZXIgdmlldyB0aGUgdGFibGUgaW4gUlN0dWRpbydzIGludGVyZmFjZSwgb3IgcHJpbnQgdGhlIHJvdyBpbiB0aGUgY2VsbCBibG9jayBiZWxvdy4gV2hhdCBkbyB5b3Ugbm90aWNlIGFib3V0IHRoZSB2YWx1ZXMgYXMgeW91IHNjcm9sbCB0byBNYXJjaCAyMm5kPw0KDQpgYGB7cn0NCiMgRmlsdGVyIHRvIGluc3BlY3QgdGhlIFVTIHJvdw0KY291bnRyaWVzX3JlY292ZXJlZCAlPiUgDQogIGZpbHRlcihgQ291bnRyeS5SZWdpb25gID09ICJVUyIpDQpgYGANCg0KSXQgc2VlbXMgbGlrZSB0aGUgbnVtYmVyIG9mIHJlY292ZXJlZCBjYXNlcyBpcyBzdGVhZGlseSBpbmNyZWFzaW5nIHRvIDE3LCB1bnRpbCBNYXJjaCAxOHRoLCB3aGVuIGl0IHN1ZGRlbmx5IGRyb3BzIGJhY2sgdG8gMC4gVGhpcyBpcyBzdXJwcmlzaW5nLCBhbmQgbm90IHdoYXQgd2UgZXhwZWN0ZWQhIFBlcmhhcHMgd2UncmUgbWlzdW5kZXJzdGFuZGluZyB0aGUgZm9ybWF0IG9mIHRoZSBkYXRhLCBvciBwZXJoYXBzIHRoZXJlJ3MgYW4gZXJyb3IgaW4gcmVwb3J0aW5nIHRoZSBkYXRhLg0KDQpXaGF0J3MgdGhlIGJlc3QgY291cnNlIG9mIGFjdGlvbiBhdCB0aGlzIHBvaW50PyBPbmUgc3RyYXRlZ3kgaXMgdG8gaW52ZXN0aWdhdGUgdGhlIHNvdXJjZSBvZiB0aGUgZGF0YSB0byB0cnVseSB1bmRlcnN0YW5kIHdoYXQgaXQgaXMgcmVwb3J0aW5nLiBXaGVuIHdlIHdlbnQgYmFjayB0byBbSm9obnMgSG9wa2lucycgcmVwb3NpdG9yeV0oaHR0cHM6Ly9naXRodWIuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L3RyZWUvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzKSwgd2UgZm91bmQgYSBub3RlIHNheWluZyB0aGF0IHRoZSBkYXRhIGhhZCBtb3ZlZCBpbnRvIGEgZGlmZmVyZW50IGZpbGUuIEl0IG1pZ2h0IGJlIGJlc3QgdG8gdXNlIHRoYXQgZmlsZSENCg0KSWYgd2Ugd2FudGVkIHRvIHVzZSB0aGUgY3VycmVudCBmaWxlLCBpbnN0ZWFkIG9mIHJlcG9ydGluZyB0aGUgbnVtYmVyIG9mIGNvbmZpcm1lZCBhbmQgcmVjb3ZlcmVkIGNhc2VzIGluIHRoZSBVUyBvbiBNYXJjaCAyMm5kLCB3ZSBjb3VsZCByZXBvcnQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmZpcm1lZCBhbmQgcmVjb3ZlcmVkIGNhc2VzLiBMZXQncyBnaXZlIHRoYXQgYSBzaG90IHVzaW5nIHRoZSBgbWF4KClgIGZ1bmN0aW9uLiBNYWtlIHN1cmUgdG8gYWxzbyB1c2UgYHNlbGVjdCgpYCB0byByZW1vdmUgdGhlIGBDb3VudHJ5L1JlZ2lvbmAgY29sdW1uICZtZGFzaDsgd2UgY2FuJ3QgdGFrZSB0aGUgbWF4IG9mIGEgcm93IGlmIGl0IGNvbnRhaW5zIGEgc3RyaW5nLg0KYGBge3J9DQojIEZpbmQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmZpcm1lZCBhbmQgcmVjb3ZlcmVkIGNhc2VzDQptYXhfcmVjb3ZlcmVkIDwtIGNvdW50cmllc19yZWNvdmVyZWQgJT4lDQogIGZpbHRlcihgQ291bnRyeS5SZWdpb25gID09ICJVUyIpICU+JQ0KICBzZWxlY3QoLUNvdW50cnkuUmVnaW9uKSAlPiUNCiAgbWF4KCkNCiAgICANCm1heF9jb25maXJtZWQgPC0gY291bnRyaWVzX2NvbmZpcm1lZCAlPiUNCiAgZmlsdGVyKGBDb3VudHJ5L1JlZ2lvbmAgPT0gIlVTIikgJT4lDQogIHNlbGVjdCgtYENvdW50cnkvUmVnaW9uYCkgJT4lDQogIG1heCgpDQoNCm1heF9yZWNvdmVyZWQgLyBtYXhfY29uZmlybWVkICogMTAwDQpgYGANCg0KIyBUcmFuc3Bvc2luZyBEYXRhIEZyYW1lcw0KDQpUaGUgY29kZSB0aGF0IHlvdSBqdXN0IHdyb3RlIGluIHRoZSBsYXN0IHByb21wdCBpcyBhIGxpdHRsZSB1Z2x5ICZtZGFzaDsgd2UgZmlyc3QgaGF2ZSB0byBzZWxlY3QgYSBwYXJ0aWN1bGFyIHJvdyBieSB0aGUgdmFsdWUgaW4gYSBwYXJ0aWN1bGFyIGNvbHVtbiwgcmVtb3ZlIHRoYXQgY29sdW1uIGZyb20gdGhlIHJvdywgYW5kIHRoZW4gZmluZCB0aGUgbWF4aW11bSB2YWx1ZSBvZiB0aGUgcmVtYWluaW5nIHZhbHVlcy4gSXQgd291bGQgYmUgbXVjaCBlYXNpZXIgaWYgd2Ugcm90YXRlZCB0aGUgZGF0YSBmcmFtZSBzbyByb3dzIHJlcHJlc2VudGVkIGRhdGVzIGFuZCBjb2x1bW5zIHJlcHJlc2VudGVkIGNvdW50cmllcy4gVHJ5IGRyYXdpbmcgYSBza2V0Y2ggb2Ygd2hhdCB0aGlzIHJvdGF0ZWQgZGF0YSBmcmFtZSBtaWdodCBsb29rIGxpa2UhDQoNCldlIGNvdWxkIHRoZW4gZmluZCB0aGUgbWF4aW11bSB2YWx1ZSBvZiBhIGNvdW50cnkgYnkgc2ltcGx5IHNlbGVjdGluZyB0aGUgYXBwcm9wcmlhdGUgY29sdW1uIGFuZCBmaW5kaW5nIHRoZSBtYXhpbXVtIHZhbHVlIGluIHRoYXQgY29sdW1uLiBMZXQncyB0cnkgdGhhdCEgVGhlIGB0KClgIGZ1bmN0aW9uIHdpbGwgdHJhbnNwb3NlIHRoZSBkYXRhIGZyYW1lIHNvIGFsbCBvZiB0aGUgY29sdW1ucyBiZWNvbWUgcm93cy4gYHQoKWAgdW5mb3J0dW5hdGVseSByZXR1cm5zIGEgbWF0cml4IGluc3RlYWQgb2YgYSBkYXRhIGZyYW1lLiBJZiB5b3UgcGlwZSB0aGUgcmVzdWx0IG9mIGB0KClgIHRocm91Z2ggdGhlIGBhcy5kYXRhLmZyYW1lKClgIGZ1bmN0aW9uLCB5b3VyIHJlc3VsdCB3aWxsIGJlIHRoZSB0cmFuc3Bvc2VkIGRhdGEgZnJhbWUuIFRyeSB0cmFuc3Bvc2luZyBvbmUgb2YgeW91ciBkYXRhIGZyYW1lcyBieSBwaXBpbmcgaXQgdGhyb3VnaCBib3RoIGB0KClgIGFuZCBgYXMuZGF0YS5mcmFtZSgpYCBhbmQgY2FsbCBgaGVhZCgpYCB0byBpbnZlc3RpZ2F0ZSBpdC4NCg0KYGBge3J9DQojIFRyYW5zcG9zZSB0aGUgZGF0YSBmcmFtZQ0KdHJhbnNwb3NlZF9jb25maXJtZWQgPC0gY291bnRyaWVzX2NvbmZpcm1lZCAlPiUgdCgpICU+JSBhcy5kYXRhLmZyYW1lKCkNCnRyYW5zcG9zZWRfY29uZmlybWVkICU+JSBoZWFkKCkNCmBgYA0KDQoNCkdyZWF0ISBXZSdyZSBhbG1vc3QgdGhlcmUuIEJ1dCB0YWtlIGEgbG9vayBhdCB0aGUgY29sdW1uIG5hbWVzLiBXZSB3YW50IHRoZSBjb2x1bW4gbmFtZXMgdG8gYmUgdGhlIGNvdW50cmllcywgYnV0IGluc3RlYWQsIHRoZSBjb3VudHJ5IG5hbWVzIGFyZSBpbiB0aGUgZmlyc3Qgcm93LiBXZSBuZWVkIHRvIGZpZ3VyZSBvdXQgaG93IHRvIGNoYW5nZSB0aGUgZmlyc3Qgcm93IG9mIGEgZGF0YSBmcmFtZSBpbnRvIGNvbHVtbiBuYW1lcy4gQ2hhbGxlbmdlIHlvdXJzZWxmIHRvIHRyeSB0byBmaWd1cmUgdGhpcyBvdXQgb24geW91ciBvd24gaW4gdGhlIGZvbGxvd2luZyBjb2RlIGJsb2NrLiBXZSdsbCB3YWxrIHlvdSB0aHJvdWdoIG91ciBzb2x1dGlvbiBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb24uDQoNCmBgYHtyfQ0KY29sbmFtZXModHJhbnNwb3NlZF9jb25maXJtZWQpIDwtIGNvdW50cmllc19jb25maXJtZWQkYENvdW50cnkvUmVnaW9uYA0KaGVhZCh0cmFuc3Bvc2VkX2NvbmZpcm1lZCkNCmBgYA0KDQpBcyB1c3VhbCwgd2UgdXNlZCBHb29nbGUgdG8gaGVscCBzb2x2ZSB0aGlzIHByb2JsZW0uIExlYXJuaW5nIGhvdyB0byBwcm9wZXJseSBzZWFyY2ggZm9yIGFuc3dlcnMgdG8geW91ciBwcm9ncmFtbWluZyBxdWVzdGlvbnMgaXMgYW4gZXNzZW50aWFsIHNraWxsLiBJbiB0aGlzIGNhc2UsICB3ZSBzcGVjaWZpY2FsbHkgc2VhcmNoZWQgInIgZGF0YSBmcmFtZSBzZXQgZmlyc3Qgcm93IGFzIGNvbHVtbiBuYW1lcyIsIHdoaWNoIGxlZCB1cyB0byBhIFtTdGFjayBPdmVyZmxvdyBwYWdlXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zMjA1NDM2OC91c2UtZmlyc3Qtcm93LWRhdGEtYXMtY29sdW1uLW5hbWVzLWluLXIpIHRoYXQgc3VnZ2VzdGVkIHVzaW5nIHRoZSBgamFuaXRvcmAgbGlicmFyeS4gVGhhdCdzIGEgY3V0ZSBuYW1lIGZvciBhIGxpYnJhcnkgJm1kYXNoOyB0aGlzIGBqYW5pdG9yYCBsaWJyYXJ5IHdpbGwgaGVscCB1cyBjbGVhbiB1cCBvdXIgZGF0YSBmcmFtZS4NCg0KRmlyc3QsIHdlIGluc3RhbGxlZCBhbmQgbG9hZGVkIHRoZSBgamFuaXRvcmAgcGFja2FnZS4gVGhlbiB3ZSBwYXNzZWQgb3VyIHRyYW5zcG9zZWQgZGF0YSBmcmFtZSBpbnRvIHRoZSBgcm93X3RvX25hbWVzKClgIGZ1bmN0aW9uIGFuZCB1c2VkIHRoZSBwYXJhbWV0ZXIgYHJvd19udW1iZXIgPSAxYC4NCg0KYGBge3J9DQpsaWJyYXJ5KGphbml0b3IpDQoNCiMgTWFrZSB0aGUgZmlyc3Qgcm93IHRoZSBjb2x1bW4gdGl0bGVzDQp0cmFuc3Bvc2VkX2NvbmZpcm1lZCA8LSB0cmFuc3Bvc2VkX2NvbmZpcm1lZCAlPiUgcm93X3RvX25hbWVzKHJvd19udW1iZXIgPSAxKQ0KdHJhbnNwb3NlZF9jb25maXJtZWQgJT4lIGhlYWQoKQ0KYGBgDQoNCldlJ3JlIGdldHRpbmcgY2xvc2VyLiBJZiB5b3UgcHJpbnRlZCB0aGUgaGVhZCBvZiB0aGUgZGF0YSBmcmFtZSB5b3UganVzdCBjcmVhdGVkLCB5b3UgbWlnaHQgaGF2ZSBub3RpY2VkIHRoYXQgdGhlIGNvbHVtbnMgYXJlIG5vdyBvZiB0eXBlIGA8ZmN0cj5gLCBvciBmYWN0b3IuIFRoaXMgd2FzIG9uZSBvZiB0aGUgc2lkZSBlZmZlY3RzIG9mIHJvdGF0aW5nIHRoZSBkYXRhIGZyYW1lLiBXZSB3YW50IHRvIHR1cm4gYWxsIG9mIHRoZXNlIGNvbHVtbnMgYmFjayBpbnRvIGRvdWJsZXMuDQoNClRvIGRvIHNvLCBwYXNzIHlvdXIgZGF0YSBmcmFtZSB0byB0aGUgW2BhcHBseSgpYF0oaHR0cHM6Ly93d3cuZ3VydTk5LmNvbS9yLWFwcGx5LXNhcHBseS10YXBwbHkuaHRtbCkgZnVuY3Rpb24uIGBhcHBseSgpYCB0YWtlcyB0d28gcGFyYW1ldGVycyBpbiBhZGRpdGlvbiB0byB0aGUgZGF0YSBzZXQgdGhhdCB5b3UncmUgdXNpbmcuIFRoZSBgTUFSR0lOYCBwYXJhbWV0ZXIgc2hvdWxkIGJlIHNldCB0byBgMmAgJm1kYXNoOyB0aGlzIHRlbGxzIGBhcHBseSgpYCB0byB3b3JrIHdpdGggY29sdW1ucyByYXRoZXIgdGhhbiByb3dzLg0KDQpUaGUgc2Vjb25kIHBhcmFtZXRlciBzaG91bGQgYmUgdGhlIGZ1bmN0aW9uIHlvdSB3YW50IHRvIGFwcGx5IHRvIGFsbCBjb2x1bW5zLiBJbiB0aGlzIGNhc2UsIHdlIHdhbnQgYGFzLm51bWVyaWNgLg0KDQpGaW5hbGx5LCBhZnRlciBjYWxsaW5nIGBhcHBseSgpYCwgcGlwZSB0aGUgcmVzdWx0IHRocm91Z2ggYGFzLmRhdGEuZnJhbWUoKWAgdG8gZW5zdXJlIHdlJ3JlIHN0aWxsIHdvcmtpbmcgd2l0aCBhIGRhdGEgZnJhbWUuIFByaW50IG91dCB0aGUgaGVhZCBvZiB5b3VyIHJlc3VsdCB0byBzZWUgd2hhdCB0aGUgZGF0YSBub3cgbG9va3MgbGlrZS4gTG9vayBhdCB0aGUgZGF0YSB0eXBlIG9mIHRoZSBjb2x1bW5zIG5vdyENCg0KYGBge3J9DQojIFRyYW5zZm9ybSB0aGUgY29sdW1ucyB0byBudW1lcmljIHZhbHVlcw0KdHJhbnNwb3NlZF9jb25maXJtZWQgPC0gYXBwbHkodHJhbnNwb3NlZF9jb25maXJtZWQsIDIsIGFzLm51bWVyaWMpICU+JSBhcy5kYXRhLmZyYW1lKCkNCmhlYWQodHJhbnNwb3NlZF9jb25maXJtZWQpDQpgYGANCg0KTmljZSB3b3JrIC0gd2UgZ290IHRoZXJlISBMZXQncyBzZWUgd2hhdCBvdXIgaGFyZCB3b3JrIGFsbG93cyB1cyB0byBkby4gTGV0J3Mgb25jZSBhZ2FpbiBmaW5kIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjYXNlcyByZXBvcnRlZCBpbiB0aGUgYFVTYC4gVG8gZG8gc28sIHNpbXBseSBzZWxlY3QgdGhlIGBVU2AgY29sdW1uIGFuZCBwaXBlIHRoZSByZXN1bHQgdG8gdGhlIGBtYXgoKWAgZnVuY3Rpb24uIFRoaXMgc2hvdWxkIGJlIHRoZSBzYW1lIHZhbHVlIHRoYXQgd2UgZm91bmQgYmVmb3JlIHdlIHJvdGF0ZSB0aGUgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQojIEZpbmQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBpbiB0aGUgVVMNCnRyYW5zcG9zZWRfY29uZmlybWVkICU+JSANCiAgc2VsZWN0KFVTKSAlPiUgDQogIG1heCgpDQpgYGANCg0KIyBFeHRyYSBDcmVkaXQ6IFZpc3VhbGl6YXRpb24NCg0KV2UgcHV0IGEgbG90IG9mIHdvcmsgaW50byByb3RhdGluZyB0aGF0IGRhdGEgZnJhbWUsIGFuZCB5b3UgbWF5IGJlIHdvbmRlcmluZyBpZiBpdCB3YXMgd29ydGggaXQuIEluIHRoaXMgc2VjdGlvbiwgd2UncmUgZ29pbmcgdG8gbWFrZSBzb21lIGJhc2ljIGdyYXBocyB0aGF0IGRlbW9uc3RyYXRlIHRoZSB2YWx1ZSBvZiByb3RhdGluZyB0aGUgZGF0YSBmcmFtZS4NCg0KWW91IG1heSBub3QgaGF2ZSBiZWVuIGludHJvZHVjZWQgdG8gdmlzdWFsaXphdGlvbiB5ZXQuIElmIHRoaXMgaXMgdW5mYW1pbGlhciB0byB5b3UsIGZlZWwgZnJlZSB0byBza2lwIHRoaXMgc2VjdGlvbiAmbWRhc2g7IGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbiB3ZSBvZmZlciBzb21lIGlkZWFzIGZvciBtb3JlIGRhdGEgbWFuaXB1bGF0aW9uIGNoYWxsZW5nZXMgdXNpbmcgdGhpcyBkYXRhIHNldC4gVGhhdCBiZWluZyBzYWlkLCBpZiB2aXN1YWxpemF0aW9uIGlzIHVuZmFtaWxpYXIgdG8geW91LCB5b3UgY2FuIHVzZSB0aGlzIGFzIGFuIG9wcG9ydHVuaXR5IHRvIGRvIHNvbWUgaW5kZXBlbmRlbnQgbGVhcm5pbmchIENoYWxsZW5nZSB5b3Vyc2VsZiB0byBkbyBzb21lIHJlc2VhcmNoIHRvIGNyZWF0ZSB0aGUgZm9sbG93aW5nIGdyYXBoLg0KDQpMZXQncyBidWlsZCBhIGxpbmUgZ3JhcGggc2hvd2luZyB0aGUgbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBvdmVyIHRpbWUgZm9yIGEgcGFydGljdWxhciBjb3VudHJ5LiBUbyBkbyB0aGlzLCB3ZSdsbCBmaXJzdCBuZWVkIHRvIGFkZCBhIG5ldyBjb2x1bW4gdG8gb3VyIGRhdGFzZXQgdG8gcmVwcmVzZW50IHRoZSBkYXRlIChub3RpY2UgdGhhdCB3aGVuIHdlIHJvdGF0ZWQgb3VyIGRhdGEgZnJhbWUsIHdlIGxvc3QgdGhlIGNvbHVtbiBjb250YWluaW5nIHRoZSBkYXRlKS4gVGhlIGZpcnN0IGRheSBpbiBvdXIgZGF0YXNldCB3YXMgSmFudWFyeSAyMm5kLiBMZXQncyByZXByZXNlbnQgdGhhdCBhcyBkYXkgYDFgLiBKYW51YXJ5IDIzcmQgd291bGQgdGhlbiBiZSBkYXkgYDJgLCBhbmQgc28gb24uIFdlIHdhbnQgdG8gYWRkIGEgY29sdW1uIG5hbWVkIGBkYXRlYCB0aGF0IGNvbnRhaW5zIHRoZSBudW1iZXJzIGAxYCB0aHJvdWdoIHRoZSB0b3RhbCBudW1iZXIgb2Ygcm93cyBpbiB0aGUgZGF0YXNldC4gVXNlIGBtdXRhdGUoKWAgdG8gZG8gdGhpcy4gYG5yb3coKWAgd2lsbCBoZWxwIGZpbmQgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSBkYXRhc2V0LiBQcmludCBvdXQgdGhlIGhlYWQgb2YgdGhlIGBkYXRlYCBjb2x1bW4gdG8gZW5zdXJlIHRoaXMgd29ya2VkLg0KDQpgYGB7cn0NCiMgQWRkIHRoZSBkYXRlIGNvbHVtbg0KdHJhbnNwb3NlZF9jb25maXJtZWQgPC0gdHJhbnNwb3NlZF9jb25maXJtZWQgJT4lIG11dGF0ZShkYXRlID0gYygxOm5yb3codHJhbnNwb3NlZF9jb25maXJtZWQpKSkNCmhlYWQoc2VsZWN0KHRyYW5zcG9zZWRfY29uZmlybWVkLCBkYXRlKSkNCnRyYW5zcG9zZWRfY29uZmlybWVkDQpgYGANCg0KR3JlYXQhIE91ciBkYXRlIGNvbHVtbiBpcyBhbGwgc2V0LiBMZXQncyBub3cgZ3JhcGggdGhlIG51bWJlciBvZiBjYXNlcyBpbiBJdGFseSBvdmVyIHRoZSBkYXlzIGluIG91ciBkYXRhc2V0LiBXZSdsbCB1c2UgdGhlIGBnZ3Bsb3QyYCBsaWJyYXJ5IHRvIGRvIHRoaXMuIFVzZSBhIGNvbWJpbmF0aW9uIG9mIGBnZ3Bsb3QoKWAsIGBhZXMoKWAsIGFuZCBgZ2VvbV9saW5lKClgIHRvIG1ha2Ugb3VyIGdyYXBoISBPbmNlIGFnYWluLCBpZiB5b3UncmUgdW5mYW1pbGlhciB3aXRoIGdyYXBoaW5nLCB0cnkgdG8gZG8gc29tZSBvbmxpbmUgcmVzZWFyY2ggdG8gbGVhcm4gaG93IHRvIGRvIHRoaXMhDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KIyBDcmVhdGUgYSBsaW5lIGdyYXBoIHdpdGggZGF0ZSBvbiB0aGUgWCBheGlzIGFuZCBudW1iZXIgb2YgY2FzZXMgaW4gSXRhbHkgb24gdGhlIFkgYXhpcw0KcCA8LSBnZ3Bsb3QodHJhbnNwb3NlZF9jb25maXJtZWQsIGFlcyh4ID0gZGF0ZSwgeSA9IEl0YWx5KSkgKw0KICBnZW9tX2xpbmUoKQ0KcA0KYGBgDQoNClRoYXQgbGluZSBvZiBjb2RlIGlzIHByZXR0eSBjb25jaXNlLiBUaGF0J3Mgb25lIG9mIHRoZSByZWFzb25zIHdoeSB3ZSB3YW50ZWQgdG8gcm90YXRlIHRoZSBkYXRhIGZyYW1lLiBIYXZpbmcgYSBjb2x1bW4gY29udGFpbmluZyBvbmx5IHRoZSBjb25maXJtZWQgY2FzZXMgZnJvbSBhIHBhcnRpY3VsYXIgY291bnRyeSBtYWRlIHRoaXMgZ3JhcGggcmVsYXRpdmVseSBzaW1wbGUgdG8gY3JlYXRlLg0KDQpGaW5hbGx5LCBsZXQncyBkbyBhIGJpdCBvZiB3b3JrIHRvIGFkZCBhIHRpdGxlLCBhbmQgdG8gZml4IHRoZSBuYW1lcyBvZiB0aGUgYXhlcy4gVGhlcmUgYXJlIGEgZmV3IGRpZmZlcmVudCB3YXlzIHRvIGRvIHRoaXMsIGJ1dCB3ZSB1c2VkIHRoZSBgbGFicygpYCBmdW5jdGlvbiB3aGljaCB0YWtlcyBgeGAsIGB5YCBhbmQgYHRpdGxlYCBwYXJhbWV0ZXJzLg0KDQpgYGB7cn0NCiMgQWRkIGEgcHJvcGVyIHRpdGxlLCB4IGxhYmVsLCBhbmQgeSBsYWJlbA0KcCArIGxhYnMoeCA9ICJOdW1iZXIgb2YgZGF5cyBzaW5jZSBKYW51YXJ5IDIybmQsIDIwMjAiLCB5ID0gIk51bWJlciBvZiBjb25maXJtZWQgY2FzZXMiLCB0aXRsZSA9ICJDb25maXJtZWQgY2FzZXMgb2YgV3VoYW4gVmlydXMgaW4gSXRhbHkiKQ0KYGBgDQoNCkluIHRoZSBuZXh0IHNlY3Rpb24gd2UnbGwgZ2l2ZSB5b3UgaWRlYXMgb24gd2F5cyB0byBjb250aW51ZSB2aXN1YWxpemluZyB0aGlzIGRhdGEuDQoNCiMgRnVydGhlciBXb3JrDQoNCkdyZWF0IHdvcmshIEluIHRoaXMgcHJvamVjdCB5b3UgaGF2ZSB0cnVseSByZWZpbmVkIHlvdXIgZGF0YSBtYW5pcHVsYXRpb24gc2tpbGxzIHVzaW5nIGEgcmVhbCBkYXRhIHNldC4gSW4gZG9pbmcgc28sIHdlIGhvcGUgdGhhdCB5b3UndmUgc2VlbiBzb21lIG9mIHRoZSBkaWZmaWN1bHRpZXMgeW91IG5lZWQgdG8gd29yayB0aHJvdWdoIHdoZW4gd29ya2luZyB3aXRoIGRhdGEuIFlvdXIgZGF0YSBzZXQgbWlnaHQgaGF2ZSBhbm5veWluZyBjb2x1bW4gbmFtZXMuIERhdGEgbWF5IGJlIG1pc3Npbmcgb3IgaW5jb3JyZWN0LiBZb3UgbWlnaHQgbmVlZCB0byByZXNoYXBlIHRoZSBkYXRhIG9yIGNoYW5nZSBpdHMgdHlwZS4gR2FpbmluZyB0aGUgY29uZmlkZW5jZSB0byBtYXN0ZXIgdGhlc2UgY2hhbGxlbmdlcyB1bmlxdWUgdG8geW91ciBkYXRhc2V0IGlzIGFuIGltcG9ydGFudCBwYXJ0IG9mIGJlaW5nIGEgZGF0YSBzY2llbnRpc3QuDQoNCklmIHlvdSdkIGxpa2UgdG8gY29udGludWUgd29ya2luZyB3aXRoIHRoaXMgZGF0YSwgaGVyZSBhcmUgc29tZSBjaGFsbGVuZ2VzOg0KDQotIEZpbmQgdGhlIHJhdGUgb2YgY2hhbmdlLiBJbnN0ZWFkIG9mIHJlcG9ydGluZyB0aGUgdG90YWwgbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBpbiBhIGNvdW50cnksIHJlcG9ydCBob3cgbWFueSBfbmV3XyBjYXNlcyB0aGVyZSB3ZXJlIHRoYXQgZGF5LiBXaGljaCBjb3VudHJpZXMgYXJlIHNsb3dpbmcgdGhlaXIgcmF0ZSBvZiBpbmZlY3Rpb24/DQotIEdyYXBoIG11bHRpcGxlIGNvdW50cmllcyBvbiBhIHNpbmdsZSBncmFwaC4gTGV0J3MgdmlzdWFsbHkgY29tcGFyZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGNvdW50cmllcy4NCi0gQ3JlYXRlIGEgW3NpZGUgYnkgc2lkZSBiYXIgcGxvdF0oaHR0cHM6Ly9ycHVicy5jb20vZHZkdW5uZS9nZ3Bsb3RfdHdvX2JhcnMpIHRoYXQgc2hvd3MgdGhlIG51bWJlciBvZiBjb25maXJtZWQgY2FzZXMsIHJlY292ZXJpZXMsIGFuZCBkZWF0aHMgZXZlcnkgZGF5IGZvciBhIHBhcnRpY3VsYXIgY291bnRyeS4NCi0gRmluZCBtb3JlIGRhdGEuIE5vdyB0aGF0IHlvdSd2ZSBnb3R0ZW4gY29tZm9ydGFibGUgd2l0aCB0aGlzIGRhdGFzZXQsIHRyeSBmaW5kaW5nIGFub3RoZXIgdGhhdCBjYW4gZXhwYW5kIHlvdXIgdW5kZXJzdGFuZGluZyBvZiB0aGlzIHBhbmRlbWljLiBbVGhlIE5ldyBZb3JrIFRpbWVzIEdpdEh1YiBwYWdlXShodHRwczovL2dpdGh1Yi5jb20vbnl0aW1lcy9jb3ZpZC0xOS1kYXRhKSBoYXMgYSBkYXRhc2V0IGNvbnRhaW5pbmcgaW5mb3JtYXRpb24gYWJvdXQgc3BlY2lmaWMgY291bnRpZXMgaW4gdGhlIFVTLiBUcnlpbmcgdG8gZm9ybWF0IHRoaXMgZGF0YXNldCB0byBmaW5kIG1vcmUgdXNlZnVsIGluZm9ybWF0aW9uIHdvdWxkIGJlIGEgZmFudGFzdGljIGV4ZXJjaXNlIQ0KDQo=