Overview

This Notebook will demonstrate how you can use caladaptR to:


Setup

The first thing we do is to load caladaptR and the other package we’re going to need. (If you haven’t installed these yet, see this setup script).

library(caladaptr)
library(units)
library(ggplot2)
library(dplyr)
library(sf)
library(tidyr)
library(conflicted)
conflict_prefer("filter", "dplyr", quiet = TRUE)
conflict_prefer("count", "dplyr", quiet = TRUE)
conflict_prefer("select", "dplyr", quiet = TRUE)

Part I. Retrieve County Level Data

In this section, we’ll fetch and retrieve average daily minimum temperature by year for a single county, and create a time-series plot showing the difference between RCP85 and RCP45:

Preset Areas-of-Interest

For this exercise, we need to use a Preset Area-of-Interest.

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"            


Pro Tip:

  • all of the Preset AOIs can be imported into R as sf objects with ca_aoipreset_geom().


1. Find the FIPS code for Kings County

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 View the attribute table of the counties preset layer:

# ca_aoipreset_geom("counties") %>% View()

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.

cnty_cap <- 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")

cnty_cap
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
 
cnty_cap %>% ca_preflight()
General issues
 - none found
Issues for querying values
 - none found
Issues for downloading rasters
 - none found


Pro Tip:

  • An API request can have more than one location. If using AOI Presets, pass multiple values to idval, or omit idval completely and all features in the layer will be queried.


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

plot(cnty_cap, locagrid = TRUE)


3. Fetch Data

Fetch data with ca_getvals_tbl():

cnty_tbl <- cnty_cap %>% ca_getvals_tbl(quiet = TRUE)

cnty_tbl %>% head()


4. Wrangle Results for Plotting

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:

cnty_diff_rcp85_45_tbl <- cnty_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(cnty_diff_rcp85_45_tbl)


5. Plot

Now we’re ready to make the plot. Since we’re mainly interested in the trend, we’ll add a smoothing line using geom_smooth():

ggplot(data = cnty_diff_rcp85_45_tbl, aes(x = as.Date(dt), y = as.numeric(rcp85 - rcp45))) +
  geom_line(aes(color=gcm)) +
  geom_smooth(method=lm, formula = y ~ x) +
  labs(title = "Difference between RCP8.5 and RCP4.5 in the Average Daily \nMinimum Temperature for Kings County", x = "year", y = "temp (F)")

This plot shows that as time goes on, the difference between RCP4.5 and RCP8.5 gets bigger and bigger.


Part II. Finding Data

Half the battle of working with climate data is finding the name of the dataset you’re interested in.

Browsing the Cal-Adapt Data Catalog

caladaptR comes with a copy of the Cal-Adapt raster series data catalog, which you can access with ca_catalog_rs():

# ca_catalog_rs() %>% View()


Challenge 1

How many raster datasets are there in the Cal-Adapt catalog? [Answer].

## Your answer here


Pro Tip:

  • you can download a fresh copy of the Cal-Adapt raster series catalog with ca_catalog_fetch()


Searching for a Dataset

One way you can search for datasets is to use the filter boxes above each column in the RStudio View pane. For example search for layers whose name contains the word ‘snow’.

caladaptR also has a search function ca_catalog_search(). You can this function to find datasets using a key word or phrase. You can also use this function to see the details for a specific slug, example:

ca_catalog_search("swe_day_ens32avg_rcp45")

swe_day_ens32avg_rcp45
  name: LOCA VIC daily snow water equivalent for RCP 4.5, derived from 32 LOCA models ensemble average
  url: https://api.cal-adapt.org/api/series/swe_day_ens32avg_rcp45/
  tres: daily
  begin: 2006-01-01T00:00:00Z
  end: 2098-12-31T00:00:00Z
  units: mm
  num_rast: 1
  id: 638
  xmin: -124.5625
  xmax: -113.375
  ymin: 31.5625
  ymax: 43.75


Challenge 2

How many datasets are from gridMET? [Answer].

## Your answer here


Specifying Datasets

When you construct an API Request object, you can mix-and-match functions to specify the dataset you want:

LOCA downscaled modeled climate variables (including all Scripps) and their derivatives (i.e., VIC) can be specified using the constructor functions: ca_gcm() + ca_scenario() + ca_cvar() + ca_period().

Livneh data (observed historical variables based on spatially interpolated measurements) can be specified with ca_livneh() + ca_cvar() + ca_period().

everything else can be specified by slug, using ca_slug().


Challenge 3

What is the raster dataset with the slug tmmn_day_gridmet? For which years is it available? What are the units? [Hint]. [Answer].

## Your answer here


Explore the Climate Future of Lindcove Research and Extension Center

In this example, we’ll explore daily projected climate data for a point location. We select the UC Lindcove Research and Extension Center (LREC), a field station in the San Joaquin Valley which has been a leading center for citrus research for decades. The trees are getting old and need to be replaced soon so its worth asking - is citrus production still going to viable in this part of California at the end of the century?


Create the API Request

Let’s start by getting 20 years worth of the daily maximum temperature for the 4 priority GCMs and 2 RCPs.

lrec_tasmax_prj_cap <- ca_loc_pt(coords = c(-119.060, 36.359), id = 1) %>% 
  ca_period("day") %>% 
  ca_gcm(gcms[1:4]) %>% 
  ca_scenario(c("rcp45", "rcp85")) %>% 
  ca_cvar("tasmax") %>% 
  ca_years(start = 2080, end = 2099)
  
lrec_tasmax_prj_cap %>% ca_preflight()
General issues
 - none found
Issues for querying values
 - none found
Issues for downloading rasters
 - none found
plot(lrec_tasmax_prj_cap)


Fetch Data

Now we’re ready to fetch data:

lrec_tasmax_prj_tbl <- lrec_tasmax_prj_cap %>% ca_getvals_tbl(quiet = TRUE)

## backup: lrec_tasmax_prj_tbl <- readRDS("data/lrec_tasmax_prj_tbl.rds")

dim(lrec_tasmax_prj_tbl)
[1] 58440     8
head(lrec_tasmax_prj_tbl)


Create a Box Plot of the Maximum Daily Temperature Values by Month

To make a box plot, we need to first add columns for Fahrenheit, month, and year:

library(lubridate)
lrec_tasmax_prj_tmpf_tbl <- lrec_tasmax_prj_tbl %>% 
  mutate(temp_f = set_units(val, degF), month = month(dt), year = year(dt))
head(lrec_tasmax_prj_tmpf_tbl)

For each month, let make a box plot of the temperature values for each emission scenario, treating all GCMs as equally likely:

ggplot(lrec_tasmax_prj_tmpf_tbl, aes(x = as.factor(month), y = as.numeric(temp_f))) + 
  geom_boxplot() +
  facet_grid(scenario ~ .) +
  labs(title = "Maximum Daily Temperature by Month", x = "month", y = "temp (F)",
       subtitle = "Lindcove REC, 4 GCMs combined, 2080-2099")


Count Extreme Heat Days

An extreme heat day is generally identified when the maximum temperature exceeds a threshold. The threshold can be chosen based on the historical range, or a biophysical process. For this example, we’ll select 105 °F.

Let’s count the total number of days the temperature exceeded 105 °F for each RCP. We start by adding a logical column whether the temperature exceeded our threshold.

lrec_hotday_tbl <- lrec_tasmax_prj_tmpf_tbl %>% 
  mutate(really_hot = (temp_f >= set_units(105, degF))) %>% 
  select(-spag, -val, -month)

head(lrec_hotday_tbl)

We can count the number of extreme heat days with a simple expression:

num_hot_days <- lrec_hotday_tbl %>%
  group_by(scenario, really_hot) %>% 
  count()

num_hot_days

We can improve the readability of this table by making each scenario a separate column. This is an example of pivoting, which you can handle using tidyr::pivot_wider.

num_hot_days %>% pivot_wider(names_from = scenario, values_from = n)


Challenge 4

Count the number of extreme heat days using a threshold of 110 Fahrenheit. Compute the number of extreme heat days per month by scenario. [Answer].

## Your answer here


Pro Tip

  • To see how to count the number consecutive heat days (i.e., heat spells), see this notebook.


Visualize the Distribution of Historical Observed Daily Precipitation by Decade

Our goal here is to look at the distribution of daily precipitation by making a histogram for each decade. We’ll use observed rainfall data from Livneh.

lrec_pr_livn_cap <- ca_loc_pt(coords = c(-119.060, 36.359), id = 1) %>% 
  ca_livneh(TRUE) %>% 
  ca_period("day") %>% 
  ca_cvar("pr") %>% 
  ca_years(start = 1950, end = 2009)

lrec_pr_livn_cap %>% ca_preflight()
General issues
 - none found
Issues for querying values
 - none found
Issues for downloading rasters
 - none found
lrec_pr_livn_tbl <- lrec_pr_livn_cap %>% 
  ca_getvals_tbl() %>% 
  rename(pr_mmday = val)  

## backup: lrec_pr_prj_tbl <- readRDS("./data/lrec_pr_prj_tbl.rds")

dim(lrec_pr_livn_tbl)
[1] 21915     7
head(lrec_pr_livn_tbl)

Finally, we’ll plot histograms of the precipitation values, logged because of the highly skewed distribution.

library(lubridate)

lrec_prdec_livn_tbl <- lrec_pr_livn_tbl %>% 
  mutate(pr_mmday_num = as.numeric(pr_mmday),
         decade = floor(year(dt) / 10) * 10) %>% 
  select(decade, pr_mmday_num)

ggplot(lrec_prdec_livn_tbl, aes(x=log(pr_mmday_num))) + 
  geom_histogram() +
  facet_wrap( ~ decade)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Warning: Removed 17073 rows containing non-finite values (stat_bin).

Conclusion

Once you get climate data in R as data frames, there’s a lot you can do with it!


LS0tDQp0aXRsZTogIkdldHRpbmcgU3RhcnRlZCB3aXRoIGNhbGFkYXB0UiINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogDQogICAgY3NzOiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci1yZXMvYXNzZXRzL25iX2NzczAxLmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgYWZ0ZXJfYm9keTogaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHItcmVzL2Fzc2V0cy9uYl9mb290ZXIwMS5odG1sDQotLS0NCg0KIyBPdmVydmlldw0KDQpUaGlzIE5vdGVib29rIHdpbGwgZGVtb25zdHJhdGUgaG93IHlvdSBjYW4gdXNlIGNhbGFkYXB0UiB0bzoNCg0KLSBjcmVhdGUgYW4gQVBJIFJlcXVlc3QgdXNpbmcgYSBwcmVzZXQgYXJlYS1vZi1pbnRlcmVzdCAoY291bnR5KQ0KLSB1c2UgcGl2b3Rfd2lkZXIgdG8gZ28gZnJvbSBhIGxvbmcgdG8gd2lkZSBmb3JtYXQgIA0KLSBhZGQgYSB0cmVuZCBsaW5lIHRvIGEgcGxvdCAgDQotIGV4cGxvcmUgdGhlIENhbC1BZGFwdCBkYXRhIGNhdGFsb2cgIA0KLSBzcGVjaWZ5IExpdm5laCBkYXRhc2V0cyAgDQotIHdyYW5nbGUgcmVzdWx0cyBmb3IgZGlmZmVyZW50IHN1bW1hcmllcyBhbmQgdmlzdWFsaXphdGlvbnMgIA0KDQpcDQoNCiMgU2V0dXANCg0KVGhlIGZpcnN0IHRoaW5nIHdlIGRvIGlzIHRvIGxvYWQgY2FsYWRhcHRSIGFuZCB0aGUgb3RoZXIgcGFja2FnZSB3ZSdyZSBnb2luZyB0byBuZWVkLiAoSWYgeW91IGhhdmVuJ3QgaW5zdGFsbGVkIHRoZXNlIHlldCwgc2VlIHRoaXMgW3NldHVwIHNjcmlwdF0oaHR0cHM6Ly9naXRodWIuY29tL3VjYW5yLWlnaXMvY2FsYWRhcHRyLXJlcy9ibG9iL21haW4vZG9jcy93b3Jrc2hvcHMvY2FfaW50cm9fb2N0MjEvc2NyaXB0cy9jYWxhZGFwdHJfc2V0dXAuUikpLiANCg0KYGBge3IgY2h1bmswMSwgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdob2xkJ30NCmxpYnJhcnkoY2FsYWRhcHRyKQ0KbGlicmFyeSh1bml0cykNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoY29uZmxpY3RlZCkNCmNvbmZsaWN0X3ByZWZlcigiZmlsdGVyIiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJjb3VudCIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmNvbmZsaWN0X3ByZWZlcigic2VsZWN0IiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KYGBgDQoNCiMgUGFydCBJLiBSZXRyaWV2ZSBDb3VudHkgTGV2ZWwgRGF0YSANCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSdsbCBmZXRjaCBhbmQgcmV0cmlldmUgYXZlcmFnZSBkYWlseSBtaW5pbXVtIHRlbXBlcmF0dXJlIGJ5IHllYXIgZm9yIGEgc2luZ2xlIGNvdW50eSwgYW5kIGNyZWF0ZSBhIHRpbWUtc2VyaWVzIHBsb3Qgc2hvd2luZyB0aGUgKmRpZmZlcmVuY2UqIGJldHdlZW4gUkNQODUgYW5kIFJDUDQ1Og0KDQohW10oaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHItcmVzL2ltYWdlcy9jbnR5X3Rhc21pbl9kaWZmXzQwMHgyNjl4MjU2LnBuZyl7Y2xhc3M9J2NlbnRlcmVkJ30NCg0KIyMgUHJlc2V0IEFyZWFzLW9mLUludGVyZXN0DQoNCkZvciB0aGlzIGV4ZXJjaXNlLCB3ZSBuZWVkIHRvIHVzZSBhICoqUHJlc2V0IEFyZWEtb2YtSW50ZXJlc3QqKi4gDQoNClRoZSBDYWwtQWRhcHQgQVBJIGhhcyBhIG51bWJlciBvZiAncHJlc2V0JyBhcmVhcy1vZi1pbnRlcmVzdCAoYWxzbyBjYWxsZWQgYm91bmRhcnkgbGF5ZXJzKSB0aGF0IHlvdSBjYW4gdXNlIGNvb2tpZS1jdXR0ZXIgc3R5bGUgd2hlbiByZXRyaWV2aW5nIGNsaW1hdGUgZGF0YS4gVGhlIGFkdmFudGFnZSBvZiB1c2luZyBhbiBBT0kgUHJlc2V0IGlzIHRoYXQgeW91IGRvbid0IG5lZWQgdG8gaW1wb3J0IGEgR0lTIGxheWVyIHRvIHF1ZXJ5IGFjY29yZGluZyB0byB0aGVzZSBjb21tb24gZmVhdHVyZXMuIFlvdSBqdXN0IG5lZWQgdG8ga25vdyB0aGUgbmFtZSBvciBJRCBudW1iZXIgb2YgdGhlIGZlYXR1cmUocykgeW91J3JlIGludGVyZXN0ZWQgaW4uDQoNClRoZSBmb2xsb3dpbmcgQU9JIFByZXNldHMgYXJlIGF2YWlsYWJsZToNCg0KYGBge3IgY2h1bmswMiwgY2FjaGUgPSBGQUxTRX0NCmFvaXByZXNldF90eXBlcw0KYGBgDQoNClwNCg0KKipQcm8gVGlwOioqDQoNCiAtIGFsbCBvZiB0aGUgUHJlc2V0IEFPSXMgY2FuIGJlIGltcG9ydGVkIGludG8gUiBhcyBzZiBvYmplY3RzIHdpdGggYGNhX2FvaXByZXNldF9nZW9tKClgLg0KDQpcDQoNCiMjIDFcLiBGaW5kIHRoZSBGSVBTIGNvZGUgZm9yIEtpbmdzIENvdW50eQ0KDQpUbyB1c2UgYW4gQU9JIFByZXNldCwgeW91IG5lZWQgdG8gc3BlY2lmeSB3aGljaCBmZWF0dXJlKHMpIHlvdSdyZSBpbnRlcmVzdGVkIGluIGJ5IHByb3ZpZGluZyB0aGUgdmFsdWUocykgZm9yIG9uZSBvZiB0aGUgaWQgZmllbGRzLiBUaGUgc3BlY2lmaWMgY29sdW1ucyBhdmFpbGFibGUgZm9yIGlkZW50aWZ5aW5nIGZlYXR1cmVzIHZhcnkgYWNjb3JkaW5nIHRvIHByZXNldC4gWW91IGNhbiB2aWV3IHRoZSBpZCBjb2x1bW5zIGFuZCB0aGVpciB2YWx1ZXMgZm9yIGFuIEFPSSBQcmVzZXQgdXNpbmcgdGhlIGJ1aWx0LWluIGBhb2lwcmVzZXRfaWR2YWxgIGNvbnN0YW50LiBGb3IgZXhhbXBsZSB0aGUgY291bnRpZXMgbGF5ZXIgYWxsb3dzIHlvdSB0byBzcGVjaWZ5IGEgY291bnR5IGJ5IG5hbWUsIGZpcHMgY29kZSwgb3IgaWQuIFJlbWVtYmVyIHRoYXQgZXZlcnl0aGluZyBpbiBSIGlzIGNhc2Utc2Vuc2l0aXZlIQ0KDQpgYGB7ciBjaHVuazAzLCBjYWNoZSA9IFRSVUV9DQphb2lwcmVzZXRfaWR2YWwkY291bnRpZXMNCmBgYA0KDQpZb3UgY2FuIGZpbmQgY291bnR5IGZpcHMgY29kZXMgb24gR29vZ2xlIC0ganVzdCBiZSBzdXJlIHRvIHVzZSB0aGUgNi1jaGFyYWN0ZXIgdmVyc2lvbiB3aGljaCBpbmNsdWRlcyB0aGUgc3RhdGUuIEFsdGVybmF0ZWx5LCB5b3UgY2FuIFZpZXcgdGhlIGF0dHJpYnV0ZSB0YWJsZSBvZiB0aGUgY291bnRpZXMgcHJlc2V0IGxheWVyOiANCg0KYGBge3IgY2h1bmswNCwgbWVzc2FnZSA9IEZBTFNFfQ0KIyBjYV9hb2lwcmVzZXRfZ2VvbSgiY291bnRpZXMiKSAlPiUgVmlldygpDQpgYGANCg0KRm9yIHRoaXMgZXhhbXBsZSwgd2UnbGwgbG9vayBhdCAqKktpbmdzIENvdW50eSoqIChGSVBTID0gYDA2MDMxYCkuDQoNClwNCg0KIyMgMlwuIENyZWF0ZSB0aGUgQVBJIFJlcXVlc3QNCg0KTGV0J3MgY3JlYXRlIGFuIEFQSSByZXF1ZXN0IGZvciBLaW5ncyBDb3VudHkuIE5vdGUgYmVsb3cgdGhlIGluY2x1c2lvbiBvZiBgY2Ffb3B0aW9ucygpYCB0byBzcGVjaWZ5IGhvdyB3ZSB3YW50IHRvIGFnZ3JlZ2F0ZSB0aGUgcGl4ZWxzIHRoYXQgZmFsbCB3aXRoaW4gdGhlIGNvdW50cnkuIFRoaXMgaXMgcmVxdWlyZWQgd2hlbmV2ZXIgeW91IHF1ZXJ5IHBvbHlnb24gZmVhdHVyZXMuDQoNCmBgYHtyIGNodW5rMDUsIGNhY2hlID0gVFJVRX0NCmNudHlfY2FwIDwtIGNhX2xvY19hb2lwcmVzZXQodHlwZT0iY291bnRpZXMiLCBpZGZsZCA9ICJmaXBzIiwgaWR2YWwgPSAiMDYwMzEiKSAlPiUNCiAgY2FfZ2NtKGdjbXNbMTo0XSkgJT4lDQogIGNhX3NjZW5hcmlvKGMoInJjcDQ1IiwgInJjcDg1IikpICU+JQ0KICBjYV9wZXJpb2QoInllYXIiKSAlPiUNCiAgY2FfeWVhcnMoc3RhcnQgPSAyMDMwLCBlbmQgPSAyMDgwKSAlPiUNCiAgY2FfY3ZhcihjKCJ0YXNtaW4iKSkgJT4lDQogIGNhX29wdGlvbnMoc3BhdGlhbF9hZyA9ICJtYXgiKQ0KDQpjbnR5X2NhcA0KDQpjbnR5X2NhcCAlPiUgY2FfcHJlZmxpZ2h0KCkNCmBgYA0KXA0KDQoqKlBybyBUaXA6KioNCg0KLSBBbiBBUEkgcmVxdWVzdCBjYW4gaGF2ZSBtb3JlIHRoYW4gb25lIGxvY2F0aW9uLiBJZiB1c2luZyBBT0kgUHJlc2V0cywgcGFzcyBtdWx0aXBsZSB2YWx1ZXMgdG8gYGlkdmFsYCwgb3Igb21pdCBgaWR2YWxgIGNvbXBsZXRlbHkgYW5kIGFsbCBmZWF0dXJlcyBpbiB0aGUgbGF5ZXIgd2lsbCBiZSBxdWVyaWVkLg0KDQpcDQoNCkFzIGJlZm9yZSwgd2UgY2FuIHBsb3QgdGhlIEFQSSByZXF1ZXN0IHRvIGRvdWJsZS1jaGVjayB3ZSBnb3QgdGhlIHJpZ2h0IGxvY2F0aW9uOiANCg0KYGBge3IgY2h1bmswNiwgY2FjaGUgPSBGQUxTRX0NCnBsb3QoY250eV9jYXAsIGxvY2FncmlkID0gVFJVRSkNCmBgYA0KDQpcDQoNCiMjIDNcLiBGZXRjaCBEYXRhDQoNCkZldGNoIGRhdGEgd2l0aCBgY2FfZ2V0dmFsc190YmwoKWA6DQoNCmBgYHtyIGNodW5rMDcsIGNhY2hlID0gVFJVRX0NCmNudHlfdGJsIDwtIGNudHlfY2FwICU+JSBjYV9nZXR2YWxzX3RibChxdWlldCA9IFRSVUUpDQoNCmNudHlfdGJsICU+JSBoZWFkKCkNCmBgYA0KDQpcDQoNCiMjIDRcLiBXcmFuZ2xlIFJlc3VsdHMgZm9yIFBsb3R0aW5nDQoNClRvIGNvbXB1dGUgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBSQ1AgOC41IGFuZCBSQ1AgNC41LCB3ZSBuZWVkIHRvIHNwbGl0IHRoZW0gaW50byBzZXBhcmF0ZSBjb2x1bW5zLiBUaGlzIGlzIGFuIGV4YW1wbGUgb2YgZ29pbmcgZnJvbSBhICdsb25nJyBmb3JtYXQgdG8gYSAnd2lkZScgZm9ybWF0LiBGb3J0dW5hdGVseSwgdGhlIHRpZHlyIHBhY2thZ2UgaGFzIGEgZnVuY3Rpb24gY2FsbGVkIGBwaXZvdF93aWRlcmAgdGhhdCBjYW4gZG8gdGhpcyBpbiBzaW5nbGUgY29tbWFuZC4gV2hpbGUgd2UncmUgYXQgaXQgd2UnbGwgY29udmVydCB0aGUgdGVtcCB0byBkZWdyZWVzIEZhaHJlbmhlaXQ6DQoNCmBgYHtyIGNodW5rMDgsIGNhY2hlID0gVFJVRX0NCmNudHlfZGlmZl9yY3A4NV80NV90YmwgPC0gY250eV90YmwgJT4lIA0KICBtdXRhdGUodGVtcF9mID0gc2V0X3VuaXRzKHZhbCwgZGVnRikpICU+JSANCiAgc2VsZWN0KGZpcHMsIGdjbSwgc2NlbmFyaW8sIGR0LCB0ZW1wX2YpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNjZW5hcmlvLCB2YWx1ZXNfZnJvbSA9IHRlbXBfZikNCg0KaGVhZChjbnR5X2RpZmZfcmNwODVfNDVfdGJsKQ0KYGBgDQoNClwNCg0KIyMgNVwuIFBsb3QgDQoNCk5vdyB3ZSdyZSByZWFkeSB0byBtYWtlIHRoZSBwbG90LiBTaW5jZSB3ZSdyZSBtYWlubHkgaW50ZXJlc3RlZCBpbiB0aGUgdHJlbmQsIHdlJ2xsIGFkZCBhIHNtb290aGluZyBsaW5lIHVzaW5nIGBnZW9tX3Ntb290aCgpYDoNCg0KYGBge3IgY2h1bmswOSwgY2FjaGUgPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBjbnR5X2RpZmZfcmNwODVfNDVfdGJsLCBhZXMoeCA9IGFzLkRhdGUoZHQpLCB5ID0gYXMubnVtZXJpYyhyY3A4NSAtIHJjcDQ1KSkpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1nY20pKSArDQogIGdlb21fc21vb3RoKG1ldGhvZD1sbSwgZm9ybXVsYSA9IHkgfiB4KSArDQogIGxhYnModGl0bGUgPSAiRGlmZmVyZW5jZSBiZXR3ZWVuIFJDUDguNSBhbmQgUkNQNC41IGluIHRoZSBBdmVyYWdlIERhaWx5IFxuTWluaW11bSBUZW1wZXJhdHVyZSBmb3IgS2luZ3MgQ291bnR5IiwgeCA9ICJ5ZWFyIiwgeSA9ICJ0ZW1wIChGKSIpDQpgYGANCg0KVGhpcyBwbG90IHNob3dzIHRoYXQgYXMgdGltZSBnb2VzIG9uLCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFJDUDQuNSBhbmQgUkNQOC41IGdldHMgYmlnZ2VyIGFuZCBiaWdnZXIuDQoNClwNCg0KIyBQYXJ0IElJLiBGaW5kaW5nIERhdGENCg0KSGFsZiB0aGUgYmF0dGxlIG9mIHdvcmtpbmcgd2l0aCBjbGltYXRlIGRhdGEgaXMgZmluZGluZyB0aGUgbmFtZSBvZiB0aGUgZGF0YXNldCB5b3UncmUgaW50ZXJlc3RlZCBpbi4NCg0KIyMgQnJvd3NpbmcgdGhlIENhbC1BZGFwdCBEYXRhIENhdGFsb2cNCg0KY2FsYWRhcHRSIGNvbWVzIHdpdGggYSBjb3B5IG9mIHRoZSBDYWwtQWRhcHQgcmFzdGVyIHNlcmllcyBkYXRhIGNhdGFsb2csIHdoaWNoIHlvdSBjYW4gYWNjZXNzIHdpdGggYGNhX2NhdGFsb2dfcnMoKWA6DQoNCmBgYHtyIGNodW5rMTB9DQojIGNhX2NhdGFsb2dfcnMoKSAlPiUgVmlldygpDQpgYGANCg0KXA0KDQojIyMgQ2hhbGxlbmdlIDENCg0KSG93IG1hbnkgcmFzdGVyIGRhdGFzZXRzIGFyZSB0aGVyZSBpbiB0aGUgQ2FsLUFkYXB0IGNhdGFsb2c/IFtbQW5zd2VyXShodHRwczovL2JpdC5seS8zSnZETGZ5KV0uDQoNCmBgYHtyIGNodW5rMTF9DQojIyBZb3VyIGFuc3dlciBoZXJlDQoNCmBgYA0KDQpcDQoNCioqUHJvIFRpcDoqKg0KDQotIHlvdSBjYW4gZG93bmxvYWQgYSBmcmVzaCBjb3B5IG9mIHRoZSBDYWwtQWRhcHQgcmFzdGVyIHNlcmllcyBjYXRhbG9nIHdpdGggYGNhX2NhdGFsb2dfZmV0Y2goKWAgDQoNClwNCg0KIyMgU2VhcmNoaW5nIGZvciBhIERhdGFzZXQNCg0KT25lIHdheSB5b3UgY2FuIHNlYXJjaCBmb3IgZGF0YXNldHMgaXMgdG8gdXNlIHRoZSBmaWx0ZXIgYm94ZXMgYWJvdmUgZWFjaCBjb2x1bW4gaW4gdGhlIFJTdHVkaW8gVmlldyBwYW5lLiBGb3IgZXhhbXBsZSBzZWFyY2ggZm9yIGxheWVycyB3aG9zZSBuYW1lIGNvbnRhaW5zIHRoZSB3b3JkICdzbm93Jy4NCg0KY2FsYWRhcHRSIGFsc28gaGFzIGEgc2VhcmNoIGZ1bmN0aW9uIGBjYV9jYXRhbG9nX3NlYXJjaCgpYC4gWW91IGNhbiB0aGlzIGZ1bmN0aW9uIHRvIGZpbmQgZGF0YXNldHMgdXNpbmcgYSBrZXkgd29yZCBvciBwaHJhc2UuIFlvdSBjYW4gYWxzbyB1c2UgdGhpcyBmdW5jdGlvbiB0byBzZWUgdGhlIGRldGFpbHMgZm9yIGEgc3BlY2lmaWMgc2x1ZywgZXhhbXBsZToNCg0KYGBge3IgY2h1bmsxMn0NCmNhX2NhdGFsb2dfc2VhcmNoKCJzd2VfZGF5X2VuczMyYXZnX3JjcDQ1IikNCmBgYA0KDQpcDQoNCiMjIyBDaGFsbGVuZ2UgMg0KDQpIb3cgbWFueSBkYXRhc2V0cyBhcmUgZnJvbSBncmlkTUVUPyBbW0Fuc3dlcl0oaHR0cHM6Ly9iaXQubHkvM203ek12QSldLg0KDQpgYGB7ciBjaHVuazEzfQ0KIyMgWW91ciBhbnN3ZXIgaGVyZQ0KDQpgYGANCg0KXA0KDQojIyBTcGVjaWZ5aW5nIERhdGFzZXRzDQoNCldoZW4geW91IGNvbnN0cnVjdCBhbiBBUEkgUmVxdWVzdCBvYmplY3QsIHlvdSBjYW4gbWl4LWFuZC1tYXRjaCBmdW5jdGlvbnMgdG8gc3BlY2lmeSB0aGUgZGF0YXNldCB5b3Ugd2FudDoNCg0KKipMT0NBIGRvd25zY2FsZWQgbW9kZWxlZCBjbGltYXRlIHZhcmlhYmxlcyoqIChpbmNsdWRpbmcgYWxsIFNjcmlwcHMpIGFuZCAqKnRoZWlyIGRlcml2YXRpdmVzKiogKGkuZS4sIFZJQykgY2FuIGJlIHNwZWNpZmllZCB1c2luZyB0aGUgY29uc3RydWN0b3IgZnVuY3Rpb25zOiBgY2FfZ2NtKClgICsgYGNhX3NjZW5hcmlvKClgICsgYGNhX2N2YXIoKWAgKyBgY2FfcGVyaW9kKClgLg0KDQoqKkxpdm5laCBkYXRhKiogKG9ic2VydmVkIGhpc3RvcmljYWwgdmFyaWFibGVzIGJhc2VkIG9uIHNwYXRpYWxseSBpbnRlcnBvbGF0ZWQgbWVhc3VyZW1lbnRzKSBjYW4gYmUgc3BlY2lmaWVkIHdpdGggYGNhX2xpdm5laCgpYCArIGBjYV9jdmFyKClgICsgYGNhX3BlcmlvZCgpYC4NCg0KKipldmVyeXRoaW5nIGVsc2UqKiBjYW4gYmUgc3BlY2lmaWVkIGJ5IHNsdWcsIHVzaW5nIGBjYV9zbHVnKClgLg0KDQpcDQoNCiMjIyBDaGFsbGVuZ2UgMw0KDQpXaGF0IGlzIHRoZSByYXN0ZXIgZGF0YXNldCB3aXRoIHRoZSBzbHVnIGB0bW1uX2RheV9ncmlkbWV0YD8gRm9yIHdoaWNoIHllYXJzIGlzIGl0IGF2YWlsYWJsZT8gV2hhdCBhcmUgdGhlIHVuaXRzPyBbW0hpbnRdKGh0dHBzOi8vYml0Lmx5LzNnUWI2cDApXS4gW1tBbnN3ZXJdKGh0dHBzOi8vYml0Lmx5LzNMSmwwYXkpXS4NCg0KYGBge3IgY2h1bmsxNH0NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCg0KYGBgDQoNClwNCg0KIyBFeHBsb3JlIHRoZSBDbGltYXRlIEZ1dHVyZSBvZiBbTGluZGNvdmUgUmVzZWFyY2ggYW5kIEV4dGVuc2lvbiBDZW50ZXJdKGh0dHA6Ly9scmVjLnVjYW5yLmVkdS8pDQoNCkluIHRoaXMgZXhhbXBsZSwgd2UnbGwgZXhwbG9yZSBkYWlseSBwcm9qZWN0ZWQgY2xpbWF0ZSBkYXRhIGZvciBhIHBvaW50IGxvY2F0aW9uLiBXZSBzZWxlY3QgdGhlIFVDIFsqKkxpbmRjb3ZlIFJlc2VhcmNoIGFuZCBFeHRlbnNpb24gQ2VudGVyKipdKGh0dHA6Ly9scmVjLnVjYW5yLmVkdS8pIChMUkVDKSwgYSBmaWVsZCBzdGF0aW9uIGluIHRoZSBTYW4gSm9hcXVpbiBWYWxsZXkgd2hpY2ggaGFzIGJlZW4gYSBsZWFkaW5nIGNlbnRlciBmb3IgY2l0cnVzIHJlc2VhcmNoIGZvciBkZWNhZGVzLiBUaGUgdHJlZXMgYXJlIGdldHRpbmcgb2xkIGFuZCBuZWVkIHRvIGJlIHJlcGxhY2VkIHNvb24gc28gaXRzIHdvcnRoIGFza2luZyAtIGlzIGNpdHJ1cyBwcm9kdWN0aW9uIHN0aWxsIGdvaW5nIHRvIHZpYWJsZSBpbiB0aGlzIHBhcnQgb2YgQ2FsaWZvcm5pYSBhdCB0aGUgZW5kIG9mIHRoZSBjZW50dXJ5Pw0KDQpcDQoNCiMjIENyZWF0ZSB0aGUgQVBJIFJlcXVlc3QNCg0KTGV0J3Mgc3RhcnQgYnkgZ2V0dGluZyAyMCB5ZWFycyB3b3J0aCBvZiB0aGUgZGFpbHkgbWF4aW11bSB0ZW1wZXJhdHVyZSBmb3IgdGhlIDQgcHJpb3JpdHkgR0NNcyBhbmQgMiBSQ1BzLiANCg0KYGBge3IgIGNodW5rMTV9DQpscmVjX3Rhc21heF9wcmpfY2FwIDwtIGNhX2xvY19wdChjb29yZHMgPSBjKC0xMTkuMDYwLCAzNi4zNTkpLCBpZCA9IDEpICU+JSANCiAgY2FfcGVyaW9kKCJkYXkiKSAlPiUgDQogIGNhX2djbShnY21zWzE6NF0pICU+JSANCiAgY2Ffc2NlbmFyaW8oYygicmNwNDUiLCAicmNwODUiKSkgJT4lIA0KICBjYV9jdmFyKCJ0YXNtYXgiKSAlPiUgDQogIGNhX3llYXJzKHN0YXJ0ID0gMjA4MCwgZW5kID0gMjA5OSkNCiAgDQpscmVjX3Rhc21heF9wcmpfY2FwICU+JSBjYV9wcmVmbGlnaHQoKQ0KYGBgDQoNCmBgYHtyIGNodW5rMTYsIGNhY2hlID0gRkFMU0V9DQpwbG90KGxyZWNfdGFzbWF4X3Byal9jYXApDQpgYGANCg0KXA0KDQojIyBGZXRjaCBEYXRhDQoNCk5vdyB3ZSdyZSByZWFkeSB0byBmZXRjaCBkYXRhOg0KDQpgYGB7ciBjaHVuazE3fQ0KbHJlY190YXNtYXhfcHJqX3RibCA8LSBscmVjX3Rhc21heF9wcmpfY2FwICU+JSBjYV9nZXR2YWxzX3RibChxdWlldCA9IFRSVUUpDQoNCiMjIGJhY2t1cDogbHJlY190YXNtYXhfcHJqX3RibCA8LSByZWFkUkRTKCJkYXRhL2xyZWNfdGFzbWF4X3Byal90YmwucmRzIikNCg0KZGltKGxyZWNfdGFzbWF4X3Byal90YmwpDQpoZWFkKGxyZWNfdGFzbWF4X3Byal90YmwpDQpgYGANCg0KXA0KDQojIyBDcmVhdGUgYSBCb3ggUGxvdCBvZiB0aGUgTWF4aW11bSBEYWlseSBUZW1wZXJhdHVyZSBWYWx1ZXMgYnkgTW9udGgNCg0KVG8gbWFrZSBhIGJveCBwbG90LCB3ZSBuZWVkIHRvIGZpcnN0IGFkZCBjb2x1bW5zIGZvciBGYWhyZW5oZWl0LCBtb250aCwgYW5kIHllYXI6DQoNCmBgYHtyIGNodW5rMTh9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxyZWNfdGFzbWF4X3Byal90bXBmX3RibCA8LSBscmVjX3Rhc21heF9wcmpfdGJsICU+JSANCiAgbXV0YXRlKHRlbXBfZiA9IHNldF91bml0cyh2YWwsIGRlZ0YpLCBtb250aCA9IG1vbnRoKGR0KSwgeWVhciA9IHllYXIoZHQpKQ0KaGVhZChscmVjX3Rhc21heF9wcmpfdG1wZl90YmwpDQpgYGANCg0KRm9yIGVhY2ggbW9udGgsIGxldCBtYWtlIGEgYm94IHBsb3Qgb2YgdGhlIHRlbXBlcmF0dXJlIHZhbHVlcyBmb3IgZWFjaCBlbWlzc2lvbiBzY2VuYXJpbywgdHJlYXRpbmcgYWxsIEdDTXMgYXMgZXF1YWxseSBsaWtlbHk6DQoNCmBgYHtyIGNodW5rMTl9DQpnZ3Bsb3QobHJlY190YXNtYXhfcHJqX3RtcGZfdGJsLCBhZXMoeCA9IGFzLmZhY3Rvcihtb250aCksIHkgPSBhcy5udW1lcmljKHRlbXBfZikpKSArIA0KICBnZW9tX2JveHBsb3QoKSArDQogIGZhY2V0X2dyaWQoc2NlbmFyaW8gfiAuKSArDQogIGxhYnModGl0bGUgPSAiTWF4aW11bSBEYWlseSBUZW1wZXJhdHVyZSBieSBNb250aCIsIHggPSAibW9udGgiLCB5ID0gInRlbXAgKEYpIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJMaW5kY292ZSBSRUMsIDQgR0NNcyBjb21iaW5lZCwgMjA4MC0yMDk5IikNCmBgYA0KDQpcDQoNCiMjIyBDb3VudCBFeHRyZW1lIEhlYXQgRGF5cw0KDQpBbiBleHRyZW1lIGhlYXQgZGF5IGlzIGdlbmVyYWxseSBpZGVudGlmaWVkIHdoZW4gdGhlIG1heGltdW0gdGVtcGVyYXR1cmUgZXhjZWVkcyBhIHRocmVzaG9sZC4gVGhlIHRocmVzaG9sZCBjYW4gYmUgY2hvc2VuIGJhc2VkIG9uIHRoZSBoaXN0b3JpY2FsIHJhbmdlLCBvciBhIGJpb3BoeXNpY2FsIHByb2Nlc3MuIEZvciB0aGlzIGV4YW1wbGUsIHdlJ2xsIHNlbGVjdCAxMDUgJiMxNzY7Ri4NCg0KTGV0J3MgY291bnQgdGhlIHRvdGFsIG51bWJlciBvZiBkYXlzIHRoZSB0ZW1wZXJhdHVyZSBleGNlZWRlZCAxMDUgJiMxNzY7RiBmb3IgZWFjaCBSQ1AuIFdlIHN0YXJ0IGJ5IGFkZGluZyBhIGxvZ2ljYWwgY29sdW1uIHdoZXRoZXIgdGhlIHRlbXBlcmF0dXJlIGV4Y2VlZGVkIG91ciB0aHJlc2hvbGQuDQoNCmBgYHtyICBjaHVuazIwfQ0KbHJlY19ob3RkYXlfdGJsIDwtIGxyZWNfdGFzbWF4X3Byal90bXBmX3RibCAlPiUgDQogIG11dGF0ZShyZWFsbHlfaG90ID0gKHRlbXBfZiA+PSBzZXRfdW5pdHMoMTA1LCBkZWdGKSkpICU+JSANCiAgc2VsZWN0KC1zcGFnLCAtdmFsLCAtbW9udGgpDQoNCmhlYWQobHJlY19ob3RkYXlfdGJsKQ0KYGBgDQoNCldlIGNhbiBjb3VudCB0aGUgbnVtYmVyIG9mIGV4dHJlbWUgaGVhdCBkYXlzIHdpdGggYSBzaW1wbGUgZXhwcmVzc2lvbjoNCg0KYGBge3IgY2h1bmsyMX0NCm51bV9ob3RfZGF5cyA8LSBscmVjX2hvdGRheV90YmwgJT4lDQogIGdyb3VwX2J5KHNjZW5hcmlvLCByZWFsbHlfaG90KSAlPiUgDQogIGNvdW50KCkNCg0KbnVtX2hvdF9kYXlzDQpgYGANCg0KV2UgY2FuIGltcHJvdmUgdGhlIHJlYWRhYmlsaXR5IG9mIHRoaXMgdGFibGUgYnkgbWFraW5nIGVhY2ggc2NlbmFyaW8gYSBzZXBhcmF0ZSBjb2x1bW4uIFRoaXMgaXMgYW4gZXhhbXBsZSBvZiBbcGl2b3RpbmddKGh0dHBzOi8vdGlkeXIudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9waXZvdC5odG1sKSwgd2hpY2ggeW91IGNhbiBoYW5kbGUgdXNpbmcgYHRpZHlyOjpwaXZvdF93aWRlcmAuDQoNCmBgYHtyIGNodW5rMjJ9DQpudW1faG90X2RheXMgJT4lIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzY2VuYXJpbywgdmFsdWVzX2Zyb20gPSBuKQ0KYGBgDQoNClwNCg0KIyMjIENoYWxsZW5nZSA0DQoNCkNvdW50IHRoZSBudW1iZXIgb2YgZXh0cmVtZSBoZWF0IGRheXMgdXNpbmcgYSB0aHJlc2hvbGQgb2YgMTEwIEZhaHJlbmhlaXQuIENvbXB1dGUgdGhlIG51bWJlciBvZiBleHRyZW1lIGhlYXQgZGF5cyBwZXIgbW9udGggYnkgc2NlbmFyaW8uIFtbQW5zd2VyXShodHRwczovL2JpdC5seS8zR1B3Mkh1KV0uDQoNCmBgYHtyIGNodW5rMjN9DQojIyBZb3VyIGFuc3dlciBoZXJlDQoNCg0KYGBgDQoNClwNCg0KKipQcm8gVGlwKioNCg0KIC0gVG8gc2VlIGhvdyB0byBjb3VudCB0aGUgbnVtYmVyIGNvbnNlY3V0aXZlIGhlYXQgZGF5cyAoaS5lLiwgaGVhdCBzcGVsbHMpLCBzZWUgW3RoaXMgbm90ZWJvb2tdKGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vY2FsYWRhcHRyLXJlcy9ub3RlYm9va3MvY2FsYWRhcHRyX2ludHJvLm5iLmh0bWwjZXhhbXBsZS0zLWFuYWx5emUtZXh0cmVtZS1oZWF0LWluLWEtY2Vuc3VzLXRyYWN0KS4gDQogDQpcDQoNCiMjIFZpc3VhbGl6ZSB0aGUgRGlzdHJpYnV0aW9uIG9mIEhpc3RvcmljYWwgT2JzZXJ2ZWQgRGFpbHkgUHJlY2lwaXRhdGlvbiBieSBEZWNhZGUNCg0KT3VyIGdvYWwgaGVyZSBpcyB0byBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgZGFpbHkgcHJlY2lwaXRhdGlvbiBieSBtYWtpbmcgYSBoaXN0b2dyYW0gZm9yIGVhY2ggZGVjYWRlLiBXZSdsbCB1c2Ugb2JzZXJ2ZWQgcmFpbmZhbGwgZGF0YSBmcm9tIExpdm5laC4NCg0KYGBge3IgY2h1bmsyNH0NCmxyZWNfcHJfbGl2bl9jYXAgPC0gY2FfbG9jX3B0KGNvb3JkcyA9IGMoLTExOS4wNjAsIDM2LjM1OSksIGlkID0gMSkgJT4lIA0KICBjYV9saXZuZWgoVFJVRSkgJT4lIA0KICBjYV9wZXJpb2QoImRheSIpICU+JSANCiAgY2FfY3ZhcigicHIiKSAlPiUgDQogIGNhX3llYXJzKHN0YXJ0ID0gMTk1MCwgZW5kID0gMjAwOSkNCg0KbHJlY19wcl9saXZuX2NhcCAlPiUgY2FfcHJlZmxpZ2h0KCkNCmBgYA0KYGBge3IgY2h1bmsyNX0NCmxyZWNfcHJfbGl2bl90YmwgPC0gbHJlY19wcl9saXZuX2NhcCAlPiUgDQogIGNhX2dldHZhbHNfdGJsKCkgJT4lIA0KICByZW5hbWUocHJfbW1kYXkgPSB2YWwpICANCg0KIyMgYmFja3VwOiBscmVjX3ByX3Byal90YmwgPC0gcmVhZFJEUygiLi9kYXRhL2xyZWNfcHJfcHJqX3RibC5yZHMiKQ0KDQpkaW0obHJlY19wcl9saXZuX3RibCkNCmhlYWQobHJlY19wcl9saXZuX3RibCkNCmBgYA0KDQpGaW5hbGx5LCB3ZSdsbCBwbG90IGhpc3RvZ3JhbXMgb2YgdGhlIHByZWNpcGl0YXRpb24gdmFsdWVzLCBsb2dnZWQgYmVjYXVzZSBvZiB0aGUgaGlnaGx5IHNrZXdlZCBkaXN0cmlidXRpb24uDQoNCmBgYHtyIGNodW5rMjZ9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KbHJlY19wcmRlY19saXZuX3RibCA8LSBscmVjX3ByX2xpdm5fdGJsICU+JSANCiAgbXV0YXRlKHByX21tZGF5X251bSA9IGFzLm51bWVyaWMocHJfbW1kYXkpLA0KICAgICAgICAgZGVjYWRlID0gZmxvb3IoeWVhcihkdCkgLyAxMCkgKiAxMCkgJT4lIA0KICBzZWxlY3QoZGVjYWRlLCBwcl9tbWRheV9udW0pDQoNCmdncGxvdChscmVjX3ByZGVjX2xpdm5fdGJsLCBhZXMoeD1sb2cocHJfbW1kYXlfbnVtKSkpICsgDQogIGdlb21faGlzdG9ncmFtKCkgKw0KICBmYWNldF93cmFwKCB+IGRlY2FkZSkNCg0KYGBgDQoNCiMgQ29uY2x1c2lvbg0KDQpPbmNlIHlvdSBnZXQgY2xpbWF0ZSBkYXRhIGluIFIgYXMgZGF0YSBmcmFtZXMsIHRoZXJlJ3MgYSBsb3QgeW91IGNhbiBkbyB3aXRoIGl0IQ0KDQpcDQoNCg0KDQoNCg==