This document is intended to help you quickly learn how to perform basic manipulation and visualization of spatial data that can be represented using simple features. We will primarily focus on using the sf package (Pebesma 2022) for our analysis.

An accompanying YouTube playlist that walks through this document is available by following the link here. I have also included direct, relevant video links throughout the file (e.g., immediately after the the relevant heading). An html version of this document can be downloaded to your current working direction (run getwd() in the Console to see where this is) by running the following command:

download.file("https://raw.githubusercontent.com/jfrench/DataWrangleViz/master/05-simple-features-wrangling-visualizing.nb.html", "05-simple-features-wrangling-visualizing.nb.html")

The raw R Markdown code used to generate the html file can be obtained by toggling the “Code” box in the upper-right corner of the html file and selecting “Download Rmd”.

Wrangling and visualizing simple features spatial data in R (Video: YouTube, Panopto)

Spatial data can take many forms.

For a data scientist, spatial data is usually data for which each observation is geographic region or location.

Simple Features (officially Simple Feature Access) is a set of standards that specify a common storage and access model of geographic feature made of mostly two-dimensional geometries (point, line, polygon, multi-point, multi-line, etc.) used by geographic information systems. It is formalized by both the Open Geospatial Consortium (OGC) and the International Organization for Standardization (ISO). Wikipedia

Simple features are comprised of:

  1. A geometry object (e.g., a point, a polygon, a line, etc.)
  2. Attribute data associated with the geometry object (e.g., the temperature at a certain time on a certain day, the number of new cases of a disease in a county in the last month).

Simple features

As stated in the vignette to the sf package:

Features have a geometry describing where on Earth the feature is located, and they have attributes, which describe other properties. The geometry of a tree can be the delineation of its crown, of its stem, or the point indicating its centre. Other properties may include its height, color, diameter at breast height at a particular date, and so on.

Just to clarify:

  • The geometry of an observation describes where the object is located.
    • A geometry can be a point, a polygon (which is really just an ordered collection of points), or something more complicated.
  • The attributes of a geometry object are what data scientists would usually consider the “data”.

All geometries are made of points, which can be combined to create more and more complex objects.

A point can be 2-, 3-, or 4-dimensional.

The most common kinds of points are two-dimensional and are described using a 2-dimensional set of coordinates (X and Y), e.g., longitude and latitude or easting and northing.

  • 2-dimensional points are referred to as XY points.

A 3-dimensional point could include the X and Y coordinates as well as the altitude of the 2-dimensional point in 3-dimensional space.

  • The Z coordinate is another dimension denoting altitude of the point.
  • Combining a Z coordinate with an XY coordinate results in a 3-dimensional XYZ point.

Alternatively, a 3-dimensional point could include some other measure associated with the point.

  • The M coordinate is another dimension denoting some measure associated with the point.
  • It’s pretty rare, but examples include time or measurement error.
  • Combining an M coordinate with an XY coordinate results in a 3-dimensional XYM point.

Combining X, Y, Z, and M coordinates results in a 4-dimensional point.

Packages and tools for working with simple features (Video: YouTube, Panopto)

In what follows, we will use several packages.

  • The sf package (Pebesma 2022) is an R package for working with simple features (sf objects) both in terms of the geometry objects and the associated attributes.
    • The sf package can import, manipulate, and plot sf objects.
    • The sf package is intended to supersede the sp package (Pebesma and Bivand 2022), which is an older R package for working with spatial data.
    • Since the sp package is being superseded by the sf package, I recommend working with the sf package for spatial data analysis moving forward.

Because of how sf objects are represented in R, simple features (once constructed or imported) can be manipulated and plotted by many other well-known packages.

  • The dplyr package (Wickham et al. 2022) can be used to manipulate sf objects, and we will utilize it as certain times.
  • The ggplot2 package (Wickham 2016) can also be used to plot sf objects, which we will demonstrate.

Choosing a color palette to represent the attributes of your simple features is very important.

  • The grDevices package included with base R provides many color palettes.
    • The traditional palettes are rainbow, heat.colors, terrain.colors, topo.colors, and cm.colors.
    • The hcl.colors palette function was added in R 4.0.0 and provides numerous excellent color palettes.
    • Running the Examples in the documentation found in ?grDevices::hcl.colors will show you examples of the available palettes through grDevices.
    • We provide color swatches for many palettes at the end of this tutorial (without the code).
  • The colorspace package (Ihaka et al. 2022) provides many of the same HCL palettes available through the hcl.colors palette, but it also provides convenient functions for accessing this in ggplot2.

We load all of these packages below, with the exception of colorspace, as it has a coords function that masks (i.e., replaces) a needed function in the sf package. So we will access the necessary colorspace function using the :: approach.

# library(colorspace)
library(sf)
library(dplyr)
library(ggplot2)

Simple feature geometry objects (Video: YouTube, Panopto)

The most common simple feature geometry objects used by data scientists are:

geometry function description
POINT sf::st_point A geometry containing a single point
POLYGON sf::st_polygon A geometry with a sequence of points that form a closed ring that doesn’t intersect itself. Multiple rings form outer rings and holes within the polygon.

We go through the process of creating and plotting these geometries below.

# create an XY point
(p1 = st_point(c(1,2)))
POINT (1 2)
# create an XYZ point
(p2 = st_point(c(1,2,3)))
POINT Z (1 2 3)
# the points look the same when plotted in two dimensions
plot(p1)

plot(p2)

# create a ring (connected set of points)
outer <- matrix(c(0, 0, 10, 0, 10, 10, 0, 10, 0, 0), ncol = 2, byrow = TRUE)
# create additional rings for holes
hole1 <- matrix(c(1, 1, 1, 2, 2, 2, 2, 1, 1, 1), ncol = 2, byrow = TRUE)
hole2 <- matrix(c(5, 5, 5, 6, 6, 6, 6, 5, 5, 5), ncol = 2, byrow = TRUE)
# combine rings into a list
pts <- list(outer, hole1, hole2)
# turn list of rings into a polygon
(pl1 <- st_polygon(pts))
POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (5 5, 5 6, 6 6, 6 5, 5 5))
# plot polygon
plot(pl1)

The other common geometry objects are:

geometry function description
LINESTRING sf::st_linestring A sequence of points that is connected with straight, non-self intersecting lines
MULTIPOINT sf::st_multipoint A set of points
MULTIPOLYGON sf::st_multipolygon A set of polygons
MULTILINESTRING sf::st_multipoint A set of line strings
GEOMETRYCOLLECTION sf::st_geometrycollection A set of the other geometries (except itself)

We provide examples of creating and plotting these geometries below.

# create a matrix of multiple points
(pts <- matrix(rnorm(10), ncol = 2))
           [,1]       [,2]
[1,] -0.2942841  0.4042901
[2,] -0.5631947 -2.8190104
[3,]  0.3776016 -1.3146976
[4,] -0.1430829  1.4013844
[5,]  0.2235321 -1.9659875
# convert matrix of points to linestring
(ls1 <- st_linestring(pts))
LINESTRING (-0.2942841 0.4042901, -0.5631947 -2.81901, 0.3776016 -1.314698, -0.1430829 1.401384, 0.2235321 -1.965988)
plot(ls1)

# convert matrix fo points to multipoints
(mp1 <- st_multipoint(pts))
MULTIPOINT ((-0.2942841 0.4042901), (-0.5631947 -2.81901), (0.3776016 -1.314698), (-0.1430829 1.401384), (0.2235321 -1.965988))
plot(mp1)

# create multipolygons
pol1 <- list(outer, hole1, hole2)
pol2 <- list(outer + 24)
mp <- list(pol1, pol2)
(mpl1 <- st_multipolygon(mp))
MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (5 5, 5 6, 6 6, 6 5, 5 5)), ((24 24, 34 24, 34 34, 24 34, 24 24)))
plot(mpl1, axes = TRUE)

# create a multilinestring
(pts2 <- matrix(rnorm(6), ncol = 2))
         [,1]       [,2]
[1,] 1.189007 -1.0965091
[2,] 1.721922 -0.7875281
[3,] 0.918795  0.6693628
(ml1 <- st_multilinestring(list(pts, pts2)))
MULTILINESTRING ((-0.2942841 0.4042901, -0.5631947 -2.81901, 0.3776016 -1.314698, -0.1430829 1.401384, 0.2235321 -1.965988), (1.189007 -1.096509, 1.721922 -0.7875281, 0.918795 0.6693628))
plot(ml1)

# create a geometry collection
(gc <- st_geometrycollection(list(p1, pl1, ls1)))
GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (5 5, 5 6, 6 6, 6 5, 5 5)), LINESTRING (-0.2942841 0.4042901, -0.5631947 -2.81901, 0.3776016 -1.314698, -0.1430829 1.401384, 0.2235321 -1.965988))
plot(gc, col = "grey")

How do I need what type of geometry I need?

  • Often, this is determined automatically when you read in shapefiles (which we’ll discuss later).
  • Attributes observed at a single location require a POINT.
  • Most regions can be represented by a POLYGON.
  • Complicated objects made of regions, e.g., an island chain like Hawaii, require MULTIPOLYGONS.
  • The other types are for more complicated objects.
  • There are 10 other rarer geometry types that we do not discuss (CIRCULARSTRING, CURVE, SURFACE, TRIANGLE, COMPOUNDCURVE, CURVEPOLYGON, MULTICURVE, MULTISURFACE, POLYHEDRALSURFACE, TIN). You can learn about them through the additional resources provided at the end of this document.

Coordinate reference systems

A coordinate reference system (CRS) must be provided in order to place a point on the earth’s surface.

When your import a geometry object from file, the CRS will often be provided.

  • The WGS84 CRS is often the default for longitude/latitude coordinates.

Sometimes, in order to combine geometry objects, you must specify the CRS of a geometry object.

There are many CRSs. A CRS is often used because it is known to have a certain desirable property. A discussion of CRSs is beyond the scope of this tutorial. And when you do need to know about CRSs, it will probably be so specific that a general discussion won’t help. However, here are a few references related to CRSs that may provide a bit more detail:

Constructing simple feature (sf) objects (Video: YouTube, Panopto)

An sf object is a data.frame that has a simple feature geometry list column (i.e., a column of geometry objects). So you can work with sf objects similar to how you would work with a data.frame object, though it may have different default behaviors because it has been extended to an sf object.

  • The geometry-list column contains the geometry object for each observation.
  • The other columns of the sf object contain the attributes of the geometry object.
  • Each row of the sf object is a simple feature. Alternatively, each observation is a simple feature.

In practice, sf objects are often initially created by reading in a shapefile. However, particularly for POINT observations, you may need to create an sf object manually.

In what follows, we create sf objects for POINT geometry objects and POLYGON geometry objects.

  • We can use the same previously discussed functions (e.g., sf::st_point, sf::st_polygon, etc.) to create the geometry objects.
  • The sf::st_sfc function is used to create a geometry list column.
  • The sf::st_sf function is used to combined a data.frame of attributes with the geometry list column.
# create POINT objects
pt1 <- st_point(c(0, 0))
pt2 <- st_point(c(0, 1))
pt3 <- st_point(c(1, 1))
# create geometry list column
glc1 <- st_sfc(list(pt1, pt2, pt3))
# is glc1 a list?
is.list(glc1)
[1] TRUE
# what class is glc1
class(glc1)
[1] "sfc_POINT" "sfc"      
# create attribute data.frame with temperature and rainfall attributes
df1 <- data.frame(temperature = c(10, 11, 10.4), rainfall = c(1, 1.3, 0.9))
# create sf object
sf1 <- st_sf(df1, geometry = glc1)
# class of sf1
class(sf1)
[1] "sf"         "data.frame"
# plot sf1
plot(sf1["temperature"], pch = 20, axes = TRUE)

# create polygon objects
outer1 <- matrix(c(0, 0, 1, 1, 0, 0, 1, 1, 0, 0), ncol = 2)
pl1 <- st_polygon(list(outer1))
# outer2 is outer1 shifted 1 unit to the right
outer2 <- matrix(c(1, 1, 2, 2, 1, 0, 1, 1, 0, 0), ncol = 2)
pl2 <- st_polygon(list(outer2))
# create geometry list columns
glc2 <- st_sfc(list(pl1, pl2))
# what class is glc2
class(glc2)
[1] "sfc_POLYGON" "sfc"        
# create second sf object (only include an attribute column and geometry)
sf2 <- st_sf(cases = c(10, 12), geometry = glc2)
# class of sf2
class(sf2)
[1] "sf"         "data.frame"
# plot sf2
plot(sf2)

# what happens if you combine geometry types?
glc3 <- st_sfc(list(pt1, pt2, pt3, pl1, pl2))
# what class is glc3
class(glc3)
[1] "sfc_GEOMETRY" "sfc"         
# create sf3
sf3 <- st_sf(attribute1 = rnorm(5), geometry = glc3)
# class of sf3
class(sf3)
[1] "sf"         "data.frame"
# plot sf3
plot(sf3, pch = 20, pal = topo.colors)

# plot sf3 with even breaks
plot(sf3, pch = 20, pal = topo.colors, breaks = seq(min(sf3$attribute1), max(sf3$attribute1), length = 12))

Importing shapefiles as sf objects (Video: YouTube, Panopto)

A data scientist is most likely to work with sf objects obtained from importing a shapefile into R.

ArcGIS defines a shapefile as:

A shapefile is a simple, nontopological format for storing the geometric location and attribute information of geographic features. Geographic features in a shapefile can be represented by points, lines, or polygons (areas). What is a shapefile?

Generally, a shapefile can be imported into R as an sf object.

  • Each row is an observation related to a geometry object.
  • There should be a geometry column that contains the geometry object for each observation (this is the geometry list column).
  • The other columns will represent the attributes associated with each geometry object.

Shapefiles are widely available for describing many different spatial objects like counties, census tracts, zip code tabulation areas (ZCTAs), states, countries, etc. There are even shapefiles that can be used to describe other spatial objects like roads, rivers, lakes, etc.

  • A simple web search with appropriate terms will usually bring up a website with a relevant shapefile for your data.
  • e.g., search “usa shapefile” brings up a Census bureau page with many different shapefiles for different areas and characteristics of the United States.
  • A “shapefile” is often a zipped folder that contains many files inside it.
  • The .shp file is usually the file you want to import.

We can import that shapefile into R as an sf object using the st_read function.

  • Typically, we want to provide the shp file to the dsn argument (data source name) of st_read.

In this case, I have downloaded the cb_2018_us_state_20m.zip containing a shapefile of the U.S. states. I have unzipped this file into the cb_2018_us_state_20m folder in the data folder in my current working directory.

  • The current working directory is the location to which R currently reads or saves files.
  • The can learn what your current working directory is by running getwd() in the Console.
  • You can change your current working directory by running setwd("path") in the Console, where path is the directory path you want to set as your working directory.

In our case, we want to read the file cb_2018_us_state_20m.shp in the cb_2018_us_state_20m folder in the data folder of our current working directory. We can run the following command to import the desired shapefile.

  • ./” indicates the current directory.
  • The dsn argument “./data/cb_2018_us_state_20m/cb_2018_us_state_20m.shp tells R to look for the cb_state_20m.shp file in the file path ./data/cb_state_2018_us_state_20m.

For simplicity, I have uploaded cb_2018_us_state_20m.zip to my GitHub repository. Prior to importing the the .shp file, running the download and unzip commands should download and unzip the necessary folder into your working directory so that you can run the same code to import the shapefile.

# download.file("https://github.com/jfrench/DataWrangleViz/raw/master/data/ca-county-boundaries.zip", destfile = "./ca-county-boundaries.zip", method = "auto")
# unzip("cb_2018_us_state_20m.zip", exdir = "./data/cb_2018_us_state_20m")
us_sf <- st_read(dsn = "./data/cb_2018_us_state_20m/cb_2018_us_state_20m.shp")
Reading layer `cb_2018_us_state_20m' from data source 
  `C:\Users\frencjos\Documents\OneDrive - The University of Colorado Denver\teaching\math3376\github_dwv\data\cb_2018_us_state_20m\cb_2018_us_state_20m.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 52 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -179.1743 ymin: 17.91377 xmax: 179.7739 ymax: 71.35256
Geodetic CRS:  NAD83

The st_read provides helpful information automatically when run. In this case, we learn:

  • We imported an sf object with 52 features (observations).
  • The imported sf object has 9 attributes (columns).
  • The features appear to be the simple feature geometry MULTIPOLGYON.
  • The dimension is XY, so we are working with 2-dimensional data.
  • The bounding box tells us the largest and smallest y-coordinates.
  • The CRS is NAD83.

We use the class function to see the classes of us_sf.

class(us_sf)
[1] "sf"         "data.frame"

us_sf is a data.frame that has been extended to the sf class.

Let’s use the str function to learn more about the structure of us_sf.

str(us_sf)
Classes ‘sf’ and 'data.frame':  52 obs. of  10 variables:
 $ STATEFP : chr  "24" "19" "10" "39" ...
 $ STATENS : chr  "01714934" "01779785" "01779781" "01085497" ...
 $ AFFGEOID: chr  "0400000US24" "0400000US19" "0400000US10" "0400000US39" ...
 $ GEOID   : chr  "24" "19" "10" "39" ...
 $ STUSPS  : chr  "MD" "IA" "DE" "OH" ...
 $ NAME    : chr  "Maryland" "Iowa" "Delaware" "Ohio" ...
 $ LSAD    : chr  "00" "00" "00" "00" ...
 $ ALAND   : num  2.52e+10 1.45e+11 5.05e+09 1.06e+11 1.16e+11 ...
 $ AWATER  : num  6.98e+09 1.08e+09 1.40e+09 1.03e+10 3.39e+09 ...
 $ geometry:sfc_MULTIPOLYGON of length 52; first list element: List of 2
  ..$ :List of 1
  .. ..$ : num [1:6, 1:2] -76 -76 -76 -76 -76 ...
  ..$ :List of 1
  .. ..$ : num [1:261, 1:2] -79.5 -79.5 -79.5 -79.4 -79 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA
  ..- attr(*, "names")= chr [1:9] "STATEFP" "STATENS" "AFFGEOID" "GEOID" ...

us_sf has 52 rows and 10 columns. The (most) useful columns are:

  • STUSPS: the state abbreviation
  • NAME: the state name
  • ALAND: the land area of each state
  • AWATER: the water area of each state

The geometry column is the simple feature geometry list column and contains the geometry object for each observation.

Wrangling sf objects (Video: YouTube, Panopto)

An sf object is a type of data frame, similar to how a tibble is a type of data frame. Both classes extend the data.frame class.

We can select columns of the us_sf sf object in the following ways:

us_sf$STUSPS
 [1] "MD" "IA" "DE" "OH" "PA" "NE" "WA" "PR" "AL" "AR" "NM" "TX" "CA" "KY"
[15] "GA" "WI" "OR" "MO" "VA" "TN" "LA" "NY" "MI" "ID" "FL" "AK" "IL" "MT"
[29] "MN" "IN" "MA" "KS" "NV" "VT" "CT" "NJ" "DC" "NC" "UT" "ND" "SC" "MS"
[43] "CO" "SD" "OK" "WY" "WV" "ME" "HI" "NH" "AZ" "RI"
us_sf[,5]
Simple feature collection with 52 features and 1 field
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -179.1743 ymin: 17.91377 xmax: 179.7739 ymax: 71.35256
Geodetic CRS:  NAD83
First 10 features:
   STUSPS                       geometry
1      MD MULTIPOLYGON (((-76.04621 3...
2      IA MULTIPOLYGON (((-96.62187 4...
3      DE MULTIPOLYGON (((-75.77379 3...
4      OH MULTIPOLYGON (((-82.86334 4...
5      PA MULTIPOLYGON (((-80.51989 4...
6      NE MULTIPOLYGON (((-104.0531 4...
7      WA MULTIPOLYGON (((-123.2371 4...
8      PR MULTIPOLYGON (((-65.34207 1...
9      AL MULTIPOLYGON (((-88.46866 3...
10     AR MULTIPOLYGON (((-94.61792 3...
us_sf[,"STUSPS"]
Simple feature collection with 52 features and 1 field
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -179.1743 ymin: 17.91377 xmax: 179.7739 ymax: 71.35256
Geodetic CRS:  NAD83
First 10 features:
   STUSPS                       geometry
1      MD MULTIPOLYGON (((-76.04621 3...
2      IA MULTIPOLYGON (((-96.62187 4...
3      DE MULTIPOLYGON (((-75.77379 3...
4      OH MULTIPOLYGON (((-82.86334 4...
5      PA MULTIPOLYGON (((-80.51989 4...
6      NE MULTIPOLYGON (((-104.0531 4...
7      WA MULTIPOLYGON (((-123.2371 4...
8      PR MULTIPOLYGON (((-65.34207 1...
9      AL MULTIPOLYGON (((-88.46866 3...
10     AR MULTIPOLYGON (((-94.61792 3...
us_sf["STUSPS"]
Simple feature collection with 52 features and 1 field
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -179.1743 ymin: 17.91377 xmax: 179.7739 ymax: 71.35256
Geodetic CRS:  NAD83
First 10 features:
   STUSPS                       geometry
1      MD MULTIPOLYGON (((-76.04621 3...
2      IA MULTIPOLYGON (((-96.62187 4...
3      DE MULTIPOLYGON (((-75.77379 3...
4      OH MULTIPOLYGON (((-82.86334 4...
5      PA MULTIPOLYGON (((-80.51989 4...
6      NE MULTIPOLYGON (((-104.0531 4...
7      WA MULTIPOLYGON (((-123.2371 4...
8      PR MULTIPOLYGON (((-65.34207 1...
9      AL MULTIPOLYGON (((-88.46866 3...
10     AR MULTIPOLYGON (((-94.61792 3...

Note that the $ operator extracts the column from us_sf (returning a character vector), while the other choices subset us_sf and remain sf objects (i.e., the geometry list column is retained).

We can select rows in a similar fashion.

us_sf[2:3,]
Simple feature collection with 2 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -96.6247 ymin: 38.45101 xmax: -75.04894 ymax: 43.50088
Geodetic CRS:  NAD83
  STATEFP  STATENS    AFFGEOID GEOID STUSPS     NAME LSAD        ALAND
2      19 01779785 0400000US19    19     IA     Iowa   00 144661267977
3      10 01779781 0400000US10    10     DE Delaware   00   5045925646
      AWATER                       geometry
2 1084180812 MULTIPOLYGON (((-96.62187 4...
3 1399985648 MULTIPOLYGON (((-75.77379 3...
us_sf[us_sf$STUSPS == "CO",]
Simple feature collection with 1 feature and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -109.0601 ymin: 36.99243 xmax: -102.0419 ymax: 41.00307
Geodetic CRS:  NAD83
   STATEFP  STATENS    AFFGEOID GEOID STUSPS     NAME LSAD        ALAND
43      08 01779779 0400000US08    08     CO Colorado   00 268422891711
       AWATER                       geometry
43 1181621593 MULTIPOLYGON (((-109.06 38....
us_sf[startsWith(us_sf$STUSPS, "C"),]
Simple feature collection with 3 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -124.4096 ymin: 32.53416 xmax: -71.78936 ymax: 42.04964
Geodetic CRS:  NAD83
   STATEFP  STATENS    AFFGEOID GEOID STUSPS        NAME LSAD        ALAND
13      06 01779778 0400000US06    06     CA  California   00 403503931312
35      09 01779780 0400000US09    09     CT Connecticut   00  12542497068
43      08 01779779 0400000US08    08     CO    Colorado   00 268422891711
        AWATER                       geometry
13 20463871877 MULTIPOLYGON (((-118.594 33...
35  1815617571 MULTIPOLYGON (((-73.69594 4...
43  1181621593 MULTIPOLYGON (((-109.06 38....
us_sf %>% filter(startsWith(us_sf$STUSPS, "C"))
Simple feature collection with 3 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -124.4096 ymin: 32.53416 xmax: -71.78936 ymax: 42.04964
Geodetic CRS:  NAD83
  STATEFP  STATENS    AFFGEOID GEOID STUSPS        NAME LSAD        ALAND
1      06 01779778 0400000US06    06     CA  California   00 403503931312
2      09 01779780 0400000US09    09     CT Connecticut   00  12542497068
3      08 01779779 0400000US08    08     CO    Colorado   00 268422891711
       AWATER                       geometry
1 20463871877 MULTIPOLYGON (((-118.594 33...
2  1815617571 MULTIPOLYGON (((-73.69594 4...
3  1181621593 MULTIPOLYGON (((-109.06 38....

Note that startsWith is a base R function that finds the elements of a character vector that start with a certain set of characters while start_with is a dplyr function that is used to select columns of a data frame based on a pattern.

A really neat feature of sf objects is that can use a spatial object to select rows. Let’s extract the “Colorado” row from us_sf.

co <- us_sf[us_sf$NAME == "Colorado",]
class(co)
[1] "sf"         "data.frame"

If we pass the co object as the row argument inside the square brackets, [], then the rows of us_sf with geometry objects that intersect the co geometry object will be returned

(co_intersects <- us_sf[co,])
Simple feature collection with 8 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -114.8142 ymin: 31.33224 xmax: -94.43151 ymax: 45.0059
Geodetic CRS:  NAD83
   STATEFP  STATENS    AFFGEOID GEOID STUSPS       NAME LSAD        ALAND
6       31 01779792 0400000US31    31     NE   Nebraska   00 198956658395
11      35 00897535 0400000US35    35     NM New Mexico   00 314196306401
32      20 00481813 0400000US20    20     KS     Kansas   00 211755344060
39      49 01455989 0400000US49    49     UT       Utah   00 212886221680
43      08 01779779 0400000US08    08     CO   Colorado   00 268422891711
45      40 01102857 0400000US40    40     OK   Oklahoma   00 177662925723
46      56 01779807 0400000US56    56     WY    Wyoming   00 251458544898
51      04 01779777 0400000US04    04     AZ    Arizona   00 294198551143
       AWATER                       geometry
6  1371829134 MULTIPOLYGON (((-104.0531 4...
11  728776523 MULTIPOLYGON (((-109.0492 3...
32 1344141205 MULTIPOLYGON (((-102.0517 4...
39 6998824394 MULTIPOLYGON (((-114.0525 3...
43 1181621593 MULTIPOLYGON (((-109.06 38....
45 3374587997 MULTIPOLYGON (((-103.0025 3...
46 1867670745 MULTIPOLYGON (((-111.0569 4...
51 1027337603 MULTIPOLYGON (((-114.7997 3...
plot(co_intersects["NAME"])

plot(st_geometry(co_intersects))

caco <- us_sf[us_sf$NAME == "Colorado" | 
                us_sf$NAME == "California",]
(caco_intersects <- us_sf[caco,])
Simple feature collection with 11 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -124.5524 ymin: 31.33224 xmax: -94.43151 ymax: 46.26913
Geodetic CRS:  NAD83
First 10 features:
   STATEFP  STATENS    AFFGEOID GEOID STUSPS       NAME LSAD        ALAND
6       31 01779792 0400000US31    31     NE   Nebraska   00 198956658395
11      35 00897535 0400000US35    35     NM New Mexico   00 314196306401
13      06 01779778 0400000US06    06     CA California   00 403503931312
17      41 01155107 0400000US41    41     OR     Oregon   00 248606993270
32      20 00481813 0400000US20    20     KS     Kansas   00 211755344060
33      32 01779793 0400000US32    32     NV     Nevada   00 284329506470
39      49 01455989 0400000US49    49     UT       Utah   00 212886221680
43      08 01779779 0400000US08    08     CO   Colorado   00 268422891711
45      40 01102857 0400000US40    40     OK   Oklahoma   00 177662925723
46      56 01779807 0400000US56    56     WY    Wyoming   00 251458544898
        AWATER                       geometry
6   1371829134 MULTIPOLYGON (((-104.0531 4...
11   728776523 MULTIPOLYGON (((-109.0492 3...
13 20463871877 MULTIPOLYGON (((-118.594 33...
17  6192386935 MULTIPOLYGON (((-124.5524 4...
32  1344141205 MULTIPOLYGON (((-102.0517 4...
33  2047206072 MULTIPOLYGON (((-120.0048 3...
39  6998824394 MULTIPOLYGON (((-114.0525 3...
43  1181621593 MULTIPOLYGON (((-109.06 38....
45  3374587997 MULTIPOLYGON (((-103.0025 3...
46  1867670745 MULTIPOLYGON (((-111.0569 4...
plot(st_geometry(caco_intersects))

The base merge and dplyr *_join functions can be used to merge a data frame with an sf object.

Let’s access a COVID-19 related data frame available in the bayesutils package, which can be installed from GitHub.

# install.packages("remotes")
# remotes::install_github("jfrench/bayesutils")
data("covid_20210307", package = "bayesutils")

The state_abb column of covid_20210307 has the state abbreviations and matches the STUSPS column of us_sf.

We use the base merge function to unite these two objects into a new object, covid_us.

covid_us <- merge(us_sf, covid_20210307,
                  by.x = "STUSPS", by.y = "state_abb")
head(covid_us)
Simple feature collection with 6 features and 17 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -179.1743 ymin: 30.22831 xmax: 179.7739 ymax: 71.35256
Geodetic CRS:  NAD83
  STUSPS STATEFP  STATENS    AFFGEOID GEOID       NAME LSAD        ALAND
1     AK      02 01785533 0400000US02    02     Alaska   00 1.478840e+12
2     AL      01 01779775 0400000US01    01    Alabama   00 1.311740e+11
3     AR      05 00068085 0400000US05    05   Arkansas   00 1.347689e+11
4     AZ      04 01779777 0400000US04    04    Arizona   00 2.941986e+11
5     CA      06 01779778 0400000US06    06 California   00 4.035039e+11
6     CO      08 01779779 0400000US08    08   Colorado   00 2.684229e+11
        AWATER state_name deaths   cases population income   hs   bs
1 245481577452     Alaska    305   56886     738516  35455 91.0 27.9
2   4593327154    Alabama  10148  499819    4864680  25734 82.1 21.9
3   2962859592   Arkansas   5319  324818    2990671  25359 82.9 19.5
4   1027337603    Arizona  16328  826454    6946685  29348 85.6 25.9
5  20463871877 California  54124 3501394   39148760  31086 80.7 30.1
6   1181621593   Colorado   5989  436602    5531141  35053 89.7 36.4
  vote_diff_2020                       geometry
1          -10.5 MULTIPOLYGON (((179.4813 51...
2          -25.8 MULTIPOLYGON (((-88.46866 3...
3          -28.4 MULTIPOLYGON (((-94.61792 3...
4            0.3 MULTIPOLYGON (((-114.7997 3...
5           29.8 MULTIPOLYGON (((-118.594 33...
6           13.9 MULTIPOLYGON (((-109.06 38....

Alternatively, we could have used a dplyr *_join function. We’ll use full_join. Note the special syntax in the by argument to address the fact that we want to merge the rows based on different columns in the data frames.

covid_us2 <- full_join(us_sf, covid_20210307, by = c("STUSPS" = "state_abb"))
head(covid_us2)
Simple feature collection with 6 features and 17 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -104.0532 ymin: 37.91685 xmax: -74.69491 ymax: 43.50088
Geodetic CRS:  NAD83
  STATEFP  STATENS    AFFGEOID GEOID STUSPS         NAME LSAD        ALAND
1      24 01714934 0400000US24    24     MD     Maryland   00  25151100280
2      19 01779785 0400000US19    19     IA         Iowa   00 144661267977
3      10 01779781 0400000US10    10     DE     Delaware   00   5045925646
4      39 01085497 0400000US39    39     OH         Ohio   00 105828882568
5      42 01779798 0400000US42    42     PA Pennsylvania   00 115884442321
6      31 01779792 0400000US31    31     NE     Nebraska   00 198956658395
       AWATER   state_name deaths  cases population income   hs   bs
1  6979966958     Maryland   7955 387319    6003435  39682 88.1 36.1
2  1084180812         Iowa   5558 282384    3132499  31509 90.6 24.9
3  1399985648     Delaware   1473  88354     949495  32928 87.7 27.8
4 10268850702         Ohio  17656 978471   11641879  29602 88.1 24.6
5  3394589990 Pennsylvania  24349 948643   12791181  30681 88.4 27.1
6  1371829134     Nebraska   2113 203026    1904760  31609 90.4 28.6
  vote_diff_2020                       geometry
1           34.1 MULTIPOLYGON (((-76.04621 3...
2           -8.4 MULTIPOLYGON (((-96.62187 4...
3           19.3 MULTIPOLYGON (((-75.77379 3...
4           -8.2 MULTIPOLYGON (((-82.86334 4...
5            1.2 MULTIPOLYGON (((-80.51989 4...
6          -19.6 MULTIPOLYGON (((-104.0531 4...

If a new row is added to the sf object without a corresponding geometry, then an empty geometry object is added for that row.

Plotting simple features

The power of the sf package is the ability to easily create plots of spatial data.

Plotting sfc objects (Video: YouTube, Panopto)

To simply plot the geometry list column of an sf object, you can use:

  • st_geometry to extract the list column of simple feature geometries from the sf object (this will be an object of class sfc).
  • plot to plot the sfc object.
plot(st_geometry(us_sf))

Alternatively, you can directly extract the sfc component of the sf object using $, then plot the sfc object.

plot(us_sf$geometry)

You can easily change aspects of the plotted sfc object (or an sf) object using the standard arguments:

  • axes can be set to TRUE to show the axes
  • xlab and ylab will change the axis labels
  • xlim and ylim can be used to constrain the plotting regions.
    • Note that “W” longitude values are indicated using negative numbers, while “E” longitude values are positive numbers.
    • Note that “N” latitude values in the northern hemisphere are positive numbers. “S” latitude values in the southern hemisphere are negative numbers.

Each geometry has specific arguments that the user can change (consider looking at the documentation for ?sf::plot.sf for details). In this case, we can change the fill color of the MULTIPOLYGON objects using the color argument and the border using the border argument.

plot(us_sf$geometry, axes = TRUE,
     xlab = "longitude", ylab = "latitude",
     col = "grey", border = "blue",
     xlim = c(-125, -75),
     ylim = c(22, 50))

You can change the colors of the individual geometry objects with a little creativity. Let’s color all the “C states” (California, Colorado, Connecticut) a little differently than the other states.

# determine indices of C states
c_states <- startsWith(us_sf$STUSPS, "C")
# create a character vector of replicated "grey"
# values matching the number of rows in us_sf
mycol <- rep("grey", nrow(us_sf))
#change "grey" to "orange" for the c_states indices
mycol[c_states] <- "orange"
# create a character vector of replicated "yellow"
# values matching the number of rows in us_sf
myborder <- rep("yellow", nrow(us_sf))
#change "yellow" to "blue" for the c_states indices
myborder[c_states] <- "blue"
# create the customized plot
plot(st_geometry(us_sf), col = mycol, border = myborder, xlim = c(-125, -75),
     ylim = c(22, 50))

Customize color for subset of sf object (Video: YouTube, Panopto)

Let’s look at a different approach to coloring a subset of the sf object differently from the rest of the sf object.

# draw all geometries
plot(st_geometry(us_sf), col = "grey", border = "yellow",
     xlim = c(-125, -75), ylim = c(22, 50))
# plot subset of all geometries with different colors
# add = TRUE overlays new drawing on current graphic
plot(st_geometry(us_sf)[c_states],
     col = "orange", border = "blue",
     add = TRUE)

Plotting attributes of an sf object (Video: YouTube, Panopto)

By default, use you can use the plot function to plot all the attributes of an sf object. In general, this isn’t very useful.

plot(covid_us)
Warning: plotting the first 10 out of 17 attributes; use max.plot = 17 to plot all

More likely, you will want to plot a single attribute (variable), which can be done by subsetting that variable in the sf object and plotting the subsetted object.

Let’s plot the land area of the states, excluding Alaska and Hawaii. First, we exclude the Alaska and Hawaii rows (and save the filtered object).

covid_us <- covid_us %>% filter(!is.element(STUSPS, c("AK", "HI")))
plot(covid_us["ALAND"])

Land area is directly related to the area of the state. It would be interesting to visualize the states that have the greatest percentage of water. Let’s create a new variable in covid_us that computes the percentage of the state that is water. We’ll then plot this variable (excluding Alaska and Hawaii)

covid_us <- covid_us %>% mutate(prop_water = AWATER/(AWATER + ALAND))
plot(covid_us["prop_water"])

Not surprisingly, coastal states and states on the Great Lakes have the highest percentage of water.

We can use the following commands to identify the states with the 6 largest proportions of water.

covid_us %>% slice_max(prop_water, n = 6) %>% select(NAME, prop_water)
Simple feature collection with 6 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -90.41814 ymin: 24.49813 xmax: -69.92826 ymax: 48.21065
Geodetic CRS:  NAD83
           NAME prop_water                       geometry
1      Michigan  0.4147358 MULTIPOLYGON (((-84.61622 4...
2  Rhode Island  0.3307977 MULTIPOLYGON (((-71.63147 4...
3 Massachusetts  0.2608345 MULTIPOLYGON (((-70.27553 4...
4      Maryland  0.2172342 MULTIPOLYGON (((-76.04621 3...
5      Delaware  0.2171897 MULTIPOLYGON (((-75.77379 3...
6       Florida  0.1841410 MULTIPOLYGON (((-81.81169 2...

We can change the number of breaks in our color bar via the nbreaks.

We can change the colors in our color bar by specifying the pal argument.

  • The pal argument takes a function that, when given n, the number of desired colors, returns a vector of colors.
  • The hcl.colors, rainbow, heat.colors, terrain.colors, topo.colors, and cm.colors are all color palettes in base R that can be used to change the colors palettes.
  • The hcl.colors palette is particularly good, as it includes color palettes viridis and cividis (corrected viridis) that are particularly well-suited to displaying colors that are color-blind and can be understood when printed in black and white.
  • The colorspace package also has a wide variety of palettes you might consider.

However, the hcl.colors function has a palette argument to specify the desired palette. To use an hcl.color palette when plotting an sf object, we need to build a custom palette that only requires the number of colors desired. We create those function for the viridis and cividis palettes below. We then see that it produces the desired results.

viridis_pal <- function(n) hcl.colors(n, palette = "viridis")
cividis_pal <- function(n) hcl.colors(n, palette = "cividis")
# dirty approach to see colors in the palette
image(matrix(0:4), col = viridis_pal(5))

image(matrix(0:4), col = cividis_pal(5))

Let’s do some analysis of the actual COVID-19 data. Let’s create a new column for death_rate_100k, which is the number of confirmed and probable COVID-19 deaths per 100,000 persons. Let’s display the death rate using the viridis palette and then the cividis palette.

covid_us <- covid_us %>% mutate(death_rate_100k = deaths/population*100000)
plot(covid_us["death_rate_100k"],
     nbreaks = 5,
     pal = viridis_pal)

plot(covid_us["death_rate_100k"],
     nbreaks = 5,
     pal = cividis_pal)

Plotting sf objects with ggplot2 (Video: YouTube, Panopto)

The ggplot2 package includes a geometry for sf objects, geom_sf, the is typically adequate for using ggplot2 to produce graphics for sf objects.

  • The sf object is passed as the data argument to the ggplot function.
  • geom_sf is the geometry of the data
  • The XY coordinates of the sf object are automatically mapped to x and y aesthetics.
  • The color, linetype, fill, etc., of the geometry objects can be changed by mapping an attribute of the sf object to the appropriate aesthetic.

If we only want to plot the geometry list column (i.e., the geometry objects) of each observation, then we don’t have to specify any aesthetics.

ggplot(covid_us) + geom_sf()

We will create a choropleth map of the covid_us data. * A choropleth map is a map of regions colored to indicate the level of some variable associated with the regions.

ggplot(covid_us) + geom_sf(aes(fill = death_rate_100k))

Changing the color palette for our fill aesthetic to a viridis palette.

ggplot(covid_us) + geom_sf(aes(fill = death_rate_100k)) +
  colorspace::scale_fill_continuous_sequential(palette = "viridis")

Change the color palette to viridis using ggplot2’s built-in function.

ggplot(covid_us) + geom_sf(aes(fill = death_rate_100k)) +
  scale_fill_viridis_c(option = "viridis")

Reverse the order of the colors.

ggplot(covid_us) + geom_sf(aes(fill = death_rate_100k)) +
  scale_fill_viridis_c(option = "viridis", direction = -1)

Use the cividis palette using the scale_fill_viridis function from the viridis package (Garnier 2021).

ggplot(covid_us) + geom_sf(aes(fill = death_rate_100k)) +
  viridis::scale_fill_viridis(option = "E")

Additional resources

sf help and tutorials from the package authors [https://r-spatial.github.io/sf/]

Geocomputation with R by Robin Lovelace [https://bookdown.org/robinlovelace/geocompr/]. This book covers tons of aspect of spatial data (not just from an R perspective, but general theory and concepts). This will help you to learn a lot more about representing and working with spatial data in general, not just working with it in R.

Spatial Data Science with applications in R by Edzer Pebesma and Roger Bivand [https://keen-swartz-3146c4.netlify.app/]. Also covers much much about representing spatial data than you probably ever thought possible! The authors are the main creators of the sf package.

Color palettes available with base R

References

Csárdi, Gábor, Jim Hester, Hadley Wickham, Winston Chang, Martin Morgan, and Dan Tenenbaum. 2021. Remotes: R Package Installation from Remote Repositories, Including GitHub. https://CRAN.R-project.org/package=remotes.
Garnier, Simon. 2021. Viridis: Colorblind-Friendly Color Maps for r. https://CRAN.R-project.org/package=viridis.
Ihaka, Ross, Paul Murrell, Kurt Hornik, Jason C. Fisher, Reto Stauffer, Claus O. Wilke, Claire D. McWhite, and Achim Zeileis. 2022. Colorspace: A Toolbox for Manipulating and Assessing Colors and Palettes. https://CRAN.R-project.org/package=colorspace.
Pebesma, Edzer. 2022. Sf: Simple Features for r. https://CRAN.R-project.org/package=sf.
Pebesma, Edzer, and Roger Bivand. 2022. Sp: Classes and Methods for Spatial Data. https://CRAN.R-project.org/package=sp.
Wickham, Hadley. 2016. Ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York. https://ggplot2.tidyverse.org.
Wickham, Hadley, Romain François, Lionel Henry, and Kirill Müller. 2022. Dplyr: A Grammar of Data Manipulation. https://CRAN.R-project.org/package=dplyr.
LS0tDQp0aXRsZTogIldyYW5nbGluZyBhbmQgdmlzdWFsaXppbmcgc2ltcGxlIGZlYXR1cmVzIHNwYXRpYWwgZGF0YSBpbiBSIg0KYXV0aG9yOiAiSm9zaHVhIEZyZW5jaCINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVZLSVtLSVkJylgIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQpiaWJsaW9ncmFwaHk6DQogIC0gZHd2LmJpYg0KICAtIHBhY2thZ2VzX3NmLmJpYg0KLS0tDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICB0aWR5ID0gVFJVRQ0KKQ0Ka25pdHI6OndyaXRlX2JpYihjKCJkcGx5ciIsICJ0aWJibGUiLCAidGlkeXIiLCAicGFsbWVycGVuZ3VpbnMiLCAibWFncml0dHIiLCAiZGF0YS50YWJsZSIsICJyZXNoYXBlIiwgInJlc2hhcGUyIiwgImRicGx5ciIsICJkdHBseXIiLCAic2YiLCAic3AiLCAiY29sb3JzcGFjZSIsICJyZW1vdGVzIiwgInZpcmlkaXMiKSwgZmlsZSA9ICJwYWNrYWdlc19zZi5iaWIiKQ0Kc2V0LnNlZWQoMTA0MDMpDQpgYGANCg0KVGhpcyBkb2N1bWVudCBpcyBpbnRlbmRlZCB0byBoZWxwIHlvdSBxdWlja2x5IGxlYXJuIGhvdyB0byBwZXJmb3JtIGJhc2ljIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXphdGlvbiBvZiBzcGF0aWFsIGRhdGEgdGhhdCBjYW4gYmUgcmVwcmVzZW50ZWQgdXNpbmcgc2ltcGxlIGZlYXR1cmVzLiBXZSB3aWxsIHByaW1hcmlseSBmb2N1cyBvbiB1c2luZyB0aGUgKipzZioqIHBhY2thZ2UgW0BSLXNmXSBmb3Igb3VyIGFuYWx5c2lzLg0KDQpBbiBhY2NvbXBhbnlpbmcgWW91VHViZSBwbGF5bGlzdCB0aGF0IHdhbGtzIHRocm91Z2ggdGhpcyBkb2N1bWVudCBpcyBhdmFpbGFibGUNCmJ5IGZvbGxvd2luZyB0aGUgbGluayBbaGVyZV0oaHR0cHM6Ly95b3V0dWJlLmNvbS9wbGF5bGlzdD9saXN0PVBMa3JKckxzN3hmYldqRDJycDNwSVY4NWxieS10UjNDbnUpLiBJIGhhdmUgYWxzbyBpbmNsdWRlZCBkaXJlY3QsIHJlbGV2YW50IHZpZGVvIGxpbmtzIHRocm91Z2hvdXQgdGhlIGZpbGUgKGUuZy4sIGltbWVkaWF0ZWx5IGFmdGVyIHRoZSB0aGUgcmVsZXZhbnQgaGVhZGluZykuIEFuIGh0bWwgdmVyc2lvbiBvZiB0aGlzIGRvY3VtZW50IGNhbiBiZSBkb3dubG9hZGVkIHRvIHlvdXIgY3VycmVudCB3b3JraW5nIGRpcmVjdGlvbiAocnVuIGBnZXR3ZCgpYCBpbiB0aGUgQ29uc29sZSB0byBzZWUgd2hlcmUgdGhpcyBpcykgYnkgcnVubmluZyB0aGUgZm9sbG93aW5nIGNvbW1hbmQ6DQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qZnJlbmNoL0RhdGFXcmFuZ2xlVml6L21hc3Rlci8wNS1zaW1wbGUtZmVhdHVyZXMtd3JhbmdsaW5nLXZpc3VhbGl6aW5nLm5iLmh0bWwiLCAiMDUtc2ltcGxlLWZlYXR1cmVzLXdyYW5nbGluZy12aXN1YWxpemluZy5uYi5odG1sIikNCmBgYA0KVGhlIHJhdyBSIE1hcmtkb3duIGNvZGUgdXNlZCB0byBnZW5lcmF0ZSB0aGUgaHRtbCBmaWxlIGNhbiBiZSBvYnRhaW5lZCBieSB0b2dnbGluZyB0aGUgIkNvZGUiIGJveCBpbiB0aGUgdXBwZXItcmlnaHQgY29ybmVyIG9mIHRoZSBodG1sIGZpbGUgYW5kIHNlbGVjdGluZyAiRG93bmxvYWQgUm1kIi4NCg0KDQojIFdyYW5nbGluZyBhbmQgdmlzdWFsaXppbmcgc2ltcGxlIGZlYXR1cmVzIHNwYXRpYWwgZGF0YSBpbiBSIChWaWRlbzogW1lvdVR1YmVdKGh0dHBzOi8veW91dHUuYmUvOUJkRkNtOE42dlEpLCBbUGFub3B0b10oaHR0cHM6Ly91Y2RlbnZlci5ob3N0ZWQucGFub3B0by5jb20vUGFub3B0by9QYWdlcy9WaWV3ZXIuYXNweD9pZD02NzUyYjNjZi1jMmQzLTQxNDQtODViNS1hZjMyMDE1NDkxNzgpKQ0KDQpTcGF0aWFsIGRhdGEgY2FuIHRha2UgbWFueSBmb3Jtcy4NCg0KRm9yIGEgZGF0YSBzY2llbnRpc3QsIHNwYXRpYWwgZGF0YSBpcyB1c3VhbGx5IGRhdGEgZm9yIHdoaWNoIGVhY2ggb2JzZXJ2YXRpb24gaXMgZ2VvZ3JhcGhpYyByZWdpb24gb3IgbG9jYXRpb24uDQoNCj4gKlNpbXBsZSBGZWF0dXJlcyogKG9mZmljaWFsbHkgKlNpbXBsZSBGZWF0dXJlIEFjY2VzcyopIGlzIGEgc2V0IG9mIHN0YW5kYXJkcyB0aGF0IHNwZWNpZnkgYSBjb21tb24gc3RvcmFnZSBhbmQgYWNjZXNzIG1vZGVsIG9mIGdlb2dyYXBoaWMgZmVhdHVyZSBtYWRlIG9mIG1vc3RseSB0d28tZGltZW5zaW9uYWwgZ2VvbWV0cmllcyAocG9pbnQsIGxpbmUsIHBvbHlnb24sIG11bHRpLXBvaW50LCBtdWx0aS1saW5lLCBldGMuKSB1c2VkIGJ5IGdlb2dyYXBoaWMgaW5mb3JtYXRpb24gc3lzdGVtcy4gSXQgaXMgZm9ybWFsaXplZCBieSBib3RoIHRoZSBPcGVuIEdlb3NwYXRpYWwgQ29uc29ydGl1bSAoT0dDKSBhbmQgdGhlIEludGVybmF0aW9uYWwgT3JnYW5pemF0aW9uIGZvciBTdGFuZGFyZGl6YXRpb24gKElTTykuIFtXaWtpcGVkaWFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NpbXBsZV9GZWF0dXJlcykNCg0KU2ltcGxlIGZlYXR1cmVzIGFyZSBjb21wcmlzZWQgb2Y6DQoNCjEuIEEgZ2VvbWV0cnkgb2JqZWN0IChlLmcuLCBhIHBvaW50LCBhIHBvbHlnb24sIGEgbGluZSwgZXRjLikgDQoyLiBBdHRyaWJ1dGUgZGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGdlb21ldHJ5IG9iamVjdCAoZS5nLiwgdGhlIHRlbXBlcmF0dXJlIGF0IGEgY2VydGFpbiB0aW1lIG9uIGEgY2VydGFpbiBkYXksIHRoZSBudW1iZXIgb2YgbmV3IGNhc2VzIG9mIGEgZGlzZWFzZSBpbiBhIGNvdW50eSBpbiB0aGUgbGFzdCBtb250aCkuDQoNCiMjIFNpbXBsZSBmZWF0dXJlcw0KDQpBcyBzdGF0ZWQgaW4gdGhlIHZpZ25ldHRlIHRvIHRoZSAqKnNmKiogcGFja2FnZToNCg0KPiBGZWF0dXJlcyBoYXZlIGEgZ2VvbWV0cnkgZGVzY3JpYmluZyB3aGVyZSBvbiBFYXJ0aCB0aGUgZmVhdHVyZSBpcyBsb2NhdGVkLCBhbmQgdGhleSBoYXZlIGF0dHJpYnV0ZXMsIHdoaWNoIGRlc2NyaWJlIG90aGVyIHByb3BlcnRpZXMuIFRoZSBnZW9tZXRyeSBvZiBhIHRyZWUgY2FuIGJlIHRoZSBkZWxpbmVhdGlvbiBvZiBpdHMgY3Jvd24sIG9mIGl0cyBzdGVtLCBvciB0aGUgcG9pbnQgaW5kaWNhdGluZyBpdHMgY2VudHJlLiBPdGhlciBwcm9wZXJ0aWVzIG1heSBpbmNsdWRlIGl0cyBoZWlnaHQsIGNvbG9yLCBkaWFtZXRlciBhdCBicmVhc3QgaGVpZ2h0IGF0IGEgcGFydGljdWxhciBkYXRlLCBhbmQgc28gb24uDQoNCkp1c3QgdG8gY2xhcmlmeToNCg0KKiBUaGUgKmdlb21ldHJ5KiBvZiBhbiBvYnNlcnZhdGlvbiBkZXNjcmliZXMgd2hlcmUgdGhlIG9iamVjdCBpcyBsb2NhdGVkLg0KICAqIEEgZ2VvbWV0cnkgY2FuIGJlIGEgcG9pbnQsIGEgcG9seWdvbiAod2hpY2ggaXMgcmVhbGx5IGp1c3QgYW4gb3JkZXJlZCBjb2xsZWN0aW9uIG9mIHBvaW50cyksIG9yIHNvbWV0aGluZyBtb3JlIGNvbXBsaWNhdGVkLg0KKiBUaGUgKmF0dHJpYnV0ZXMqIG9mIGEgZ2VvbWV0cnkgb2JqZWN0IGFyZSB3aGF0IGRhdGEgc2NpZW50aXN0cyB3b3VsZCB1c3VhbGx5IGNvbnNpZGVyIHRoZSAiZGF0YSIuDQoNCkFsbCBnZW9tZXRyaWVzIGFyZSBtYWRlIG9mIHBvaW50cywgd2hpY2ggY2FuIGJlIGNvbWJpbmVkIHRvIGNyZWF0ZSBtb3JlIGFuZCBtb3JlIGNvbXBsZXggb2JqZWN0cy4NCg0KQSBwb2ludCBjYW4gYmUgMi0sIDMtLCBvciA0LWRpbWVuc2lvbmFsLg0KDQpUaGUgbW9zdCBjb21tb24ga2luZHMgb2YgcG9pbnRzIGFyZSB0d28tZGltZW5zaW9uYWwgYW5kIGFyZSBkZXNjcmliZWQgdXNpbmcgYSAyLWRpbWVuc2lvbmFsIHNldCBvZiBjb29yZGluYXRlcyAoWCBhbmQgWSksIGUuZy4sIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgb3IgZWFzdGluZyBhbmQgbm9ydGhpbmcuDQogIA0KICAqIDItZGltZW5zaW9uYWwgcG9pbnRzIGFyZSByZWZlcnJlZCB0byBhcyBYWSBwb2ludHMuDQogIA0KQSAzLWRpbWVuc2lvbmFsIHBvaW50IGNvdWxkIGluY2x1ZGUgdGhlIFggYW5kIFkgY29vcmRpbmF0ZXMgYXMgd2VsbCBhcyB0aGUgYWx0aXR1ZGUgb2YgdGhlIDItZGltZW5zaW9uYWwgcG9pbnQgaW4gMy1kaW1lbnNpb25hbCBzcGFjZS4NCiAgDQogICogVGhlIFogY29vcmRpbmF0ZSBpcyBhbm90aGVyIGRpbWVuc2lvbiBkZW5vdGluZyBhbHRpdHVkZSBvZiB0aGUgcG9pbnQuDQogICogQ29tYmluaW5nIGEgWiBjb29yZGluYXRlIHdpdGggYW4gWFkgY29vcmRpbmF0ZSByZXN1bHRzIGluIGEgMy1kaW1lbnNpb25hbCBYWVogcG9pbnQuDQogIA0KQWx0ZXJuYXRpdmVseSwgYSAzLWRpbWVuc2lvbmFsIHBvaW50IGNvdWxkIGluY2x1ZGUgc29tZSBvdGhlciBtZWFzdXJlIGFzc29jaWF0ZWQgd2l0aCB0aGUgcG9pbnQuDQogIA0KICAqIFRoZSBNIGNvb3JkaW5hdGUgaXMgYW5vdGhlciBkaW1lbnNpb24gZGVub3Rpbmcgc29tZSBtZWFzdXJlIGFzc29jaWF0ZWQgd2l0aCB0aGUgcG9pbnQuDQogICogSXQncyBwcmV0dHkgcmFyZSwgYnV0IGV4YW1wbGVzIGluY2x1ZGUgdGltZSBvciBtZWFzdXJlbWVudCBlcnJvci4NCiAgKiBDb21iaW5pbmcgYW4gTSBjb29yZGluYXRlIHdpdGggYW4gWFkgY29vcmRpbmF0ZSByZXN1bHRzIGluIGEgMy1kaW1lbnNpb25hbCBYWU0gcG9pbnQuDQogIA0KQ29tYmluaW5nIFgsIFksIFosIGFuZCBNIGNvb3JkaW5hdGVzIHJlc3VsdHMgaW4gYSA0LWRpbWVuc2lvbmFsIHBvaW50Lg0KDQojIyBQYWNrYWdlcyBhbmQgdG9vbHMgZm9yIHdvcmtpbmcgd2l0aCBzaW1wbGUgZmVhdHVyZXMgKFZpZGVvOiBbWW91VHViZV0oaHR0cHM6Ly95b3V0dS5iZS91VDhtcjJWM1lQQSksIFtQYW5vcHRvXShodHRwczovL3VjZGVudmVyLmhvc3RlZC5wYW5vcHRvLmNvbS9QYW5vcHRvL1BhZ2VzL1ZpZXdlci5hc3B4P2lkPWY3YTc5ZTA0LTFhMTMtNGJjMS1hMzViLWFmMzIwMTU0Nzk5NykpDQoNCkluIHdoYXQgZm9sbG93cywgd2Ugd2lsbCB1c2Ugc2V2ZXJhbCBwYWNrYWdlcy4NCg0KKiBUaGUgKipzZioqIHBhY2thZ2UgW0BSLXNmXSBpcyBhbiBSIHBhY2thZ2UgZm9yIHdvcmtpbmcgd2l0aCBzaW1wbGUgZmVhdHVyZXMgKGBzZmAgb2JqZWN0cykgYm90aCBpbiB0ZXJtcyBvZiB0aGUgZ2VvbWV0cnkgb2JqZWN0cyBhbmQgdGhlIGFzc29jaWF0ZWQgYXR0cmlidXRlcy4NCiAgKiBUaGUgKipzZioqIHBhY2thZ2UgY2FuIGltcG9ydCwgbWFuaXB1bGF0ZSwgYW5kIHBsb3QgYHNmYCBvYmplY3RzLg0KICAqIFRoZSAqKnNmKiogcGFja2FnZSBpcyBpbnRlbmRlZCB0byBzdXBlcnNlZGUgdGhlICoqc3AqKiBwYWNrYWdlIFtAUi1zcF0sIHdoaWNoIGlzIGFuIG9sZGVyIFIgcGFja2FnZSBmb3Igd29ya2luZyB3aXRoIHNwYXRpYWwgZGF0YS4NCiAgKiBTaW5jZSB0aGUgKipzcCoqIHBhY2thZ2UgaXMgYmVpbmcgc3VwZXJzZWRlZCBieSB0aGUgKipzZioqIHBhY2thZ2UsIEkgcmVjb21tZW5kIHdvcmtpbmcgd2l0aCB0aGUgKipzZioqIHBhY2thZ2UgZm9yIHNwYXRpYWwgZGF0YSBhbmFseXNpcyBtb3ZpbmcgZm9yd2FyZC4NCiAgDQpCZWNhdXNlIG9mIGhvdyBgc2ZgIG9iamVjdHMgYXJlIHJlcHJlc2VudGVkIGluIFIsIHNpbXBsZSBmZWF0dXJlcyAob25jZSBjb25zdHJ1Y3RlZCBvciBpbXBvcnRlZCkgY2FuIGJlIG1hbmlwdWxhdGVkIGFuZCBwbG90dGVkIGJ5IG1hbnkgb3RoZXIgd2VsbC1rbm93biBwYWNrYWdlcy4NCg0KKiBUaGUgKipkcGx5cioqIHBhY2thZ2UgW0BSLWRwbHlyXSBjYW4gYmUgdXNlZCB0byBtYW5pcHVsYXRlIGBzZmAgb2JqZWN0cywgYW5kIHdlIHdpbGwgdXRpbGl6ZSBpdCBhcyBjZXJ0YWluIHRpbWVzLg0KKiBUaGUgKipnZ3Bsb3QyKiogcGFja2FnZSBbQGdncGxvdDIyMDE2XSBjYW4gYWxzbyBiZSB1c2VkIHRvIHBsb3QgYHNmYCBvYmplY3RzLCB3aGljaCB3ZSB3aWxsIGRlbW9uc3RyYXRlLg0KDQpDaG9vc2luZyBhIGNvbG9yIHBhbGV0dGUgdG8gcmVwcmVzZW50IHRoZSBhdHRyaWJ1dGVzIG9mIHlvdXIgc2ltcGxlIGZlYXR1cmVzIGlzIHZlcnkgaW1wb3J0YW50Lg0KDQoqIFRoZSAqKmdyRGV2aWNlcyoqIHBhY2thZ2UgaW5jbHVkZWQgd2l0aCAqKmJhc2UqKiBSIHByb3ZpZGVzICBtYW55IGNvbG9yIHBhbGV0dGVzLg0KICAqIFRoZSB0cmFkaXRpb25hbCBwYWxldHRlcyBhcmUgYHJhaW5ib3dgLCBgaGVhdC5jb2xvcnNgLCBgdGVycmFpbi5jb2xvcnNgLCBgdG9wby5jb2xvcnNgLCBhbmQgYGNtLmNvbG9yc2AuDQogICogVGhlIGBoY2wuY29sb3JzYCBwYWxldHRlIGZ1bmN0aW9uIHdhcyBhZGRlZCBpbiBSIDQuMC4wIGFuZCBwcm92aWRlcyBudW1lcm91cyBleGNlbGxlbnQgY29sb3IgcGFsZXR0ZXMuDQogICogUnVubmluZyB0aGUgRXhhbXBsZXMgaW4gdGhlIGRvY3VtZW50YXRpb24gZm91bmQgaW4gYD9nckRldmljZXM6OmhjbC5jb2xvcnNgIHdpbGwgc2hvdyB5b3UgZXhhbXBsZXMgb2YgdGhlIGF2YWlsYWJsZSBwYWxldHRlcyB0aHJvdWdoICoqZ3JEZXZpY2VzKiouDQogICogV2UgcHJvdmlkZSBjb2xvciBzd2F0Y2hlcyBmb3IgbWFueSBwYWxldHRlcyBhdCB0aGUgZW5kIG9mIHRoaXMgdHV0b3JpYWwgKHdpdGhvdXQgdGhlIGNvZGUpLg0KDQoqIFRoZSAqKmNvbG9yc3BhY2UqKiBwYWNrYWdlIFtAUi1jb2xvcnNwYWNlXSBwcm92aWRlcyBtYW55IG9mIHRoZSBzYW1lIEhDTCBwYWxldHRlcyBhdmFpbGFibGUgdGhyb3VnaCB0aGUgYGhjbC5jb2xvcnNgIHBhbGV0dGUsIGJ1dCBpdCBhbHNvIHByb3ZpZGVzIGNvbnZlbmllbnQgZnVuY3Rpb25zIGZvciBhY2Nlc3NpbmcgdGhpcyBpbiAqKmdncGxvdDIqKi4gDQoNCldlIGxvYWQgYWxsIG9mIHRoZXNlIHBhY2thZ2VzIGJlbG93LCB3aXRoIHRoZSBleGNlcHRpb24gb2YgKipjb2xvcnNwYWNlKiosIGFzIGl0IGhhcyBhIGBjb29yZHNgIGZ1bmN0aW9uIHRoYXQgKm1hc2tzKiAoaS5lLiwgcmVwbGFjZXMpIGEgbmVlZGVkIGZ1bmN0aW9uIGluIHRoZSAqKnNmKiogcGFja2FnZS4gU28gd2Ugd2lsbCBhY2Nlc3MgdGhlIG5lY2Vzc2FyeSAqKmNvbG9yc3BhY2UqKiBmdW5jdGlvbiB1c2luZyB0aGUgYDo6YCBhcHByb2FjaC4NCg0KYGBge3J9DQojIGxpYnJhcnkoY29sb3JzcGFjZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCiMjIFNpbXBsZSBmZWF0dXJlIGdlb21ldHJ5IG9iamVjdHMgKFZpZGVvOiBbWW91VHViZV0oaHR0cHM6Ly95b3V0dS5iZS9FSTVmNXRXS1lhWSksIFtQYW5vcHRvXShodHRwczovL3VjZGVudmVyLmhvc3RlZC5wYW5vcHRvLmNvbS9QYW5vcHRvL1BhZ2VzL1ZpZXdlci5hc3B4P2lkPTk4YzExZjNmLTY2MTYtNDE0OC1hOTJiLWFmMzIwMTU0NjQ3YikpDQpUaGUgbW9zdCBjb21tb24gc2ltcGxlIGZlYXR1cmUgZ2VvbWV0cnkgb2JqZWN0cyB1c2VkIGJ5IGRhdGEgc2NpZW50aXN0cyBhcmU6DQoNCmdlb21ldHJ5IHwgZnVuY3Rpb24gfCBkZXNjcmlwdGlvbg0KLS0tIHwgLS0tIHwgLS0tDQpgUE9JTlRgIHwJYHNmOjpzdF9wb2ludGAgfCBBIGdlb21ldHJ5IGNvbnRhaW5pbmcgYSBzaW5nbGUgcG9pbnQNCmBQT0xZR09OYCB8CWBzZjo6c3RfcG9seWdvbmAgfCBBIGdlb21ldHJ5IHdpdGggYSBzZXF1ZW5jZSBvZiBwb2ludHMgdGhhdCBmb3JtIGEgY2xvc2VkIHJpbmcgdGhhdCBkb2Vzbid0IGludGVyc2VjdCBpdHNlbGYuIE11bHRpcGxlIHJpbmdzIGZvcm0gb3V0ZXIgcmluZ3MgYW5kIGhvbGVzIHdpdGhpbiB0aGUgcG9seWdvbi4NCg0KV2UgZ28gdGhyb3VnaCB0aGUgcHJvY2VzcyBvZiBjcmVhdGluZyBhbmQgcGxvdHRpbmcgdGhlc2UgZ2VvbWV0cmllcyBiZWxvdy4NCg0KYGBge3J9DQojIGNyZWF0ZSBhbiBYWSBwb2ludA0KKHAxID0gc3RfcG9pbnQoYygxLDIpKSkNCiMgY3JlYXRlIGFuIFhZWiBwb2ludA0KKHAyID0gc3RfcG9pbnQoYygxLDIsMykpKQ0KIyB0aGUgcG9pbnRzIGxvb2sgdGhlIHNhbWUgd2hlbiBwbG90dGVkIGluIHR3byBkaW1lbnNpb25zDQpwbG90KHAxKQ0KcGxvdChwMikNCiMgY3JlYXRlIGEgcmluZyAoY29ubmVjdGVkIHNldCBvZiBwb2ludHMpDQpvdXRlciA8LSBtYXRyaXgoYygwLCAwLCAxMCwgMCwgMTAsIDEwLCAwLCAxMCwgMCwgMCksIG5jb2wgPSAyLCBieXJvdyA9IFRSVUUpDQojIGNyZWF0ZSBhZGRpdGlvbmFsIHJpbmdzIGZvciBob2xlcw0KaG9sZTEgPC0gbWF0cml4KGMoMSwgMSwgMSwgMiwgMiwgMiwgMiwgMSwgMSwgMSksIG5jb2wgPSAyLCBieXJvdyA9IFRSVUUpDQpob2xlMiA8LSBtYXRyaXgoYyg1LCA1LCA1LCA2LCA2LCA2LCA2LCA1LCA1LCA1KSwgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSkNCiMgY29tYmluZSByaW5ncyBpbnRvIGEgbGlzdA0KcHRzIDwtIGxpc3Qob3V0ZXIsIGhvbGUxLCBob2xlMikNCiMgdHVybiBsaXN0IG9mIHJpbmdzIGludG8gYSBwb2x5Z29uDQoocGwxIDwtIHN0X3BvbHlnb24ocHRzKSkNCiMgcGxvdCBwb2x5Z29uDQpwbG90KHBsMSkNCmBgYA0KDQpUaGUgb3RoZXIgY29tbW9uIGdlb21ldHJ5IG9iamVjdHMgYXJlOg0KDQpnZW9tZXRyeSB8IGZ1bmN0aW9uIHwgZGVzY3JpcHRpb24NCi0tLSB8IC0tLSB8IC0tLQ0KYExJTkVTVFJJTkdgIHwJYHNmOjpzdF9saW5lc3RyaW5nYCB8IEEgc2VxdWVuY2Ugb2YgcG9pbnRzIHRoYXQgaXMgY29ubmVjdGVkIHdpdGggc3RyYWlnaHQsIG5vbi1zZWxmIGludGVyc2VjdGluZyBsaW5lcw0KYE1VTFRJUE9JTlRgIHwJYHNmOjpzdF9tdWx0aXBvaW50YCB8IEEgc2V0IG9mIHBvaW50cw0KYE1VTFRJUE9MWUdPTmAgfAlgc2Y6OnN0X211bHRpcG9seWdvbmAgfCBBIHNldCBvZiBwb2x5Z29ucw0KYE1VTFRJTElORVNUUklOR2AgfAlgc2Y6OnN0X211bHRpcG9pbnRgIHwgQSBzZXQgb2YgbGluZSBzdHJpbmdzDQpgR0VPTUVUUllDT0xMRUNUSU9OYCB8CWBzZjo6c3RfZ2VvbWV0cnljb2xsZWN0aW9uYCB8IEEgc2V0IG9mIHRoZSBvdGhlciBnZW9tZXRyaWVzIChleGNlcHQgaXRzZWxmKQ0KDQpXZSBwcm92aWRlIGV4YW1wbGVzIG9mIGNyZWF0aW5nIGFuZCBwbG90dGluZyB0aGVzZSBnZW9tZXRyaWVzIGJlbG93Lg0KYGBge3J9DQojIGNyZWF0ZSBhIG1hdHJpeCBvZiBtdWx0aXBsZSBwb2ludHMNCihwdHMgPC0gbWF0cml4KHJub3JtKDEwKSwgbmNvbCA9IDIpKQ0KIyBjb252ZXJ0IG1hdHJpeCBvZiBwb2ludHMgdG8gbGluZXN0cmluZw0KKGxzMSA8LSBzdF9saW5lc3RyaW5nKHB0cykpDQpwbG90KGxzMSkNCiMgY29udmVydCBtYXRyaXggZm8gcG9pbnRzIHRvIG11bHRpcG9pbnRzDQoobXAxIDwtIHN0X211bHRpcG9pbnQocHRzKSkNCnBsb3QobXAxKQ0KIyBjcmVhdGUgbXVsdGlwb2x5Z29ucw0KcG9sMSA8LSBsaXN0KG91dGVyLCBob2xlMSwgaG9sZTIpDQpwb2wyIDwtIGxpc3Qob3V0ZXIgKyAyNCkNCm1wIDwtIGxpc3QocG9sMSwgcG9sMikNCihtcGwxIDwtIHN0X211bHRpcG9seWdvbihtcCkpDQpwbG90KG1wbDEsIGF4ZXMgPSBUUlVFKQ0KIyBjcmVhdGUgYSBtdWx0aWxpbmVzdHJpbmcNCihwdHMyIDwtIG1hdHJpeChybm9ybSg2KSwgbmNvbCA9IDIpKQ0KKG1sMSA8LSBzdF9tdWx0aWxpbmVzdHJpbmcobGlzdChwdHMsIHB0czIpKSkNCnBsb3QobWwxKQ0KIyBjcmVhdGUgYSBnZW9tZXRyeSBjb2xsZWN0aW9uDQooZ2MgPC0gc3RfZ2VvbWV0cnljb2xsZWN0aW9uKGxpc3QocDEsIHBsMSwgbHMxKSkpDQpwbG90KGdjLCBjb2wgPSAiZ3JleSIpDQpgYGANCg0KSG93IGRvIEkgbmVlZCB3aGF0IHR5cGUgb2YgZ2VvbWV0cnkgSSBuZWVkPw0KDQoqIE9mdGVuLCB0aGlzIGlzIGRldGVybWluZWQgYXV0b21hdGljYWxseSB3aGVuIHlvdSByZWFkIGluIHNoYXBlZmlsZXMgKHdoaWNoIHdlJ2xsIGRpc2N1c3MgbGF0ZXIpLg0KKiBBdHRyaWJ1dGVzIG9ic2VydmVkIGF0IGEgc2luZ2xlIGxvY2F0aW9uIHJlcXVpcmUgYSBgUE9JTlRgLg0KKiBNb3N0IHJlZ2lvbnMgY2FuIGJlIHJlcHJlc2VudGVkIGJ5IGEgYFBPTFlHT05gLg0KKiBDb21wbGljYXRlZCBvYmplY3RzIG1hZGUgb2YgcmVnaW9ucywgZS5nLiwgYW4gaXNsYW5kIGNoYWluIGxpa2UgSGF3YWlpLCByZXF1aXJlIGBNVUxUSVBPTFlHT05TYC4NCiogVGhlIG90aGVyIHR5cGVzIGFyZSBmb3IgbW9yZSBjb21wbGljYXRlZCBvYmplY3RzLg0KKiBUaGVyZSBhcmUgMTAgb3RoZXIgcmFyZXIgZ2VvbWV0cnkgdHlwZXMgdGhhdCB3ZSBkbyBub3QgZGlzY3VzcyAoYENJUkNVTEFSU1RSSU5HYCwgYENVUlZFYCwgYFNVUkZBQ0VgLCBgVFJJQU5HTEVgLCBgQ09NUE9VTkRDVVJWRWAsIGBDVVJWRVBPTFlHT05gLCBgTVVMVElDVVJWRWAsIGBNVUxUSVNVUkZBQ0VgLCBgUE9MWUhFRFJBTFNVUkZBQ0VgLCBgVElOYCkuIFlvdSBjYW4gbGVhcm4gYWJvdXQgdGhlbSB0aHJvdWdoIHRoZSBhZGRpdGlvbmFsIHJlc291cmNlcyBwcm92aWRlZCBhdCB0aGUgZW5kIG9mIHRoaXMgZG9jdW1lbnQuDQoNCiMjIENvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbXMNCg0KQSBjb29yZGluYXRlIHJlZmVyZW5jZSBzeXN0ZW0gKENSUykgbXVzdCBiZSBwcm92aWRlZCBpbiBvcmRlciB0byBwbGFjZSBhIHBvaW50IG9uIHRoZSBlYXJ0aCdzIHN1cmZhY2UuDQoNCldoZW4geW91ciBpbXBvcnQgYSBnZW9tZXRyeSBvYmplY3QgZnJvbSBmaWxlLCB0aGUgQ1JTIHdpbGwgb2Z0ZW4gYmUgcHJvdmlkZWQuDQoNCiogVGhlIFdHUzg0IENSUyBpcyBvZnRlbiB0aGUgZGVmYXVsdCBmb3IgbG9uZ2l0dWRlL2xhdGl0dWRlIGNvb3JkaW5hdGVzLg0KDQpTb21ldGltZXMsIGluIG9yZGVyIHRvIGNvbWJpbmUgZ2VvbWV0cnkgb2JqZWN0cywgeW91IG11c3Qgc3BlY2lmeSB0aGUgQ1JTIG9mIGEgZ2VvbWV0cnkgb2JqZWN0Lg0KDQpUaGVyZSBhcmUgbWFueSBDUlNzLiBBIENSUyBpcyBvZnRlbiB1c2VkIGJlY2F1c2UgaXQgaXMga25vd24gdG8gaGF2ZSBhIGNlcnRhaW4gZGVzaXJhYmxlIHByb3BlcnR5LiBBIGRpc2N1c3Npb24gb2YgQ1JTcyBpcyBiZXlvbmQgdGhlIHNjb3BlIG9mIHRoaXMgdHV0b3JpYWwuIEFuZCB3aGVuIHlvdSBkbyBuZWVkIHRvIGtub3cgYWJvdXQgQ1JTcywgaXQgd2lsbCBwcm9iYWJseSBiZSBzbyBzcGVjaWZpYyB0aGF0IGEgZ2VuZXJhbCBkaXNjdXNzaW9uIHdvbid0IGhlbHAuIEhvd2V2ZXIsIGhlcmUgYXJlIGEgZmV3IHJlZmVyZW5jZXMgcmVsYXRlZCB0byBDUlNzIHRoYXQgbWF5IHByb3ZpZGUgYSBiaXQgbW9yZSBkZXRhaWw6DQoNCiogW1FHSVMgQ1JTc10oaHR0cHM6Ly9kb2NzLnFnaXMub3JnLzMuMTYvZW4vZG9jcy9nZW50bGVfZ2lzX2ludHJvZHVjdGlvbi9jb29yZGluYXRlX3JlZmVyZW5jZV9zeXN0ZW1zLmh0bWwpDQoqIFtJbnRyb2R1Y3Rpb24gdG8gQ1JTc10oaHR0cHM6Ly9ib29rZG93bi5vcmcvdGVwL2dpc2Jvb2tsZXQvaW50cm9kdWN0aW9uLXRvLWNvb3JkaW5hdGUtcmVmZXJlbmNlLXN5c3RlbXMuaHRtbCkNCg0KIyMgQ29uc3RydWN0aW5nIHNpbXBsZSBmZWF0dXJlIChgc2ZgKSBvYmplY3RzIChWaWRlbzogW1lvdVR1YmVdKGh0dHBzOi8veW91dHUuYmUvYWdIQjhFZU5VTmspLCBbUGFub3B0b10oaHR0cHM6Ly91Y2RlbnZlci5ob3N0ZWQucGFub3B0by5jb20vUGFub3B0by9QYWdlcy9WaWV3ZXIuYXNweD9pZD00ZTg3NmY4NC04MDUzLTQ2YjEtYWU1ZS1hZjMyMDE1NDQ1MDIpKQ0KDQpBbiBgc2ZgIG9iamVjdCBpcyBhIGBkYXRhLmZyYW1lYCB0aGF0IGhhcyBhIHNpbXBsZSBmZWF0dXJlIGdlb21ldHJ5IGxpc3QgY29sdW1uIChpLmUuLCBhIGNvbHVtbiBvZiBnZW9tZXRyeSBvYmplY3RzKS4gU28geW91IGNhbiB3b3JrIHdpdGggYHNmYCBvYmplY3RzIHNpbWlsYXIgdG8gaG93IHlvdSB3b3VsZCB3b3JrIHdpdGggYSBgZGF0YS5mcmFtZWAgb2JqZWN0LCB0aG91Z2ggaXQgbWF5IGhhdmUgZGlmZmVyZW50IGRlZmF1bHQgYmVoYXZpb3JzIGJlY2F1c2UgaXQgaGFzIGJlZW4gZXh0ZW5kZWQgdG8gYW4gYHNmYCBvYmplY3QuDQoNCiogVGhlIGdlb21ldHJ5LWxpc3QgY29sdW1uIGNvbnRhaW5zIHRoZSBnZW9tZXRyeSBvYmplY3QgZm9yIGVhY2ggb2JzZXJ2YXRpb24uDQoqIFRoZSBvdGhlciBjb2x1bW5zIG9mIHRoZSBgc2ZgIG9iamVjdCBjb250YWluIHRoZSBhdHRyaWJ1dGVzIG9mIHRoZSBnZW9tZXRyeSBvYmplY3QuDQoqIEVhY2ggcm93IG9mIHRoZSBgc2ZgIG9iamVjdCBpcyBhIHNpbXBsZSBmZWF0dXJlLiBBbHRlcm5hdGl2ZWx5LCBlYWNoIG9ic2VydmF0aW9uIGlzIGEgc2ltcGxlIGZlYXR1cmUuIA0KDQpJbiBwcmFjdGljZSwgYHNmYCBvYmplY3RzIGFyZSBvZnRlbiBpbml0aWFsbHkgY3JlYXRlZCBieSByZWFkaW5nIGluIGEgc2hhcGVmaWxlLiBIb3dldmVyLCBwYXJ0aWN1bGFybHkgZm9yIGBQT0lOVGAgb2JzZXJ2YXRpb25zLCB5b3UgbWF5IG5lZWQgdG8gY3JlYXRlIGFuIGBzZmAgb2JqZWN0IG1hbnVhbGx5Lg0KDQpJbiB3aGF0IGZvbGxvd3MsIHdlIGNyZWF0ZSBgc2ZgIG9iamVjdHMgZm9yIGBQT0lOVGAgZ2VvbWV0cnkgb2JqZWN0cyBhbmQgYFBPTFlHT05gIGdlb21ldHJ5IG9iamVjdHMuDQoNCiogV2UgY2FuIHVzZSB0aGUgc2FtZSBwcmV2aW91c2x5IGRpc2N1c3NlZCBmdW5jdGlvbnMgKGUuZy4sIGBzZjo6c3RfcG9pbnRgLCBgc2Y6OnN0X3BvbHlnb25gLCBldGMuKSB0byBjcmVhdGUgdGhlIGdlb21ldHJ5IG9iamVjdHMuIA0KKiBUaGUgYHNmOjpzdF9zZmNgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gY3JlYXRlIGEgZ2VvbWV0cnkgbGlzdCBjb2x1bW4uDQoqIFRoZSBgc2Y6OnN0X3NmYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGNvbWJpbmVkIGEgYGRhdGEuZnJhbWVgIG9mIGF0dHJpYnV0ZXMgd2l0aCB0aGUgZ2VvbWV0cnkgbGlzdCBjb2x1bW4uDQoNCmBgYHtyfQ0KIyBjcmVhdGUgUE9JTlQgb2JqZWN0cw0KcHQxIDwtIHN0X3BvaW50KGMoMCwgMCkpDQpwdDIgPC0gc3RfcG9pbnQoYygwLCAxKSkNCnB0MyA8LSBzdF9wb2ludChjKDEsIDEpKQ0KIyBjcmVhdGUgZ2VvbWV0cnkgbGlzdCBjb2x1bW4NCmdsYzEgPC0gc3Rfc2ZjKGxpc3QocHQxLCBwdDIsIHB0MykpDQojIGlzIGdsYzEgYSBsaXN0Pw0KaXMubGlzdChnbGMxKQ0KIyB3aGF0IGNsYXNzIGlzIGdsYzENCmNsYXNzKGdsYzEpDQojIGNyZWF0ZSBhdHRyaWJ1dGUgZGF0YS5mcmFtZSB3aXRoIHRlbXBlcmF0dXJlIGFuZCByYWluZmFsbCBhdHRyaWJ1dGVzDQpkZjEgPC0gZGF0YS5mcmFtZSh0ZW1wZXJhdHVyZSA9IGMoMTAsIDExLCAxMC40KSwgcmFpbmZhbGwgPSBjKDEsIDEuMywgMC45KSkNCiMgY3JlYXRlIHNmIG9iamVjdA0Kc2YxIDwtIHN0X3NmKGRmMSwgZ2VvbWV0cnkgPSBnbGMxKQ0KIyBjbGFzcyBvZiBzZjENCmNsYXNzKHNmMSkNCiMgcGxvdCBzZjENCnBsb3Qoc2YxWyJ0ZW1wZXJhdHVyZSJdLCBwY2ggPSAyMCwgYXhlcyA9IFRSVUUpDQojIGNyZWF0ZSBwb2x5Z29uIG9iamVjdHMNCm91dGVyMSA8LSBtYXRyaXgoYygwLCAwLCAxLCAxLCAwLCAwLCAxLCAxLCAwLCAwKSwgbmNvbCA9IDIpDQpwbDEgPC0gc3RfcG9seWdvbihsaXN0KG91dGVyMSkpDQojIG91dGVyMiBpcyBvdXRlcjEgc2hpZnRlZCAxIHVuaXQgdG8gdGhlIHJpZ2h0DQpvdXRlcjIgPC0gbWF0cml4KGMoMSwgMSwgMiwgMiwgMSwgMCwgMSwgMSwgMCwgMCksIG5jb2wgPSAyKQ0KcGwyIDwtIHN0X3BvbHlnb24obGlzdChvdXRlcjIpKQ0KIyBjcmVhdGUgZ2VvbWV0cnkgbGlzdCBjb2x1bW5zDQpnbGMyIDwtIHN0X3NmYyhsaXN0KHBsMSwgcGwyKSkNCiMgd2hhdCBjbGFzcyBpcyBnbGMyDQpjbGFzcyhnbGMyKQ0KIyBjcmVhdGUgc2Vjb25kIHNmIG9iamVjdCAob25seSBpbmNsdWRlIGFuIGF0dHJpYnV0ZSBjb2x1bW4gYW5kIGdlb21ldHJ5KQ0Kc2YyIDwtIHN0X3NmKGNhc2VzID0gYygxMCwgMTIpLCBnZW9tZXRyeSA9IGdsYzIpDQojIGNsYXNzIG9mIHNmMg0KY2xhc3Moc2YyKQ0KIyBwbG90IHNmMg0KcGxvdChzZjIpDQojIHdoYXQgaGFwcGVucyBpZiB5b3UgY29tYmluZSBnZW9tZXRyeSB0eXBlcz8NCmdsYzMgPC0gc3Rfc2ZjKGxpc3QocHQxLCBwdDIsIHB0MywgcGwxLCBwbDIpKQ0KIyB3aGF0IGNsYXNzIGlzIGdsYzMNCmNsYXNzKGdsYzMpDQojIGNyZWF0ZSBzZjMNCnNmMyA8LSBzdF9zZihhdHRyaWJ1dGUxID0gcm5vcm0oNSksIGdlb21ldHJ5ID0gZ2xjMykNCiMgY2xhc3Mgb2Ygc2YzDQpjbGFzcyhzZjMpDQojIHBsb3Qgc2YzDQpwbG90KHNmMywgcGNoID0gMjAsIHBhbCA9IHRvcG8uY29sb3JzKQ0KIyBwbG90IHNmMyB3aXRoIGV2ZW4gYnJlYWtzDQpwbG90KHNmMywgcGNoID0gMjAsIHBhbCA9IHRvcG8uY29sb3JzLCBicmVha3MgPSBzZXEobWluKHNmMyRhdHRyaWJ1dGUxKSwgbWF4KHNmMyRhdHRyaWJ1dGUxKSwgbGVuZ3RoID0gMTIpKQ0KYGBgDQoNCiMjIEltcG9ydGluZyBzaGFwZWZpbGVzIGFzIGBzZmAgb2JqZWN0cyAoVmlkZW86IFtZb3VUdWJlXShodHRwczovL3lvdXR1LmJlL0VjNkQxMi1MYTd3KSwgW1Bhbm9wdG9dKGh0dHBzOi8vdWNkZW52ZXIuaG9zdGVkLnBhbm9wdG8uY29tL1Bhbm9wdG8vUGFnZXMvVmlld2VyLmFzcHg/aWQ9MDNhY2M5YzUtYWJhMy00NTYyLTliMjItYWYzMjAxNTQzYmI5KSkNCg0KQSBkYXRhIHNjaWVudGlzdCBpcyBtb3N0IGxpa2VseSB0byB3b3JrIHdpdGggYHNmYCBvYmplY3RzIG9idGFpbmVkIGZyb20gaW1wb3J0aW5nIGEgc2hhcGVmaWxlIGludG8gUi4gDQoNCkFyY0dJUyBkZWZpbmVzIGEgc2hhcGVmaWxlIGFzOg0KDQo+IEEgc2hhcGVmaWxlIGlzIGEgc2ltcGxlLCBub250b3BvbG9naWNhbCBmb3JtYXQgZm9yIHN0b3JpbmcgdGhlIGdlb21ldHJpYyBsb2NhdGlvbiBhbmQgYXR0cmlidXRlIGluZm9ybWF0aW9uIG9mIGdlb2dyYXBoaWMgZmVhdHVyZXMuIEdlb2dyYXBoaWMgZmVhdHVyZXMgaW4gYSBzaGFwZWZpbGUgY2FuIGJlIHJlcHJlc2VudGVkIGJ5IHBvaW50cywgbGluZXMsIG9yIHBvbHlnb25zIChhcmVhcykuIFtXaGF0IGlzIGEgc2hhcGVmaWxlP10oaHR0cHM6Ly9kZXNrdG9wLmFyY2dpcy5jb20vZW4vYXJjbWFwLzEwLjMvbWFuYWdlLWRhdGEvc2hhcGVmaWxlcy93aGF0LWlzLWEtc2hhcGVmaWxlLmh0bSkNCg0KR2VuZXJhbGx5LCBhIHNoYXBlZmlsZSBjYW4gYmUgaW1wb3J0ZWQgaW50byBSIGFzIGFuIGBzZmAgb2JqZWN0Lg0KICANCiogRWFjaCByb3cgaXMgYW4gb2JzZXJ2YXRpb24gcmVsYXRlZCB0byBhIGdlb21ldHJ5IG9iamVjdC4NCiogVGhlcmUgc2hvdWxkIGJlIGEgYGdlb21ldHJ5YCBjb2x1bW4gdGhhdCBjb250YWlucyB0aGUgZ2VvbWV0cnkgb2JqZWN0IGZvciBlYWNoIG9ic2VydmF0aW9uICh0aGlzIGlzIHRoZSBnZW9tZXRyeSBsaXN0IGNvbHVtbikuDQoqIFRoZSBvdGhlciBjb2x1bW5zIHdpbGwgcmVwcmVzZW50IHRoZSBhdHRyaWJ1dGVzIGFzc29jaWF0ZWQgd2l0aCBlYWNoIGdlb21ldHJ5IG9iamVjdC4NCiAgDQpTaGFwZWZpbGVzIGFyZSB3aWRlbHkgYXZhaWxhYmxlIGZvciBkZXNjcmliaW5nIG1hbnkgZGlmZmVyZW50IHNwYXRpYWwgb2JqZWN0cyBsaWtlIGNvdW50aWVzLCBjZW5zdXMgdHJhY3RzLCB6aXAgY29kZSB0YWJ1bGF0aW9uIGFyZWFzIChaQ1RBcyksIHN0YXRlcywgY291bnRyaWVzLCBldGMuIFRoZXJlIGFyZSBldmVuIHNoYXBlZmlsZXMgdGhhdCBjYW4gYmUgdXNlZCB0byBkZXNjcmliZSBvdGhlciBzcGF0aWFsIG9iamVjdHMgbGlrZSByb2Fkcywgcml2ZXJzLCBsYWtlcywgZXRjLg0KDQoqIEEgc2ltcGxlIHdlYiBzZWFyY2ggd2l0aCBhcHByb3ByaWF0ZSB0ZXJtcyB3aWxsIHVzdWFsbHkgYnJpbmcgdXAgYSB3ZWJzaXRlIHdpdGggYSByZWxldmFudCBzaGFwZWZpbGUgZm9yIHlvdXIgZGF0YS4NCiogZS5nLiwgc2VhcmNoICJ1c2Egc2hhcGVmaWxlIiBicmluZ3MgdXAgYSBbQ2Vuc3VzIGJ1cmVhdSBwYWdlXShodHRwczovL3d3dy5jZW5zdXMuZ292L2dlb2dyYXBoaWVzL21hcHBpbmctZmlsZXMvdGltZS1zZXJpZXMvZ2VvL2NhcnRvLWJvdW5kYXJ5LWZpbGUuaHRtbCkgd2l0aCBtYW55IGRpZmZlcmVudCBzaGFwZWZpbGVzIGZvciBkaWZmZXJlbnQgYXJlYXMgYW5kIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgVW5pdGVkIFN0YXRlcy4gDQoqIEEgInNoYXBlZmlsZSIgaXMgb2Z0ZW4gYSB6aXBwZWQgZm9sZGVyIHRoYXQgY29udGFpbnMgbWFueSBmaWxlcyBpbnNpZGUgaXQuDQoqIFRoZSBgLnNocGAgZmlsZSBpcyB1c3VhbGx5IHRoZSBmaWxlIHlvdSB3YW50IHRvIGltcG9ydC4NCiAgDQpXZSBjYW4gaW1wb3J0IHRoYXQgc2hhcGVmaWxlIGludG8gUiBhcyBhbiBgc2ZgIG9iamVjdCB1c2luZyB0aGUgYHN0X3JlYWRgIGZ1bmN0aW9uLg0KDQoqIFR5cGljYWxseSwgd2Ugd2FudCB0byBwcm92aWRlIHRoZSBgc2hwYCBmaWxlIHRvIHRoZSBgZHNuYCBhcmd1bWVudCAoZGF0YSBzb3VyY2UgbmFtZSkgb2YgYHN0X3JlYWRgLg0KDQpJbiB0aGlzIGNhc2UsIEkgaGF2ZSBkb3dubG9hZGVkIHRoZSBgY2JfMjAxOF91c19zdGF0ZV8yMG0uemlwYCBjb250YWluaW5nIGEgc2hhcGVmaWxlIG9mIHRoZSBVLlMuIHN0YXRlcy4gSSBoYXZlIHVuemlwcGVkIHRoaXMgZmlsZSBpbnRvIHRoZSBgY2JfMjAxOF91c19zdGF0ZV8yMG1gIGZvbGRlciBpbiB0aGUgYGRhdGFgIGZvbGRlciBpbiBteSBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5Lg0KDQoqIFRoZSBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5IGlzIHRoZSBsb2NhdGlvbiB0byB3aGljaCBSIGN1cnJlbnRseSByZWFkcyBvciBzYXZlcyBmaWxlcy4NCiogVGhlIGNhbiBsZWFybiB3aGF0IHlvdXIgY3VycmVudCB3b3JraW5nIGRpcmVjdG9yeSBpcyBieSBydW5uaW5nIGBnZXR3ZCgpYCBpbiB0aGUgQ29uc29sZS4NCiogWW91IGNhbiBjaGFuZ2UgeW91ciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5IGJ5IHJ1bm5pbmcgYHNldHdkKCJwYXRoIilgIGluIHRoZSBDb25zb2xlLCB3aGVyZSBgcGF0aGAgaXMgdGhlIGRpcmVjdG9yeSBwYXRoIHlvdSB3YW50IHRvIHNldCBhcyB5b3VyIHdvcmtpbmcgZGlyZWN0b3J5Lg0KICANCkluIG91ciBjYXNlLCB3ZSB3YW50IHRvIHJlYWQgdGhlIGZpbGUgYGNiXzIwMThfdXNfc3RhdGVfMjBtLnNocGAgaW4gdGhlIGBjYl8yMDE4X3VzX3N0YXRlXzIwbWAgZm9sZGVyIGluIHRoZSBgZGF0YWAgZm9sZGVyIG9mIG91ciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5LiBXZSBjYW4gcnVuIHRoZSBmb2xsb3dpbmcgY29tbWFuZCB0byBpbXBvcnQgdGhlIGRlc2lyZWQgc2hhcGVmaWxlLg0KDQoqICJgLi9gIiBpbmRpY2F0ZXMgdGhlIGN1cnJlbnQgZGlyZWN0b3J5Lg0KKiBUaGUgYGRzbmAgYXJndW1lbnQgImAuL2RhdGEvY2JfMjAxOF91c19zdGF0ZV8yMG0vY2JfMjAxOF91c19zdGF0ZV8yMG0uc2hwYCB0ZWxscyBSIHRvIGxvb2sgZm9yIHRoZSBgY2Jfc3RhdGVfMjBtLnNocGAgZmlsZSBpbiB0aGUgZmlsZSBwYXRoIGAuL2RhdGEvY2Jfc3RhdGVfMjAxOF91c19zdGF0ZV8yMG1gLg0KDQpGb3Igc2ltcGxpY2l0eSwgSSBoYXZlIHVwbG9hZGVkIGBjYl8yMDE4X3VzX3N0YXRlXzIwbS56aXBgIHRvIG15IEdpdEh1YiByZXBvc2l0b3J5LiBQcmlvciB0byBpbXBvcnRpbmcgdGhlIHRoZSBgLnNocGAgZmlsZSwgcnVubmluZyB0aGUgYGRvd25sb2FkYCBhbmQgYHVuemlwYCBjb21tYW5kcyBzaG91bGQgZG93bmxvYWQgYW5kIHVuemlwIHRoZSBuZWNlc3NhcnkgZm9sZGVyIGludG8geW91ciB3b3JraW5nIGRpcmVjdG9yeSBzbyB0aGF0IHlvdSBjYW4gcnVuIHRoZSBzYW1lIGNvZGUgdG8gaW1wb3J0IHRoZSBzaGFwZWZpbGUuDQoNCmBgYHtyfQ0KIyBkb3dubG9hZC5maWxlKCJodHRwczovL2dpdGh1Yi5jb20vamZyZW5jaC9EYXRhV3JhbmdsZVZpei9yYXcvbWFzdGVyL2RhdGEvY2EtY291bnR5LWJvdW5kYXJpZXMuemlwIiwgZGVzdGZpbGUgPSAiLi9jYS1jb3VudHktYm91bmRhcmllcy56aXAiLCBtZXRob2QgPSAiYXV0byIpDQojIHVuemlwKCJjYl8yMDE4X3VzX3N0YXRlXzIwbS56aXAiLCBleGRpciA9ICIuL2RhdGEvY2JfMjAxOF91c19zdGF0ZV8yMG0iKQ0KdXNfc2YgPC0gc3RfcmVhZChkc24gPSAiLi9kYXRhL2NiXzIwMThfdXNfc3RhdGVfMjBtL2NiXzIwMThfdXNfc3RhdGVfMjBtLnNocCIpDQpgYGANCg0KVGhlIGBzdF9yZWFkYCBwcm92aWRlcyBoZWxwZnVsIGluZm9ybWF0aW9uIGF1dG9tYXRpY2FsbHkgd2hlbiBydW4uIEluIHRoaXMgY2FzZSwgd2UgbGVhcm46DQoNCiogV2UgaW1wb3J0ZWQgYW4gYHNmYCBvYmplY3Qgd2l0aCA1MiBmZWF0dXJlcyAob2JzZXJ2YXRpb25zKS4NCiogVGhlIGltcG9ydGVkIGBzZmAgb2JqZWN0IGhhcyA5IGF0dHJpYnV0ZXMgKGNvbHVtbnMpLg0KKiBUaGUgZmVhdHVyZXMgYXBwZWFyIHRvIGJlIHRoZSBzaW1wbGUgZmVhdHVyZSBnZW9tZXRyeSBgTVVMVElQT0xHWU9OYC4NCiogVGhlIGRpbWVuc2lvbiBpcyBgWFlgLCBzbyB3ZSBhcmUgd29ya2luZyB3aXRoIDItZGltZW5zaW9uYWwgZGF0YS4NCiogVGhlIGJvdW5kaW5nIGJveCB0ZWxscyB1cyB0aGUgbGFyZ2VzdCBhbmQgc21hbGxlc3QgeS1jb29yZGluYXRlcy4NCiogVGhlIENSUyBpcyBOQUQ4My4NCg0KV2UgdXNlIHRoZSBgY2xhc3NgIGZ1bmN0aW9uIHRvIHNlZSB0aGUgY2xhc3NlcyBvZiBgdXNfc2ZgLg0KYGBge3J9DQpjbGFzcyh1c19zZikNCmBgYA0KDQpgdXNfc2ZgIGlzIGEgYGRhdGEuZnJhbWVgIHRoYXQgaGFzIGJlZW4gZXh0ZW5kZWQgdG8gdGhlIGBzZmAgY2xhc3MuDQoNCkxldCdzIHVzZSB0aGUgYHN0cmAgZnVuY3Rpb24gdG8gbGVhcm4gbW9yZSBhYm91dCB0aGUgc3RydWN0dXJlIG9mIGB1c19zZmAuDQpgYGB7cn0NCnN0cih1c19zZikNCmBgYA0KDQpgdXNfc2ZgIGhhcyA1MiByb3dzIGFuZCAxMCBjb2x1bW5zLiBUaGUgKG1vc3QpIHVzZWZ1bCBjb2x1bW5zIGFyZToNCg0KICAqIGBTVFVTUFNgOiB0aGUgc3RhdGUgYWJicmV2aWF0aW9uDQogICogYE5BTUVgOiB0aGUgc3RhdGUgbmFtZQ0KICAqIGBBTEFORGA6IHRoZSBsYW5kIGFyZWEgb2YgZWFjaCBzdGF0ZQ0KICAqIGBBV0FURVJgOiB0aGUgd2F0ZXIgYXJlYSBvZiBlYWNoIHN0YXRlDQogIA0KVGhlIGBnZW9tZXRyeWAgY29sdW1uIGlzIHRoZSBzaW1wbGUgZmVhdHVyZSBnZW9tZXRyeSBsaXN0IGNvbHVtbiBhbmQgY29udGFpbnMgdGhlIGdlb21ldHJ5IG9iamVjdCBmb3IgZWFjaCBvYnNlcnZhdGlvbi4NCg0KIyBXcmFuZ2xpbmcgYHNmYCBvYmplY3RzIChWaWRlbzogW1lvdVR1YmVdKGh0dHBzOi8veW91dHUuYmUvYlJ3RXpJQVZjWXcpLCBbUGFub3B0b10oaHR0cHM6Ly91Y2RlbnZlci5ob3N0ZWQucGFub3B0by5jb20vUGFub3B0by9QYWdlcy9WaWV3ZXIuYXNweD9pZD1lN2I5NmUwOC03ZjZiLTQ3ZDgtOGUwYi1hZjMyMDE1NDI2N2UpKQ0KDQpBbiBgc2ZgIG9iamVjdCBpcyBhIHR5cGUgb2YgZGF0YSBmcmFtZSwgc2ltaWxhciB0byBob3cgYSB0aWJibGUgaXMgYSB0eXBlIG9mIGRhdGEgZnJhbWUuIEJvdGggY2xhc3NlcyBleHRlbmQgdGhlIGBkYXRhLmZyYW1lYCBjbGFzcy4NCg0KKiBUaGlzIG1lYW5zIHRoYXQgeW91IGNhbiB3b3JrIHdpdGggYHNmYCBvYmplY3RzIHNpbWlsYXJseSB0byBob3cgeW91IHdvdWxkIHdvcmsgd2l0aCBhIGBkYXRhLmZyYW1lYCwgdGhvdWdoIHRoZSBkZWZhdWx0IGJlaGF2aW9ycyBtYXkgYmUgZGlmZmVyZW50Lg0KKiBXZSBjYW4gdXNlIHRoZSAqKmRwbHlyKiogcGFja2FnZSBvciBzaW1pbGFyIHRvb2xzIHRvIG1hbmlwdWxhdGUgYW4gYHNmYCBvYmplY3QuDQogIA0KV2UgY2FuIHNlbGVjdCBjb2x1bW5zIG9mIHRoZSBgdXNfc2ZgIGBzZmAgb2JqZWN0IGluIHRoZSBmb2xsb3dpbmcgd2F5czoNCg0KYGBge3J9DQp1c19zZiRTVFVTUFMNCnVzX3NmWyw1XQ0KdXNfc2ZbLCJTVFVTUFMiXQ0KdXNfc2ZbIlNUVVNQUyJdDQpgYGANCk5vdGUgdGhhdCB0aGUgYCRgIG9wZXJhdG9yIGV4dHJhY3RzIHRoZSBjb2x1bW4gZnJvbSBgdXNfc2ZgIChyZXR1cm5pbmcgYSBgY2hhcmFjdGVyYCB2ZWN0b3IpLCB3aGlsZSB0aGUgb3RoZXIgY2hvaWNlcyAqc3Vic2V0KiBgdXNfc2ZgIGFuZCByZW1haW4gYHNmYCBvYmplY3RzIChpLmUuLCB0aGUgZ2VvbWV0cnkgbGlzdCBjb2x1bW4gaXMgcmV0YWluZWQpLg0KDQpXZSBjYW4gc2VsZWN0IHJvd3MgaW4gYSBzaW1pbGFyIGZhc2hpb24uDQoNCmBgYHtyfQ0KdXNfc2ZbMjozLF0NCnVzX3NmW3VzX3NmJFNUVVNQUyA9PSAiQ08iLF0NCnVzX3NmW3N0YXJ0c1dpdGgodXNfc2YkU1RVU1BTLCAiQyIpLF0NCnVzX3NmICU+JSBmaWx0ZXIoc3RhcnRzV2l0aCh1c19zZiRTVFVTUFMsICJDIikpDQpgYGANCg0KTm90ZSB0aGF0IGBzdGFydHNXaXRoYCBpcyBhICoqYmFzZSoqIFIgZnVuY3Rpb24gdGhhdCBmaW5kcyB0aGUgZWxlbWVudHMgb2YgYSBgY2hhcmFjdGVyYCB2ZWN0b3IgdGhhdCBzdGFydCB3aXRoIGEgY2VydGFpbiBzZXQgb2YgY2hhcmFjdGVycyB3aGlsZSBgc3RhcnRfd2l0aGAgaXMgYSAqKmRwbHlyKiogZnVuY3Rpb24gdGhhdCBpcyB1c2VkIHRvIHNlbGVjdCBjb2x1bW5zIG9mIGEgZGF0YSBmcmFtZSBiYXNlZCBvbiBhIHBhdHRlcm4uDQoNCkEgcmVhbGx5IG5lYXQgZmVhdHVyZSBvZiBgc2ZgIG9iamVjdHMgaXMgdGhhdCBjYW4gdXNlIGEgc3BhdGlhbCBvYmplY3QgdG8gc2VsZWN0IHJvd3MuIExldCdzIGV4dHJhY3QgdGhlICAiQ29sb3JhZG8iIHJvdyBmcm9tIGB1c19zZmAuDQoNCmBgYHtyfQ0KY28gPC0gdXNfc2ZbdXNfc2YkTkFNRSA9PSAiQ29sb3JhZG8iLF0NCmNsYXNzKGNvKQ0KYGBgDQoNCklmIHdlIHBhc3MgdGhlIGBjb2Agb2JqZWN0IGFzIHRoZSByb3cgYXJndW1lbnQgaW5zaWRlIHRoZSBzcXVhcmUgYnJhY2tldHMsIGBbXWAsIHRoZW4gdGhlIHJvd3Mgb2YgYHVzX3NmYCB3aXRoIGdlb21ldHJ5IG9iamVjdHMgdGhhdCBpbnRlcnNlY3QgdGhlIGBjb2AgZ2VvbWV0cnkgb2JqZWN0IHdpbGwgYmUgcmV0dXJuZWQNCg0KYGBge3J9DQooY29faW50ZXJzZWN0cyA8LSB1c19zZltjbyxdKQ0KcGxvdChjb19pbnRlcnNlY3RzWyJOQU1FIl0pDQpwbG90KHN0X2dlb21ldHJ5KGNvX2ludGVyc2VjdHMpKQ0KYGBgDQpgYGB7cn0NCmNhY28gPC0gdXNfc2ZbdXNfc2YkTkFNRSA9PSAiQ29sb3JhZG8iIHwgDQogICAgICAgICAgICAgICAgdXNfc2YkTkFNRSA9PSAiQ2FsaWZvcm5pYSIsXQ0KKGNhY29faW50ZXJzZWN0cyA8LSB1c19zZltjYWNvLF0pDQpwbG90KHN0X2dlb21ldHJ5KGNhY29faW50ZXJzZWN0cykpDQpgYGANCg0KVGhlICoqYmFzZSoqIGBtZXJnZWAgYW5kICoqZHBseXIqKiBgKl9qb2luYCBmdW5jdGlvbnMgY2FuIGJlIHVzZWQgdG8gbWVyZ2UgYSBkYXRhIGZyYW1lIHdpdGggYW4gYHNmYCBvYmplY3QuDQoNCiAgKiBUaGUgYHNmYCBvYmplY3QgbXVzdCBiZSB0aGUgZmlyc3QgYXJndW1lbnQgb2YgdGhlc2UgZnVuY3Rpb25zLg0KICANCkxldCdzIGFjY2VzcyBhIENPVklELTE5IHJlbGF0ZWQgZGF0YSBmcmFtZSBhdmFpbGFibGUgaW4gdGhlICoqYmF5ZXN1dGlscyoqIHBhY2thZ2UsIHdoaWNoIGNhbiBiZSBpbnN0YWxsZWQgZnJvbSBHaXRIdWIuIA0KDQoqIFdlIGluc3RhbGwgdGhlIHBhY2thZ2UgZnJvbSBHaXRIdWIgdXNpbmcgdGhlIGByZW1vdGU6Omluc3RhbGxfZ2l0aHViYCBmdW5jdGlvbi4NCiAgKiBNYWtlIHN1cmUgdG8gaW5zdGFsbCB0aGUgKipyZW1vdGVzKiogcGFja2FnZSBbQFItcmVtb3Rlc10gaWYgeW91IGRvbid0IGhhdmUgaXQuDQoqIFdlIHRoZW4gbG9hZCB0aGUgYGNvdmlkXzIwMjEwMzA3YCBkYXRhIHNldCBmcm9tIHRoZSAqKmJheWVzdXRpbHMqKiBwYWNrYWdlLg0KDQpgYGB7cn0NCiMgaW5zdGFsbC5wYWNrYWdlcygicmVtb3RlcyIpDQojIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJqZnJlbmNoL2JheWVzdXRpbHMiKQ0KZGF0YSgiY292aWRfMjAyMTAzMDciLCBwYWNrYWdlID0gImJheWVzdXRpbHMiKQ0KYGBgDQoNClRoZSBgc3RhdGVfYWJiYCBjb2x1bW4gb2YgYGNvdmlkXzIwMjEwMzA3YCBoYXMgdGhlIHN0YXRlIGFiYnJldmlhdGlvbnMgYW5kIG1hdGNoZXMgdGhlIGBTVFVTUFNgIGNvbHVtbiBvZiBgdXNfc2ZgLg0KDQpXZSB1c2UgdGhlICoqYmFzZSoqIGBtZXJnZWAgZnVuY3Rpb24gdG8gdW5pdGUgdGhlc2UgdHdvIG9iamVjdHMgaW50byBhIG5ldyBvYmplY3QsIGBjb3ZpZF91c2AuDQoNCmBgYHtyfQ0KY292aWRfdXMgPC0gbWVyZ2UodXNfc2YsIGNvdmlkXzIwMjEwMzA3LA0KICAgICAgICAgICAgICAgICAgYnkueCA9ICJTVFVTUFMiLCBieS55ID0gInN0YXRlX2FiYiIpDQpoZWFkKGNvdmlkX3VzKQ0KYGBgDQoNCkFsdGVybmF0aXZlbHksIHdlIGNvdWxkIGhhdmUgdXNlZCBhICoqZHBseXIqKiBgKl9qb2luYCBmdW5jdGlvbi4gV2UnbGwgdXNlIGBmdWxsX2pvaW5gLiBOb3RlIHRoZSBzcGVjaWFsIHN5bnRheCBpbiB0aGUgYGJ5YCBhcmd1bWVudCB0byBhZGRyZXNzIHRoZSBmYWN0IHRoYXQgd2Ugd2FudCB0byBtZXJnZSB0aGUgcm93cyBiYXNlZCBvbiBkaWZmZXJlbnQgY29sdW1ucyBpbiB0aGUgZGF0YSBmcmFtZXMuDQoNCmBgYHtyfQ0KY292aWRfdXMyIDwtIGZ1bGxfam9pbih1c19zZiwgY292aWRfMjAyMTAzMDcsIGJ5ID0gYygiU1RVU1BTIiA9ICJzdGF0ZV9hYmIiKSkNCmhlYWQoY292aWRfdXMyKQ0KYGBgDQoNCklmIGEgbmV3IHJvdyBpcyBhZGRlZCB0byB0aGUgYHNmYCBvYmplY3Qgd2l0aG91dCBhIGNvcnJlc3BvbmRpbmcgZ2VvbWV0cnksIHRoZW4gYW4gZW1wdHkgZ2VvbWV0cnkgb2JqZWN0IGlzIGFkZGVkIGZvciB0aGF0IHJvdy4NCg0KIyMgUGxvdHRpbmcgc2ltcGxlIGZlYXR1cmVzDQoNClRoZSBwb3dlciBvZiB0aGUgYHNmYCBwYWNrYWdlIGlzIHRoZSBhYmlsaXR5IHRvIGVhc2lseSBjcmVhdGUgcGxvdHMgb2Ygc3BhdGlhbCBkYXRhLg0KDQojIyMgUGxvdHRpbmcgYHNmY2Agb2JqZWN0cyAoVmlkZW86IFtZb3VUdWJlXShodHRwczovL3lvdXR1LmJlL09uWm5pWUJqZlJVKSwgW1Bhbm9wdG9dKGh0dHBzOi8vdWNkZW52ZXIuaG9zdGVkLnBhbm9wdG8uY29tL1Bhbm9wdG8vUGFnZXMvVmlld2VyLmFzcHg/aWQ9MGU2MGQwNmQtZDNiMy00MDc5LTliODItYWYzMjAxNTQyNmIxKSkNCg0KVG8gc2ltcGx5IHBsb3QgdGhlIGdlb21ldHJ5IGxpc3QgY29sdW1uIG9mIGFuIGBzZmAgb2JqZWN0LCB5b3UgY2FuIHVzZToNCg0KKiBgc3RfZ2VvbWV0cnlgIHRvIGV4dHJhY3QgdGhlIGxpc3QgY29sdW1uIG9mIHNpbXBsZSBmZWF0dXJlIGdlb21ldHJpZXMgZnJvbSB0aGUgYHNmYCBvYmplY3QgKHRoaXMgd2lsbCBiZSBhbiBvYmplY3Qgb2YgY2xhc3MgYHNmY2ApLg0KKiBgcGxvdGAgdG8gcGxvdCB0aGUgYHNmY2Agb2JqZWN0Lg0KDQpgYGB7cn0NCnBsb3Qoc3RfZ2VvbWV0cnkodXNfc2YpKQ0KYGBgDQoNCkFsdGVybmF0aXZlbHksIHlvdSBjYW4gZGlyZWN0bHkgZXh0cmFjdCB0aGUgYHNmY2AgY29tcG9uZW50IG9mIHRoZSBgc2ZgIG9iamVjdCB1c2luZyBgJGAsIHRoZW4gcGxvdCB0aGUgYHNmY2Agb2JqZWN0Lg0KDQpgYGB7cn0NCnBsb3QodXNfc2YkZ2VvbWV0cnkpDQpgYGANCg0KWW91IGNhbiBlYXNpbHkgY2hhbmdlIGFzcGVjdHMgb2YgdGhlIHBsb3R0ZWQgYHNmY2Agb2JqZWN0IChvciBhbiBgc2ZgKSBvYmplY3QgdXNpbmcgdGhlIHN0YW5kYXJkIGFyZ3VtZW50czoNCg0KKiBgYXhlc2AgY2FuIGJlIHNldCB0byBgVFJVRWAgdG8gc2hvdyB0aGUgYXhlcw0KKiBgeGxhYmAgYW5kIGB5bGFiYCB3aWxsIGNoYW5nZSB0aGUgYXhpcyBsYWJlbHMNCiogYHhsaW1gIGFuZCBgeWxpbWAgY2FuIGJlIHVzZWQgdG8gY29uc3RyYWluIHRoZSBwbG90dGluZyByZWdpb25zLg0KICAqIE5vdGUgdGhhdCAiVyIgbG9uZ2l0dWRlIHZhbHVlcyBhcmUgaW5kaWNhdGVkIHVzaW5nIG5lZ2F0aXZlIG51bWJlcnMsIHdoaWxlICJFIiBsb25naXR1ZGUgdmFsdWVzIGFyZSBwb3NpdGl2ZSBudW1iZXJzLg0KICAqIE5vdGUgdGhhdCAiTiIgbGF0aXR1ZGUgdmFsdWVzIGluIHRoZSBub3J0aGVybiBoZW1pc3BoZXJlIGFyZSBwb3NpdGl2ZSBudW1iZXJzLiAiUyIgbGF0aXR1ZGUgdmFsdWVzIGluIHRoZSBzb3V0aGVybiBoZW1pc3BoZXJlIGFyZSBuZWdhdGl2ZSBudW1iZXJzLg0KDQpFYWNoIGdlb21ldHJ5IGhhcyBzcGVjaWZpYyBhcmd1bWVudHMgdGhhdCB0aGUgdXNlciBjYW4gY2hhbmdlIChjb25zaWRlciBsb29raW5nIGF0IHRoZSBkb2N1bWVudGF0aW9uIGZvciBgP3NmOjpwbG90LnNmYCBmb3IgZGV0YWlscykuIEluIHRoaXMgY2FzZSwgd2UgY2FuIGNoYW5nZSB0aGUgZmlsbCBjb2xvciBvZiB0aGUgYE1VTFRJUE9MWUdPTmAgb2JqZWN0cyB1c2luZyB0aGUgYGNvbG9yYCBhcmd1bWVudCBhbmQgdGhlIGJvcmRlciB1c2luZyB0aGUgYm9yZGVyIGFyZ3VtZW50Lg0KDQpgYGB7cn0NCnBsb3QodXNfc2YkZ2VvbWV0cnksIGF4ZXMgPSBUUlVFLA0KICAgICB4bGFiID0gImxvbmdpdHVkZSIsIHlsYWIgPSAibGF0aXR1ZGUiLA0KICAgICBjb2wgPSAiZ3JleSIsIGJvcmRlciA9ICJibHVlIiwNCiAgICAgeGxpbSA9IGMoLTEyNSwgLTc1KSwNCiAgICAgeWxpbSA9IGMoMjIsIDUwKSkNCmBgYA0KDQpZb3UgY2FuIGNoYW5nZSB0aGUgY29sb3JzIG9mIHRoZSBpbmRpdmlkdWFsIGdlb21ldHJ5IG9iamVjdHMgd2l0aCBhIGxpdHRsZSBjcmVhdGl2aXR5LiBMZXQncyBjb2xvciBhbGwgdGhlICJDIHN0YXRlcyIgKENhbGlmb3JuaWEsIENvbG9yYWRvLCBDb25uZWN0aWN1dCkgYSBsaXR0bGUgZGlmZmVyZW50bHkgdGhhbiB0aGUgb3RoZXIgc3RhdGVzLg0KDQpgYGB7cn0NCiMgZGV0ZXJtaW5lIGluZGljZXMgb2YgQyBzdGF0ZXMNCmNfc3RhdGVzIDwtIHN0YXJ0c1dpdGgodXNfc2YkU1RVU1BTLCAiQyIpDQojIGNyZWF0ZSBhIGNoYXJhY3RlciB2ZWN0b3Igb2YgcmVwbGljYXRlZCAiZ3JleSINCiMgdmFsdWVzIG1hdGNoaW5nIHRoZSBudW1iZXIgb2Ygcm93cyBpbiB1c19zZg0KbXljb2wgPC0gcmVwKCJncmV5IiwgbnJvdyh1c19zZikpDQojY2hhbmdlICJncmV5IiB0byAib3JhbmdlIiBmb3IgdGhlIGNfc3RhdGVzIGluZGljZXMNCm15Y29sW2Nfc3RhdGVzXSA8LSAib3JhbmdlIg0KIyBjcmVhdGUgYSBjaGFyYWN0ZXIgdmVjdG9yIG9mIHJlcGxpY2F0ZWQgInllbGxvdyINCiMgdmFsdWVzIG1hdGNoaW5nIHRoZSBudW1iZXIgb2Ygcm93cyBpbiB1c19zZg0KbXlib3JkZXIgPC0gcmVwKCJ5ZWxsb3ciLCBucm93KHVzX3NmKSkNCiNjaGFuZ2UgInllbGxvdyIgdG8gImJsdWUiIGZvciB0aGUgY19zdGF0ZXMgaW5kaWNlcw0KbXlib3JkZXJbY19zdGF0ZXNdIDwtICJibHVlIg0KIyBjcmVhdGUgdGhlIGN1c3RvbWl6ZWQgcGxvdA0KcGxvdChzdF9nZW9tZXRyeSh1c19zZiksIGNvbCA9IG15Y29sLCBib3JkZXIgPSBteWJvcmRlciwgeGxpbSA9IGMoLTEyNSwgLTc1KSwNCiAgICAgeWxpbSA9IGMoMjIsIDUwKSkNCmBgYA0KDQojIyMgQ3VzdG9taXplIGNvbG9yIGZvciBzdWJzZXQgb2YgYHNmYCBvYmplY3QgKFZpZGVvOiBbWW91VHViZV0oaHR0cHM6Ly95b3V0dS5iZS9mX20tNXN1eDhvTSksIFtQYW5vcHRvXShodHRwczovL3VjZGVudmVyLmhvc3RlZC5wYW5vcHRvLmNvbS9QYW5vcHRvL1BhZ2VzL1ZpZXdlci5hc3B4P2lkPWFmMDI2ZTExLTZiZTktNDgxOS04NzAwLWFmMzIwMTY1ZjNjOSkpDQoNCkxldCdzIGxvb2sgYXQgYSBkaWZmZXJlbnQgYXBwcm9hY2ggdG8gY29sb3JpbmcgYSBzdWJzZXQgb2YgdGhlIGBzZmAgb2JqZWN0IGRpZmZlcmVudGx5IGZyb20gdGhlIHJlc3Qgb2YgdGhlIGBzZmAgb2JqZWN0LiANCg0KYGBge3J9DQojIGRyYXcgYWxsIGdlb21ldHJpZXMNCnBsb3Qoc3RfZ2VvbWV0cnkodXNfc2YpLCBjb2wgPSAiZ3JleSIsIGJvcmRlciA9ICJ5ZWxsb3ciLA0KICAgICB4bGltID0gYygtMTI1LCAtNzUpLCB5bGltID0gYygyMiwgNTApKQ0KIyBwbG90IHN1YnNldCBvZiBhbGwgZ2VvbWV0cmllcyB3aXRoIGRpZmZlcmVudCBjb2xvcnMNCiMgYWRkID0gVFJVRSBvdmVybGF5cyBuZXcgZHJhd2luZyBvbiBjdXJyZW50IGdyYXBoaWMNCnBsb3Qoc3RfZ2VvbWV0cnkodXNfc2YpW2Nfc3RhdGVzXSwNCiAgICAgY29sID0gIm9yYW5nZSIsIGJvcmRlciA9ICJibHVlIiwNCiAgICAgYWRkID0gVFJVRSkNCmBgYA0KDQoNCiMjIyBQbG90dGluZyBhdHRyaWJ1dGVzIG9mIGFuIGBzZmAgb2JqZWN0IChWaWRlbzogW1lvdVR1YmVdKGh0dHBzOi8veW91dHUuYmUvVFY4QVRQR29kT2cpLCBbUGFub3B0b10oaHR0cHM6Ly91Y2RlbnZlci5ob3N0ZWQucGFub3B0by5jb20vUGFub3B0by9QYWdlcy9WaWV3ZXIuYXNweD9pZD0yNzVkZjA3YS1kNGFjLTQ3M2ItODFiYS1hZjMyMDE1NDI3MjQpKQ0KDQpCeSBkZWZhdWx0LCB1c2UgeW91IGNhbiB1c2UgdGhlIGBwbG90YCBmdW5jdGlvbiB0byBwbG90IGFsbCB0aGUgYXR0cmlidXRlcyBvZiBhbiBgc2ZgIG9iamVjdC4gSW4gZ2VuZXJhbCwgdGhpcyBpc24ndCB2ZXJ5IHVzZWZ1bC4NCg0KYGBge3J9DQpwbG90KGNvdmlkX3VzKQ0KYGBgDQoNCk1vcmUgbGlrZWx5LCB5b3Ugd2lsbCB3YW50IHRvIHBsb3QgYSBzaW5nbGUgYXR0cmlidXRlICh2YXJpYWJsZSksIHdoaWNoIGNhbiBiZSBkb25lIGJ5IHN1YnNldHRpbmcgdGhhdCB2YXJpYWJsZSBpbiB0aGUgYHNmYCBvYmplY3QgYW5kIHBsb3R0aW5nIHRoZSBzdWJzZXR0ZWQgb2JqZWN0Lg0KDQpMZXQncyBwbG90IHRoZSBsYW5kIGFyZWEgb2YgdGhlIHN0YXRlcywgZXhjbHVkaW5nIEFsYXNrYSBhbmQgSGF3YWlpLiBGaXJzdCwgd2UgZXhjbHVkZSB0aGUgQWxhc2thIGFuZCBIYXdhaWkgcm93cyAoYW5kIHNhdmUgdGhlIGZpbHRlcmVkIG9iamVjdCkuDQoNCmBgYHtyfQ0KY292aWRfdXMgPC0gY292aWRfdXMgJT4lIGZpbHRlcighaXMuZWxlbWVudChTVFVTUFMsIGMoIkFLIiwgIkhJIikpKQ0KcGxvdChjb3ZpZF91c1siQUxBTkQiXSkNCmBgYA0KDQpMYW5kIGFyZWEgaXMgZGlyZWN0bHkgcmVsYXRlZCB0byB0aGUgYXJlYSBvZiB0aGUgc3RhdGUuIEl0IHdvdWxkIGJlIGludGVyZXN0aW5nIHRvIHZpc3VhbGl6ZSB0aGUgc3RhdGVzIHRoYXQgaGF2ZSB0aGUgZ3JlYXRlc3QgcGVyY2VudGFnZSBvZiB3YXRlci4gTGV0J3MgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGluIGBjb3ZpZF91c2AgdGhhdCBjb21wdXRlcyB0aGUgcGVyY2VudGFnZSBvZiB0aGUgc3RhdGUgdGhhdCBpcyB3YXRlci4gV2UnbGwgdGhlbiBwbG90IHRoaXMgdmFyaWFibGUgKGV4Y2x1ZGluZyBBbGFza2EgYW5kIEhhd2FpaSkNCg0KYGBge3J9DQpjb3ZpZF91cyA8LSBjb3ZpZF91cyAlPiUgbXV0YXRlKHByb3Bfd2F0ZXIgPSBBV0FURVIvKEFXQVRFUiArIEFMQU5EKSkNCnBsb3QoY292aWRfdXNbInByb3Bfd2F0ZXIiXSkNCmBgYA0KDQpOb3Qgc3VycHJpc2luZ2x5LCBjb2FzdGFsIHN0YXRlcyBhbmQgc3RhdGVzIG9uIHRoZSBHcmVhdCBMYWtlcyBoYXZlIHRoZSBoaWdoZXN0IHBlcmNlbnRhZ2Ugb2Ygd2F0ZXIuDQoNCldlIGNhbiB1c2UgdGhlIGZvbGxvd2luZyBjb21tYW5kcyB0byBpZGVudGlmeSB0aGUgc3RhdGVzIHdpdGggdGhlIDYgbGFyZ2VzdCBwcm9wb3J0aW9ucyBvZiB3YXRlci4NCmBgYHtyfQ0KY292aWRfdXMgJT4lIHNsaWNlX21heChwcm9wX3dhdGVyLCBuID0gNikgJT4lIHNlbGVjdChOQU1FLCBwcm9wX3dhdGVyKQ0KYGBgDQoNCldlIGNhbiBjaGFuZ2UgdGhlIG51bWJlciBvZiBicmVha3MgaW4gb3VyIGNvbG9yIGJhciB2aWEgdGhlIGBuYnJlYWtzYC4NCg0KV2UgY2FuIGNoYW5nZSB0aGUgY29sb3JzIGluIG91ciBjb2xvciBiYXIgYnkgc3BlY2lmeWluZyB0aGUgYHBhbGAgYXJndW1lbnQuDQoNCiogVGhlIGBwYWxgIGFyZ3VtZW50IHRha2VzIGEgZnVuY3Rpb24gdGhhdCwgd2hlbiBnaXZlbiBgbmAsIHRoZSBudW1iZXIgb2YgZGVzaXJlZCBjb2xvcnMsIHJldHVybnMgYSB2ZWN0b3Igb2YgY29sb3JzLg0KKiBUaGUgYGhjbC5jb2xvcnNgLCBgcmFpbmJvd2AsIGBoZWF0LmNvbG9yc2AsIGB0ZXJyYWluLmNvbG9yc2AsIGB0b3BvLmNvbG9yc2AsIGFuZCBgY20uY29sb3JzYCBhcmUgYWxsIGNvbG9yIHBhbGV0dGVzIGluIGJhc2UgUiB0aGF0IGNhbiBiZSB1c2VkIHRvIGNoYW5nZSB0aGUgY29sb3JzIHBhbGV0dGVzLg0KKiBUaGUgYGhjbC5jb2xvcnNgIHBhbGV0dGUgaXMgcGFydGljdWxhcmx5IGdvb2QsIGFzIGl0IGluY2x1ZGVzIGNvbG9yIHBhbGV0dGVzIGB2aXJpZGlzYCBhbmQgYGNpdmlkaXNgIChjb3JyZWN0ZWQgdmlyaWRpcykgdGhhdCBhcmUgcGFydGljdWxhcmx5IHdlbGwtc3VpdGVkIHRvIGRpc3BsYXlpbmcgY29sb3JzIHRoYXQgYXJlIGNvbG9yLWJsaW5kIGFuZCBjYW4gYmUgdW5kZXJzdG9vZCB3aGVuIHByaW50ZWQgaW4gYmxhY2sgYW5kIHdoaXRlLg0KKiBUaGUgKipjb2xvcnNwYWNlKiogcGFja2FnZSBhbHNvIGhhcyBhIHdpZGUgdmFyaWV0eSBvZiBwYWxldHRlcyB5b3UgbWlnaHQgY29uc2lkZXIuDQoNCkhvd2V2ZXIsIHRoZSBgaGNsLmNvbG9yc2AgZnVuY3Rpb24gaGFzIGEgYHBhbGV0dGVgIGFyZ3VtZW50IHRvIHNwZWNpZnkgdGhlIGRlc2lyZWQgcGFsZXR0ZS4gVG8gdXNlIGFuIGBoY2wuY29sb3JgIHBhbGV0dGUgd2hlbiBwbG90dGluZyBhbiBgc2ZgIG9iamVjdCwgd2UgbmVlZCB0byBidWlsZCBhIGN1c3RvbSBwYWxldHRlIHRoYXQgb25seSByZXF1aXJlcyB0aGUgbnVtYmVyIG9mIGNvbG9ycyBkZXNpcmVkLiBXZSBjcmVhdGUgdGhvc2UgZnVuY3Rpb24gZm9yIHRoZSB2aXJpZGlzIGFuZCBjaXZpZGlzIHBhbGV0dGVzIGJlbG93LiBXZSB0aGVuIHNlZSB0aGF0IGl0IHByb2R1Y2VzIHRoZSBkZXNpcmVkIHJlc3VsdHMuDQoNCmBgYHtyfQ0KdmlyaWRpc19wYWwgPC0gZnVuY3Rpb24obikgaGNsLmNvbG9ycyhuLCBwYWxldHRlID0gInZpcmlkaXMiKQ0KY2l2aWRpc19wYWwgPC0gZnVuY3Rpb24obikgaGNsLmNvbG9ycyhuLCBwYWxldHRlID0gImNpdmlkaXMiKQ0KIyBkaXJ0eSBhcHByb2FjaCB0byBzZWUgY29sb3JzIGluIHRoZSBwYWxldHRlDQppbWFnZShtYXRyaXgoMDo0KSwgY29sID0gdmlyaWRpc19wYWwoNSkpDQppbWFnZShtYXRyaXgoMDo0KSwgY29sID0gY2l2aWRpc19wYWwoNSkpDQpgYGANCg0KDQpMZXQncyBkbyBzb21lIGFuYWx5c2lzIG9mIHRoZSBhY3R1YWwgQ09WSUQtMTkgZGF0YS4gTGV0J3MgY3JlYXRlIGEgbmV3IGNvbHVtbiBmb3IgYGRlYXRoX3JhdGVfMTAwa2AsIHdoaWNoIGlzIHRoZSBudW1iZXIgb2YgY29uZmlybWVkIGFuZCBwcm9iYWJsZSBDT1ZJRC0xOSBkZWF0aHMgcGVyIDEwMCwwMDAgcGVyc29ucy4gTGV0J3MgZGlzcGxheSB0aGUgZGVhdGggcmF0ZSB1c2luZyB0aGUgdmlyaWRpcyBwYWxldHRlIGFuZCB0aGVuIHRoZSBjaXZpZGlzIHBhbGV0dGUuDQoNCmBgYHtyfQ0KY292aWRfdXMgPC0gY292aWRfdXMgJT4lIG11dGF0ZShkZWF0aF9yYXRlXzEwMGsgPSBkZWF0aHMvcG9wdWxhdGlvbioxMDAwMDApDQpwbG90KGNvdmlkX3VzWyJkZWF0aF9yYXRlXzEwMGsiXSwNCiAgICAgbmJyZWFrcyA9IDUsDQogICAgIHBhbCA9IHZpcmlkaXNfcGFsKQ0KcGxvdChjb3ZpZF91c1siZGVhdGhfcmF0ZV8xMDBrIl0sDQogICAgIG5icmVha3MgPSA1LA0KICAgICBwYWwgPSBjaXZpZGlzX3BhbCkNCmBgYA0KDQojIyMgUGxvdHRpbmcgYHNmYCBvYmplY3RzIHdpdGggKipnZ3Bsb3QyKiogKFZpZGVvOiBbWW91VHViZV0oaHR0cHM6Ly95b3V0dS5iZS9NN2tobElSbHI0cyksIFtQYW5vcHRvXShodHRwczovL3VjZGVudmVyLmhvc3RlZC5wYW5vcHRvLmNvbS9QYW5vcHRvL1BhZ2VzL1ZpZXdlci5hc3B4P2lkPTk0ZjJlMWE5LTE5YWEtNDQ3Ny1hZWI1LWFmMzIwMTU0MjZmNCkpDQoNClRoZSAqKmdncGxvdDIqKiBwYWNrYWdlIGluY2x1ZGVzIGEgZ2VvbWV0cnkgZm9yIGBzZmAgb2JqZWN0cywgYGdlb21fc2ZgLCB0aGUgaXMgdHlwaWNhbGx5IGFkZXF1YXRlIGZvciB1c2luZyAqKmdncGxvdDIqKiB0byBwcm9kdWNlIGdyYXBoaWNzIGZvciBgc2ZgIG9iamVjdHMuDQoNCiogVGhlIGBzZmAgb2JqZWN0IGlzIHBhc3NlZCBhcyB0aGUgYGRhdGFgIGFyZ3VtZW50IHRvIHRoZSBgZ2dwbG90YCBmdW5jdGlvbi4NCiogYGdlb21fc2ZgIGlzIHRoZSBnZW9tZXRyeSBvZiB0aGUgYGRhdGFgDQoqIFRoZSBgWFlgIGNvb3JkaW5hdGVzIG9mIHRoZSBgc2ZgIG9iamVjdCBhcmUgYXV0b21hdGljYWxseSBtYXBwZWQgdG8gYHhgIGFuZCBgeWAgYWVzdGhldGljcy4NCiogVGhlIGBjb2xvcmAsIGBsaW5ldHlwZWAsIGBmaWxsYCwgZXRjLiwgb2YgdGhlIGdlb21ldHJ5IG9iamVjdHMgY2FuIGJlIGNoYW5nZWQgYnkgbWFwcGluZyBhbiBhdHRyaWJ1dGUgb2YgdGhlIGBzZmAgb2JqZWN0IHRvIHRoZSBhcHByb3ByaWF0ZSBhZXN0aGV0aWMuDQoNCklmIHdlIG9ubHkgd2FudCB0byBwbG90IHRoZSBnZW9tZXRyeSBsaXN0IGNvbHVtbiAoaS5lLiwgdGhlIGdlb21ldHJ5IG9iamVjdHMpIG9mIGVhY2ggb2JzZXJ2YXRpb24sIHRoZW4gd2UgZG9uJ3QgaGF2ZSB0byBzcGVjaWZ5IGFueSBhZXN0aGV0aWNzLg0KDQpgYGB7cn0NCmdncGxvdChjb3ZpZF91cykgKyBnZW9tX3NmKCkNCmBgYA0KDQpXZSB3aWxsIGNyZWF0ZSBhIGNob3JvcGxldGggbWFwIG9mIHRoZSBgY292aWRfdXNgIGRhdGEuDQogICogQSBjaG9yb3BsZXRoIG1hcCBpcyBhIG1hcCBvZiByZWdpb25zIGNvbG9yZWQgdG8gaW5kaWNhdGUgdGhlIGxldmVsIG9mIHNvbWUgdmFyaWFibGUgYXNzb2NpYXRlZCB3aXRoIHRoZSByZWdpb25zLg0KDQpgYGB7cn0NCmdncGxvdChjb3ZpZF91cykgKyBnZW9tX3NmKGFlcyhmaWxsID0gZGVhdGhfcmF0ZV8xMDBrKSkNCmBgYA0KDQpDaGFuZ2luZyB0aGUgY29sb3IgcGFsZXR0ZSBmb3Igb3VyIGBmaWxsYCBhZXN0aGV0aWMgdG8gYSB2aXJpZGlzIHBhbGV0dGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGNvdmlkX3VzKSArIGdlb21fc2YoYWVzKGZpbGwgPSBkZWF0aF9yYXRlXzEwMGspKSArDQogIGNvbG9yc3BhY2U6OnNjYWxlX2ZpbGxfY29udGludW91c19zZXF1ZW50aWFsKHBhbGV0dGUgPSAidmlyaWRpcyIpDQpgYGANCg0KQ2hhbmdlIHRoZSBjb2xvciBwYWxldHRlIHRvIHZpcmlkaXMgdXNpbmcgKipnZ3Bsb3QyKioncyBidWlsdC1pbiBmdW5jdGlvbi4NCg0KYGBge3J9DQpnZ3Bsb3QoY292aWRfdXMpICsgZ2VvbV9zZihhZXMoZmlsbCA9IGRlYXRoX3JhdGVfMTAwaykpICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInZpcmlkaXMiKQ0KYGBgDQoNClJldmVyc2UgdGhlIG9yZGVyIG9mIHRoZSBjb2xvcnMuDQoNCmBgYHtyfQ0KZ2dwbG90KGNvdmlkX3VzKSArIGdlb21fc2YoYWVzKGZpbGwgPSBkZWF0aF9yYXRlXzEwMGspKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJ2aXJpZGlzIiwgZGlyZWN0aW9uID0gLTEpDQpgYGANCg0KVXNlIHRoZSBjaXZpZGlzIHBhbGV0dGUgdXNpbmcgdGhlIGBzY2FsZV9maWxsX3ZpcmlkaXNgIGZ1bmN0aW9uIGZyb20gdGhlICoqdmlyaWRpcyoqIHBhY2thZ2UgW0BSLXZpcmlkaXNdLg0KDQpgYGB7cn0NCmdncGxvdChjb3ZpZF91cykgKyBnZW9tX3NmKGFlcyhmaWxsID0gZGVhdGhfcmF0ZV8xMDBrKSkgKw0KICB2aXJpZGlzOjpzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIkUiKQ0KYGBgDQoNCiMjIEFkZGl0aW9uYWwgcmVzb3VyY2VzDQoNCioqc2YqKiBoZWxwIGFuZCB0dXRvcmlhbHMgZnJvbSB0aGUgcGFja2FnZSBhdXRob3JzIFtodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vc2YvXQ0KDQoqR2VvY29tcHV0YXRpb24gd2l0aCBSKiBieSBSb2JpbiBMb3ZlbGFjZSBbaHR0cHM6Ly9ib29rZG93bi5vcmcvcm9iaW5sb3ZlbGFjZS9nZW9jb21wci9dLiBUaGlzIGJvb2sgY292ZXJzIHRvbnMgb2YgYXNwZWN0IG9mIHNwYXRpYWwgZGF0YSAobm90IGp1c3QgZnJvbSBhbiBSIHBlcnNwZWN0aXZlLCBidXQgZ2VuZXJhbCB0aGVvcnkgYW5kIGNvbmNlcHRzKS4gVGhpcyB3aWxsIGhlbHAgeW91IHRvIGxlYXJuIGEgbG90IG1vcmUgYWJvdXQgcmVwcmVzZW50aW5nIGFuZCB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhIGluIGdlbmVyYWwsIG5vdCBqdXN0IHdvcmtpbmcgd2l0aCBpdCBpbiBSLg0KDQoqU3BhdGlhbCBEYXRhIFNjaWVuY2Ugd2l0aCBhcHBsaWNhdGlvbnMgaW4gUiogYnkgRWR6ZXIgUGViZXNtYSBhbmQgUm9nZXIgQml2YW5kIFtodHRwczovL2tlZW4tc3dhcnR6LTMxNDZjNC5uZXRsaWZ5LmFwcC9dLiBBbHNvIGNvdmVycyBtdWNoIG11Y2ggYWJvdXQgcmVwcmVzZW50aW5nIHNwYXRpYWwgZGF0YSB0aGFuIHlvdSBwcm9iYWJseSBldmVyIHRob3VnaHQgcG9zc2libGUhIFRoZSBhdXRob3JzIGFyZSB0aGUgbWFpbiBjcmVhdG9ycyBvZiB0aGUgKipzZioqIHBhY2thZ2UuDQoNCiMjIENvbG9yIHBhbGV0dGVzIGF2YWlsYWJsZSB3aXRoICoqYmFzZSBSKioNCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCnJlcXVpcmUoImdyYXBoaWNzIikNCg0KcGFyKG1mcm93ID0gYygxLCAxKSkNCg0KIyMgY29sb3Igc3dhdGNoZXMgZm9yIFJHQi9IU1YgcGFsZXR0ZXMNCmRlbW8ucGFsIDwtDQogIGZ1bmN0aW9uKG4sIGJvcmRlciA9IGlmIChuIDwgMzIpICJsaWdodCBncmF5IiBlbHNlIE5BLA0KICAgICAgICAgICBtYWluID0gcGFzdGUoImNvbG9yIHBhbGV0dGVzOyAgbj0iLCBuKSwNCiAgICAgICAgICAgY2guY29sID0gYygicmFpbmJvdyhuLCBzdGFydD0uNywgZW5kPS4xKSIsIA0KICAgICAgICAgICAgICAgICAgICAgICJoZWF0LmNvbG9ycyhuKSIsDQogICAgICAgICAgICAgICAgICAgICAgInRlcnJhaW4uY29sb3JzKG4pIiwgInRvcG8uY29sb3JzKG4pIiwNCiAgICAgICAgICAgICAgICAgICAgICAiY20uY29sb3JzKG4pIikpIHsNCiAgICBudCA8LSBsZW5ndGgoY2guY29sKQ0KICAgIGkgPC0gMTpuOyBqIDwtIG4gLyBudDsgZCA8LSBqLzY7IGR5IDwtIDIqZA0KICAgIHBsb3QoaSwgaStkLCB0eXBlID0gIm4iLCB5YXh0ID0gIm4iLCB5bGFiID0gIiIsIG1haW4gPSBtYWluKQ0KICAgIGZvciAoayBpbiAxOm50KSB7DQogICAgICAgIHJlY3QoaS0uNSwgKGstMSkqaisgZHksIGkrLjQsIGsqaiwNCiAgICAgICAgICAgICBjb2wgPSBldmFsKHN0cjJsYW5nKGNoLmNvbFtrXSkpLCBib3JkZXIgPSBib3JkZXIpDQogICAgICAgIHRleHQoMipqLCAgayAqIGogKyBkeS80LCBjaC5jb2xba10pDQogICAgfQ0KfQ0KZGVtby5wYWwoMTYpDQoNCiMjIGNvbG9yIHN3YXRjaGVzIGZvciBIQ0wgcGFsZXR0ZXMNCmhjbC5zd2F0Y2ggPC0gZnVuY3Rpb24odHlwZSA9IE5VTEwsIG4gPSA1LCBucm93ID0gNCwNCiAgYm9yZGVyID0gaWYgKG4gPCAxNSkgImJsYWNrIiBlbHNlIE5BKSB7DQogICAgcGFsZXR0ZSA8LSBoY2wucGFscyh0eXBlKQ0KICAgIGNvbHMgPC0gc2FwcGx5KHBhbGV0dGUsIGhjbC5jb2xvcnMsIG4gPSBuKQ0KICAgIG5jb2wgPC0gbmNvbChjb2xzKQ0KICAgIG5zd2F0Y2ggPC0gbWluKG5jb2wsIG5yb3cpDQoNCiAgICBwYXIobWFyID0gcmVwKDAuMSwgNCksDQogICAgICAgIG1mcm93ID0gYygxLCBtaW4oNSwgY2VpbGluZyhuY29sL25yb3cpKSksDQogICAgICAgIHBpbiA9IGMoMSwgMC41ICogbnN3YXRjaCksDQogICAgICAgIGNleCA9IDAuNykNCg0KICAgIHdoaWxlIChsZW5ndGgocGFsZXR0ZSkpIHsNCiAgICAgICAgc3Vic2V0IDwtIDE6bWluKG5yb3csIG5jb2woY29scykpDQogICAgICAgIHBsb3QubmV3KCkNCiAgICAgICAgcGxvdC53aW5kb3coYygwLCBuKSwgYygwLCBucm93ICsgMSkpDQogICAgICAgIHRleHQoMCwgcmV2KHN1YnNldCkgKyAwLjEsIHBhbGV0dGVbc3Vic2V0XSwgYWRqID0gYygwLCAwKSkNCiAgICAgICAgeSA8LSByZXAoc3Vic2V0LCBlYWNoID0gbikNCiAgICAgICAgcmVjdChyZXAoMDoobi0xKSwgbiksIHJldih5KSwgcmVwKDE6biwgbiksIHJldih5KSAtIDAuNSwNCiAgICAgICAgICAgICBjb2wgPSBjb2xzWywgc3Vic2V0XSwgYm9yZGVyID0gYm9yZGVyKQ0KICAgICAgICBwYWxldHRlIDwtIHBhbGV0dGVbLXN1YnNldF0NCiAgICAgICAgY29scyA8LSBjb2xzWywgLXN1YnNldCwgZHJvcCA9IEZBTFNFXQ0KICAgIH0NCg0KICAgIHBhcihtZnJvdyA9IGMoMSwgMSksIG1hciA9IGMoNS4xLCA0LjEsIDQuMSwgMi4xKSwgY2V4ID0gMSkNCn0NCg0KaGNsLnN3YXRjaCgicXVhbGl0YXRpdmUiKQ0KaGNsLnN3YXRjaCgic2VxdWVudGlhbCIpDQpoY2wuc3dhdGNoKCJkaXZlcmdpbmciKQ0KaGNsLnN3YXRjaCgiZGl2ZXJnaW5neCIpDQpgYGANCg0KIyMgUmVmZXJlbmNlcw0K