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:
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:
- A geometry object (e.g., a point, a polygon, a line, etc.)
- 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.
Simple feature geometry objects (Video: YouTube, Panopto)
The most common simple feature geometry objects used by data
scientists are:
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:
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.
- This means that you can work with
sf
objects similarly
to how you would work with a data.frame
, though the default
behaviors may be different.
- We can use the dplyr package or similar tools to
manipulate an
sf
object.
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.
- The
sf
object must be the first argument of these
functions.
Let’s access a COVID-19 related data frame available in the
bayesutils package, which can be installed from
GitHub.
- We install the package from GitHub using the
remote::install_github
function.
- Make sure to install the remotes package (Csárdi et al. 2021) if you don’t have it.
- We then load the
covid_20210307
data set from the
bayesutils package.
# 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