Summary

In this Notebook, we will see how to:

  • download 10 years of daily historic observed temperature data for a single location from an interpolated dataset called gridMET hosted on Cal-Adapt

  • convert units using the units package

  • time slice multi-year data including custom time periods based on months or calendar dates

  • create tabular and visual summaries of multi-year metrics

  • compute accumulated degree days for specific crops & pests using the degday package

  • find the date when a specific accumulated degree day threshold is reached

  • interpolate hourly temperatures from the daily min and max

  • compute accumulated chill portions using the chillR package

Load Packages

First load the packages we’ll be using:

library(dplyr)
library(tidyr)
library(lubridate)
library(ggplot2)


Import gridMET data

For this exercise, we’ll work with 10 years (2011-2020) of daily observed historical data from gridMET for a location in the northern San Joaquin Valley. gridMET weather data come as 4km rasters interpolated from weather stations. It is based on PRISM with additional regional reanalysis using climatically aided interpolation to better capture microclimates.

We can get gridMET data from Cal-Adapt, which hosts gridMET data up through 2020. The caladaptR package allows us to import data through the Cal-Adapt API. The first step is to create the API request object (more info). For gridMET data, we need to identify the dataset by its ‘slug’, which we can find by searching the Cal-Adapt API data catalog:

library(caladaptr)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
caladaptr (version 0.6.8)
URL: https://ucanr-igis.github.io/caladaptr
Bug reports: https://github.com/ucanr-igis/caladaptr/issues
## Search the data catalog for gridMET:  
ca_catalog_search("gridmet")

pr_day_gridmet
  name: gridMET daily precipitation historical
  url: https://api.cal-adapt.org/api/series/pr_day_gridmet/
  tres: daily
  begin: 1979-01-01T00:00:00Z
  end: 2020-12-31T00:00:00Z
  units: mm
  num_rast: 1
  id: 338
  xmin: -124.579167
  xmax: -113.370833
  ymin: 31.545833
  ymax: 43.754167

pr_year_gridmet
  name: gridMET yearly precipitation historical
  url: https://api.cal-adapt.org/api/series/pr_year_gridmet/
  tres: annual
  begin: 1979-01-01T00:00:00Z
  end: 2020-12-31T00:00:00Z
  units: mm
  num_rast: 42
  id: 381
  xmin: -124.579167
  xmax: -113.370833
  ymin: 31.545833
  ymax: 43.754167

tmmn_day_gridmet
  name: gridMET daily minimum temperature historical
  url: https://api.cal-adapt.org/api/series/tmmn_day_gridmet/
  tres: daily
  begin: 1979-01-01T00:00:00Z
  end: 2020-12-31T00:00:00Z
  units: K
  num_rast: 1
  id: 919
  xmin: -124.579167
  xmax: -113.370833
  ymin: 31.545833
  ymax: 43.754167

tmmn_year_gridmet
  name: gridMET yearly minimum temperature historical
  url: https://api.cal-adapt.org/api/series/tmmn_year_gridmet/
  tres: annual
  begin: 1979-01-01T00:00:00Z
  end: 2020-12-31T00:00:00Z
  units: K
  num_rast: 42
  id: 920
  xmin: -124.579167
  xmax: -113.370833
  ymin: 31.545833
  ymax: 43.754167

tmmx_day_gridmet
  name: gridMET daily maximum temperature historical
  url: https://api.cal-adapt.org/api/series/tmmx_day_gridmet/
  tres: daily
  begin: 1979-01-01T00:00:00Z
  end: 2020-12-31T00:00:00Z
  units: K
  num_rast: 1
  id: 921
  xmin: -124.579167
  xmax: -113.370833
  ymin: 31.545833
  ymax: 43.754167

tmmx_year_gridmet
  name: gridMET yearly maximum temperature historical
  url: https://api.cal-adapt.org/api/series/tmmx_year_gridmet/
  tres: annual
  begin: 1979-01-01T00:00:00Z
  end: 2020-12-31T00:00:00Z
  units: K
  num_rast: 42
  id: 922
  xmin: -124.579167
  xmax: -113.370833
  ymin: 31.545833
  ymax: 43.754167


Once you find the ‘slugs’ of the dataset(s) of interest, you can construct a API request object:

## Define an object to hold longitude & latitude coordinates
pt1_coords <- c(-121.171, 37.730)

pt1_cap <- ca_loc_pt(coords = pt1_coords) |>
  ca_slug(c("tmmn_day_gridmet", "tmmx_day_gridmet")) |> 
  ca_dates(start = as.Date("2010-10-01"), end = as.Date("2020-09-30"))

pt1_cap
Cal-Adapt API Request
Location(s): 
  x: -121.171
  y: 37.73
Slug(s): tmmn_day_gridmet, tmmx_day_gridmet
Dates: 2010-10-01 to 2020-09-30
 


You can plot a caladaptR API request object to see exactly where it is:

plot(pt1_cap)


To actually retrieve data, you feed the API request object into a function that communicates with the server and retrieves data:

## Uncomment the following to retreive data from the server
# pt1_tbl <- pt1_cap |> ca_getvals_tbl()

pt1_tbl <- readRDS("./data/pt1_tbl.Rds")

glimpse(pt1_tbl)
Rows: 7,306
Columns: 5
$ id   <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ slug <chr> "tmmn_day_gridmet", "tmmn_day_gridmet", "tmmn_day_gridmet", "tmmn_day_gridmet", "tmmn_day_gridmet", "tmmn_day_gridme…
$ spag <fct> none, none, none, none, none, none, none, none, none, none, none, none, none, none, none, none, none, none, none, no…
$ dt   <chr> "2010-10-01", "2010-10-02", "2010-10-03", "2010-10-04", "2010-10-05", "2010-10-06", "2010-10-07", "2010-10-08", "201…
$ val  [K] 288.6 [K], 289.2 [K], 286.4 [K], 285.6 [K], 284.1 [K], 283.1 [K], 284.0 [K], 283.3 [K], 284.3 [K], 285.5 [K], 287.7 [K…
head(pt1_tbl)


Convert Units

The climate variable column returned by ca_getvals_tbl() (in this case temperature) is a units objects (i.e., numeric with the units encoded) from the units package. This makes it easy to convert units, for example Kelvin to Fahrenheit, with set_units().

We also need to convert the dt column from character values to Date values, so we can use it for filtering and sorting.

library(units)
udunits database from C:/Users/Andy/AppData/Local/R/win-library/4.2/units/share/udunits/udunits2.xml
pt1_degf_tbl <- pt1_tbl |> 
  mutate(dt_date = as.Date(dt),
         temp_f = set_units(val, degF)) |> 
  select(dt_date, slug, val, temp_f)

head(pt1_degf_tbl)


Plot the raw data:

ggplot(pt1_degf_tbl, mapping = aes(x = dt_date, y = temp_f, group = slug)) + 
  geom_line() + 
  scale_x_date(date_breaks = "1 years", date_labels = "%Y") +
  facet_wrap(~ slug, ncol = 1) 


Note: Because we are using modeled data, gridMET doesn’t contain any missing values. Normally, if we were using weather station data, we would first need to check for missing rows and/or NA values, and then deal with them using substitution and/or interpolation (details).

Time Filtering Multi-Year Datasets

To filter multi-year datasets by month or season, we can use functions from lubridate to pull out date parts. For example to add columns for the month and year:

pt1_month_year_tbl <- pt1_degf_tbl |> 
  mutate(month_num = lubridate::month(dt_date), 
         year = lubridate::year(dt_date)) |> 
  select(dt_date, month_num, year, slug, temp_f)

head(pt1_month_year_tbl)


Group and Summarize

To compute descriptive stats of groups of rows, you can use dplyr’s group_by() followed by summarise(). For example to compute the average daily high temp by year:

pt1_month_year_tbl |> 
  filter(slug == "tmmx_day_gridmet") |> 
  group_by(year) |> 
  summarise(mean_daily_high = mean(temp_f))


McBride and Lacan (2018) computed the average daily high temperature in July as a measure of heat stress for trees. Here’s how that would look for our historical data:

pt1_month_year_tbl |> 
  filter(slug == "tmmx_day_gridmet", month_num == 7) |> 
  group_by(year) |> 
  summarise(mean_daily_high_july = mean(temp_f))


If we wanted the mean daily high in July for this entire time period (not by year), simply remove the group_by() statement:

pt1_month_year_tbl |> 
  filter(slug == "tmmx_day_gridmet", month_num == 7) |> 
  summarise(mean_daily_high_july_2010s = mean(temp_f)) 


Custom Time Slices

Some metrics are computed for custom time periods defined by months or calendar days. You can create columns for custom time periods using vectorized conditional functions such as dplyr::if_else() and dplyr::case_when().

Water Year

The water year starts on Oct. 1st and goes through Sep. 30. It is designated by the calendar year on which it ends.

We can add a column for water year using a mutate expression that includes if_else():

pt1_water_year_tbl <- pt1_month_year_tbl |> 
  mutate(water_year = year + if_else(month_num >= 10, 1, 0))

head(pt1_water_year_tbl)


For more flexibility, lubridate::yday() returns the ‘Julian day’ (1..365) of a date. For example, to pull out April 15 - June 15 each year, we can use:

(jday_start <- lubridate::yday(as.Date("1970-04-15")))
[1] 105
(jday_end <- lubridate::yday(as.Date("1970-06-15")))
[1] 166
pt1_afterbloom_tbl <- pt1_month_year_tbl |> 
  mutate(jday = yday(dt_date)) |> 
  filter(jday >= jday_start, jday <= jday_end) |> 
  select(dt_date, year, jday, slug, temp_f)
  
head(pt1_afterbloom_tbl)


To display the minimum daily temperature during this period as box and whiskers plots:

ggplot(pt1_afterbloom_tbl |> filter(slug == "tmmn_day_gridmet"), 
       mapping = aes(x = year, y = temp_f, group = year)) +
  geom_boxplot() +
  labs(title = "Minimum Daily Temp April 15 - June 15",
       subtitle = "Point 1, 2011 - 2022",
       x = "year",
       y = "temp")


Degree Days

Many phenology events for trees (e.g., blooming) and insects (e.g., egg laying) can be predicted by degree days. Degree days can be thought of as the total amount of warmth, within a usable temperature range, that a plant or insect is exposed to over time. Accumulated degree days make good predictors because plants and insects are cold blooded, hence their development rates are influenced by the ambient temperature.

Some things to know about degree days:

  • Degree day are not a real thing that you can measure with a sensor, like temperature or humidity. Rather they are an analytical construct that aims to mirror plant and insect physiology.

  • There is no such thing as a ‘universal’ or ‘standard’ degree day. Degree days take into consider the usable range of temperature for a specific species, so they are always in reference to a specific insect, crop, or insect-crop combo.

  • Degree days are computed from the daily minimum and maximum temperatures, with additional parameters specific to a crop and/or insect (degree hours are computed from hourly temperature).

  • Degree days be can computed in Fahrenheit or Celsius.

  • Degree days are not very useful by themselves. You need to use them with a phenology table that predicts when events will take place based on accumulated degree days.

  • Phenology tables also tell you when to start counting degree days. This could be a calendar event, or the date of an observation such as when you see eggs in your bug traps.

  • There are different formula for computing degree days, including the simple average method, single sine, single triangle, double-sine, and double-triangle. The phenology table will tell you which one to use.

  • You can compute degree days using the degday package.

Example: Navel Orangeworm Degree Days

In this example, we’ll use degree days to explore the timing of generations of Navel Orangeworm living in an almond orchard. We begin by looking up which degree day formula to use, and the range of usable temperatures, from UC IPM website, which tells us:

Navel Orangeworm in Almonds

Lower/upper threshold: 55/94°F

Calculation/upper cutoff method: single sine/horizontal

Biofix: The first biofix is the beginning of a consistent increase in egg laying on egg traps. When at least 75% of the egg traps in a given location show increases in the number of eggs on two consecutive monitoring dates, the biofix is the first of those two dates.

Degree Day Events:

  • the best time to spray is when 100 NOW-DD have accumulated after biofix

  • the next generation of adults can be expected in 1056 NOW-DD after biofix

To compute degree days, we start by putting the daily min and max temperatures in separate columns:

pt1_minmax_tbl <- pt1_tbl |> 
  mutate(dt = as.Date(dt),
         temp_f = set_units(val, degF)) |> 
  pivot_wider(id_cols = dt, names_from = slug, values_from = temp_f) |> 
  rename(tmin = tmmn_day_gridmet, tmax = tmmx_day_gridmet) |> 
  mutate(tmax = if_else(tmax < tmin, tmin, tmax))

head(pt1_minmax_tbl)


Now we can compute NOW Almond degree days:

library(degday)
thresh_low <- 55
thresh_up <- 94

pt1_nowdd_tbl <- pt1_minmax_tbl |> 
  mutate(now_dd = dd_sng_sine(daily_min = tmin, 
                              daily_max = tmax, 
                              thresh_low = thresh_low, 
                              thresh_up = thresh_up))
 - using single sine method
  
head(pt1_nowdd_tbl)


Applying Degree Days: Pest Management

Suppose an almond grower sees a consistent increase in egg laying on egg traps on April 18, 2011 (i.e., the biofix event). The UC IPM website says the best day to spray is when 100 DD have accumulated after the biofix, and the next generation of adults can be expected 1056 DD after biofix. Find the dates for these events.

Step 1 is to filter the dates to begin with the day after biofix, and add a column for accumulated degree days:

pt1_nowdd_2011_tbl <- pt1_nowdd_tbl |> 
  filter(dt > as.Date("2011-04-18"), dt <= as.Date("2011-10-31")) |> 
  mutate(now_dd_acc = cumsum(now_dd)) 

head(pt1_nowdd_2011_tbl)  


Find the date when 100 DD have accumulated:

pt1_nowdd_2011_tbl |> 
  filter(now_dd_acc >= 100) |> 
  slice(1) 


And 1056 DD:

pt1_nowdd_2011_tbl |> 
  filter(now_dd_acc >= 1056) |> 
  slice(1) |> 
  pull(dt)
[1] "2011-07-17"


Applying Degree Days: Estimating the biofix when you don’t have observations

Pathak et al (2021) estimate the emergence of the first flight of Navel Orangeworm as occurring when 300 °F NOW DD have accumulated after January 1st. Compute when this threshold was reached for the historic period.

Step 1 is to remove incomplete years and compute accumulated degree days for each year:

pt1_nowdd_yr_acc_tbl <- pt1_nowdd_tbl |> 
  mutate(year = lubridate::year(dt)) |>    ## add a year column
  filter(year >= 2011) |>                  ## remove 2010 (incomplete year)
  group_by(year) |>                        ## group by years
  mutate(dd_acc_yr = cumsum(now_dd))       ## for each year, compute accumulated DD

head(pt1_nowdd_yr_acc_tbl)


Step 2: on what day each year did we reach 300 DD °F?

pt1_nowdd_yr_acc_tbl |> 
  filter(dd_acc_yr >= 300) |> 
  summarise(first_300dd_date = min(dt), first_300dd_jday = yday(min(dt)))


Adding a column with tomorrow’s temperature

Some degree day formulas (i.e., double-sine and double-triangle) require the next day’s minimum temp to be included. We can add this to our data frame using dplyr::lead().

For example, starting with:

head(pt1_minmax_tbl)


Add the next day’s minimum temperature:

pt1_minmax_nextmin_tbl <- pt1_minmax_tbl |> 
  mutate(tmin_next = lead(tmin, n = 1))

head(pt1_minmax_nextmin_tbl)


Note how lead() treats the last row:

tail(pt1_minmax_nextmin_tbl)


Now we can compute degree days using the double-sine method:

thresh_low <- 55
thresh_up <- 94

pt1_minmax_nextmin_tbl |>
  mutate(now_dd_dblsine = dd_dbl_sine(daily_min = tmin,
                                      daily_max = tmax, 
                                      nextday_min = tmin_next,
                                      thresh_low = thresh_low, 
                                      thresh_up = thresh_up)) |> 
  head()
 - using double sine method


Interpolating Hourly Temps

Some agroclimate metrics require hourly temperatures, such as chill hours and frost exposure (Parker et al, 2021). Ideally you would have hourly temperature data for these metrics, but if not you can interpolate hourly temps from the daily min and max.

One of the best algorithms for interpolating hourly temps comes from Linvill (1990). This method uses an idealized sine curve to describe daytime warming, and a logarithmic decay function for nighttime cooling. The transition between warming and cool is a function of the day length, which in turn is modeled by sunrise and sunset, which in turn is modeled by latitude.

The chillR package has a function make_hourly_temps() hat applies the Linvill method. You need to feed it a data frame that is formatted with specific columns, plus a latitude value (details).

The first step is to make the min and max temps separate columns:

pt1_minmax_tbl <- pt1_tbl |> 
  mutate(dt = as.Date(dt),
         temp_f = set_units(val, degF)) |> 
  pivot_wider(id_cols = dt, names_from = slug, values_from = temp_f) |> 
  rename(tmin = tmmn_day_gridmet, tmax = tmmx_day_gridmet) |> 
  mutate(tmax = if_else(tmax < tmin, tmin, tmax))

head(pt1_minmax_tbl)


Next, we have to add a couple of columns, and change the column names, to match the format expected by chillR::make_hourly_temps() (as described on the help page). This is an example of data wrangling to ‘work backwards’ from what you need to what you’ve got:

pt1_minmax_chillr_tbl <- pt1_minmax_tbl |> 
  mutate(Year = lubridate::year(dt),
         Month = lubridate::month(dt),
         Day = lubridate::day(dt),
         Tmax = as.numeric(tmax),
         Tmin = as.numeric(tmin)) |> 
  select(DATE = dt, Year, Month, Day, Tmax, Tmin)

head(pt1_minmax_chillr_tbl)


Now we can call chillR::make_hourly_temps(), also passing the latitude of our location:

library(chillR)

Attaching package: ‘chillR’

The following object is masked from ‘package:lubridate’:

    leap_year
pt1_coords[2]
[1] 37.73
pt1_hourtemps_wide_tbl <- make_hourly_temps(latitude = pt1_coords[2],
                                            year_file = pt1_minmax_chillr_tbl)
head(pt1_hourtemps_wide_tbl)


make_hourly_temps() gives us wide data. It’s generally easier to work with hourly temperature data in a long format. We can reshape the hourly temps with tidyr::pivot_longer().

pt1_hourtemps_long_tbl <- pt1_hourtemps_wide_tbl |>
  pivot_longer(cols = starts_with("Hour_"),
               names_to = "Hour",
               names_prefix = "Hour_",
               names_transform = list(Hour = as.integer),
               values_to = "temp_f") |>
  mutate(date_hour = lubridate::make_datetime(year = Year, month = Month,
                                              day = Day, hour = Hour,
                                              tz = "America/Los_Angeles")) |>
  select(date_hour, temp_f)

head(pt1_hourtemps_long_tbl)


To see what these hourly temperatures look like, let’s plot one week of them:

pt1_hourtemps_long_tbl |> 
  filter(date_hour >= as.Date("2011-01-01"), date_hour <= as.Date("2011-01-07")) |> 
  ggplot(aes(x = date_hour, y = temp_f)) +
  geom_line(aes(color="red"), show.legend = FALSE)


Chill Portions

Chill portions and chill hours are like degree days, but for cold temperatures. Certain phenology events, like blooming in fruit and nut trees, are correlated with the net amount of cold temperatures the trees been exposed to. This reflect an evolutionary adaptation trees have developed to prevent blooming too early and hence risking damage from a late frost. Different fruit and nut trees have developed different thresholds of chill portions that tell them when its time to ‘wake up’ from their winter dormancy.

We can compute accumulated chill portions by passing a vector of hourly temperature values to chillR::Dynamic_Model(). Note however that Dynamic_Model() expects the temperatures to be in Celsius, so the first step is to add a column with the temperature in °C using the units package:

pt1_hourtemps_degc_tbl <- pt1_hourtemps_long_tbl |> 
  mutate(temp_c = as.numeric(set_units(set_units(temp_f, degF), degC)))
         
head(pt1_hourtemps_degc_tbl)        


Now we can compute accumulated chill portions using Dynamic_Model():

pt1_chillport_tbl <- pt1_hourtemps_degc_tbl |> 
  mutate(chillport_acc = chillR::Dynamic_Model(temp_c, summ = TRUE))

head(pt1_chillport_tbl)


Plot accumulated chill portions for one season:

pt1_chillport_tbl |> 
  filter(date_hour >= as.Date("2010-10-01"), date_hour <= as.Date("2011-07-01")) |> 
  ggplot(aes(x = date_hour, y = chillport_acc)) +
  geom_line() +
  ggtitle("Chill Portions 2010-11")


Challenge Question

New Star Cherries require 54 chill portions to come out of their winter dormancy. Identify the date when this level of chill was reached each year of the observed dataset. For the purposes of the exercise, consider the chill season to go from Nov 1 thru June 30.

Hint: This will look a lot like the question about the date when degree days were reached. Answer

pt1_hourtemps_degc_tbl |> 
  mutate(year = year(date_hour), month = month(date_hour)) |> 
  filter(date_hour >= as.Date("2010-11-01") & (month >= 11 | month <= 6)) |> 
  mutate(chill_season = year + if_else(month >= 11, 1, 0)) |> 
  group_by(chill_season) |> 
  mutate(chillport_acc = chillR::Dynamic_Model(temp_c)) |> 
  filter(chillport_acc >= 54) |> 
  summarise(req_chill_reached = min(date_hour)) |> 
  mutate(month = month(req_chill_reached), day = day(req_chill_reached))


End!

Remember to save the Notebook to generate a HTML version that includes all executed code that you can save for keeps!

LS0tDQp0aXRsZTogIkFncm9jbGltYXRlIE1ldHJpY3MgTm90ZWJvb2sgIzI6IEN1bW11bGF0aXZlIG1ldHJpY3MgYW5kIG11bHRpLXllYXIgc3VtbWFyaWVzIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgY3NzOiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2Fncm9jbGltUi9hc3NldHMvbmJfY3NzMDEuY3NzDQogICAgaW5jbHVkZXM6IA0KICAgICAgYWZ0ZXJfYm9keTogaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9hZ3JvY2xpbVIvYXNzZXRzL25iX2Zvb3Rlcl9hZ3JvY2xpbXIuaHRtbA0KLS0tDQoNCiMgU3VtbWFyeQ0KDQpJbiB0aGlzIE5vdGVib29rLCB3ZSB3aWxsIHNlZSBob3cgdG86DQoNCi0gICBkb3dubG9hZCAxMCB5ZWFycyBvZiBkYWlseSBoaXN0b3JpYyBvYnNlcnZlZCB0ZW1wZXJhdHVyZSBkYXRhIGZvciBhIHNpbmdsZSBsb2NhdGlvbiBmcm9tIGFuIGludGVycG9sYXRlZCBkYXRhc2V0IGNhbGxlZCBncmlkTUVUIGhvc3RlZCBvbiBDYWwtQWRhcHQNCg0KLSAgIGNvbnZlcnQgdW5pdHMgdXNpbmcgdGhlIFt1bml0c10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT11bml0cyAidW5pdHMgcGFja2FnZSIpIHBhY2thZ2UNCg0KLSAgIHRpbWUgc2xpY2UgbXVsdGkteWVhciBkYXRhIGluY2x1ZGluZyBjdXN0b20gdGltZSBwZXJpb2RzIGJhc2VkIG9uIG1vbnRocyBvciBjYWxlbmRhciBkYXRlcw0KDQotICAgY3JlYXRlIHRhYnVsYXIgYW5kIHZpc3VhbCBzdW1tYXJpZXMgb2YgbXVsdGkteWVhciBtZXRyaWNzDQoNCi0gICBjb21wdXRlIGFjY3VtdWxhdGVkIGRlZ3JlZSBkYXlzIGZvciBzcGVjaWZpYyBjcm9wcyAmIHBlc3RzIHVzaW5nIHRoZSBbZGVnZGF5XShodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2RlZ2RheS8gImRlZ2RheSBwYWNrYWdlIikgcGFja2FnZQ0KDQotICAgZmluZCB0aGUgZGF0ZSB3aGVuIGEgc3BlY2lmaWMgYWNjdW11bGF0ZWQgZGVncmVlIGRheSB0aHJlc2hvbGQgaXMgcmVhY2hlZA0KDQotICAgaW50ZXJwb2xhdGUgaG91cmx5IHRlbXBlcmF0dXJlcyBmcm9tIHRoZSBkYWlseSBtaW4gYW5kIG1heA0KDQotICAgY29tcHV0ZSBhY2N1bXVsYXRlZCBjaGlsbCBwb3J0aW9ucyB1c2luZyB0aGUgW2NoaWxsUl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1jaGlsbFIgImNoaWxsUiBwYWNrYWdlIikgcGFja2FnZQ0KDQojIExvYWQgUGFja2FnZXMNCg0KRmlyc3QgbG9hZCB0aGUgcGFja2FnZXMgd2UnbGwgYmUgdXNpbmc6DQoNCmBgYHtyIGNodW5rMDEsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNClwNCg0KIyBJbXBvcnQgZ3JpZE1FVCBkYXRhDQoNCkZvciB0aGlzIGV4ZXJjaXNlLCB3ZSdsbCB3b3JrIHdpdGggMTAgeWVhcnMgKDIwMTEtMjAyMCkgb2YgZGFpbHkgb2JzZXJ2ZWQgaGlzdG9yaWNhbCBkYXRhIGZyb20gZ3JpZE1FVCBmb3IgYSBsb2NhdGlvbiBpbiB0aGUgbm9ydGhlcm4gU2FuIEpvYXF1aW4gVmFsbGV5LiBbZ3JpZE1FVF0oaHR0cHM6Ly93d3cuY2xpbWF0b2xvZ3lsYWIub3JnL2dyaWRtZXQuaHRtbCkgd2VhdGhlciBkYXRhIGNvbWUgYXMgNGttIHJhc3RlcnMgaW50ZXJwb2xhdGVkIGZyb20gd2VhdGhlciBzdGF0aW9ucy4gSXQgaXMgYmFzZWQgb24gUFJJU00gd2l0aCBhZGRpdGlvbmFsIHJlZ2lvbmFsIHJlYW5hbHlzaXMgdXNpbmcgY2xpbWF0aWNhbGx5IGFpZGVkIGludGVycG9sYXRpb24gdG8gYmV0dGVyIGNhcHR1cmUgbWljcm9jbGltYXRlcy4NCg0KV2UgY2FuIGdldCBncmlkTUVUIGRhdGEgZnJvbSBDYWwtQWRhcHQsIHdoaWNoIGhvc3RzIGdyaWRNRVQgZGF0YSB1cCB0aHJvdWdoIDIwMjAuIFRoZSBbY2FsYWRhcHRSXShodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci8pIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGltcG9ydCBkYXRhIHRocm91Z2ggdGhlIENhbC1BZGFwdCBBUEkuIFRoZSBmaXJzdCBzdGVwIGlzIHRvIGNyZWF0ZSB0aGUgQVBJIHJlcXVlc3Qgb2JqZWN0IChbbW9yZSBpbmZvXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PUFQQ0lCczM1QkpnKSkuIEZvciBncmlkTUVUIGRhdGEsIHdlIG5lZWQgdG8gaWRlbnRpZnkgdGhlIGRhdGFzZXQgYnkgaXRzICdzbHVnJywgd2hpY2ggd2UgY2FuIGZpbmQgYnkgc2VhcmNoaW5nIHRoZSBDYWwtQWRhcHQgQVBJIGRhdGEgY2F0YWxvZzoNCg0KYGBge3IgY2h1bmswMn0NCmxpYnJhcnkoY2FsYWRhcHRyKQ0KDQojIyBTZWFyY2ggdGhlIGRhdGEgY2F0YWxvZyBmb3IgZ3JpZE1FVDogIA0KY2FfY2F0YWxvZ19zZWFyY2goImdyaWRtZXQiKQ0KYGBgDQoNClwNCg0KT25jZSB5b3UgZmluZCB0aGUgJ3NsdWdzJyBvZiB0aGUgZGF0YXNldChzKSBvZiBpbnRlcmVzdCwgeW91IGNhbiBjb25zdHJ1Y3QgYSBBUEkgcmVxdWVzdCBvYmplY3Q6DQoNCmBgYHtyIGNodW5rMDN9DQojIyBEZWZpbmUgYW4gb2JqZWN0IHRvIGhvbGQgbG9uZ2l0dWRlICYgbGF0aXR1ZGUgY29vcmRpbmF0ZXMNCnB0MV9jb29yZHMgPC0gYygtMTIxLjE3MSwgMzcuNzMwKQ0KDQpwdDFfY2FwIDwtIGNhX2xvY19wdChjb29yZHMgPSBwdDFfY29vcmRzKSB8Pg0KICBjYV9zbHVnKGMoInRtbW5fZGF5X2dyaWRtZXQiLCAidG1teF9kYXlfZ3JpZG1ldCIpKSB8PiANCiAgY2FfZGF0ZXMoc3RhcnQgPSBhcy5EYXRlKCIyMDEwLTEwLTAxIiksIGVuZCA9IGFzLkRhdGUoIjIwMjAtMDktMzAiKSkNCg0KcHQxX2NhcA0KYGBgDQoNClwNCg0KWW91IGNhbiBwbG90IGEgY2FsYWRhcHRSIEFQSSByZXF1ZXN0IG9iamVjdCB0byBzZWUgZXhhY3RseSB3aGVyZSBpdCBpczoNCg0KYGBge3IgY2h1bmswNH0NCnBsb3QocHQxX2NhcCkNCmBgYA0KDQpcDQoNClRvIGFjdHVhbGx5IHJldHJpZXZlIGRhdGEsIHlvdSBmZWVkIHRoZSBBUEkgcmVxdWVzdCBvYmplY3QgaW50byBhIGZ1bmN0aW9uIHRoYXQgY29tbXVuaWNhdGVzIHdpdGggdGhlIHNlcnZlciBhbmQgcmV0cmlldmVzIGRhdGE6DQoNCmBgYHtyIGNodW5rMDV9DQojIyBVbmNvbW1lbnQgdGhlIGZvbGxvd2luZyB0byByZXRyZWl2ZSBkYXRhIGZyb20gdGhlIHNlcnZlcg0KIyBwdDFfdGJsIDwtIHB0MV9jYXAgfD4gY2FfZ2V0dmFsc190YmwoKQ0KDQpwdDFfdGJsIDwtIHJlYWRSRFMoIi4vZGF0YS9wdDFfdGJsLlJkcyIpDQoNCmdsaW1wc2UocHQxX3RibCkNCmhlYWQocHQxX3RibCkNCmBgYA0KDQpcDQoNCiMgQ29udmVydCBVbml0cw0KDQpUaGUgY2xpbWF0ZSB2YXJpYWJsZSBjb2x1bW4gcmV0dXJuZWQgYnkgYGNhX2dldHZhbHNfdGJsKClgIChpbiB0aGlzIGNhc2UgdGVtcGVyYXR1cmUpIGlzIGEgKnVuaXRzKiBvYmplY3RzIChpLmUuLCBudW1lcmljIHdpdGggdGhlIHVuaXRzIGVuY29kZWQpIGZyb20gdGhlIFt1bml0c10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT11bml0cyAidW5pdHMgcGFja2FnZSIpIHBhY2thZ2UuIFRoaXMgbWFrZXMgaXQgZWFzeSB0byBjb252ZXJ0IHVuaXRzLCBmb3IgZXhhbXBsZSBLZWx2aW4gdG8gRmFocmVuaGVpdCwgd2l0aCBgc2V0X3VuaXRzKClgLg0KDQpXZSBhbHNvIG5lZWQgdG8gY29udmVydCB0aGUgYGR0YCBjb2x1bW4gZnJvbSBjaGFyYWN0ZXIgdmFsdWVzIHRvIERhdGUgdmFsdWVzLCBzbyB3ZSBjYW4gdXNlIGl0IGZvciBmaWx0ZXJpbmcgYW5kIHNvcnRpbmcuDQoNCmBgYHtyIGNodW5rMDZ9DQpsaWJyYXJ5KHVuaXRzKQ0KDQpwdDFfZGVnZl90YmwgPC0gcHQxX3RibCB8PiANCiAgbXV0YXRlKGR0X2RhdGUgPSBhcy5EYXRlKGR0KSwNCiAgICAgICAgIHRlbXBfZiA9IHNldF91bml0cyh2YWwsIGRlZ0YpKSB8PiANCiAgc2VsZWN0KGR0X2RhdGUsIHNsdWcsIHZhbCwgdGVtcF9mKQ0KDQpoZWFkKHB0MV9kZWdmX3RibCkNCmBgYA0KDQpcDQoNClBsb3QgdGhlIHJhdyBkYXRhOg0KDQpgYGB7ciBjaHVuazA3fQ0KZ2dwbG90KHB0MV9kZWdmX3RibCwgbWFwcGluZyA9IGFlcyh4ID0gZHRfZGF0ZSwgeSA9IHRlbXBfZiwgZ3JvdXAgPSBzbHVnKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjEgeWVhcnMiLCBkYXRlX2xhYmVscyA9ICIlWSIpICsNCiAgZmFjZXRfd3JhcCh+IHNsdWcsIG5jb2wgPSAxKSANCmBgYA0KDQpcDQoNCioqTm90ZToqKiBCZWNhdXNlIHdlIGFyZSB1c2luZyBtb2RlbGVkIGRhdGEsIGdyaWRNRVQgZG9lc24ndCBjb250YWluIGFueSBtaXNzaW5nIHZhbHVlcy4gTm9ybWFsbHksIGlmIHdlIHdlcmUgdXNpbmcgd2VhdGhlciBzdGF0aW9uIGRhdGEsIHdlIHdvdWxkIGZpcnN0IG5lZWQgdG8gY2hlY2sgZm9yIG1pc3Npbmcgcm93cyBhbmQvb3IgTkEgdmFsdWVzLCBhbmQgdGhlbiBkZWFsIHdpdGggdGhlbSB1c2luZyBzdWJzdGl0dXRpb24gYW5kL29yIGludGVycG9sYXRpb24gKFtkZXRhaWxzXShodHRwOi8vaW5yZXNnYi1sZWhyZS5pYWFzLnVuaS1ib25uLmRlL2NoaWxsUl9ib29rL2ZpbGxpbmctZ2Fwcy1pbi10ZW1wZXJhdHVyZS1yZWNvcmRzLmh0bWwpKS4NCg0KIyBUaW1lIEZpbHRlcmluZyBNdWx0aS1ZZWFyIERhdGFzZXRzDQoNClRvIGZpbHRlciBtdWx0aS15ZWFyIGRhdGFzZXRzIGJ5IG1vbnRoIG9yIHNlYXNvbiwgd2UgY2FuIHVzZSBmdW5jdGlvbnMgZnJvbSBbbHVicmlkYXRlXShodHRwczovL2x1YnJpZGF0ZS50aWR5dmVyc2Uub3JnLyAibHVicmlkYXRlIFIgcGFja2FnZSIpIHRvIHB1bGwgb3V0IGRhdGUgcGFydHMuIEZvciBleGFtcGxlIHRvIGFkZCBjb2x1bW5zIGZvciB0aGUgbW9udGggYW5kIHllYXI6DQoNCmBgYHtyIGNodW5rMDh9DQpwdDFfbW9udGhfeWVhcl90YmwgPC0gcHQxX2RlZ2ZfdGJsIHw+IA0KICBtdXRhdGUobW9udGhfbnVtID0gbHVicmlkYXRlOjptb250aChkdF9kYXRlKSwgDQogICAgICAgICB5ZWFyID0gbHVicmlkYXRlOjp5ZWFyKGR0X2RhdGUpKSB8PiANCiAgc2VsZWN0KGR0X2RhdGUsIG1vbnRoX251bSwgeWVhciwgc2x1ZywgdGVtcF9mKQ0KDQpoZWFkKHB0MV9tb250aF95ZWFyX3RibCkNCmBgYA0KDQpcDQoNCiMgR3JvdXAgYW5kIFN1bW1hcml6ZQ0KDQpUbyBjb21wdXRlIGRlc2NyaXB0aXZlIHN0YXRzIG9mIGdyb3VwcyBvZiByb3dzLCB5b3UgY2FuIHVzZSBkcGx5cidzIGBncm91cF9ieSgpYCBmb2xsb3dlZCBieSBgc3VtbWFyaXNlKClgLiBGb3IgZXhhbXBsZSB0byBjb21wdXRlIHRoZSBhdmVyYWdlIGRhaWx5IGhpZ2ggdGVtcCBieSB5ZWFyOg0KDQpgYGB7ciBjaHVuazA5fQ0KcHQxX21vbnRoX3llYXJfdGJsIHw+IA0KICBmaWx0ZXIoc2x1ZyA9PSAidG1teF9kYXlfZ3JpZG1ldCIpIHw+IA0KICBncm91cF9ieSh5ZWFyKSB8PiANCiAgc3VtbWFyaXNlKG1lYW5fZGFpbHlfaGlnaCA9IG1lYW4odGVtcF9mKSkNCmBgYA0KDQpcDQoNCk1jQnJpZGUgYW5kIExhY2FuIChbMjAxOF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai51ZnVnLjIwMTguMDcuMDIwKSkgY29tcHV0ZWQgdGhlICphdmVyYWdlIGRhaWx5IGhpZ2ggdGVtcGVyYXR1cmUgaW4gSnVseSogYXMgYSBtZWFzdXJlIG9mIGhlYXQgc3RyZXNzIGZvciB0cmVlcy4gSGVyZSdzIGhvdyB0aGF0IHdvdWxkIGxvb2sgZm9yIG91ciBoaXN0b3JpY2FsIGRhdGE6DQoNCmBgYHtyIGNodW5rMTB9DQpwdDFfbW9udGhfeWVhcl90YmwgfD4gDQogIGZpbHRlcihzbHVnID09ICJ0bW14X2RheV9ncmlkbWV0IiwgbW9udGhfbnVtID09IDcpIHw+IA0KICBncm91cF9ieSh5ZWFyKSB8PiANCiAgc3VtbWFyaXNlKG1lYW5fZGFpbHlfaGlnaF9qdWx5ID0gbWVhbih0ZW1wX2YpKQ0KYGBgDQoNClwNCg0KSWYgd2Ugd2FudGVkIHRoZSBtZWFuIGRhaWx5IGhpZ2ggaW4gSnVseSBmb3IgdGhpcyBlbnRpcmUgdGltZSBwZXJpb2QgKG5vdCBieSB5ZWFyKSwgc2ltcGx5IHJlbW92ZSB0aGUgYGdyb3VwX2J5KClgIHN0YXRlbWVudDoNCg0KYGBge3IgY2h1bmsxMX0NCnB0MV9tb250aF95ZWFyX3RibCB8PiANCiAgZmlsdGVyKHNsdWcgPT0gInRtbXhfZGF5X2dyaWRtZXQiLCBtb250aF9udW0gPT0gNykgfD4gDQogIHN1bW1hcmlzZShtZWFuX2RhaWx5X2hpZ2hfanVseV8yMDEwcyA9IG1lYW4odGVtcF9mKSkgDQpgYGANCg0KXA0KDQojIEN1c3RvbSBUaW1lIFNsaWNlcw0KDQpTb21lIG1ldHJpY3MgYXJlIGNvbXB1dGVkIGZvciBjdXN0b20gdGltZSBwZXJpb2RzIGRlZmluZWQgYnkgbW9udGhzIG9yIGNhbGVuZGFyIGRheXMuIFlvdSBjYW4gY3JlYXRlIGNvbHVtbnMgZm9yIGN1c3RvbSB0aW1lIHBlcmlvZHMgdXNpbmcgdmVjdG9yaXplZCBjb25kaXRpb25hbCBmdW5jdGlvbnMgc3VjaCBhcyBgZHBseXI6OmlmX2Vsc2UoKWAgYW5kIGBkcGx5cjo6Y2FzZV93aGVuKClgLg0KDQojIyBXYXRlciBZZWFyDQoNClRoZSBbd2F0ZXIgeWVhcl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvV2F0ZXJfeWVhcikgc3RhcnRzIG9uIE9jdC4gMXN0IGFuZCBnb2VzIHRocm91Z2ggU2VwLiAzMC4gSXQgaXMgZGVzaWduYXRlZCBieSB0aGUgY2FsZW5kYXIgeWVhciBvbiB3aGljaCBpdCBlbmRzLg0KDQpXZSBjYW4gYWRkIGEgY29sdW1uIGZvciB3YXRlciB5ZWFyIHVzaW5nIGEgbXV0YXRlIGV4cHJlc3Npb24gdGhhdCBpbmNsdWRlcyBgaWZfZWxzZSgpYDoNCg0KYGBge3IgY2h1bmsxMn0NCnB0MV93YXRlcl95ZWFyX3RibCA8LSBwdDFfbW9udGhfeWVhcl90YmwgfD4gDQogIG11dGF0ZSh3YXRlcl95ZWFyID0geWVhciArIGlmX2Vsc2UobW9udGhfbnVtID49IDEwLCAxLCAwKSkNCg0KaGVhZChwdDFfd2F0ZXJfeWVhcl90YmwpDQpgYGANCg0KXA0KDQpGb3IgbW9yZSBmbGV4aWJpbGl0eSwgYGx1YnJpZGF0ZTo6eWRheSgpYCByZXR1cm5zIHRoZSAnSnVsaWFuIGRheScgKDEuLjM2NSkgb2YgYSBkYXRlLiBGb3IgZXhhbXBsZSwgdG8gcHVsbCBvdXQgQXByaWwgMTUgLSBKdW5lIDE1IGVhY2ggeWVhciwgd2UgY2FuIHVzZToNCg0KYGBge3IgY2h1bmsxM30NCihqZGF5X3N0YXJ0IDwtIGx1YnJpZGF0ZTo6eWRheShhcy5EYXRlKCIxOTcwLTA0LTE1IikpKQ0KDQooamRheV9lbmQgPC0gbHVicmlkYXRlOjp5ZGF5KGFzLkRhdGUoIjE5NzAtMDYtMTUiKSkpDQoNCnB0MV9hZnRlcmJsb29tX3RibCA8LSBwdDFfbW9udGhfeWVhcl90YmwgfD4gDQogIG11dGF0ZShqZGF5ID0geWRheShkdF9kYXRlKSkgfD4gDQogIGZpbHRlcihqZGF5ID49IGpkYXlfc3RhcnQsIGpkYXkgPD0gamRheV9lbmQpIHw+IA0KICBzZWxlY3QoZHRfZGF0ZSwgeWVhciwgamRheSwgc2x1ZywgdGVtcF9mKQ0KICANCmhlYWQocHQxX2FmdGVyYmxvb21fdGJsKQ0KYGBgDQoNClwNCg0KVG8gZGlzcGxheSB0aGUgbWluaW11bSBkYWlseSB0ZW1wZXJhdHVyZSBkdXJpbmcgdGhpcyBwZXJpb2QgYXMgYm94IGFuZCB3aGlza2VycyBwbG90czoNCg0KYGBge3IgY2h1bmsxNH0NCmdncGxvdChwdDFfYWZ0ZXJibG9vbV90YmwgfD4gZmlsdGVyKHNsdWcgPT0gInRtbW5fZGF5X2dyaWRtZXQiKSwgDQogICAgICAgbWFwcGluZyA9IGFlcyh4ID0geWVhciwgeSA9IHRlbXBfZiwgZ3JvdXAgPSB5ZWFyKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnModGl0bGUgPSAiTWluaW11bSBEYWlseSBUZW1wIEFwcmlsIDE1IC0gSnVuZSAxNSIsDQogICAgICAgc3VidGl0bGUgPSAiUG9pbnQgMSwgMjAxMSAtIDIwMjIiLA0KICAgICAgIHggPSAieWVhciIsDQogICAgICAgeSA9ICJ0ZW1wIikNCmBgYA0KDQpcDQoNCiMgRGVncmVlIERheXMNCg0KTWFueSBwaGVub2xvZ3kgZXZlbnRzIGZvciB0cmVlcyAoZS5nLiwgYmxvb21pbmcpIGFuZCBpbnNlY3RzIChlLmcuLCBlZ2cgbGF5aW5nKSBjYW4gYmUgcHJlZGljdGVkIGJ5IFtkZWdyZWUgZGF5c10oaHR0cDovL3d3dy5pcG0udWNkYXZpcy5lZHUvV0VBVEhFUi9kZGNvbmNlcHRzLmh0bWwpLiBEZWdyZWUgZGF5cyBjYW4gYmUgdGhvdWdodCBvZiBhcyB0aGUgdG90YWwgYW1vdW50IG9mIHdhcm10aCwgd2l0aGluIGEgdXNhYmxlIHRlbXBlcmF0dXJlIHJhbmdlLCB0aGF0IGEgcGxhbnQgb3IgaW5zZWN0IGlzIGV4cG9zZWQgdG8gb3ZlciB0aW1lLiBBY2N1bXVsYXRlZCBkZWdyZWUgZGF5cyBtYWtlIGdvb2QgcHJlZGljdG9ycyBiZWNhdXNlIHBsYW50cyBhbmQgaW5zZWN0cyBhcmUgY29sZCBibG9vZGVkLCBoZW5jZSB0aGVpciBkZXZlbG9wbWVudCByYXRlcyBhcmUgaW5mbHVlbmNlZCBieSB0aGUgYW1iaWVudCB0ZW1wZXJhdHVyZS4NCg0KU29tZSB0aGluZ3MgdG8ga25vdyBhYm91dCBkZWdyZWUgZGF5czoNCg0KLSAgIERlZ3JlZSBkYXkgYXJlIG5vdCBhIHJlYWwgdGhpbmcgdGhhdCB5b3UgY2FuIG1lYXN1cmUgd2l0aCBhIHNlbnNvciwgbGlrZSB0ZW1wZXJhdHVyZSBvciBodW1pZGl0eS4gUmF0aGVyIHRoZXkgYXJlIGFuIGFuYWx5dGljYWwgY29uc3RydWN0IHRoYXQgYWltcyB0byBtaXJyb3IgcGxhbnQgYW5kIGluc2VjdCBwaHlzaW9sb2d5Lg0KDQotICAgVGhlcmUgaXMgbm8gc3VjaCB0aGluZyBhcyBhICd1bml2ZXJzYWwnIG9yICdzdGFuZGFyZCcgZGVncmVlIGRheS4gRGVncmVlIGRheXMgdGFrZSBpbnRvIGNvbnNpZGVyIHRoZSB1c2FibGUgcmFuZ2Ugb2YgdGVtcGVyYXR1cmUgZm9yIGEgc3BlY2lmaWMgc3BlY2llcywgc28gdGhleSBhcmUgYWx3YXlzIGluIHJlZmVyZW5jZSB0byBhIHNwZWNpZmljIGluc2VjdCwgY3JvcCwgb3IgaW5zZWN0LWNyb3AgY29tYm8uDQoNCi0gICBEZWdyZWUgZGF5cyBhcmUgY29tcHV0ZWQgZnJvbSB0aGUgZGFpbHkgbWluaW11bSBhbmQgbWF4aW11bSB0ZW1wZXJhdHVyZXMsIHdpdGggYWRkaXRpb25hbCBwYXJhbWV0ZXJzIHNwZWNpZmljIHRvIGEgY3JvcCBhbmQvb3IgaW5zZWN0IChkZWdyZWUgaG91cnMgYXJlIGNvbXB1dGVkIGZyb20gaG91cmx5IHRlbXBlcmF0dXJlKS4NCg0KLSAgIERlZ3JlZSBkYXlzIGJlIGNhbiBjb21wdXRlZCBpbiBGYWhyZW5oZWl0IG9yIENlbHNpdXMuDQoNCi0gICBEZWdyZWUgZGF5cyBhcmUgbm90IHZlcnkgdXNlZnVsIGJ5IHRoZW1zZWx2ZXMuIFlvdSBuZWVkIHRvIHVzZSB0aGVtIHdpdGggYSBwaGVub2xvZ3kgdGFibGUgdGhhdCBwcmVkaWN0cyB3aGVuIGV2ZW50cyB3aWxsIHRha2UgcGxhY2UgYmFzZWQgb24gYWNjdW11bGF0ZWQgZGVncmVlIGRheXMuDQoNCi0gICBQaGVub2xvZ3kgdGFibGVzIGFsc28gdGVsbCB5b3Ugd2hlbiB0byBzdGFydCBjb3VudGluZyBkZWdyZWUgZGF5cy4gVGhpcyBjb3VsZCBiZSBhIGNhbGVuZGFyIGV2ZW50LCBvciB0aGUgZGF0ZSBvZiBhbiBvYnNlcnZhdGlvbiBzdWNoIGFzIHdoZW4geW91IHNlZSBlZ2dzIGluIHlvdXIgYnVnIHRyYXBzLg0KDQotICAgVGhlcmUgYXJlIGRpZmZlcmVudCBmb3JtdWxhIGZvciBjb21wdXRpbmcgZGVncmVlIGRheXMsIGluY2x1ZGluZyB0aGUgc2ltcGxlIGF2ZXJhZ2UgbWV0aG9kLCBzaW5nbGUgc2luZSwgc2luZ2xlIHRyaWFuZ2xlLCBkb3VibGUtc2luZSwgYW5kIGRvdWJsZS10cmlhbmdsZS4gVGhlIHBoZW5vbG9neSB0YWJsZSB3aWxsIHRlbGwgeW91IHdoaWNoIG9uZSB0byB1c2UuDQoNCi0gICBZb3UgY2FuIGNvbXB1dGUgZGVncmVlIGRheXMgdXNpbmcgdGhlIFtkZWdkYXldKGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vZGVnZGF5LykgcGFja2FnZS4NCg0KIyMgRXhhbXBsZTogTmF2ZWwgT3Jhbmdld29ybSBEZWdyZWUgRGF5cw0KDQpJbiB0aGlzIGV4YW1wbGUsIHdlJ2xsIHVzZSBkZWdyZWUgZGF5cyB0byBleHBsb3JlIHRoZSB0aW1pbmcgb2YgZ2VuZXJhdGlvbnMgb2YgTmF2ZWwgT3Jhbmdld29ybSBsaXZpbmcgaW4gYW4gYWxtb25kIG9yY2hhcmQuIFdlIGJlZ2luIGJ5IGxvb2tpbmcgdXAgd2hpY2ggZGVncmVlIGRheSBmb3JtdWxhIHRvIHVzZSwgYW5kIHRoZSByYW5nZSBvZiB1c2FibGUgdGVtcGVyYXR1cmVzLCBmcm9tIFtVQyBJUE1dKGh0dHA6Ly93d3cuaXBtLnVjZGF2aXMuZWR1L2NhbGx1ZHQuY2dpL0RETU9ERUw/TU9ERUw9Tk9XJkNST1A9YWxtb25kcykgd2Vic2l0ZSwgd2hpY2ggdGVsbHMgdXM6DQoNCjo6OiBzaGFkZWQtYm94DQoqKk5hdmVsIE9yYW5nZXdvcm0gaW4gQWxtb25kcyoqDQoNCipMb3dlci91cHBlciB0aHJlc2hvbGQqOiA1NS85NMKwRg0KDQoqQ2FsY3VsYXRpb24vdXBwZXIgY3V0b2ZmIG1ldGhvZCo6IHNpbmdsZSBzaW5lL2hvcml6b250YWwNCg0KKkJpb2ZpeCo6IFRoZSBmaXJzdCBiaW9maXggaXMgdGhlIGJlZ2lubmluZyBvZiBhIGNvbnNpc3RlbnQgaW5jcmVhc2UgaW4gZWdnIGxheWluZyBvbiBlZ2cgdHJhcHMuIFdoZW4gYXQgbGVhc3QgNzUlIG9mIHRoZSBlZ2cgdHJhcHMgaW4gYSBnaXZlbiBsb2NhdGlvbiBzaG93IGluY3JlYXNlcyBpbiB0aGUgbnVtYmVyIG9mIGVnZ3Mgb24gdHdvIGNvbnNlY3V0aXZlIG1vbml0b3JpbmcgZGF0ZXMsIHRoZSBiaW9maXggaXMgdGhlIGZpcnN0IG9mIHRob3NlIHR3byBkYXRlcy4NCg0KKkRlZ3JlZSBEYXkgRXZlbnRzOioNCg0KLSAgIHRoZSBiZXN0IHRpbWUgdG8gc3ByYXkgaXMgd2hlbiAxMDAgTk9XLUREIGhhdmUgYWNjdW11bGF0ZWQgYWZ0ZXIgYmlvZml4DQoNCi0gICB0aGUgbmV4dCBnZW5lcmF0aW9uIG9mIGFkdWx0cyBjYW4gYmUgZXhwZWN0ZWQgaW4gMTA1NiBOT1ctREQgYWZ0ZXIgYmlvZml4DQo6OjoNCg0KVG8gY29tcHV0ZSBkZWdyZWUgZGF5cywgd2Ugc3RhcnQgYnkgcHV0dGluZyB0aGUgZGFpbHkgbWluIGFuZCBtYXggdGVtcGVyYXR1cmVzIGluIHNlcGFyYXRlIGNvbHVtbnM6DQoNCmBgYHtyIGNodW5rMTV9DQpwdDFfbWlubWF4X3RibCA8LSBwdDFfdGJsIHw+IA0KICBtdXRhdGUoZHQgPSBhcy5EYXRlKGR0KSwNCiAgICAgICAgIHRlbXBfZiA9IHNldF91bml0cyh2YWwsIGRlZ0YpKSB8PiANCiAgcGl2b3Rfd2lkZXIoaWRfY29scyA9IGR0LCBuYW1lc19mcm9tID0gc2x1ZywgdmFsdWVzX2Zyb20gPSB0ZW1wX2YpIHw+IA0KICByZW5hbWUodG1pbiA9IHRtbW5fZGF5X2dyaWRtZXQsIHRtYXggPSB0bW14X2RheV9ncmlkbWV0KSB8PiANCiAgbXV0YXRlKHRtYXggPSBpZl9lbHNlKHRtYXggPCB0bWluLCB0bWluLCB0bWF4KSkNCg0KaGVhZChwdDFfbWlubWF4X3RibCkNCmBgYA0KDQpcDQoNCk5vdyB3ZSBjYW4gY29tcHV0ZSBOT1cgQWxtb25kIGRlZ3JlZSBkYXlzOg0KDQpgYGB7ciBjaHVuazE2fQ0KbGlicmFyeShkZWdkYXkpDQp0aHJlc2hfbG93IDwtIDU1DQp0aHJlc2hfdXAgPC0gOTQNCg0KcHQxX25vd2RkX3RibCA8LSBwdDFfbWlubWF4X3RibCB8PiANCiAgbXV0YXRlKG5vd19kZCA9IGRkX3NuZ19zaW5lKGRhaWx5X21pbiA9IHRtaW4sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGFpbHlfbWF4ID0gdG1heCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlc2hfbG93ID0gdGhyZXNoX2xvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlc2hfdXAgPSB0aHJlc2hfdXApKQ0KICANCmhlYWQocHQxX25vd2RkX3RibCkNCmBgYA0KDQpcDQoNCiMjIyBBcHBseWluZyBEZWdyZWUgRGF5czogUGVzdCBNYW5hZ2VtZW50DQoNClN1cHBvc2UgYW4gYWxtb25kIGdyb3dlciBzZWVzIGEgY29uc2lzdGVudCBpbmNyZWFzZSBpbiBlZ2cgbGF5aW5nIG9uIGVnZyB0cmFwcyBvbiAqKkFwcmlsIDE4LCAyMDExKiogKGkuZS4sIHRoZSBiaW9maXggZXZlbnQpLiBUaGUgVUMgSVBNIHdlYnNpdGUgc2F5cyB0aGUgYmVzdCBkYXkgdG8gc3ByYXkgaXMgd2hlbiAxMDAgREQgaGF2ZSBhY2N1bXVsYXRlZCBhZnRlciB0aGUgYmlvZml4LCBhbmQgdGhlIG5leHQgZ2VuZXJhdGlvbiBvZiBhZHVsdHMgY2FuIGJlIGV4cGVjdGVkIDEwNTYgREQgYWZ0ZXIgYmlvZml4LiBGaW5kIHRoZSBkYXRlcyBmb3IgdGhlc2UgZXZlbnRzLg0KDQpTdGVwIDEgaXMgdG8gZmlsdGVyIHRoZSBkYXRlcyB0byBiZWdpbiB3aXRoIHRoZSBkYXkgYWZ0ZXIgYmlvZml4LCBhbmQgYWRkIGEgY29sdW1uIGZvciBhY2N1bXVsYXRlZCBkZWdyZWUgZGF5czoNCg0KYGBge3IgY2h1bmsxN30NCnB0MV9ub3dkZF8yMDExX3RibCA8LSBwdDFfbm93ZGRfdGJsIHw+IA0KICBmaWx0ZXIoZHQgPiBhcy5EYXRlKCIyMDExLTA0LTE4IiksIGR0IDw9IGFzLkRhdGUoIjIwMTEtMTAtMzEiKSkgfD4gDQogIG11dGF0ZShub3dfZGRfYWNjID0gY3Vtc3VtKG5vd19kZCkpIA0KDQpoZWFkKHB0MV9ub3dkZF8yMDExX3RibCkgIA0KYGBgDQoNClwNCg0KRmluZCB0aGUgZGF0ZSB3aGVuIDEwMCBERCBoYXZlIGFjY3VtdWxhdGVkOg0KDQpgYGB7ciBjaHVuazE4fQ0KcHQxX25vd2RkXzIwMTFfdGJsIHw+IA0KICBmaWx0ZXIobm93X2RkX2FjYyA+PSAxMDApIHw+IA0KICBzbGljZSgxKSANCmBgYA0KDQpcDQoNCkFuZCAxMDU2IEREOg0KDQpgYGB7ciBjaHVuazE5fQ0KcHQxX25vd2RkXzIwMTFfdGJsIHw+IA0KICBmaWx0ZXIobm93X2RkX2FjYyA+PSAxMDU2KSB8PiANCiAgc2xpY2UoMSkgfD4gDQogIHB1bGwoZHQpDQpgYGANCg0KXA0KDQojIyMgQXBwbHlpbmcgRGVncmVlIERheXM6IEVzdGltYXRpbmcgdGhlIGJpb2ZpeCB3aGVuIHlvdSBkb24ndCBoYXZlIG9ic2VydmF0aW9ucw0KDQpQYXRoYWsgZXQgYWwgKFsyMDIxXShodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLnNjaXRvdGVudi4yMDIwLjE0MjY1NykpIGVzdGltYXRlIHRoZSBlbWVyZ2VuY2Ugb2YgdGhlIGZpcnN0IGZsaWdodCBvZiBOYXZlbCBPcmFuZ2V3b3JtIGFzIG9jY3VycmluZyB3aGVuIDMwMCDCsEYgTk9XIEREIGhhdmUgYWNjdW11bGF0ZWQgYWZ0ZXIgSmFudWFyeSAxc3QuIENvbXB1dGUgd2hlbiB0aGlzIHRocmVzaG9sZCB3YXMgcmVhY2hlZCBmb3IgdGhlIGhpc3RvcmljIHBlcmlvZC4NCg0KU3RlcCAxIGlzIHRvIHJlbW92ZSBpbmNvbXBsZXRlIHllYXJzIGFuZCBjb21wdXRlIGFjY3VtdWxhdGVkIGRlZ3JlZSBkYXlzIGZvciBlYWNoIHllYXI6DQoNCmBgYHtyIGNodW5rMjB9DQpwdDFfbm93ZGRfeXJfYWNjX3RibCA8LSBwdDFfbm93ZGRfdGJsIHw+IA0KICBtdXRhdGUoeWVhciA9IGx1YnJpZGF0ZTo6eWVhcihkdCkpIHw+ICAgICMjIGFkZCBhIHllYXIgY29sdW1uDQogIGZpbHRlcih5ZWFyID49IDIwMTEpIHw+ICAgICAgICAgICAgICAgICAgIyMgcmVtb3ZlIDIwMTAgKGluY29tcGxldGUgeWVhcikNCiAgZ3JvdXBfYnkoeWVhcikgfD4gICAgICAgICAgICAgICAgICAgICAgICAjIyBncm91cCBieSB5ZWFycw0KICBtdXRhdGUoZGRfYWNjX3lyID0gY3Vtc3VtKG5vd19kZCkpICAgICAgICMjIGZvciBlYWNoIHllYXIsIGNvbXB1dGUgYWNjdW11bGF0ZWQgREQNCg0KaGVhZChwdDFfbm93ZGRfeXJfYWNjX3RibCkNCmBgYA0KDQpcDQoNClN0ZXAgMjogb24gd2hhdCBkYXkgZWFjaCB5ZWFyIGRpZCB3ZSByZWFjaCAzMDAgREQgwrBGPw0KDQpgYGB7ciBjaHVuazIxfQ0KcHQxX25vd2RkX3lyX2FjY190YmwgfD4gDQogIGZpbHRlcihkZF9hY2NfeXIgPj0gMzAwKSB8PiANCiAgc3VtbWFyaXNlKGZpcnN0XzMwMGRkX2RhdGUgPSBtaW4oZHQpLCBmaXJzdF8zMDBkZF9qZGF5ID0geWRheShtaW4oZHQpKSkNCmBgYA0KDQpcDQoNCiMgQWRkaW5nIGEgY29sdW1uIHdpdGggdG9tb3Jyb3cncyB0ZW1wZXJhdHVyZQ0KDQpTb21lIGRlZ3JlZSBkYXkgZm9ybXVsYXMgKGkuZS4sIGRvdWJsZS1zaW5lIGFuZCBkb3VibGUtdHJpYW5nbGUpIHJlcXVpcmUgdGhlIG5leHQgZGF5J3MgbWluaW11bSB0ZW1wIHRvIGJlIGluY2x1ZGVkLiBXZSBjYW4gYWRkIHRoaXMgdG8gb3VyIGRhdGEgZnJhbWUgdXNpbmcgYGRwbHlyOjpsZWFkKClgLg0KDQpGb3IgZXhhbXBsZSwgc3RhcnRpbmcgd2l0aDoNCg0KYGBge3IgY2h1bmsyMn0NCmhlYWQocHQxX21pbm1heF90YmwpDQpgYGANCg0KXA0KDQpBZGQgdGhlIG5leHQgZGF5J3MgbWluaW11bSB0ZW1wZXJhdHVyZToNCg0KYGBge3IgY2h1bmsyM30NCnB0MV9taW5tYXhfbmV4dG1pbl90YmwgPC0gcHQxX21pbm1heF90YmwgfD4gDQogIG11dGF0ZSh0bWluX25leHQgPSBsZWFkKHRtaW4sIG4gPSAxKSkNCg0KaGVhZChwdDFfbWlubWF4X25leHRtaW5fdGJsKQ0KYGBgDQoNClwNCg0KTm90ZSBob3cgYGxlYWQoKWAgdHJlYXRzIHRoZSBsYXN0IHJvdzoNCg0KYGBge3IgY2h1bmsyNH0NCnRhaWwocHQxX21pbm1heF9uZXh0bWluX3RibCkNCmBgYA0KDQpcDQoNCk5vdyB3ZSBjYW4gY29tcHV0ZSBkZWdyZWUgZGF5cyB1c2luZyB0aGUgZG91YmxlLXNpbmUgbWV0aG9kOg0KDQpgYGB7ciBjaHVuazI1fQ0KdGhyZXNoX2xvdyA8LSA1NQ0KdGhyZXNoX3VwIDwtIDk0DQoNCnB0MV9taW5tYXhfbmV4dG1pbl90YmwgfD4NCiAgbXV0YXRlKG5vd19kZF9kYmxzaW5lID0gZGRfZGJsX3NpbmUoZGFpbHlfbWluID0gdG1pbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGFpbHlfbWF4ID0gdG1heCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5leHRkYXlfbWluID0gdG1pbl9uZXh0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlc2hfbG93ID0gdGhyZXNoX2xvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaF91cCA9IHRocmVzaF91cCkpIHw+IA0KICBoZWFkKCkNCmBgYA0KDQpcDQoNCiMgSW50ZXJwb2xhdGluZyBIb3VybHkgVGVtcHMNCg0KU29tZSBhZ3JvY2xpbWF0ZSBtZXRyaWNzIHJlcXVpcmUgaG91cmx5IHRlbXBlcmF0dXJlcywgc3VjaCBhcyBjaGlsbCBob3VycyBhbmQgZnJvc3QgZXhwb3N1cmUgKFtQYXJrZXIgZXQgYWwsIDIwMjFdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDE2L2ouc2NpdG90ZW52LjIwMjAuMTQzOTcxKSkuIElkZWFsbHkgeW91IHdvdWxkIGhhdmUgaG91cmx5IHRlbXBlcmF0dXJlIGRhdGEgZm9yIHRoZXNlIG1ldHJpY3MsIGJ1dCBpZiBub3QgeW91IGNhbiBpbnRlcnBvbGF0ZSBob3VybHkgdGVtcHMgZnJvbSB0aGUgZGFpbHkgbWluIGFuZCBtYXguDQoNCk9uZSBvZiB0aGUgYmVzdCBhbGdvcml0aG1zIGZvciBpbnRlcnBvbGF0aW5nIGhvdXJseSB0ZW1wcyBjb21lcyBmcm9tIExpbnZpbGwgKFsxOTkwXShodHRwczovL2RvaS5vcmcvMTAuMjEyNzMvSE9SVFNDSS4yNS4xLjE0KSkuIFRoaXMgbWV0aG9kIHVzZXMgYW4gaWRlYWxpemVkIHNpbmUgY3VydmUgdG8gZGVzY3JpYmUgZGF5dGltZSB3YXJtaW5nLCBhbmQgYSBsb2dhcml0aG1pYyBkZWNheSBmdW5jdGlvbiBmb3IgbmlnaHR0aW1lIGNvb2xpbmcuIFRoZSB0cmFuc2l0aW9uIGJldHdlZW4gd2FybWluZyBhbmQgY29vbCBpcyBhIGZ1bmN0aW9uIG9mIHRoZSBkYXkgbGVuZ3RoLCB3aGljaCBpbiB0dXJuIGlzIG1vZGVsZWQgYnkgc3VucmlzZSBhbmQgc3Vuc2V0LCB3aGljaCBpbiB0dXJuIGlzIG1vZGVsZWQgYnkgbGF0aXR1ZGUuDQoNClRoZSBbY2hpbGxSXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPWNoaWxsUikgcGFja2FnZSBoYXMgYSBmdW5jdGlvbiBgbWFrZV9ob3VybHlfdGVtcHMoKWAgaGF0IGFwcGxpZXMgdGhlIExpbnZpbGwgbWV0aG9kLiBZb3UgbmVlZCB0byBmZWVkIGl0IGEgZGF0YSBmcmFtZSB0aGF0IGlzIGZvcm1hdHRlZCB3aXRoIHNwZWNpZmljIGNvbHVtbnMsIHBsdXMgYSBsYXRpdHVkZSB2YWx1ZSAoW2RldGFpbHNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jaGlsbFIvdmlnbmV0dGVzL2hvdXJseV90ZW1wZXJhdHVyZXMuaHRtbCkpLg0KDQpUaGUgZmlyc3Qgc3RlcCBpcyB0byBtYWtlIHRoZSBtaW4gYW5kIG1heCB0ZW1wcyBzZXBhcmF0ZSBjb2x1bW5zOg0KDQpgYGB7ciBjaHVuazI2fQ0KcHQxX21pbm1heF90YmwgPC0gcHQxX3RibCB8PiANCiAgbXV0YXRlKGR0ID0gYXMuRGF0ZShkdCksDQogICAgICAgICB0ZW1wX2YgPSBzZXRfdW5pdHModmFsLCBkZWdGKSkgfD4gDQogIHBpdm90X3dpZGVyKGlkX2NvbHMgPSBkdCwgbmFtZXNfZnJvbSA9IHNsdWcsIHZhbHVlc19mcm9tID0gdGVtcF9mKSB8PiANCiAgcmVuYW1lKHRtaW4gPSB0bW1uX2RheV9ncmlkbWV0LCB0bWF4ID0gdG1teF9kYXlfZ3JpZG1ldCkgfD4gDQogIG11dGF0ZSh0bWF4ID0gaWZfZWxzZSh0bWF4IDwgdG1pbiwgdG1pbiwgdG1heCkpDQoNCmhlYWQocHQxX21pbm1heF90YmwpDQpgYGANCg0KXA0KDQpOZXh0LCB3ZSBoYXZlIHRvIGFkZCBhIGNvdXBsZSBvZiBjb2x1bW5zLCBhbmQgY2hhbmdlIHRoZSBjb2x1bW4gbmFtZXMsIHRvIG1hdGNoIHRoZSBmb3JtYXQgZXhwZWN0ZWQgYnkgYGNoaWxsUjo6bWFrZV9ob3VybHlfdGVtcHMoKWAgKGFzIGRlc2NyaWJlZCBvbiB0aGUgaGVscCBwYWdlKS4gVGhpcyBpcyBhbiBleGFtcGxlIG9mIGRhdGEgd3JhbmdsaW5nIHRvICd3b3JrIGJhY2t3YXJkcycgZnJvbSB3aGF0IHlvdSBuZWVkIHRvIHdoYXQgeW91J3ZlIGdvdDoNCg0KYGBge3IgY2h1bmsyN30NCnB0MV9taW5tYXhfY2hpbGxyX3RibCA8LSBwdDFfbWlubWF4X3RibCB8PiANCiAgbXV0YXRlKFllYXIgPSBsdWJyaWRhdGU6OnllYXIoZHQpLA0KICAgICAgICAgTW9udGggPSBsdWJyaWRhdGU6Om1vbnRoKGR0KSwNCiAgICAgICAgIERheSA9IGx1YnJpZGF0ZTo6ZGF5KGR0KSwNCiAgICAgICAgIFRtYXggPSBhcy5udW1lcmljKHRtYXgpLA0KICAgICAgICAgVG1pbiA9IGFzLm51bWVyaWModG1pbikpIHw+IA0KICBzZWxlY3QoREFURSA9IGR0LCBZZWFyLCBNb250aCwgRGF5LCBUbWF4LCBUbWluKQ0KDQpoZWFkKHB0MV9taW5tYXhfY2hpbGxyX3RibCkNCmBgYA0KDQpcDQoNCk5vdyB3ZSBjYW4gY2FsbCBgY2hpbGxSOjptYWtlX2hvdXJseV90ZW1wcygpYCwgYWxzbyBwYXNzaW5nIHRoZSBsYXRpdHVkZSBvZiBvdXIgbG9jYXRpb246DQoNCmBgYHtyIGNodW5rMjh9DQpsaWJyYXJ5KGNoaWxsUikNCg0KcHQxX2Nvb3Jkc1syXQ0KDQpwdDFfaG91cnRlbXBzX3dpZGVfdGJsIDwtIG1ha2VfaG91cmx5X3RlbXBzKGxhdGl0dWRlID0gcHQxX2Nvb3Jkc1syXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhcl9maWxlID0gcHQxX21pbm1heF9jaGlsbHJfdGJsKQ0KaGVhZChwdDFfaG91cnRlbXBzX3dpZGVfdGJsKQ0KYGBgDQoNClwNCg0KYG1ha2VfaG91cmx5X3RlbXBzKClgIGdpdmVzIHVzICp3aWRlKiBkYXRhLiBJdCdzIGdlbmVyYWxseSBlYXNpZXIgdG8gd29yayB3aXRoIGhvdXJseSB0ZW1wZXJhdHVyZSBkYXRhIGluIGEgKmxvbmcqIGZvcm1hdC4gV2UgY2FuIHJlc2hhcGUgdGhlIGhvdXJseSB0ZW1wcyB3aXRoIGB0aWR5cjo6cGl2b3RfbG9uZ2VyKClgLg0KDQpgYGB7ciBjaHVuazI5fQ0KcHQxX2hvdXJ0ZW1wc19sb25nX3RibCA8LSBwdDFfaG91cnRlbXBzX3dpZGVfdGJsIHw+DQogIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoIkhvdXJfIiksDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJIb3VyIiwNCiAgICAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJIb3VyXyIsDQogICAgICAgICAgICAgICBuYW1lc190cmFuc2Zvcm0gPSBsaXN0KEhvdXIgPSBhcy5pbnRlZ2VyKSwNCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ0ZW1wX2YiKSB8Pg0KICBtdXRhdGUoZGF0ZV9ob3VyID0gbHVicmlkYXRlOjptYWtlX2RhdGV0aW1lKHllYXIgPSBZZWFyLCBtb250aCA9IE1vbnRoLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRheSA9IERheSwgaG91ciA9IEhvdXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHogPSAiQW1lcmljYS9Mb3NfQW5nZWxlcyIpKSB8Pg0KICBzZWxlY3QoZGF0ZV9ob3VyLCB0ZW1wX2YpDQoNCmhlYWQocHQxX2hvdXJ0ZW1wc19sb25nX3RibCkNCmBgYA0KDQpcDQoNClRvIHNlZSB3aGF0IHRoZXNlIGhvdXJseSB0ZW1wZXJhdHVyZXMgbG9vayBsaWtlLCBsZXQncyBwbG90IG9uZSB3ZWVrIG9mIHRoZW06DQoNCmBgYHtyIGNodW5rMzB9DQpwdDFfaG91cnRlbXBzX2xvbmdfdGJsIHw+IA0KICBmaWx0ZXIoZGF0ZV9ob3VyID49IGFzLkRhdGUoIjIwMTEtMDEtMDEiKSwgZGF0ZV9ob3VyIDw9IGFzLkRhdGUoIjIwMTEtMDEtMDciKSkgfD4gDQogIGdncGxvdChhZXMoeCA9IGRhdGVfaG91ciwgeSA9IHRlbXBfZikpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj0icmVkIiksIHNob3cubGVnZW5kID0gRkFMU0UpDQpgYGANCg0KXA0KDQojIENoaWxsIFBvcnRpb25zDQoNCkNoaWxsIHBvcnRpb25zIGFuZCBjaGlsbCBob3VycyBhcmUgbGlrZSBkZWdyZWUgZGF5cywgYnV0IGZvciBjb2xkIHRlbXBlcmF0dXJlcy4gQ2VydGFpbiBwaGVub2xvZ3kgZXZlbnRzLCBsaWtlIGJsb29taW5nIGluIGZydWl0IGFuZCBudXQgdHJlZXMsIGFyZSBjb3JyZWxhdGVkIHdpdGggdGhlIG5ldCBhbW91bnQgb2YgY29sZCB0ZW1wZXJhdHVyZXMgdGhlIHRyZWVzIGJlZW4gZXhwb3NlZCB0by4gVGhpcyByZWZsZWN0IGFuIGV2b2x1dGlvbmFyeSBhZGFwdGF0aW9uIHRyZWVzIGhhdmUgZGV2ZWxvcGVkIHRvIHByZXZlbnQgYmxvb21pbmcgdG9vIGVhcmx5IGFuZCBoZW5jZSByaXNraW5nIGRhbWFnZSBmcm9tIGEgbGF0ZSBmcm9zdC4gRGlmZmVyZW50IGZydWl0IGFuZCBudXQgdHJlZXMgaGF2ZSBkZXZlbG9wZWQgW2RpZmZlcmVudCB0aHJlc2hvbGRzIG9mIGNoaWxsIHBvcnRpb25zXShodHRwczovL2ZydWl0c2FuZG51dHMudWNhbnIuZWR1L1dlYXRoZXJfU2VydmljZXMvY2hpbGxpbmdfYWNjdW11bGF0aW9uX21vZGVscy9Dcm9wQ2hpbGxSZXEvKSB0aGF0IHRlbGwgdGhlbSB3aGVuIGl0cyB0aW1lIHRvICd3YWtlIHVwJyBmcm9tIHRoZWlyIHdpbnRlciBkb3JtYW5jeS4NCg0KV2UgY2FuIGNvbXB1dGUgYWNjdW11bGF0ZWQgY2hpbGwgcG9ydGlvbnMgYnkgcGFzc2luZyBhIHZlY3RvciBvZiBob3VybHkgdGVtcGVyYXR1cmUgdmFsdWVzIHRvIGBjaGlsbFI6OkR5bmFtaWNfTW9kZWwoKWAuIE5vdGUgaG93ZXZlciB0aGF0IGBEeW5hbWljX01vZGVsKClgIGV4cGVjdHMgdGhlIHRlbXBlcmF0dXJlcyB0byBiZSBpbiBDZWxzaXVzLCBzbyB0aGUgZmlyc3Qgc3RlcCBpcyB0byBhZGQgYSBjb2x1bW4gd2l0aCB0aGUgdGVtcGVyYXR1cmUgaW4gwrBDIHVzaW5nIHRoZSB1bml0cyBwYWNrYWdlOg0KDQpgYGB7ciBjaHVuazMxfQ0KcHQxX2hvdXJ0ZW1wc19kZWdjX3RibCA8LSBwdDFfaG91cnRlbXBzX2xvbmdfdGJsIHw+IA0KICBtdXRhdGUodGVtcF9jID0gYXMubnVtZXJpYyhzZXRfdW5pdHMoc2V0X3VuaXRzKHRlbXBfZiwgZGVnRiksIGRlZ0MpKSkNCiAgICAgICAgIA0KaGVhZChwdDFfaG91cnRlbXBzX2RlZ2NfdGJsKSAgICAgICAgDQpgYGANCg0KXA0KDQpOb3cgd2UgY2FuIGNvbXB1dGUgYWNjdW11bGF0ZWQgY2hpbGwgcG9ydGlvbnMgdXNpbmcgYER5bmFtaWNfTW9kZWwoKWA6DQoNCmBgYHtyIGNodW5rMzJ9DQpwdDFfY2hpbGxwb3J0X3RibCA8LSBwdDFfaG91cnRlbXBzX2RlZ2NfdGJsIHw+IA0KICBtdXRhdGUoY2hpbGxwb3J0X2FjYyA9IGNoaWxsUjo6RHluYW1pY19Nb2RlbCh0ZW1wX2MsIHN1bW0gPSBUUlVFKSkNCg0KaGVhZChwdDFfY2hpbGxwb3J0X3RibCkNCmBgYA0KDQpcDQoNClBsb3QgYWNjdW11bGF0ZWQgY2hpbGwgcG9ydGlvbnMgZm9yIG9uZSBzZWFzb246DQoNCmBgYHtyIGNodW5rMzN9DQpwdDFfY2hpbGxwb3J0X3RibCB8PiANCiAgZmlsdGVyKGRhdGVfaG91ciA+PSBhcy5EYXRlKCIyMDEwLTEwLTAxIiksIGRhdGVfaG91ciA8PSBhcy5EYXRlKCIyMDExLTA3LTAxIikpIHw+IA0KICBnZ3Bsb3QoYWVzKHggPSBkYXRlX2hvdXIsIHkgPSBjaGlsbHBvcnRfYWNjKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGdndGl0bGUoIkNoaWxsIFBvcnRpb25zIDIwMTAtMTEiKQ0KYGBgDQoNClwNCg0KIyBDaGFsbGVuZ2UgUXVlc3Rpb24NCg0KTmV3IFN0YXIgQ2hlcnJpZXMgcmVxdWlyZSBbNTQgY2hpbGwgcG9ydGlvbnNdKGh0dHBzOi8vZnJ1aXRzYW5kbnV0cy51Y2Fuci5lZHUvV2VhdGhlcl9TZXJ2aWNlcy9jaGlsbGluZ19hY2N1bXVsYXRpb25fbW9kZWxzL0Nyb3BDaGlsbFJlcS8gIjU0IGNoaWxsIHBvcnRpb25zIikgdG8gY29tZSBvdXQgb2YgdGhlaXIgd2ludGVyIGRvcm1hbmN5LiBJZGVudGlmeSB0aGUgZGF0ZSB3aGVuIHRoaXMgbGV2ZWwgb2YgY2hpbGwgd2FzIHJlYWNoZWQgZWFjaCB5ZWFyIG9mIHRoZSBvYnNlcnZlZCBkYXRhc2V0LiBGb3IgdGhlIHB1cnBvc2VzIG9mIHRoZSBleGVyY2lzZSwgY29uc2lkZXIgdGhlIGNoaWxsIHNlYXNvbiB0byBnbyBmcm9tIE5vdiAxIHRocnUgSnVuZSAzMC4NCg0KKipIaW50OioqIFRoaXMgd2lsbCBsb29rIGEgbG90IGxpa2UgdGhlIHF1ZXN0aW9uIGFib3V0IHRoZSBkYXRlIHdoZW4gZGVncmVlIGRheXMgd2VyZSByZWFjaGVkLiBbQW5zd2VyXShodHRwOi8vYml0Lmx5LzNWSDJVS3MpDQoNCmBgYHtyIGNodW5rMzR9DQpwdDFfaG91cnRlbXBzX2RlZ2NfdGJsIHw+IA0KICBtdXRhdGUoeWVhciA9IHllYXIoZGF0ZV9ob3VyKSwgbW9udGggPSBtb250aChkYXRlX2hvdXIpKSB8PiANCiAgZmlsdGVyKGRhdGVfaG91ciA+PSBhcy5EYXRlKCIyMDEwLTExLTAxIikgJiAobW9udGggPj0gMTEgfCBtb250aCA8PSA2KSkgfD4gDQogIG11dGF0ZShjaGlsbF9zZWFzb24gPSB5ZWFyICsgaWZfZWxzZShtb250aCA+PSAxMSwgMSwgMCkpIHw+IA0KICBncm91cF9ieShjaGlsbF9zZWFzb24pIHw+IA0KICBtdXRhdGUoY2hpbGxwb3J0X2FjYyA9IGNoaWxsUjo6RHluYW1pY19Nb2RlbCh0ZW1wX2MpKSB8PiANCiAgZmlsdGVyKGNoaWxscG9ydF9hY2MgPj0gNTQpIHw+IA0KICBzdW1tYXJpc2UocmVxX2NoaWxsX3JlYWNoZWQgPSBtaW4oZGF0ZV9ob3VyKSkgfD4gDQogIG11dGF0ZShtb250aCA9IG1vbnRoKHJlcV9jaGlsbF9yZWFjaGVkKSwgZGF5ID0gZGF5KHJlcV9jaGlsbF9yZWFjaGVkKSkNCmBgYA0KDQpcDQoNCiMgRW5kIQ0KDQpSZW1lbWJlciB0byBzYXZlIHRoZSBOb3RlYm9vayB0byBnZW5lcmF0ZSBhIEhUTUwgdmVyc2lvbiB0aGF0IGluY2x1ZGVzIGFsbCBleGVjdXRlZCBjb2RlIHRoYXQgeW91IGNhbiBzYXZlIGZvciBrZWVwcyENCg==