About this R Notebook

R Notebooks are a ‘flavor’ of R markdown that combine plain text and R commands in code chunks. You can download the Rmd file from the ‘code’ button at the top of the page, then open it in RStudio. You run code chunks in the document line-by-line, and the output appears immediately below the code chunk. When you save the Rmd file, it automatically creates a HTML file showing results from all the code that has been run so far.

If you’re in RStudio, you can minimize the console window (and probably close the right-hand panes as well). You won’t need it, because when you run R commands in a R Notebook the output appears below the code chunk (not the console). This takes some getting used to.

Keyboard shortcuts:
- run the current line of R: ctrl + enter
- run everything in the current code chunk: ctrl + shift + enter
- insert a new code chunk: ctrl + alt + i

Setup

Install caladaptr:

if (!require(devtools)) {install.packages("devtools"); library(devtools)}
Loading required package: devtools
Loading required package: usethis
The working directory was changed to D:/GitHub/cal-adapt/caladaptr inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
# 
# devtools::install_github("ucanr-igis/caladaptr")
library(caladaptr)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
caladaptr (version 0.4.5)
URL: https://ucanr-igis.github.io/caladaptr
Bug reports: https://github.com/ucanr-igis/caladaptr/issues
## Make sure you have version 0.4.1 caladaptr or later. 
packageVersion("caladaptr")
[1] ‘0.4.5’

Now we can load the other packages we’ll be using below:

pkgs_req <- c("ggplot2", "dplyr", "tidyr", "lubridate", "conflicted")
pkgs_missing <- pkgs_req[!(pkgs_req %in% installed.packages()[,"Package"])]
if (length(pkgs_missing)) install.packages(pkgs_missing, dependencies=TRUE)

library(units)
library(ggplot2)
library(dplyr)
library(conflicted)
library(tidyr)
library(tmap)
library(lubridate)
library(sf)

The last setup task is to define your preferences when you use an ambiguous function name (i.e., a function that exists in more than one package). This is particularly needed with a few common generic functions from dplyr:

conflict_prefer("filter", "dplyr", quiet = TRUE)
conflict_prefer("count", "dplyr", quiet = TRUE)
conflict_prefer("select", "dplyr", quiet = TRUE)

Example #1: Get Projected Temperature for a Point

Goal: Make a time series plot of projected maximum annual temperature for a single point, using the four recommended GCMs for California under RCP 4.5.

1. Create the API Request

The first step in getting climate variables back is to create a Cal-Adapt API request object. This involves stringing together a series of functions that specify the pieces of the request. The following constants can help you specify pieces of the request:

cap1 <- ca_loc_pt(coords = c(-121.4687, 38.5938)) %>%
  ca_gcm(gcms[1:4]) %>%                                 
  ca_scenario(c("rcp45", "rcp85")) %>%
  ca_period("year") %>%
  ca_years(start = 2030, end = 2080) %>%
  ca_cvar(c("tasmax"))

Entering the name of an API request object at the console will display its components:

cap1
Cal-Adapt API Request
Location(s): 
  x: -121.469
  y: 38.594
Variable(s): tasmax
Temporal aggregration period(s): year
GCM(s): HadGEM2-ES, CNRM-CM5, CanESM2, MIROC5
Scenario(s): rcp45, rcp85
Dates: 2030-01-01 to 2080-12-31
 

Tip: You don’t have to memorize a bunch of keywords. caladaptr provides several constants that contain the values you can pass to API construction functions, including gcms, scenarios, cvars, and periods.

## Available global change models
## Note, the first 4 GCMs are the recommended priority models for California
gcms
 [1] "HadGEM2-ES" "CNRM-CM5"   "CanESM2"    "MIROC5"     "ACCESS1-0"  "CCSM4"      "CESM1-BGC"  "CMCC-CMS"  
 [9] "GFDL-CM3"   "HadGEM2-CC" "ens32avg"   "ens32max"   "ens32min"  
## Available emissions scenarios
scenarios
[1] "rcp45"      "rcp85"      "historical"
## Available climate variables
cvars
[1] "tasmax" "tasmin" "pr"     "swe"   
## Available temporal aggregation periods
periods
[1] "day"    "month"  "year"   "30yavg"

Note: Cal-Adapt has data for many but by no means all combinations of the above constants.

You can use these values as arguments to build up the API request:

To verify the location in an API request, you can plot it. (Note we still haven’t fetched any climate data yet, this just shows you the location the request will ask for.)

plot(cap1)

2. Fetch Data

Now it’s time to fetch data with ca_getvals_tbl(). The object returned by ca_getvals_tbl() is tibble (data frame):

cap1_tbl <- cap1 %>% 
  ca_getvals_tbl(quiet = TRUE)

cap1_tbl

3. Munge the Results

To produce the desired time series plot, we need to i) pull out just values for RCP 4.5, and ii) convert degrees to °F. For the unit conversion, we can use the handy set_units function from the units package.

cap1_rcp45_tbl <- cap1_tbl %>%
  filter(scenario == "rcp45") %>%
  mutate(temp_f = set_units(val, degF))

cap1_rcp45_tbl

Plot the Time Series

Plot these with ggplot:

ggplot(data = cap1_rcp45_tbl, aes(x = as.Date(dt), y = as.numeric(temp_f), group = gcm)) +
  geom_line(aes(color=gcm)) +
  labs(title = "Average Annual Maximum Temperature for RCP4.5", x = "year", y = "temp (F)")

YOUR TURN

Modify the above to create a similar plot for RCP 8.5.

## Plot of RCP 8.5 goes here

Example #2: Retrieve County Data

Goal: produce a time series plot showing the difference between RCP85 and RCP45 on maximum annual temperature for a single county.

The Cal-Adapt API has a number of ‘preset’ areas-of-interest (also called boundary layers) that you can use cookie-cutter style when retrieving climate data. The advantage of using an AOI Preset is that you don’t need to import a GIS layer to query according to these common features. You just need to know the name or ID number of the feature(s) you’re interested in.

The following AOI Presets are available:

aoipreset_types
 [1] "censustracts"      "counties"          "cdistricts"        "ccc4aregions"      "climregions"      
 [6] "hydrounits"        "irwm"              "electricutilities" "wecc-load-area"    "evtlocations"     
[11] "place"            

1. Find the FIPS code for your county of interest

To use an AOI Preset, you need to specify which feature(s) you’re interested in by providing the value(s) for one of the id fields. The specific columns available for identifying features vary according to preset. You can view the id columns and their values for an AOI Preset using the built-in aoipreset_idval constant. For example the counties layer allows you to specify a county by name, fips code, or id. Remember that everything in R is case-sensitive!

aoipreset_idval$counties

You can find county fips codes on Google - just be sure to use the 6-character version which includes the state. Alternately, you can plot the county preset layer and click on your county of interest:

counties_sf <- ca_aoipreset_geom("counties")
Reading layer `counties' from data source `C:\Users\Andy\AppData\Local\R\cache\R\caladaptr\counties.gpkg' using driver `GPKG'
Simple feature collection with 87 features and 54 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -13871160 ymin: 3833648 xmax: -12625080 ymax: 5416187
projected CRS:  WGS 84 / Pseudo-Mercator
tmap_mode("view")
tm_shape(counties_sf) + tm_polygons()

For this example, we’ll look at Kings County (FIPS = 06031).

2. Create the API Request

Let’s create an API request for Kings County. Note below the inclusion of ca_options() to specify how we want to aggregate the pixels that fall within the country. This is required whenever you query polygon features.

cap2 <- ca_loc_aoipreset(type="counties", idfld = "fips", idval = "06031") %>%
  ca_gcm(gcms[1:4]) %>%
  ca_scenario(c("rcp45", "rcp85")) %>%
  ca_period("year") %>%
  ca_years(start = 2030, end = 2080) %>%
  ca_cvar(c("tasmin")) %>%
  ca_options(spatial_ag = "max")

cap2
Cal-Adapt API Request
Location(s): 
  AOI Preset: counties
  fips(s): 06031
Variable(s): tasmin
Temporal aggregration period(s): year
GCM(s): HadGEM2-ES, CNRM-CM5, CanESM2, MIROC5
Scenario(s): rcp45, rcp85
Dates: 2030-01-01 to 2080-12-31
Options:
  spatial ag: max
 

As before, we can plot the API request to double-check we got the right location:

plot(cap2)

3. Fetch Data

Fetch data with ca_getvals_tbl():

cap2_tbl <- cap2 %>% 
  ca_getvals_tbl(quiet = TRUE) 

cap2_tbl

4. Munge Results

To compute the difference between RCP 8.5 and RCP 4.5, we need to split them into separate columns. This is an example of going from a ‘long’ format to a ‘wide’ format. Fortunately, the tidyr package has a function called pivot_wider that can do this in single command. While we’re at it we’ll convert the temp to degrees Fahrenheit:

rcp85_minus_rcp45_tbl <- cap2_tbl %>% 
  mutate(temp_f = set_units(val, degF)) %>% 
  select(fips, gcm, scenario, dt, temp_f) %>% 
  pivot_wider(names_from = scenario, values_from = temp_f)

head(rcp85_minus_rcp45_tbl)

5. Plot

Now we’re ready to make the plot:

ggplot(data = rcp85_minus_rcp45_tbl, aes(x = as.Date(dt), y = as.numeric(rcp85 - rcp45), group = gcm)) +
  geom_line(aes(color=gcm)) +
  labs(title = "Difference between RCP8.5 and RCP4.5 in the Maximum Annual Minimum \nTemperature for Kings County", x = "year", y = "temp (F)")

Example #3: Analyze Extreme Heat in a Census Tract

Goal: Compare extreme heat occurrence under RCP 4.5 and RCP 8.5 for a single census tract over a 20-year time span. We’ll measure ‘extreme heat’ in two ways, i) the proportion of total days when the max temp exceeds a certain threshold, and ii) the number of ‘heat spells’ defined as a period of n or more consecutive days when the temperature exceeds a threshold.

1. Find the GEOID of a Census Tract in Kings County

Census tracts have 10-digit GEOID numbers rather than 6-digit FIPS codes (more info). To find the GEOID for a census tract in Kings County, we could:

  1. Ask Google.
  2. Plot all the census tracts (as we did above with counties), and click on the one we’re interested in.
  3. Look it up using the tidycensus package.
  4. Take advantage of the fact that the GEOID of census tracts in Kings County all start with its FIPS code (6031).

We’ll use method #4, and grab the census tracts GEOIDs from aoipreset_idval:

tracts_kings <- grep(pattern = "^6031", aoipreset_idval$censustracts$tract, value = TRUE)
tracts_kings
 [1] "6031000100" "6031000200" "6031000300" "6031000402" "6031000403" "6031000404" "6031000405" "6031000500"
 [9] "6031000601" "6031000602" "6031000701" "6031000702" "6031000800" "6031000900" "6031001001" "6031001002"
[17] "6031001003" "6031001100" "6031001200" "6031001300" "6031001401" "6031001402" "6031001500" "6031001601"
[25] "6031001602" "6031001701" "6031981800"

To plot just these tracts we can feed these GEOID values into filter():

census_tracts_sf <- ca_aoipreset_geom("censustracts")
Reading layer `censustracts' from data source `C:\Users\Andy\AppData\Local\R\cache\R\caladaptr\censustracts.gpkg' using driver `GPKG'
Simple feature collection with 8034 features and 63 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -13850030 ymin: 3833633 xmax: -12705030 ymax: 5162403
projected CRS:  WGS 84 / Pseudo-Mercator
tmap_mode("view")
tm_shape(census_tracts_sf %>% filter(tract %in% tracts_kings)) + tm_polygons()

For the rest of this exercise we’ll use 6031001701.

2. Construct the API Request

The following will give us 20 years of daily maximum temperature for our census tract. To keep it simple we’ll just use one GCM.

cap3 <- ca_loc_aoipreset(type="censustracts", idfld = "tract", idval = 6031001701) %>%
  ca_gcm("MIROC5") %>%
  ca_scenario(c("rcp45", "rcp85")) %>%
  ca_period("day") %>%
  ca_years(start = "2070-01-01", end = "2089-12-31") %>%
  ca_cvar("tasmax") %>% 
  ca_options(spatial_ag = "max")

cap3
Cal-Adapt API Request
Location(s): 
  AOI Preset: censustracts
  tract(s): 6031001701
Variable(s): tasmax
Temporal aggregration period(s): day
GCM(s): MIROC5
Scenario(s): rcp45, rcp85
Dates: 2070-01-01 to 2089-12-31
Options:
  spatial ag: max
 
plot(cap3)

3. Fetch Data

Now we’re ready to fetch data:

cap3_tbl <- cap3 %>% 
  ca_getvals_tbl(quiet = TRUE)

dim(cap3_tbl)
[1] 14610     8
head(cap3_tbl)

4. Munge the Results

The only ‘munging’ we need to do is to add a column for the temperature in Farenheit.

cap3_degf_tbl <- cap3_tbl %>% mutate(temp_f = set_units(val, degF))

head(cap3_degf_tbl)

5. Compare the Distribution of Maximum Daily Temp

Plot a histogram of maximum temperature over this 20-year period for each emission scenario:

hist(cap3_degf_tbl %>%  filter(scenario == "rcp45") %>% pull(temp_f),
     main = "RCP 4.5: Maximum Daily Temp, 2070-2089",
     xlab = "temp (F)") 


hist(cap3_degf_tbl %>%  filter(scenario == "rcp85") %>% pull(temp_f),
     main = "RCP 8.5: Maximum Daily Temp, 2070-2089",
     xlab = "temp (F)") 

6. Count the Number of Days over 110°F Projected Under each RCP

cap3_degf_tbl %>% 
  mutate(really_hot_tf = temp_f >= set_units(110, degF)) %>%
  group_by(scenario, really_hot_tf) %>% 
  count() %>% 
  pivot_wider(names_from = scenario, values_from = n)

7. Count the Number of Heat Spells Projected Under each RCP

A simple definition of ‘heat spell’ is consecutive hot days. Let’s count Count the number of heat spells consisting of 4 or more consecutive days of >110°F.

The first step is to pull out the values for just one RCP, sort them by date, add a logical column if the maximum temp that day exceeded our treshhold. We’ll then feed those values into rle(), which breaks up the time series into ‘runs’ of TRUE and FALSE.

## Use rle() to chop up a time series of TRUE of FALSE into 'runs'
rcp45_heat_runs <- cap3_degf_tbl %>% 
  filter(scenario == "rcp45") %>% 
  arrange(dt) %>% 
  mutate(really_hot_tf = temp_f >= set_units(110, degF)) %>% 
  pull(really_hot_tf) %>% 
  rle()

rcp45_heat_runs
Run Length Encoding
  lengths: int [1:241] 206 4 49 2 274 2 45 1 1 2 ...
  values : logi [1:241] FALSE TRUE FALSE TRUE FALSE TRUE ...

Next, we find the runs where two conditions are met: run length is >= 4 days, and the run value = TRUE (meaning its a run of really hot days).

sum(rcp45_heat_runs$values & rcp45_heat_runs$lengths >= 4)
[1] 36

We can do the same for RCP 8.5:

rcp85_heat_runs <- cap3_degf_tbl %>% 
  filter(scenario == "rcp85") %>% 
  arrange(dt) %>% 
  mutate(really_hot_tf = temp_f >= set_units(110, degF)) %>% 
  pull(really_hot_tf) %>% 
  rle()

sum(rcp85_heat_runs$values & rcp85_heat_runs$lengths >= 4)
[1] 45

Conclusion

Under RCP 4.5, the MIROC5 model predicts this census tract will experience 36 extreme heat spells (defined as 4 or more days where the temperature hit 110°F) during this 20 year period. In comparison, MIROC5 predicts 45 extreme heat spells under RCP 8.5.

YOUR TURN

How many heat spells will there be under each RCP if we define a heat spell to be at least 7 days over 100 °F?

## Your answer goes here:

Plot the distribution of the number of consecutive days over 100 °F (i.e., heat spell duration).

## Your answer goes here:
LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KYGBge2NzcyBlY2hvID0gRkFMU0V9DQpoMSB7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LXNpemU6IDI0cHg7DQogIGNvbG9yOiBkYXJrb2xpdmVncmVlbjsNCiAgYm9yZGVyLXRvcDogM3B4IHNvbGlkIGRpbWdyZXk7DQogIG1hcmdpbi10b3A6IDFlbTsNCiAgcGFkZGluZy10b3A6IDAuNWVtOw0KfQ0KaDEudGl0bGUgew0KICBjb2xvcjogYmxhY2s7DQogIGJvcmRlcjogbm9uZTsNCn0NCmgyIHsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGZvbnQtc2l6ZTogMjJweDsNCiAgY29sb3I6IGRpbWdyYXk7DQp9DQoNCmgzIHsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGZvbnQtc2l6ZTogMThweDsNCiAgY29sb3I6IGJsYWNrOw0KfQ0KDQpgYGANCg0KPHAgc3R5bGU9InRleHQtYWxpZ246cmlnaHQ7Ij48aW1nIHNyYz0iaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHIvcmVmZXJlbmNlL2ZpZ3VyZXMvY2FsYWRhcHRyLWJldGFfbG9nby5zdmciIHdpZHRoPSIyNDAiIC8+PC9wPg0KDQojIEFib3V0IHRoaXMgUiBOb3RlYm9vaw0KDQpSIE5vdGVib29rcyBhcmUgYSAnZmxhdm9yJyBvZiBSIG1hcmtkb3duIHRoYXQgY29tYmluZSBwbGFpbiB0ZXh0IGFuZCBSIGNvbW1hbmRzIGluIGNvZGUgY2h1bmtzLiBZb3UgY2FuIGRvd25sb2FkIHRoZSBSbWQgZmlsZSBmcm9tIHRoZSAnY29kZScgYnV0dG9uIGF0IHRoZSB0b3Agb2YgdGhlIHBhZ2UsIHRoZW4gb3BlbiBpdCBpbiBSU3R1ZGlvLiBZb3UgcnVuIGNvZGUgY2h1bmtzIGluIHRoZSBkb2N1bWVudCBsaW5lLWJ5LWxpbmUsIGFuZCB0aGUgb3V0cHV0IGFwcGVhcnMgaW1tZWRpYXRlbHkgYmVsb3cgdGhlIGNvZGUgY2h1bmsuIFdoZW4geW91IHNhdmUgdGhlIFJtZCBmaWxlLCBpdCBhdXRvbWF0aWNhbGx5IGNyZWF0ZXMgYSBIVE1MIGZpbGUgc2hvd2luZyByZXN1bHRzIGZyb20gYWxsIHRoZSBjb2RlIHRoYXQgaGFzIGJlZW4gcnVuIHNvIGZhci4NCg0KSWYgeW91J3JlIGluIFJTdHVkaW8sIHlvdSBjYW4gKm1pbmltaXplIHRoZSBjb25zb2xlIHdpbmRvdyogKGFuZCBwcm9iYWJseSBjbG9zZSB0aGUgcmlnaHQtaGFuZCBwYW5lcyBhcyB3ZWxsKS4gWW91IHdvbid0IG5lZWQgaXQsICBiZWNhdXNlIHdoZW4geW91IHJ1biBSIGNvbW1hbmRzIGluIGEgUiBOb3RlYm9vayB0aGUgKm91dHB1dCBhcHBlYXJzIGJlbG93IHRoZSBjb2RlIGNodW5rKiAobm90IHRoZSBjb25zb2xlKS4gVGhpcyB0YWtlcyBzb21lIGdldHRpbmcgdXNlZCB0by4NCg0KS2V5Ym9hcmQgc2hvcnRjdXRzOiAgDQotIHJ1biB0aGUgY3VycmVudCBsaW5lIG9mIFI6ICpjdHJsICsgZW50ZXIqICANCi0gcnVuIGV2ZXJ5dGhpbmcgaW4gdGhlIGN1cnJlbnQgY29kZSBjaHVuazogKmN0cmwgKyBzaGlmdCArIGVudGVyKiAgDQotIGluc2VydCBhIG5ldyBjb2RlIGNodW5rOiAqY3RybCArIGFsdCArIGkqICANCg0KIyBTZXR1cA0KDQpJbnN0YWxsIGBjYWxhZGFwdHJgOg0KDQpgYGB7cn0NCmlmICghcmVxdWlyZShkZXZ0b29scykpIHtpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpOyBsaWJyYXJ5KGRldnRvb2xzKX0NCiMgDQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigidWNhbnItaWdpcy9jYWxhZGFwdHIiKQ0KbGlicmFyeShjYWxhZGFwdHIpDQojIyBNYWtlIHN1cmUgeW91IGhhdmUgdmVyc2lvbiAwLjQuNSBjYWxhZGFwdHIgb3IgbGF0ZXIuIA0KcGFja2FnZVZlcnNpb24oImNhbGFkYXB0ciIpDQpgYGANCg0KTm93IHdlIGNhbiBsb2FkIHRoZSBvdGhlciBwYWNrYWdlcyB3ZSdsbCBiZSB1c2luZyBiZWxvdzoNCg0KYGBge3IgbGlicmFyeV9hbGwsIG1lc3NhZ2UgPSBGQUxTRX0NCnBrZ3NfcmVxIDwtIGMoImdncGxvdDIiLCAiZHBseXIiLCAidGlkeXIiLCAibHVicmlkYXRlIiwgImNvbmZsaWN0ZWQiKQ0KcGtnc19taXNzaW5nIDwtIHBrZ3NfcmVxWyEocGtnc19yZXEgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssIlBhY2thZ2UiXSldDQppZiAobGVuZ3RoKHBrZ3NfbWlzc2luZykpIGluc3RhbGwucGFja2FnZXMocGtnc19taXNzaW5nLCBkZXBlbmRlbmNpZXM9VFJVRSkNCg0KbGlicmFyeSh1bml0cykNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGNvbmZsaWN0ZWQpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHNmKQ0KYGBgDQoNClRoZSBsYXN0IHNldHVwIHRhc2sgaXMgdG8gZGVmaW5lIHlvdXIgcHJlZmVyZW5jZXMgd2hlbiB5b3UgdXNlIGFuIGFtYmlndW91cyBmdW5jdGlvbiBuYW1lIChpLmUuLCBhIGZ1bmN0aW9uIHRoYXQgZXhpc3RzIGluIG1vcmUgdGhhbiBvbmUgcGFja2FnZSkuIFRoaXMgaXMgcGFydGljdWxhcmx5IG5lZWRlZCB3aXRoIGEgZmV3IGNvbW1vbiBnZW5lcmljIGZ1bmN0aW9ucyBmcm9tIGBkcGx5cmA6DQoNCmBgYHtyIHNldF9jb25mbGljdHN9DQpjb25mbGljdF9wcmVmZXIoImZpbHRlciIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmNvbmZsaWN0X3ByZWZlcigiY291bnQiLCAiZHBseXIiLCBxdWlldCA9IFRSVUUpDQpjb25mbGljdF9wcmVmZXIoInNlbGVjdCIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmBgYA0KDQojIEV4YW1wbGUgIzE6IEdldCBQcm9qZWN0ZWQgVGVtcGVyYXR1cmUgZm9yIGEgUG9pbnQNCg0KKipHb2FsKio6IE1ha2UgYSAqKnRpbWUgc2VyaWVzIHBsb3QqKiBvZiBwcm9qZWN0ZWQgbWF4aW11bSBhbm51YWwgdGVtcGVyYXR1cmUgZm9yIGEgKipzaW5nbGUgcG9pbnQqKiwgdXNpbmcgdGhlICoqZm91ciByZWNvbW1lbmRlZCBHQ01zKiogZm9yIENhbGlmb3JuaWEgdW5kZXIgUkNQIDQuNS4NCg0KIyMgMVwuIENyZWF0ZSB0aGUgQVBJIFJlcXVlc3QgDQoNClRoZSBmaXJzdCBzdGVwIGluIGdldHRpbmcgY2xpbWF0ZSB2YXJpYWJsZXMgYmFjayBpcyB0byBjcmVhdGUgYSBDYWwtQWRhcHQgQVBJIHJlcXVlc3Qgb2JqZWN0LiBUaGlzIGludm9sdmVzIHN0cmluZ2luZyB0b2dldGhlciBhIHNlcmllcyBvZiBmdW5jdGlvbnMgdGhhdCBzcGVjaWZ5IHRoZSBwaWVjZXMgb2YgdGhlIHJlcXVlc3QuIFRoZSBmb2xsb3dpbmcgY29uc3RhbnRzIGNhbiBoZWxwIHlvdSBzcGVjaWZ5IHBpZWNlcyBvZiB0aGUgcmVxdWVzdDoNCg0KYGBge3J9DQpjYXAxIDwtIGNhX2xvY19wdChjb29yZHMgPSBjKC0xMjEuNDY4NywgMzguNTkzOCkpICU+JQ0KICBjYV9nY20oZ2Ntc1sxOjRdKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgY2Ffc2NlbmFyaW8oYygicmNwNDUiLCAicmNwODUiKSkgJT4lDQogIGNhX3BlcmlvZCgieWVhciIpICU+JQ0KICBjYV95ZWFycyhzdGFydCA9IDIwMzAsIGVuZCA9IDIwODApICU+JQ0KICBjYV9jdmFyKGMoInRhc21heCIpKQ0KYGBgDQoNCkVudGVyaW5nIHRoZSBuYW1lIG9mIGFuIEFQSSByZXF1ZXN0IG9iamVjdCBhdCB0aGUgY29uc29sZSB3aWxsIGRpc3BsYXkgaXRzIGNvbXBvbmVudHM6DQoNCmBgYHtyfQ0KY2FwMQ0KYGBgDQoNCg0KKlRpcCo6IFlvdSBkb24ndCBoYXZlIHRvIG1lbW9yaXplIGEgYnVuY2ggb2Yga2V5d29yZHMuIGNhbGFkYXB0ciBwcm92aWRlcyBzZXZlcmFsIGNvbnN0YW50cyB0aGF0IGNvbnRhaW4gdGhlIHZhbHVlcyB5b3UgY2FuIHBhc3MgdG8gQVBJIGNvbnN0cnVjdGlvbiBmdW5jdGlvbnMsIGluY2x1ZGluZyBgZ2Ntc2AsIGBzY2VuYXJpb3NgLCBgY3ZhcnNgLCBhbmQgYHBlcmlvZHNgLg0KDQpgYGB7ciB2aWV3X2NvbnN0YW50c30NCiMjIEF2YWlsYWJsZSBnbG9iYWwgY2hhbmdlIG1vZGVscw0KIyMgTm90ZSwgdGhlIGZpcnN0IDQgR0NNcyBhcmUgdGhlIHJlY29tbWVuZGVkIHByaW9yaXR5IG1vZGVscyBmb3IgQ2FsaWZvcm5pYQ0KZ2Ntcw0KDQojIyBBdmFpbGFibGUgZW1pc3Npb25zIHNjZW5hcmlvcw0Kc2NlbmFyaW9zDQoNCiMjIEF2YWlsYWJsZSBjbGltYXRlIHZhcmlhYmxlcw0KY3ZhcnMNCg0KIyMgQXZhaWxhYmxlIHRlbXBvcmFsIGFnZ3JlZ2F0aW9uIHBlcmlvZHMNCnBlcmlvZHMNCmBgYA0KDQoqKk5vdGUqKjogQ2FsLUFkYXB0IGhhcyBkYXRhIGZvciBtYW55IGJ1dCBieSBubyBtZWFucyBhbGwgY29tYmluYXRpb25zIG9mIHRoZSBhYm92ZSBjb25zdGFudHMuDQoNCllvdSBjYW4gdXNlIHRoZXNlIHZhbHVlcyBhcyBhcmd1bWVudHMgdG8gYnVpbGQgdXAgdGhlIEFQSSByZXF1ZXN0Og0KDQoNCg0KVG8gdmVyaWZ5IHRoZSBsb2NhdGlvbiBpbiBhbiBBUEkgcmVxdWVzdCwgeW91IGNhbiBwbG90IGl0LiAoTm90ZSB3ZSBzdGlsbCBoYXZlbid0IGZldGNoZWQgYW55IGNsaW1hdGUgZGF0YSB5ZXQsIHRoaXMganVzdCBzaG93cyB5b3UgdGhlIGxvY2F0aW9uIHRoZSByZXF1ZXN0IHdpbGwgYXNrIGZvci4pDQoNCmBgYHtyIHBsb3RfY2FwMX0NCnBsb3QoY2FwMSkNCmBgYA0KDQojIyAyXC4gRmV0Y2ggRGF0YQ0KDQpOb3cgaXQncyB0aW1lIHRvIGZldGNoIGRhdGEgd2l0aCBgY2FfZ2V0dmFsc190YmwoKWAuIFRoZSBvYmplY3QgcmV0dXJuZWQgYnkgYGNhX2dldHZhbHNfdGJsKClgIGlzIHRpYmJsZSAoZGF0YSBmcmFtZSk6ICANCg0KYGBge3IgY2FwMV9mZXRjaCwgY2FjaGUgPSBUUlVFfQ0KY2FwMV90YmwgPC0gY2FwMSAlPiUgDQogIGNhX2dldHZhbHNfdGJsKHF1aWV0ID0gVFJVRSkNCg0KY2FwMV90YmwNCmBgYA0KDQojIyAzXC4gTXVuZ2UgdGhlIFJlc3VsdHMNCg0KVG8gcHJvZHVjZSB0aGUgZGVzaXJlZCB0aW1lIHNlcmllcyBwbG90LCB3ZSBuZWVkIHRvIGkpIHB1bGwgb3V0IGp1c3QgdmFsdWVzIGZvciBSQ1AgNC41LCBhbmQgaWkpIGNvbnZlcnQgZGVncmVlcyB0byAmIzE3NjtGLiBGb3IgdGhlIHVuaXQgY29udmVyc2lvbiwgd2UgY2FuIHVzZSB0aGUgaGFuZHkgYHNldF91bml0c2AgZnVuY3Rpb24gZnJvbSB0aGUgYHVuaXRzYCBwYWNrYWdlLg0KDQpgYGB7ciBjYXAxX211bmdlfQ0KY2FwMV9yY3A0NV90YmwgPC0gY2FwMV90YmwgJT4lDQogIGZpbHRlcihzY2VuYXJpbyA9PSAicmNwNDUiKSAlPiUNCiAgbXV0YXRlKHRlbXBfZiA9IHNldF91bml0cyh2YWwsIGRlZ0YpKQ0KDQpjYXAxX3JjcDQ1X3RibA0KYGBgDQoNCiMjIFBsb3QgdGhlIFRpbWUgU2VyaWVzDQoNClBsb3QgdGhlc2Ugd2l0aCBnZ3Bsb3Q6DQoNCmBgYHtyIGdncGxvdF9jYXAxLCBjYWNoZSA9IFRSVUV9DQpnZ3Bsb3QoZGF0YSA9IGNhcDFfcmNwNDVfdGJsLCBhZXMoeCA9IGFzLkRhdGUoZHQpLCB5ID0gYXMubnVtZXJpYyh0ZW1wX2YpLCBncm91cCA9IGdjbSkpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1nY20pKSArDQogIGxhYnModGl0bGUgPSAiQXZlcmFnZSBBbm51YWwgTWF4aW11bSBUZW1wZXJhdHVyZSBmb3IgUkNQNC41IiwgeCA9ICJ5ZWFyIiwgeSA9ICJ0ZW1wIChGKSIpDQpgYGANCg0KIyMgWU9VUiBUVVJODQoNCk1vZGlmeSB0aGUgYWJvdmUgdG8gY3JlYXRlIGEgc2ltaWxhciBwbG90IGZvciBSQ1AgOC41Lg0KDQpgYGB7cn0NCiMjIFBsb3Qgb2YgUkNQIDguNSBnb2VzIGhlcmUNCg0KYGBgDQoNCiMgRXhhbXBsZSAjMjogUmV0cmlldmUgQ291bnR5IERhdGEgDQoNCipHb2FsKjogcHJvZHVjZSBhICoqdGltZSBzZXJpZXMgcGxvdCoqIHNob3dpbmcgdGhlICoqZGlmZmVyZW5jZSBiZXR3ZWVuIFJDUDg1IGFuZCBSQ1A0NSoqIG9uIG1heGltdW0gYW5udWFsIHRlbXBlcmF0dXJlIGZvciBhICoqc2luZ2xlIGNvdW50eSoqLg0KDQpUaGUgQ2FsLUFkYXB0IEFQSSBoYXMgYSBudW1iZXIgb2YgJ3ByZXNldCcgYXJlYXMtb2YtaW50ZXJlc3QgKGFsc28gY2FsbGVkIGJvdW5kYXJ5IGxheWVycykgdGhhdCB5b3UgY2FuIHVzZSBjb29raWUtY3V0dGVyIHN0eWxlIHdoZW4gcmV0cmlldmluZyBjbGltYXRlIGRhdGEuIFRoZSBhZHZhbnRhZ2Ugb2YgdXNpbmcgYW4gQU9JIFByZXNldCBpcyB0aGF0IHlvdSBkb24ndCBuZWVkIHRvIGltcG9ydCBhIEdJUyBsYXllciB0byBxdWVyeSBhY2NvcmRpbmcgdG8gdGhlc2UgY29tbW9uIGZlYXR1cmVzLiBZb3UganVzdCBuZWVkIHRvIGtub3cgdGhlIG5hbWUgb3IgSUQgbnVtYmVyIG9mIHRoZSBmZWF0dXJlKHMpIHlvdSdyZSBpbnRlcmVzdGVkIGluLg0KDQpUaGUgZm9sbG93aW5nIEFPSSBQcmVzZXRzIGFyZSBhdmFpbGFibGU6DQoNCmBgYHtyIHByZXNldF90eXBlcywgY2FjaGUgPSBGQUxTRX0NCmFvaXByZXNldF90eXBlcw0KYGBgDQoNCiMjIDFcLiBGaW5kIHRoZSBGSVBTIGNvZGUgZm9yIHlvdXIgY291bnR5IG9mIGludGVyZXN0DQoNClRvIHVzZSBhbiBBT0kgUHJlc2V0LCB5b3UgbmVlZCB0byBzcGVjaWZ5IHdoaWNoIGZlYXR1cmUocykgeW91J3JlIGludGVyZXN0ZWQgaW4gYnkgcHJvdmlkaW5nIHRoZSB2YWx1ZShzKSBmb3Igb25lIG9mIHRoZSBpZCBmaWVsZHMuIFRoZSBzcGVjaWZpYyBjb2x1bW5zIGF2YWlsYWJsZSBmb3IgaWRlbnRpZnlpbmcgZmVhdHVyZXMgdmFyeSBhY2NvcmRpbmcgdG8gcHJlc2V0LiBZb3UgY2FuIHZpZXcgdGhlIGlkIGNvbHVtbnMgYW5kIHRoZWlyIHZhbHVlcyBmb3IgYW4gQU9JIFByZXNldCB1c2luZyB0aGUgYnVpbHQtaW4gYGFvaXByZXNldF9pZHZhbGAgY29uc3RhbnQuIEZvciBleGFtcGxlIHRoZSBjb3VudGllcyBsYXllciBhbGxvd3MgeW91IHRvIHNwZWNpZnkgYSBjb3VudHkgYnkgbmFtZSwgZmlwcyBjb2RlLCBvciBpZC4gUmVtZW1iZXIgdGhhdCBldmVyeXRoaW5nIGluIFIgaXMgY2FzZS1zZW5zaXRpdmUhDQoNCmBgYHtyIGlkdmFsX2NvdW50aWVzLCBjYWNoZSA9IFRSVUV9DQphb2lwcmVzZXRfaWR2YWwkY291bnRpZXMNCmBgYA0KDQpZb3UgY2FuIGZpbmQgY291bnR5IGZpcHMgY29kZXMgb24gR29vZ2xlIC0ganVzdCBiZSBzdXJlIHRvIHVzZSB0aGUgNi1jaGFyYWN0ZXIgdmVyc2lvbiB3aGljaCBpbmNsdWRlcyB0aGUgc3RhdGUuIEFsdGVybmF0ZWx5LCB5b3UgY2FuIHBsb3QgdGhlIGNvdW50eSBwcmVzZXQgbGF5ZXIgYW5kIGNsaWNrIG9uIHlvdXIgY291bnR5IG9mIGludGVyZXN0OiANCg0KYGBge3IgcGxvdF9jb3VudGllcywgbWVzc2FnZSA9IEZBTFNFfQ0KY291bnRpZXNfc2YgPC0gY2FfYW9pcHJlc2V0X2dlb20oImNvdW50aWVzIikNCnRtYXBfbW9kZSgidmlldyIpDQp0bV9zaGFwZShjb3VudGllc19zZikgKyB0bV9wb2x5Z29ucygpDQpgYGANCg0KRm9yIHRoaXMgZXhhbXBsZSwgd2UnbGwgbG9vayBhdCAqKktpbmdzIENvdW50eSoqIChGSVBTID0gYDA2MDMxYCkuDQoNCiMjIDJcLiBDcmVhdGUgdGhlIEFQSSBSZXF1ZXN0DQoNCkxldCdzIGNyZWF0ZSBhbiBBUEkgcmVxdWVzdCBmb3IgS2luZ3MgQ291bnR5LiBOb3RlIGJlbG93IHRoZSBpbmNsdXNpb24gb2YgYGNhX29wdGlvbnMoKWAgdG8gc3BlY2lmeSBob3cgd2Ugd2FudCB0byBhZ2dyZWdhdGUgdGhlIHBpeGVscyB0aGF0IGZhbGwgd2l0aGluIHRoZSBjb3VudHJ5LiBUaGlzIGlzIHJlcXVpcmVkIHdoZW5ldmVyIHlvdSBxdWVyeSBwb2x5Z29uIGZlYXR1cmVzLg0KDQpgYGB7ciBjYXAyX2RlZmluZSwgY2FjaGUgPSBUUlVFfQ0KY2FwMiA8LSBjYV9sb2NfYW9pcHJlc2V0KHR5cGU9ImNvdW50aWVzIiwgaWRmbGQgPSAiZmlwcyIsIGlkdmFsID0gIjA2MDMxIikgJT4lDQogIGNhX2djbShnY21zWzE6NF0pICU+JQ0KICBjYV9zY2VuYXJpbyhjKCJyY3A0NSIsICJyY3A4NSIpKSAlPiUNCiAgY2FfcGVyaW9kKCJ5ZWFyIikgJT4lDQogIGNhX3llYXJzKHN0YXJ0ID0gMjAzMCwgZW5kID0gMjA4MCkgJT4lDQogIGNhX2N2YXIoYygidGFzbWluIikpICU+JQ0KICBjYV9vcHRpb25zKHNwYXRpYWxfYWcgPSAibWF4IikNCg0KY2FwMg0KYGBgDQoNCkFzIGJlZm9yZSwgd2UgY2FuIHBsb3QgdGhlIEFQSSByZXF1ZXN0IHRvIGRvdWJsZS1jaGVjayB3ZSBnb3QgdGhlIHJpZ2h0IGxvY2F0aW9uOiANCg0KYGBge3IgcGxvdF9jYXAyLCBjYWNoZSA9IFRSVUV9DQpwbG90KGNhcDIpDQpgYGANCg0KIyMgM1wuIEZldGNoIERhdGENCg0KRmV0Y2ggZGF0YSB3aXRoIGBjYV9nZXR2YWxzX3RibCgpYDoNCg0KYGBge3IgY2FwMl9mZXRjaCwgY2FjaGUgPSBUUlVFfQ0KY2FwMl90YmwgPC0gY2FwMiAlPiUgDQogIGNhX2dldHZhbHNfdGJsKHF1aWV0ID0gVFJVRSkgDQoNCmNhcDJfdGJsDQpgYGANCg0KIyMgNFwuIE11bmdlIFJlc3VsdHMNCg0KVG8gY29tcHV0ZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFJDUCA4LjUgYW5kIFJDUCA0LjUsIHdlIG5lZWQgdG8gc3BsaXQgdGhlbSBpbnRvIHNlcGFyYXRlIGNvbHVtbnMuIFRoaXMgaXMgYW4gZXhhbXBsZSBvZiBnb2luZyBmcm9tIGEgJ2xvbmcnIGZvcm1hdCB0byBhICd3aWRlJyBmb3JtYXQuIEZvcnR1bmF0ZWx5LCB0aGUgdGlkeXIgcGFja2FnZSBoYXMgYSBmdW5jdGlvbiBjYWxsZWQgYHBpdm90X3dpZGVyYCB0aGF0IGNhbiBkbyB0aGlzIGluIHNpbmdsZSBjb21tYW5kLiBXaGlsZSB3ZSdyZSBhdCBpdCB3ZSdsbCBjb252ZXJ0IHRoZSB0ZW1wIHRvIGRlZ3JlZXMgRmFocmVuaGVpdDoNCg0KYGBge3IgY2FwMl93aWRlciwgY2FjaGUgPSBUUlVFfQ0KcmNwODVfbWludXNfcmNwNDVfdGJsIDwtIGNhcDJfdGJsICU+JSANCiAgbXV0YXRlKHRlbXBfZiA9IHNldF91bml0cyh2YWwsIGRlZ0YpKSAlPiUgDQogIHNlbGVjdChmaXBzLCBnY20sIHNjZW5hcmlvLCBkdCwgdGVtcF9mKSAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzY2VuYXJpbywgdmFsdWVzX2Zyb20gPSB0ZW1wX2YpDQoNCmhlYWQocmNwODVfbWludXNfcmNwNDVfdGJsKQ0KYGBgDQoNCiMjIDVcLiBQbG90IA0KDQpOb3cgd2UncmUgcmVhZHkgdG8gbWFrZSB0aGUgcGxvdDoNCg0KYGBge3IgY2FwMl9nZ3Bsb3QsIGNhY2hlID0gVFJVRX0NCmdncGxvdChkYXRhID0gcmNwODVfbWludXNfcmNwNDVfdGJsLCBhZXMoeCA9IGFzLkRhdGUoZHQpLCB5ID0gYXMubnVtZXJpYyhyY3A4NSAtIHJjcDQ1KSwgZ3JvdXAgPSBnY20pKSArDQogIGdlb21fbGluZShhZXMoY29sb3I9Z2NtKSkgKw0KICBsYWJzKHRpdGxlID0gIkRpZmZlcmVuY2UgYmV0d2VlbiBSQ1A4LjUgYW5kIFJDUDQuNSBpbiB0aGUgTWF4aW11bSBBbm51YWwgTWluaW11bSBcblRlbXBlcmF0dXJlIGZvciBLaW5ncyBDb3VudHkiLCB4ID0gInllYXIiLCB5ID0gInRlbXAgKEYpIikNCmBgYA0KDQojIEV4YW1wbGUgIzM6IEFuYWx5emUgRXh0cmVtZSBIZWF0IGluIGEgQ2Vuc3VzIFRyYWN0DQoNCipHb2FsKjogQ29tcGFyZSBleHRyZW1lIGhlYXQgb2NjdXJyZW5jZSB1bmRlciBSQ1AgNC41IGFuZCBSQ1AgOC41IGZvciBhIHNpbmdsZSBjZW5zdXMgdHJhY3Qgb3ZlciBhIDIwLXllYXIgdGltZSBzcGFuLiBXZSdsbCBtZWFzdXJlICdleHRyZW1lIGhlYXQnIGluIHR3byB3YXlzLCBpKSB0aGUgcHJvcG9ydGlvbiBvZiB0b3RhbCBkYXlzIHdoZW4gdGhlIG1heCB0ZW1wIGV4Y2VlZHMgYSBjZXJ0YWluIHRocmVzaG9sZCwgYW5kIGlpKSB0aGUgbnVtYmVyIG9mICdoZWF0IHNwZWxscycgZGVmaW5lZCBhcyBhIHBlcmlvZCBvZiAqbiogb3IgbW9yZSBjb25zZWN1dGl2ZSBkYXlzIHdoZW4gdGhlIHRlbXBlcmF0dXJlIGV4Y2VlZHMgYSB0aHJlc2hvbGQuDQoNCiMjIDFcLiBGaW5kIHRoZSBHRU9JRCBvZiBhIENlbnN1cyBUcmFjdCBpbiBLaW5ncyBDb3VudHkNCg0KQ2Vuc3VzIHRyYWN0cyBoYXZlIDEwLWRpZ2l0IEdFT0lEIG51bWJlcnMgcmF0aGVyIHRoYW4gNi1kaWdpdCBGSVBTIGNvZGVzIChbbW9yZSBpbmZvXShodHRwczovL3d3dy5jZW5zdXMuZ292L3Byb2dyYW1zLXN1cnZleXMvZ2VvZ3JhcGh5L2d1aWRhbmNlL2dlby1pZGVudGlmaWVycy5odG1sKXt0YXJnZXQ9Il9ibGFuayIgcmVsPSJub29wZW5lciJ9KS4gVG8gZmluZCB0aGUgR0VPSUQgZm9yIGEgY2Vuc3VzIHRyYWN0IGluIEtpbmdzIENvdW50eSwgd2UgY291bGQ6DQoNCjEpIEFzayBHb29nbGUuICANCjIpIFBsb3QgYWxsIHRoZSBjZW5zdXMgdHJhY3RzIChhcyB3ZSBkaWQgYWJvdmUgd2l0aCBjb3VudGllcyksIGFuZCBjbGljayBvbiB0aGUgb25lIHdlJ3JlIGludGVyZXN0ZWQgaW4uICANCjMpIExvb2sgaXQgdXAgdXNpbmcgdGhlIGB0aWR5Y2Vuc3VzYCBwYWNrYWdlLiAgDQo0KSBUYWtlIGFkdmFudGFnZSBvZiB0aGUgZmFjdCB0aGF0IHRoZSBHRU9JRCBvZiBjZW5zdXMgdHJhY3RzIGluIEtpbmdzIENvdW50eSBhbGwgc3RhcnQgd2l0aCBpdHMgRklQUyBjb2RlICg2MDMxKS4gIA0KDQpXZSdsbCB1c2UgbWV0aG9kICM0LCBhbmQgZ3JhYiB0aGUgY2Vuc3VzIHRyYWN0cyBHRU9JRHMgZnJvbSBgYW9pcHJlc2V0X2lkdmFsYDoNCg0KYGBge3J9DQp0cmFjdHNfa2luZ3MgPC0gZ3JlcChwYXR0ZXJuID0gIl42MDMxIiwgYW9pcHJlc2V0X2lkdmFsJGNlbnN1c3RyYWN0cyR0cmFjdCwgdmFsdWUgPSBUUlVFKQ0KdHJhY3RzX2tpbmdzDQpgYGANCg0KVG8gcGxvdCBqdXN0IHRoZXNlIHRyYWN0cyB3ZSBjYW4gZmVlZCB0aGVzZSBHRU9JRCB2YWx1ZXMgaW50byBgZmlsdGVyKClgOg0KDQpgYGB7ciBwbG90X2tpbmdzLCBtZXNzYWdlID0gRkFMU0V9DQpjZW5zdXNfdHJhY3RzX3NmIDwtIGNhX2FvaXByZXNldF9nZW9tKCJjZW5zdXN0cmFjdHMiKQ0KdG1hcF9tb2RlKCJ2aWV3IikNCnRtX3NoYXBlKGNlbnN1c190cmFjdHNfc2YgJT4lIGZpbHRlcih0cmFjdCAlaW4lIHRyYWN0c19raW5ncykpICsgdG1fcG9seWdvbnMoKQ0KYGBgDQoNCkZvciB0aGUgcmVzdCBvZiB0aGlzIGV4ZXJjaXNlIHdlJ2xsIHVzZSAqKjYwMzEwMDE3MDEqKi4NCg0KIyMgMlwuIENvbnN0cnVjdCB0aGUgQVBJIFJlcXVlc3QNCg0KVGhlIGZvbGxvd2luZyB3aWxsIGdpdmUgdXMgMjAgeWVhcnMgb2YgZGFpbHkgbWF4aW11bSB0ZW1wZXJhdHVyZSBmb3Igb3VyIGNlbnN1cyB0cmFjdC4gVG8ga2VlcCBpdCBzaW1wbGUgd2UnbGwganVzdCB1c2Ugb25lIEdDTS4NCg0KYGBge3IgY2FwM19kZWZpbmUsIGNhY2hlID0gVFJVRX0NCmNhcDMgPC0gY2FfbG9jX2FvaXByZXNldCh0eXBlPSJjZW5zdXN0cmFjdHMiLCBpZGZsZCA9ICJ0cmFjdCIsIGlkdmFsID0gNjAzMTAwMTcwMSkgJT4lDQogIGNhX2djbSgiTUlST0M1IikgJT4lDQogIGNhX3NjZW5hcmlvKGMoInJjcDQ1IiwgInJjcDg1IikpICU+JQ0KICBjYV9wZXJpb2QoImRheSIpICU+JQ0KICBjYV95ZWFycyhzdGFydCA9ICIyMDcwLTAxLTAxIiwgZW5kID0gIjIwODktMTItMzEiKSAlPiUNCiAgY2FfY3ZhcigidGFzbWF4IikgJT4lIA0KICBjYV9vcHRpb25zKHNwYXRpYWxfYWcgPSAibWF4IikNCg0KY2FwMw0KYGBgDQoNCmBgYHtyfQ0KcGxvdChjYXAzKQ0KYGBgDQoNCiMjIDNcLiBGZXRjaCBEYXRhDQoNCk5vdyB3ZSdyZSByZWFkeSB0byBmZXRjaCBkYXRhOg0KDQpgYGB7ciBjYXAzX2ZldGNoLCBjYWNoZSA9IFRSVUV9DQpjYXAzX3RibCA8LSBjYXAzICU+JSANCiAgY2FfZ2V0dmFsc190YmwocXVpZXQgPSBUUlVFKQ0KDQpkaW0oY2FwM190YmwpDQpoZWFkKGNhcDNfdGJsKQ0KYGBgDQoNCiMjIDRcLiBNdW5nZSB0aGUgUmVzdWx0cw0KDQpUaGUgb25seSAnbXVuZ2luZycgd2UgbmVlZCB0byBkbyBpcyB0byBhZGQgYSBjb2x1bW4gZm9yIHRoZSB0ZW1wZXJhdHVyZSBpbiBGYXJlbmhlaXQuDQoNCmBgYHtyfQ0KY2FwM19kZWdmX3RibCA8LSBjYXAzX3RibCAlPiUgbXV0YXRlKHRlbXBfZiA9IHNldF91bml0cyh2YWwsIGRlZ0YpKQ0KDQpoZWFkKGNhcDNfZGVnZl90YmwpDQpgYGANCg0KIyMgNVwuIENvbXBhcmUgdGhlIERpc3RyaWJ1dGlvbiBvZiBNYXhpbXVtIERhaWx5IFRlbXANCg0KUGxvdCBhIGhpc3RvZ3JhbSBvZiBtYXhpbXVtIHRlbXBlcmF0dXJlIG92ZXIgdGhpcyAyMC15ZWFyIHBlcmlvZCBmb3IgZWFjaCBlbWlzc2lvbiBzY2VuYXJpbzoNCg0KYGBge3IgY2FwM192YWxzX2hpc3QxLCBjYWNoZT1UUlVFfQ0KaGlzdChjYXAzX2RlZ2ZfdGJsICU+JSAgZmlsdGVyKHNjZW5hcmlvID09ICJyY3A0NSIpICU+JSBwdWxsKHRlbXBfZiksDQogICAgIG1haW4gPSAiUkNQIDQuNTogTWF4aW11bSBEYWlseSBUZW1wLCAyMDcwLTIwODkiLA0KICAgICB4bGFiID0gInRlbXAgKEYpIikgDQoNCmhpc3QoY2FwM19kZWdmX3RibCAlPiUgIGZpbHRlcihzY2VuYXJpbyA9PSAicmNwODUiKSAlPiUgcHVsbCh0ZW1wX2YpLA0KICAgICBtYWluID0gIlJDUCA4LjU6IE1heGltdW0gRGFpbHkgVGVtcCwgMjA3MC0yMDg5IiwNCiAgICAgeGxhYiA9ICJ0ZW1wIChGKSIpIA0KDQpgYGANCg0KIyMgNlwuIENvdW50IHRoZSBOdW1iZXIgb2YgRGF5cyBvdmVyIDExMCYjMTc2O0YgUHJvamVjdGVkIFVuZGVyIGVhY2ggUkNQDQoNCmBgYHtyfQ0KY2FwM19kZWdmX3RibCAlPiUgDQogIG11dGF0ZShyZWFsbHlfaG90X3RmID0gdGVtcF9mID49IHNldF91bml0cygxMTAsIGRlZ0YpKSAlPiUNCiAgZ3JvdXBfYnkoc2NlbmFyaW8sIHJlYWxseV9ob3RfdGYpICU+JSANCiAgY291bnQoKSAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzY2VuYXJpbywgdmFsdWVzX2Zyb20gPSBuKQ0KYGBgDQoNCiMjIDdcLiBDb3VudCB0aGUgTnVtYmVyIG9mIEhlYXQgU3BlbGxzIFByb2plY3RlZCBVbmRlciBlYWNoIFJDUA0KDQpBIHNpbXBsZSBkZWZpbml0aW9uIG9mICdoZWF0IHNwZWxsJyBpcyAqY29uc2VjdXRpdmUgaG90IGRheXMqLiBMZXQncyBjb3VudCBDb3VudCB0aGUgbnVtYmVyIG9mIGhlYXQgc3BlbGxzIGNvbnNpc3Rpbmcgb2YgKio0IG9yIG1vcmUgY29uc2VjdXRpdmUgZGF5cyoqIG9mID4xMTAmIzE3NjtGLg0KDQpUaGUgZmlyc3Qgc3RlcCBpcyB0byBwdWxsIG91dCB0aGUgdmFsdWVzIGZvciBqdXN0IG9uZSBSQ1AsIHNvcnQgdGhlbSBieSBkYXRlLCBhZGQgYSBsb2dpY2FsIGNvbHVtbiBpZiB0aGUgbWF4aW11bSB0ZW1wIHRoYXQgZGF5IGV4Y2VlZGVkIG91ciB0cmVzaGhvbGQuIFdlJ2xsIHRoZW4gZmVlZCB0aG9zZSB2YWx1ZXMgaW50byBgcmxlKClgLCB3aGljaCBicmVha3MgdXAgdGhlIHRpbWUgc2VyaWVzIGludG8gJ3J1bnMnIG9mIFRSVUUgYW5kIEZBTFNFLg0KDQpgYGB7ciByY3A0NV9ydW5zLCBjYWNoZSA9IFRSVUV9DQojIyBVc2UgcmxlKCkgdG8gY2hvcCB1cCBhIHRpbWUgc2VyaWVzIG9mIFRSVUUgb2YgRkFMU0UgaW50byAncnVucycNCnJjcDQ1X2hlYXRfcnVucyA8LSBjYXAzX2RlZ2ZfdGJsICU+JSANCiAgZmlsdGVyKHNjZW5hcmlvID09ICJyY3A0NSIpICU+JSANCiAgYXJyYW5nZShkdCkgJT4lIA0KICBtdXRhdGUocmVhbGx5X2hvdF90ZiA9IHRlbXBfZiA+PSBzZXRfdW5pdHMoMTEwLCBkZWdGKSkgJT4lIA0KICBwdWxsKHJlYWxseV9ob3RfdGYpICU+JSANCiAgcmxlKCkNCg0KcmNwNDVfaGVhdF9ydW5zDQpgYGANCg0KTmV4dCwgd2UgZmluZCB0aGUgcnVucyB3aGVyZSB0d28gY29uZGl0aW9ucyBhcmUgbWV0OiBydW4gbGVuZ3RoIGlzID49IDQgZGF5cywgYW5kIHRoZSBydW4gdmFsdWUgPSBUUlVFIChtZWFuaW5nIGl0cyBhIHJ1biBvZiByZWFsbHkgaG90IGRheXMpLg0KDQpgYGB7cn0NCnN1bShyY3A0NV9oZWF0X3J1bnMkdmFsdWVzICYgcmNwNDVfaGVhdF9ydW5zJGxlbmd0aHMgPj0gNCkNCmBgYA0KDQpXZSBjYW4gZG8gdGhlIHNhbWUgZm9yIFJDUCA4LjU6DQoNCmBgYHtyIHJjcDg1X3J1bnMsIGNhY2hlID0gVFJVRX0NCnJjcDg1X2hlYXRfcnVucyA8LSBjYXAzX2RlZ2ZfdGJsICU+JSANCiAgZmlsdGVyKHNjZW5hcmlvID09ICJyY3A4NSIpICU+JSANCiAgYXJyYW5nZShkdCkgJT4lIA0KICBtdXRhdGUocmVhbGx5X2hvdF90ZiA9IHRlbXBfZiA+PSBzZXRfdW5pdHMoMTEwLCBkZWdGKSkgJT4lIA0KICBwdWxsKHJlYWxseV9ob3RfdGYpICU+JSANCiAgcmxlKCkNCg0Kc3VtKHJjcDg1X2hlYXRfcnVucyR2YWx1ZXMgJiByY3A4NV9oZWF0X3J1bnMkbGVuZ3RocyA+PSA0KQ0KYGBgDQoNCiMjIENvbmNsdXNpb24NCg0KVW5kZXIgUkNQIDQuNSwgdGhlIE1JUk9DNSBtb2RlbCBwcmVkaWN0cyB0aGlzIGNlbnN1cyB0cmFjdCB3aWxsIGV4cGVyaWVuY2UgKiozNiBleHRyZW1lIGhlYXQgc3BlbGxzKiogKGRlZmluZWQgYXMgNCBvciBtb3JlIGRheXMgd2hlcmUgdGhlIHRlbXBlcmF0dXJlIGhpdCAxMTAmIzE3NjtGKSBkdXJpbmcgdGhpcyAyMCB5ZWFyIHBlcmlvZC4gSW4gY29tcGFyaXNvbiwgTUlST0M1IHByZWRpY3RzICoqNDUgZXh0cmVtZSBoZWF0IHNwZWxscyoqIHVuZGVyIFJDUCA4LjUuDQoNCg0KIyMgWU9VUiBUVVJODQoNCkhvdyBtYW55IGhlYXQgc3BlbGxzIHdpbGwgdGhlcmUgYmUgdW5kZXIgZWFjaCBSQ1AgaWYgd2UgZGVmaW5lIGEgaGVhdCBzcGVsbCB0byBiZSBhdCBsZWFzdCAqKjcgZGF5cyoqIG92ZXIgKioxMDAgJiMxNzY7RioqPw0KDQpgYGB7cn0NCiMjIFlvdXIgYW5zd2VyIGdvZXMgaGVyZToNCg0KYGBgDQoNClBsb3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgbnVtYmVyIG9mIGNvbnNlY3V0aXZlIGRheXMgb3ZlciAxMDAgJiMxNzY7RiAoaS5lLiwgaGVhdCBzcGVsbCBkdXJhdGlvbikuDQoNCmBgYHtyfQ0KIyMgWW91ciBhbnN3ZXIgZ29lcyBoZXJlOg0KDQpgYGANCg0KDQo=