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:
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==