Summary
In this Notebook we will learn how to:
- find the closest CIMIS weather station
- import weather station data for a single season and a single
location from CIMIS
- filter weather data based on dates
- plot weather data
- compute rolling averages
- compute threshold based metrics, including hot days and frost
days
- compte the last spring freeze date
- find and quantify spells of threshold based metrics, such as heat
waves
- reshape data to put separate variables in separate columns
- compute the diurnal temperature range
- compute irrigation requirements based on reference
evapotranspiration
Load Packages
First load the packages we’ll need below:
library(dplyr)
library(tidyr)
library(ggplot2)
library(sf)
library(cimir)
library(zoo)
library(leaflet)
Import Weather Data from CIMIS
Agroclimate metrics require weather data, such as the minimum and
maximum temperature, precipitation, relative humidity, reference ETo,
and so on. For this exercise, we’ll import weather records from a
station in the CIMIS
network.
Hourly or Daily Weather Data?
In theory, hourly data should be a better predictor of plant or
insect growth, because it captures nuances and processes on a smaller
time scale. Hourly data is also not that hard to get these days, at
least for the current time period.
However in practice most of the crop and pest models in use are based
on daily data, so if you want to use those models you
to provide daily data. The rest of this Notebook therefore will use
daily weather data.
CIMIS
CIMIS is a network of ~150
automated weather stations run by CADWR for the purposes of informing
irrigators. It is a popular source of weather data because of the
coverage area, the stations record hourly and daily values of main
weather variables. The data are also freely available through various
websites and an API.
The cimir
package provides functions to import data from the CIMIS network
directly into R. Many of these functions require creating a CIMIS account
so you can get a CIMIS API key
(free).
Map the CIMIS Stations
The easiest way to get CIMIS data is if you know which station. Let’s
make a map of the CIMIS Stations. Step 1 is to get the list of active
stations:
## If you have a CIMIS key, you can uncomment and run the following:
## cimir::set_key("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
## stations_all_tbl <- cimir::cimis_station()
stations_all_tbl <- read.csv("./data/cimis_stations_all.csv")
head(stations_all_tbl)
Next we’ll do a little data cleaning, keeping only the columns we’ll
need for to map the active stations:
stations_cleaned_tbl <- stations_all_tbl |>
filter(IsActive == "True") |>
select(StationNbr, Name, HmsLatitude, HmsLongitude) |>
distinct() |>
transmute(station_id = as.numeric(StationNbr),
name = Name,
lon = as.numeric(gsub("^.*/ ", "", HmsLongitude)),
lat = as.numeric(gsub("^.*/ ", "", HmsLatitude)))
head(stations_cleaned_tbl)
Turn this into a spatial object and map it with leaflet:
stations_cleaned_sf <- stations_cleaned_tbl |>
mutate(title = paste0(name, " (#", station_id, ")")) |>
st_as_sf(coords = c("lon", "lat"), crs = 4326) |>
select(title)
leaflet(stations_cleaned_sf) |>
addTiles() |>
addCircleMarkers(radius = 5, popup = ~title)
Query a CIMIS Station
To see what weather variables are available, run
cimis_items()
:
cimis_items()
You can retrieve data using cimis_data()
. Let’s get
daily temperature, precipitation, and reference ETo for the Verona
station (#235), north of Sacramento.
# If you've entered a CIMIS key, uncomment the following line.
# Otherwise, run the next command to load the saved data
# cimis_verona22_tbl <- cimis_data(targets = 235, start.date = "2021-10-01", end.date = "2022-09-30",
# items = "day-air-tmp-max,day-air-tmp-min,day-eto,day-precip")
cimis_verona22_tbl <- readRDS("./data/cimis_verona22.Rds")
head(cimis_verona22_tbl)
Notes about the data frame returned by
cimis_data()
- our data frame contains 4 weather variables in a long (as
opposed to wide) format
- the
Date
column is already formatted as a date
object
Julian
is the day of the year (0..365)
Qc
is a quality control flag (details)
Time Filtering
To filter rows based on the date, you need a column as a Date or time
(POSIXct) object. The lubridate
package has functions to
convert character and number columns into dates.
Once you have a date column, filtering is pretty easy. For example to
pull the records for the 2022 growing season from
April 15 thru Sept 30, 2022:
grwsn_vals_tbl <- cimis_verona22_tbl |>
select(Date, Item, Value, Qc) |>
filter(Date >= as.Date("2022-04-15"), Date <= as.Date("2022-09-30"))
grwsn_vals_tbl |> slice(1:20)
Plot the daily high temps
We can plot daily high temperature at this location with a little
help from dplyr and ggplot:
grwsn_dailymax_tbl <- grwsn_vals_tbl |>
filter(Item == "DayAirTmpMax") |>
rename(max_temp = Value)
ggplot(grwsn_dailymax_tbl, mapping = aes(x = Date, y = max_temp)) +
geom_line() +
labs(title = "Daily High Temps",
subtitle = "Verona CIMIS Station, Spring 2022")
Rolling Averages
Rolling / moving averages are generically useful for smoothing out
the bumps in a time series. For crop management you may not want to
smooth out the bumps, but other applications are easier to address by
looking at trends on a weekly or longer time period.
Moving averages can also be useful to identify multi-day extreme
events. For example, 5 consecutive days over 95 °F is killer for
tomatoes. Computing the 5-day rolling average would be one way to
identify when tomatoes might be in trouble (see also a threshold
technique, coming up next).
You can compute a rolling average with zoo::rollmean()
.
k
is the rolling window size (should be an odd number). You
should also pass fill = NA
, which tells it to assign NA as
the rolling average for the first and last days.
grwsn_dailymax_movavg_tbl <- grwsn_dailymax_tbl |>
mutate(dailymax_avg5d = zoo::rollmean(max_temp, k=5, fill=NA))
ggplot(grwsn_dailymax_movavg_tbl, mapping = aes(x = Date, y = dailymax_avg5d)) +
geom_line() +
labs(title = "Daily High Temps (5-day moving average)",
subtitle = "Verona CIMIS Station, Spring 2022")
Challenge Question #1
Compute the daily high temperature rolling average for an 11 day
window, then plot it. Answer
## Your answer here
grwsn_dailymax_tbl |>
mutate(dailymax_avg5d = zoo::rollmean(max_temp, k=11, fill=NA)) |>
ggplot(mapping = aes(x = Date, y = dailymax_avg5d)) +
geom_line() +
labs(title = "Daily High Temps (11-day moving average)",
subtitle = "Verona CIMIS Station, Spring 2022")
Threshold Methods
Many agroclimate metrics are defined by a threshold value. The
threshold may be in reference to the range of variability at that
location in the historic period, or it may be in reference to when some
kind of physical or biological change happens.
‘extreme heat’ and ‘extreme precipitation’ generally use a
threshhold based on historic values for that location
‘hot days’ are defined by a threshold temperature that affects
crops (Parker et
al. 2022).
Hot Days
How many ‘hot days’ were there in 2022, where ‘hot’ is defined as
over 38 °C (100.4 °F)?
Testing whether a day was ‘hot’ can be done with a simple comparison
expression:
grwsn_hotyn_tbl <- grwsn_dailymax_tbl |>
mutate(hotyn = max_temp > 100.4)
head(grwsn_hotyn_tbl)
The number of hot days can be computed simply by summing the column
of TRUE/FALSE values:
grwsn_hotyn_tbl$hotyn |> sum()
[1] 28
grwsn_hotyn_tbl$hotyn |> table()
FALSE TRUE
141 28
To see when the hot days occurred, we can overlay the
threshold value on the time series plot:
ggplot(grwsn_dailymax_tbl, mapping = aes(x = Date, y = max_temp)) +
geom_line() +
geom_hline(yintercept = 100.4, color = "red", linewidth = 1) +
labs(title = "Daily High Temps",
subtitle = "Verona CIMIS Station, Spring 2022")
Challenge Question #2
Write an expression that returns the exact dates of hot days. Answer
## Your answer here
grwsn_dailymax_tbl |>
filter(Item == "DayAirTmpMax", max_temp > 100.4) |>
select(Date, max_temp)
Last Spring Freeze
Planting and other crop management practices have to be timed to take
place after the last freeze of the winter. The date of the last freeze
can be calculated by:
Identifying all freeze events from say February thru June, using
a simple threshold test (minimum daily temp ≤ 32 °F)
Find the date associated with the last freeze
Step 1: add a ‘Frost Day’ column:
daily_min_tbl <- cimis_verona22_tbl |>
filter(Item == "DayAirTmpMin", Date >= as.Date("2022-02-01"), Date <= as.Date("2022-06-30")) |>
mutate(frost_day = Value <= 32) |>
select(Date, Item, Value, frost_day)
head(daily_min_tbl)
Step 2. How many frost days were there from February thru June?
daily_min_tbl$frost_day |> table()
FALSE TRUE
134 16
Step 3. What was the last freeze date?
daily_min_tbl |>
filter(frost_day == TRUE) |>
arrange(desc(Date))
daily_min_tbl |>
filter(frost_day == TRUE) |>
slice_max(Date, n = 1) |>
pull(Date)
[1] "2022-04-12"
Spells and Runs
Some climate metrics are defined by a series of days when a threshold
is surpassed. Examples include heatwaves. We may want to know the number
of heatwaves, or the length of the heatwaves.
rle()
can be used to answer these questions. Let’s see
how rle()
works:
x <- c("a", "a", "a", "h", "t", "t", "t", "t", "a", "a", "a", "a", "c", "c", "d", "d", "d")
xrle_lst <- rle(x)
xrle_lst
Run Length Encoding
lengths: int [1:6] 3 1 4 4 2 3
values : chr [1:6] "a" "h" "t" "a" "c" "d"
As you can see, rle()
returns a list with two elements
containing properties of groups of repeated letters. The
values
element contains the letter in each group, and the
lengths
element contains the number of letters (which could
be 1).
Say we’re interested in groups of 3 or more repeated letters. We can
find the number of groups of 3 or more letters by summing up the results
of a logical expression:
(xrle_lst$lengths >= 3) |> sum()
[1] 4
And we can find the average length of these groups with:
xrle_lst$lengths[ xrle_lst$lengths >= 3 ] |> mean()
[1] 3.5
But what if we only wanted the number of ‘runs’ of the letter ‘a’? We
can simply use a compound logical expression:
## Number of groups of 'a' of length 3 or more
((xrle_lst$values == "a") & (xrle_lst$lengths >= 3)) |> sum()
[1] 2
So how many heatwaves where the high temperature was > 100.4 °F
for 3 or more days?
First we create the rle()
list:
hotyn_rle_lst <- rle(grwsn_hotyn_tbl$hotyn)
hotyn_rle_lst
Run Length Encoding
lengths: int [1:27] 40 1 15 1 10 1 4 3 12 1 ...
values : logi [1:27] FALSE TRUE FALSE TRUE FALSE TRUE ...
Next we find the number of groups of TRUE:
((hotyn_rle_lst$values == TRUE) & (hotyn_rle_lst$lengths >= 3)) |> sum()
[1] 4
Multi-Variable Metrics with Separate Columns
Some metrics combine multiple weather station variables, such as the
daily minimum and daily maximum temperature. Both of these variables are
included in our CIMIS data, but they’re mixed in with other variables in
a long format. Remember what we got back from CIMIS:
cimis_verona22_tbl |>
select(Date, Item, Value, Qc, Unit) |>
slice(1:10)
For many multi-variable metrics, it is often easiest to pull out the
variables we need as separate columns. This can be easily done
tidyr::pivot_wider()
. The key arguments we need to give it
are names_from
and values_from
(details):
daily_temps_tbl <- cimis_verona22_tbl |>
filter(Item %in% c("DayAirTmpMin", "DayAirTmpMax")) |>
select(Date, Item, Value) |>
pivot_wider(names_from = Item, values_from = Value)
daily_temps_tbl |> head()
Average Daily Temperature
With the daily minimum and maximum temperature as separate columns,
computing the average temperature is a simple expression:
daily_mean_tbl <- daily_temps_tbl |>
mutate(daily_mean = (DayAirTmpMax + DayAirTmpMin) / 2)
head(daily_mean_tbl)
Diurnal Temperature Range
The Diurnal Temperature Range (DTR) is a useful metric for evaluating
crop suitability, and is simply the maximum daily temperature minus the
minimum:
daily_dtr_tbl <- daily_temps_tbl |>
mutate(DTR = DayAirTmpMax - DayAirTmpMin)
head(daily_dtr_tbl)
Large swings in temperature may represent days when a front passed
through.
ggplot(daily_dtr_tbl, mapping = aes(x = Date, y = DTR)) +
geom_line() +
labs(title = "Diurnal Temperature Range",
subtitle = "Verona CIMIS Station",
y = "DTR (degrees F)")
The histogram of DTR can be used to compare the magnitude of daily
temperature variation across sites or over time:
ggplot(daily_dtr_tbl, mapping = aes(x = DTR)) +
geom_histogram() +
labs(title = "Diurnal Temperature Range",
subtitle = "Verona CIMIS Station. Oct '21 - Sep '22'")
Evapotranspiration
The goal of precision irrigation is to give the crop just the amount
of water it needs, and nothing more. A standard method for determining
how much water is needed is to figure out how much water was lost since
the last time it was irrigated, and put back exactly that much.
Crops lose water due to evapotranspiration (ET), which combines
evaporation (i.e., from the soil) and respiration (from the plants). The
challenge however is that different crops respire at different rates,
which further vary based on the stage of the crop (i.e., baby plants
don’t respire nearly as much as mature plants). So there is not
one-size-fits-all value of ET that you can get from weather
variables.
To get around this, CIMIS stations have a sensor that measure
‘reference ET’ (ET0) from a standard ‘crop’ (grass), which
can be converted to crop ET (ETc) by multiplying the
reference ET by a ‘crop coefficient’ (Kc) (more
info). Crop coefficients have been developed through research for
many crops (more
info).
How much water do my tomatoes need?
Let’s compute the amount of daily evapotranspiration for tomatoes for
the month of June. During the middle of the growing season, tomatoes
have a Kc = 1.15.
To compute irrigation requirements, we need to:
Pull out ET0 and precipitation for the month of
June
Put them in separate columns:
june_eto_pr_tbl <- cimis_verona22_tbl |>
filter(Item %in% c("DayEto", "DayPrecip"), Date >= as.Date("2022-06-01"), Date <= as.Date("2022-06-30") ) |>
pivot_wider(id_cols = Date, names_from = Item, values_from = Value)
head(june_eto_pr_tbl)
To compute the daily ETc for tomatoes, we simply multiply
the reference ET0 by the crop coefficient for tomatoes:
Kc <- 1.15
june_etc_tbl <- june_eto_pr_tbl |>
mutate(ETc_tomato = DayEto * Kc)
june_etc_tbl |> head()
The total water loss each day is the amount of ETc minus
any precipitation (which CIMIS also reports in inches):
june_netwaterloss_tbl <- june_etc_tbl |>
mutate(net_water_loss_in = ETc_tomato - DayPrecip )
june_netwaterloss_tbl |> head()
To calculate the total amount of water the tomatoes need, we simply
add up the daily net water lost since the last irrigation event.
Suppose the last irrigation was June 10, 2022, and today is June 15.
How much water do we need to add?
june_netwaterloss_tbl |>
filter(Date > as.Date("2022-06-10"), Date <= as.Date("2022-06-15")) |>
mutate(cummulative_water_lost = cumsum(net_water_loss_in))
Challenge Question #3
From October 1 2021, thru March 31, 2022, how many days did the
temperature dip below 53 °F (a temperature which reduces the load of
certain overwintering insects)? Answer
## Your answer here
daily_coldday_tbl <- cimis_verona22_tbl |>
filter(Item == "DayAirTmpMin", Date >= as.Date("2021-10-01"), Date <= as.Date("2022-03-31")) |>
mutate(cold_day = Value <= 53) |>
select(Date, Item, Value, cold_day)
head(daily_coldday_tbl)
daily_coldday_tbl |> pull(cold_day) |> table()
FALSE TRUE
7 175
End
Remember to save the Notebook to generate a HTML version that
includes all executed code that you can save for keeps!
LS0tDQp0aXRsZTogIkFncm9jbGltYXRlIE1ldHJpY3MgTm90ZWJvb2sgIzEiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjc3M6IGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vYWdyb2NsaW1SL2Fzc2V0cy9uYl9jc3MwMS5jc3MNCiAgICBpbmNsdWRlczogDQogICAgICBhZnRlcl9ib2R5OiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2Fncm9jbGltUi9hc3NldHMvbmJfZm9vdGVyX2Fncm9jbGltci5odG1sDQotLS0NCg0KIyBTdW1tYXJ5DQoNCkluIHRoaXMgTm90ZWJvb2sgd2Ugd2lsbCBsZWFybiBob3cgdG86DQoNCi0gICBmaW5kIHRoZSBjbG9zZXN0IENJTUlTIHdlYXRoZXIgc3RhdGlvblwNCi0gICBpbXBvcnQgd2VhdGhlciBzdGF0aW9uIGRhdGEgZm9yIGEgc2luZ2xlIHNlYXNvbiBhbmQgYSBzaW5nbGUgbG9jYXRpb24gZnJvbSBDSU1JU1wNCi0gICBmaWx0ZXIgd2VhdGhlciBkYXRhIGJhc2VkIG9uIGRhdGVzXA0KLSAgIHBsb3Qgd2VhdGhlciBkYXRhXA0KLSAgIGNvbXB1dGUgcm9sbGluZyBhdmVyYWdlc1wNCi0gICBjb21wdXRlIHRocmVzaG9sZCBiYXNlZCBtZXRyaWNzLCBpbmNsdWRpbmcgaG90IGRheXMgYW5kIGZyb3N0IGRheXNcDQotICAgY29tcHRlIHRoZSBsYXN0IHNwcmluZyBmcmVlemUgZGF0ZVwNCi0gICBmaW5kIGFuZCBxdWFudGlmeSBzcGVsbHMgb2YgdGhyZXNob2xkIGJhc2VkIG1ldHJpY3MsIHN1Y2ggYXMgaGVhdCB3YXZlc1wNCi0gICByZXNoYXBlIGRhdGEgdG8gcHV0IHNlcGFyYXRlIHZhcmlhYmxlcyBpbiBzZXBhcmF0ZSBjb2x1bW5zXA0KLSAgIGNvbXB1dGUgdGhlIGRpdXJuYWwgdGVtcGVyYXR1cmUgcmFuZ2VcDQotICAgY29tcHV0ZSBpcnJpZ2F0aW9uIHJlcXVpcmVtZW50cyBiYXNlZCBvbiByZWZlcmVuY2UgZXZhcG90cmFuc3BpcmF0aW9uDQoNClwNCg0KIyBMb2FkIFBhY2thZ2VzDQoNCkZpcnN0IGxvYWQgdGhlIHBhY2thZ2VzIHdlJ2xsIG5lZWQgYmVsb3c6DQoNCmBgYHtyIGNodW5rMDEsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGNpbWlyKQ0KbGlicmFyeSh6b28pDQpsaWJyYXJ5KGxlYWZsZXQpDQpgYGANCg0KXA0KDQojIEltcG9ydCBXZWF0aGVyIERhdGEgZnJvbSBDSU1JUw0KDQpBZ3JvY2xpbWF0ZSBtZXRyaWNzIHJlcXVpcmUgd2VhdGhlciBkYXRhLCBzdWNoIGFzIHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtIHRlbXBlcmF0dXJlLCBwcmVjaXBpdGF0aW9uLCByZWxhdGl2ZSBodW1pZGl0eSwgcmVmZXJlbmNlIEVUbywgYW5kIHNvIG9uLiBGb3IgdGhpcyBleGVyY2lzZSwgd2UnbGwgaW1wb3J0IHdlYXRoZXIgcmVjb3JkcyBmcm9tIGEgc3RhdGlvbiBpbiB0aGUgW0NJTUlTXShodHRwczovL2NpbWlzLndhdGVyLmNhLmdvdi8pIG5ldHdvcmsuDQoNCjo6OiBzaGFkZWQtYm94DQoqKkhvdXJseSBvciBEYWlseSBXZWF0aGVyIERhdGE/KioNCg0KSW4gdGhlb3J5LCBob3VybHkgZGF0YSBzaG91bGQgYmUgYSBiZXR0ZXIgcHJlZGljdG9yIG9mIHBsYW50IG9yIGluc2VjdCBncm93dGgsIGJlY2F1c2UgaXQgY2FwdHVyZXMgbnVhbmNlcyBhbmQgcHJvY2Vzc2VzIG9uIGEgc21hbGxlciB0aW1lIHNjYWxlLiBIb3VybHkgZGF0YSBpcyBhbHNvIG5vdCB0aGF0IGhhcmQgdG8gZ2V0IHRoZXNlIGRheXMsIGF0IGxlYXN0IGZvciB0aGUgY3VycmVudCB0aW1lIHBlcmlvZC4NCg0KSG93ZXZlciBpbiBwcmFjdGljZSBtb3N0IG9mIHRoZSBjcm9wIGFuZCBwZXN0IG1vZGVscyBpbiB1c2UgYXJlIGJhc2VkIG9uICoqZGFpbHkgZGF0YSoqLCBzbyBpZiB5b3Ugd2FudCB0byB1c2UgdGhvc2UgbW9kZWxzIHlvdSB0byBwcm92aWRlIGRhaWx5IGRhdGEuIFRoZSByZXN0IG9mIHRoaXMgTm90ZWJvb2sgdGhlcmVmb3JlIHdpbGwgdXNlICoqZGFpbHkqKiB3ZWF0aGVyIGRhdGEuDQo6OjoNCg0KIyMgQ0lNSVMNCg0KW0NJTUlTXShodHRwczovL2NpbWlzLndhdGVyLmNhLmdvdi8pIGlzIGEgbmV0d29yayBvZiBcfjE1MCBhdXRvbWF0ZWQgd2VhdGhlciBzdGF0aW9ucyBydW4gYnkgW0NBRFdSXShodHRwczovL3dhdGVyLmNhLmdvdi8pIGZvciB0aGUgcHVycG9zZXMgb2YgaW5mb3JtaW5nIGlycmlnYXRvcnMuIEl0IGlzIGEgcG9wdWxhciBzb3VyY2Ugb2Ygd2VhdGhlciBkYXRhIGJlY2F1c2Ugb2YgdGhlIGNvdmVyYWdlIGFyZWEsIHRoZSBzdGF0aW9ucyByZWNvcmQgaG91cmx5IGFuZCBkYWlseSB2YWx1ZXMgb2YgbWFpbiB3ZWF0aGVyIHZhcmlhYmxlcy4gVGhlIGRhdGEgYXJlIGFsc28gZnJlZWx5IGF2YWlsYWJsZSB0aHJvdWdoIHZhcmlvdXMgd2Vic2l0ZXMgYW5kIGFuIEFQSS4NCg0KVGhlIFtgY2ltaXJgXShodHRwczovL2h5ZHJvZWNvbG9neS5uZXQvY2ltaXIvKSBwYWNrYWdlIHByb3ZpZGVzIGZ1bmN0aW9ucyB0byBpbXBvcnQgZGF0YSBmcm9tIHRoZSBDSU1JUyBuZXR3b3JrIGRpcmVjdGx5IGludG8gUi4gTWFueSBvZiB0aGVzZSBmdW5jdGlvbnMgcmVxdWlyZSBjcmVhdGluZyBhIFtDSU1JUyBhY2NvdW50XShodHRwczovL2NpbWlzLndhdGVyLmNhLmdvdi9BdXRoL1JlZ2lzdGVyLmFzcHgpIHNvIHlvdSBjYW4gZ2V0IGEgW0NJTUlTIEFQSSBrZXldKGh0dHBzOi8vY2ltaXMud2F0ZXIuY2EuZ292L0F1dGgvUmVnaXN0ZXIuYXNweCkgKGZyZWUpLg0KDQojIyMgTWFwIHRoZSBDSU1JUyBTdGF0aW9ucw0KDQpUaGUgZWFzaWVzdCB3YXkgdG8gZ2V0IENJTUlTIGRhdGEgaXMgaWYgeW91IGtub3cgd2hpY2ggc3RhdGlvbi4gTGV0J3MgbWFrZSBhIG1hcCBvZiB0aGUgQ0lNSVMgU3RhdGlvbnMuIFN0ZXAgMSBpcyB0byBnZXQgdGhlIGxpc3Qgb2YgYWN0aXZlIHN0YXRpb25zOg0KDQpgYGB7ciBjaHVuazAyfQ0KIyMgSWYgeW91IGhhdmUgYSBDSU1JUyBrZXksIHlvdSBjYW4gdW5jb21tZW50IGFuZCBydW4gdGhlIGZvbGxvd2luZzoNCiMjIGNpbWlyOjpzZXRfa2V5KCJ4eHh4eHh4eC14eHh4LXh4eHgteHh4eC14eHh4eHh4eHh4eHgiKQ0KIyMgc3RhdGlvbnNfYWxsX3RibCA8LSBjaW1pcjo6Y2ltaXNfc3RhdGlvbigpDQoNCnN0YXRpb25zX2FsbF90YmwgPC0gcmVhZC5jc3YoIi4vZGF0YS9jaW1pc19zdGF0aW9uc19hbGwuY3N2IikNCmhlYWQoc3RhdGlvbnNfYWxsX3RibCkNCmBgYA0KDQpcDQoNCk5leHQgd2UnbGwgZG8gYSBsaXR0bGUgZGF0YSBjbGVhbmluZywga2VlcGluZyBvbmx5IHRoZSBjb2x1bW5zIHdlJ2xsIG5lZWQgZm9yIHRvIG1hcCB0aGUgYWN0aXZlIHN0YXRpb25zOg0KDQpgYGB7ciBjaHVuazAzfQ0Kc3RhdGlvbnNfY2xlYW5lZF90YmwgPC0gc3RhdGlvbnNfYWxsX3RibCB8PiANCiAgZmlsdGVyKElzQWN0aXZlID09ICJUcnVlIikgfD4gDQogIHNlbGVjdChTdGF0aW9uTmJyLCBOYW1lLCBIbXNMYXRpdHVkZSwgSG1zTG9uZ2l0dWRlKSB8PiANCiAgZGlzdGluY3QoKSB8PiANCiAgdHJhbnNtdXRlKHN0YXRpb25faWQgPSBhcy5udW1lcmljKFN0YXRpb25OYnIpLA0KICAgICAgICAgICAgbmFtZSA9IE5hbWUsDQogICAgICAgICAgICBsb24gPSBhcy5udW1lcmljKGdzdWIoIl4uKi8gIiwgIiIsIEhtc0xvbmdpdHVkZSkpLA0KICAgICAgICAgICAgbGF0ID0gYXMubnVtZXJpYyhnc3ViKCJeLiovICIsICIiLCBIbXNMYXRpdHVkZSkpKQ0KDQpoZWFkKHN0YXRpb25zX2NsZWFuZWRfdGJsKQ0KYGBgDQoNClwNCg0KVHVybiB0aGlzIGludG8gYSBzcGF0aWFsIG9iamVjdCBhbmQgbWFwIGl0IHdpdGggbGVhZmxldDoNCg0KYGBge3IgY2h1bmswNH0NCnN0YXRpb25zX2NsZWFuZWRfc2YgPC0gc3RhdGlvbnNfY2xlYW5lZF90YmwgfD4gDQogIG11dGF0ZSh0aXRsZSA9IHBhc3RlMChuYW1lLCAiICgjIiwgc3RhdGlvbl9pZCwgIikiKSkgfD4gDQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoImxvbiIsICJsYXQiKSwgY3JzID0gNDMyNikgfD4gDQogIHNlbGVjdCh0aXRsZSkgDQoNCmxlYWZsZXQoc3RhdGlvbnNfY2xlYW5lZF9zZikgfD4NCiAgYWRkVGlsZXMoKSB8PiANCiAgYWRkQ2lyY2xlTWFya2VycyhyYWRpdXMgPSA1LCBwb3B1cCA9IH50aXRsZSkNCmBgYA0KDQpcDQoNCiMjIFF1ZXJ5IGEgQ0lNSVMgU3RhdGlvbg0KDQpUbyBzZWUgd2hhdCB3ZWF0aGVyIHZhcmlhYmxlcyBhcmUgYXZhaWxhYmxlLCBydW4gYGNpbWlzX2l0ZW1zKClgOg0KDQpgYGB7ciBjaHVuazA1fQ0KY2ltaXNfaXRlbXMoKQ0KYGBgDQoNClwNCg0KWW91IGNhbiByZXRyaWV2ZSBkYXRhIHVzaW5nIGBjaW1pc19kYXRhKClgLiBMZXQncyBnZXQgZGFpbHkgdGVtcGVyYXR1cmUsIHByZWNpcGl0YXRpb24sIGFuZCByZWZlcmVuY2UgRVRvIGZvciB0aGUgVmVyb25hIHN0YXRpb24gKCMyMzUpLCBub3J0aCBvZiBTYWNyYW1lbnRvLg0KDQpgYGB7ciBjaHVuazA2fQ0KIyBJZiB5b3UndmUgZW50ZXJlZCBhIENJTUlTIGtleSwgdW5jb21tZW50IHRoZSBmb2xsb3dpbmcgbGluZS4gDQojIE90aGVyd2lzZSwgcnVuIHRoZSBuZXh0IGNvbW1hbmQgdG8gbG9hZCB0aGUgc2F2ZWQgZGF0YQ0KIyBjaW1pc192ZXJvbmEyMl90YmwgPC0gY2ltaXNfZGF0YSh0YXJnZXRzID0gMjM1LCBzdGFydC5kYXRlID0gIjIwMjEtMTAtMDEiLCBlbmQuZGF0ZSA9ICIyMDIyLTA5LTMwIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbXMgPSAiZGF5LWFpci10bXAtbWF4LGRheS1haXItdG1wLW1pbixkYXktZXRvLGRheS1wcmVjaXAiKQ0KDQpjaW1pc192ZXJvbmEyMl90YmwgPC0gIHJlYWRSRFMoIi4vZGF0YS9jaW1pc192ZXJvbmEyMi5SZHMiKQ0KDQpoZWFkKGNpbWlzX3Zlcm9uYTIyX3RibCkNCmBgYA0KDQpcDQoNCioqTm90ZXMgYWJvdXQgdGhlIGRhdGEgZnJhbWUgcmV0dXJuZWQgYnkgYGNpbWlzX2RhdGEoKWAqKg0KDQotICAgb3VyIGRhdGEgZnJhbWUgY29udGFpbnMgNCB3ZWF0aGVyIHZhcmlhYmxlcyBpbiBhICpsb25nKiAoYXMgb3Bwb3NlZCB0byB3aWRlKSBmb3JtYXRcDQotICAgdGhlIGBEYXRlYCBjb2x1bW4gaXMgYWxyZWFkeSBmb3JtYXR0ZWQgYXMgYSBkYXRlIG9iamVjdFwNCi0gICBgSnVsaWFuYCBpcyB0aGUgZGF5IG9mIHRoZSB5ZWFyICgwLi4zNjUpDQotICAgYFFjYCBpcyBhIHF1YWxpdHkgY29udHJvbCBmbGFnIChbZGV0YWlsc10oaHR0cHM6Ly9jaW1pcy53YXRlci5jYS5nb3YvQ29udGVudC9QREYvQ3VycmVudEZsYWdzMi5wZGYpKQ0KDQpcDQoNCiMgVGltZSBGaWx0ZXJpbmcNCg0KVG8gZmlsdGVyIHJvd3MgYmFzZWQgb24gdGhlIGRhdGUsIHlvdSBuZWVkIGEgY29sdW1uIGFzIGEgRGF0ZSBvciB0aW1lIChQT1NJWGN0KSBvYmplY3QuIFRoZSBgbHVicmlkYXRlYCBwYWNrYWdlIGhhcyBmdW5jdGlvbnMgdG8gY29udmVydCBjaGFyYWN0ZXIgYW5kIG51bWJlciBjb2x1bW5zIGludG8gZGF0ZXMuDQoNCk9uY2UgeW91IGhhdmUgYSBkYXRlIGNvbHVtbiwgZmlsdGVyaW5nIGlzIHByZXR0eSBlYXN5LiBGb3IgZXhhbXBsZSB0byBwdWxsIHRoZSByZWNvcmRzIGZvciB0aGUgKioyMDIyIGdyb3dpbmcgc2Vhc29uKiogZnJvbSAqKkFwcmlsIDE1KiogdGhydSAqKlNlcHQgMzAsIDIwMjIqKjoNCg0KYGBge3IgY2h1bmswN30NCmdyd3NuX3ZhbHNfdGJsIDwtIGNpbWlzX3Zlcm9uYTIyX3RibCB8PiANCiAgc2VsZWN0KERhdGUsIEl0ZW0sIFZhbHVlLCBRYykgfD4gDQogIGZpbHRlcihEYXRlID49IGFzLkRhdGUoIjIwMjItMDQtMTUiKSwgRGF0ZSA8PSBhcy5EYXRlKCIyMDIyLTA5LTMwIikpDQoNCmdyd3NuX3ZhbHNfdGJsIHw+IHNsaWNlKDE6MjApDQpgYGANCg0KXA0KDQojIyMgUGxvdCB0aGUgZGFpbHkgaGlnaCB0ZW1wcw0KDQpXZSBjYW4gcGxvdCBkYWlseSBoaWdoIHRlbXBlcmF0dXJlIGF0IHRoaXMgbG9jYXRpb24gd2l0aCBhIGxpdHRsZSBoZWxwIGZyb20gZHBseXIgYW5kIGdncGxvdDoNCg0KYGBge3IgY2h1bmswOH0NCmdyd3NuX2RhaWx5bWF4X3RibCA8LSBncndzbl92YWxzX3RibCB8PiANCiAgZmlsdGVyKEl0ZW0gPT0gIkRheUFpclRtcE1heCIpIHw+IA0KICByZW5hbWUobWF4X3RlbXAgPSBWYWx1ZSkNCiANCmdncGxvdChncndzbl9kYWlseW1heF90YmwsIG1hcHBpbmcgPSBhZXMoeCA9IERhdGUsIHkgPSBtYXhfdGVtcCkpICsgDQogIGdlb21fbGluZSgpICsNCiAgbGFicyh0aXRsZSA9ICJEYWlseSBIaWdoIFRlbXBzIiwgDQogICAgICAgICAgIHN1YnRpdGxlID0gIlZlcm9uYSBDSU1JUyBTdGF0aW9uLCBTcHJpbmcgMjAyMiIpDQpgYGANCg0KXA0KDQojIFJvbGxpbmcgQXZlcmFnZXMNCg0KUm9sbGluZyAvIG1vdmluZyBhdmVyYWdlcyBhcmUgZ2VuZXJpY2FsbHkgdXNlZnVsIGZvciBzbW9vdGhpbmcgb3V0IHRoZSBidW1wcyBpbiBhIHRpbWUgc2VyaWVzLiBGb3IgY3JvcCBtYW5hZ2VtZW50IHlvdSBtYXkgbm90IHdhbnQgdG8gc21vb3RoIG91dCB0aGUgYnVtcHMsIGJ1dCBvdGhlciBhcHBsaWNhdGlvbnMgYXJlIGVhc2llciB0byBhZGRyZXNzIGJ5IGxvb2tpbmcgYXQgdHJlbmRzIG9uIGEgd2Vla2x5IG9yIGxvbmdlciB0aW1lIHBlcmlvZC4NCg0KTW92aW5nIGF2ZXJhZ2VzIGNhbiBhbHNvIGJlIHVzZWZ1bCB0byBpZGVudGlmeSBtdWx0aS1kYXkgZXh0cmVtZSBldmVudHMuIEZvciBleGFtcGxlLCA1IGNvbnNlY3V0aXZlIGRheXMgb3ZlciA5NSDCsEYgaXMga2lsbGVyIGZvciB0b21hdG9lcy4gQ29tcHV0aW5nIHRoZSA1LWRheSByb2xsaW5nIGF2ZXJhZ2Ugd291bGQgYmUgb25lIHdheSB0byBpZGVudGlmeSB3aGVuIHRvbWF0b2VzIG1pZ2h0IGJlIGluIHRyb3VibGUgKHNlZSBhbHNvIGEgdGhyZXNob2xkIHRlY2huaXF1ZSwgY29taW5nIHVwIG5leHQpLg0KDQpZb3UgY2FuIGNvbXB1dGUgYSByb2xsaW5nIGF2ZXJhZ2Ugd2l0aCBgem9vOjpyb2xsbWVhbigpYC4gYGtgIGlzIHRoZSByb2xsaW5nIHdpbmRvdyBzaXplIChzaG91bGQgYmUgYW4gb2RkIG51bWJlcikuIFlvdSBzaG91bGQgYWxzbyBwYXNzIGBmaWxsID0gTkFgLCB3aGljaCB0ZWxscyBpdCB0byBhc3NpZ24gTkEgYXMgdGhlIHJvbGxpbmcgYXZlcmFnZSBmb3IgdGhlIGZpcnN0IGFuZCBsYXN0IGRheXMuDQoNCmBgYHtyIGNodW5rMDl9DQpncndzbl9kYWlseW1heF9tb3ZhdmdfdGJsIDwtIGdyd3NuX2RhaWx5bWF4X3RibCB8PiANCiAgbXV0YXRlKGRhaWx5bWF4X2F2ZzVkID0gem9vOjpyb2xsbWVhbihtYXhfdGVtcCwgaz01LCBmaWxsPU5BKSkNCg0KZ2dwbG90KGdyd3NuX2RhaWx5bWF4X21vdmF2Z190YmwsIG1hcHBpbmcgPSBhZXMoeCA9IERhdGUsIHkgPSBkYWlseW1heF9hdmc1ZCkpICsgDQogIGdlb21fbGluZSgpICsNCiAgbGFicyh0aXRsZSA9ICJEYWlseSBIaWdoIFRlbXBzICg1LWRheSBtb3ZpbmcgYXZlcmFnZSkiLCANCiAgICAgICAgICAgc3VidGl0bGUgPSAiVmVyb25hIENJTUlTIFN0YXRpb24sIFNwcmluZyAyMDIyIikNCmBgYA0KDQpcDQoNCiMgQ2hhbGxlbmdlIFF1ZXN0aW9uICMxDQoNCkNvbXB1dGUgdGhlIGRhaWx5IGhpZ2ggdGVtcGVyYXR1cmUgcm9sbGluZyBhdmVyYWdlIGZvciBhbiAxMSBkYXkgd2luZG93LCB0aGVuIHBsb3QgaXQuIFtBbnN3ZXJdKGh0dHA6Ly9iaXQubHkvM0FYZlU2RykNCg0KYGBge3IgY2h1bmsxMH0NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCmdyd3NuX2RhaWx5bWF4X3RibCB8PiANCiAgbXV0YXRlKGRhaWx5bWF4X2F2ZzVkID0gem9vOjpyb2xsbWVhbihtYXhfdGVtcCwgaz0xMSwgZmlsbD1OQSkpIHw+IA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gRGF0ZSwgeSA9IGRhaWx5bWF4X2F2ZzVkKSkgKyANCiAgZ2VvbV9saW5lKCkgKw0KICBsYWJzKHRpdGxlID0gIkRhaWx5IEhpZ2ggVGVtcHMgKDExLWRheSBtb3ZpbmcgYXZlcmFnZSkiLCANCiAgICAgICAgICAgc3VidGl0bGUgPSAiVmVyb25hIENJTUlTIFN0YXRpb24sIFNwcmluZyAyMDIyIikNCmBgYA0KDQpcDQoNCiMgVGhyZXNob2xkIE1ldGhvZHMNCg0KTWFueSBhZ3JvY2xpbWF0ZSBtZXRyaWNzIGFyZSBkZWZpbmVkIGJ5IGEgdGhyZXNob2xkIHZhbHVlLiBUaGUgdGhyZXNob2xkIG1heSBiZSBpbiByZWZlcmVuY2UgdG8gdGhlIHJhbmdlIG9mIHZhcmlhYmlsaXR5IGF0IHRoYXQgbG9jYXRpb24gaW4gdGhlIGhpc3RvcmljIHBlcmlvZCwgb3IgaXQgbWF5IGJlIGluIHJlZmVyZW5jZSB0byB3aGVuIHNvbWUga2luZCBvZiBwaHlzaWNhbCBvciBiaW9sb2dpY2FsIGNoYW5nZSBoYXBwZW5zLg0KDQotICAgJ2V4dHJlbWUgaGVhdCcgYW5kICdleHRyZW1lIHByZWNpcGl0YXRpb24nIGdlbmVyYWxseSB1c2UgYSB0aHJlc2hob2xkIGJhc2VkIG9uIGhpc3RvcmljIHZhbHVlcyBmb3IgdGhhdCBsb2NhdGlvbg0KDQotICAgJ2hvdCBkYXlzJyBhcmUgZGVmaW5lZCBieSBhIHRocmVzaG9sZCB0ZW1wZXJhdHVyZSB0aGF0IGFmZmVjdHMgY3JvcHMgKFtQYXJrZXIgZXQgYWwuIDIwMjJdKGh0dHBzOi8vZG9pLm9yZy8xMC4zMzkwL2Fncm9ub215MTIwMTAyMDUpKS4NCg0KXA0KDQojIyBIb3QgRGF5cw0KDQpIb3cgbWFueSAnaG90IGRheXMnIHdlcmUgdGhlcmUgaW4gMjAyMiwgd2hlcmUgJ2hvdCcgaXMgZGVmaW5lZCBhcyBvdmVyIDM4IMKwQyAoMTAwLjQgwrBGKT8NCg0KVGVzdGluZyB3aGV0aGVyIGEgZGF5IHdhcyAnaG90JyBjYW4gYmUgZG9uZSB3aXRoIGEgc2ltcGxlIGNvbXBhcmlzb24gZXhwcmVzc2lvbjoNCg0KYGBge3IgY2h1bmsxMX0NCmdyd3NuX2hvdHluX3RibCA8LSBncndzbl9kYWlseW1heF90YmwgfD4gDQogIG11dGF0ZShob3R5biA9IG1heF90ZW1wID4gMTAwLjQpDQoNCmhlYWQoZ3J3c25faG90eW5fdGJsKQ0KYGBgDQoNClwNCg0KVGhlIG51bWJlciBvZiBob3QgZGF5cyBjYW4gYmUgY29tcHV0ZWQgc2ltcGx5IGJ5IHN1bW1pbmcgdGhlIGNvbHVtbiBvZiBUUlVFL0ZBTFNFIHZhbHVlczoNCg0KYGBge3IgY2h1bmsxMn0NCmdyd3NuX2hvdHluX3RibCRob3R5biB8PiBzdW0oKQ0KZ3J3c25faG90eW5fdGJsJGhvdHluIHw+IHRhYmxlKCkNCmBgYA0KDQpcDQoNClRvIHNlZSAqd2hlbiogdGhlIGhvdCBkYXlzIG9jY3VycmVkLCB3ZSBjYW4gb3ZlcmxheSB0aGUgdGhyZXNob2xkIHZhbHVlIG9uIHRoZSB0aW1lIHNlcmllcyBwbG90Og0KDQpgYGB7ciBjaHVuazEzfQ0KZ2dwbG90KGdyd3NuX2RhaWx5bWF4X3RibCwgbWFwcGluZyA9IGFlcyh4ID0gRGF0ZSwgeSA9IG1heF90ZW1wKSkgKyANCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxMDAuNCwgY29sb3IgPSAicmVkIiwgbGluZXdpZHRoID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIkRhaWx5IEhpZ2ggVGVtcHMiLCANCiAgICAgICAgICAgc3VidGl0bGUgPSAiVmVyb25hIENJTUlTIFN0YXRpb24sIFNwcmluZyAyMDIyIikNCmBgYA0KDQpcDQoNCiMgQ2hhbGxlbmdlIFF1ZXN0aW9uICMyDQoNCldyaXRlIGFuIGV4cHJlc3Npb24gdGhhdCByZXR1cm5zIHRoZSBleGFjdCBkYXRlcyBvZiBob3QgZGF5cy4gW0Fuc3dlcl0oaHR0cDovL2JpdC5seS8zWExpaEQ4KQ0KDQpgYGB7ciBjaHVuazE0fQ0KIyMgWW91ciBhbnN3ZXIgaGVyZQ0KZ3J3c25fZGFpbHltYXhfdGJsIHw+IA0KICBmaWx0ZXIoSXRlbSA9PSAiRGF5QWlyVG1wTWF4IiwgbWF4X3RlbXAgPiAxMDAuNCkgfD4gDQogIHNlbGVjdChEYXRlLCBtYXhfdGVtcCkNCmBgYA0KDQpcDQoNCiMgTGFzdCBTcHJpbmcgRnJlZXplDQoNClBsYW50aW5nIGFuZCBvdGhlciBjcm9wIG1hbmFnZW1lbnQgcHJhY3RpY2VzIGhhdmUgdG8gYmUgdGltZWQgdG8gdGFrZSBwbGFjZSBhZnRlciB0aGUgbGFzdCBmcmVlemUgb2YgdGhlIHdpbnRlci4gVGhlIGRhdGUgb2YgdGhlIGxhc3QgZnJlZXplIGNhbiBiZSBjYWxjdWxhdGVkIGJ5Og0KDQoxLiAgSWRlbnRpZnlpbmcgYWxsIGZyZWV6ZSBldmVudHMgZnJvbSBzYXkgRmVicnVhcnkgdGhydSBKdW5lLCB1c2luZyBhIHNpbXBsZSB0aHJlc2hvbGQgdGVzdCAobWluaW11bSBkYWlseSB0ZW1wICZsZTsgMzIgJmRlZztGKQ0KDQoyLiAgRmluZCB0aGUgZGF0ZSBhc3NvY2lhdGVkIHdpdGggdGhlIGxhc3QgZnJlZXplDQoNClwNCg0KU3RlcCAxOiBhZGQgYSAnRnJvc3QgRGF5JyBjb2x1bW46DQoNCmBgYHtyIGNodW5rMTV9DQpkYWlseV9taW5fdGJsIDwtIGNpbWlzX3Zlcm9uYTIyX3RibCB8PiANCiAgZmlsdGVyKEl0ZW0gPT0gIkRheUFpclRtcE1pbiIsIERhdGUgPj0gYXMuRGF0ZSgiMjAyMi0wMi0wMSIpLCBEYXRlIDw9IGFzLkRhdGUoIjIwMjItMDYtMzAiKSkgfD4gDQogIG11dGF0ZShmcm9zdF9kYXkgPSBWYWx1ZSA8PSAzMikgfD4gDQogIHNlbGVjdChEYXRlLCBJdGVtLCBWYWx1ZSwgZnJvc3RfZGF5KQ0KDQpoZWFkKGRhaWx5X21pbl90YmwpDQpgYGANCg0KXA0KDQpTdGVwIDIuIEhvdyBtYW55IGZyb3N0IGRheXMgd2VyZSB0aGVyZSBmcm9tIEZlYnJ1YXJ5IHRocnUgSnVuZT8NCg0KYGBge3IgY2h1bmsxNn0NCmRhaWx5X21pbl90YmwkZnJvc3RfZGF5IHw+IHRhYmxlKCkNCmBgYA0KDQpcDQoNClN0ZXAgMy4gV2hhdCB3YXMgdGhlICoqbGFzdCoqIGZyZWV6ZSBkYXRlPw0KDQpgYGB7ciBjaHVuazE3fQ0KZGFpbHlfbWluX3RibCB8PiANCiAgZmlsdGVyKGZyb3N0X2RheSA9PSBUUlVFKSB8PiANCiAgYXJyYW5nZShkZXNjKERhdGUpKSANCmBgYA0KDQpcDQoNCmBgYHtyIGNodW5rMTh9DQpkYWlseV9taW5fdGJsIHw+IA0KICBmaWx0ZXIoZnJvc3RfZGF5ID09IFRSVUUpIHw+IA0KICBzbGljZV9tYXgoRGF0ZSwgbiA9IDEpIHw+IA0KICBwdWxsKERhdGUpDQpgYGANCg0KXA0KDQojIFNwZWxscyBhbmQgUnVucw0KDQpTb21lIGNsaW1hdGUgbWV0cmljcyBhcmUgZGVmaW5lZCBieSBhIHNlcmllcyBvZiBkYXlzIHdoZW4gYSB0aHJlc2hvbGQgaXMgc3VycGFzc2VkLiBFeGFtcGxlcyBpbmNsdWRlIGhlYXR3YXZlcy4gV2UgbWF5IHdhbnQgdG8ga25vdyB0aGUgbnVtYmVyIG9mIGhlYXR3YXZlcywgb3IgdGhlIGxlbmd0aCBvZiB0aGUgaGVhdHdhdmVzLg0KDQpgcmxlKClgIGNhbiBiZSB1c2VkIHRvIGFuc3dlciB0aGVzZSBxdWVzdGlvbnMuIExldCdzIHNlZSBob3cgYHJsZSgpYCB3b3JrczoNCg0KYGBge3IgY2h1bmsxOX0NCnggPC0gYygiYSIsICJhIiwgImEiLCAiaCIsICJ0IiwgInQiLCAidCIsICJ0IiwgImEiLCAiYSIsICJhIiwgImEiLCAiYyIsICJjIiwgImQiLCAiZCIsICJkIikNCnhybGVfbHN0IDwtIHJsZSh4KQ0KeHJsZV9sc3QNCmBgYA0KDQpcDQoNCkFzIHlvdSBjYW4gc2VlLCBgcmxlKClgIHJldHVybnMgYSBsaXN0IHdpdGggdHdvIGVsZW1lbnRzIGNvbnRhaW5pbmcgcHJvcGVydGllcyBvZiBncm91cHMgb2YgcmVwZWF0ZWQgbGV0dGVycy4gVGhlIGB2YWx1ZXNgIGVsZW1lbnQgY29udGFpbnMgdGhlIGxldHRlciBpbiBlYWNoIGdyb3VwLCBhbmQgdGhlIGBsZW5ndGhzYCBlbGVtZW50IGNvbnRhaW5zIHRoZSBudW1iZXIgb2YgbGV0dGVycyAod2hpY2ggY291bGQgYmUgMSkuDQoNClNheSB3ZSdyZSBpbnRlcmVzdGVkIGluIGdyb3VwcyBvZiAzIG9yIG1vcmUgcmVwZWF0ZWQgbGV0dGVycy4gV2UgY2FuIGZpbmQgdGhlIG51bWJlciBvZiBncm91cHMgb2YgMyBvciBtb3JlIGxldHRlcnMgYnkgc3VtbWluZyB1cCB0aGUgcmVzdWx0cyBvZiBhIGxvZ2ljYWwgZXhwcmVzc2lvbjoNCg0KYGBge3IgY2h1bmsyMH0NCih4cmxlX2xzdCRsZW5ndGhzID49IDMpIHw+IHN1bSgpDQpgYGANCg0KXA0KDQpBbmQgd2UgY2FuIGZpbmQgdGhlIGF2ZXJhZ2UgbGVuZ3RoIG9mIHRoZXNlIGdyb3VwcyB3aXRoOg0KDQpgYGB7ciBjaHVuazIxfQ0KeHJsZV9sc3QkbGVuZ3Roc1sgeHJsZV9sc3QkbGVuZ3RocyA+PSAzIF0gfD4gbWVhbigpDQpgYGANCg0KXA0KDQpCdXQgd2hhdCBpZiB3ZSBvbmx5IHdhbnRlZCB0aGUgbnVtYmVyIG9mICdydW5zJyBvZiB0aGUgbGV0dGVyICdhJz8gV2UgY2FuIHNpbXBseSB1c2UgYSBjb21wb3VuZCBsb2dpY2FsIGV4cHJlc3Npb246DQoNCmBgYHtyIGNodW5rMjJ9DQojIyBOdW1iZXIgb2YgZ3JvdXBzIG9mICdhJyBvZiBsZW5ndGggMyBvciBtb3JlDQooKHhybGVfbHN0JHZhbHVlcyA9PSAiYSIpICYgKHhybGVfbHN0JGxlbmd0aHMgPj0gMykpIHw+IHN1bSgpDQpgYGANCg0KXA0KDQpTbyBob3cgbWFueSBoZWF0d2F2ZXMgd2hlcmUgdGhlIGhpZ2ggdGVtcGVyYXR1cmUgd2FzIFw+IDEwMC40IMKwRiBmb3IgMyBvciBtb3JlIGRheXM/DQoNCkZpcnN0IHdlIGNyZWF0ZSB0aGUgYHJsZSgpYCBsaXN0Og0KDQpgYGB7ciBjaHVuazIzfQ0KaG90eW5fcmxlX2xzdCA8LSBybGUoZ3J3c25faG90eW5fdGJsJGhvdHluKQ0KaG90eW5fcmxlX2xzdA0KYGBgDQoNClwNCg0KTmV4dCB3ZSBmaW5kIHRoZSBudW1iZXIgb2YgZ3JvdXBzIG9mIFRSVUU6DQoNCmBgYHtyIGNodW5rMjR9DQooKGhvdHluX3JsZV9sc3QkdmFsdWVzID09IFRSVUUpICYgKGhvdHluX3JsZV9sc3QkbGVuZ3RocyA+PSAzKSkgfD4gc3VtKCkNCmBgYA0KDQpcDQoNCiMgTXVsdGktVmFyaWFibGUgTWV0cmljcyB3aXRoIFNlcGFyYXRlIENvbHVtbnMNCg0KU29tZSBtZXRyaWNzIGNvbWJpbmUgbXVsdGlwbGUgd2VhdGhlciBzdGF0aW9uIHZhcmlhYmxlcywgc3VjaCBhcyB0aGUgZGFpbHkgbWluaW11bSBhbmQgZGFpbHkgbWF4aW11bSB0ZW1wZXJhdHVyZS4gQm90aCBvZiB0aGVzZSB2YXJpYWJsZXMgYXJlIGluY2x1ZGVkIGluIG91ciBDSU1JUyBkYXRhLCBidXQgdGhleSdyZSBtaXhlZCBpbiB3aXRoIG90aGVyIHZhcmlhYmxlcyBpbiBhIGxvbmcgZm9ybWF0LiBSZW1lbWJlciB3aGF0IHdlIGdvdCBiYWNrIGZyb20gQ0lNSVM6DQoNCmBgYHtyIGNodW5rMjV9DQpjaW1pc192ZXJvbmEyMl90YmwgfD4gDQogIHNlbGVjdChEYXRlLCBJdGVtLCBWYWx1ZSwgUWMsIFVuaXQpIHw+IA0KICBzbGljZSgxOjEwKQ0KYGBgDQoNClwNCg0KRm9yIG1hbnkgbXVsdGktdmFyaWFibGUgbWV0cmljcywgaXQgaXMgb2Z0ZW4gZWFzaWVzdCB0byBwdWxsIG91dCB0aGUgdmFyaWFibGVzIHdlIG5lZWQgYXMgc2VwYXJhdGUgY29sdW1ucy4gVGhpcyBjYW4gYmUgZWFzaWx5IGRvbmUgYHRpZHlyOjpwaXZvdF93aWRlcigpYC4gVGhlIGtleSBhcmd1bWVudHMgd2UgbmVlZCB0byBnaXZlIGl0IGFyZSBgbmFtZXNfZnJvbWAgYW5kIGB2YWx1ZXNfZnJvbWAgKFtkZXRhaWxzXShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvYXJ0aWNsZXMvcGl2b3QuaHRtbCkpOg0KDQpgYGB7ciBjaHVuazI2fQ0KZGFpbHlfdGVtcHNfdGJsIDwtIGNpbWlzX3Zlcm9uYTIyX3RibCB8PiANCiAgZmlsdGVyKEl0ZW0gJWluJSBjKCJEYXlBaXJUbXBNaW4iLCAiRGF5QWlyVG1wTWF4IikpIHw+IA0KICBzZWxlY3QoRGF0ZSwgSXRlbSwgVmFsdWUpIHw+IA0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gSXRlbSwgdmFsdWVzX2Zyb20gPSBWYWx1ZSkNCiAgDQpkYWlseV90ZW1wc190YmwgfD4gaGVhZCgpDQpgYGANCg0KXA0KDQojIyBBdmVyYWdlIERhaWx5IFRlbXBlcmF0dXJlDQoNCldpdGggdGhlIGRhaWx5IG1pbmltdW0gYW5kIG1heGltdW0gdGVtcGVyYXR1cmUgYXMgc2VwYXJhdGUgY29sdW1ucywgY29tcHV0aW5nIHRoZSBhdmVyYWdlIHRlbXBlcmF0dXJlIGlzIGEgc2ltcGxlIGV4cHJlc3Npb246DQoNCmBgYHtyIGNodW5rMjd9DQpkYWlseV9tZWFuX3RibCA8LSBkYWlseV90ZW1wc190YmwgfD4gDQogIG11dGF0ZShkYWlseV9tZWFuID0gKERheUFpclRtcE1heCArIERheUFpclRtcE1pbikgLyAyKQ0KICANCmhlYWQoZGFpbHlfbWVhbl90YmwpDQpgYGANCg0KXA0KDQojIyBEaXVybmFsIFRlbXBlcmF0dXJlIFJhbmdlDQoNClRoZSBEaXVybmFsIFRlbXBlcmF0dXJlIFJhbmdlIChEVFIpIGlzIGEgdXNlZnVsIG1ldHJpYyBmb3IgZXZhbHVhdGluZyBjcm9wIHN1aXRhYmlsaXR5LCBhbmQgaXMgc2ltcGx5IHRoZSBtYXhpbXVtIGRhaWx5IHRlbXBlcmF0dXJlIG1pbnVzIHRoZSBtaW5pbXVtOg0KDQpgYGB7ciBjaHVuazI4fQ0KZGFpbHlfZHRyX3RibCA8LSBkYWlseV90ZW1wc190YmwgfD4gDQogIG11dGF0ZShEVFIgPSBEYXlBaXJUbXBNYXggLSBEYXlBaXJUbXBNaW4pDQogIA0KaGVhZChkYWlseV9kdHJfdGJsKQ0KYGBgDQoNClwNCg0KTGFyZ2Ugc3dpbmdzIGluIHRlbXBlcmF0dXJlIG1heSByZXByZXNlbnQgZGF5cyB3aGVuIGEgZnJvbnQgcGFzc2VkIHRocm91Z2guDQoNCmBgYHtyIGNodW5rMjl9DQpnZ3Bsb3QoZGFpbHlfZHRyX3RibCwgbWFwcGluZyA9IGFlcyh4ID0gRGF0ZSwgeSA9IERUUikpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBsYWJzKHRpdGxlID0gIkRpdXJuYWwgVGVtcGVyYXR1cmUgUmFuZ2UiLA0KICAgICAgIHN1YnRpdGxlID0gIlZlcm9uYSBDSU1JUyBTdGF0aW9uIiwNCiAgICAgICB5ID0gIkRUUiAoZGVncmVlcyBGKSIpDQpgYGANCg0KXA0KDQpUaGUgaGlzdG9ncmFtIG9mIERUUiBjYW4gYmUgdXNlZCB0byBjb21wYXJlIHRoZSBtYWduaXR1ZGUgb2YgZGFpbHkgdGVtcGVyYXR1cmUgdmFyaWF0aW9uIGFjcm9zcyBzaXRlcyBvciBvdmVyIHRpbWU6DQoNCmBgYHtyIGNodW5rMzB9DQpnZ3Bsb3QoZGFpbHlfZHRyX3RibCwgbWFwcGluZyA9IGFlcyh4ID0gRFRSKSkgKw0KICBnZW9tX2hpc3RvZ3JhbSgpICsNCiAgbGFicyh0aXRsZSA9ICJEaXVybmFsIFRlbXBlcmF0dXJlIFJhbmdlIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJWZXJvbmEgQ0lNSVMgU3RhdGlvbi4gT2N0ICcyMSAtIFNlcCAnMjInIikNCmBgYA0KDQpcDQoNCiMgRXZhcG90cmFuc3BpcmF0aW9uDQoNClRoZSBnb2FsIG9mIHByZWNpc2lvbiBpcnJpZ2F0aW9uIGlzIHRvIGdpdmUgdGhlIGNyb3AganVzdCB0aGUgYW1vdW50IG9mIHdhdGVyIGl0IG5lZWRzLCBhbmQgbm90aGluZyBtb3JlLiBBIHN0YW5kYXJkIG1ldGhvZCBmb3IgZGV0ZXJtaW5pbmcgaG93IG11Y2ggd2F0ZXIgaXMgbmVlZGVkIGlzIHRvIGZpZ3VyZSBvdXQgaG93IG11Y2ggd2F0ZXIgd2FzIGxvc3Qgc2luY2UgdGhlIGxhc3QgdGltZSBpdCB3YXMgaXJyaWdhdGVkLCBhbmQgcHV0IGJhY2sgZXhhY3RseSB0aGF0IG11Y2guDQoNCkNyb3BzIGxvc2Ugd2F0ZXIgZHVlIHRvIGV2YXBvdHJhbnNwaXJhdGlvbiAoRVQpLCB3aGljaCBjb21iaW5lcyBldmFwb3JhdGlvbiAoaS5lLiwgZnJvbSB0aGUgc29pbCkgYW5kIHJlc3BpcmF0aW9uIChmcm9tIHRoZSBwbGFudHMpLiBUaGUgY2hhbGxlbmdlIGhvd2V2ZXIgaXMgdGhhdCBkaWZmZXJlbnQgY3JvcHMgcmVzcGlyZSBhdCBkaWZmZXJlbnQgcmF0ZXMsIHdoaWNoIGZ1cnRoZXIgdmFyeSBiYXNlZCBvbiB0aGUgc3RhZ2Ugb2YgdGhlIGNyb3AgKGkuZS4sIGJhYnkgcGxhbnRzIGRvbid0IHJlc3BpcmUgbmVhcmx5IGFzIG11Y2ggYXMgbWF0dXJlIHBsYW50cykuIFNvIHRoZXJlIGlzIG5vdCBvbmUtc2l6ZS1maXRzLWFsbCB2YWx1ZSBvZiBFVCB0aGF0IHlvdSBjYW4gZ2V0IGZyb20gd2VhdGhlciB2YXJpYWJsZXMuDQoNClRvIGdldCBhcm91bmQgdGhpcywgQ0lNSVMgc3RhdGlvbnMgaGF2ZSBhIHNlbnNvciB0aGF0IG1lYXN1cmUgJ3JlZmVyZW5jZSBFVCcgKEVUfjB+KSBmcm9tIGEgc3RhbmRhcmQgJ2Nyb3AnIChncmFzcyksIHdoaWNoIGNhbiBiZSBjb252ZXJ0ZWQgdG8gY3JvcCBFVCAoRVR+Y34pIGJ5IG11bHRpcGx5aW5nIHRoZSByZWZlcmVuY2UgRVQgYnkgYSAnY3JvcCBjb2VmZmljaWVudCcgKEt+Y34pIChbbW9yZSBpbmZvXShodHRwczovL2NpbWlzLndhdGVyLmNhLmdvdi9Db250ZW50L3BkZi9Dcm9wX0NvZWZmaWVudHMucGRmKSkuIENyb3AgY29lZmZpY2llbnRzIGhhdmUgYmVlbiBkZXZlbG9wZWQgdGhyb3VnaCByZXNlYXJjaCBmb3IgbWFueSBjcm9wcyAoW21vcmUgaW5mb10oaHR0cHM6Ly93d3cuZmFvLm9yZy8zL3gwNDkwZS94MDQ5MGUwYi5odG0jY3JvcCUyMGNvZWZmaWNpZW50cykpLg0KDQpcDQoNCiMjIEhvdyBtdWNoIHdhdGVyIGRvIG15IHRvbWF0b2VzIG5lZWQ/DQoNCkxldCdzIGNvbXB1dGUgdGhlIGFtb3VudCBvZiBkYWlseSBldmFwb3RyYW5zcGlyYXRpb24gZm9yIHRvbWF0b2VzIGZvciB0aGUgbW9udGggb2YgSnVuZS4gRHVyaW5nIHRoZSBtaWRkbGUgb2YgdGhlIGdyb3dpbmcgc2Vhc29uLCB0b21hdG9lcyBoYXZlIGEgS35jfiA9IDEuMTUuDQoNClRvIGNvbXB1dGUgaXJyaWdhdGlvbiByZXF1aXJlbWVudHMsIHdlIG5lZWQgdG86DQoNCjEuICBQdWxsIG91dCBFVH4wfiBhbmQgcHJlY2lwaXRhdGlvbiBmb3IgdGhlIG1vbnRoIG9mIEp1bmUNCg0KMi4gIFB1dCB0aGVtIGluIHNlcGFyYXRlIGNvbHVtbnM6DQoNCmBgYHtyIGNodW5rMzF9DQpqdW5lX2V0b19wcl90YmwgPC0gY2ltaXNfdmVyb25hMjJfdGJsIHw+IA0KICBmaWx0ZXIoSXRlbSAlaW4lIGMoIkRheUV0byIsICJEYXlQcmVjaXAiKSwgRGF0ZSA+PSBhcy5EYXRlKCIyMDIyLTA2LTAxIiksIERhdGUgPD0gYXMuRGF0ZSgiMjAyMi0wNi0zMCIpICkgfD4gDQogIHBpdm90X3dpZGVyKGlkX2NvbHMgPSBEYXRlLCBuYW1lc19mcm9tID0gSXRlbSwgdmFsdWVzX2Zyb20gPSBWYWx1ZSkNCg0KaGVhZChqdW5lX2V0b19wcl90YmwpDQpgYGANCg0KXA0KDQpUbyBjb21wdXRlIHRoZSBkYWlseSBFVH5jfiBmb3IgdG9tYXRvZXMsIHdlIHNpbXBseSBtdWx0aXBseSB0aGUgcmVmZXJlbmNlIEVUfjB+IGJ5IHRoZSBjcm9wIGNvZWZmaWNpZW50IGZvciB0b21hdG9lczoNCg0KYGBge3IgY2h1bmszMn0NCktjIDwtIDEuMTUNCg0KanVuZV9ldGNfdGJsIDwtIGp1bmVfZXRvX3ByX3RibCB8PiANCiAgbXV0YXRlKEVUY190b21hdG8gPSBEYXlFdG8gKiBLYykgDQoNCmp1bmVfZXRjX3RibCB8PiBoZWFkKCkNCmBgYA0KDQpcDQoNClRoZSB0b3RhbCB3YXRlciBsb3NzIGVhY2ggZGF5IGlzIHRoZSBhbW91bnQgb2YgRVR+Y34gbWludXMgYW55IHByZWNpcGl0YXRpb24gKHdoaWNoIENJTUlTIGFsc28gcmVwb3J0cyBpbiBpbmNoZXMpOg0KDQpgYGB7ciBjaHVuazMzfQ0KanVuZV9uZXR3YXRlcmxvc3NfdGJsIDwtIGp1bmVfZXRjX3RibCB8PiANCiAgbXV0YXRlKG5ldF93YXRlcl9sb3NzX2luID0gRVRjX3RvbWF0byAtIERheVByZWNpcCApDQoNCmp1bmVfbmV0d2F0ZXJsb3NzX3RibCB8PiBoZWFkKCkNCmBgYA0KDQpcDQoNClRvIGNhbGN1bGF0ZSB0aGUgdG90YWwgYW1vdW50IG9mIHdhdGVyIHRoZSB0b21hdG9lcyBuZWVkLCB3ZSBzaW1wbHkgYWRkIHVwIHRoZSBkYWlseSBuZXQgd2F0ZXIgbG9zdCBzaW5jZSB0aGUgbGFzdCBpcnJpZ2F0aW9uIGV2ZW50Lg0KDQpTdXBwb3NlIHRoZSBsYXN0IGlycmlnYXRpb24gd2FzIEp1bmUgMTAsIDIwMjIsIGFuZCB0b2RheSBpcyBKdW5lIDE1LiBIb3cgbXVjaCB3YXRlciBkbyB3ZSBuZWVkIHRvIGFkZD8NCg0KYGBge3IgY2h1bmszNH0NCmp1bmVfbmV0d2F0ZXJsb3NzX3RibCB8PiANCiAgZmlsdGVyKERhdGUgPiBhcy5EYXRlKCIyMDIyLTA2LTEwIiksIERhdGUgPD0gYXMuRGF0ZSgiMjAyMi0wNi0xNSIpKSB8PiANCiAgbXV0YXRlKGN1bW11bGF0aXZlX3dhdGVyX2xvc3QgPSBjdW1zdW0obmV0X3dhdGVyX2xvc3NfaW4pKQ0KYGBgDQoNClwNCg0KIyBDaGFsbGVuZ2UgUXVlc3Rpb24gIzMNCg0KRnJvbSBPY3RvYmVyIDEgMjAyMSwgdGhydSBNYXJjaCAzMSwgMjAyMiwgaG93IG1hbnkgZGF5cyBkaWQgdGhlIHRlbXBlcmF0dXJlIGRpcCBiZWxvdyA1MyDCsEYgKGEgdGVtcGVyYXR1cmUgd2hpY2ggcmVkdWNlcyB0aGUgbG9hZCBvZiBjZXJ0YWluIG92ZXJ3aW50ZXJpbmcgaW5zZWN0cyk/IFtBbnN3ZXJdKGh0dHA6Ly9iaXQubHkvM0FXaTRVZCkNCg0KYGBge3IgY2h1bmszNX0NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCmRhaWx5X2NvbGRkYXlfdGJsIDwtIGNpbWlzX3Zlcm9uYTIyX3RibCB8PiANCiAgZmlsdGVyKEl0ZW0gPT0gIkRheUFpclRtcE1pbiIsIERhdGUgPj0gYXMuRGF0ZSgiMjAyMS0xMC0wMSIpLCBEYXRlIDw9IGFzLkRhdGUoIjIwMjItMDMtMzEiKSkgfD4gDQogIG11dGF0ZShjb2xkX2RheSA9IFZhbHVlIDw9IDUzKSB8PiANCiAgc2VsZWN0KERhdGUsIEl0ZW0sIFZhbHVlLCBjb2xkX2RheSkNCg0KaGVhZChkYWlseV9jb2xkZGF5X3RibCkNCg0KZGFpbHlfY29sZGRheV90YmwgfD4gcHVsbChjb2xkX2RheSkgfD4gdGFibGUoKQ0KYGBgDQoNCiMgRW5kDQoNClJlbWVtYmVyIHRvIHNhdmUgdGhlIE5vdGVib29rIHRvIGdlbmVyYXRlIGEgSFRNTCB2ZXJzaW9uIHRoYXQgaW5jbHVkZXMgYWxsIGV4ZWN1dGVkIGNvZGUgdGhhdCB5b3UgY2FuIHNhdmUgZm9yIGtlZXBzIQ0K