All raster operations in this topic are accomplished using the
raster
library.
library(raster)
Loading required package: sp
Raster are representations of continuous, or semi-continuous, data.
TYou can envision a raster just like an image. When me make a
leaflet()
map and how the tiles, each pixel is colored a
particular value representing elevation, temperature, precipitation,
habitat type, or whatever. This is exactly the same for rasters. The
key point here is that each pixel represents some defined
region on the earth and as such the raster itself is georeferenced. It
has a coordinate reference system (CRS), boundaries, etc.
Making Rasters de novo
A raster is simply a matrix with rows and columns and each element
has a value associated with it. You can create a raster de novo
by making a matrix of data and filling it with values, then turning it
into a raster.
Here I make a raster with random numbrers selected from the Poisson
Distribution (fishy, I know) using the rpois()
function. I
then turn it into a matrix with 7 rows (and 7 columns).
vals <- rpois(49, lambda=12)
x <- matrix( vals, nrow=7)
x
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] 7 10 15 13 12 8 17
[2,] 14 17 22 19 10 9 8
[3,] 13 10 7 8 9 7 12
[4,] 10 11 5 15 10 13 19
[5,] 14 10 13 13 12 12 15
[6,] 13 8 14 14 11 16 14
[7,] 7 9 11 10 16 14 4
While we haven’t used matrices much thus far, it is a lot like a
data.frame
with respect to getting and setting values using
numerical indices. For example, the value of the 3rd row and 5th column
is:
x[3,5]
[1] 9
To convert this set of data, as a matrix, into a geospatially
referenced raster()
object we do the following:
r <- raster( x )
r
class : RasterLayer
dimensions : 7, 7, 49 (nrow, ncol, ncell)
resolution : 0.1428571, 0.1428571 (x, y)
extent : 0, 1, 0, 1 (xmin, xmax, ymin, ymax)
crs : NA
source : memory
names : layer
values : 4, 22 (min, max)
Notice that when I plot it out, it does not show the data, but a
summary of the data along with some key data about the contents,
including:
- A class definition
- The dimensions of the underlying data matrix,
- The resolution (e.g., the spatial extent of the sides of each pixel).
Since we have no CRS here, it is equal to \(nrows(x)^{-1}\) and \(ncols(x)^{-1}\).
- The extent (the bounding box) and again since we do not have a CRS
defined it just goes from \(0\) to
\(1\). - The crs
(missing)
- The source can be either memory
if the raster is not that
big or out of memory
if it is just referencing.
If these data represent something on the planet, we can assign the
dimensions and CRS
values to it and use it in our normal
day-to-day operations.
Loading Rasters from Files or URLs
We can also grab a raster object from the filesystem or from some
online repository by passing the link to the raster()
function. Here is the elevation, in meters, of the region in which
Mexico is found. To load it in, pass the url.
url <- "https://github.com/dyerlab/ENVS-Lectures/raw/master/data/alt_22.tif"
r <- raster::raster( url )
r
class : RasterLayer
dimensions : 3600, 3600, 12960000 (nrow, ncol, ncell)
resolution : 0.008333333, 0.008333333 (x, y)
extent : -120, -90, 0, 30 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs
source : alt_22.tif
names : alt_22
values : -202, 5469 (min, max)
Notice that this raster has a defined CRS and as such it is projected
and the extent relates to the units of the datum (e.g., from -120 to -90
degrees longitude and 0 to 30 degrees latitude).
If we plot it, we can see the whole raster.
plot(r)

Now, this raster is elevation where there is land but where there is
no land, it is full of NA
values. As such, there is a ton
of them.
format( sum( is.na( values(r) ) ), big.mark = "," )
[1] "10,490,650"
10,490,650
Cropping
One of the first things to do is to crop the data down to represent
the size and extent of our study area. If we over 10 million missing
data points (the ocean) and most of Mexico in this raster above but we
are only working with sites in Baja California (Norte y Sur), we would
do well to excise (or crop) the raster to only include the area we are
interested in working with.
Top do this, we need to figure out a bounding box (e.g., the minimim
and maximum values of longitude and latitude that enclose our data).
Let’s assume we are working with the Beetle Data from the Spatial
Points Slides and load in the Sex-biased dispersal data set and use
those points as a starting estimate of the bounding box.
library( sf )
Linking to GEOS 3.9.1, GDAL 3.4.0, PROJ 8.1.1; sf_use_s2() is TRUE
library( tidyverse )
beetle_url <- "https://raw.githubusercontent.com/dyerlab/ENVS-Lectures/master/data/Araptus_Disperal_Bias.csv"
read_csv( beetle_url ) %>%
st_as_sf( coords=c("Longitude","Latitude"), crs=4326 ) -> beetles
Rows: 31 Columns: 9
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (1): Site
dbl (8): Males, Females, Suitability, MFRatio, GenVarArapat, GenVarEuphli, L...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
summary( beetles )
Site Males Females Suitability
Length:31 Min. : 9.00 Min. : 5.00 Min. :0.0563
Class :character 1st Qu.:16.00 1st Qu.:15.50 1st Qu.:0.2732
Mode :character Median :21.00 Median :21.00 Median :0.3975
Mean :25.68 Mean :23.52 Mean :0.4276
3rd Qu.:31.50 3rd Qu.:29.00 3rd Qu.:0.5442
Max. :64.00 Max. :63.00 Max. :0.9019
MFRatio GenVarArapat GenVarEuphli geometry
Min. :0.5938 Min. :0.0500 Min. :0.0500 POINT :31
1st Qu.:0.8778 1st Qu.:0.1392 1st Qu.:0.1777 epsg:4326 : 0
Median :1.1200 Median :0.2002 Median :0.2171 +proj=long...: 0
Mean :1.1598 Mean :0.2006 Mean :0.2203
3rd Qu.:1.3618 3rd Qu.:0.2592 3rd Qu.:0.2517
Max. :2.2000 Max. :0.3379 Max. :0.5122
Now, we can take the bounding box of these points and get a first
approximation.
beetles %>% st_bbox()
xmin ymin xmax ymax
-114.29353 23.28550 -109.32700 29.32541
OK, so this is the strict bounding box for these points. This means
that the minimum and maximum values for these points are defined by the
original locations—for both the latitude and longitude (both minimum and
maximum)—we have sites on each of the edges. This is fine here but we
could probably add a little bit of a buffer around that
bounding box so that we do not have our sites on the very edge of the
plot. We can do this by either eyeballing-it to round up to
some reasonable area around the points or apply a buffer
(st_buffer
) to the union of all the points with some
distance and then take the boounding box. I’ll go for the former and
make it into an extent
object.
baja_extent <- extent( c(-116, -109, 22, 30 ) )
baja_extent
class : Extent
xmin : -116
xmax : -109
ymin : 22
ymax : 30
Then we can crop()
the original raster using this extent
object to create our working raster. I can then dump my points onto the
same raster plot by indicaating add=TRUE
alt <- crop( r, baja_extent )
plot(alt)
plot( beetles["Suitability"], pch=16, add=TRUE)

⚠️
|
|
You need to be careful here. When you use built-in graphics processes in
a markdown document such as this and intend to add subsequent plots to
an existing plot you cannot run the lines individuall.
They must be all executed as the whole chunk. So there
is no CTRL/CMD + RETURN action here, it will plot the first one
and then complain throughout the remaining ones saying something like
plot.new has not been called yet . So you have to either
knit the whole document or just run the whole chunk to get them to
overlay.
|
Masking
There is another way to grab just a portion of the raster—similar to
cropping—which is to mask. A mask will not change the size of the raster
but just put NA
values in the cells that are not in the are
of interest. So if we were to just mask above, it would never actually
reduce the size of the raster, just add a lot more NA
values. However, the setup is the same.
beetles %>%
filter( Site != 32 ) %>%
st_union() %>%
st_buffer( dist = 1 ) %>%
st_convex_hull() -> hull
baja <- mask( alt, as(hull, "Spatial"))
baja
class : RasterLayer
dimensions : 960, 840, 806400 (nrow, ncol, ncell)
resolution : 0.008333333, 0.008333333 (x, y)
extent : -116, -109, 22, 30 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs
source : memory
names : alt_22
values : -202, 1838 (min, max)
And it looks like.
plot(baja)

Plotting with GGPlot
As you may suspect, our old friend ggplot
has some
tricks up its sleave for us. The main thing here is that
ggplot
requires a data.frame
object and a
raster is not a data.frame
— Unless we turn it into one
(hehehe) using a cool function called rasterToPoints()
.
This takes the cells of the raster (and underlying matrix) and makes
points from it.
alt %>%
rasterToPoints() %>%
head()
x y alt_22
[1,] -115.7958 29.99583 55
[2,] -115.7875 29.99583 126
[3,] -115.7792 29.99583 94
[4,] -115.7708 29.99583 99
[5,] -115.7625 29.99583 106
[6,] -115.7542 29.99583 120
However, they are not a data.frame
but a matrix.
alt %>%
rasterToPoints() %>%
class()
[1] "matrix" "array"
matrix
array
So, if we are going to use this, w need to transform it from a
matrix
object into a data.frame
object. We can
do this using the as.data.frame()
function. Remember from
the lecture
on data.frame
objects that we can coerce columns of
data (either matrix
or array
) into a
data.frame
this way.
So here it is in one pipe, using the following tricks:
- Converting raster to points and then to data.frame
so it
will go into ggplot
- Renaming the columns of data I am going to keep so I don’t have to
make xlab
and ylab
alt %>%
rasterToPoints() %>%
as.data.frame() %>%
transmute(Longitude=x,
Latitude=y,
Elevation=alt_22) -> alt.df
head( alt.df )
Then we can plot it by:
- Plotting it using geom_raster()
and setting the fill
color to the value of elevation. - Making the coordinates equal (e.g.,
roughtly equal in area for longitude and latitude), and - Applying only
a minimal theme.
alt.df %>%
ggplot() +
geom_raster( aes( x = Longitude,
y = Latitude,
fill = Elevation) ) +
coord_equal() +
theme_minimal() -> baja_elevation
baja_elevation

That looks good but we should probably do something with the colors.
There is a built-in terrain.colors()
and tell
ggplot
to use this for the fill gradient.
baja_elevation +
scale_fill_gradientn( colors=terrain.colors(100))

Or you can go dive into colors and set your own, you can set up your
own gradient for ggplot
using independent colors and then
tell it where the midpoint is along that gradient and it will do the
right thing©.
baja_elevation +
scale_fill_gradient2( low = "darkolivegreen",
mid = "yellow",
high = "brown",
midpoint = 1000 ) -> baja_map
baja_map

Now that looks great. Now, how about overlaying the points onto the
plot and indicate the size of the point by the ♂♀
ratio.
baja_map +
geom_sf( aes(size = MFRatio ),
data = beetles,
color = "dodgerblue2",
alpha = 0.75)

Now that looks nice.
Identifying Points
You can get some information from a raster plot interactively by
using the click
function. This must be
done with an active raster plot. After that, you use the
click()
function to grab what you need. Your mouse will
turn from an arrow into a cross hair and you can position it where you
like and get information such as the corrdinates (spatial) of the point
and the value of the raster pixel at that location.
If you do not specify n=
in the function then it will
continue to collect data until you click outside the graphing area. If
you set id=TRUE
it will plot the number of the point onto
the map so you can see where you had clicked. Since this is interactive,
you will not see the process when you execute the code below, but it
will look like.
plot( alt )
click(alt, xy=TRUE, value=TRUE, n=3 ) -> points
Here are what the points look like.
points
I’m going to rename the column names
points %>%
transmute( Longitude = x,
Latitude = y,
Value = value) -> sites
And then I can plot those points (using geom_point()
)
onto our background map.
baja_map +
geom_point( aes(x = Longitude,
y = Latitude,
size = Value), data=sites, color="red")

Mexellent!
Reprojecting Rasters
Just like points, we can reproject the entire raster using the
projectRaster
function. HJere I am going to project the
raster into UTM Zone 12N, a common projection for this part of Mexico from epsg.io.
Unfortunatly, the raster
library does not use epsg codes
so we’ll have to use the large description of that projection. See the
page for this projection and scroll
down to the proj.4 definition.
new.proj <- "+proj=utm +zone=12 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "
Copy this into a character variable and then use the
projectRaster()
function and assign that new value as the
CRS
.
alt.utm <- projectRaster( alt, crs=new.proj)
plot( alt.utm, xlab="Easting", ylab="Northing" )

Easy.
Raster Operations
OK, so now we can make and show a raster but what about doing some
operations? A raster is just a matrix decorated with more
geospatial information. This allows us to do normal R
like
data manipulations on the underlying data.
Consider the following question.
What are the parts of Baja California that are within 100m of the
elevation of site named San Francisquito
(sfran
)?
To answer this, we have the following general outline of
operations.
- Find the coordinates of the site named
sfran
- Extract the elevation from the
alt
raster that is
within 100m (+/-) of that site.
- Plot the whole baja data as a background
- Overlay all the locations within that elevation band.
To do this we will use both the alt
and the
beetles
data objects.
First, we find out the coordinates of the site.
sfran <- beetles$geometry[ beetles$Site == "sfran"]
sfran
Geometry set for 1 feature
Geometry type: POINT
Dimension: XY
Bounding box: xmin: -112.964 ymin: 27.3632 xmax: -112.964 ymax: 27.3632
Geodetic CRS: WGS 84
POINT (-112.964 27.3632)
Now, we need to figure out what the value of elevation in the
alt
raster is at this site. This can be done with the
extract()
function from the raster
library.
However, the this function doesn’t work directly
with sf
objects so we need to cast it into a
Spatial
object. Fortunatly, that is a pretty easy
coercion.
raster::extract(alt, as(sfran,"Spatial") )
[1] 305
Warning: in the above code, I used the function
extract()
to extract the data from the alt
raster for the coordinate of the target locale. However, there is also
an extract()
function that has been brought in from the
dplyr
library (as part of tidyverse
). In this
file, I loaded library(raster)
before
library(tidyverse)
and as such the
dplyr::extract()
function has overridden the one from
raster
—they cannot both be available. As a
consequence, I use the full name of the function with
package::function
when I call it as
raster::extract()
to remove all ambiguity. If I had not, I
got a message saying something like,
Error in UseMethod("extract_") : no applicable method for 'extract_' applied to an object of class "c('RasterLayer', 'Raster', 'BasicRaster')"
.
Now, I know there is an extract()
function in
raster
so this is the dead giveaway that
it has been overwritten by a subsequent library call.
Option 1 - Manipulate the Raster
To work on a raster directly, we can access the values within it
using the values()
function (I know, these
statistican/programmers are quite cleaver).
So, to make a copy and make only the values that are +/- 100m of
sfran
we can.
alt_band <- alt
values( alt_band )[ values(alt_band) <= 205 ] <- NA
values( alt_band )[ values(alt_band) >= 405 ] <- NA
alt_band
class : RasterLayer
dimensions : 960, 840, 806400 (nrow, ncol, ncell)
resolution : 0.008333333, 0.008333333 (x, y)
extent : -116, -109, 22, 30 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs
source : memory
names : alt_22
values : 206, 404 (min, max)
Then we can plot overlay plots of each (notice how I hid the legend
for the first alt
raster).
plot( alt, col="gray", legend=FALSE, xlab="Longitude", ylab="Latitude")
plot( alt_band, add=TRUE )

Option 2 - Manipulate the Data Frames
We can also proceed by relying upon the data.frame
objects representing the elevation. So let’s go back to our the
alt.df
object and use that in combination with a
filter
and plot both data.frame
objects (the
outline of the landscape in gray and the elevation range as a gradient).
I then overlay the beetle data with the ratios as sizes and label the
locales with ggrepel
. Notice here that you can use the
sf::geometry
object from beetles
if you pass
it through the st_coordinates
function as a statistical
tranform making it regular coordinates and not sf
objects
(yes this is kind of a trick and hack but KEEP IT HANDY!).
library( ggrepel )
alt.df %>%
filter( Elevation >= 205,
Elevation <= 405) %>%
ggplot() +
geom_raster( aes( x = Longitude,
y = Latitude),
fill = "gray80",
data=alt.df ) +
geom_raster( aes( x = Longitude,
y = Latitude,
fill = Elevation ) ) +
scale_fill_gradient2( low = "darkolivegreen",
mid = "yellow",
high = "brown",
midpoint = 305 ) +
geom_sf( aes(size=MFRatio),
alpha=0.5,
color="dodgerblue3",
data=beetles) +
geom_text_repel( aes( label = Site,
geometry = geometry),
data = beetles,
stat = "sf_coordinates",
size = 4,
color = "dodgerblue4") +
coord_sf() +
theme_minimal()
Warning in st_point_on_surface.sfc(sf::st_zm(x)): st_point_on_surface may not
give correct results for longitude/latitude data

Very nice indeed.
LS0tCnRpdGxlOiAiUmFzdGVyIERhdGEiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgICAgY3NzOiAiZW52czU0My1zdHlsZXMuY3NzIgotLS0KPGNlbnRlcj4KIVtSYXN0ZXJzIV0oaHR0cHM6Ly9saXZlLnN0YXRpY2ZsaWNrci5jb20vNjU1MzUvNTA1MTA3NTc4MzdfYzM2MDY2ODJhY19jX2QuanBnKQo8L2NlbnRlcj4KCkFsbCByYXN0ZXIgb3BlcmF0aW9ucyBpbiB0aGlzIHRvcGljIGFyZSBhY2NvbXBsaXNoZWQgdXNpbmcgdGhlIGByYXN0ZXJgIGxpYnJhcnkuCgpgYGB7cn0KbGlicmFyeShyYXN0ZXIpCmBgYAoKUmFzdGVyIGFyZSByZXByZXNlbnRhdGlvbnMgb2YgY29udGludW91cywgb3Igc2VtaS1jb250aW51b3VzLCBkYXRhLiAgVFlvdSBjYW4gZW52aXNpb24gYSByYXN0ZXIganVzdCBsaWtlIGFuIGltYWdlLiAgV2hlbiBtZSBtYWtlIGEgYGxlYWZsZXQoKWAgbWFwIGFuZCBob3cgdGhlIHRpbGVzLCBlYWNoIHBpeGVsIGlzIGNvbG9yZWQgYSBwYXJ0aWN1bGFyIHZhbHVlIHJlcHJlc2VudGluZyBlbGV2YXRpb24sIHRlbXBlcmF0dXJlLCBwcmVjaXBpdGF0aW9uLCBoYWJpdGF0IHR5cGUsIG9yIHdoYXRldmVyLiAgVGhpcyBpcyBleGFjdGx5IHRoZSBzYW1lIGZvciByYXN0ZXJzLiAgVGhlICprZXkgcG9pbnQqIGhlcmUgaXMgdGhhdCBlYWNoIHBpeGVsIHJlcHJlc2VudHMgc29tZSBkZWZpbmVkIHJlZ2lvbiBvbiB0aGUgZWFydGggYW5kIGFzIHN1Y2ggdGhlIHJhc3RlciBpdHNlbGYgaXMgZ2VvcmVmZXJlbmNlZC4gIEl0IGhhcyBhIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSAoQ1JTKSwgYm91bmRhcmllcywgZXRjLgoKIyMgTWFraW5nIFJhc3RlcnMgKmRlIG5vdm8qCgpBIHJhc3RlciBpcyBzaW1wbHkgYSBtYXRyaXggd2l0aCByb3dzIGFuZCBjb2x1bW5zIGFuZCBlYWNoIGVsZW1lbnQgaGFzIGEgdmFsdWUgYXNzb2NpYXRlZCB3aXRoIGl0LiAgWW91IGNhbiBjcmVhdGUgYSByYXN0ZXIgKmRlIG5vdm8qIGJ5IG1ha2luZyBhIG1hdHJpeCBvZiBkYXRhIGFuZCBmaWxsaW5nIGl0IHdpdGggdmFsdWVzLCB0aGVuIHR1cm5pbmcgaXQgaW50byBhIHJhc3Rlci4KCkhlcmUgSSBtYWtlIGEgcmFzdGVyIHdpdGggcmFuZG9tIG51bWJyZXJzIHNlbGVjdGVkIGZyb20gdGhlIFBvaXNzb24gRGlzdHJpYnV0aW9uIChmaXNoeSwgSSBrbm93KSB1c2luZyB0aGUgYHJwb2lzKClgIGZ1bmN0aW9uLiAgSSB0aGVuIHR1cm4gaXQgaW50byBhIG1hdHJpeCB3aXRoIDcgcm93cyAoYW5kIDcgY29sdW1ucykuCgoKYGBge3J9CnZhbHMgPC0gcnBvaXMoNDksIGxhbWJkYT0xMikKeCA8LSBtYXRyaXgoIHZhbHMsIG5yb3c9NykKeApgYGAKCldoaWxlIHdlIGhhdmVuJ3QgdXNlZCBtYXRyaWNlcyBtdWNoIHRodXMgZmFyLCBpdCBpcyBhIGxvdCBsaWtlIGEgYGRhdGEuZnJhbWVgIHdpdGggcmVzcGVjdCB0byBnZXR0aW5nIGFuZCBzZXR0aW5nIHZhbHVlcyB1c2luZyBudW1lcmljYWwgaW5kaWNlcy4gIEZvciBleGFtcGxlLCB0aGUgdmFsdWUgb2YgdGhlIDNyZCByb3cgYW5kIDV0aCBjb2x1bW4gaXM6CgpgYGB7cn0KeFszLDVdCmBgYAoKVG8gY29udmVydCB0aGlzIHNldCBvZiBkYXRhLCBhcyBhIG1hdHJpeCwgaW50byBhIGdlb3NwYXRpYWxseSByZWZlcmVuY2VkIGByYXN0ZXIoKWAgb2JqZWN0IHdlIGRvIHRoZSBmb2xsb3dpbmc6CgpgYGB7cn0KciA8LSByYXN0ZXIoIHggKQpyCmBgYAoKTm90aWNlIHRoYXQgd2hlbiBJIHBsb3QgaXQgb3V0LCBpdCBkb2VzIG5vdCBzaG93IHRoZSBkYXRhLCBidXQgYSBzdW1tYXJ5IG9mIHRoZSBkYXRhIGFsb25nIHdpdGggc29tZSBrZXkgZGF0YSBhYm91dCB0aGUgY29udGVudHMsIGluY2x1ZGluZzogIAotIEEgY2xhc3MgZGVmaW5pdGlvbiAgCi0gVGhlIGRpbWVuc2lvbnMgb2YgdGhlIHVuZGVybHlpbmcgZGF0YSBtYXRyaXgsICAKLSBUaGUgcmVzb2x1dGlvbiAoZS5nLiwgdGhlIHNwYXRpYWwgZXh0ZW50IG9mIHRoZSBzaWRlcyBvZiBlYWNoIHBpeGVsKS4gIFNpbmNlIHdlIGhhdmUgbm8gQ1JTIGhlcmUsIGl0IGlzIGVxdWFsIHRvICRucm93cyh4KV57LTF9JCBhbmQgJG5jb2xzKHgpXnstMX0kLiAgCi0gVGhlIGV4dGVudCAodGhlIGJvdW5kaW5nIGJveCkgYW5kIGFnYWluIHNpbmNlIHdlIGRvIG5vdCBoYXZlIGEgQ1JTIGRlZmluZWQgaXQganVzdCBnb2VzIGZyb20gJDAkIHRvICQxJC4KLSBUaGUgYGNyc2AgKG1pc3NpbmcpCi0gVGhlIHNvdXJjZSBjYW4gYmUgZWl0aGVyIGBtZW1vcnlgIGlmIHRoZSByYXN0ZXIgaXMgbm90IHRoYXQgYmlnIG9yIGBvdXQgb2YgbWVtb3J5YCBpZiBpdCBpcyBqdXN0IHJlZmVyZW5jaW5nLgoKSWYgdGhlc2UgZGF0YSByZXByZXNlbnQgc29tZXRoaW5nIG9uIHRoZSBwbGFuZXQsIHdlIGNhbiBhc3NpZ24gdGhlIGRpbWVuc2lvbnMgYW5kIGBDUlNgIHZhbHVlcyB0byBpdCBhbmQgdXNlIGl0IGluIG91ciBub3JtYWwgZGF5LXRvLWRheSBvcGVyYXRpb25zLgoKIyMgTG9hZGluZyBSYXN0ZXJzIGZyb20gRmlsZXMgb3IgVVJMcwoKV2UgY2FuIGFsc28gZ3JhYiBhIHJhc3RlciBvYmplY3QgZnJvbSB0aGUgZmlsZXN5c3RlbSBvciBmcm9tIHNvbWUgb25saW5lIHJlcG9zaXRvcnkgYnkgcGFzc2luZyB0aGUgbGluayB0byB0aGUgYHJhc3RlcigpYCBmdW5jdGlvbi4gIEhlcmUgaXMgdGhlIGVsZXZhdGlvbiwgaW4gbWV0ZXJzLCBvZiB0aGUgcmVnaW9uIGluIHdoaWNoIE1leGljbyBpcyBmb3VuZC4gVG8gbG9hZCBpdCBpbiwgcGFzcyB0aGUgdXJsLgoKYGBge3J9CnVybCA8LSAiaHR0cHM6Ly9naXRodWIuY29tL2R5ZXJsYWIvRU5WUy1MZWN0dXJlcy9yYXcvbWFzdGVyL2RhdGEvYWx0XzIyLnRpZiIKciA8LSByYXN0ZXI6OnJhc3RlciggdXJsICkKcgpgYGAKCk5vdGljZSB0aGF0IHRoaXMgcmFzdGVyIGhhcyBhIGRlZmluZWQgQ1JTIGFuZCBhcyBzdWNoIGl0IGlzIHByb2plY3RlZCBhbmQgdGhlIGV4dGVudCByZWxhdGVzIHRvIHRoZSB1bml0cyBvZiB0aGUgZGF0dW0gKGUuZy4sIGZyb20gLTEyMCB0byAtOTAgZGVncmVlcyBsb25naXR1ZGUgYW5kIDAgdG8gMzAgZGVncmVlcyBsYXRpdHVkZSkuCgpJZiB3ZSBwbG90IGl0LCB3ZSBjYW4gc2VlIHRoZSB3aG9sZSByYXN0ZXIuCgpgYGB7cn0KcGxvdChyKQpgYGAKCk5vdywgdGhpcyByYXN0ZXIgaXMgZWxldmF0aW9uIHdoZXJlIHRoZXJlIGlzIGxhbmQgYnV0IHdoZXJlIHRoZXJlIGlzIG5vIGxhbmQsIGl0IGlzIGZ1bGwgb2YgYE5BYCB2YWx1ZXMuICBBcyBzdWNoLCB0aGVyZSBpcyBhIHRvbiBvZiB0aGVtLgoKYGBge3J9CmZvcm1hdCggc3VtKCBpcy5uYSggdmFsdWVzKHIpICkgKSwgYmlnLm1hcmsgPSAiLCIgKQpgYGAKCgojIyBDcm9wcGluZwoKT25lIG9mIHRoZSBmaXJzdCB0aGluZ3MgdG8gZG8gaXMgdG8gY3JvcCB0aGUgZGF0YSBkb3duIHRvIHJlcHJlc2VudCB0aGUgc2l6ZSBhbmQgZXh0ZW50IG9mIG91ciBzdHVkeSBhcmVhLiAgSWYgd2Ugb3ZlciAxMCBtaWxsaW9uIG1pc3NpbmcgZGF0YSBwb2ludHMgKHRoZSBvY2VhbikgYW5kIG1vc3Qgb2YgTWV4aWNvIGluIHRoaXMgcmFzdGVyIGFib3ZlIGJ1dCB3ZSBhcmUgb25seSB3b3JraW5nIHdpdGggc2l0ZXMgaW4gQmFqYSBDYWxpZm9ybmlhIChOb3J0ZSB5IFN1ciksIHdlIHdvdWxkIGRvIHdlbGwgdG8gZXhjaXNlIChvciBjcm9wKSB0aGUgcmFzdGVyIHRvIG9ubHkgaW5jbHVkZSB0aGUgYXJlYSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB3b3JraW5nIHdpdGguICAKClRvcCBkbyB0aGlzLCB3ZSBuZWVkIHRvIGZpZ3VyZSBvdXQgYSBib3VuZGluZyBib3ggKGUuZy4sIHRoZSBtaW5pbWltIGFuZCBtYXhpbXVtIHZhbHVlcyBvZiBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIHRoYXQgZW5jbG9zZSBvdXIgZGF0YSkuICBMZXQncyBhc3N1bWUgd2UgYXJlIHdvcmtpbmcgd2l0aCB0aGUgQmVldGxlIERhdGEgZnJvbSB0aGUgW1NwYXRpYWwgUG9pbnRzIFNsaWRlc10oaHR0cHM6Ly9keWVybGFiLmdpdGh1Yi5pby9FTlZTLUxlY3R1cmVzL3NwYXRpYWwvc3BhdGlhbF9wb2ludHMvc2xpZGVzLmh0bWwjMSkgYW5kIGxvYWQgaW4gdGhlIFNleC1iaWFzZWQgZGlzcGVyc2FsIGRhdGEgc2V0IGFuZCB1c2UgdGhvc2UgcG9pbnRzIGFzIGEgc3RhcnRpbmcgZXN0aW1hdGUgb2YgdGhlIGJvdW5kaW5nIGJveC4KCgpgYGB7cn0KbGlicmFyeSggc2YgKQpsaWJyYXJ5KCB0aWR5dmVyc2UgKQpiZWV0bGVfdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZHllcmxhYi9FTlZTLUxlY3R1cmVzL21hc3Rlci9kYXRhL0FyYXB0dXNfRGlzcGVyYWxfQmlhcy5jc3YiCgpyZWFkX2NzdiggYmVldGxlX3VybCApICU+JQogIHN0X2FzX3NmKCBjb29yZHM9YygiTG9uZ2l0dWRlIiwiTGF0aXR1ZGUiKSwgY3JzPTQzMjYgKSAtPiBiZWV0bGVzCgpzdW1tYXJ5KCBiZWV0bGVzICkKYGBgCgpOb3csIHdlIGNhbiB0YWtlIHRoZSBib3VuZGluZyBib3ggb2YgdGhlc2UgcG9pbnRzIGFuZCBnZXQgYSBmaXJzdCBhcHByb3hpbWF0aW9uLgoKYGBge3J9CmJlZXRsZXMgJT4lIHN0X2Jib3goKQpgYGAKCk9LLCBzbyB0aGlzIGlzIHRoZSBzdHJpY3QgYm91bmRpbmcgYm94IGZvciB0aGVzZSBwb2ludHMuICBUaGlzIG1lYW5zIHRoYXQgdGhlIG1pbmltdW0gYW5kIG1heGltdW0gdmFsdWVzIGZvciB0aGVzZSBwb2ludHMgYXJlIGRlZmluZWQgYnkgdGhlIG9yaWdpbmFsIGxvY2F0aW9uc+KAlGZvciBib3RoIHRoZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIChib3RoIG1pbmltdW0gYW5kIG1heGltdW0p4oCUd2UgaGF2ZSBzaXRlcyBvbiBlYWNoIG9mIHRoZSBlZGdlcy4gIFRoaXMgaXMgZmluZSBoZXJlIGJ1dCB3ZSBjb3VsZCBwcm9iYWJseSBhZGQgYSBsaXR0bGUgYml0IG9mIGEgKmJ1ZmZlciogYXJvdW5kIHRoYXQgYm91bmRpbmcgYm94IHNvIHRoYXQgd2UgZG8gbm90IGhhdmUgb3VyIHNpdGVzIG9uIHRoZSB2ZXJ5IGVkZ2Ugb2YgdGhlIHBsb3QuICBXZSBjYW4gZG8gdGhpcyBieSBlaXRoZXIgKmV5ZWJhbGxpbmctaXQqIHRvIHJvdW5kIHVwIHRvIHNvbWUgcmVhc29uYWJsZSBhcmVhIGFyb3VuZCB0aGUgcG9pbnRzICpvciogYXBwbHkgYSBidWZmZXIgKGBzdF9idWZmZXJgKSB0byB0aGUgdW5pb24gb2YgYWxsIHRoZSBwb2ludHMgd2l0aCBzb21lIGRpc3RhbmNlIGFuZCB0aGVuIHRha2UgdGhlIGJvb3VuZGluZyBib3guICAgSSdsbCBnbyBmb3IgdGhlIGZvcm1lciBhbmQgbWFrZSBpdCBpbnRvIGFuIGBleHRlbnRgIG9iamVjdC4KCmBgYHtyfQpiYWphX2V4dGVudCA8LSBleHRlbnQoIGMoLTExNiwgLTEwOSwgMjIsIDMwICkgKQpiYWphX2V4dGVudApgYGAKClRoZW4gd2UgY2FuIGBjcm9wKClgIHRoZSBvcmlnaW5hbCByYXN0ZXIgdXNpbmcgdGhpcyBleHRlbnQgb2JqZWN0IHRvIGNyZWF0ZSBvdXIgd29ya2luZyByYXN0ZXIuICBJIGNhbiB0aGVuIGR1bXAgbXkgcG9pbnRzIG9udG8gdGhlIHNhbWUgcmFzdGVyIHBsb3QgYnkgaW5kaWNhYXRpbmcgYGFkZD1UUlVFYAoKYGBge3J9CmFsdCA8LSBjcm9wKCByLCBiYWphX2V4dGVudCApCnBsb3QoYWx0KQpwbG90KCBiZWV0bGVzWyJTdWl0YWJpbGl0eSJdLCBwY2g9MTYsIGFkZD1UUlVFKQpgYGAKCjxkaXYgY2xhc3M9ImJveC1yZWQiPgo8dGFibGU+Cjx0cj4KPHRkPjxoMT7imqDvuI88L2gxPjwvdGQ+Cjx0ZD4gJm5ic3A7ICAmbmJzcDsgPC90ZD4KPHRkPllvdSBuZWVkIHRvIGJlIGNhcmVmdWwgaGVyZS4gIFdoZW4geW91IHVzZSBidWlsdC1pbiBncmFwaGljcyBwcm9jZXNzZXMgaW4gYSBtYXJrZG93biBkb2N1bWVudCBzdWNoIGFzIHRoaXMgYW5kIGludGVuZCB0byBhZGQgc3Vic2VxdWVudCBwbG90cyB0byBhbiBleGlzdGluZyBwbG90IHlvdSAqKmNhbm5vdCoqIHJ1biB0aGUgbGluZXMgaW5kaXZpZHVhbGwuICBUaGV5ICoqbXVzdCoqIGJlIGFsbCBleGVjdXRlZCBhcyB0aGUgd2hvbGUgY2h1bmsuICBTbyB0aGVyZSBpcyBubyA8dHQ+Q1RSTC9DTUQgKyBSRVRVUk48L3R0PiBhY3Rpb24gaGVyZSwgaXQgd2lsbCBwbG90IHRoZSBmaXJzdCBvbmUgYW5kIHRoZW4gY29tcGxhaW4gdGhyb3VnaG91dCB0aGUgcmVtYWluaW5nIG9uZXMgc2F5aW5nIHNvbWV0aGluZyBsaWtlIGBwbG90Lm5ldyBoYXMgbm90IGJlZW4gY2FsbGVkIHlldGAuICBTbyB5b3UgaGF2ZSB0byBlaXRoZXIga25pdCB0aGUgd2hvbGUgZG9jdW1lbnQgb3IganVzdCBydW4gdGhlIHdob2xlIGNodW5rIHRvIGdldCB0aGVtIHRvIG92ZXJsYXkuPC90ZD4KPC90cj4KPC90YWJsZT4KPC9kaXY+CgojIyBNYXNraW5nCgpUaGVyZSBpcyBhbm90aGVyIHdheSB0byBncmFiIGp1c3QgYSBwb3J0aW9uIG9mIHRoZSByYXN0ZXLigJRzaW1pbGFyIHRvIGNyb3BwaW5n4oCUd2hpY2ggaXMgdG8gbWFzay4gIEEgbWFzayB3aWxsIG5vdCBjaGFuZ2UgdGhlIHNpemUgb2YgdGhlIHJhc3RlciBidXQganVzdCBwdXQgYE5BYCB2YWx1ZXMgaW4gdGhlIGNlbGxzIHRoYXQgYXJlIG5vdCBpbiB0aGUgYXJlIG9mIGludGVyZXN0LiAgU28gaWYgd2Ugd2VyZSB0byBqdXN0IG1hc2sgYWJvdmUsIGl0IHdvdWxkIG5ldmVyIGFjdHVhbGx5IHJlZHVjZSB0aGUgc2l6ZSBvZiB0aGUgcmFzdGVyLCBqdXN0IGFkZCBhIGxvdCBtb3JlIGBOQWAgdmFsdWVzLiAgSG93ZXZlciwgdGhlIHNldHVwIGlzIHRoZSBzYW1lLgoKYGBge3J9CmJlZXRsZXMgJT4lCiAgZmlsdGVyKCBTaXRlICE9IDMyICkgJT4lCiAgc3RfdW5pb24oKSAlPiUKICBzdF9idWZmZXIoIGRpc3QgPSAxICkgJT4lCiAgc3RfY29udmV4X2h1bGwoKSAtPiBodWxsCgpiYWphIDwtIG1hc2soIGFsdCwgYXMoaHVsbCwgIlNwYXRpYWwiKSkKYmFqYQpgYGAKCkFuZCBpdCBsb29rcyBsaWtlLgoKYGBge3J9CnBsb3QoYmFqYSkKYGBgCgoKCgojIyBQbG90dGluZyB3aXRoIEdHUGxvdAoKQXMgeW91IG1heSBzdXNwZWN0LCBvdXIgb2xkIGZyaWVuZCBgZ2dwbG90YCBoYXMgc29tZSB0cmlja3MgdXAgaXRzIHNsZWF2ZSBmb3IgdXMuICBUaGUgbWFpbiB0aGluZyBoZXJlIGlzIHRoYXQgYGdncGxvdGAgcmVxdWlyZXMgYSBgZGF0YS5mcmFtZWAgb2JqZWN0IGFuZCBhIHJhc3RlciBpcyBub3QgYSBgZGF0YS5mcmFtZWAgLS0tIFVubGVzcyB3ZSB0dXJuIGl0IGludG8gb25lIChoZWhlaGUpIHVzaW5nIGEgY29vbCBmdW5jdGlvbiBjYWxsZWQgYHJhc3RlclRvUG9pbnRzKClgLiAgVGhpcyB0YWtlcyB0aGUgY2VsbHMgb2YgdGhlIHJhc3RlciAoYW5kIHVuZGVybHlpbmcgbWF0cml4KSBhbmQgbWFrZXMgcG9pbnRzIGZyb20gaXQuCgpgYGB7cn0KYWx0ICU+JQogIHJhc3RlclRvUG9pbnRzKCkgJT4lCiAgaGVhZCgpCmBgYAoKSG93ZXZlciwgdGhleSBhcmUgbm90IGEgYGRhdGEuZnJhbWVgIGJ1dCBhIG1hdHJpeC4KCmBgYHtyfQphbHQgJT4lCiAgcmFzdGVyVG9Qb2ludHMoKSAlPiUKICBjbGFzcygpCmBgYAoKU28sIGlmIHdlIGFyZSBnb2luZyB0byB1c2UgdGhpcywgdyBuZWVkIHRvIHRyYW5zZm9ybSBpdCBmcm9tIGEgYG1hdHJpeGAgb2JqZWN0IGludG8gYSBgZGF0YS5mcmFtZWAgb2JqZWN0LiAgV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGBhcy5kYXRhLmZyYW1lKClgIGZ1bmN0aW9uLiAgUmVtZW1iZXIgZnJvbSB0aGUgW2xlY3R1cmUgb24gYGRhdGEuZnJhbWVgIG9iamVjdHNdKGh0dHBzOi8vZHllcmxhYi5naXRodWIuaW8vRU5WUy1MZWN0dXJlcy9yX2xhbmd1YWdlL2RhdGFfZnJhbWVzL3NsaWRlcy5odG1sIzEpIHRoYXQgd2UgY2FuIGNvZXJjZSBjb2x1bW5zIG9mIGRhdGEgKGVpdGhlciBgbWF0cml4YCBvciBgYXJyYXlgKSBpbnRvIGEgYGRhdGEuZnJhbWVgIHRoaXMgd2F5LgoKClNvIGhlcmUgaXQgaXMgaW4gb25lIHBpcGUsIHVzaW5nIHRoZSBmb2xsb3dpbmcgdHJpY2tzOiAgCi0gQ29udmVydGluZyByYXN0ZXIgdG8gcG9pbnRzIGFuZCB0aGVuIHRvIGBkYXRhLmZyYW1lYCBzbyBpdCB3aWxsIGdvIGludG8gYGdncGxvdGAgICAgCi0gUmVuYW1pbmcgdGhlIGNvbHVtbnMgb2YgZGF0YSBJIGFtIGdvaW5nIHRvIGtlZXAgc28gSSBkb24ndCBoYXZlIHRvIG1ha2UgYHhsYWJgIGFuZCBgeWxhYmAgIAoKYGBge3J9CmFsdCAlPiUKICByYXN0ZXJUb1BvaW50cygpICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgdHJhbnNtdXRlKExvbmdpdHVkZT14LAogICAgICAgICAgICBMYXRpdHVkZT15LAogICAgICAgICAgICBFbGV2YXRpb249YWx0XzIyKSAgLT4gYWx0LmRmCmhlYWQoIGFsdC5kZiApCmBgYAoKVGhlbiB3ZSBjYW4gcGxvdCBpdCBieTogIAotIFBsb3R0aW5nIGl0IHVzaW5nIGBnZW9tX3Jhc3RlcigpYCBhbmQgc2V0dGluZyB0aGUgZmlsbCBjb2xvciB0byB0aGUgdmFsdWUgb2YgZWxldmF0aW9uLgotIE1ha2luZyB0aGUgY29vcmRpbmF0ZXMgZXF1YWwgKGUuZy4sIHJvdWdodGx5IGVxdWFsIGluIGFyZWEgZm9yIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUpLCBhbmQKLSBBcHBseWluZyBvbmx5IGEgbWluaW1hbCB0aGVtZS4KCgoKYGBge3J9CmFsdC5kZiAlPiUKICBnZ3Bsb3QoKSAgKyAKICBnZW9tX3Jhc3RlciggYWVzKCB4ID0gTG9uZ2l0dWRlLCAKICAgICAgICAgICAgICAgICAgICB5ID0gTGF0aXR1ZGUsIAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBFbGV2YXRpb24pICkgKyAKICBjb29yZF9lcXVhbCgpICsKICB0aGVtZV9taW5pbWFsKCkgLT4gYmFqYV9lbGV2YXRpb24KCmJhamFfZWxldmF0aW9uCmBgYAoKCgoKVGhhdCBsb29rcyBnb29kIGJ1dCB3ZSBzaG91bGQgcHJvYmFibHkgZG8gc29tZXRoaW5nIHdpdGggdGhlIGNvbG9ycy4gIFRoZXJlIGlzIGEgYnVpbHQtaW4gYHRlcnJhaW4uY29sb3JzKClgIGFuZCB0ZWxsIGBnZ3Bsb3RgIHRvIHVzZSB0aGlzIGZvciB0aGUgZmlsbCBncmFkaWVudC4KCmBgYHtyfQpiYWphX2VsZXZhdGlvbiArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKCBjb2xvcnM9dGVycmFpbi5jb2xvcnMoMTAwKSkKYGBgCgoKT3IgeW91IGNhbiBnbyBkaXZlIGludG8gY29sb3JzIGFuZCBzZXQgeW91ciBvd24sIHlvdSBjYW4gc2V0IHVwIHlvdXIgb3duIGdyYWRpZW50IGZvciBgZ2dwbG90YCB1c2luZyBpbmRlcGVuZGVudCBjb2xvcnMgYW5kIHRoZW4gdGVsbCBpdCB3aGVyZSB0aGUgbWlkcG9pbnQgaXMgYWxvbmcgdGhhdCBncmFkaWVudCBhbmQgaXQgd2lsbCAqZG8gdGhlIHJpZ2h0IHRoaW5nPHN1cD4mY29weTs8L2NvcHk+Ki4KCmBgYHtyfQpiYWphX2VsZXZhdGlvbiArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKCBsb3cgPSAiZGFya29saXZlZ3JlZW4iLAogICAgICAgICAgICAgICAgICAgICAgICBtaWQgPSAieWVsbG93IiwKICAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJicm93biIsIAogICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDEwMDAgKSAtPiBiYWphX21hcApiYWphX21hcApgYGAKCk5vdyB0aGF0IGxvb2tzIGdyZWF0LiAgTm93LCBob3cgYWJvdXQgb3ZlcmxheWluZyB0aGUgcG9pbnRzIG9udG8gdGhlIHBsb3QgYW5kIGluZGljYXRlIHRoZSBzaXplIG9mIHRoZSBwb2ludCBieSB0aGUgKirimYLimYAqKiByYXRpby4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmJhamFfbWFwICsgCiAgZ2VvbV9zZiggYWVzKHNpemUgPSBNRlJhdGlvICksIAogICAgICAgICAgIGRhdGEgPSBiZWV0bGVzLCAKICAgICAgICAgICBjb2xvciA9ICJkb2RnZXJibHVlMiIsCiAgICAgICAgICAgYWxwaGEgPSAwLjc1KSAKYGBgCk5vdyB0aGF0IGxvb2tzIG5pY2UuCgojIyBJZGVudGlmeWluZyBQb2ludHMKCllvdSBjYW4gZ2V0IHNvbWUgaW5mb3JtYXRpb24gZnJvbSBhIHJhc3RlciBwbG90IGludGVyYWN0aXZlbHkgYnkgdXNpbmcgdGhlIGBjbGlja2AgZnVuY3Rpb24uICBUaGlzICoqbXVzdCoqIGJlIGRvbmUgd2l0aCBhbiBhY3RpdmUgcmFzdGVyIHBsb3QuICBBZnRlciB0aGF0LCB5b3UgdXNlIHRoZSBgY2xpY2soKWAgZnVuY3Rpb24gdG8gZ3JhYiB3aGF0IHlvdSBuZWVkLiAgWW91ciBtb3VzZSB3aWxsIHR1cm4gZnJvbSBhbiBhcnJvdyBpbnRvIGEgY3Jvc3MgaGFpciBhbmQgeW91IGNhbiBwb3NpdGlvbiBpdCB3aGVyZSB5b3UgbGlrZSBhbmQgZ2V0IGluZm9ybWF0aW9uIHN1Y2ggYXMgdGhlIGNvcnJkaW5hdGVzIChzcGF0aWFsKSBvZiB0aGUgcG9pbnQgYW5kIHRoZSB2YWx1ZSBvZiB0aGUgcmFzdGVyIHBpeGVsIGF0IHRoYXQgbG9jYXRpb24uCgpJZiB5b3UgZG8gbm90IHNwZWNpZnkgYG49YCBpbiB0aGUgZnVuY3Rpb24gdGhlbiBpdCB3aWxsIGNvbnRpbnVlIHRvIGNvbGxlY3QgZGF0YSB1bnRpbCB5b3UgY2xpY2sgb3V0c2lkZSB0aGUgZ3JhcGhpbmcgYXJlYS4gIElmIHlvdSBzZXQgYGlkPVRSVUVgIGl0IHdpbGwgcGxvdCB0aGUgbnVtYmVyIG9mIHRoZSBwb2ludCBvbnRvIHRoZSBtYXAgc28geW91IGNhbiBzZWUgd2hlcmUgeW91IGhhZCBjbGlja2VkLiAgU2luY2UgdGhpcyBpcyBpbnRlcmFjdGl2ZSwgeW91IHdpbGwgbm90IHNlZSB0aGUgcHJvY2VzcyB3aGVuIHlvdSBleGVjdXRlIHRoZSBjb2RlIGJlbG93LCBidXQgaXQgd2lsbCBsb29rIGxpa2UuCgpgYGB7ciwgZXZhbD1GQUxTRX0KcGxvdCggYWx0ICkKY2xpY2soYWx0LCB4eT1UUlVFLCB2YWx1ZT1UUlVFLCBuPTMgKSAtPiBwb2ludHMKYGBgCgohW21hcCB3aXRoIHBvaW50c10oaHR0cHM6Ly9saXZlLnN0YXRpY2ZsaWNrci5jb20vNjU1MzUvNTA1MDU1MDU5NDhfMDhlM2U5MWRmYl9jX2QuanBnKQpgYGB7ciBlY2hvPUZBTFNFfQpwb2ludHMgPC0gZGF0YS5mcmFtZSggeCA9IGMoLTExMy42MjkyLCAtMTEyLjQ3OTIsIC0xMTEuMjQ1OCwgLTEwOS45OTU4KSwKICAgICAgICAgICAgICAgICAgICAgIHkgPSBjKDI4LjQ1NDE3LCAyNi44NTQxNywgMjQuODM3NTAsIDIzLjQ4NzUwKSwKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyg4NzAsIDExODUsIDEzNSwgMTE0NSkgKQpgYGAKCkhlcmUgYXJlIHdoYXQgdGhlIHBvaW50cyBsb29rIGxpa2UuICAKCmBgYHtyfQpwb2ludHMKYGBgCgpJJ20gZ29pbmcgdG8gcmVuYW1lIHRoZSBjb2x1bW4gbmFtZXMKCmBgYHtyfQpwb2ludHMgJT4lCiAgdHJhbnNtdXRlKCBMb25naXR1ZGUgPSB4LAogICAgICAgICAgICAgTGF0aXR1ZGUgPSB5LAogICAgICAgICAgICAgVmFsdWUgPSB2YWx1ZSkgLT4gc2l0ZXMKYGBgCgpBbmQgdGhlbiBJIGNhbiBwbG90IHRob3NlIHBvaW50cyAodXNpbmcgYGdlb21fcG9pbnQoKWApIG9udG8gb3VyIGJhY2tncm91bmQgbWFwLgoKYGBge3J9CmJhamFfbWFwICsgCiAgZ2VvbV9wb2ludCggYWVzKHggPSBMb25naXR1ZGUsCiAgICAgICAgICAgICAgICAgIHkgPSBMYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgIHNpemUgPSBWYWx1ZSksIGRhdGE9c2l0ZXMsIGNvbG9yPSJyZWQiKSAKYGBgCgpNZXhlbGxlbnQhIAoKIyMgUmVwcm9qZWN0aW5nIFJhc3RlcnMgCgpKdXN0IGxpa2UgcG9pbnRzLCB3ZSBjYW4gcmVwcm9qZWN0IHRoZSBlbnRpcmUgcmFzdGVyIHVzaW5nIHRoZSBgcHJvamVjdFJhc3RlcmAgZnVuY3Rpb24uICBISmVyZSBJIGFtIGdvaW5nIHRvIHByb2plY3QgdGhlIHJhc3RlciBpbnRvIFVUTSBab25lIDEyTiwgYSBjb21tb24gcHJvamVjdGlvbiBmb3IgdGhpcyBwYXJ0IG9mIFtNZXhpY28gZnJvbSBlcHNnLmlvXShodHRwczovL2Vwc2cuaW8vNjM2NykuICAKClVuZm9ydHVuYXRseSwgdGhlIGByYXN0ZXJgIGxpYnJhcnkgZG9lcyBub3QgdXNlIGVwc2cgY29kZXMgc28gd2UnbGwgaGF2ZSB0byB1c2UgdGhlIGxhcmdlIGRlc2NyaXB0aW9uIG9mIHRoYXQgcHJvamVjdGlvbi4gIFNlZSB0aGUgW3BhZ2VdKGh0dHBzOi8vZXBzZy5pby82MzY3KSBmb3IgdGhpcyBwcm9qZWN0aW9uIGFuZCBzY3JvbGwgZG93biB0byB0aGUgcHJvai40IGRlZmluaXRpb24uICAKCmBgYHtyfQpuZXcucHJvaiA8LSAiK3Byb2o9dXRtICt6b25lPTEyICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCwwLDAsMCwwICt1bml0cz1tICtub19kZWZzICIKYGBgCgpDb3B5IHRoaXMgaW50byBhIGNoYXJhY3RlciB2YXJpYWJsZSBhbmQgdGhlbiB1c2UgdGhlIGBwcm9qZWN0UmFzdGVyKClgIGZ1bmN0aW9uIGFuZCBhc3NpZ24gdGhhdCBuZXcgdmFsdWUgYXMgdGhlIGBDUlNgLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWx0LnV0bSA8LSBwcm9qZWN0UmFzdGVyKCBhbHQsIGNycz1uZXcucHJvaikKcGxvdCggYWx0LnV0bSwgeGxhYj0iRWFzdGluZyIsIHlsYWI9Ik5vcnRoaW5nIiApCmBgYAoKRWFzeS4KCgojIyBSYXN0ZXIgT3BlcmF0aW9ucwoKT0ssIHNvIG5vdyB3ZSBjYW4gbWFrZSBhbmQgc2hvdyBhIHJhc3RlciBidXQgd2hhdCBhYm91dCBkb2luZyBzb21lIG9wZXJhdGlvbnM/ICBBIHJhc3RlciBpcyBqdXN0IGEgbWF0cml4ICpkZWNvcmF0ZWQqIHdpdGggbW9yZSBnZW9zcGF0aWFsIGluZm9ybWF0aW9uLiAgVGhpcyBhbGxvd3MgdXMgdG8gZG8gbm9ybWFsIGBSYCBsaWtlIGRhdGEgbWFuaXB1bGF0aW9ucyBvbiB0aGUgdW5kZXJseWluZyBkYXRhLiAgCgpDb25zaWRlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9uLiAgCgo+IFdoYXQgYXJlIHRoZSBwYXJ0cyBvZiBCYWphIENhbGlmb3JuaWEgdGhhdCBhcmUgd2l0aGluIDEwMG0gb2YgdGhlIGVsZXZhdGlvbiBvZiBzaXRlIG5hbWVkICpTYW4gRnJhbmNpc3F1aXRvKiAoYHNmcmFuYCk/ICAKClRvIGFuc3dlciB0aGlzLCB3ZSBoYXZlIHRoZSBmb2xsb3dpbmcgZ2VuZXJhbCBvdXRsaW5lIG9mIG9wZXJhdGlvbnMuCgoxLiBGaW5kIHRoZSBjb29yZGluYXRlcyBvZiB0aGUgc2l0ZSBuYW1lZCBgc2ZyYW5gICAKMi4gRXh0cmFjdCB0aGUgZWxldmF0aW9uIGZyb20gdGhlIGBhbHRgIHJhc3RlciB0aGF0IGlzIHdpdGhpbiAxMDBtICgrLy0pIG9mIHRoYXQgc2l0ZS4KMy4gUGxvdCB0aGUgd2hvbGUgYmFqYSBkYXRhIGFzIGEgYmFja2dyb3VuZCAgCjQuIE92ZXJsYXkgYWxsIHRoZSBsb2NhdGlvbnMgd2l0aGluIHRoYXQgZWxldmF0aW9uIGJhbmQuCgpUbyBkbyB0aGlzIHdlIHdpbGwgdXNlIGJvdGggdGhlIGBhbHRgIGFuZCB0aGUgYGJlZXRsZXNgIGRhdGEgb2JqZWN0cy4KCkZpcnN0LCB3ZSBmaW5kIG91dCB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIHNpdGUuCgpgYGB7cn0Kc2ZyYW4gPC0gYmVldGxlcyRnZW9tZXRyeVsgYmVldGxlcyRTaXRlID09ICJzZnJhbiJdCnNmcmFuCmBgYAoKCk5vdywgd2UgbmVlZCB0byBmaWd1cmUgb3V0IHdoYXQgdGhlIHZhbHVlIG9mIGVsZXZhdGlvbiBpbiB0aGUgYGFsdGAgcmFzdGVyIGlzIGF0IHRoaXMgc2l0ZS4gIFRoaXMgY2FuIGJlIGRvbmUgd2l0aCB0aGUgYGV4dHJhY3QoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHJhc3RlcmAgbGlicmFyeS4gIAoKKipIb3dldmVyKiosIHRoZSB0aGlzIGZ1bmN0aW9uIGRvZXNuJ3Qgd29yayBkaXJlY3RseSB3aXRoIGBzZmAgb2JqZWN0cyBzbyB3ZSBuZWVkIHRvIGNhc3QgaXQgaW50byBhIGBTcGF0aWFsYCBvYmplY3RbXjFdLiBGb3J0dW5hdGx5LCB0aGF0IGlzIGEgcHJldHR5IGVhc3kgY29lcmNpb24uCgpgYGB7cn0KcmFzdGVyOjpleHRyYWN0KGFsdCwgYXMoc2ZyYW4sIlNwYXRpYWwiKSApIApgYGAKCgo8ZGl2IGNsYXNzPSJib3gteWVsbG93Ij4qKldhcm5pbmc6KiogaW4gdGhlIGFib3ZlIGNvZGUsIEkgdXNlZCB0aGUgZnVuY3Rpb24gYGV4dHJhY3QoKWAgdG8gZXh0cmFjdCB0aGUgZGF0YSBmcm9tIHRoZSBgYWx0YCByYXN0ZXIgZm9yIHRoZSBjb29yZGluYXRlIG9mIHRoZSB0YXJnZXQgbG9jYWxlLiAgSG93ZXZlciwgdGhlcmUgaXMgYWxzbyBhbiBgZXh0cmFjdCgpYCBmdW5jdGlvbiB0aGF0IGhhcyBiZWVuIGJyb3VnaHQgaW4gZnJvbSB0aGUgYGRwbHlyYCBsaWJyYXJ5IChhcyBwYXJ0IG9mIGB0aWR5dmVyc2VgKS4gIEluIHRoaXMgZmlsZSwgSSBsb2FkZWQgYGxpYnJhcnkocmFzdGVyKWAgYmVmb3JlIGBsaWJyYXJ5KHRpZHl2ZXJzZSlgIGFuZCBhcyBzdWNoIHRoZSBgZHBseXI6OmV4dHJhY3QoKWAgZnVuY3Rpb24gaGFzIG92ZXJyaWRkZW4gdGhlIG9uZSBmcm9tIGByYXN0ZXJg4oCUdGhleSBjYW5ub3QgKmJvdGgqIGJlIGF2YWlsYWJsZS4gIEFzIGEgY29uc2VxdWVuY2UsIEkgdXNlIHRoZSBmdWxsIG5hbWUgb2YgdGhlIGZ1bmN0aW9uIHdpdGggYHBhY2thZ2U6OmZ1bmN0aW9uYCB3aGVuIEkgY2FsbCBpdCBhcyBgcmFzdGVyOjpleHRyYWN0KClgIHRvIHJlbW92ZSBhbGwgYW1iaWd1aXR5LiAgSWYgSSBoYWQgbm90LCBJIGdvdCBhIG1lc3NhZ2Ugc2F5aW5nIHNvbWV0aGluZyBsaWtlLCBgRXJyb3IgaW4gVXNlTWV0aG9kKCJleHRyYWN0XyIpIDogbm8gYXBwbGljYWJsZSBtZXRob2QgZm9yICdleHRyYWN0XycgYXBwbGllZCB0byBhbiBvYmplY3Qgb2YgY2xhc3MgImMoJ1Jhc3RlckxheWVyJywgJ1Jhc3RlcicsICdCYXNpY1Jhc3RlcicpImAuICBOb3csIEkga25vdyB0aGVyZSBpcyBhbiBgZXh0cmFjdCgpYCBmdW5jdGlvbiBpbiBgcmFzdGVyYCBzbyB0aGlzIGlzIHRoZSAqKmRlYWQgZ2l2ZWF3YXkqKiB0aGF0IGl0IGhhcyBiZWVuIG92ZXJ3cml0dGVuIGJ5IGEgc3Vic2VxdWVudCBsaWJyYXJ5IGNhbGwuICAKPC9kaXY+CgojIyMgT3B0aW9uIDEgLSBNYW5pcHVsYXRlIHRoZSBSYXN0ZXIKClRvIHdvcmsgb24gYSByYXN0ZXIgZGlyZWN0bHksIHdlIGNhbiBhY2Nlc3MgdGhlIHZhbHVlcyB3aXRoaW4gaXQgdXNpbmcgdGhlIGB2YWx1ZXMoKWAgZnVuY3Rpb24gKEkga25vdywgdGhlc2Ugc3RhdGlzdGljYW4vcHJvZ3JhbW1lcnMgYXJlIHF1aXRlIGNsZWF2ZXIpLgoKU28sIHRvIG1ha2UgYSBjb3B5IGFuZCBtYWtlIG9ubHkgdGhlIHZhbHVlcyB0aGF0IGFyZSArLy0gMTAwbSBvZiBgc2ZyYW5gIHdlIGNhbi4KCmBgYHtyfQphbHRfYmFuZCA8LSBhbHQKdmFsdWVzKCBhbHRfYmFuZCApWyB2YWx1ZXMoYWx0X2JhbmQpIDw9IDIwNSBdIDwtIE5BCnZhbHVlcyggYWx0X2JhbmQgKVsgdmFsdWVzKGFsdF9iYW5kKSA+PSA0MDUgXSA8LSBOQQphbHRfYmFuZApgYGAKClRoZW4gd2UgY2FuIHBsb3Qgb3ZlcmxheSBwbG90cyBvZiBlYWNoIChub3RpY2UgaG93IEkgaGlkIHRoZSBsZWdlbmQgZm9yIHRoZSBmaXJzdCBgYWx0YCByYXN0ZXIpLgoKYGBge3J9CnBsb3QoIGFsdCwgY29sPSJncmF5IiwgbGVnZW5kPUZBTFNFLCB4bGFiPSJMb25naXR1ZGUiLCB5bGFiPSJMYXRpdHVkZSIpCnBsb3QoIGFsdF9iYW5kLCBhZGQ9VFJVRSApCmBgYAoKCiMjIyBPcHRpb24gMiAtIE1hbmlwdWxhdGUgdGhlIERhdGEgRnJhbWVzCgpXZSBjYW4gYWxzbyBwcm9jZWVkIGJ5IHJlbHlpbmcgdXBvbiB0aGUgYGRhdGEuZnJhbWVgIG9iamVjdHMgcmVwcmVzZW50aW5nIHRoZSBlbGV2YXRpb24uICBTbyBsZXQncyBnbyBiYWNrIHRvIG91ciB0aGUgYGFsdC5kZmAgb2JqZWN0IGFuZCB1c2UgdGhhdCBpbiBjb21iaW5hdGlvbiB3aXRoIGEgYGZpbHRlcmAgYW5kIHBsb3QgYm90aCBgZGF0YS5mcmFtZWAgb2JqZWN0cyAodGhlIG91dGxpbmUgb2YgdGhlIGxhbmRzY2FwZSBpbiBncmF5IGFuZCB0aGUgZWxldmF0aW9uIHJhbmdlIGFzIGEgZ3JhZGllbnQpLiAgSSB0aGVuIG92ZXJsYXkgdGhlIGJlZXRsZSBkYXRhIHdpdGggdGhlIHJhdGlvcyBhcyBzaXplcyBhbmQgbGFiZWwgdGhlIGxvY2FsZXMgd2l0aCBgZ2dyZXBlbGAuICBOb3RpY2UgaGVyZSB0aGF0IHlvdSBjYW4gdXNlIHRoZSBgc2Y6Omdlb21ldHJ5YCBvYmplY3QgZnJvbSBgYmVldGxlc2AgaWYgeW91IHBhc3MgaXQgdGhyb3VnaCB0aGUgYHN0X2Nvb3JkaW5hdGVzYCBmdW5jdGlvbiBhcyBhIHN0YXRpc3RpY2FsIHRyYW5mb3JtIG1ha2luZyBpdCByZWd1bGFyIGNvb3JkaW5hdGVzIGFuZCBub3QgYHNmYCBvYmplY3RzICh5ZXMgdGhpcyBpcyBraW5kIG9mIGEgdHJpY2sgYW5kIGhhY2sgYnV0IEtFRVAgSVQgSEFORFkhKS4KCmBgYHtyIGVjaG89VFJVRX0KbGlicmFyeSggZ2dyZXBlbCApCmFsdC5kZiAlPiUKICBmaWx0ZXIoIEVsZXZhdGlvbiA+PSAyMDUsCiAgICAgICAgICBFbGV2YXRpb24gPD0gNDA1KSAlPiUKICBnZ3Bsb3QoKSArIAogIGdlb21fcmFzdGVyKCBhZXMoIHggPSBMb25naXR1ZGUsCiAgICAgICAgICAgICAgICAgICAgeSA9IExhdGl0dWRlKSwKICAgICAgICAgICAgICAgZmlsbCA9ICJncmF5ODAiLCAKICAgICAgICAgICAgICAgZGF0YT1hbHQuZGYgKSArIAogIGdlb21fcmFzdGVyKCBhZXMoIHggPSBMb25naXR1ZGUsCiAgICAgICAgICAgICAgICAgICAgeSA9IExhdGl0dWRlLCAKICAgICAgICAgICAgICAgICAgICBmaWxsID0gRWxldmF0aW9uICkgKSArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKCBsb3cgPSAiZGFya29saXZlZ3JlZW4iLAogICAgICAgICAgICAgICAgICAgICAgICBtaWQgPSAieWVsbG93IiwKICAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJicm93biIsIAogICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDMwNSApICsKICBnZW9tX3NmKCBhZXMoc2l6ZT1NRlJhdGlvKSwgCiAgICAgICAgICAgYWxwaGE9MC41LCAKICAgICAgICAgICBjb2xvcj0iZG9kZ2VyYmx1ZTMiLCAKICAgICAgICAgICBkYXRhPWJlZXRsZXMpICsKICBnZW9tX3RleHRfcmVwZWwoIGFlcyggbGFiZWwgPSBTaXRlLAogICAgICAgICAgICAgICAgICAgICAgICBnZW9tZXRyeSA9IGdlb21ldHJ5KSwKICAgICAgICAgICAgICAgICAgIGRhdGEgPSBiZWV0bGVzLAogICAgICAgICAgICAgICAgICAgc3RhdCA9ICJzZl9jb29yZGluYXRlcyIsIAogICAgICAgICAgICAgICAgICAgc2l6ZSA9IDQsIAogICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZG9kZ2VyYmx1ZTQiKSArIAogIGNvb3JkX3NmKCkgKyAKICB0aGVtZV9taW5pbWFsKCkgCmBgYAoKCgpWZXJ5IG5pY2UgaW5kZWVkLgoKCgpbXjFdOiBBIGBTcGF0aWFsYCBvYmplY3QgaXMgZnJvbSB0aGUgYHNwYCBsaWJyYXJ5LiAgVGhpcyBpcyBhbiBvbGRlciBsaWJyYXJ5IHRoYXQgaXMgc3RpbGwgdXNlZCBieSBzb21lLiAgSXQgaXMgYSByb2J1c3QgbGlicmFyeSAqYnV0KiBpdCBpcyBwdXQgdG9nZXRoZXIgaW4gYSBzbGlnaHRseSBkaWZmZXJlbnQgd2F5IHRoYXQgY29tcGxpY2F0ZXMgc2l0dWF0aW9ucyBhIGJpdCwgd2hpY2ggaXMgbm90IHdoeSB3ZSBhcmUgY292ZXJpbmcgaXQgaW4gdGhpcyB0b3BpYy4=