Rasters!

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

map with 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.

  1. Find the coordinates of the site named sfran
  2. Extract the elevation from the alt raster that is within 100m (+/-) of that site.
  3. Plot the whole baja data as a background
  4. 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 object1. 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.


  1. A Spatial object is from the sp library. This is an older library that is still used by some. It is a robust library but it is put together in a slightly different way that complicates situations a bit, which is not why we are covering it in this topic.↩︎

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