1 Intro

You now have a solid understanding of how dates are stored, displayed, and formatted in R. In this lesson, you will learn how to perform simple analyses with dates, such as calculating the time between date intervals and creating time series graphs! These skills are crucial for anyone working with health data, as they are the basis to understanding temporal patterns such as the progression of diseases over time and the fluctuation in population health metrics across different periods.

2 Learning Objectives

  • You know how to calculate intervals between dates

  • You know how to extract components from date columns

  • You know how to round dates

  • You are able to create simple time series graphs

3 Packages

Please load the packages needed for this lesson with the code below:

if(!require(pacman)) install.packages("pacman")
pacman::p_load(tidyverse, 
               here,
               lubridate)

3.1 Datasets

‣ We will be working with two datasets related to indoor residual spraying (IRS) for malaria control efforts in Illovo, Malawi.

‣ The first dataset provides the start and end dates of mosquito spraying campaigns in different villages.

irs <- read_csv(here("data/Illovo_data.csv"))
irs
## # A tibble: 112 × 9
##    village   target_spray sprayed coverage_p start_date_default end_date_default
##    <chr>            <dbl>   <dbl>      <dbl> <date>             <date>          
##  1 Mess                87      64       73.6 2014-04-07         2014-04-17      
##  2 Nkombedzi          183     169       92.4 2014-04-22         2014-04-27      
##  3 B Compou…           16      16      100   2014-05-13         2014-05-13      
##  4 D Compou…            3       2       66.7 2014-05-13         2014-05-13      
##  5 Post Off…            6       3       50   2014-05-13         2014-05-13      
##  6 Mangulen…          375     372       99.2 2014-05-15         2014-05-26      
##  7 Mangulen…            7       4       57.1 2014-05-27         2014-05-27      
##  8 Old Scho…           24      23       95.8 2014-05-27         2014-05-27      
##  9 Mwanza             671     636       94.8 2014-05-28         2014-06-16      
## 10 Alumenda           226     226      100   2014-06-18         2014-06-27      
## # ℹ 102 more rows
## # ℹ 3 more variables: start_date_typical <chr>, start_date_long <chr>,
## #   start_date_messy <chr>

‣ The second dataset gives monthly data from 2015-2019 comparing the average incidence of malaria per 1000 people.

‣ It contrasts villages that received IRS against villages that didn’t.

incidence_temp <- read_csv(here("data/Illovo_ir_weather.csv"))
incidence_temp
## # A tibble: 48 × 5
##    date       ir_case ir_control avg_min avg_max
##    <date>       <dbl>      <dbl>   <dbl>   <dbl>
##  1 2015-01-10    42.9       19.6    21.2    31.6
##  2 2015-02-03    61.0       10.1    21.5    32.9
##  3 2015-03-11    74.1       56.8    20.6    33.4
##  4 2015-04-15    95.2       34.7    18.5    32.3
##  5 2015-05-05    89.8       31.9    15.9    31.4
##  6 2015-06-22    59.8       22.6    14.0    29.1
##  7 2015-07-18    36.0       21.2    13.4    29.9
##  8 2015-08-19    32.4       16.6    14.4    30.8
##  9 2015-09-22    42.0       23.0    16.9    34.1
## 10 2015-10-14    17.4       19.8    20.5    36.2
## # ℹ 38 more rows
names(incidence_temp)
## [1] "date"       "ir_case"    "ir_control" "avg_min"    "avg_max"

‣ Columns:

ir_case: Malaria incidence in IRS villages

ir_control: Malaria incidence in non-IRS villages

date: Contains the month and random day

‣ Average monthly minimum and maximum temperatures (avg_min and avg_max)


‣ The final dataset has 1,460 rows of daily weather data for the same Illovo region.

weather <- read_csv(here("data/Illovo_weather.csv"))
weather
## # A tibble: 1,460 × 4
##    date       min_temp max_temp  rain
##    <date>        <dbl>    <dbl> <dbl>
##  1 2015-01-01     21.5     29.9  21.7
##  2 2015-01-02     19.6     30.4   2.2
##  3 2015-01-03     21.6     29.9  25.8
##  4 2015-01-04     20       29.5   1  
##  5 2015-01-05     20       32.2  53  
##  6 2015-01-06     21.8     31.1  60  
##  7 2015-01-07     21       28.7  44.4
##  8 2015-01-08     22       29.5  30  
##  9 2015-01-09     22       31     3  
## 10 2015-01-10     19.9     31.6   0  
## # ℹ 1,450 more rows

‣ Each row signifies a single day and offers measurements of:

‣ Minimum temperature (min_temp) in Celsius

‣ Maximum temperature (max_temp) in Celsius

‣ Rainfall (rain) in millimeters


3.2 Calculating Date Intervals

‣ To begin, we’ll explore two ways to calculate intervals.

‣ The first uses the “-” operator in base R.

‣ The second utilizes the interval operator from the {lubridate} package.

‣ Let’s examine both methods and see how they differ.

3.3 Using the “-” operator

‣ This approach calculates time differences by simply subtracting one date from another.

‣ Let’s craft two date variables and test this out!

date_1 <- as.Date("2020-01-01") # January 1st, 2000
date_2 <- as.Date("2020-01-31") # January 31st, 2000
# subtract the dates
date_2 - date_1
## Time difference of 30 days

‣ And there we have it! R displays the time difference in days.

3.4 Using the interval operator from {lubridate}

‣ Let’s see a second way to calculate time intervals

‣ We’ll use the %--% operator from the {lubridate} package.

‣ This operator is sometimes called the interval operator.

# use the interval operator:
date_2 %--% date_1
## [1] 2020-01-31 UTC--2020-01-01 UTC

‣ The output shows an interval between two dates.

‣ But what if we want to know how long has passed in days?

‣ For this, we need to use the days() function.

‣ Dividing by days(1) will tell lubridate to count in increments of one day at a time.

# divide the interval by days(1):
date_1 %--% date_2/days(1)
## [1] 30

‣ Leaving the parentheses empty, i.e., days(), would also work. This is because lubridate’s default is to count in increments of 1.

‣ But let’s say we want to count in increments of 5 days.

‣ We’d specify days(5)

# divide the interval by days(5)
date_1 %--% date_2/days(5)
## [1] 6

Lubridate weeks

Use the weeks() function in place of days() in the lubridate method to calculate the time difference in weeks between the two dates below:

oct_31 <- as.Date("2023-10-31")
jul_20 <- as.Date("2023-07-20")

jul_20 %--% oct_31/weeks()

3.5 Comparison

‣ So which of the methods is best?

‣ Lubridate provides more flexibility and accuracy when working with dates in R.

‣ Let’s look at a simple example to see why.

‣ First, we’ll set two dates that are 6 years apart:

date_1 <- as.Date("2000-01-01") # January 1st, 2000
date_2 <- as.Date("2006-01-01") # January 1st, 2006

‣ How to calculate the years passed between these dates in base R?

‣ Subtract the two dates, date_2 - date_1

‣ Then, divide by an average day count, like 365.25 (accounting for leap years)

as.numeric((date_2 - date_1)/365.25) # complete the code
## [1] 6.001369

‣ Result is close to 6 but imprecise due to the averaging of leap years!

‣ (Can remove “days” by converting to numeric)


‣ Dividing by 365 or 366 will also give imprecise results:

# divide by 365
(date_2 - date_1)/365 
## Time difference of 6.005479 days
# divide by 366
(date_2 - date_1)/366
## Time difference of 5.989071 days

‣ Need to account for two leap years (two extra days) between the dates

‣ Subtract those two days out first:

# fill in
as.numeric((date_2 - date_1)/365)
## [1] 6.005479

‣ Painful for real data!


‣ With lubridate intervals, process is more straightforward:

‣ Leap years are handled for you

date_1 %--%date_2 /days(365) # interval divided by years()
## [1] 6.005479

‣ Small difference, but lubridate is the winner here.

‣ Also better at handling daylight savings with date-times.

Lubridate intervals

Can you apply lubridate’s interval function to our IRS dataset? Create a new column called spraying_time and using lubridates %--% operator, calculate the number of days between start_date_default and end_date_default.

irs %>% 
  mutate(spraying_time =
           (start_date_default %--% end_date_default)/days())
## # A tibble: 112 × 10
##    village   target_spray sprayed coverage_p start_date_default end_date_default
##    <chr>            <dbl>   <dbl>      <dbl> <date>             <date>          
##  1 Mess                87      64       73.6 2014-04-07         2014-04-17      
##  2 Nkombedzi          183     169       92.4 2014-04-22         2014-04-27      
##  3 B Compou…           16      16      100   2014-05-13         2014-05-13      
##  4 D Compou…            3       2       66.7 2014-05-13         2014-05-13      
##  5 Post Off…            6       3       50   2014-05-13         2014-05-13      
##  6 Mangulen…          375     372       99.2 2014-05-15         2014-05-26      
##  7 Mangulen…            7       4       57.1 2014-05-27         2014-05-27      
##  8 Old Scho…           24      23       95.8 2014-05-27         2014-05-27      
##  9 Mwanza             671     636       94.8 2014-05-28         2014-06-16      
## 10 Alumenda           226     226      100   2014-06-18         2014-06-27      
## # ℹ 102 more rows
## # ℹ 4 more variables: start_date_typical <chr>, start_date_long <chr>,
## #   start_date_messy <chr>, spraying_time <dbl>

‣ Lubridate has a technical distinction between “intervals”, “periods” and “durations”.

‣ You can find out more here: STA 444/5 - Introductory Data Science using R

3.6 Extracting Date Components

‣ During data cleaning or analysis, sometimes you need to extract a specific component of your date variable.

‣ {lubridate} package offers a set of useful functions for this.

‣ For example, to create a column with just the month of spraying, use the month() function.


irs %>% 
  mutate(month_start = month(start_date_default)) %>%
  select(village, start_date_default, month_start)
## # A tibble: 112 × 3
##    village           start_date_default month_start
##    <chr>             <date>                   <dbl>
##  1 Mess              2014-04-07                   4
##  2 Nkombedzi         2014-04-22                   4
##  3 B Compound        2014-05-13                   5
##  4 D Compound        2014-05-13                   5
##  5 Post Office       2014-05-13                   5
##  6 Mangulenje        2014-05-15                   5
##  7 Mangulenje Senior 2014-05-27                   5
##  8 Old School        2014-05-27                   5
##  9 Mwanza            2014-05-28                   5
## 10 Alumenda          2014-06-18                   6
## # ℹ 102 more rows

‣ The function returns the month as a number from 1-12.

‣ If you want R to display the month’s name, use label=TRUE argument.


irs %>% 
  mutate(month_start = month(start_date_default, label=T)) %>%
  select(village, start_date_default, month_start)
## # A tibble: 112 × 3
##    village           start_date_default month_start
##    <chr>             <date>             <ord>      
##  1 Mess              2014-04-07         Apr        
##  2 Nkombedzi         2014-04-22         Apr        
##  3 B Compound        2014-05-13         May        
##  4 D Compound        2014-05-13         May        
##  5 Post Office       2014-05-13         May        
##  6 Mangulenje        2014-05-15         May        
##  7 Mangulenje Senior 2014-05-27         May        
##  8 Old School        2014-05-27         May        
##  9 Mwanza            2014-05-28         May        
## 10 Alumenda          2014-06-18         Jun        
## # ℹ 102 more rows

‣ Similarly, to extract the year, use the year() function.

irs %>% 
  mutate(year_start = year(start_date_default)) %>%
  select(village, start_date_default, year_start)
## # A tibble: 112 × 3
##    village           start_date_default year_start
##    <chr>             <date>                  <dbl>
##  1 Mess              2014-04-07               2014
##  2 Nkombedzi         2014-04-22               2014
##  3 B Compound        2014-05-13               2014
##  4 D Compound        2014-05-13               2014
##  5 Post Office       2014-05-13               2014
##  6 Mangulenje        2014-05-15               2014
##  7 Mangulenje Senior 2014-05-27               2014
##  8 Old School        2014-05-27               2014
##  9 Mwanza            2014-05-28               2014
## 10 Alumenda          2014-06-18               2014
## # ℹ 102 more rows

Extracting weekdays

Create a new variable called wday_start and extract the day of the week that the spraying started in the same way as above but with the wday() function. Try to display the days of the week written out rather than numerically.

irs %>% 
  mutate(wday_start = wday(start_date_default, label = TRUE)) %>%
  select(village, start_date_default, wday_start)
## # A tibble: 112 × 3
##    village           start_date_default wday_start
##    <chr>             <date>             <ord>     
##  1 Mess              2014-04-07         Mon       
##  2 Nkombedzi         2014-04-22         Tue       
##  3 B Compound        2014-05-13         Tue       
##  4 D Compound        2014-05-13         Tue       
##  5 Post Office       2014-05-13         Tue       
##  6 Mangulenje        2014-05-15         Thu       
##  7 Mangulenje Senior 2014-05-27         Tue       
##  8 Old School        2014-05-27         Tue       
##  9 Mwanza            2014-05-28         Wed       
## 10 Alumenda          2014-06-18         Wed       
## # ℹ 102 more rows

3.7 Visualizing Date Components

‣ Often, you’ll extract specific date components for visualization.

‣ For instance, to visualize the months when spraying starts:

‣ First, create a new month variable using month().

‣ Then, plot a bar graph with geom_bar.

irs %>%
  mutate(month = month(start_date_default, label=T)) %>%
  # then pass to ggplot:
  ggplot() +
  geom_bar(aes(x= month))

‣ Most spraying campaigns began between July and November. No campaigns in the first three months of the year.

Visualizing spray end months

Using the irs dataset, create a new graph showing the months when the spraying campaign ended and compare it to the graph of when they started. Do they have a similar pattern?

irs %>%
  mutate(month_end = month(end_date_default, label=T)) %>%
  # then pass to ggplot:
  ggplot() +
  geom_bar(aes(x= month_end))

3.8 Rounding

‣ We often round dates up or down for analysis or visualization.

‣ Let’s see what we mean by rounding with a few examples.


‣ Consider the date: March 17th 2012.

‣ If we want to round down to the nearest month, we use the floor_date() function from {lubridate}.

‣ with unit="month".

my_date_down <- as.Date("2012-03-17")

floor_date(my_date_down, unit = "month")
## [1] "2012-03-01"

‣ As we observe, our date becomes March 1st, 2012.


‣ Now let’s round up.

‣ Consider the date: January 3rd 2020.

‣ To round up, we use the ceiling_date() function.

my_date_up <- as.Date("2020-01-03")

ceiling_date(my_date_up, unit = "month")
## [1] "2020-02-01"

‣ With ceiling_date(), January 3rd becomes February 1st.


‣ We can also round without specifying up or down.

‣ The dates automatically round to the nearest specified unit.

my_dates <- as.Date(c("2000-11-03", "2000-11-27"))

round_date(my_dates, unit = "month")
## [1] "2000-11-01" "2000-12-01"

‣ Here, by rounding to the nearest month:

‣ November 3rd becomes November 1st

‣ November 27th becomes December 1st.

Rounding dates practice

We can also round up or down to the nearest year. What do you think the output would be if we round down the date November 29th 2001 to the nearest year:

date_round <- as.Date("2001-11-29")

floor_date(date_round, unit="year")

‣ Let’s see how rounding can be useful!

‣ Consider our weather data.

weather
## # A tibble: 1,460 × 4
##    date       min_temp max_temp  rain
##    <date>        <dbl>    <dbl> <dbl>
##  1 2015-01-01     21.5     29.9  21.7
##  2 2015-01-02     19.6     30.4   2.2
##  3 2015-01-03     21.6     29.9  25.8
##  4 2015-01-04     20       29.5   1  
##  5 2015-01-05     20       32.2  53  
##  6 2015-01-06     21.8     31.1  60  
##  7 2015-01-07     21       28.7  44.4
##  8 2015-01-08     22       29.5  30  
##  9 2015-01-09     22       31     3  
## 10 2015-01-10     19.9     31.6   0  
## # ℹ 1,450 more rows

‣ The data you see is daily weather data.

‣ Daily data can be noisy due to day-to-day variation.

weather %>%
  # pass to ggplot
  ggplot() +
  geom_line(aes(x = date, y = rain))

‣ We want to look at seasonal patterns; monthly averages might be more suitable.

‣ How do we do this? Let’s try aggregating by month using the str_sub() function.

weather %>% 
  mutate(month_year = str_sub(date, 1, 7))
## # A tibble: 1,460 × 5
##    date       min_temp max_temp  rain month_year
##    <date>        <dbl>    <dbl> <dbl> <chr>     
##  1 2015-01-01     21.5     29.9  21.7 2015-01   
##  2 2015-01-02     19.6     30.4   2.2 2015-01   
##  3 2015-01-03     21.6     29.9  25.8 2015-01   
##  4 2015-01-04     20       29.5   1   2015-01   
##  5 2015-01-05     20       32.2  53   2015-01   
##  6 2015-01-06     21.8     31.1  60   2015-01   
##  7 2015-01-07     21       28.7  44.4 2015-01   
##  8 2015-01-08     22       29.5  30   2015-01   
##  9 2015-01-09     22       31     3   2015-01   
## 10 2015-01-10     19.9     31.6   0   2015-01   
## # ℹ 1,450 more rows

‣ Now, we’ll group by month_year and calculate the average rainfall.

weather_summary_1 <- weather %>% 
  mutate(month_year = str_sub(date, 1, 7)) %>%
  group_by(month_year) %>% 
  summarise(avg_rain = mean(rain))

‣ A problem arises! Our month_year is a character, not a date.

‣ That means it’s not continuous. Let’s try plotting:

weather_summary_1 %>% # pass to ggplot
  ggplot() +
  geom_line(aes(x = month_year, y = avg_rain))
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

‣ We need a different approach!

‣ Let’s round dates to the month using floor_date().

‣ This way, we get a true date variable for our grouping.

weather_summary_2 <- weather %>% 
  mutate(month_year= floor_date(date, unit = "months")) %>%
  # group by and summarise
  group_by(month_year) %>% 
  summarise(avg_rain = mean(rain))

‣ Now, let’s plot this newly aggregated data!

weather_summary_2 %>% # pass to ggplot
  ggplot() +
  geom_line(aes(x= month_year, y = avg_rain))

‣ That’s much better!

‣ Easier to see seasonal trends and yearly variations.

NOW TRY THIS FINAL PRACTICE QUESTION!

Plot avg monthly min and max temperatures

Using the weather data, create a new line graph plotting the average monthly minimum and maximum temperatures from 2015-2019.

weather %>% 
  mutate(month_year= floor_date(date, unit = "months")) %>%
  # group by and summarise
  group_by(month_year) %>% 
  summarise(avg_min = mean(min_temp),
            avg_max = mean(max_temp)) %>%
  
  ggplot() +
  geom_line(aes(x=month_year, y = avg_min), color = "blue") +
  geom_line(aes(x=month_year, y = avg_max), 
            color = "green")

Wrap Up!

This lesson covered fundamental skills for working with dates in R - calculating intervals, extracting components, rounding, and creating time series visualizations. With these key building blocks now mastered, you can can now start to wrangle date data to uncover and analyze patterns over time.

4 Learning Objectives

‣ You know how to calculate intervals between dates

‣ You know how to extract components from date columns

‣ You know how to round dates

‣ You are able to create simple time series graphs

Answer Key

Lubridate weeks

oct_31 <- as.Date("2023-10-31")
jul_20 <- as.Date("2023-07-20")
time_difference <- oct_31 %--% jul_20
time_difference/weeks(1)
## [1] -14.71429

Lubridate intervals

irs %>%
  mutate(spraying_time = interval(start_date_default, end_date_default)/days(1)) %>% 
  select(spraying_time)
## # A tibble: 112 × 1
##    spraying_time
##            <dbl>
##  1            10
##  2             5
##  3             0
##  4             0
##  5             0
##  6            11
##  7             0
##  8             0
##  9            19
## 10             9
## # ℹ 102 more rows

Extracting weekdays

irs %>%
  mutate(wday_start = wday(start_date_default, label = TRUE)) %>% 
  select(wday_start)
## # A tibble: 112 × 1
##    wday_start
##    <ord>     
##  1 Mon       
##  2 Tue       
##  3 Tue       
##  4 Tue       
##  5 Tue       
##  6 Thu       
##  7 Tue       
##  8 Tue       
##  9 Wed       
## 10 Wed       
## # ℹ 102 more rows

Visualizing spray end months

irs %>%
  mutate(month_end = month(end_date_default, label = TRUE)) %>% 
  ggplot(aes(x = month_end)) +
  geom_bar() 

Rounding dates practice

date_round <- as.Date("2001-11-29")
rounded_date <- floor_date(date_round, unit="year")
rounded_date
## [1] "2001-01-01"

Plot avg monthly min and max temperatures

weather %>% 
  mutate(month_year = floor_date(date, unit="month")) %>% 
  group_by(month_year) %>%
  summarise(avg_min_temp = mean(min_temp), 
            avg_max_temp = mean(max_temp)) %>% 
  ggplot() + 
  geom_line(aes(x = month_year, y = avg_min_temp), color = "blue") + 
  geom_line(aes(x = month_year, y = avg_max_temp), color = "red")

Contributors

The following team members contributed to this lesson:

LS0tDQp0aXRsZTogJ0RhdGVzIDI6IEludGVydmFscywgQ29tcG9uZW50cyBhbmQgUm91bmRpbmcnDQphdXRob3I6DQogIC0gbmFtZTogIktFTkUgREFWSUQgTldPU1UiDQogIC0gbmFtZTogIkFNQU5EQSBNQ0tJTkxFWSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY3NzOiAhZXhwciBoZXJlOjpoZXJlKCJnbG9iYWwvc3R5bGUvc3R5bGUuY3NzIikNCiAgICBoaWdobGlnaHQ6IGthdGUNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciwgZWNobyA9IEYsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCiMgTG9hZCBwYWNrYWdlcyANCmlmKCFyZXF1aXJlKHBhY21hbikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQpwYWNtYW46OnBfbG9hZChybGFuZywgdGlkeXZlcnNlLCBrbml0ciwgaGVyZSwgcmVhY3RhYmxlLCBndCwgZmxleHRhYmxlKQ0KDQojIyBmdW5jdGlvbnMNCnNvdXJjZShoZXJlOjpoZXJlKCJnbG9iYWwvZnVuY3Rpb25zL21pc2NfZnVuY3Rpb25zLlIiKSkNCg0KIyMgZGVmYXVsdCByZW5kZXINCnJlZ2lzdGVyUzNtZXRob2QoInJlYWN0YWJsZV81X3Jvd3MiLCAiZGF0YS5mcmFtZSIsIHJlYWN0YWJsZV81X3Jvd3MpDQprbml0cjo6b3B0c19jaHVuayRzZXQoY2xhc3Muc291cmNlID0gInRnYy1jb2RlLWJsb2NrIikNCmBgYA0KDQojIEludHJvDQoNCllvdSBub3cgaGF2ZSBhIHNvbGlkIHVuZGVyc3RhbmRpbmcgb2YgaG93IGRhdGVzIGFyZSBzdG9yZWQsIGRpc3BsYXllZCwgYW5kIGZvcm1hdHRlZCBpbiBSLiBJbiB0aGlzIGxlc3NvbiwgeW91IHdpbGwgbGVhcm4gaG93IHRvIHBlcmZvcm0gc2ltcGxlIGFuYWx5c2VzIHdpdGggZGF0ZXMsIHN1Y2ggYXMgY2FsY3VsYXRpbmcgdGhlIHRpbWUgYmV0d2VlbiBkYXRlIGludGVydmFscyBhbmQgY3JlYXRpbmcgdGltZSBzZXJpZXMgZ3JhcGhzISBUaGVzZSBza2lsbHMgYXJlIGNydWNpYWwgZm9yIGFueW9uZSB3b3JraW5nIHdpdGggaGVhbHRoIGRhdGEsIGFzIHRoZXkgYXJlIHRoZSBiYXNpcyB0byB1bmRlcnN0YW5kaW5nIHRlbXBvcmFsIHBhdHRlcm5zIHN1Y2ggYXMgdGhlIHByb2dyZXNzaW9uIG9mIGRpc2Vhc2VzIG92ZXIgdGltZSBhbmQgdGhlIGZsdWN0dWF0aW9uIGluIHBvcHVsYXRpb24gaGVhbHRoIG1ldHJpY3MgYWNyb3NzIGRpZmZlcmVudCBwZXJpb2RzLg0KDQojIExlYXJuaW5nIE9iamVjdGl2ZXMNCg0KLSAgIFlvdSBrbm93IGhvdyB0byBjYWxjdWxhdGUgaW50ZXJ2YWxzIGJldHdlZW4gZGF0ZXMNCg0KLSAgIFlvdSBrbm93IGhvdyB0byBleHRyYWN0IGNvbXBvbmVudHMgZnJvbSBkYXRlIGNvbHVtbnMNCg0KLSAgIFlvdSBrbm93IGhvdyB0byByb3VuZCBkYXRlcw0KDQotICAgWW91IGFyZSBhYmxlIHRvIGNyZWF0ZSBzaW1wbGUgdGltZSBzZXJpZXMgZ3JhcGhzDQoNCiMgUGFja2FnZXMNCg0KUGxlYXNlIGxvYWQgdGhlIHBhY2thZ2VzIG5lZWRlZCBmb3IgdGhpcyBsZXNzb24gd2l0aCB0aGUgY29kZSBiZWxvdzoNCg0KYGBge3Igd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGLCBlY2hvID0gVH0NCmlmKCFyZXF1aXJlKHBhY21hbikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQpwYWNtYW46OnBfbG9hZCh0aWR5dmVyc2UsIA0KICAgICAgICAgICAgICAgaGVyZSwNCiAgICAgICAgICAgICAgIGx1YnJpZGF0ZSkNCmBgYA0KDQojIyBEYXRhc2V0cw0KDQrigKMgV2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggKip0d28gZGF0YXNldHMqKiByZWxhdGVkIHRvICoqaW5kb29yIHJlc2lkdWFsIHNwcmF5aW5nIChJUlMpKiogZm9yICoqbWFsYXJpYSBjb250cm9sKiogZWZmb3J0cyBpbiBJbGxvdm8sIE1hbGF3aS4NCg0K4oCjIFRoZSBmaXJzdCBkYXRhc2V0IHByb3ZpZGVzIHRoZSAqKnN0YXJ0IGFuZCBlbmQgZGF0ZXMqKiBvZiBtb3NxdWl0byBzcHJheWluZyBjYW1wYWlnbnMgaW4gZGlmZmVyZW50IHZpbGxhZ2VzLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KaXJzIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvSWxsb3ZvX2RhdGEuY3N2IikpDQppcnMNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0K4oCjIFRoZSBzZWNvbmQgZGF0YXNldCBnaXZlcyBtb250aGx5IGRhdGEgZnJvbSAyMDE1LTIwMTkgY29tcGFyaW5nIHRoZSAqKmF2ZXJhZ2UgaW5jaWRlbmNlIG9mIG1hbGFyaWEgcGVyIDEwMDAgcGVvcGxlKiouDQoNCuKAoyBJdCBjb250cmFzdHMgdmlsbGFnZXMgdGhhdCByZWNlaXZlZCBJUlMgYWdhaW5zdCB2aWxsYWdlcyB0aGF0IGRpZG4ndC4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmluY2lkZW5jZV90ZW1wIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvSWxsb3ZvX2lyX3dlYXRoZXIuY3N2IikpDQppbmNpZGVuY2VfdGVtcA0KDQpuYW1lcyhpbmNpZGVuY2VfdGVtcCkNCmBgYA0KDQrigKMgQ29sdW1uczoNCg0K4oCjIGBpcl9jYXNlYDogTWFsYXJpYSBpbmNpZGVuY2UgaW4gSVJTIHZpbGxhZ2VzDQoNCuKAoyBgaXJfY29udHJvbGA6IE1hbGFyaWEgaW5jaWRlbmNlIGluIG5vbi1JUlMgdmlsbGFnZXMNCg0K4oCjIGBkYXRlYDogQ29udGFpbnMgdGhlIG1vbnRoIGFuZCByYW5kb20gZGF5DQoNCuKAoyBBdmVyYWdlIG1vbnRobHkgbWluaW11bSBhbmQgbWF4aW11bSB0ZW1wZXJhdHVyZXMgKGBhdmdfbWluYCBhbmQgYGF2Z19tYXhgKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0K4oCjIFRoZSBmaW5hbCBkYXRhc2V0IGhhcyAqKjEsNDYwIHJvd3MqKiBvZiBkYWlseSB3ZWF0aGVyIGRhdGEgZm9yIHRoZSBzYW1lIElsbG92byByZWdpb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp3ZWF0aGVyIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvSWxsb3ZvX3dlYXRoZXIuY3N2IikpDQp3ZWF0aGVyDQpgYGANCg0K4oCjIEVhY2ggcm93IHNpZ25pZmllcyBhIHNpbmdsZSBkYXkgYW5kIG9mZmVycyBtZWFzdXJlbWVudHMgb2Y6DQoNCuKAoyBNaW5pbXVtIHRlbXBlcmF0dXJlIChgbWluX3RlbXBgKSBpbiBDZWxzaXVzDQoNCuKAoyBNYXhpbXVtIHRlbXBlcmF0dXJlIChgbWF4X3RlbXBgKSBpbiBDZWxzaXVzDQoNCuKAoyBSYWluZmFsbCAoYHJhaW5gKSBpbiBtaWxsaW1ldGVycw0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgQ2FsY3VsYXRpbmcgRGF0ZSBJbnRlcnZhbHMNCg0K4oCjIFRvIGJlZ2luLCB3ZSdsbCBleHBsb3JlICoqdHdvIHdheXMgdG8gY2FsY3VsYXRlIGludGVydmFscyoqLg0KDQrigKMgVGhlIGZpcnN0IHVzZXMgdGhlICItIiBvcGVyYXRvciBpbiBiYXNlIFIuDQoNCuKAoyBUaGUgc2Vjb25kIHV0aWxpemVzIHRoZSBpbnRlcnZhbCBvcGVyYXRvciBmcm9tIHRoZSB7bHVicmlkYXRlfSBwYWNrYWdlLg0KDQrigKMgTGV0J3MgZXhhbWluZSBib3RoIG1ldGhvZHMgYW5kIHNlZSBob3cgdGhleSBkaWZmZXIuDQoNCiMjIFVzaW5nIHRoZSAiLSIgb3BlcmF0b3INCg0K4oCjIFRoaXMgYXBwcm9hY2ggY2FsY3VsYXRlcyB0aW1lIGRpZmZlcmVuY2VzIGJ5IHNpbXBseSAqKnN1YnRyYWN0aW5nIG9uZSBkYXRlIGZyb20gYW5vdGhlcioqLg0KDQrigKMgTGV0J3MgY3JhZnQgdHdvIGRhdGUgdmFyaWFibGVzIGFuZCB0ZXN0IHRoaXMgb3V0IQ0KDQpgYGB7cn0NCmRhdGVfMSA8LSBhcy5EYXRlKCIyMDIwLTAxLTAxIikgIyBKYW51YXJ5IDFzdCwgMjAwMA0KZGF0ZV8yIDwtIGFzLkRhdGUoIjIwMjAtMDEtMzEiKSAjIEphbnVhcnkgMzFzdCwgMjAwMA0KIyBzdWJ0cmFjdCB0aGUgZGF0ZXMNCmRhdGVfMiAtIGRhdGVfMQ0KYGBgDQoNCuKAoyBBbmQgdGhlcmUgd2UgaGF2ZSBpdCEgUiBkaXNwbGF5cyB0aGUgdGltZSBkaWZmZXJlbmNlIGluIGRheXMuDQoNCiMjIFVzaW5nIHRoZSBpbnRlcnZhbCBvcGVyYXRvciBmcm9tIHtsdWJyaWRhdGV9DQoNCuKAoyBMZXQncyBzZWUgYSAqKnNlY29uZCB3YXkqKiB0byBjYWxjdWxhdGUgdGltZSBpbnRlcnZhbHMNCg0K4oCjIFdlJ2xsIHVzZSB0aGUgYCUtLSVgIG9wZXJhdG9yIGZyb20gdGhlIHtsdWJyaWRhdGV9IHBhY2thZ2UuDQoNCuKAoyBUaGlzIG9wZXJhdG9yIGlzIHNvbWV0aW1lcyBjYWxsZWQgdGhlICoqaW50ZXJ2YWwgb3BlcmF0b3IqKi4NCg0KYGBge3J9DQojIHVzZSB0aGUgaW50ZXJ2YWwgb3BlcmF0b3I6DQpkYXRlXzIgJS0tJSBkYXRlXzENCmBgYA0KDQrigKMgVGhlIG91dHB1dCBzaG93cyBhbiBpbnRlcnZhbCBiZXR3ZWVuIHR3byBkYXRlcy4NCg0K4oCjIEJ1dCB3aGF0IGlmIHdlIHdhbnQgdG8ga25vdyAqKmhvdyBsb25nIGhhcyBwYXNzZWQqKiBpbiBkYXlzPw0KDQrigKMgRm9yIHRoaXMsIHdlIG5lZWQgdG8gdXNlIHRoZSBgZGF5cygpYCBmdW5jdGlvbi4NCg0K4oCjIERpdmlkaW5nIGJ5IGBkYXlzKDEpYCB3aWxsIHRlbGwgbHVicmlkYXRlIHRvIGNvdW50IGluIGluY3JlbWVudHMgb2YgKipvbmUgZGF5KiogYXQgYSB0aW1lLg0KDQpgYGB7cn0NCiMgZGl2aWRlIHRoZSBpbnRlcnZhbCBieSBkYXlzKDEpOg0KZGF0ZV8xICUtLSUgZGF0ZV8yL2RheXMoMSkNCmBgYA0KDQrigKMgTGVhdmluZyB0aGUgcGFyZW50aGVzZXMgZW1wdHksIGkuZS4sIGBkYXlzKClgLCB3b3VsZCBhbHNvIHdvcmsuIFRoaXMgaXMgYmVjYXVzZSBsdWJyaWRhdGUncyAqKmRlZmF1bHQqKiBpcyB0byBjb3VudCBpbiBpbmNyZW1lbnRzIG9mIDEuDQoNCuKAoyBCdXQgbGV0J3Mgc2F5IHdlIHdhbnQgdG8gY291bnQgaW4gaW5jcmVtZW50cyBvZiA1IGRheXMuDQoNCuKAoyBXZSdkIHNwZWNpZnkgYGRheXMoNSlgDQoNCmBgYHtyfQ0KIyBkaXZpZGUgdGhlIGludGVydmFsIGJ5IGRheXMoNSkNCmRhdGVfMSAlLS0lIGRhdGVfMi9kYXlzKDUpDQpgYGANCg0KOjo6IHItcHJhY3RpY2UNCioqTHVicmlkYXRlIHdlZWtzKioNCg0KVXNlIHRoZSBgd2Vla3MoKWAgZnVuY3Rpb24gaW4gcGxhY2Ugb2YgYGRheXMoKWAgaW4gdGhlIGx1YnJpZGF0ZSBtZXRob2QgdG8gY2FsY3VsYXRlIHRoZSB0aW1lIGRpZmZlcmVuY2UgaW4gd2Vla3MgYmV0d2VlbiB0aGUgdHdvIGRhdGVzIGJlbG93Og0KDQpgYGB7ciBldmFsPSBGQUxTRX0NCm9jdF8zMSA8LSBhcy5EYXRlKCIyMDIzLTEwLTMxIikNCmp1bF8yMCA8LSBhcy5EYXRlKCIyMDIzLTA3LTIwIikNCg0KanVsXzIwICUtLSUgb2N0XzMxL3dlZWtzKCkNCmBgYA0KOjo6DQoNCiMjIENvbXBhcmlzb24NCg0K4oCjIFNvIHdoaWNoIG9mIHRoZSBtZXRob2RzIGlzIGJlc3Q/DQoNCuKAoyBMdWJyaWRhdGUgcHJvdmlkZXMgKiptb3JlIGZsZXhpYmlsaXR5IGFuZCBhY2N1cmFjeSoqIHdoZW4gd29ya2luZyB3aXRoIGRhdGVzIGluIFIuDQoNCuKAoyBMZXQncyBsb29rIGF0IGEgc2ltcGxlIGV4YW1wbGUgdG8gc2VlIHdoeS4NCg0K4oCjIEZpcnN0LCB3ZSdsbCBzZXQgdHdvIGRhdGVzIHRoYXQgYXJlICoqNiB5ZWFycyBhcGFydCoqOg0KDQpgYGB7cn0NCmRhdGVfMSA8LSBhcy5EYXRlKCIyMDAwLTAxLTAxIikgIyBKYW51YXJ5IDFzdCwgMjAwMA0KZGF0ZV8yIDwtIGFzLkRhdGUoIjIwMDYtMDEtMDEiKSAjIEphbnVhcnkgMXN0LCAyMDA2DQpgYGANCg0K4oCjIEhvdyB0byBjYWxjdWxhdGUgdGhlIHllYXJzIHBhc3NlZCBiZXR3ZWVuIHRoZXNlIGRhdGVzIGluICoqYmFzZSBSKio/DQoNCuKAoyBTdWJ0cmFjdCB0aGUgdHdvIGRhdGVzLCBgZGF0ZV8yIC0gZGF0ZV8xYA0KDQrigKMgVGhlbiwgZGl2aWRlIGJ5IGFuIGF2ZXJhZ2UgZGF5IGNvdW50LCBsaWtlICoqMzY1LjI1KiogKGFjY291bnRpbmcgZm9yIGxlYXAgeWVhcnMpDQoNCmBgYHtyfQ0KYXMubnVtZXJpYygoZGF0ZV8yIC0gZGF0ZV8xKS8zNjUuMjUpICMgY29tcGxldGUgdGhlIGNvZGUNCmBgYA0KDQrigKMgUmVzdWx0IGlzIGNsb3NlIHRvIDYgYnV0IGltcHJlY2lzZSBkdWUgdG8gdGhlIGF2ZXJhZ2luZyBvZiBsZWFwIHllYXJzIQ0KDQrigKMgKENhbiByZW1vdmUgImRheXMiIGJ5IGNvbnZlcnRpbmcgdG8gbnVtZXJpYykNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCuKAoyBEaXZpZGluZyBieSAzNjUgb3IgMzY2IHdpbGwgYWxzbyBnaXZlICoqaW1wcmVjaXNlIHJlc3VsdHMqKjoNCg0KYGBge3J9DQojIGRpdmlkZSBieSAzNjUNCihkYXRlXzIgLSBkYXRlXzEpLzM2NSANCiMgZGl2aWRlIGJ5IDM2Ng0KKGRhdGVfMiAtIGRhdGVfMSkvMzY2DQpgYGANCg0K4oCjIE5lZWQgdG8gYWNjb3VudCBmb3IgKip0d28gbGVhcCB5ZWFycyoqICh0d28gZXh0cmEgZGF5cykgYmV0d2VlbiB0aGUgZGF0ZXMNCg0K4oCjIFN1YnRyYWN0IHRob3NlIHR3byBkYXlzIG91dCBmaXJzdDoNCg0KYGBge3J9DQojIGZpbGwgaW4NCmFzLm51bWVyaWMoKGRhdGVfMiAtIGRhdGVfMSkvMzY1KQ0KYGBgDQoNCuKAoyBQYWluZnVsIGZvciByZWFsIGRhdGEhDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQrigKMgV2l0aCAqKmx1YnJpZGF0ZSBpbnRlcnZhbHMqKiwgcHJvY2VzcyBpcyBtb3JlIHN0cmFpZ2h0Zm9yd2FyZDoNCg0K4oCjIExlYXAgeWVhcnMgYXJlIGhhbmRsZWQgZm9yIHlvdQ0KDQpgYGB7cn0NCmRhdGVfMSAlLS0lZGF0ZV8yIC9kYXlzKDM2NSkgIyBpbnRlcnZhbCBkaXZpZGVkIGJ5IHllYXJzKCkNCmBgYA0KDQrigKMgU21hbGwgZGlmZmVyZW5jZSwgYnV0ICoqbHVicmlkYXRlIGlzIHRoZSB3aW5uZXIqKiBoZXJlLg0KDQrigKMgQWxzbyBiZXR0ZXIgYXQgaGFuZGxpbmcgZGF5bGlnaHQgc2F2aW5ncyB3aXRoIGRhdGUtdGltZXMuDQoNCjo6OiByLXByYWN0aWNlDQoqKkx1YnJpZGF0ZSBpbnRlcnZhbHMqKg0KDQpDYW4geW91IGFwcGx5IGx1YnJpZGF0ZSdzIGludGVydmFsIGZ1bmN0aW9uIHRvIG91ciBJUlMgZGF0YXNldD8gQ3JlYXRlIGEgbmV3IGNvbHVtbiBjYWxsZWQgYHNwcmF5aW5nX3RpbWVgIGFuZCB1c2luZyBsdWJyaWRhdGVzIGAlLS0lYCBvcGVyYXRvciwgY2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgZGF5cyBiZXR3ZWVuIGBzdGFydF9kYXRlX2RlZmF1bHRgIGFuZCBgZW5kX2RhdGVfZGVmYXVsdGAuDQoNCmBgYHtyfQ0KaXJzICU+JSANCiAgbXV0YXRlKHNwcmF5aW5nX3RpbWUgPQ0KICAgICAgICAgICAoc3RhcnRfZGF0ZV9kZWZhdWx0ICUtLSUgZW5kX2RhdGVfZGVmYXVsdCkvZGF5cygpKQ0KYGBgDQoNCjo6Og0KDQrigKMgTHVicmlkYXRlIGhhcyBhIHRlY2huaWNhbCBkaXN0aW5jdGlvbiBiZXR3ZWVuICJpbnRlcnZhbHMiLCAicGVyaW9kcyIgYW5kICJkdXJhdGlvbnMiLg0KDQrigKMgWW91IGNhbiBmaW5kIG91dCBtb3JlIGhlcmU6IDxhIGhyZWY9Imh0dHBzOi8vYm9va2Rvd24ub3JnL2RlcmVrc29uZGVyZWdnZXIvNDQ0L2RhdGVzLWFuZC10aW1lcy5odG1sIiB0YXJnZXQ9Il9ibGFuayI+U1RBIDQ0NC81IC0gSW50cm9kdWN0b3J5IERhdGEgU2NpZW5jZSB1c2luZyBSPC9hPg0KIA0KDQojIyBFeHRyYWN0aW5nIERhdGUgQ29tcG9uZW50cw0KDQrigKMgRHVyaW5nIGRhdGEgY2xlYW5pbmcgb3IgYW5hbHlzaXMsIHNvbWV0aW1lcyB5b3UgbmVlZCB0byAqKmV4dHJhY3QgYSBzcGVjaWZpYyBjb21wb25lbnQqKiBvZiB5b3VyIGRhdGUgdmFyaWFibGUuDQoNCuKAoyB7bHVicmlkYXRlfSBwYWNrYWdlIG9mZmVycyBhIHNldCBvZiAqKnVzZWZ1bCBmdW5jdGlvbnMqKiBmb3IgdGhpcy4NCg0K4oCjIEZvciBleGFtcGxlLCB0byBjcmVhdGUgYSBjb2x1bW4gd2l0aCBqdXN0IHRoZSBtb250aCBvZiBzcHJheWluZywgdXNlIHRoZSBgbW9udGgoKWAgZnVuY3Rpb24uDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpgYGB7cn0NCmlycyAlPiUgDQogIG11dGF0ZShtb250aF9zdGFydCA9IG1vbnRoKHN0YXJ0X2RhdGVfZGVmYXVsdCkpICU+JQ0KICBzZWxlY3QodmlsbGFnZSwgc3RhcnRfZGF0ZV9kZWZhdWx0LCBtb250aF9zdGFydCkNCmBgYA0KDQrigKMgVGhlIGZ1bmN0aW9uIHJldHVybnMgdGhlIG1vbnRoIGFzIGEgKipudW1iZXIgZnJvbSAxLTEyKiouDQoNCuKAoyBJZiB5b3Ugd2FudCBSIHRvIGRpc3BsYXkgdGhlIG1vbnRoJ3MgbmFtZSwgdXNlIGBsYWJlbD1UUlVFYCBhcmd1bWVudC4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCmBgYHtyfQ0KaXJzICU+JSANCiAgbXV0YXRlKG1vbnRoX3N0YXJ0ID0gbW9udGgoc3RhcnRfZGF0ZV9kZWZhdWx0LCBsYWJlbD1UKSkgJT4lDQogIHNlbGVjdCh2aWxsYWdlLCBzdGFydF9kYXRlX2RlZmF1bHQsIG1vbnRoX3N0YXJ0KQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQrigKMgU2ltaWxhcmx5LCB0byBleHRyYWN0IHRoZSAqKnllYXIqKiwgdXNlIHRoZSBgeWVhcigpYCBmdW5jdGlvbi4NCg0KYGBge3J9DQppcnMgJT4lIA0KICBtdXRhdGUoeWVhcl9zdGFydCA9IHllYXIoc3RhcnRfZGF0ZV9kZWZhdWx0KSkgJT4lDQogIHNlbGVjdCh2aWxsYWdlLCBzdGFydF9kYXRlX2RlZmF1bHQsIHllYXJfc3RhcnQpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCjo6OiByLXByYWN0aWNlDQoqKkV4dHJhY3Rpbmcgd2Vla2RheXMqKg0KDQpDcmVhdGUgYSBuZXcgdmFyaWFibGUgY2FsbGVkIGB3ZGF5X3N0YXJ0YCBhbmQgZXh0cmFjdCB0aGUgZGF5IG9mIHRoZSB3ZWVrIHRoYXQgdGhlIHNwcmF5aW5nIHN0YXJ0ZWQgaW4gdGhlIHNhbWUgd2F5IGFzIGFib3ZlIGJ1dCB3aXRoIHRoZSBgd2RheSgpYCBmdW5jdGlvbi4gVHJ5IHRvIGRpc3BsYXkgdGhlIGRheXMgb2YgdGhlIHdlZWsgd3JpdHRlbiBvdXQgcmF0aGVyIHRoYW4gbnVtZXJpY2FsbHkuDQoNCmBgYHtyfQ0KaXJzICU+JSANCiAgbXV0YXRlKHdkYXlfc3RhcnQgPSB3ZGF5KHN0YXJ0X2RhdGVfZGVmYXVsdCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIHNlbGVjdCh2aWxsYWdlLCBzdGFydF9kYXRlX2RlZmF1bHQsIHdkYXlfc3RhcnQpDQpgYGANCg0KOjo6DQoNCiMjIFZpc3VhbGl6aW5nIERhdGUgQ29tcG9uZW50cw0KDQrigKMgT2Z0ZW4sIHlvdSdsbCBleHRyYWN0IHNwZWNpZmljIGRhdGUgY29tcG9uZW50cyBmb3IgKip2aXN1YWxpemF0aW9uKiouDQoNCuKAoyBGb3IgaW5zdGFuY2UsIHRvIHZpc3VhbGl6ZSB0aGUgKiptb250aHMgd2hlbiBzcHJheWluZyBzdGFydHMqKjoNCg0K4oCjIEZpcnN0LCBjcmVhdGUgYSBuZXcgbW9udGggdmFyaWFibGUgdXNpbmcgYG1vbnRoKClgLg0KDQrigKMgVGhlbiwgcGxvdCBhIGJhciBncmFwaCB3aXRoIGBnZW9tX2JhcmAuDQoNCmBgYHtyfQ0KaXJzICU+JQ0KICBtdXRhdGUobW9udGggPSBtb250aChzdGFydF9kYXRlX2RlZmF1bHQsIGxhYmVsPVQpKSAlPiUNCiAgIyB0aGVuIHBhc3MgdG8gZ2dwbG90Og0KICBnZ3Bsb3QoKSArDQogIGdlb21fYmFyKGFlcyh4PSBtb250aCkpDQpgYGANCg0K4oCjIE1vc3Qgc3ByYXlpbmcgY2FtcGFpZ25zIGJlZ2FuIGJldHdlZW4gKipKdWx5IGFuZCBOb3ZlbWJlcioqLiBObyBjYW1wYWlnbnMgaW4gdGhlICoqZmlyc3QgdGhyZWUgbW9udGhzKiogb2YgdGhlIHllYXIuDQoNCjo6OiByLXByYWN0aWNlDQoqKlZpc3VhbGl6aW5nIHNwcmF5IGVuZCBtb250aHMqKg0KDQpVc2luZyB0aGUgYGlyc2AgZGF0YXNldCwgY3JlYXRlIGEgbmV3IGdyYXBoIHNob3dpbmcgdGhlIG1vbnRocyB3aGVuIHRoZSBzcHJheWluZyBjYW1wYWlnbiBlbmRlZCBhbmQgY29tcGFyZSBpdCB0byB0aGUgZ3JhcGggb2Ygd2hlbiB0aGV5IHN0YXJ0ZWQuIERvIHRoZXkgaGF2ZSBhIHNpbWlsYXIgcGF0dGVybj8NCg0KYGBge3J9DQppcnMgJT4lDQogIG11dGF0ZShtb250aF9lbmQgPSBtb250aChlbmRfZGF0ZV9kZWZhdWx0LCBsYWJlbD1UKSkgJT4lDQogICMgdGhlbiBwYXNzIHRvIGdncGxvdDoNCiAgZ2dwbG90KCkgKw0KICBnZW9tX2JhcihhZXMoeD0gbW9udGhfZW5kKSkNCmBgYA0KDQo6OjoNCiMjIFJvdW5kaW5nDQoNCuKAoyBXZSBvZnRlbiByb3VuZCBkYXRlcyB1cCBvciBkb3duIGZvciAqKmFuYWx5c2lzKiogb3IgKip2aXN1YWxpemF0aW9uKiouDQoNCuKAoyBMZXQncyBzZWUgd2hhdCB3ZSBtZWFuIGJ5ICoqcm91bmRpbmcqKiB3aXRoIGEgZmV3IGV4YW1wbGVzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0K4oCjIENvbnNpZGVyIHRoZSBkYXRlOiAqKk1hcmNoIDE3dGggMjAxMioqLg0KDQrigKMgSWYgd2Ugd2FudCB0byByb3VuZCAqKmRvd24gdG8gdGhlIG5lYXJlc3QgbW9udGgqKiwgd2UgdXNlIHRoZSBgZmxvb3JfZGF0ZSgpYCBmdW5jdGlvbiBmcm9tIGB7bHVicmlkYXRlfWAuDQoNCuKAoyB3aXRoIGB1bml0PSJtb250aCJgLg0KDQpgYGB7cn0NCm15X2RhdGVfZG93biA8LSBhcy5EYXRlKCIyMDEyLTAzLTE3IikNCg0KZmxvb3JfZGF0ZShteV9kYXRlX2Rvd24sIHVuaXQgPSAibW9udGgiKQ0KYGBgDQoNCuKAoyBBcyB3ZSBvYnNlcnZlLCBvdXIgZGF0ZSBiZWNvbWVzICoqTWFyY2ggMXN0LCAyMDEyKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQrigKMgTm93IGxldCdzIHJvdW5kICoqdXAqKi4NCg0K4oCjIENvbnNpZGVyIHRoZSBkYXRlOiAqKkphbnVhcnkgM3JkIDIwMjAqKi4NCg0K4oCjIFRvIHJvdW5kIHVwLCB3ZSB1c2UgdGhlIGBjZWlsaW5nX2RhdGUoKWAgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KbXlfZGF0ZV91cCA8LSBhcy5EYXRlKCIyMDIwLTAxLTAzIikNCg0KY2VpbGluZ19kYXRlKG15X2RhdGVfdXAsIHVuaXQgPSAibW9udGgiKQ0KYGBgDQoNCuKAoyBXaXRoIGBjZWlsaW5nX2RhdGUoKWAsIEphbnVhcnkgM3JkIGJlY29tZXMgKipGZWJydWFyeSAxc3QqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCuKAoyBXZSBjYW4gYWxzbyByb3VuZCAqKndpdGhvdXQgc3BlY2lmeWluZyB1cCBvciBkb3duKiouDQoNCuKAoyBUaGUgZGF0ZXMgYXV0b21hdGljYWxseSByb3VuZCB0byB0aGUgKipuZWFyZXN0IHNwZWNpZmllZCB1bml0KiouDQoNCmBgYHtyfQ0KbXlfZGF0ZXMgPC0gYXMuRGF0ZShjKCIyMDAwLTExLTAzIiwgIjIwMDAtMTEtMjciKSkNCg0Kcm91bmRfZGF0ZShteV9kYXRlcywgdW5pdCA9ICJtb250aCIpDQpgYGANCg0K4oCjIEhlcmUsIGJ5IHJvdW5kaW5nIHRvIHRoZSBuZWFyZXN0IG1vbnRoOg0KDQrigKMgTm92ZW1iZXIgM3JkIGJlY29tZXMgKipOb3ZlbWJlciAxc3QqKg0KDQrigKMgTm92ZW1iZXIgMjd0aCBiZWNvbWVzICoqRGVjZW1iZXIgMXN0KiouDQoNCjo6OiByLXByYWN0aWNlDQoqKlJvdW5kaW5nIGRhdGVzIHByYWN0aWNlKioNCg0KV2UgY2FuIGFsc28gcm91bmQgdXAgb3IgZG93biB0byB0aGUgbmVhcmVzdCB5ZWFyLiBXaGF0IGRvIHlvdSB0aGluayB0aGUgb3V0cHV0IHdvdWxkIGJlIGlmIHdlIHJvdW5kIGRvd24gdGhlIGRhdGUgTm92ZW1iZXIgMjl0aCAyMDAxIHRvIHRoZSBuZWFyZXN0IHllYXI6DQoNCmBgYHtyICBldmFsPUZBTFNFfQ0KZGF0ZV9yb3VuZCA8LSBhcy5EYXRlKCIyMDAxLTExLTI5IikNCg0KZmxvb3JfZGF0ZShkYXRlX3JvdW5kLCB1bml0PSJ5ZWFyIikNCmBgYA0KOjo6DQoNCuKAoyBMZXQncyBzZWUgaG93IHJvdW5kaW5nIGNhbiBiZSB1c2VmdWwhDQoNCuKAoyBDb25zaWRlciBvdXIgKip3ZWF0aGVyIGRhdGEqKi4NCg0KYGBge3J9DQp3ZWF0aGVyDQpgYGANCg0K4oCjIFRoZSBkYXRhIHlvdSBzZWUgaXMgKipkYWlseSB3ZWF0aGVyIGRhdGEqKi4NCg0K4oCjIERhaWx5IGRhdGEgY2FuIGJlICoqbm9pc3kqKiBkdWUgdG8gZGF5LXRvLWRheSB2YXJpYXRpb24uDQoNCmBgYHtyfQ0Kd2VhdGhlciAlPiUNCiAgIyBwYXNzIHRvIGdncGxvdA0KICBnZ3Bsb3QoKSArDQogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSByYWluKSkNCmBgYA0KDQrigKMgV2Ugd2FudCB0byBsb29rIGF0ICoqc2Vhc29uYWwgcGF0dGVybnMqKjsgbW9udGhseSBhdmVyYWdlcyBtaWdodCBiZSBtb3JlIHN1aXRhYmxlLg0KDQrigKMgSG93IGRvIHdlIGRvIHRoaXM/IExldCdzIHRyeSBhZ2dyZWdhdGluZyBieSBtb250aCB1c2luZyB0aGUgYHN0cl9zdWIoKWAgZnVuY3Rpb24uDQoNCmBgYHtyfQ0Kd2VhdGhlciAlPiUgDQogIG11dGF0ZShtb250aF95ZWFyID0gc3RyX3N1YihkYXRlLCAxLCA3KSkNCmBgYA0KDQrigKMgTm93LCB3ZSdsbCBncm91cCBieSBgbW9udGhfeWVhcmAgYW5kIGNhbGN1bGF0ZSB0aGUgKiphdmVyYWdlIHJhaW5mYWxsKiouDQoNCmBgYHtyfQ0Kd2VhdGhlcl9zdW1tYXJ5XzEgPC0gd2VhdGhlciAlPiUgDQogIG11dGF0ZShtb250aF95ZWFyID0gc3RyX3N1YihkYXRlLCAxLCA3KSkgJT4lDQogIGdyb3VwX2J5KG1vbnRoX3llYXIpICU+JSANCiAgc3VtbWFyaXNlKGF2Z19yYWluID0gbWVhbihyYWluKSkNCmBgYA0KDQrigKMgQSBwcm9ibGVtIGFyaXNlcyEgT3VyIGBtb250aF95ZWFyYCBpcyBhIGNoYXJhY3Rlciwgbm90IGEgZGF0ZS4NCg0K4oCjIFRoYXQgbWVhbnMgaXQncyBub3QgY29udGludW91cy4gTGV0J3MgdHJ5IHBsb3R0aW5nOg0KDQpgYGB7cn0NCndlYXRoZXJfc3VtbWFyeV8xICU+JSAjIHBhc3MgdG8gZ2dwbG90DQogIGdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gbW9udGhfeWVhciwgeSA9IGF2Z19yYWluKSkNCiAgDQpgYGANCg0K4oCjIFdlIG5lZWQgYSBkaWZmZXJlbnQgYXBwcm9hY2ghDQoNCuKAoyBMZXQncyByb3VuZCBkYXRlcyB0byB0aGUgbW9udGggdXNpbmcgYGZsb29yX2RhdGUoKWAuDQoNCuKAoyBUaGlzIHdheSwgd2UgZ2V0IGEgdHJ1ZSBkYXRlIHZhcmlhYmxlIGZvciBvdXIgZ3JvdXBpbmcuDQoNCmBgYHtyfQ0Kd2VhdGhlcl9zdW1tYXJ5XzIgPC0gd2VhdGhlciAlPiUgDQogIG11dGF0ZShtb250aF95ZWFyPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQgPSAibW9udGhzIikpICU+JQ0KICAjIGdyb3VwIGJ5IGFuZCBzdW1tYXJpc2UNCiAgZ3JvdXBfYnkobW9udGhfeWVhcikgJT4lIA0KICBzdW1tYXJpc2UoYXZnX3JhaW4gPSBtZWFuKHJhaW4pKQ0KYGBgDQoNCuKAoyBOb3csIGxldCdzIHBsb3QgdGhpcyBuZXdseSBhZ2dyZWdhdGVkIGRhdGEhDQoNCmBgYHtyfQ0Kd2VhdGhlcl9zdW1tYXJ5XzIgJT4lICMgcGFzcyB0byBnZ3Bsb3QNCiAgZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoYWVzKHg9IG1vbnRoX3llYXIsIHkgPSBhdmdfcmFpbikpDQpgYGANCg0K4oCjIFRoYXQncyBtdWNoIGJldHRlciENCg0K4oCjIEVhc2llciB0byBzZWUgKipzZWFzb25hbCB0cmVuZHMqKiBhbmQgKip5ZWFybHkgdmFyaWF0aW9ucyoqLg0KDQoqKk5PVyBUUlkgVEhJUyBGSU5BTCBQUkFDVElDRSBRVUVTVElPTiEqKg0KDQo6Ojogci1wcmFjdGljZQ0KKipQbG90IGF2ZyBtb250aGx5IG1pbiBhbmQgbWF4IHRlbXBlcmF0dXJlcyoqDQoNClVzaW5nIHRoZSB3ZWF0aGVyIGRhdGEsIGNyZWF0ZSBhIG5ldyBsaW5lIGdyYXBoIHBsb3R0aW5nIHRoZSBhdmVyYWdlIG1vbnRobHkgbWluaW11bSBhbmQgbWF4aW11bSB0ZW1wZXJhdHVyZXMgZnJvbSAyMDE1LTIwMTkuDQoNCmBgYHtyfQ0Kd2VhdGhlciAlPiUgDQogIG11dGF0ZShtb250aF95ZWFyPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQgPSAibW9udGhzIikpICU+JQ0KICAjIGdyb3VwIGJ5IGFuZCBzdW1tYXJpc2UNCiAgZ3JvdXBfYnkobW9udGhfeWVhcikgJT4lIA0KICBzdW1tYXJpc2UoYXZnX21pbiA9IG1lYW4obWluX3RlbXApLA0KICAgICAgICAgICAgYXZnX21heCA9IG1lYW4obWF4X3RlbXApKSAlPiUNCiAgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGFlcyh4PW1vbnRoX3llYXIsIHkgPSBhdmdfbWluKSwgY29sb3IgPSAiYmx1ZSIpICsNCiAgZ2VvbV9saW5lKGFlcyh4PW1vbnRoX3llYXIsIHkgPSBhdmdfbWF4KSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmVlbiIpDQpgYGANCjo6Og0KDQojICB7LnVubnVtYmVyZWR9DQoNCiMgV3JhcCBVcCEgey51bm51bWJlcmVkfQ0KDQpUaGlzIGxlc3NvbiBjb3ZlcmVkIGZ1bmRhbWVudGFsIHNraWxscyBmb3Igd29ya2luZyB3aXRoIGRhdGVzIGluIFIgLSBjYWxjdWxhdGluZyBpbnRlcnZhbHMsIGV4dHJhY3RpbmcgY29tcG9uZW50cywgcm91bmRpbmcsIGFuZCBjcmVhdGluZyB0aW1lIHNlcmllcyB2aXN1YWxpemF0aW9ucy4gV2l0aCB0aGVzZSBrZXkgYnVpbGRpbmcgYmxvY2tzIG5vdyBtYXN0ZXJlZCwgeW91IGNhbiBjYW4gbm93IHN0YXJ0IHRvIHdyYW5nbGUgZGF0ZSBkYXRhIHRvIHVuY292ZXIgYW5kIGFuYWx5emUgcGF0dGVybnMgb3ZlciB0aW1lLg0KDQojIExlYXJuaW5nIE9iamVjdGl2ZXMNCg0K4oCjIFlvdSBrbm93IGhvdyB0byBjYWxjdWxhdGUgaW50ZXJ2YWxzIGJldHdlZW4gZGF0ZXMNCg0K4oCjIFlvdSBrbm93IGhvdyB0byBleHRyYWN0IGNvbXBvbmVudHMgZnJvbSBkYXRlIGNvbHVtbnMNCg0K4oCjIFlvdSBrbm93IGhvdyB0byByb3VuZCBkYXRlcw0KDQrigKMgWW91IGFyZSBhYmxlIHRvIGNyZWF0ZSBzaW1wbGUgdGltZSBzZXJpZXMgZ3JhcGhzDQoNCiMgQW5zd2VyIEtleSB7LnVubnVtYmVyZWR9DQoNCioqTHVicmlkYXRlIHdlZWtzKioNCg0KYGBge3J9DQpvY3RfMzEgPC0gYXMuRGF0ZSgiMjAyMy0xMC0zMSIpDQpqdWxfMjAgPC0gYXMuRGF0ZSgiMjAyMy0wNy0yMCIpDQp0aW1lX2RpZmZlcmVuY2UgPC0gb2N0XzMxICUtLSUganVsXzIwDQp0aW1lX2RpZmZlcmVuY2Uvd2Vla3MoMSkNCmBgYA0KDQoqKkx1YnJpZGF0ZSBpbnRlcnZhbHMqKg0KDQpgYGB7cn0NCmlycyAlPiUNCiAgbXV0YXRlKHNwcmF5aW5nX3RpbWUgPSBpbnRlcnZhbChzdGFydF9kYXRlX2RlZmF1bHQsIGVuZF9kYXRlX2RlZmF1bHQpL2RheXMoMSkpICU+JSANCiAgc2VsZWN0KHNwcmF5aW5nX3RpbWUpDQpgYGANCg0KKipFeHRyYWN0aW5nIHdlZWtkYXlzKioNCg0KYGBge3J9DQppcnMgJT4lDQogIG11dGF0ZSh3ZGF5X3N0YXJ0ID0gd2RheShzdGFydF9kYXRlX2RlZmF1bHQsIGxhYmVsID0gVFJVRSkpICU+JSANCiAgc2VsZWN0KHdkYXlfc3RhcnQpDQpgYGANCg0KKipWaXN1YWxpemluZyBzcHJheSBlbmQgbW9udGhzKioNCg0KYGBge3J9DQoNCmlycyAlPiUNCiAgbXV0YXRlKG1vbnRoX2VuZCA9IG1vbnRoKGVuZF9kYXRlX2RlZmF1bHQsIGxhYmVsID0gVFJVRSkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gbW9udGhfZW5kKSkgKw0KICBnZW9tX2JhcigpIA0KYGBgDQoNCioqUm91bmRpbmcgZGF0ZXMgcHJhY3RpY2UqKg0KDQpgYGB7cn0NCmRhdGVfcm91bmQgPC0gYXMuRGF0ZSgiMjAwMS0xMS0yOSIpDQpyb3VuZGVkX2RhdGUgPC0gZmxvb3JfZGF0ZShkYXRlX3JvdW5kLCB1bml0PSJ5ZWFyIikNCnJvdW5kZWRfZGF0ZQ0KYGBgDQoNCioqUGxvdCBhdmcgbW9udGhseSBtaW4gYW5kIG1heCB0ZW1wZXJhdHVyZXMqKg0KDQpgYGB7cn0NCndlYXRoZXIgJT4lIA0KICBtdXRhdGUobW9udGhfeWVhciA9IGZsb29yX2RhdGUoZGF0ZSwgdW5pdD0ibW9udGgiKSkgJT4lIA0KICBncm91cF9ieShtb250aF95ZWFyKSAlPiUNCiAgc3VtbWFyaXNlKGF2Z19taW5fdGVtcCA9IG1lYW4obWluX3RlbXApLCANCiAgICAgICAgICAgIGF2Z19tYXhfdGVtcCA9IG1lYW4obWF4X3RlbXApKSAlPiUgDQogIGdncGxvdCgpICsgDQogIGdlb21fbGluZShhZXMoeCA9IG1vbnRoX3llYXIsIHkgPSBhdmdfbWluX3RlbXApLCBjb2xvciA9ICJibHVlIikgKyANCiAgZ2VvbV9saW5lKGFlcyh4ID0gbW9udGhfeWVhciwgeSA9IGF2Z19tYXhfdGVtcCksIGNvbG9yID0gInJlZCIpDQpgYGANCg0KIyBDb250cmlidXRvcnMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIGZvbGxvd2luZyB0ZWFtIG1lbWJlcnMgY29udHJpYnV0ZWQgdG8gdGhpcyBsZXNzb246DQoNCmByIHRnY19jb250cmlidXRvcnNfbGlzdChjKCJhbWNraW5sZXkiLCAia2VuZGF2aWRuIikpYA0K