Visualizing and working with spatial data in R is a basic skill needed when working on global change science and communicating your findings to stakeholders. This data tutorial will teach you the basics of spatial data analyses using NEON data.

1 Setup

1.1 Needed R knowledge and packages

This data tutorial requires you to have basic understanding of ggplot2 and dplyr from the tidyverse. It is also helpful if you have the basic R data wrangling and plotting knowledge outlined in R for Data Science.

To run the analyses outlined in this tutorial you need the following packages.

install.packages("tidyverse", dependencies = TRUE)
install.packages("sf", dependencies = TRUE)
install.packages("rnaturalearth", dependencies = TRUE)
install.packages("rnaturalearthdata", dependencies = TRUE)
install.packages("rnaturalearthhires", dependencies = TRUE)
install.packages("tigris", dependencies = TRUE)
install.packages("ggrepel", dependencies = TRUE)

If you do not want to install all of the dependencies, then set this argument to FALSE. If you are having problems installing the rnaturalearthhires try the code below to install directly from GitHub (you must have devtools installed).

library(devtools)
install_github("ropensci/rnaturalearthhires")

Finally, if you are having difficulty running the code, then update all of the required packages and try again.

1.2 NEON data

The data used in this module and for the module exercises can be obtained from the installed packages, from the NEON data portal, and from the NEON spatial data website. Specifically you will need the sites and domains shapefiles, which are located in zip files that you extract to your working directory.


2 Vectors and Rasters

There are two fundamental types of spatial objects: vectors and rasters. Vector data are discrete entities in space and time, such as a lakes, trees, houses, roads, states and countries. Raster data represent continuous data and are divided into pixels at a defined resolution. Rasters are images with associated spatial coordinates and typically are represented in data formats such as a GeoTIFF, but they can also be represented as text files. Raster data can be made from vector data and vice versa depending on the goals of an analysis.

Spatial data may can typically be boiled down to data tables and and often wrangled using methods from tidyverse. In this module we focus on learning how to wrangle and plot vector data.

Figure from: Martin Wegmann

Figure from: Martin Wegmann


3 Wrangling Vector Data

Vector data in R are represented by simple features in the sf package. Features are a standard way to represent discrete spatiotemporal data (i.e., data measured at a particular place at a particular time). Features are simply tidy data tables with observations as rows and variables as columns. Features have both spatial and non-spatial variables.

Feature spatial variables are termed geometries and indicate where on Earth the feature is located. For example, a GPS point would have a geometry made up of a latitude and longitude, and a polygon would have a geometry made up of multiple connected points that define the area encompassed by the polygon. Non-spatial variables are termed metadata, attributes or properties. In this module, we use the term properties. For example, a single point object can have a name and date associated with it as properties.

3.1 Types of vector data

Points, lines and polygons are vector data. Points are 0D (zero-dimensional) geometries like a GPS location with a latitude and longitude. Lines are 1D geometries made as a sequence of points connected with lines like a river or a road that do not self intersect to form a closed loop. Polygons are 2D geometries of sequences of connected points (lines) that form closed 2D loops (e.g., squares, donuts).

Vector geometries are all situated in 2D XY space. However, all simple features can also have two more dimensions for a total of 4D (XYZM space). The Z dimension indicates altitude, and the M dimension indicates measure like measurement error or the time the measurement was taken. We do not consider these other dimensions, but see here for more information.

All sf objects are stored as data tables (data.frame or tbl_df) with the number of rows equal to the number of features. For example, an sf object with 10 GPS points would have 10 rows. Geometries are stored as list-columns because all feature geometries require at least X and Y values (e.g., latitude and longitude). Properties can either be stored as standard or list-columns.

3.2 Read in vector files

sf functions are prefixed by st_ (standing for spatial and temporal).

The sf package can read many file types that contain vector data with the st_read function. Common file types include .csv and .shp files. Comma-delimited files (.csv) are the simplest and a common way to store point features. The shapefile format is a common file type for vector data maintained by the ERSI company.

Here we read in the NEON site locations shapefile with the st_read function. You will first need to download the zip file and extract it to your working directory.

library(sf)
neon_sites <- st_read("NEON_Field_Sites/NEON_Field_Sites_v16_1.shp", as_tibble = TRUE, quiet = TRUE)
# Make sure that the path argument in st_read is where you saved the .shp file
neon_sites
Simple feature collection with 81 features and 11 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -156.6194 ymin: 17.96955 xmax: -66.79851 ymax: 71.28241
CRS:            4326

The printed summary indicates that the object neon_sites contains 81 POINT features each with 11 properties. The features are 2D with X and Y dimensions. The area encompassed by all of the points is the bounding box bbox.

The epsg (SRID) value and the proj4string value provide information on the coordinate reference system (CRS) of the object. All spatial data must include a CRS that says how to project the data onto the surface of the earth (e.g., circular like on a sphere or flat like on a map). GPS data treat the earth as a sphere and use the WGS84 CRS. Your phone’s GPS uses this CRS. The NEON shapefile is in this projection. Make sure that if you are mapping multiple data sets, all of the data sets are in the same CRS. You can change a CRS with st_transform.
See here for more information on CRS in R. Also see this video that explains what projection are and their historical context. This website is a quick way to see a lot of different projections at once.

Shapefiles .shp are always associated with a set of other supporting files that provide metadata for the shapefile and explain to R how to visualize and map the shapefile. Thus, sf_read not only reads in the .shp file but also the other associated files in the same directory. There are 8 files read by st_read when loading NEON_Field_Sites.shp.

tibble(`Files Read by sf_read` = 
         str_subset(dir("NEON_Field_Sites"), 
                    pattern = "NEON_Field_Sites_v16"))

Note that if you want to use st_read for .csv files then you will need to pass along options that say which columns are the x and y geometry variables, for example

mysf <- st_read("my.csv", options = c("Longitude=x", "Latitude=y")).

Or you could just read in your csv as usual and use st_as_sf() setting the coords argument (and possibly the crs argument), for example

mysf <- read_csv("my.csv") %>% st_as_sf(coords = c("Longitude", "Latitude"))

3.3 Wrangling Vectors

sf objects are data tables and thus many of the functions that are used to wrangle data tables can be used with sf objects. For example, dplyr::select can choose and rearrange variables; dplyr::filter can subset features based on properties; and dplyr::summarize can aggregate and summarize based on properties. First let’s see what variables are in the neon_sites object.

colnames(neon_sites)
 [1] "domainNumb" "domainName" "siteName"   "PMC"       
 [5] "siteID"     "siteType"   "siteHost"   "stateID"   
 [9] "stateName"  "latitude"   "longitude"  "geometry"  

Select the domainName property

# selct only the DomainName variable.
neon_sites %>% dplyr::select(domainName)
Simple feature collection with 81 features and 1 field
geometry type:  POINT
dimension:      XY
bbox:           xmin: -156.6194 ymin: 17.96955 xmax: -66.79851 ymax: 71.28241
CRS:            4326

Note that because this is an sf object, the geometry list-column is also returned. Now let’s subset the sites within the Mid-Atlantic Domain.

# subset sites within the Mid-Atlantic Domain
neon_sites %>% 
  filter(domainName == "Mid-Atlantic")
Simple feature collection with 5 features and 11 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -78.14678 ymin: 38.89008 xmax: -76.56001 ymax: 39.09564
CRS:            4326

Now, we summarize the data by counting the number of sites located within each U.S. state.

# count number of sites colocated in each State
neon_sites %>% 
  group_by(stateName) %>% 
  summarize(`# of sites` = n()) %>% 
  arrange(desc(`# of sites`))
Simple feature collection with 25 features and 2 fields
geometry type:  GEOMETRY
dimension:      XY
bbox:           xmin: -156.6194 ymin: 17.96955 xmax: -66.79851 ymax: 71.28241
CRS:            4326

3.4 Exercises

  1. Download the site .csv file from the online map of NEON sites. Read in this file. Is the sf object produced by this file identical to the sites shapefile we used above? Use R code to show if and how they are different.

  2. Add properties to neon_sites that include:
  • the number of other sites co-located in the same Domain.
  • if a site is aquatic or terrestrial.
  • if a site is located in the tropics.

4 Plotting Vector Data

Use geom_sf in ggplot2 to plot any sf object.

ggplot() + geom_sf(data = neon_sites, size = 2, shape = 21, fill = "red") + theme_bw()

Like points, polygons can be read in with st_read. Here we read in the NEON domains shapefile that give the outlines of the regions that NEON used to choose their sites. See here for a video that describes the statistical process used.
Like for the NEON sites shapefile used above, you have to download the zip file and extract the domains shapefile to your working directory.

neon_doms <- st_read("NEONDomains_0/NEON_Domains.shp", as_tibble = TRUE, quiet = TRUE)

Let’s see what is in neon_doms

neon_doms
Simple feature collection with 22 features and 6 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -168.1244 ymin: 17.92621 xmax: -65.59024 ymax: 71.40624
CRS:            4326

In this object, there are multiple polygons and the DomainName variable provides the name of the domain. The simplest way to make a map of sf objects is to use the geom_sf function in ggplot2. We can map and then label the domains with the following code:

neon_doms %>%
  ggplot() + 
  geom_sf(aes(fill = DomainName)) + 
  geom_sf(data = neon_sites) + 
  geom_sf_label(aes(label = DomainName), size = 1.8) +
  labs(title = "NEON sites and labeled domains") + 
  theme(legend.position = "none")

We plotted both the sites and the polygons and we use geom_sf_labels to label each of the domains with their name. The function geom_sf_text provides similar functionality. There is also a way to make the labels better spaced usingggrepel for labeling as described here.

library(ggrepel)
neon_doms %>%
  ggplot() + 
  geom_sf(aes(fill = DomainName)) +
  geom_label_repel( aes(label = DomainName, geometry = geometry),
                    stat = "sf_coordinates",
                    min.segment.length = 0,
                    size = 1.8) +
  labs(title = "Use ggrepel to make popout annotations") + 
  theme(legend.position = "none")

4.1 Exercises

  1. Compare the functions geom_sf and geom_point by plotting the NEON sites data (i.e., neon_sites that we made above). Do the two maps look the same? How can you produce a map with geom_point that looks better? Can you use the same technique with geom_sf? What errors do you get?

  2. Change the projection of one of the above maps into an equal area projection.

5 Plotting with Base Maps

So far we have just been plotting vector data that we supplied to R, but sometimes you will want to plot your vector data onto a base map, like the outline of the USA. Natural Earth is an open access database of global spatial data. It allows you to easily make base maps on which to plot your data. The packages you need are rnaturalearth and rnaturalearthdata.

5.1 Global base map

library(rnaturalearth)
library(rnaturalearthdata)
global_basemap <- ne_countries(scale = "small", returnclass = "sf")
ggplot(data = global_basemap) + 
  geom_sf(fill = "white") +
  labs(title = "World map from Natural Earth (low resolution)")

The ne_countries function from rnaturalearth returns country polygons. The returnclass argument just says to return an sf object. The scale argument provides the map resolution, there are small, medium and large resolution base maps. For the large resolution you will need to install rnaturalearthhires and you will be prompted to do so when you set scale = "large" if you have not already installed the package. To obtain even higher resolution base maps, then see the GADM R package GADMTools.

To zoom in on an area, use coord_sf, which is the default coord_ function that geom_sf uses to plot data. See the R4DS chapter on coordinate systems.

ggplot(data = global_basemap) + 
  geom_sf(fill = "white") + 
  geom_sf(data = neon_sites) +
  coord_sf(xlim = c(-90, -50), ylim = c(10, 50), expand = FALSE) + 
  theme(panel.background = element_rect(fill = "lightblue")) + 
  labs(title = "Zoom in with xlim and ylim")

Notice how with this low resolution map, many of the smaller Caribbean Islands are missing.

5.2 Lower administration levels

The globe can be partitioned into hierarchical geopolitical administrative boundaries. rnaturalearth provides data at country and state level boundaries. To illustrate these levels, first let’s get a base map for an individual country.

usa_basemap <- ne_countries(country = "united states of america", 
                            scale = "medium", 
                            returnclass = "sf")
ggplot() +
  geom_sf(data = usa_basemap) +
  coord_sf(xlim = c(-175, -60)) + 
  theme_dark() +
  labs(title = "U.S.A.")

To get a base-map of individual states use the ne_states function. The first time that you run the code below, you will be asked to install the rnaturalearthhires package.

state_basemap <- ne_states(country = "united states of america", 
                           returnclass = "sf")
ggplot() +
  geom_sf(data = state_basemap) +
  coord_sf(xlim = c(-175, -60)) +
  theme_bw() + 
  labs(title = "U.S.A. States")

But Puerto Rico is not included in this base map, and NEON has a site in Puerto Rico.

pr_basemap <- ne_states(country="puerto rico", returnclass = "sf")
# Combine the maps
state_pr_basemap=rbind(state_basemap, pr_basemap)
ggplot() +
  geom_sf(data = state_pr_basemap) +
  coord_sf(xlim = c(-175, -60)) +
  theme_bw() + 
  labs(title = "U.S.A. States + Puerto Rico")

What about even lower administration base maps? For U.S. county scale data, use the tigris package. This package downloads county (and other) data from the Geography Program of the U.S. Census Bureau. To download county data use the counties function.

county_basemap
Simple feature collection with 3233 features and 9 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -179.1489 ymin: -14.5487 xmax: 179.7785 ymax: 71.36516
CRS:            +proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0
First 10 features:
  STATEFP COUNTYFP COUNTYNS       AFFGEOID GEOID    NAME LSAD
0      21      007 00516850 0500000US21007 21007 Ballard   06
1      21      017 00516855 0500000US21017 21017 Bourbon   06
2      21      031 00516862 0500000US21031 21031  Butler   06
3      21      065 00516879 0500000US21065 21065  Estill   06
4      21      069 00516881 0500000US21069 21069 Fleming   06
5      21      093 00516893 0500000US21093 21093  Hardin   06
6      21      099 00516896 0500000US21099 21099    Hart   06
7      21      131 00516912 0500000US21131 21131  Leslie   06
8      21      151 00516919 0500000US21151 21151 Madison   06
9      21      155 00516921 0500000US21155 21155  Marion   06
       ALAND   AWATER                       geometry
0  639387454 69473325 MULTIPOLYGON (((-89.18137 3...
1  750439351  4829777 MULTIPOLYGON (((-84.44266 3...
2 1103571974 13943044 MULTIPOLYGON (((-86.94486 3...
3  655509930  6516335 MULTIPOLYGON (((-84.12662 3...
4  902727151  7182793 MULTIPOLYGON (((-83.98428 3...
5 1614569777 17463238 MULTIPOLYGON (((-86.27756 3...
6 1068530028 13692536 MULTIPOLYGON (((-86.16112 3...
7 1038206077  9189732 MULTIPOLYGON (((-83.5531 37...
8 1132729653 15306635 MULTIPOLYGON (((-84.52564 3...
9  888463701  9891797 MULTIPOLYGON (((-85.52129 3...
county_plot <- ggplot() + 
  geom_sf(data = county_basemap) +
  labs(title = "
       U.S.A. counties") 
county_plot + coord_sf(xlim = c(-175, -60), ylim = c(16, 73))

On this map you can plot other layers like the neon_sites.

county_plot + 
  geom_sf(data = neon_sites, size = 2, shape = 21, fill = "red") + 
  coord_sf(xlim = c(-175, -60), ylim = c(16, 73))

If you want to just get the counties for one state, then you have to work with the STATEFP property in the county_basemap object. This property provides a unique numeric code called a FIPS code for each state. To find the state you want, use lookup_code. Let’s look at Colorado, which as we saw above, has 7 NEON sites, the second most of any state. It is also where the NEON headquarters is located.

lookup_code("colorado")
[1] "The code for Colorado is '08'."
county_basemap %>% 
  filter(STATEFP == '08') %>% 
    ggplot() +
      geom_sf() +
      geom_sf(data = filter(neon_sites, stateName == "Colorado"), 
              aes(fill = siteType), shape = 21, size = 3, alpha = .7) +
  labs(title = "Colorado and its NEON sites") + 
  theme_bw() + 
  guides(fill = guide_legend(override.aes = list( size = 1, alpha = 1)))

5.3 Map data values

Once you are able to map polygons and points, you will want to display your data on the map. Just like any ggplot, you can set aesthetics to plot data. Here, we use fill to color countries according to income grouping. The countries data set from rnaturalearth already contains a lot of data at the per country scale, and income group is one included variable.

ggplot(data = global_basemap) + 
  geom_sf(aes(fill = income_grp)) +
  labs(title = "Income groups")

5.4 Exercises

  1. Calculate the number of NEON sites in each state, join the data to state_basemap and use fill to plot those values on a map.

  2. Choose one of the NEON taxa that we used in our midterm and make a map of species richness across the domains.

6 Further Reading

To learn more than what is presented here, see:

This teaching model borrowed from these sources. Be sure to cite the sf package in any product you produce using the sf package.

7 Acknowledgments

Donal O’Leary a science education specialist at NEON made comments and edits to this module. Thank you!

LS0tDQp0aXRsZTogIlZlY3RvciBkYXRhIGJhc2ljcyBpbiBSIg0KYXV0aG9yOiAnW01hdHRoZXcgUi4gSGVsbXVzXShodHRwczovL3d3dy5pZWNvbGFiLm9yZyknDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBsdW1lbg0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAyDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB5ZXMNCiAgICAgIHNtb290aF9zY3JvbGw6IG5vDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0Kc3VidGl0bGU6IE5FT04gZGF0YSB0dXRvcmlhbA0KLS0tDQoNCmBgYHtyIHNldHVwLCBlY2hvID0gRkFMU0UsIGluY2x1ZGUgPSBGQUxTRX0NCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHJuYXR1cmFsZWFydGgpDQpsaWJyYXJ5KHJuYXR1cmFsZWFydGhkYXRhKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoaGlyZXMpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlncmlzKQ0KYGBgDQo8YmFzZSB0YXJnZXQgPSAiX2JsYW5rIj4NClZpc3VhbGl6aW5nIGFuZCB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhIGluIFIgaXMgYSBiYXNpYyBza2lsbCBuZWVkZWQgd2hlbiB3b3JraW5nIG9uIGdsb2JhbCBjaGFuZ2Ugc2NpZW5jZSBhbmQgY29tbXVuaWNhdGluZyB5b3VyIGZpbmRpbmdzIHRvIHN0YWtlaG9sZGVycy4gVGhpcyBkYXRhIHR1dG9yaWFsIHdpbGwgdGVhY2ggeW91IHRoZSBiYXNpY3Mgb2Ygc3BhdGlhbCBkYXRhIGFuYWx5c2VzIHVzaW5nIFtORU9OIGRhdGFdKGh0dHBzOi8vZGF0YS5uZW9uc2NpZW5jZS5vcmcvZmFxKS4NCg0KIyBTZXR1cA0KDQojIyBOZWVkZWQgUiBrbm93bGVkZ2UgYW5kIHBhY2thZ2VzDQpUaGlzIGRhdGEgdHV0b3JpYWwgcmVxdWlyZXMgeW91IHRvIGhhdmUgYmFzaWMgdW5kZXJzdGFuZGluZyBvZiBgZ2dwbG90MmAgYW5kIGBkcGx5cmAgZnJvbSB0aGUgYHRpZHl2ZXJzZWAuIEl0IGlzIGFsc28gaGVscGZ1bCBpZiB5b3UgaGF2ZSB0aGUgYmFzaWMgUiBkYXRhIHdyYW5nbGluZyBhbmQgcGxvdHRpbmcga25vd2xlZGdlIG91dGxpbmVkIGluIFtSIGZvciBEYXRhIFNjaWVuY2VdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovKS4NCg0KVG8gcnVuIHRoZSBhbmFseXNlcyBvdXRsaW5lZCBpbiB0aGlzIHR1dG9yaWFsIHlvdSBuZWVkIHRoZSBmb2xsb3dpbmcgcGFja2FnZXMuDQoNCj4gYGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpYCAgDQo+IGBpbnN0YWxsLnBhY2thZ2VzKCJzZiIsIGRlcGVuZGVuY2llcyA9IFRSVUUpYCAgDQo+IGBpbnN0YWxsLnBhY2thZ2VzKCJybmF0dXJhbGVhcnRoIiwgZGVwZW5kZW5jaWVzID0gVFJVRSlgICANCj4gYGluc3RhbGwucGFja2FnZXMoInJuYXR1cmFsZWFydGhkYXRhIiwgZGVwZW5kZW5jaWVzID0gVFJVRSlgICANCj4gYGluc3RhbGwucGFja2FnZXMoInJuYXR1cmFsZWFydGhoaXJlcyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpYCAgDQo+IGBpbnN0YWxsLnBhY2thZ2VzKCJ0aWdyaXMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKWAgIA0KPiBgaW5zdGFsbC5wYWNrYWdlcygiZ2dyZXBlbCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpYCAgDQoNCklmIHlvdSBkbyBub3Qgd2FudCB0byBpbnN0YWxsIGFsbCBvZiB0aGUgZGVwZW5kZW5jaWVzLCB0aGVuIHNldCB0aGlzIGFyZ3VtZW50IHRvIGBGQUxTRWAuIElmIHlvdSBhcmUgaGF2aW5nIHByb2JsZW1zIGluc3RhbGxpbmcgdGhlIGBybmF0dXJhbGVhcnRoaGlyZXNgIHRyeSB0aGUgY29kZSBiZWxvdyB0byBpbnN0YWxsIGRpcmVjdGx5IGZyb20gR2l0SHViICh5b3UgbXVzdCBoYXZlIFtgZGV2dG9vbHNgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgaW5zdGFsbGVkKS4gDQoNCj4gYGxpYnJhcnkoZGV2dG9vbHMpYCAgDQo+IGBpbnN0YWxsX2dpdGh1Yigicm9wZW5zY2kvcm5hdHVyYWxlYXJ0aGhpcmVzIilgDQoNCkZpbmFsbHksIGlmIHlvdSBhcmUgaGF2aW5nIGRpZmZpY3VsdHkgcnVubmluZyB0aGUgY29kZSwgdGhlbiB1cGRhdGUgYWxsIG9mIHRoZSByZXF1aXJlZCBwYWNrYWdlcyBhbmQgdHJ5IGFnYWluLg0KDQojIyBORU9OIGRhdGENClRoZSBkYXRhIHVzZWQgaW4gdGhpcyBtb2R1bGUgYW5kIGZvciB0aGUgbW9kdWxlIGV4ZXJjaXNlcyBjYW4gYmUgb2J0YWluZWQgZnJvbSB0aGUgaW5zdGFsbGVkIHBhY2thZ2VzLCBmcm9tIHRoZSBbTkVPTiBkYXRhIHBvcnRhbF0oaHR0cHM6Ly9kYXRhLm5lb25zY2llbmNlLm9yZy9kYXRhLXByb2R1Y3RzL2V4cGxvcmUpLCBhbmQgZnJvbSB0aGUgW05FT04gc3BhdGlhbCBkYXRhIHdlYnNpdGVdKGh0dHBzOi8vd3d3Lm5lb25zY2llbmNlLm9yZy9kYXRhL2Fib3V0LWRhdGEvc3BhdGlhbC1kYXRhLW1hcHMpLiBTcGVjaWZpY2FsbHkgeW91IHdpbGwgbmVlZCB0aGUgc2l0ZXMgYW5kIGRvbWFpbnMgc2hhcGVmaWxlcywgd2hpY2ggYXJlIGxvY2F0ZWQgaW4gemlwIGZpbGVzIHRoYXQgeW91IGV4dHJhY3QgdG8geW91ciB3b3JraW5nIGRpcmVjdG9yeS4NCg0KKioqDQoNCiMgVmVjdG9ycyBhbmQgUmFzdGVycw0KDQpUaGVyZSBhcmUgdHdvIGZ1bmRhbWVudGFsIHR5cGVzIG9mIHNwYXRpYWwgb2JqZWN0czogdmVjdG9ycyBhbmQgcmFzdGVycy4gVmVjdG9yIGRhdGEgYXJlIGRpc2NyZXRlIGVudGl0aWVzIGluIHNwYWNlIGFuZCB0aW1lLCBzdWNoIGFzIGEgbGFrZXMsIHRyZWVzLCBob3VzZXMsIHJvYWRzLCBzdGF0ZXMgYW5kIGNvdW50cmllcy4gUmFzdGVyIGRhdGEgcmVwcmVzZW50IGNvbnRpbnVvdXMgZGF0YSBhbmQgYXJlIGRpdmlkZWQgaW50byBwaXhlbHMgYXQgYSBkZWZpbmVkIHJlc29sdXRpb24uIFJhc3RlcnMgYXJlIGltYWdlcyB3aXRoIGFzc29jaWF0ZWQgc3BhdGlhbCBjb29yZGluYXRlcyBhbmQgdHlwaWNhbGx5IGFyZSByZXByZXNlbnRlZCBpbiBkYXRhIGZvcm1hdHMgc3VjaCBhcyBhICpHZW9USUZGKiwgYnV0IHRoZXkgY2FuIGFsc28gYmUgcmVwcmVzZW50ZWQgYXMgdGV4dCBmaWxlcy4gUmFzdGVyIGRhdGEgY2FuIGJlIG1hZGUgZnJvbSB2ZWN0b3IgZGF0YSBhbmQgdmljZSB2ZXJzYSBkZXBlbmRpbmcgb24gdGhlIGdvYWxzIG9mIGFuIGFuYWx5c2lzLiAgDQoNClNwYXRpYWwgZGF0YSBtYXkgY2FuIHR5cGljYWxseSBiZSBib2lsZWQgZG93biB0byBkYXRhIHRhYmxlcyBhbmQgYW5kIG9mdGVuIHdyYW5nbGVkIHVzaW5nIG1ldGhvZHMgZnJvbSBgdGlkeXZlcnNlYC4gSW4gdGhpcyBtb2R1bGUgd2UgZm9jdXMgb24gbGVhcm5pbmcgaG93IHRvIHdyYW5nbGUgYW5kIHBsb3QgdmVjdG9yIGRhdGEuDQoNCiFbRmlndXJlIGZyb206IE1hcnRpbiBXZWdtYW5uXShodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL2IvYjgvUmFzdGVyX3ZlY3Rvcl90aWt6LnBuZyl7IHdpZHRoPTcwJSB9DQoNCioqKg0KDQojIFdyYW5nbGluZyBWZWN0b3IgRGF0YQ0KVmVjdG9yIGRhdGEgaW4gUiBhcmUgcmVwcmVzZW50ZWQgYnkgW3NpbXBsZSBmZWF0dXJlc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU2ltcGxlX0ZlYXR1cmVzKSBpbiB0aGUgW2BzZiBwYWNrYWdlYF0oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2luZGV4Lmh0bWwpLiBGZWF0dXJlcyBhcmUgYSBzdGFuZGFyZCB3YXkgdG8gcmVwcmVzZW50IGRpc2NyZXRlIHNwYXRpb3RlbXBvcmFsIGRhdGEgKCppLmUuKiwgZGF0YSBtZWFzdXJlZCBhdCBhIHBhcnRpY3VsYXIgcGxhY2UgYXQgYSBwYXJ0aWN1bGFyIHRpbWUpLiBGZWF0dXJlcyBhcmUgc2ltcGx5IHRpZHkgZGF0YSB0YWJsZXMgd2l0aCBvYnNlcnZhdGlvbnMgYXMgcm93cyBhbmQgdmFyaWFibGVzIGFzIGNvbHVtbnMuIEZlYXR1cmVzIGhhdmUgYm90aCBzcGF0aWFsIGFuZCBub24tc3BhdGlhbCB2YXJpYWJsZXMuICANCg0KRmVhdHVyZSBzcGF0aWFsIHZhcmlhYmxlcyBhcmUgdGVybWVkICpnZW9tZXRyaWVzKiBhbmQgaW5kaWNhdGUgd2hlcmUgb24gRWFydGggdGhlIGZlYXR1cmUgaXMgbG9jYXRlZC4gRm9yIGV4YW1wbGUsIGEgR1BTIHBvaW50IHdvdWxkIGhhdmUgYSBnZW9tZXRyeSBtYWRlIHVwIG9mIGEgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSwgYW5kIGEgcG9seWdvbiB3b3VsZCBoYXZlIGEgZ2VvbWV0cnkgbWFkZSB1cCBvZiBtdWx0aXBsZSBjb25uZWN0ZWQgcG9pbnRzIHRoYXQgZGVmaW5lIHRoZSBhcmVhIGVuY29tcGFzc2VkIGJ5IHRoZSBwb2x5Z29uLiBOb24tc3BhdGlhbCB2YXJpYWJsZXMgYXJlIHRlcm1lZCAqbWV0YWRhdGEqLCAqYXR0cmlidXRlcyogb3IgKnByb3BlcnRpZXMqLiBJbiB0aGlzIG1vZHVsZSwgd2UgdXNlIHRoZSB0ZXJtICpwcm9wZXJ0aWVzKi4gRm9yIGV4YW1wbGUsIGEgc2luZ2xlIHBvaW50IG9iamVjdCBjYW4gaGF2ZSBhIG5hbWUgYW5kIGRhdGUgYXNzb2NpYXRlZCB3aXRoIGl0IGFzIHByb3BlcnRpZXMuICANCg0KIyMgVHlwZXMgb2YgdmVjdG9yIGRhdGENCipQb2ludHMqLCAqbGluZXMqIGFuZCAqcG9seWdvbnMqIGFyZSB2ZWN0b3IgZGF0YS4gUG9pbnRzIGFyZSAwRCAoemVyby1kaW1lbnNpb25hbCkgZ2VvbWV0cmllcyBsaWtlIGEgR1BTIGxvY2F0aW9uIHdpdGggYSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlLiBMaW5lcyBhcmUgMUQgZ2VvbWV0cmllcyBtYWRlIGFzIGEgc2VxdWVuY2Ugb2YgcG9pbnRzIGNvbm5lY3RlZCB3aXRoIGxpbmVzIGxpa2UgYSByaXZlciBvciBhIHJvYWQgdGhhdCBkbyBub3Qgc2VsZiBpbnRlcnNlY3QgdG8gZm9ybSBhIGNsb3NlZCBsb29wLiBQb2x5Z29ucyBhcmUgMkQgZ2VvbWV0cmllcyBvZiBzZXF1ZW5jZXMgb2YgY29ubmVjdGVkIHBvaW50cyAobGluZXMpIHRoYXQgZm9ybSBjbG9zZWQgMkQgbG9vcHMgKGUuZy4sIHNxdWFyZXMsIGRvbnV0cykuICANCg0KVmVjdG9yIGdlb21ldHJpZXMgYXJlIGFsbCBzaXR1YXRlZCBpbiAyRCBYWSBzcGFjZS4gSG93ZXZlciwgYWxsIHNpbXBsZSBmZWF0dXJlcyBjYW4gYWxzbyBoYXZlIHR3byBtb3JlIGRpbWVuc2lvbnMgZm9yIGEgdG90YWwgb2YgNEQgKFhZWk0gc3BhY2UpLiBUaGUgWiBkaW1lbnNpb24gaW5kaWNhdGVzICphbHRpdHVkZSosIGFuZCB0aGUgTSBkaW1lbnNpb24gaW5kaWNhdGVzICptZWFzdXJlKiBsaWtlIG1lYXN1cmVtZW50IGVycm9yIG9yIHRoZSB0aW1lIHRoZSBtZWFzdXJlbWVudCB3YXMgdGFrZW4uIFdlIGRvIG5vdCBjb25zaWRlciB0aGVzZSBvdGhlciBkaW1lbnNpb25zLCBidXQgc2VlIFtoZXJlICBmb3IgbW9yZSBpbmZvcm1hdGlvbl0oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMS5odG1sKS4NCg0KQWxsIGBzZmAgb2JqZWN0cyBhcmUgc3RvcmVkIGFzIGRhdGEgdGFibGVzIChgZGF0YS5mcmFtZWAgb3IgYHRibF9kZmApIHdpdGggdGhlIG51bWJlciBvZiByb3dzIGVxdWFsIHRvIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMuIEZvciBleGFtcGxlLCBhbiBgc2ZgIG9iamVjdCB3aXRoIDEwIEdQUyBwb2ludHMgd291bGQgaGF2ZSAxMCByb3dzLiBHZW9tZXRyaWVzIGFyZSBzdG9yZWQgYXMgW2xpc3QtY29sdW1uc10oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9tYW55LW1vZGVscy5odG1sI2xpc3QtY29sdW1ucy0xKSBiZWNhdXNlIGFsbCBmZWF0dXJlIGdlb21ldHJpZXMgcmVxdWlyZSBhdCBsZWFzdCBYIGFuZCBZIHZhbHVlcyAoZS5nLiwgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSkuIFByb3BlcnRpZXMgY2FuIGVpdGhlciBiZSBzdG9yZWQgYXMgc3RhbmRhcmQgb3IgbGlzdC1jb2x1bW5zLiANCg0KIyMgUmVhZCBpbiB2ZWN0b3IgZmlsZXMNCg0KYHNmYCBmdW5jdGlvbnMgYXJlIHByZWZpeGVkIGJ5IGBzdF9gIChzdGFuZGluZyBmb3Igc3BhdGlhbCBhbmQgdGVtcG9yYWwpLiAgDQoNClRoZSBgc2YgcGFja2FnZWAgY2FuIHJlYWQgbWFueSBmaWxlIHR5cGVzIHRoYXQgY29udGFpbiB2ZWN0b3IgZGF0YSB3aXRoIHRoZSBgc3RfcmVhZGAgZnVuY3Rpb24uIENvbW1vbiBmaWxlIHR5cGVzIGluY2x1ZGUgLmNzdiBhbmQgLnNocCBmaWxlcy4gQ29tbWEtZGVsaW1pdGVkIGZpbGVzICguY3N2KSBhcmUgdGhlIHNpbXBsZXN0IGFuZCBhIGNvbW1vbiB3YXkgdG8gc3RvcmUgcG9pbnQgZmVhdHVyZXMuIFRoZSBzaGFwZWZpbGUgZm9ybWF0IGlzIGEgIGNvbW1vbiBmaWxlIHR5cGUgZm9yIHZlY3RvciBkYXRhIG1haW50YWluZWQgYnkgdGhlIFtFUlNJIGNvbXBhbnldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NoYXBlZmlsZSkuICANCg0KSGVyZSB3ZSByZWFkIGluIHRoZSBbTkVPTiBzaXRlIGxvY2F0aW9ucyBzaGFwZWZpbGVdKGh0dHBzOi8vd3d3Lm5lb25zY2llbmNlLm9yZy9kYXRhL2Fib3V0LWRhdGEvc3BhdGlhbC1kYXRhLW1hcHMpIHdpdGggdGhlIGBzdF9yZWFkYCBmdW5jdGlvbi4gWW91IHdpbGwgZmlyc3QgbmVlZCB0byBkb3dubG9hZCB0aGUgW3ppcCBmaWxlXShodHRwczovL3d3dy5uZW9uc2NpZW5jZS5vcmcvc2l0ZXMvZGVmYXVsdC9maWxlcy9ORU9OX0ZpZWxkX1NpdGVzLnppcCkgYW5kIGV4dHJhY3QgaXQgdG8geW91ciB3b3JraW5nIGRpcmVjdG9yeS4NCg0KYGBge3IgbG9hZCBORU9OIFNpdGVzIHNoYXBlZmlsZX0NCmxpYnJhcnkoc2YpDQpuZW9uX3NpdGVzIDwtIHN0X3JlYWQoIk5FT05fRmllbGRfU2l0ZXMvTkVPTl9GaWVsZF9TaXRlc192MTZfMS5zaHAiLCBhc190aWJibGUgPSBUUlVFLCBxdWlldCA9IFRSVUUpDQojIE1ha2Ugc3VyZSB0aGF0IHRoZSBwYXRoIGFyZ3VtZW50IGluIHN0X3JlYWQgaXMgd2hlcmUgeW91IHNhdmVkIHRoZSAuc2hwIGZpbGUNCm5lb25fc2l0ZXMNCmBgYA0KVGhlIHByaW50ZWQgc3VtbWFyeSBpbmRpY2F0ZXMgdGhhdCB0aGUgb2JqZWN0IGBuZW9uX3NpdGVzYCBjb250YWlucyBgciBkaW0obmVvbl9zaXRlcylbMV1gIFBPSU5UIGZlYXR1cmVzIGVhY2ggd2l0aCBgciBkaW0obmVvbl9zaXRlcylbMl0gLSAxYCBwcm9wZXJ0aWVzLiBUaGUgZmVhdHVyZXMgYXJlIDJEIHdpdGggWCBhbmQgWSBkaW1lbnNpb25zLiBUaGUgYXJlYSBlbmNvbXBhc3NlZCBieSBhbGwgb2YgdGhlIHBvaW50cyBpcyB0aGUgYm91bmRpbmcgYm94IGBiYm94YC4gIA0KDQpUaGUgYGVwc2cgKFNSSUQpYCB2YWx1ZSBhbmQgdGhlIGBwcm9qNHN0cmluZ2AgdmFsdWUgcHJvdmlkZSBpbmZvcm1hdGlvbiBvbiB0aGUgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtIChDUlMpIG9mIHRoZSBvYmplY3QuIEFsbCBzcGF0aWFsIGRhdGEgbXVzdCBpbmNsdWRlIGEgQ1JTIHRoYXQgc2F5cyBob3cgdG8gcHJvamVjdCB0aGUgZGF0YSBvbnRvIHRoZSBzdXJmYWNlIG9mIHRoZSBlYXJ0aCAoZS5nLiwgY2lyY3VsYXIgbGlrZSBvbiBhIHNwaGVyZSBvciBmbGF0IGxpa2Ugb24gYSBtYXApLiBHUFMgZGF0YSB0cmVhdCB0aGUgZWFydGggYXMgYSBzcGhlcmUgYW5kIHVzZSB0aGUgV0dTODQgQ1JTLiBZb3VyIHBob25lJ3MgR1BTIHVzZXMgdGhpcyBDUlMuIFRoZSBORU9OIHNoYXBlZmlsZSBpcyBpbiB0aGlzIHByb2plY3Rpb24uIE1ha2Ugc3VyZSB0aGF0IGlmIHlvdSBhcmUgbWFwcGluZyBtdWx0aXBsZSBkYXRhIHNldHMsIGFsbCBvZiB0aGUgZGF0YSBzZXRzIGFyZSBpbiB0aGUgc2FtZSBDUlMuIFlvdSBjYW4gY2hhbmdlIGEgQ1JTIHdpdGggYHN0X3RyYW5zZm9ybWAuICANClNlZSBbaGVyZSBmb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiBDUlMgaW4gUl0oaHR0cHM6Ly9ib29rZG93bi5vcmcvcm9iaW5sb3ZlbGFjZS9nZW9jb21wci9zcGF0aWFsLWNsYXNzLmh0bWwjY3JzLWludHJvKS4gQWxzbyBzZWUgdGhpcyBbdmlkZW9dKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9a0lJRDVGRGkySlEpIHRoYXQgZXhwbGFpbnMgd2hhdCBwcm9qZWN0aW9uIGFyZSBhbmQgdGhlaXIgaGlzdG9yaWNhbCBjb250ZXh0LiBUaGlzIFt3ZWJzaXRlXShodHRwczovL2JsLm9ja3Mub3JnL21ib3N0b2NrLzM3MTE2NTIpIGlzIGEgcXVpY2sgd2F5IHRvIHNlZSBhIGxvdCBvZiBkaWZmZXJlbnQgcHJvamVjdGlvbnMgYXQgb25jZS4NCg0KU2hhcGVmaWxlcyBgLnNocGAgYXJlIGFsd2F5cyBhc3NvY2lhdGVkIHdpdGggYSBzZXQgb2Ygb3RoZXIgc3VwcG9ydGluZyBmaWxlcyB0aGF0IHByb3ZpZGUgbWV0YWRhdGEgZm9yIHRoZSBzaGFwZWZpbGUgYW5kIGV4cGxhaW4gdG8gUiBob3cgdG8gdmlzdWFsaXplIGFuZCBtYXAgdGhlIHNoYXBlZmlsZS4gVGh1cywgYHNmX3JlYWRgIG5vdCBvbmx5IHJlYWRzIGluIHRoZSBgLnNocGAgZmlsZSBidXQgYWxzbyB0aGUgb3RoZXIgYXNzb2NpYXRlZCBmaWxlcyBpbiB0aGUgc2FtZSBkaXJlY3RvcnkuIFRoZXJlIGFyZSBgciBsZW5ndGgoc3RyX3N1YnNldChkaXIoIk5FT05GaWVsZFNpdGVzLXYxNiIpLCBwYXR0ZXJuID0gIk5FT05fRmllbGRfU2l0ZXMiKSlgIGZpbGVzIHJlYWQgYnkgYHN0X3JlYWRgIHdoZW4gbG9hZGluZyBgTkVPTl9GaWVsZF9TaXRlcy5zaHBgLg0KDQpgYGB7ciBsaXN0IHRoZSBzaGFwZWZpbGVzfQ0KdGliYmxlKGBGaWxlcyBSZWFkIGJ5IHNmX3JlYWRgID0gDQogICAgICAgICBzdHJfc3Vic2V0KGRpcigiTkVPTl9GaWVsZF9TaXRlcyIpLCANCiAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICJORU9OX0ZpZWxkX1NpdGVzX3YxNiIpKQ0KYGBgDQoNCk5vdGUgdGhhdCBpZiB5b3Ugd2FudCB0byB1c2UgYHN0X3JlYWRgIGZvciAuY3N2IGZpbGVzIHRoZW4geW91IHdpbGwgbmVlZCB0byBwYXNzIGFsb25nIGBvcHRpb25zYCB0aGF0IHNheSB3aGljaCBjb2x1bW5zIGFyZSB0aGUgYHhgIGFuZCBgeWAgZ2VvbWV0cnkgdmFyaWFibGVzLCBmb3IgZXhhbXBsZSAgDQoNCj4gYG15c2YgPC0gc3RfcmVhZCgibXkuY3N2Iiwgb3B0aW9ucyA9IGMoIkxvbmdpdHVkZT14IiwgIkxhdGl0dWRlPXkiKSlgLiAgDQoNCk9yIHlvdSBjb3VsZCBqdXN0IHJlYWQgaW4geW91ciBjc3YgYXMgdXN1YWwgYW5kIHVzZSBgc3RfYXNfc2YoKWAgc2V0dGluZyB0aGUgYGNvb3Jkc2AgYXJndW1lbnQgKGFuZCBwb3NzaWJseSB0aGUgYGNyc2AgYXJndW1lbnQpLCBmb3IgZXhhbXBsZQ0KDQo+IGBteXNmIDwtIHJlYWRfY3N2KCJteS5jc3YiKSAgJT4lIHN0X2FzX3NmKGNvb3JkcyA9IGMoIkxvbmdpdHVkZSIsICJMYXRpdHVkZSIpKWAgIA0KDQoNCiMjIFdyYW5nbGluZyBWZWN0b3JzDQoNCmBzZmAgb2JqZWN0cyBhcmUgZGF0YSB0YWJsZXMgYW5kIHRodXMgbWFueSBvZiB0aGUgZnVuY3Rpb25zIHRoYXQgYXJlIHVzZWQgdG8gd3JhbmdsZSBkYXRhIHRhYmxlcyBjYW4gYmUgdXNlZCB3aXRoIGBzZmAgb2JqZWN0cy4gRm9yIGV4YW1wbGUsIGBkcGx5cjo6c2VsZWN0YCBjYW4gY2hvb3NlIGFuZCByZWFycmFuZ2UgdmFyaWFibGVzOyBgZHBseXI6OmZpbHRlcmAgY2FuIHN1YnNldCBmZWF0dXJlcyBiYXNlZCBvbiBwcm9wZXJ0aWVzOyBhbmQgYGRwbHlyOjpzdW1tYXJpemVgIGNhbiBhZ2dyZWdhdGUgYW5kIHN1bW1hcml6ZSBiYXNlZCBvbiBwcm9wZXJ0aWVzLiBGaXJzdCBsZXQncyBzZWUgd2hhdCB2YXJpYWJsZXMgYXJlIGluIHRoZSBgbmVvbl9zaXRlc2Agb2JqZWN0Lg0KDQpgYGB7ciBsb29rIGF0IHRoZSB0aWJibGV9DQpjb2xuYW1lcyhuZW9uX3NpdGVzKQ0KYGBgDQoNClNlbGVjdCB0aGUgYGRvbWFpbk5hbWVgIHByb3BlcnR5DQoNCmBgYHtyIHJlYXJyYW5nZSB3aXRoIHNlbGVjdH0NCiMgc2VsY3Qgb25seSB0aGUgRG9tYWluTmFtZSB2YXJpYWJsZS4NCm5lb25fc2l0ZXMgJT4lIGRwbHlyOjpzZWxlY3QoZG9tYWluTmFtZSkNCmBgYA0KDQpOb3RlIHRoYXQgYmVjYXVzZSB0aGlzIGlzIGFuIGBzZmAgb2JqZWN0LCB0aGUgYGdlb21ldHJ5YCBbbGlzdC1jb2x1bW5dKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovbWFueS1tb2RlbHMuaHRtbCNsaXN0LWNvbHVtbnMtMSkgaXMgYWxzbyByZXR1cm5lZC4gIE5vdyBsZXQncyBzdWJzZXQgdGhlIHNpdGVzIHdpdGhpbiB0aGUgTWlkLUF0bGFudGljIERvbWFpbi4NCg0KYGBge3Igc3Vic2V0IHdpdGggZmlsdGVyfQ0KIyBzdWJzZXQgc2l0ZXMgd2l0aGluIHRoZSBNaWQtQXRsYW50aWMgRG9tYWluDQpuZW9uX3NpdGVzICU+JSANCiAgZmlsdGVyKGRvbWFpbk5hbWUgPT0gIk1pZC1BdGxhbnRpYyIpDQpgYGANCg0KDQpOb3csIHdlIHN1bW1hcml6ZSB0aGUgZGF0YSBieSBjb3VudGluZyB0aGUgbnVtYmVyIG9mIHNpdGVzIGxvY2F0ZWQgd2l0aGluIGVhY2ggVS5TLiBzdGF0ZS4NCmBgYHtyIHN1bW1hcml6ZX0NCiMgY291bnQgbnVtYmVyIG9mIHNpdGVzIGNvbG9jYXRlZCBpbiBlYWNoIFN0YXRlDQpuZW9uX3NpdGVzICU+JSANCiAgZ3JvdXBfYnkoc3RhdGVOYW1lKSAlPiUgDQogIHN1bW1hcml6ZShgIyBvZiBzaXRlc2AgPSBuKCkpICU+JSANCiAgYXJyYW5nZShkZXNjKGAjIG9mIHNpdGVzYCkpDQpgYGANCg0KIyMgRXhlcmNpc2VzDQoNCiAgMS4gRG93bmxvYWQgdGhlIHNpdGUgLmNzdiBmaWxlIGZyb20gdGhlIFtvbmxpbmUgbWFwIG9mIE5FT04gc2l0ZXNdKGh0dHBzOi8vd3d3Lm5lb25zY2llbmNlLm9yZy9maWVsZC1zaXRlcy9maWVsZC1zaXRlcy1tYXAvbGlzdCkuIFJlYWQgaW4gdGhpcyBmaWxlLiBJcyB0aGUgYHNmYCBvYmplY3QgcHJvZHVjZWQgYnkgdGhpcyBmaWxlIGlkZW50aWNhbCB0byB0aGUgc2l0ZXMgc2hhcGVmaWxlIHdlIHVzZWQgYWJvdmU/IFVzZSBSIGNvZGUgdG8gc2hvdyBpZiBhbmQgaG93IHRoZXkgYXJlIGRpZmZlcmVudC4gDQoNCiAgMi4gQWRkIHByb3BlcnRpZXMgdG8gYG5lb25fc2l0ZXNgIHRoYXQgaW5jbHVkZToNCiAgKyB0aGUgbnVtYmVyIG9mIG90aGVyIHNpdGVzIGNvLWxvY2F0ZWQgaW4gdGhlIHNhbWUgRG9tYWluLg0KICArIGlmIGEgc2l0ZSBpcyBhcXVhdGljIG9yIHRlcnJlc3RyaWFsLg0KICArIGlmIGEgc2l0ZSBpcyBsb2NhdGVkIGluIHRoZSB0cm9waWNzLg0KDQoqKioNCg0KIyBQbG90dGluZyBWZWN0b3IgRGF0YQ0KDQpVc2UgYGdlb21fc2ZgIGluIGBnZ3Bsb3QyYCB0byBwbG90IGFueSBgc2ZgIG9iamVjdC4gIA0KYGBge3J9DQpnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IG5lb25fc2l0ZXMsIHNpemUgPSAyLCBzaGFwZSA9IDIxLCBmaWxsID0gInJlZCIpICsgdGhlbWVfYncoKQ0KYGBgDQoNCkxpa2UgcG9pbnRzLCBwb2x5Z29ucyBjYW4gYmUgcmVhZCBpbiB3aXRoIGBzdF9yZWFkYC4gSGVyZSB3ZSByZWFkIGluIHRoZSBbTkVPTiBkb21haW5zXShodHRwczovL3d3dy5uZW9uc2NpZW5jZS5vcmcvZGF0YS9uZW9uLWRhdGEtcG9ydGFsL3NwYXRpYWwtZGF0YS1tYXBzKSBzaGFwZWZpbGUgdGhhdCBnaXZlIHRoZSBvdXRsaW5lcyBvZiB0aGUgcmVnaW9ucyB0aGF0IE5FT04gdXNlZCB0byBjaG9vc2UgdGhlaXIgc2l0ZXMuIFNlZSBbaGVyZSBmb3IgYSB2aWRlbyB0aGF0IGRlc2NyaWJlcyB0aGUgc3RhdGlzdGljYWwgcHJvY2VzcyB1c2VkXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PTZKb0lfOFQ1a3o0KS4gIA0KTGlrZSBmb3IgdGhlIE5FT04gc2l0ZXMgc2hhcGVmaWxlIHVzZWQgYWJvdmUsIHlvdSBoYXZlIHRvIGRvd25sb2FkIHRoZSBbemlwIGZpbGVdKGh0dHBzOi8vd3d3Lm5lb25zY2llbmNlLm9yZy9zaXRlcy9kZWZhdWx0L2ZpbGVzL05FT05Eb21haW5zXzAuemlwKSBhbmQgZXh0cmFjdCB0aGUgZG9tYWlucyBzaGFwZWZpbGUgdG8geW91ciB3b3JraW5nIGRpcmVjdG9yeS4gDQoNCmBgYHtyIGxvYWQgTkVPTiBEb21haW5zIHNoYXBlZmlsZX0NCm5lb25fZG9tcyA8LSBzdF9yZWFkKCJORU9ORG9tYWluc18wL05FT05fRG9tYWlucy5zaHAiLCBhc190aWJibGUgPSBUUlVFLCBxdWlldCA9IFRSVUUpDQpgYGANCiAgDQpMZXQncyBzZWUgd2hhdCBpcyBpbiBgbmVvbl9kb21zYA0KYGBge3IgdGhlIGRvbWFpbnMgc2hhcGVmaWxlfQ0KbmVvbl9kb21zDQpgYGANCg0KSW4gdGhpcyBvYmplY3QsIHRoZXJlIGFyZSBtdWx0aXBsZSBwb2x5Z29ucyBhbmQgdGhlIGBEb21haW5OYW1lYCB2YXJpYWJsZSBwcm92aWRlcyB0aGUgbmFtZSBvZiB0aGUgZG9tYWluLiBUaGUgc2ltcGxlc3Qgd2F5IHRvIG1ha2UgYSBtYXAgb2YgYHNmYCBvYmplY3RzIGlzIHRvIHVzZSB0aGUgYGdlb21fc2ZgIGZ1bmN0aW9uIGluIGBnZ3Bsb3QyYC4gV2UgY2FuIG1hcCBhbmQgdGhlbiBsYWJlbCB0aGUgZG9tYWlucyB3aXRoIHRoZSBmb2xsb3dpbmcgY29kZToNCg0KYGBge3IgbWFwcGluZyBkb21haW5zLCBjYWNoZT1UUlVFfQ0KbmVvbl9kb21zICU+JQ0KICBnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGFlcyhmaWxsID0gRG9tYWluTmFtZSkpICsgDQogIGdlb21fc2YoZGF0YSA9IG5lb25fc2l0ZXMpICsgDQogIGdlb21fc2ZfbGFiZWwoYWVzKGxhYmVsID0gRG9tYWluTmFtZSksIHNpemUgPSAxLjgpICsNCiAgbGFicyh0aXRsZSA9ICJORU9OIHNpdGVzIGFuZCBsYWJlbGVkIGRvbWFpbnMiKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQpgYGANCiANCldlIHBsb3R0ZWQgYm90aCB0aGUgc2l0ZXMgYW5kIHRoZSBwb2x5Z29ucyBhbmQgd2UgdXNlIGBnZW9tX3NmX2xhYmVsc2AgdG8gbGFiZWwgZWFjaCBvZiB0aGUgZG9tYWlucyB3aXRoIHRoZWlyIG5hbWUuIFRoZSBmdW5jdGlvbiBgZ2VvbV9zZl90ZXh0YCBwcm92aWRlcyBzaW1pbGFyIGZ1bmN0aW9uYWxpdHkuIFRoZXJlIGlzIGFsc28gYSB3YXkgdG8gbWFrZSB0aGUgbGFiZWxzIGJldHRlciBzcGFjZWQgdXNpbmdgZ2dyZXBlbGAgZm9yIGxhYmVsaW5nIGFzIGRlc2NyaWJlZCBbaGVyZS5dKGh0dHBzOi8veXV0YW5pLnJiaW5kLmlvL3Bvc3QvZ2VvbS1zZi10ZXh0LWFuZC1nZW9tLXNmLWxhYmVsLWFyZS1jb21pbmcvKQ0KDQpgYGB7ciBnZ3JlcGVsIG1hcHBpbmcgZG9tYWlucywgY2FjaGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZ2dyZXBlbCkNCg0KbmVvbl9kb21zICU+JQ0KICBnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGFlcyhmaWxsID0gRG9tYWluTmFtZSkpICsNCiAgZ2VvbV9sYWJlbF9yZXBlbCggYWVzKGxhYmVsID0gRG9tYWluTmFtZSwgZ2VvbWV0cnkgPSBnZW9tZXRyeSksDQogICAgICAgICAgICAgICAgICAgIHN0YXQgPSAic2ZfY29vcmRpbmF0ZXMiLA0KICAgICAgICAgICAgICAgICAgICBtaW4uc2VnbWVudC5sZW5ndGggPSAwLA0KICAgICAgICAgICAgICAgICAgICBzaXplID0gMS44KSArDQogIGxhYnModGl0bGUgPSAiVXNlIGdncmVwZWwgdG8gbWFrZSBwb3BvdXQgYW5ub3RhdGlvbnMiKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCmBgYA0KDQojIyBFeGVyY2lzZXMNCjEuIENvbXBhcmUgdGhlIGZ1bmN0aW9ucyBgZ2VvbV9zZmAgYW5kIGBnZW9tX3BvaW50YCBieSBwbG90dGluZyB0aGUgTkVPTiBzaXRlcyBkYXRhIChpLmUuLCBgbmVvbl9zaXRlc2AgdGhhdCB3ZSBtYWRlIGFib3ZlKS4gRG8gdGhlIHR3byBtYXBzIGxvb2sgdGhlIHNhbWU/IEhvdyBjYW4geW91IHByb2R1Y2UgYSBtYXAgd2l0aCBgZ2VvbV9wb2ludGAgdGhhdCBsb29rcyBiZXR0ZXI/IENhbiB5b3UgdXNlIHRoZSBzYW1lIHRlY2huaXF1ZSB3aXRoIGBnZW9tX3NmYD8gV2hhdCBlcnJvcnMgZG8geW91IGdldD8NCg0KMi4gQ2hhbmdlIHRoZSBwcm9qZWN0aW9uIG9mIG9uZSBvZiB0aGUgYWJvdmUgbWFwcyBpbnRvIGFuIGVxdWFsIGFyZWEgcHJvamVjdGlvbi4gIA0KDQojIFBsb3R0aW5nIHdpdGggQmFzZSBNYXBzDQoNClNvIGZhciB3ZSBoYXZlIGp1c3QgYmVlbiBwbG90dGluZyB2ZWN0b3IgZGF0YSB0aGF0IHdlIHN1cHBsaWVkIHRvIFIsIGJ1dCBzb21ldGltZXMgeW91IHdpbGwgd2FudCB0byBwbG90IHlvdXIgdmVjdG9yIGRhdGEgb250byBhIGJhc2UgbWFwLCBsaWtlIHRoZSBvdXRsaW5lIG9mIHRoZSBVU0EuIFtOYXR1cmFsIEVhcnRoXShodHRwczovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS8pIGlzIGFuIG9wZW4gYWNjZXNzIGRhdGFiYXNlIG9mIGdsb2JhbCBzcGF0aWFsIGRhdGEuIEl0IGFsbG93cyB5b3UgdG8gZWFzaWx5IG1ha2UgYmFzZSBtYXBzIG9uIHdoaWNoIHRvIHBsb3QgeW91ciBkYXRhLiBUaGUgIHBhY2thZ2VzIHlvdSBuZWVkIGFyZSBgcm5hdHVyYWxlYXJ0aGAgYW5kIGBybmF0dXJhbGVhcnRoZGF0YWAuICANCg0KIyMgR2xvYmFsIGJhc2UgbWFwDQoNCmBgYHtyIG1ha2UgYSBnbG9iYWwgYmFzZW1hcCwgY2FjaGU9VFJVRX0NCmxpYnJhcnkocm5hdHVyYWxlYXJ0aCkNCmxpYnJhcnkocm5hdHVyYWxlYXJ0aGRhdGEpDQoNCmdsb2JhbF9iYXNlbWFwIDwtIG5lX2NvdW50cmllcyhzY2FsZSA9ICJzbWFsbCIsIHJldHVybmNsYXNzID0gInNmIikNCg0KZ2dwbG90KGRhdGEgPSBnbG9iYWxfYmFzZW1hcCkgKyANCiAgZ2VvbV9zZihmaWxsID0gIndoaXRlIikgKw0KICBsYWJzKHRpdGxlID0gIldvcmxkIG1hcCBmcm9tIE5hdHVyYWwgRWFydGggKGxvdyByZXNvbHV0aW9uKSIpDQpgYGANCg0KVGhlIGBuZV9jb3VudHJpZXNgIGZ1bmN0aW9uIGZyb20gYHJuYXR1cmFsZWFydGhgIHJldHVybnMgY291bnRyeSBwb2x5Z29ucy4gVGhlIGByZXR1cm5jbGFzc2AgYXJndW1lbnQganVzdCBzYXlzIHRvIHJldHVybiBhbiBgc2ZgIG9iamVjdC4gVGhlIGBzY2FsZWAgYXJndW1lbnQgcHJvdmlkZXMgdGhlIG1hcCByZXNvbHV0aW9uLCB0aGVyZSBhcmUgYHNtYWxsYCwgYG1lZGl1bWAgYW5kIGBsYXJnZWAgcmVzb2x1dGlvbiBiYXNlIG1hcHMuIEZvciB0aGUgbGFyZ2UgcmVzb2x1dGlvbiB5b3Ugd2lsbCBuZWVkIHRvIGluc3RhbGwgYHJuYXR1cmFsZWFydGhoaXJlc2AgYW5kIHlvdSB3aWxsIGJlIHByb21wdGVkIHRvIGRvIHNvIHdoZW4geW91IHNldCBgc2NhbGUgPSAibGFyZ2UiYCBpZiB5b3UgaGF2ZSBub3QgYWxyZWFkeSBpbnN0YWxsZWQgdGhlIHBhY2thZ2UuIFRvIG9idGFpbiBldmVuIGhpZ2hlciByZXNvbHV0aW9uIGJhc2UgbWFwcywgdGhlbiBzZWUgdGhlIFtHQURNXShodHRwczovL2dhZG0ub3JnLykgUiBwYWNrYWdlIFtgR0FETVRvb2xzYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0dBRE1Ub29scy9pbmRleC5odG1sKS4NCg0KDQpUbyB6b29tIGluIG9uIGFuIGFyZWEsIHVzZSBgY29vcmRfc2ZgLCB3aGljaCBpcyB0aGUgZGVmYXVsdCBgY29vcmRfYCBmdW5jdGlvbiB0aGF0IGBnZW9tX3NmYCB1c2VzIHRvIHBsb3QgZGF0YS4gU2VlIHRoZSBSNERTIGNoYXB0ZXIgb24gW2Nvb3JkaW5hdGUgc3lzdGVtc10oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9kYXRhLXZpc3VhbGlzYXRpb24uaHRtbCNjb29yZGluYXRlLXN5c3RlbXMpLg0KDQpgYGB7ciB6b29tIGluLCBjYWNoZT1UUlVFIH0NCmdncGxvdChkYXRhID0gZ2xvYmFsX2Jhc2VtYXApICsgDQogIGdlb21fc2YoZmlsbCA9ICJ3aGl0ZSIpICsgDQogIGdlb21fc2YoZGF0YSA9IG5lb25fc2l0ZXMpICsNCiAgY29vcmRfc2YoeGxpbSA9IGMoLTkwLCAtNTApLCB5bGltID0gYygxMCwgNTApLCBleHBhbmQgPSBGQUxTRSkgKyANCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImxpZ2h0Ymx1ZSIpKSArIA0KICBsYWJzKHRpdGxlID0gIlpvb20gaW4gd2l0aCB4bGltIGFuZCB5bGltIikNCmBgYA0KTm90aWNlIGhvdyB3aXRoIHRoaXMgbG93IHJlc29sdXRpb24gbWFwLCBtYW55IG9mIHRoZSBzbWFsbGVyIENhcmliYmVhbiBJc2xhbmRzIGFyZSBtaXNzaW5nLiAgDQoNCiMjIExvd2VyIGFkbWluaXN0cmF0aW9uIGxldmVscw0KDQpUaGUgZ2xvYmUgY2FuIGJlIHBhcnRpdGlvbmVkIGludG8gaGllcmFyY2hpY2FsIFtnZW9wb2xpdGljYWwgYWRtaW5pc3RyYXRpdmUgYm91bmRhcmllc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQWRtaW5pc3RyYXRpdmVfZGl2aXNpb24pLiBgcm5hdHVyYWxlYXJ0aGAgcHJvdmlkZXMgZGF0YSBhdCBjb3VudHJ5IGFuZCBzdGF0ZSBsZXZlbCBib3VuZGFyaWVzLiBUbyBpbGx1c3RyYXRlIHRoZXNlIGxldmVscywgZmlyc3QgbGV0J3MgZ2V0IGEgYmFzZSBtYXAgZm9yIGFuIGluZGl2aWR1YWwgY291bnRyeS4NCg0KYGBge3IgZ2V0IFVTQSwgY2FjaGU9VFJVRX0NCnVzYV9iYXNlbWFwIDwtIG5lX2NvdW50cmllcyhjb3VudHJ5ID0gInVuaXRlZCBzdGF0ZXMgb2YgYW1lcmljYSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gIm1lZGl1bSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybmNsYXNzID0gInNmIikNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB1c2FfYmFzZW1hcCkgKw0KICBjb29yZF9zZih4bGltID0gYygtMTc1LCAtNjApKSArIA0KICB0aGVtZV9kYXJrKCkgKw0KICBsYWJzKHRpdGxlID0gIlUuUy5BLiIpDQpgYGANCg0KVG8gZ2V0IGEgYmFzZS1tYXAgb2YgaW5kaXZpZHVhbCBzdGF0ZXMgdXNlIHRoZSBgbmVfc3RhdGVzYCBmdW5jdGlvbi4gVGhlIGZpcnN0IHRpbWUgdGhhdCB5b3UgcnVuIHRoZSBjb2RlIGJlbG93LCB5b3Ugd2lsbCBiZSBhc2tlZCB0byBpbnN0YWxsIHRoZSBgcm5hdHVyYWxlYXJ0aGhpcmVzYCBwYWNrYWdlLg0KDQpgYGB7ciBnZXQgc3RhdGUgZGF0YSwgY2FjaGU9VFJVRX0NCnN0YXRlX2Jhc2VtYXAgPC0gbmVfc3RhdGVzKGNvdW50cnkgPSAidW5pdGVkIHN0YXRlcyBvZiBhbWVyaWNhIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm5jbGFzcyA9ICJzZiIpDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHN0YXRlX2Jhc2VtYXApICsNCiAgY29vcmRfc2YoeGxpbSA9IGMoLTE3NSwgLTYwKSkgKw0KICB0aGVtZV9idygpICsgDQogIGxhYnModGl0bGUgPSAiVS5TLkEuIFN0YXRlcyIpDQpgYGANCkJ1dCBQdWVydG8gUmljbyBpcyBub3QgaW5jbHVkZWQgaW4gdGhpcyBiYXNlIG1hcCwgYW5kIE5FT04gaGFzIGEgc2l0ZSBpbiBQdWVydG8gUmljby4gIA0KYGBge3IgZ2V0IHB1ZXJ0byByaWNvLCBjYWNoZT1UUlVFfQ0KcHJfYmFzZW1hcCA8LSBuZV9zdGF0ZXMoY291bnRyeT0icHVlcnRvIHJpY28iLCByZXR1cm5jbGFzcyA9ICJzZiIpDQoNCiMgQ29tYmluZSB0aGUgbWFwcw0Kc3RhdGVfcHJfYmFzZW1hcD1yYmluZChzdGF0ZV9iYXNlbWFwLCBwcl9iYXNlbWFwKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHN0YXRlX3ByX2Jhc2VtYXApICsNCiAgY29vcmRfc2YoeGxpbSA9IGMoLTE3NSwgLTYwKSkgKw0KICB0aGVtZV9idygpICsgDQogIGxhYnModGl0bGUgPSAiVS5TLkEuIFN0YXRlcyArIFB1ZXJ0byBSaWNvIikNCmBgYA0KDQpXaGF0IGFib3V0IGV2ZW4gbG93ZXIgYWRtaW5pc3RyYXRpb24gYmFzZSBtYXBzPyBGb3IgVS5TLiBjb3VudHkgc2NhbGUgZGF0YSwgdXNlIHRoZSBgdGlncmlzYCBwYWNrYWdlLiBUaGlzIHBhY2thZ2UgZG93bmxvYWRzIGNvdW50eSAoYW5kIG90aGVyKSBkYXRhIGZyb20gdGhlIFtHZW9ncmFwaHkgUHJvZ3JhbSBvZiB0aGUgVS5TLiBDZW5zdXMgQnVyZWF1XShodHRwczovL3d3dy5jZW5zdXMuZ292L3Byb2dyYW1zLXN1cnZleXMvZ2VvZ3JhcGh5Lmh0bWwpLiBUbyBkb3dubG9hZCBjb3VudHkgZGF0YSB1c2UgdGhlIGBjb3VudGllc2AgZnVuY3Rpb24uDQoNCmBgYHtyIGNvdW50eSBiYXNlbWFwLCBjYWNoZT1UUlVFLCBlY2hvID0gRkFMU0UsIGluY2x1ZGUgPSBUUlVFfQ0KbGlicmFyeSh0aWdyaXMpDQpjb3VudHlfYmFzZW1hcCA8LSBjb3VudGllcyhjYiA9IFRSVUUpICU+JSBzdF9hc19zZigpICMgY2IgPSBUUlVFIGZvciBhIHNtYWxsZXIgZG93bmxvYWRlZCBmaWxlDQpgYGANCg0KYGBge3IgcHJpbnQgYmFzZW1hcH0NCmNvdW50eV9iYXNlbWFwDQpgYGANCg0KYGBge3IgbWFwIHRoZSBjb3VudGllcyBkYXRhLCBjYWNoZSA9IFRSVUV9DQpjb3VudHlfcGxvdCA8LSBnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGRhdGEgPSBjb3VudHlfYmFzZW1hcCkgKw0KICBsYWJzKHRpdGxlID0gIg0KICAgICAgIFUuUy5BLiBjb3VudGllcyIpIA0KDQpjb3VudHlfcGxvdCArIGNvb3JkX3NmKHhsaW0gPSBjKC0xNzUsIC02MCksIHlsaW0gPSBjKDE2LCA3MykpDQpgYGANCg0KT24gdGhpcyBtYXAgeW91IGNhbiBwbG90IG90aGVyIGxheWVycyBsaWtlIHRoZSBgbmVvbl9zaXRlc2AuDQoNCmBgYHtyIHNpdGVzIG9uIGNvdW50aWVzLCBjYWNoZT1UUlVFfQ0KY291bnR5X3Bsb3QgKyANCiAgZ2VvbV9zZihkYXRhID0gbmVvbl9zaXRlcywgc2l6ZSA9IDIsIHNoYXBlID0gMjEsIGZpbGwgPSAicmVkIikgKyANCiAgY29vcmRfc2YoeGxpbSA9IGMoLTE3NSwgLTYwKSwgeWxpbSA9IGMoMTYsIDczKSkNCmBgYA0KDQpJZiB5b3Ugd2FudCB0byBqdXN0IGdldCB0aGUgY291bnRpZXMgZm9yIG9uZSBzdGF0ZSwgdGhlbiB5b3UgaGF2ZSB0byB3b3JrIHdpdGggdGhlIGBTVEFURUZQYCBwcm9wZXJ0eSBpbiB0aGUgYGNvdW50eV9iYXNlbWFwYCBvYmplY3QuIFRoaXMgcHJvcGVydHkgcHJvdmlkZXMgYSB1bmlxdWUgbnVtZXJpYyBjb2RlIGNhbGxlZCBhIFtGSVBTIGNvZGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZJUFNfY291bnR5X2NvZGUpIGZvciBlYWNoIHN0YXRlLiBUbyBmaW5kIHRoZSBzdGF0ZSB5b3Ugd2FudCwgdXNlIGBsb29rdXBfY29kZWAuICBMZXQncyBsb29rIGF0IENvbG9yYWRvLCB3aGljaCBhcyB3ZSBzYXcgYWJvdmUsIGhhcyA3IE5FT04gc2l0ZXMsIHRoZSBzZWNvbmQgbW9zdCBvZiBhbnkgc3RhdGUuIEl0IGlzIGFsc28gd2hlcmUgdGhlIE5FT04gaGVhZHF1YXJ0ZXJzIGlzIGxvY2F0ZWQuDQoNCg0KYGBge3IgcGxvdCBQQSBjb3VudGllcywgY2FjaGU9VFJVRX0NCg0KbG9va3VwX2NvZGUoImNvbG9yYWRvIikNCg0KY291bnR5X2Jhc2VtYXAgJT4lIA0KICBmaWx0ZXIoU1RBVEVGUCA9PSAnMDgnKSAlPiUgDQogICAgZ2dwbG90KCkgKw0KICAgICAgZ2VvbV9zZigpICsNCiAgICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcihuZW9uX3NpdGVzLCBzdGF0ZU5hbWUgPT0gIkNvbG9yYWRvIiksIA0KICAgICAgICAgICAgICBhZXMoZmlsbCA9IHNpdGVUeXBlKSwgc2hhcGUgPSAyMSwgc2l6ZSA9IDMsIGFscGhhID0gLjcpICsNCiAgbGFicyh0aXRsZSA9ICJDb2xvcmFkbyBhbmQgaXRzIE5FT04gc2l0ZXMiKSArIA0KICB0aGVtZV9idygpICsgDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoIHNpemUgPSAxLCBhbHBoYSA9IDEpKSkNCmBgYA0KDQoNCiMjIE1hcCBkYXRhIHZhbHVlcw0KDQpPbmNlIHlvdSBhcmUgYWJsZSB0byBtYXAgcG9seWdvbnMgYW5kIHBvaW50cywgeW91IHdpbGwgd2FudCB0byBkaXNwbGF5IHlvdXIgZGF0YSBvbiB0aGUgbWFwLiBKdXN0IGxpa2UgYW55IGBnZ3Bsb3RgLCB5b3UgY2FuIHNldCBhZXN0aGV0aWNzIHRvIHBsb3QgZGF0YS4gSGVyZSwgd2UgdXNlIGBmaWxsYCB0byBjb2xvciBjb3VudHJpZXMgYWNjb3JkaW5nIHRvIGluY29tZSBncm91cGluZy4gVGhlIGNvdW50cmllcyBkYXRhIHNldCBmcm9tIGBybmF0dXJhbGVhcnRoYCBhbHJlYWR5IGNvbnRhaW5zIGEgbG90IG9mIGRhdGEgYXQgdGhlIHBlciBjb3VudHJ5IHNjYWxlLCBhbmQgaW5jb21lIGdyb3VwIGlzIG9uZSBpbmNsdWRlZCB2YXJpYWJsZS4NCg0KYGBge3IgZmlsbCBwb2x5Z29ucywgY2FjaGU9VFJVRX0NCg0KZ2dwbG90KGRhdGEgPSBnbG9iYWxfYmFzZW1hcCkgKyANCiAgZ2VvbV9zZihhZXMoZmlsbCA9IGluY29tZV9ncnApKSArDQogIGxhYnModGl0bGUgPSAiSW5jb21lIGdyb3VwcyIpDQoNCmBgYA0KDQoNCiMjIEV4ZXJjaXNlcw0KDQoxLiBDYWxjdWxhdGUgdGhlIG51bWJlciBvZiBORU9OIHNpdGVzIGluIGVhY2ggc3RhdGUsIGpvaW4gdGhlIGRhdGEgdG8gYHN0YXRlX2Jhc2VtYXBgIGFuZCB1c2UgYGZpbGxgIHRvIHBsb3QgdGhvc2UgdmFsdWVzIG9uIGEgbWFwLg0KDQoyLiBDaG9vc2Ugb25lIG9mIHRoZSBORU9OIHRheGEgdGhhdCB3ZSB1c2VkIGluIG91ciBtaWR0ZXJtIGFuZCBtYWtlIGEgbWFwIG9mIHNwZWNpZXMgcmljaG5lc3MgYWNyb3NzIHRoZSBkb21haW5zLg0KDQojIEZ1cnRoZXIgUmVhZGluZw0KDQpUbyBsZWFybiBtb3JlIHRoYW4gd2hhdCBpcyBwcmVzZW50ZWQgaGVyZSwgc2VlOg0KDQotIFtzZiBwYWNrYWdlIHZpZ25ldHRlc10oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2luZGV4Lmh0bWwjYmxvZ3MtcHJlc2VudGF0aW9ucy12aWduZXR0ZXMtc3Atc2Ytd2lraSkNCi0gW3Itc3BhdGlhbF0oaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy8pIHR1dG9yaWFscyBvbiBbZHJhd2luZyBiZXV0aWZ1bCBtYXBzXShodHRwczovL3d3dy5yLXNwYXRpYWwub3JnL3IvMjAxOC8xMC8yNS9nZ3Bsb3QyLXNmLmh0bWwpLiANCi0gW0dlb2NvbXB1dGF0aW9uIHdpdGggUl0oaHR0cHM6Ly9nZW9jb21wci5yb2JpbmxvdmVsYWNlLm5ldC8pDQoNClRoaXMgdGVhY2hpbmcgbW9kZWwgYm9ycm93ZWQgZnJvbSB0aGVzZSBzb3VyY2VzLiBCZSBzdXJlIHRvIGNpdGUgdGhlIFtzZiBwYWNrYWdlXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vc2YvYXV0aG9ycy5odG1sKSBpbiBhbnkgcHJvZHVjdCB5b3UgcHJvZHVjZSB1c2luZyB0aGUgc2YgcGFja2FnZS4NCg0KIyBBY2tub3dsZWRnbWVudHMNCg0KW0RvbmFsIE8nTGVhcnldKGh0dHA6Ly93d3cuZG9uYWxvbGVhcnkuY29tLykgYSBzY2llbmNlIGVkdWNhdGlvbiBzcGVjaWFsaXN0IGF0IE5FT04gbWFkZSBjb21tZW50cyBhbmQgZWRpdHMgdG8gdGhpcyBtb2R1bGUuIFRoYW5rIHlvdSENCg0K