Introduction to R - Part III

Lets make sure we have read the gapminder data into R and have the relevant packages loaded.

library(readr)
library(dplyr)
library(ggplot2)
gapminder <- read_csv("raw_data/gapminder.csv")

Customising a plot

Now make a scatter plot of gdp versus life expectancy as we did in the previous session. One of the last topics we covered was how to add colour to a plot. This can make the plot more appealing, but also help with data interpretation. In this case, we can use different colours to indicate countries belonging to different continents.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,col=continent)) + geom_point()

The shape and size of points can also be mapped from the data. However, it is easy to get carried away.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,shape=continent,size=pop)) + geom_point()

Scales and their legends have so far been handled using ggplot2 defaults. ggplot2 offers functionality to have finer control over scales and legends using the scale methods.

Scale methods are divided into functions by combinations of

  • the aesthetics they control.

  • the type of data mapped to scale.

scale_aesthetic_type

Try typing in scale_ then tab to autocomplete. This will provide some examples of the scale functions available in ggplot2.

Although different scale functions accept some variety in their arguments, common arguments to scale functions include -

  • name - The axis or legend title

  • limits - Minimum and maximum of the scale

  • breaks - Label/tick positions along an axis

  • labels - Label names at each break

  • values - the set of aesthetic values to map data values

We can choose specific colour palettes, such as those provided by the RColorBrewer package. This package provides palettes for different types of scale (sequential, diverging, qualitative).

library(RColorBrewer)
display.brewer.all(colorblindFriendly = TRUE)

When creating a plot, always check that the colour scheme is appropriate for people with various forms of colour-blindness

When experimenting with colour palettes and labels, it is useful to save the plot as an object

p <- ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,col=continent)) + geom_point()
# We can also change the text displayed above the legend with the name parameter.
p + scale_color_manual(values=brewer.pal(6,"Set2"))

Or we can even specify our own colours; such as The University of Sheffield branding colours

my_pal <- c(rgb(0,159,218,maxColorValue = 255),
            rgb(31,20,93,maxColorValue = 255),
            rgb(249,227,0,maxColorValue = 255),
            rgb(0,155,72,maxColorValue = 255),
            rgb(190,214,0,maxColorValue = 255))
p + scale_color_manual(values=my_pal)

NEW:- A set of palettes based on works in the Metropolitan Museum of Art (New York) has been made available.

https://github.com/BlakeRMills/MetBrewer

## this will check if MetBrewer is already installed, and will only install if it is not found

if(!require("MetBrewer")) install.packages("MetBrewer")
library(MetBrewer)
p + scale_color_manual(values=met.brewer(name = "Greek"))

Various labels can be modified using the labs function.

p + labs(x="Wealth",y="Life Expectancy",title="Relationship between Wealth and Life Expectancy")

We can also modify the x- and y- limits of the plot so that any outliers are not shown. ggplot2 will give a warning that some points are excluded.

p + xlim(0,60000)
Warning: Removed 5 rows containing missing values (`geom_point()`).

Saving is supported by the ggsave function. A variety of file formats are supported (.png, .pdf, .tiff, etc) and the format used is determined from the extension given in the file argument. The height, width and resolution can also be configured. See the help on ggsave (?ggsave) for more information.

ggsave(p, file="my_ggplot.png")
Saving 7 x 5 in image

Most aspects of the plot can be modified from the background colour to the grid sizes and font. Several pre-defined “themes” exist and we can modify the appearance of the whole plot using a theme_.. function.

p + theme_bw()

More themes are supported by the ggthemes package. You can make your plots look like the Economist, Wall Street Journal or Excel (but please don’t do this!)

## this will check if ggthemes is already installed, and will only install if it is not found

if(!require("ggthemes")) install.packages("ggthemes")
library(ggthemes)
p + theme_excel()

Exercise

Exercise: Use a boxplot to compare the life expectancy values of Australia and New Zealand. Use a Set2 palette from RColorBrewer to colour the boxplots and apply a “minimal” theme to the plot.

Facets

One very useful feature of ggplot2 is faceting. This allows you to produce plots subset by variables in your data. In the scatter plot above, it was quite difficult to see if the relationship between gdp and life expectancy was the same for each continent. To overcome this, we would like a see a separate plot for each continent.

To facet our data into multiple plots we can use the facet_wrap (1 variable) or facet_grid (2 variables) functions and specify the variable(s) we split by.

p + facet_wrap(~continent)

The facet_grid function will create a grid-like plot with one variable on the x-axis and another on the y-axis.

p + facet_grid(continent~year)

The previous plot was a bit messy as it contained all combinations of year and continent. Let’s suppose we want our analysis to be a bit more focused and disregard countries in Oceania (as there are only 2 in our dataset) and maybe years between 1997 and 2002.

We should know how to restrict the rows from the gapminder dataset using the filter function. Instead of filtering the data, creating a new data frame and constructing the data frame from these new data we can use the%>% operator to create the data frame on the fly and pass directly to ggplot. Thus we don’t have to save a new data frame or alter the original data.

filter(gapminder, continent!="Oceania", year %in% c(1997,2002,2007)) %>% 
  ggplot(aes(x = gdpPercap, y=lifeExp,col=continent)) + geom_point() + facet_grid(continent~year)

Summarising and grouping with dplyr

The summarise function can take any R function that takes a vector of values (i.e. a column from a data frame) and returns a single value. Some of the more useful functions include:

  • min minimum value
  • max maximum value
  • sum sum of values
  • mean mean value
  • sd standard deviation
  • median median value
  • IQR the interquartile range
  • n_distinct the number of distinct values
  • n the number of observations (Note: this is a special function that doesn’t take a vector argument, i.e. column)
library(dplyr)
summarise(gapminder, min(lifeExp), max(gdpPercap), mean(pop))

It is also possible to summarise using a function that takes more than one value, i.e. from multiple columns. For example, we could compute the correlation between year and life expectancy. Here we also assign names to the table that is produced.

gapminder %>% 
summarise(MinLifeExpectancy = min(lifeExp), 
          MaximumGDP = max(gdpPercap), 
          AveragePop = mean(pop), 
          Correlation = cor(year, lifeExp))

However, it is not particularly useful to calculate such values from the entire table as we have different continents and years. The group_by function allows us to split the table into different categories, and compute summary statistics for each year (for example).

gapminder %>% 
    group_by(year) %>% 
    summarise(MinLifeExpectancy = min(lifeExp), 
              MaximumGDP = max(gdpPercap), 
              AveragePop = mean(pop))
gapminder %>% 
    group_by(year,continent) %>% 
    summarise(MinLifeExpectancy = min(lifeExp), 
              MaximumGDP = max(gdpPercap), 
              AveragePop = mean(pop))
`summarise()` has grouped output by 'year'. You can override using the `.groups` argument.

We can list as many summary functions as we like. Whilst this can make our code somewhat verbose there are many helper functions available. Consider an example where we want to average all the columns in our data:-

gapminder %>% 
    group_by(year) %>% 
    summarise(MeanLifeExpectancy = mean(lifeExp), 
              MeanGDP = mean(gdpPercap), 
              MeanPop = mean(pop))

This wasn’t a huge effort to write this code. However, it would be much more tedious for a dataset with many more columns. Recognising this, we can use the convenient summarise_all function. This will return NA values for columns that do not contain numeric values.

gapminder %>% 
  group_by(continent) %>% 
  summarise_all(mean)

The nice thing about summarise is that it can followed up by any of the other dplyr verbs that we have met so far (select, filter, arrange..etc). As the country column of the previous output containing missing values we can exclude it from further processing.

gapminder %>% 
  group_by(continent) %>% 
  summarise_all(mean) %>% 
  select(-country)

Returning to the correlation between life expectancy and year, we can summarise as follows:-

gapminder %>%     
    group_by(country) %>% 
    summarise(Correlation = cor(year , lifeExp))

We can then arrange the table by the correlation to see which countries have the lowest correlation

gapminder %>%      
    group_by(country) %>% 
    summarise(Correlation = cor(year , lifeExp)) %>% 
    arrange(Correlation)

We can filter the results to find observations of interest

gapminder %>%      
    group_by(country) %>% 
    summarise(Correlation = cor(year , lifeExp)) %>% 
    filter(Correlation < 0)

The countries we identify could then be used as the basis for a plot.

library(ggplot2)
filter(gapminder, country %in% c("Rwanda","Zambia","Zimbabwe")) %>% 
  ggplot(aes(x=year, y=lifeExp,col=country)) + geom_line()




Exercise

  • Summarise the gapminder data in an appropriate manner to produce a plot to show the change in average gdpPercap for each continent over time.
  • see below for a suggestion
    • HINT: you will need to use the geom_col function to create the bar plot



Joining

In many real life situations, data are spread across multiple tables or spreadsheets. Usually this occurs because different types of information about a subject, e.g. a patient, are collected from different sources. It may be desirable for some analyses to combine data from two or more tables into a single data frame based on a common column, for example, an attribute that uniquely identifies the subject.

dplyr provides a set of join functions for combining two data frames based on matches within specified columns. For those familiar with such SQL, these operations are very similar to carrying out join operations between tables in a relational database.

As a toy example, lets consider two data frames that some results of testing whether genes A, B and C are significant in our study (gene expression, mutations, etc.)

gene_results <- data.frame(Name=LETTERS[1:3], pvalue = c(0.001, 0.1,0.01))
gene_results

We might also have a data frame containing more data about the genes; such as which chromosome they are located on. As part of our data interpretation we might need to know where in the genome the genes are located. Note that both data frames have a column called Name. This column will be used to identify genes common to both tables.

gene_anno <- data.frame(Name = c("A","B","D"), chromosome=c(1,1,3))
gene_anno

There are various ways in which we can join these two tables together. We will first consider the case of a “left join”.

Animated gif by Garrick Aden-Buie

left_join returns all rows from the first data frame regardless of whether there is a match in the second data frame. Rows with no match are included in the resulting data frame but have NA values in the additional columns coming from the second data frame.

Animations to illustrate other types of join are available at https://github.com/gadenbuie/tidy-animated-verbs

left_join(gene_results, gene_anno)
Joining, by = "Name"

right_join is similar but returns all rows from the second data frame that have a match with rows in the first data frame based on the specified column.

right_join(gene_results, gene_anno)
Joining, by = "Name"

inner_join only returns those rows where matches could be made

inner_join(gene_results, gene_anno)
Joining, by = "Name"



Wrap-up

We have introduced a few of the essential packages from the R tidyverse that can help with data manipulation and visualisation.

Hopefully you will feel more confident about importing your data into R and producing some useful visualisations. You will probably have questions regarding the analysis of your own data. Some good starting points to get help are listed below.

To finish the workshop we will look at the analysis of some relevant data that we can import into R and analyse with the tools from the workshop.

Introducing the COVID-19 data

Data for global COVID-19 cases are available online from CSSE at Johns Hopkins University on their github repository.

github is an excellent way of making your code and analysis available for others to reuse and share. Private repositories with restricted access are also available. Here is a useful beginners guide.

-Friendly github intro

R is capable of downloading files to our own machine so we can analyse them. We need to know the URL (for the COVID data we can find this from github, or use the address below) and can specify what to call the file when it is downloaded.

download.file("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv",destfile = "raw_data/time_series_covid19_confirmed_global.csv")

We can use the read_csv function as before to import the data and take a look. We can see the basic structure of the data is one row for each country / region and columns for cases on each day.

covid <- read_csv("raw_data/time_series_covid19_confirmed_global.csv")
Rows: 289 Columns: 1136
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr    (2): Province/State, Country/Region
dbl (1134): Lat, Long, 1/22/20, 1/23/20, 1/24/20, 1/25/20, 1/26/20, 1/27/20, 1/28/20, 1/29/20, 1/30/20, 1/31/20, 2/1/20, 2/2/20, 2/3/20, 2/4/20, 2/5/20, 2/6/20, 2/7/20, 2/8/20, 2/9/20, 2/10/20, 2/11/20, 2/12/20, 2/13/...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
covid

We can potentially join these data to gapminder, and it would be beneficial to have one column name in common between both files. We can rename the Country/Region column of our new data frame to match gapminder.

covid <- read_csv("raw_data/time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) 
Rows: 289 Columns: 1136
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr    (2): Province/State, Country/Region
dbl (1134): Lat, Long, 1/22/20, 1/23/20, 1/24/20, 1/25/20, 1/26/20, 1/27/20, 1/28/20, 1/29/20, 1/30/20, 1/31/20, 2/1/20, 2/2/20, 2/3/20, 2/4/20, 2/5/20, 2/6/20, 2/7/20, 2/8/20, 2/9/20, 2/10/20, 2/11/20, 2/12/20, 2/13/...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
covid

Much of the analysis of this dataset has looked at trends over time (e.g. increasing /decreasing case numbers, comparing trajectories). As we know by now, the ggplot2 package allows us to map columns (variables) in our dataset to aspects of the plot.

In other words, we would expect to create plots by writing code such as:-

ggplot(covid, aes(x = Date, y =...)) + ...

Unfortunately such plots are not possible with the data in it’s current format. Counts for each date are containing in a different column. What we require is a column to indicate the date, and the corresponding count in the next column. Such data arrangements are known as long data; whereas we have wide data. Fortunately we can convert between the two using the tidyr package (also part of tidyverse).

## install tidyr if you don't already have it
install.packages("tidyr")

For more information on tidy data, and how to convert between long and wide data, see

https://r4ds.had.co.nz/tidy-data.html

library(tidyr)
covid <- read_csv("raw_data/time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) %>% 
  pivot_longer(5:last_col(),names_to="Date", values_to="Cases")
Rows: 289 Columns: 1136
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr    (2): Province/State, Country/Region
dbl (1134): Lat, Long, 1/22/20, 1/23/20, 1/24/20, 1/25/20, 1/26/20, 1/27/20, 1/28/20, 1/29/20, 1/30/20, 1/31/20, 2/1/20, 2/2/20, 2/3/20, 2/4/20, 2/5/20, 2/6/20, 2/7/20, 2/8/20, 2/9/20, 2/10/20, 2/11/20, 2/12/20, 2/13/...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
covid

Another point to note is that the dates are not in an internationally recognised format, which could cause a problem for some visualisations that rely on date order. We can fix by explicitly converting to YYYY-MM-DD format.

For more ways of dealing with dates in R see the lubridate package.

covid <- read_csv("raw_data/time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) %>% 
  pivot_longer(5:last_col(),names_to="Date", values_to="Cases") %>% 
    mutate(Date=as.Date(Date,"%m/%d/%y"))
Rows: 289 Columns: 1136
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr    (2): Province/State, Country/Region
dbl (1134): Lat, Long, 1/22/20, 1/23/20, 1/24/20, 1/25/20, 1/26/20, 1/27/20, 1/28/20, 1/29/20, 1/30/20, 1/31/20, 2/1/20, 2/2/20, 2/3/20, 2/4/20, 2/5/20, 2/6/20, 2/7/20, 2/8/20, 2/9/20, 2/10/20, 2/11/20, 2/12/20, 2/13/...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
covid

Another useful modification is to make sure only one row exists for each country. If we look at the data for some countries (e.g. China and UK) there are different entries for provinces and oversees territories.

## the count function tabulates the number of observations in a particular column

filter(covid, country == "China") %>% 
  count(`Province/State`)

So we can change the Cases to be the sum of all cases for that country on a particular day. We can do this using the group_by and summarise functions from above

covid <- read_csv("raw_data/time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) %>% 
  pivot_longer(5:last_col(),names_to="Date", values_to="Cases") %>% 
  mutate(Date=as.Date(Date,"%m/%d/%y")) %>% 
  group_by(country,Date) %>% 
  summarise(Cases = sum(Cases))
Rows: 289 Columns: 1136
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr    (2): Province/State, Country/Region
dbl (1134): Lat, Long, 1/22/20, 1/23/20, 1/24/20, 1/25/20, 1/26/20, 1/27/20, 1/28/20, 1/29/20, 1/30/20, 1/31/20, 2/1/20, 2/2/20, 2/3/20, 2/4/20, 2/5/20, 2/6/20, 2/7/20, 2/8/20, 2/9/20, 2/10/20, 2/11/20, 2/12/20, 2/13/...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
`summarise()` has grouped output by 'country'. You can override using the `.groups` argument.
covid

Exercise

What plots and summaries can you make from these data?

  • Plotting the number of cases over time for certain countries
  • Which country in each continent currently has the highest number of cases?
  • Normalise the number of cases for population size (using 2007 population figures as a population estimate)?
    • e.g. cases per 100,000
  • Which European countries have the highest number of cases per 100,000 population
LS0tCnRpdGxlOiAiUiBDcmFzaCBDb3Vyc2UiCmF1dGhvcjogIk1hcmsgRHVubmluZyIKZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiTGFzdCBtb2RpZmllZDogJWQgJWIgJVkiKWAnCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNzczogc3R5bGVzaGVldHMvc3R5bGVzLmNzcwplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCiMgSW50cm9kdWN0aW9uIHRvIFIgLSBQYXJ0IElJSQoKPGltZyBzcmM9ImltYWdlcy9sb2dvLXNtLnBuZyIgc3R5bGU9InBvc2l0aW9uOmFic29sdXRlO3RvcDo0MHB4O3JpZ2h0OjEwcHg7IiB3aWR0aD0iMjAwIi8+CgpMZXRzIG1ha2Ugc3VyZSB3ZSBoYXZlIHJlYWQgdGhlIGdhcG1pbmRlciBkYXRhIGludG8gUiBhbmQgaGF2ZSB0aGUgcmVsZXZhbnQgcGFja2FnZXMgbG9hZGVkLgoKYGBge3IgbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmdhcG1pbmRlciA8LSByZWFkX2NzdigicmF3X2RhdGEvZ2FwbWluZGVyLmNzdiIpCmBgYAoKIyMgQ3VzdG9taXNpbmcgYSBwbG90CgpOb3cgbWFrZSBhIHNjYXR0ZXIgcGxvdCBvZiBnZHAgdmVyc3VzIGxpZmUgZXhwZWN0YW5jeSBhcyB3ZSBkaWQgaW4gdGhlIHByZXZpb3VzIHNlc3Npb24uIE9uZSBvZiB0aGUgbGFzdCB0b3BpY3Mgd2UgY292ZXJlZCB3YXMgaG93IHRvIGFkZCBjb2xvdXIgdG8gYSBwbG90LiBUaGlzIGNhbiBtYWtlIHRoZSBwbG90IG1vcmUgYXBwZWFsaW5nLCBidXQgYWxzbyBoZWxwIHdpdGggZGF0YSBpbnRlcnByZXRhdGlvbi4gSW4gdGhpcyBjYXNlLCB3ZSBjYW4gdXNlIGRpZmZlcmVudCBjb2xvdXJzIHRvIGluZGljYXRlIGNvdW50cmllcyBiZWxvbmdpbmcgdG8gZGlmZmVyZW50IGNvbnRpbmVudHMuCgpgYGB7cn0KZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXAsIHk9bGlmZUV4cCxjb2w9Y29udGluZW50KSkgKyBnZW9tX3BvaW50KCkKYGBgCgpUaGUgc2hhcGUgYW5kIHNpemUgb2YgcG9pbnRzIGNhbiBhbHNvIGJlIG1hcHBlZCBmcm9tIHRoZSBkYXRhLiBIb3dldmVyLCBpdCBpcyBlYXN5IHRvIGdldCBjYXJyaWVkIGF3YXkuCgpgYGB7cn0KZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXAsIHk9bGlmZUV4cCxzaGFwZT1jb250aW5lbnQsc2l6ZT1wb3ApKSArIGdlb21fcG9pbnQoKQpgYGAKClNjYWxlcyBhbmQgdGhlaXIgbGVnZW5kcyBoYXZlIHNvIGZhciBiZWVuIGhhbmRsZWQgdXNpbmcgYGdncGxvdDJgIGRlZmF1bHRzLiBgZ2dwbG90MmAgb2ZmZXJzIGZ1bmN0aW9uYWxpdHkgdG8gaGF2ZSBmaW5lciBjb250cm9sIG92ZXIgc2NhbGVzIGFuZCBsZWdlbmRzIHVzaW5nIHRoZSBzY2FsZSBtZXRob2RzLgoKU2NhbGUgbWV0aG9kcyBhcmUgZGl2aWRlZCBpbnRvIGZ1bmN0aW9ucyBieSBjb21iaW5hdGlvbnMgb2YKCi0gICB0aGUgYWVzdGhldGljcyB0aGV5IGNvbnRyb2wuCgotICAgdGhlIHR5cGUgb2YgZGF0YSBtYXBwZWQgdG8gc2NhbGUuCgpgc2NhbGVfYCphZXN0aGV0aWMqXF8qdHlwZSoKClRyeSB0eXBpbmcgaW4gYHNjYWxlX2AgdGhlbiB0YWIgdG8gYXV0b2NvbXBsZXRlLiBUaGlzIHdpbGwgcHJvdmlkZSBzb21lIGV4YW1wbGVzIG9mIHRoZSBzY2FsZSBmdW5jdGlvbnMgYXZhaWxhYmxlIGluIGBnZ3Bsb3QyYC4KCkFsdGhvdWdoIGRpZmZlcmVudCBzY2FsZSBmdW5jdGlvbnMgYWNjZXB0IHNvbWUgdmFyaWV0eSBpbiB0aGVpciBhcmd1bWVudHMsIGNvbW1vbiBhcmd1bWVudHMgdG8gc2NhbGUgZnVuY3Rpb25zIGluY2x1ZGUgLQoKLSAgIG5hbWUgLSBUaGUgYXhpcyBvciBsZWdlbmQgdGl0bGUKCi0gICBsaW1pdHMgLSBNaW5pbXVtIGFuZCBtYXhpbXVtIG9mIHRoZSBzY2FsZQoKLSAgIGJyZWFrcyAtIExhYmVsL3RpY2sgcG9zaXRpb25zIGFsb25nIGFuIGF4aXMKCi0gICBsYWJlbHMgLSBMYWJlbCBuYW1lcyBhdCBlYWNoIGJyZWFrCgotICAgdmFsdWVzIC0gdGhlIHNldCBvZiBhZXN0aGV0aWMgdmFsdWVzIHRvIG1hcCBkYXRhIHZhbHVlcwoKV2UgY2FuIGNob29zZSBzcGVjaWZpYyBjb2xvdXIgcGFsZXR0ZXMsIHN1Y2ggYXMgdGhvc2UgcHJvdmlkZWQgYnkgdGhlIGBSQ29sb3JCcmV3ZXJgIHBhY2thZ2UuIFRoaXMgcGFja2FnZSBwcm92aWRlcyBwYWxldHRlcyBmb3IgZGlmZmVyZW50IHR5cGVzIG9mIHNjYWxlIChzZXF1ZW50aWFsLCBkaXZlcmdpbmcsIHF1YWxpdGF0aXZlKS4KCmBgYHtyfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKZGlzcGxheS5icmV3ZXIuYWxsKGNvbG9yYmxpbmRGcmllbmRseSA9IFRSVUUpCmBgYAoKOjo6IHdhcm5pbmcKKipXaGVuIGNyZWF0aW5nIGEgcGxvdCwgYWx3YXlzIGNoZWNrIHRoYXQgdGhlIGNvbG91ciBzY2hlbWUgaXMgYXBwcm9wcmlhdGUgZm9yIHBlb3BsZSB3aXRoIHZhcmlvdXMgZm9ybXMgb2YgY29sb3VyLWJsaW5kbmVzcyoqCjo6OgoKV2hlbiBleHBlcmltZW50aW5nIHdpdGggY29sb3VyIHBhbGV0dGVzIGFuZCBsYWJlbHMsIGl0IGlzIHVzZWZ1bCB0byBzYXZlIHRoZSBwbG90IGFzIGFuIG9iamVjdAoKYGBge3J9CnAgPC0gZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXAsIHk9bGlmZUV4cCxjb2w9Y29udGluZW50KSkgKyBnZW9tX3BvaW50KCkKYGBgCgpgYGB7cn0KIyBXZSBjYW4gYWxzbyBjaGFuZ2UgdGhlIHRleHQgZGlzcGxheWVkIGFib3ZlIHRoZSBsZWdlbmQgd2l0aCB0aGUgbmFtZSBwYXJhbWV0ZXIuCnAgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWJyZXdlci5wYWwoNiwiU2V0MiIpKQpgYGAKCk9yIHdlIGNhbiBldmVuIHNwZWNpZnkgb3VyIG93biBjb2xvdXJzOyBzdWNoIGFzIFRoZSBVbml2ZXJzaXR5IG9mIFNoZWZmaWVsZCBicmFuZGluZyBjb2xvdXJzCgpgYGB7cn0KbXlfcGFsIDwtIGMocmdiKDAsMTU5LDIxOCxtYXhDb2xvclZhbHVlID0gMjU1KSwKICAgICAgICAgICAgcmdiKDMxLDIwLDkzLG1heENvbG9yVmFsdWUgPSAyNTUpLAogICAgICAgICAgICByZ2IoMjQ5LDIyNywwLG1heENvbG9yVmFsdWUgPSAyNTUpLAogICAgICAgICAgICByZ2IoMCwxNTUsNzIsbWF4Q29sb3JWYWx1ZSA9IDI1NSksCiAgICAgICAgICAgIHJnYigxOTAsMjE0LDAsbWF4Q29sb3JWYWx1ZSA9IDI1NSkpCnAgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW15X3BhbCkKCmBgYAoKKipORVcqKjotIEEgc2V0IG9mIHBhbGV0dGVzIGJhc2VkIG9uIHdvcmtzIGluIHRoZSBNZXRyb3BvbGl0YW4gTXVzZXVtIG9mIEFydCAoTmV3IFlvcmspIGhhcyBiZWVuIG1hZGUgYXZhaWxhYmxlLgoKPGh0dHBzOi8vZ2l0aHViLmNvbS9CbGFrZVJNaWxscy9NZXRCcmV3ZXI+CgpgYGB7cn0KIyMgdGhpcyB3aWxsIGNoZWNrIGlmIE1ldEJyZXdlciBpcyBhbHJlYWR5IGluc3RhbGxlZCwgYW5kIHdpbGwgb25seSBpbnN0YWxsIGlmIGl0IGlzIG5vdCBmb3VuZAoKaWYoIXJlcXVpcmUoIk1ldEJyZXdlciIpKSBpbnN0YWxsLnBhY2thZ2VzKCJNZXRCcmV3ZXIiKQpsaWJyYXJ5KE1ldEJyZXdlcikKcCArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bWV0LmJyZXdlcihuYW1lID0gIkdyZWVrIikpCmBgYAoKVmFyaW91cyBsYWJlbHMgY2FuIGJlIG1vZGlmaWVkIHVzaW5nIHRoZSBgbGFic2AgZnVuY3Rpb24uCgpgYGB7cn0KcCArIGxhYnMoeD0iV2VhbHRoIix5PSJMaWZlIEV4cGVjdGFuY3kiLHRpdGxlPSJSZWxhdGlvbnNoaXAgYmV0d2VlbiBXZWFsdGggYW5kIExpZmUgRXhwZWN0YW5jeSIpCmBgYAoKV2UgY2FuIGFsc28gbW9kaWZ5IHRoZSB4LSBhbmQgeS0gbGltaXRzIG9mIHRoZSBwbG90IHNvIHRoYXQgYW55IG91dGxpZXJzIGFyZSBub3Qgc2hvd24uIGBnZ3Bsb3QyYCB3aWxsIGdpdmUgYSB3YXJuaW5nIHRoYXQgc29tZSBwb2ludHMgYXJlIGV4Y2x1ZGVkLgoKYGBge3J9CnAgKyB4bGltKDAsNjAwMDApCmBgYAoKU2F2aW5nIGlzIHN1cHBvcnRlZCBieSB0aGUgYGdnc2F2ZWAgZnVuY3Rpb24uIEEgdmFyaWV0eSBvZiBmaWxlIGZvcm1hdHMgYXJlIHN1cHBvcnRlZCAoYC5wbmdgLCBgLnBkZmAsIGAudGlmZmAsIGV0YykgYW5kIHRoZSBmb3JtYXQgdXNlZCBpcyBkZXRlcm1pbmVkIGZyb20gdGhlIGV4dGVuc2lvbiBnaXZlbiBpbiB0aGUgYGZpbGVgIGFyZ3VtZW50LiBUaGUgaGVpZ2h0LCB3aWR0aCBhbmQgcmVzb2x1dGlvbiBjYW4gYWxzbyBiZSBjb25maWd1cmVkLiBTZWUgdGhlIGhlbHAgb24gYGdnc2F2ZWAgKGA/Z2dzYXZlYCkgZm9yIG1vcmUgaW5mb3JtYXRpb24uCgpgYGB7cn0KZ2dzYXZlKHAsIGZpbGU9Im15X2dncGxvdC5wbmciKQpgYGAKCk1vc3QgYXNwZWN0cyBvZiB0aGUgcGxvdCBjYW4gYmUgbW9kaWZpZWQgZnJvbSB0aGUgYmFja2dyb3VuZCBjb2xvdXIgdG8gdGhlIGdyaWQgc2l6ZXMgYW5kIGZvbnQuIFNldmVyYWwgcHJlLWRlZmluZWQgInRoZW1lcyIgZXhpc3QgYW5kIHdlIGNhbiBtb2RpZnkgdGhlIGFwcGVhcmFuY2Ugb2YgdGhlIHdob2xlIHBsb3QgdXNpbmcgYSBgdGhlbWVfLi5gIGZ1bmN0aW9uLgoKYGBge3J9CnAgKyB0aGVtZV9idygpCmBgYAoKTW9yZSB0aGVtZXMgYXJlIHN1cHBvcnRlZCBieSB0aGUgYGdndGhlbWVzYCBwYWNrYWdlLiBZb3UgY2FuIG1ha2UgeW91ciBwbG90cyBsb29rIGxpa2UgdGhlIEVjb25vbWlzdCwgV2FsbCBTdHJlZXQgSm91cm5hbCBvciBFeGNlbCAoKipidXQgcGxlYXNlIGRvbid0IGRvIHRoaXMhKiopCgpgYGB7cn0KIyMgdGhpcyB3aWxsIGNoZWNrIGlmIGdndGhlbWVzIGlzIGFscmVhZHkgaW5zdGFsbGVkLCBhbmQgd2lsbCBvbmx5IGluc3RhbGwgaWYgaXQgaXMgbm90IGZvdW5kCgppZighcmVxdWlyZSgiZ2d0aGVtZXMiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2d0aGVtZXMiKQpsaWJyYXJ5KGdndGhlbWVzKQpwICsgdGhlbWVfZXhjZWwoKQpgYGAKIyMgRXhlcmNpc2UgCjo6OiBleGVyY2lzZQoqKkV4ZXJjaXNlKio6IFVzZSBhIGJveHBsb3QgdG8gY29tcGFyZSB0aGUgbGlmZSBleHBlY3RhbmN5IHZhbHVlcyBvZiBBdXN0cmFsaWEgYW5kIE5ldyBaZWFsYW5kLiBVc2UgYSBgU2V0MmAgcGFsZXR0ZSBmcm9tIGBSQ29sb3JCcmV3ZXJgIHRvIGNvbG91ciB0aGUgYm94cGxvdHMgYW5kIGFwcGx5IGEgIm1pbmltYWwiIHRoZW1lIHRvIHRoZSBwbG90Lgo6OjoKCiFbXShpbWFnZXMvb2NlYW5pYV9sZV9ib3hwbG90LnBuZykKCiMjIEZhY2V0cwoKT25lIHZlcnkgdXNlZnVsIGZlYXR1cmUgb2YgYGdncGxvdDJgIGlzIGZhY2V0aW5nLiBUaGlzIGFsbG93cyB5b3UgdG8gcHJvZHVjZSBwbG90cyBzdWJzZXQgYnkgdmFyaWFibGVzIGluIHlvdXIgZGF0YS4gSW4gdGhlIHNjYXR0ZXIgcGxvdCBhYm92ZSwgaXQgd2FzIHF1aXRlIGRpZmZpY3VsdCB0byBzZWUgaWYgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdkcCBhbmQgbGlmZSBleHBlY3RhbmN5IHdhcyB0aGUgc2FtZSBmb3IgZWFjaCBjb250aW5lbnQuIFRvIG92ZXJjb21lIHRoaXMsIHdlIHdvdWxkIGxpa2UgYSBzZWUgYSBzZXBhcmF0ZSBwbG90IGZvciBlYWNoIGNvbnRpbmVudC4KClRvIGZhY2V0IG91ciBkYXRhIGludG8gbXVsdGlwbGUgcGxvdHMgd2UgY2FuIHVzZSB0aGUgYGZhY2V0X3dyYXBgICgxIHZhcmlhYmxlKSBvciBgZmFjZXRfZ3JpZGAgKDIgdmFyaWFibGVzKSBmdW5jdGlvbnMgYW5kIHNwZWNpZnkgdGhlIHZhcmlhYmxlKHMpIHdlIHNwbGl0IGJ5LgoKYGBge3J9CnAgKyBmYWNldF93cmFwKH5jb250aW5lbnQpCgpgYGAKClRoZSBgZmFjZXRfZ3JpZGAgZnVuY3Rpb24gd2lsbCBjcmVhdGUgYSBncmlkLWxpa2UgcGxvdCB3aXRoIG9uZSB2YXJpYWJsZSBvbiB0aGUgeC1heGlzIGFuZCBhbm90aGVyIG9uIHRoZSB5LWF4aXMuCgpgYGB7ciBmaWcud2lkdGg9MTJ9CnAgKyBmYWNldF9ncmlkKGNvbnRpbmVudH55ZWFyKQpgYGAKClRoZSBwcmV2aW91cyBwbG90IHdhcyBhIGJpdCBtZXNzeSBhcyBpdCBjb250YWluZWQgYWxsIGNvbWJpbmF0aW9ucyBvZiB5ZWFyIGFuZCBjb250aW5lbnQuIExldCdzIHN1cHBvc2Ugd2Ugd2FudCBvdXIgYW5hbHlzaXMgdG8gYmUgYSBiaXQgbW9yZSBmb2N1c2VkIGFuZCBkaXNyZWdhcmQgY291bnRyaWVzIGluIE9jZWFuaWEgKGFzIHRoZXJlIGFyZSBvbmx5IDIgaW4gb3VyIGRhdGFzZXQpIGFuZCBtYXliZSB5ZWFycyBiZXR3ZWVuIDE5OTcgYW5kIDIwMDIuCgpXZSBzaG91bGQga25vdyBob3cgdG8gcmVzdHJpY3QgdGhlIHJvd3MgZnJvbSB0aGUgYGdhcG1pbmRlcmAgZGF0YXNldCB1c2luZyB0aGUgYGZpbHRlcmAgZnVuY3Rpb24uIEluc3RlYWQgb2YgZmlsdGVyaW5nIHRoZSBkYXRhLCBjcmVhdGluZyBhIG5ldyBkYXRhIGZyYW1lIGFuZCBjb25zdHJ1Y3RpbmcgdGhlIGRhdGEgZnJhbWUgZnJvbSB0aGVzZSBuZXcgZGF0YSB3ZSBjYW4gdXNlIHRoZWAlPiVgIG9wZXJhdG9yIHRvIGNyZWF0ZSB0aGUgZGF0YSBmcmFtZSBvbiB0aGUgZmx5IGFuZCBwYXNzIGRpcmVjdGx5IHRvIGBnZ3Bsb3RgLiBUaHVzIHdlIGRvbid0IGhhdmUgdG8gc2F2ZSBhIG5ldyBkYXRhIGZyYW1lIG9yIGFsdGVyIHRoZSBvcmlnaW5hbCBkYXRhLgoKYGBge3IgZmlnLndpZHRoPTEyfQpmaWx0ZXIoZ2FwbWluZGVyLCBjb250aW5lbnQhPSJPY2VhbmlhIiwgeWVhciAlaW4lIGMoMTk5NywyMDAyLDIwMDcpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZ2RwUGVyY2FwLCB5PWxpZmVFeHAsY29sPWNvbnRpbmVudCkpICsgZ2VvbV9wb2ludCgpICsgZmFjZXRfZ3JpZChjb250aW5lbnR+eWVhcikKYGBgCgojIFN1bW1hcmlzaW5nIGFuZCBncm91cGluZyB3aXRoIGRwbHlyCgpUaGUgYHN1bW1hcmlzZWAgZnVuY3Rpb24gY2FuIHRha2UgYW55IFIgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIHZlY3RvciBvZiB2YWx1ZXMgKGkuZS4gYSBjb2x1bW4gZnJvbSBhIGRhdGEgZnJhbWUpIGFuZCByZXR1cm5zIGEgc2luZ2xlIHZhbHVlLiBTb21lIG9mIHRoZSBtb3JlIHVzZWZ1bCBmdW5jdGlvbnMgaW5jbHVkZToKCi0gICBgbWluYCBtaW5pbXVtIHZhbHVlCi0gICBgbWF4YCBtYXhpbXVtIHZhbHVlCi0gICBgc3VtYCBzdW0gb2YgdmFsdWVzCi0gICBgbWVhbmAgbWVhbiB2YWx1ZQotICAgYHNkYCBzdGFuZGFyZCBkZXZpYXRpb24KLSAgIGBtZWRpYW5gIG1lZGlhbiB2YWx1ZQotICAgYElRUmAgdGhlIGludGVycXVhcnRpbGUgcmFuZ2UKLSAgIGBuX2Rpc3RpbmN0YCB0aGUgbnVtYmVyIG9mIGRpc3RpbmN0IHZhbHVlcwotICAgYG5gIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIChOb3RlOiB0aGlzIGlzIGEgc3BlY2lhbCBmdW5jdGlvbiB0aGF0IGRvZXNuJ3QgdGFrZSBhIHZlY3RvciBhcmd1bWVudCwgaS5lLiBjb2x1bW4pCgpgYGB7cn0KbGlicmFyeShkcGx5cikKc3VtbWFyaXNlKGdhcG1pbmRlciwgbWluKGxpZmVFeHApLCBtYXgoZ2RwUGVyY2FwKSwgbWVhbihwb3ApKQpgYGAKCkl0IGlzIGFsc28gcG9zc2libGUgdG8gc3VtbWFyaXNlIHVzaW5nIGEgZnVuY3Rpb24gdGhhdCB0YWtlcyBtb3JlIHRoYW4gb25lIHZhbHVlLCBpLmUuIGZyb20gbXVsdGlwbGUgY29sdW1ucy4gRm9yIGV4YW1wbGUsIHdlIGNvdWxkIGNvbXB1dGUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4geWVhciBhbmQgbGlmZSBleHBlY3RhbmN5LiBIZXJlIHdlIGFsc28gYXNzaWduIG5hbWVzIHRvIHRoZSB0YWJsZSB0aGF0IGlzIHByb2R1Y2VkLgoKYGBge3J9CmdhcG1pbmRlciAlPiUgCnN1bW1hcmlzZShNaW5MaWZlRXhwZWN0YW5jeSA9IG1pbihsaWZlRXhwKSwgCiAgICAgICAgICBNYXhpbXVtR0RQID0gbWF4KGdkcFBlcmNhcCksIAogICAgICAgICAgQXZlcmFnZVBvcCA9IG1lYW4ocG9wKSwgCiAgICAgICAgICBDb3JyZWxhdGlvbiA9IGNvcih5ZWFyLCBsaWZlRXhwKSkKYGBgCgpIb3dldmVyLCBpdCBpcyBub3QgcGFydGljdWxhcmx5IHVzZWZ1bCB0byBjYWxjdWxhdGUgc3VjaCB2YWx1ZXMgZnJvbSB0aGUgZW50aXJlIHRhYmxlIGFzIHdlIGhhdmUgZGlmZmVyZW50IGNvbnRpbmVudHMgYW5kIHllYXJzLiBUaGUgYGdyb3VwX2J5YCBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gc3BsaXQgdGhlIHRhYmxlIGludG8gZGlmZmVyZW50IGNhdGVnb3JpZXMsIGFuZCBjb21wdXRlIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgZWFjaCB5ZWFyIChmb3IgZXhhbXBsZSkuCgpgYGB7cn0KZ2FwbWluZGVyICU+JSAKICAgIGdyb3VwX2J5KHllYXIpICU+JSAKICAgIHN1bW1hcmlzZShNaW5MaWZlRXhwZWN0YW5jeSA9IG1pbihsaWZlRXhwKSwgCiAgICAgICAgICAgICAgTWF4aW11bUdEUCA9IG1heChnZHBQZXJjYXApLCAKICAgICAgICAgICAgICBBdmVyYWdlUG9wID0gbWVhbihwb3ApKQpgYGAKCgoKYGBge3J9CmdhcG1pbmRlciAlPiUgCiAgICBncm91cF9ieSh5ZWFyLGNvbnRpbmVudCkgJT4lIAogICAgc3VtbWFyaXNlKE1pbkxpZmVFeHBlY3RhbmN5ID0gbWluKGxpZmVFeHApLCAKICAgICAgICAgICAgICBNYXhpbXVtR0RQID0gbWF4KGdkcFBlcmNhcCksIAogICAgICAgICAgICAgIEF2ZXJhZ2VQb3AgPSBtZWFuKHBvcCkpCmBgYAoKCldlIGNhbiBsaXN0IGFzIG1hbnkgc3VtbWFyeSBmdW5jdGlvbnMgYXMgd2UgbGlrZS4gV2hpbHN0IHRoaXMgY2FuIG1ha2Ugb3VyIGNvZGUgc29tZXdoYXQgdmVyYm9zZSB0aGVyZSBhcmUgbWFueSBoZWxwZXIgZnVuY3Rpb25zIGF2YWlsYWJsZS4gQ29uc2lkZXIgYW4gZXhhbXBsZSB3aGVyZSB3ZSB3YW50IHRvIGF2ZXJhZ2UgYWxsIHRoZSBjb2x1bW5zIGluIG91ciBkYXRhOi0KCmBgYHtyfQpnYXBtaW5kZXIgJT4lIAogICAgZ3JvdXBfYnkoeWVhcikgJT4lIAogICAgc3VtbWFyaXNlKE1lYW5MaWZlRXhwZWN0YW5jeSA9IG1lYW4obGlmZUV4cCksIAogICAgICAgICAgICAgIE1lYW5HRFAgPSBtZWFuKGdkcFBlcmNhcCksIAogICAgICAgICAgICAgIE1lYW5Qb3AgPSBtZWFuKHBvcCkpCmBgYAoKVGhpcyB3YXNuJ3QgYSBodWdlIGVmZm9ydCB0byB3cml0ZSB0aGlzIGNvZGUuIEhvd2V2ZXIsIGl0IHdvdWxkIGJlIG11Y2ggbW9yZSB0ZWRpb3VzIGZvciBhIGRhdGFzZXQgd2l0aCBtYW55IG1vcmUgY29sdW1ucy4gUmVjb2duaXNpbmcgdGhpcywgd2UgY2FuIHVzZSB0aGUgY29udmVuaWVudCBgc3VtbWFyaXNlX2FsbGAgZnVuY3Rpb24uIFRoaXMgd2lsbCByZXR1cm4gYE5BYCB2YWx1ZXMgZm9yIGNvbHVtbnMgdGhhdCBkbyBub3QgY29udGFpbiBudW1lcmljIHZhbHVlcy4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KZ2FwbWluZGVyICU+JSAKICBncm91cF9ieShjb250aW5lbnQpICU+JSAKICBzdW1tYXJpc2VfYWxsKG1lYW4pCmBgYAoKVGhlIG5pY2UgdGhpbmcgYWJvdXQgYHN1bW1hcmlzZWAgaXMgdGhhdCBpdCBjYW4gZm9sbG93ZWQgdXAgYnkgYW55IG9mIHRoZSBvdGhlciBgZHBseXJgIHZlcmJzIHRoYXQgd2UgaGF2ZSBtZXQgc28gZmFyIChgc2VsZWN0YCwgYGZpbHRlcmAsIGBhcnJhbmdlYC4uZXRjKS4gQXMgdGhlIGBjb3VudHJ5YCBjb2x1bW4gb2YgdGhlIHByZXZpb3VzIG91dHB1dCBjb250YWluaW5nIG1pc3NpbmcgdmFsdWVzIHdlIGNhbiBleGNsdWRlIGl0IGZyb20gZnVydGhlciBwcm9jZXNzaW5nLgoKYGBge3Igd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQpnYXBtaW5kZXIgJT4lIAogIGdyb3VwX2J5KGNvbnRpbmVudCkgJT4lIAogIHN1bW1hcmlzZV9hbGwobWVhbikgJT4lIAogIHNlbGVjdCgtY291bnRyeSkKYGBgCgpSZXR1cm5pbmcgdG8gdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gbGlmZSBleHBlY3RhbmN5IGFuZCB5ZWFyLCB3ZSBjYW4gc3VtbWFyaXNlIGFzIGZvbGxvd3M6LQoKYGBge3IsbWVzc2FnZT1GQUxTRX0KZ2FwbWluZGVyICU+JSAgICAgCiAgICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgICBzdW1tYXJpc2UoQ29ycmVsYXRpb24gPSBjb3IoeWVhciAsIGxpZmVFeHApKQpgYGAKCldlIGNhbiB0aGVuIGFycmFuZ2UgdGhlIHRhYmxlIGJ5IHRoZSBjb3JyZWxhdGlvbiB0byBzZWUgd2hpY2ggY291bnRyaWVzIGhhdmUgdGhlIGxvd2VzdCBjb3JyZWxhdGlvbgoKYGBge3IsbWVzc2FnZT1GQUxTRX0KZ2FwbWluZGVyICU+JSAgICAgIAogICAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogICAgc3VtbWFyaXNlKENvcnJlbGF0aW9uID0gY29yKHllYXIgLCBsaWZlRXhwKSkgJT4lIAogICAgYXJyYW5nZShDb3JyZWxhdGlvbikKYGBgCgpXZSBjYW4gZmlsdGVyIHRoZSByZXN1bHRzIHRvIGZpbmQgb2JzZXJ2YXRpb25zIG9mIGludGVyZXN0CgpgYGB7cixtZXNzYWdlPUZBTFNFfQpnYXBtaW5kZXIgJT4lICAgICAgCiAgICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgICBzdW1tYXJpc2UoQ29ycmVsYXRpb24gPSBjb3IoeWVhciAsIGxpZmVFeHApKSAlPiUgCiAgICBmaWx0ZXIoQ29ycmVsYXRpb24gPCAwKQpgYGAKClRoZSBjb3VudHJpZXMgd2UgaWRlbnRpZnkgY291bGQgdGhlbiBiZSB1c2VkIGFzIHRoZSBiYXNpcyBmb3IgYSBwbG90LgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZmlsdGVyKGdhcG1pbmRlciwgY291bnRyeSAlaW4lIGMoIlJ3YW5kYSIsIlphbWJpYSIsIlppbWJhYndlIikpICU+JSAKICBnZ3Bsb3QoYWVzKHg9eWVhciwgeT1saWZlRXhwLGNvbD1jb3VudHJ5KSkgKyBnZW9tX2xpbmUoKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyBFeGVyY2lzZQoKOjo6IGV4ZXJjaXNlCi0gICBTdW1tYXJpc2UgdGhlIGBnYXBtaW5kZXJgIGRhdGEgaW4gYW4gYXBwcm9wcmlhdGUgbWFubmVyIHRvIHByb2R1Y2UgYSBwbG90IHRvIHNob3cgdGhlIGNoYW5nZSBpbiBhdmVyYWdlIGBnZHBQZXJjYXBgIGZvciBlYWNoIGNvbnRpbmVudCBvdmVyIHRpbWUuCi0gICBzZWUgYmVsb3cgZm9yIGEgc3VnZ2VzdGlvbgogICAgLSAgIEhJTlQ6IHlvdSB3aWxsIG5lZWQgdG8gdXNlIHRoZSBgZ2VvbV9jb2xgIGZ1bmN0aW9uIHRvIGNyZWF0ZSB0aGUgYmFyIHBsb3QKOjo6CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NoZWZmaWVsZC1iaW9pbmZvcm1hdGljcy1jb3JlL3ItY3Jhc2gtY291cnNlL21hc3Rlci9pbWFnZXMvc3VtbWFyaXNlX2V4YW1wbGUucG5nKQoKCiMgSm9pbmluZwoKSW4gbWFueSByZWFsIGxpZmUgc2l0dWF0aW9ucywgZGF0YSBhcmUgc3ByZWFkIGFjcm9zcyBtdWx0aXBsZSB0YWJsZXMgb3Igc3ByZWFkc2hlZXRzLiBVc3VhbGx5IHRoaXMgb2NjdXJzIGJlY2F1c2UgZGlmZmVyZW50IHR5cGVzIG9mIGluZm9ybWF0aW9uIGFib3V0IGEgc3ViamVjdCwgZS5nLiBhIHBhdGllbnQsIGFyZSBjb2xsZWN0ZWQgZnJvbSBkaWZmZXJlbnQgc291cmNlcy4gSXQgbWF5IGJlIGRlc2lyYWJsZSBmb3Igc29tZSBhbmFseXNlcyB0byBjb21iaW5lIGRhdGEgZnJvbSB0d28gb3IgbW9yZSB0YWJsZXMgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lIGJhc2VkIG9uIGEgY29tbW9uIGNvbHVtbiwgZm9yIGV4YW1wbGUsIGFuIGF0dHJpYnV0ZSB0aGF0IHVuaXF1ZWx5IGlkZW50aWZpZXMgdGhlIHN1YmplY3QuCgpgZHBseXJgIHByb3ZpZGVzIGEgc2V0IG9mIGpvaW4gZnVuY3Rpb25zIGZvciBjb21iaW5pbmcgdHdvIGRhdGEgZnJhbWVzIGJhc2VkIG9uIG1hdGNoZXMgd2l0aGluIHNwZWNpZmllZCBjb2x1bW5zLiBGb3IgdGhvc2UgZmFtaWxpYXIgd2l0aCBzdWNoIFNRTCwgdGhlc2Ugb3BlcmF0aW9ucyBhcmUgdmVyeSBzaW1pbGFyIHRvIGNhcnJ5aW5nIG91dCBqb2luIG9wZXJhdGlvbnMgYmV0d2VlbiB0YWJsZXMgaW4gYSByZWxhdGlvbmFsIGRhdGFiYXNlLgoKQXMgYSB0b3kgZXhhbXBsZSwgbGV0cyBjb25zaWRlciB0d28gZGF0YSBmcmFtZXMgdGhhdCBzb21lIHJlc3VsdHMgb2YgdGVzdGluZyB3aGV0aGVyIGdlbmVzIEEsIEIgYW5kIEMgYXJlIHNpZ25pZmljYW50IGluIG91ciBzdHVkeSAoZ2VuZSBleHByZXNzaW9uLCBtdXRhdGlvbnMsIGV0Yy4pCgpgYGB7cn0KZ2VuZV9yZXN1bHRzIDwtIGRhdGEuZnJhbWUoTmFtZT1MRVRURVJTWzE6M10sIHB2YWx1ZSA9IGMoMC4wMDEsIDAuMSwwLjAxKSkKZ2VuZV9yZXN1bHRzCmBgYAoKV2UgbWlnaHQgYWxzbyBoYXZlIGEgZGF0YSBmcmFtZSBjb250YWluaW5nIG1vcmUgZGF0YSBhYm91dCB0aGUgZ2VuZXM7IHN1Y2ggYXMgd2hpY2ggY2hyb21vc29tZSB0aGV5IGFyZSBsb2NhdGVkIG9uLiBBcyBwYXJ0IG9mIG91ciBkYXRhIGludGVycHJldGF0aW9uIHdlIG1pZ2h0IG5lZWQgdG8ga25vdyB3aGVyZSBpbiB0aGUgZ2Vub21lIHRoZSBnZW5lcyBhcmUgbG9jYXRlZC4gTm90ZSB0aGF0IGJvdGggZGF0YSBmcmFtZXMgaGF2ZSBhIGNvbHVtbiBjYWxsZWQgYE5hbWVgLiBUaGlzIGNvbHVtbiB3aWxsIGJlIHVzZWQgdG8gaWRlbnRpZnkgZ2VuZXMgY29tbW9uIHRvIGJvdGggdGFibGVzLgoKYGBge3J9CmdlbmVfYW5ubyA8LSBkYXRhLmZyYW1lKE5hbWUgPSBjKCJBIiwiQiIsIkQiKSwgY2hyb21vc29tZT1jKDEsMSwzKSkKZ2VuZV9hbm5vCmBgYAoKVGhlcmUgYXJlIHZhcmlvdXMgd2F5cyBpbiB3aGljaCB3ZSBjYW4gam9pbiB0aGVzZSB0d28gdGFibGVzIHRvZ2V0aGVyLiBXZSB3aWxsIGZpcnN0IGNvbnNpZGVyIHRoZSBjYXNlIG9mIGEgImxlZnQgam9pbiIuCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NoZWZmaWVsZC1iaW9pbmZvcm1hdGljcy1jb3JlL3ItY3Jhc2gtY291cnNlL21hc3Rlci9pbWFnZXMvbGVmdC1qb2luLmdpZikKCipBbmltYXRlZCBnaWYgYnkgR2FycmljayBBZGVuLUJ1aWUqCgpgbGVmdF9qb2luYCByZXR1cm5zIGFsbCByb3dzIGZyb20gdGhlIGZpcnN0IGRhdGEgZnJhbWUgcmVnYXJkbGVzcyBvZiB3aGV0aGVyIHRoZXJlIGlzIGEgbWF0Y2ggaW4gdGhlIHNlY29uZCBkYXRhIGZyYW1lLiBSb3dzIHdpdGggbm8gbWF0Y2ggYXJlIGluY2x1ZGVkIGluIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZSBidXQgaGF2ZSBgTkFgIHZhbHVlcyBpbiB0aGUgYWRkaXRpb25hbCBjb2x1bW5zIGNvbWluZyBmcm9tIHRoZSBzZWNvbmQgZGF0YSBmcmFtZS4KCkFuaW1hdGlvbnMgdG8gaWxsdXN0cmF0ZSBvdGhlciB0eXBlcyBvZiBqb2luIGFyZSBhdmFpbGFibGUgYXQgPGh0dHBzOi8vZ2l0aHViLmNvbS9nYWRlbmJ1aWUvdGlkeS1hbmltYXRlZC12ZXJicz4KCmBgYHtyfQpsZWZ0X2pvaW4oZ2VuZV9yZXN1bHRzLCBnZW5lX2Fubm8pCmBgYAoKYHJpZ2h0X2pvaW5gIGlzIHNpbWlsYXIgYnV0IHJldHVybnMgYWxsIHJvd3MgZnJvbSB0aGUgc2Vjb25kIGRhdGEgZnJhbWUgdGhhdCBoYXZlIGEgbWF0Y2ggd2l0aCByb3dzIGluIHRoZSBmaXJzdCBkYXRhIGZyYW1lIGJhc2VkIG9uIHRoZSBzcGVjaWZpZWQgY29sdW1uLgoKYGBge3J9CnJpZ2h0X2pvaW4oZ2VuZV9yZXN1bHRzLCBnZW5lX2Fubm8pCmBgYAoKYGlubmVyX2pvaW5gIG9ubHkgcmV0dXJucyB0aG9zZSByb3dzIHdoZXJlIG1hdGNoZXMgY291bGQgYmUgbWFkZQoKYGBge3J9CmlubmVyX2pvaW4oZ2VuZV9yZXN1bHRzLCBnZW5lX2Fubm8pCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBXcmFwLXVwCgpXZSBoYXZlIGludHJvZHVjZWQgYSBmZXcgb2YgdGhlIGVzc2VudGlhbCBwYWNrYWdlcyBmcm9tIHRoZSBSIHRpZHl2ZXJzZSB0aGF0IGNhbiBoZWxwIHdpdGggZGF0YSBtYW5pcHVsYXRpb24gYW5kIHZpc3VhbGlzYXRpb24uCgohW10oaHR0cHM6Ly9hYmVyZGVlbnN0dWR5Z3JvdXAuZ2l0aHViLmlvL3N0dWR5R3JvdXAvbGVzc29ucy9TRy1UMi1Kb2ludFdvcmtzaG9wL3RpZHl2ZXJzZS5wbmcpIEhvcGVmdWxseSB5b3Ugd2lsbCBmZWVsIG1vcmUgY29uZmlkZW50IGFib3V0IGltcG9ydGluZyB5b3VyIGRhdGEgaW50byBSIGFuZCBwcm9kdWNpbmcgc29tZSB1c2VmdWwgdmlzdWFsaXNhdGlvbnMuIFlvdSB3aWxsIHByb2JhYmx5IGhhdmUgcXVlc3Rpb25zIHJlZ2FyZGluZyB0aGUgYW5hbHlzaXMgb2YgeW91ciBvd24gZGF0YS4gU29tZSBnb29kIHN0YXJ0aW5nIHBvaW50cyB0byBnZXQgaGVscCBhcmUgbGlzdGVkIGJlbG93LgoKOjo6IGluZm9ybWF0aW9uCi0gICBbdGlkeXZlcnNlIGhvbWVwYWdlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykKLSAgIFtSIGdyYXBoIGdhbGxlcnldKGh0dHBzOi8vd3d3LnItZ3JhcGgtZ2FsbGVyeS5jb20vKQo6OjoKClRvIGZpbmlzaCB0aGUgd29ya3Nob3Agd2Ugd2lsbCBsb29rIGF0IHRoZSBhbmFseXNpcyBvZiBzb21lIHJlbGV2YW50IGRhdGEgdGhhdCB3ZSBjYW4gaW1wb3J0IGludG8gUiBhbmQgYW5hbHlzZSB3aXRoIHRoZSB0b29scyBmcm9tIHRoZSB3b3Jrc2hvcC4KCiMjIEludHJvZHVjaW5nIHRoZSBDT1ZJRC0xOSBkYXRhCgpEYXRhIGZvciBnbG9iYWwgQ09WSUQtMTkgY2FzZXMgYXJlIGF2YWlsYWJsZSBvbmxpbmUgZnJvbSBDU1NFIGF0IEpvaG5zIEhvcGtpbnMgVW5pdmVyc2l0eSBvbiB0aGVpciBnaXRodWIgcmVwb3NpdG9yeS4KCjo6OiBpbmZvcm1hdGlvbgpbZ2l0aHViXSh3d3cuZ2l0aHViLmNvbSkgaXMgYW4gZXhjZWxsZW50IHdheSBvZiBtYWtpbmcgeW91ciBjb2RlIGFuZCBhbmFseXNpcyBhdmFpbGFibGUgZm9yIG90aGVycyB0byByZXVzZSBhbmQgc2hhcmUuIFByaXZhdGUgcmVwb3NpdG9yaWVzIHdpdGggcmVzdHJpY3RlZCBhY2Nlc3MgYXJlIGFsc28gYXZhaWxhYmxlLiBIZXJlIGlzIGEgdXNlZnVsIGJlZ2lubmVycyBndWlkZS4KClwtW0ZyaWVuZGx5IGdpdGh1YiBpbnRyb10oaHR0cHM6Ly9raXJzdGllamFuZS5naXRodWIuaW8vZnJpZW5kbHktZ2l0aHViLWludHJvLykKOjo6CgpSIGlzIGNhcGFibGUgb2YgZG93bmxvYWRpbmcgZmlsZXMgdG8gb3VyIG93biBtYWNoaW5lIHNvIHdlIGNhbiBhbmFseXNlIHRoZW0uIFdlIG5lZWQgdG8ga25vdyB0aGUgVVJMIChmb3IgdGhlIENPVklEIGRhdGEgd2UgY2FuIGZpbmQgdGhpcyBmcm9tIGdpdGh1Yiwgb3IgdXNlIHRoZSBhZGRyZXNzIGJlbG93KSBhbmQgY2FuIHNwZWNpZnkgd2hhdCB0byBjYWxsIHRoZSBmaWxlIHdoZW4gaXQgaXMgZG93bmxvYWRlZC4KCmBgYHtyfQpkb3dubG9hZC5maWxlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzL3RpbWVfc2VyaWVzX2NvdmlkMTlfY29uZmlybWVkX2dsb2JhbC5jc3YiLGRlc3RmaWxlID0gInJhd19kYXRhL3RpbWVfc2VyaWVzX2NvdmlkMTlfY29uZmlybWVkX2dsb2JhbC5jc3YiKQoKYGBgCgpXZSBjYW4gdXNlIHRoZSBgcmVhZF9jc3ZgIGZ1bmN0aW9uIGFzIGJlZm9yZSB0byBpbXBvcnQgdGhlIGRhdGEgYW5kIHRha2UgYSBsb29rLiBXZSBjYW4gc2VlIHRoZSBiYXNpYyBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgaXMgb25lIHJvdyBmb3IgZWFjaCBjb3VudHJ5IC8gcmVnaW9uIGFuZCBjb2x1bW5zIGZvciBjYXNlcyBvbiBlYWNoIGRheS4KCmBgYHtyfQpjb3ZpZCA8LSByZWFkX2NzdigicmF3X2RhdGEvdGltZV9zZXJpZXNfY292aWQxOV9jb25maXJtZWRfZ2xvYmFsLmNzdiIpCmNvdmlkCmBgYAoKV2UgY2FuIHBvdGVudGlhbGx5IGpvaW4gdGhlc2UgZGF0YSB0byBgZ2FwbWluZGVyYCwgYW5kIGl0IHdvdWxkIGJlIGJlbmVmaWNpYWwgdG8gaGF2ZSBvbmUgY29sdW1uIG5hbWUgaW4gY29tbW9uIGJldHdlZW4gYm90aCBmaWxlcy4gV2UgY2FuIGByZW5hbWVgIHRoZSBgQ291bnRyeS9SZWdpb25gIGNvbHVtbiBvZiBvdXIgbmV3IGRhdGEgZnJhbWUgdG8gbWF0Y2ggYGdhcG1pbmRlcmAuCgpgYGB7cn0KY292aWQgPC0gcmVhZF9jc3YoInJhd19kYXRhL3RpbWVfc2VyaWVzX2NvdmlkMTlfY29uZmlybWVkX2dsb2JhbC5jc3YiKSAlPiUgCiAgcmVuYW1lKGNvdW50cnkgPSBgQ291bnRyeS9SZWdpb25gKSAKY292aWQKYGBgCgpNdWNoIG9mIHRoZSBhbmFseXNpcyBvZiB0aGlzIGRhdGFzZXQgaGFzIGxvb2tlZCBhdCB0cmVuZHMgb3ZlciB0aW1lIChlLmcuIGluY3JlYXNpbmcgL2RlY3JlYXNpbmcgY2FzZSBudW1iZXJzLCBjb21wYXJpbmcgdHJhamVjdG9yaWVzKS4gQXMgd2Uga25vdyBieSBub3csIHRoZSBgZ2dwbG90MmAgcGFja2FnZSBhbGxvd3MgdXMgdG8gbWFwIGNvbHVtbnMgKHZhcmlhYmxlcykgaW4gb3VyIGRhdGFzZXQgdG8gYXNwZWN0cyBvZiB0aGUgcGxvdC4KCkluIG90aGVyIHdvcmRzLCB3ZSB3b3VsZCBleHBlY3QgdG8gY3JlYXRlIHBsb3RzIGJ5IHdyaXRpbmcgY29kZSBzdWNoIGFzOi0KCiAgICBnZ3Bsb3QoY292aWQsIGFlcyh4ID0gRGF0ZSwgeSA9Li4uKSkgKyAuLi4KClVuZm9ydHVuYXRlbHkgc3VjaCBwbG90cyBhcmUgbm90IHBvc3NpYmxlIHdpdGggdGhlIGRhdGEgaW4gaXQncyBjdXJyZW50IGZvcm1hdC4gQ291bnRzIGZvciBlYWNoIGRhdGUgYXJlIGNvbnRhaW5pbmcgaW4gYSBkaWZmZXJlbnQgY29sdW1uLiBXaGF0IHdlIHJlcXVpcmUgaXMgYSBjb2x1bW4gdG8gaW5kaWNhdGUgdGhlIGRhdGUsIGFuZCB0aGUgY29ycmVzcG9uZGluZyBjb3VudCBpbiB0aGUgbmV4dCBjb2x1bW4uIFN1Y2ggZGF0YSBhcnJhbmdlbWVudHMgYXJlIGtub3duIGFzICpsb25nIGRhdGEqOyB3aGVyZWFzIHdlIGhhdmUgKndpZGUqIGRhdGEuIEZvcnR1bmF0ZWx5IHdlIGNhbiBjb252ZXJ0IGJldHdlZW4gdGhlIHR3byB1c2luZyB0aGUgYHRpZHlyYCBwYWNrYWdlIChhbHNvIHBhcnQgb2YgdGlkeXZlcnNlKS4KCmBgYHtyIGV2YWw9RkFMU0V9CiMjIGluc3RhbGwgdGlkeXIgaWYgeW91IGRvbid0IGFscmVhZHkgaGF2ZSBpdAppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5ciIpCmBgYAoKOjo6IGluZm9ybWF0aW9uCkZvciBtb3JlIGluZm9ybWF0aW9uIG9uICp0aWR5IGRhdGEqLCBhbmQgaG93IHRvIGNvbnZlcnQgYmV0d2VlbiBsb25nIGFuZCB3aWRlIGRhdGEsIHNlZQoKPGh0dHBzOi8vcjRkcy5oYWQuY28ubnovdGlkeS1kYXRhLmh0bWw+Cjo6OgoKYGBge3J9CmxpYnJhcnkodGlkeXIpCmNvdmlkIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS90aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IikgJT4lIAogIHJlbmFtZShjb3VudHJ5ID0gYENvdW50cnkvUmVnaW9uYCkgJT4lIAogIHBpdm90X2xvbmdlcig1Omxhc3RfY29sKCksbmFtZXNfdG89IkRhdGUiLCB2YWx1ZXNfdG89IkNhc2VzIikKY292aWQKYGBgCgpBbm90aGVyIHBvaW50IHRvIG5vdGUgaXMgdGhhdCB0aGUgZGF0ZXMgYXJlIG5vdCBpbiBhbiBpbnRlcm5hdGlvbmFsbHkgcmVjb2duaXNlZCBmb3JtYXQsIHdoaWNoIGNvdWxkIGNhdXNlIGEgcHJvYmxlbSBmb3Igc29tZSB2aXN1YWxpc2F0aW9ucyB0aGF0IHJlbHkgb24gZGF0ZSBvcmRlci4gV2UgY2FuIGZpeCBieSBleHBsaWNpdGx5IGNvbnZlcnRpbmcgdG8gWVlZWS1NTS1ERCBmb3JtYXQuCgo8ZGl2IGNsYXNzPSJpbmZvcm1hdGlvbiI+CkZvciBtb3JlIHdheXMgb2YgZGVhbGluZyB3aXRoIGRhdGVzIGluIFIgc2VlIHRoZSBgbHVicmlkYXRlYCBwYWNrYWdlLgo8L2Rpdj4KCmBgYHtyfQpjb3ZpZCA8LSByZWFkX2NzdigicmF3X2RhdGEvdGltZV9zZXJpZXNfY292aWQxOV9jb25maXJtZWRfZ2xvYmFsLmNzdiIpICU+JSAKICByZW5hbWUoY291bnRyeSA9IGBDb3VudHJ5L1JlZ2lvbmApICU+JSAKICBwaXZvdF9sb25nZXIoNTpsYXN0X2NvbCgpLG5hbWVzX3RvPSJEYXRlIiwgdmFsdWVzX3RvPSJDYXNlcyIpICU+JSAKICAgIG11dGF0ZShEYXRlPWFzLkRhdGUoRGF0ZSwiJW0vJWQvJXkiKSkKY292aWQKYGBgCgpBbm90aGVyIHVzZWZ1bCBtb2RpZmljYXRpb24gaXMgdG8gbWFrZSBzdXJlIG9ubHkgb25lIHJvdyBleGlzdHMgZm9yIGVhY2ggY291bnRyeS4gSWYgd2UgbG9vayBhdCB0aGUgZGF0YSBmb3Igc29tZSBjb3VudHJpZXMgKGUuZy4gQ2hpbmEgYW5kIFVLKSB0aGVyZSBhcmUgZGlmZmVyZW50IGVudHJpZXMgZm9yIHByb3ZpbmNlcyBhbmQgb3ZlcnNlZXMgdGVycml0b3JpZXMuCgpgYGB7cn0KIyMgdGhlIGNvdW50IGZ1bmN0aW9uIHRhYnVsYXRlcyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBhIHBhcnRpY3VsYXIgY29sdW1uCgpmaWx0ZXIoY292aWQsIGNvdW50cnkgPT0gIkNoaW5hIikgJT4lIAogIGNvdW50KGBQcm92aW5jZS9TdGF0ZWApCmBgYAoKU28gd2UgY2FuIGNoYW5nZSB0aGUgQ2FzZXMgdG8gYmUgdGhlIGBzdW1gIG9mIGFsbCBjYXNlcyBmb3IgdGhhdCBjb3VudHJ5IG9uIGEgcGFydGljdWxhciBkYXkuIFdlIGNhbiBkbyB0aGlzIHVzaW5nIHRoZSBgZ3JvdXBfYnlgIGFuZCBgc3VtbWFyaXNlYCBmdW5jdGlvbnMgZnJvbSBhYm92ZQoKYGBge3J9CmNvdmlkIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS90aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IikgJT4lIAogIHJlbmFtZShjb3VudHJ5ID0gYENvdW50cnkvUmVnaW9uYCkgJT4lIAogIHBpdm90X2xvbmdlcig1Omxhc3RfY29sKCksbmFtZXNfdG89IkRhdGUiLCB2YWx1ZXNfdG89IkNhc2VzIikgJT4lIAogIG11dGF0ZShEYXRlPWFzLkRhdGUoRGF0ZSwiJW0vJWQvJXkiKSkgJT4lIAogIGdyb3VwX2J5KGNvdW50cnksRGF0ZSkgJT4lIAogIHN1bW1hcmlzZShDYXNlcyA9IHN1bShDYXNlcykpCgpjb3ZpZApgYGAKCiMjIyBFeGVyY2lzZQoKOjo6IGV4ZXJjaXNlCldoYXQgcGxvdHMgYW5kIHN1bW1hcmllcyBjYW4geW91IG1ha2UgZnJvbSB0aGVzZSBkYXRhPwoKLSAgIFBsb3R0aW5nIHRoZSBudW1iZXIgb2YgY2FzZXMgb3ZlciB0aW1lIGZvciBjZXJ0YWluIGNvdW50cmllcwotICAgV2hpY2ggY291bnRyeSBpbiBlYWNoIGNvbnRpbmVudCBjdXJyZW50bHkgaGFzIHRoZSBoaWdoZXN0IG51bWJlciBvZiBjYXNlcz8KLSAgIE5vcm1hbGlzZSB0aGUgbnVtYmVyIG9mIGNhc2VzIGZvciBwb3B1bGF0aW9uIHNpemUgKHVzaW5nIDIwMDcgcG9wdWxhdGlvbiBmaWd1cmVzIGFzIGEgcG9wdWxhdGlvbiBlc3RpbWF0ZSk/CiAgICAtICAgZS5nLiBjYXNlcyBwZXIgMTAwLDAwMAotICAgV2hpY2ggRXVyb3BlYW4gY291bnRyaWVzIGhhdmUgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIGNhc2VzIHBlciAxMDAsMDAwIHBvcHVsYXRpb24KICAgIC0gICBlLmcuIDxodHRwczovL3d3dy5zdGF0aXN0YS5jb20vc3RhdGlzdGljcy8xMTEwMTg3L2Nvcm9uYXZpcnVzLWluY2lkZW5jZS1ldXJvcGUtYnktY291bnRyeS8+Cjo6OgoKYGBge3J9CgogCmBgYAo=