Datacarpentries 01

Introduction to Geospatial Raster and Vector Data with R


Markdown Author: Jessie Bell


I. Raster Data

#first, preview the metadata. It is ideal to do this before importing your data.

beforereadingdata <- describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif")


#you can store the information above from the tif file by using the next code

HARV_dsmCrop_info <- capture.output(describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif"))
Metadata

A: Nomenclature

Use the following nomenclature for naming your strings, etc…

filetype _ location

  • File Type = Digital Surface Model (DSM)
  • Location = Harvard Forest
 → DSM_HARV

B. Summaries

Use summary(raster) to get data range statistics. This function will only summarize a random 100,000 cells from the raster though. To get all cells in summary stat, use the following functions together:

summary(values(DSM_HARV)

# read in the raster using rast() function

#naming below is datatype_HARV. HARV is because the data is from Harvard Forest. The DSM because the data filetype is DSM

DSM_HARV <- rast("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif")

DSM_HARV
## class       : SpatRaster 
## dimensions  : 1367, 1697, 1  (nrow, ncol, nlyr)
## resolution  : 1, 1  (x, y)
## extent      : 731453, 733150, 4712471, 4713838  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 18N (EPSG:32618) 
## source      : HARV_dsmCrop.tif 
## name        : HARV_dsmCrop 
## min value   :       305.07 
## max value   :       416.07
Visualize

A: Terra Package

You can use the terra package to convert tif to dataframe. Once you do this conversion, your map will be plotable.

DSM_HARV_df <- as.data.frame(DSM_HARV, xy = T)

Now you can view the structure of the data and see that it is formatted as a dataframe.

str(DSM_HARV_df)
## 'data.frame':    2319799 obs. of  3 variables:
##  $ x           : num  731454 731455 731456 731457 731458 ...
##  $ y           : num  4713838 4713838 4713838 4713838 4713838 ...
##  $ HARV_dsmCrop: num  409 408 407 407 409 ...

B: ggplot()

Now we can use ggplot to view the data.

ggplot() +
    geom_raster(data = DSM_HARV_df , aes(x = x, y = y, fill = HARV_dsmCrop)) +
    scale_fill_viridis_c() +
    coord_quickmap()


C: Terra’s plot function

plot(DSM_HARV)

CRS

A: Check out project string

We can view the CRS string associated with our R object using the crs() function.

crs(DSM_HARV, proj = T)
## [1] "+proj=utm +zone=18 +datum=WGS84 +units=m +no_defs"
#this project string has a zone - so we automatically know it uses a UTM 

#our units are in meters
Min & Max

It is useful to know the minimum or maximum values of a raster dataset. In this case, given we are working with elevation data, these values represent the min/max elevation range at our site.

Raster statistics are often calculated and embedded in a GeoTIFF for us. We can view these values:

minmax(DSM_HARV)
##     HARV_dsmCrop
## min       305.07
## max       416.07

If the min and max have not been calulcated yet, we can do that by using the following code:

DSM_HARV <- setMinMax(DSM_HARV)

We can see that the elevation at our site ranges from 305.0700073 m to 416.0699768 m.

Bands

A: How many bands?

To check how many bands your raster dataset contains, use the nly() function.

nlyr(DSM_HARV)
## [1] 1

B: Multi-band

Raster data can also be multi-band, meaning that one raster file contains data for more than one variable or time period for each cell. By default the raster() function only imports the first band in a raster regardless of whether it has one or more bands. Jump to a later episode in this series for information on working with multi-band rasters: “Work with Multi-band Rasters in R”.

Missing Data

To highlight NA values in ggplot, alter the scale_fill_*() layer to contain a colour instruction for NA values, like scale_fill_viridis_c(na.value = ‘deeppink’).

Find what the no data value is within a tif by using the following code:

moredescribe <- describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif")

showfilesource <- sources(DSM_HARV)

Output of describe prints things like:

[1] “Driver: GTiff/GeoTIFF”
[2] “Files: data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif”

[3] “Size is 1697, 1367”
[4] “Coordinate System is:”
[5] “PROJCRS["WGS 84 / UTM zone 18N",”

[6] ”

Bad Data

Sometimes a raster’s metadata will tell us the range of expected values for a raster. Values outside of this range are suspect and we need to consider that when we analyze the data. Sometimes, we need to use some common sense and scientific insight as we examine the data - just as we would for field data to identify questionable values.

Plotting data with appropriate highlighting can help reveal patterns in bad values and may suggest a solution.

Histograms

We can explore the distribution of values contained within our raster using the geom_histogram() function which produces a histogram. Histograms are often useful in identifying outliers and bad data values in our raster data.

ggplot() +
    geom_histogram(data = DSM_HARV_df, aes(HARV_dsmCrop), bins = 40)

Note that the shape of this histogram looks similar to the previous one that was created using the default of 30 bins. The distribution of elevation values for our Digital Surface Model (DSM) looks reasonable. It is likely there are no bad data values in this particular raster.

practice <- describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_DSMhill.tif")

II. Plotting Rasters

Setting Breaks

A: Using Data Breaks

In the previous episode, we viewed our data using a continuous color ramp. For clarity and visibility of the plot, we may prefer to view the data “symbolized” or colored according to ranges of values. This is comparable to a “classified” map.

To do this, we need to tell ggplot how many groups to break our data into, and where those breaks should be. To make these decisions, it is useful to first explore the distribution of the data using a bar plot. To begin with, we will use dplyr’s mutate() function combined with cut() to split the data into 3 bins.

DSM_HARV_df_mutated <- DSM_HARV_df %>%
  mutate(fct_elevation = cut(HARV_dsmCrop, breaks = 6))


ggplot(data = DSM_HARV_df_mutated, aes(fct_elevation, fill=fct_elevation)) +
    geom_bar(show.legend = F)

plot(DSM_HARV_df_mutated$HARV_dsmCrop)


If we want to know the cutoff values for the groups, we can ask for the unique values of fct_elevation:

unique(DSM_HARV_df_mutated$fct_elevation)
## [1] (398,416] (379,398] (361,379] (342,361] (324,342] (305,324]
## Levels: (305,324] (324,342] (342,361] (361,379] (379,398] (398,416]

And we can get the count of values in each group using dplyr’s group_by() and count() functions:

DSM_HARV_df_mutated %>%
  group_by(fct_elevation) %>%
  count()
## # A tibble: 6 × 2
## # Groups:   fct_elevation [6]
##   fct_elevation      n
##   <fct>          <int>
## 1 (305,324]      30211
## 2 (324,342]     388680
## 3 (342,361]     777816
## 4 (361,379]     752257
## 5 (379,398]     349651
## 6 (398,416]      21184

Make it colorful!

Use groups with different colors to make cute maps!

ggplot()+
  geom_raster(data = DSM_HARV_df_mutated, aes(x, y, fill = fct_elevation))+
  coord_quickmap()

DSM_HARV_df_mutated <- DSM_HARV_df %>%
                mutate(fct_elevation = cut(HARV_dsmCrop, breaks = 10))


ggplot(data = DSM_HARV_df_mutated, aes(fct_elevation, fill=fct_elevation)) +
    geom_bar(show.legend = F)

ggplot()+
  geom_raster(data = DSM_HARV_df_mutated, aes(x, y, fill = fct_elevation))+
  coord_quickmap()+
  theme(legend.position = "bottom")

The plots above use ggplots default colors – we can use our own color palletes

my_col <- terrain.colors(10)

ggplot() +
 geom_raster(data = DSM_HARV_df_mutated , aes(x = x, y = y,
                                      fill = fct_elevation)) + 
    scale_fill_manual(values = terrain.colors(10)) + 
    coord_quickmap()

Or we can also turn off the labels of both axes by passing element_blank() to the relevant part of the theme() function.

ggplot() +
 geom_raster(data = DSM_HARV_df_mutated , aes(x = x, y = y,
                                      fill = fct_elevation)) + 
    scale_fill_manual(values = my_col, name = "Elevation") +
    theme(axis.title = element_blank()) + 
    coord_quickmap()

Axes Labels

Or we can also turn off the labels of both axes by passing element_blank() to the relevant part of the theme() function.

ggplot() +
 geom_raster(data = DSM_HARV_df_mutated , aes(x = x, y = y,
                                      fill = fct_elevation)) + 
    scale_fill_manual(values = my_col, name = "Elevation") +
    theme(axis.title = element_blank()) + 
    coord_quickmap()


Challenge

Challenge! Using Custom Breaks

  1. Six classified ranges of values (break points) that are evenly divided among the range of pixel values.

  2. Axis labels.

  3. A plot title.

DSM_HARV_df_mutated <- DSM_HARV_df %>%
                mutate(fct_elevation = cut(HARV_dsmCrop, breaks = 6))

my_col <- terrain.colors(6)

ggplot() + 
  geom_raster(data = DSM_HARV_df_mutated, aes(x = x, y = y, fill = fct_elevation)) + 
    scale_fill_manual(values = my_col, name = "Elevation") +
  xlab("UTM Easting Coordinate (m)")+
  ylab("UTM Northing Coordinate (m)")+
  ggtitle("Classified Elevation Map - NEON Harvard Forest Field Site")+
    theme() + 
    coord_quickmap()

III. Layering Rasters

Adding Hillshade

We can layer a raster on top of a hillshade raster for the same area, and use a transparency factor to create a 3-dimensional shaded effect. A hillshade is a raster that maps the shadows and texture that you would see from above when viewing terrain. We will add a custom color, making the plot grey. |

DSM_hill_HARV <- rast('data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_DSMhill.tif')


DSM_hill_HARV
## class       : SpatRaster 
## dimensions  : 1367, 1697, 1  (nrow, ncol, nlyr)
## resolution  : 1, 1  (x, y)
## extent      : 731453, 733150, 4712471, 4713838  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 18N (EPSG:32618) 
## source      : HARV_DSMhill.tif 
## name        : HARV_DSMhill 
## min value   :   -0.7136298 
## max value   :    0.9999997
Convert to DF
DSM_hill_HARV_df <- as.data.frame(DSM_hill_HARV, xy = TRUE) 

str(DSM_hill_HARV_df)
## 'data.frame':    2313675 obs. of  3 variables:
##  $ x           : num  731455 731456 731457 731458 731459 ...
##  $ y           : num  4713837 4713837 4713837 4713837 4713837 ...
##  $ HARV_DSMhill: num  -0.15567 0.00743 0.86989 0.9791 0.96283 ...
Plot Hillshade
ggplot() +
  geom_raster(data = DSM_hill_HARV_df, 
              aes(x=x, y=y, alpha=HARV_DSMhill))+
      scale_alpha(range=c(0.15, 0.65), 
                  guide = "none")+
  coord_quickmap()

Layering
ggplot()+
  geom_raster(data=DSM_HARV_df, 
              aes(x=x, y=y, 
                  fill= HARV_dsmCrop))+
  geom_raster(data = DSM_hill_HARV_df, 
              aes(x=x, y=y, 
                  alpha = HARV_DSMhill))+
  scale_fill_viridis_c()+
  scale_alpha(range=c(0.15, 0.65), guide="none") +
  ggtitle("Elevation with hillshade") +
  coord_quickmap()

Challenge

Use the files in the data/NEON-DS-Airborne-Remote-Sensing/SJER/ directory to create BOTH a Digital Terrain Model map and Digital Surface Model map of the San Joaquin Experimental Range field site.

Make sure to:

  • include hillshade in the maps

  • label axes on the DSM map and exclude them from the DTM map

  • include a title for each map

  • experiment with various alpha values and color palettes to represent the data

DSM
#DSM Data

DSM_SJER <- rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DSM/SJER_dsmCrop.tif")

R_df <- as.data.frame(DSM_SJER, xy=T)

# DSM Hillshade

DSM_hill_SJER <- 
  rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DSM/SJER_dsmHill.tif")

DSM_hill_SJER_df <- as.data.frame(DSM_hill_SJER, xy=T)

#plot

ggplot()+
  geom_raster(data=R_df, aes(x, y, fill=SJER_dsmCrop, alpha=0.8))+
  geom_raster(data=DSM_hill_SJER_df, aes(x, y, alpha = SJER_dsmHill))+
  scale_fill_viridis_c()+
  guides(fill=guide_colorbar())+
  scale_alpha(range=c(0.4, 0.7), guide = "none")+
  theme_bw()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())+
  xlab("UTM Easting Coordinate (m)")+
  ylab("UTM Northing Coordinate (m)")+
  ggtitle("DSM with Hillshade")

  coord_quickmap()
## <ggproto object: Class CoordQuickmap, CoordCartesian, Coord, gg>
##     aspect: function
##     backtransform_range: function
##     clip: on
##     default: FALSE
##     distance: function
##     expand: TRUE
##     is_free: function
##     is_linear: function
##     labels: function
##     limits: list
##     modify_scales: function
##     range: function
##     render_axis_h: function
##     render_axis_v: function
##     render_bg: function
##     render_fg: function
##     setup_data: function
##     setup_layout: function
##     setup_panel_guides: function
##     setup_panel_params: function
##     setup_params: function
##     train_panel_guides: function
##     transform: function
##     super:  <ggproto object: Class CoordQuickmap, CoordCartesian, Coord, gg>
DTM
DTM_SJER <- rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DTM/SJER_dtmCrop.tif")

DTM_hill_SJER <- rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DTM/SJER_dtmHill.tif")

#dataframe em

DTM_hill_SJER_df <- as.data.frame(DTM_hill_SJER, xy=T)

DTM_SJER_df <- as.data.frame(DTM_SJER, xy=T)

#ggplot it

ggplot()+
  geom_raster(data=DTM_SJER_df, aes(x, y, fill=SJER_dtmCrop, alpha=2.0))+
  geom_raster(data=DTM_hill_SJER_df, aes(x, y, alpha=SJER_dtmHill))+
  xlab("")+
  ylab("")+
  scale_fill_viridis_c()+
  guides(fill=guide_colorbar())+
  scale_alpha(range=c(0.4, 0.7), guide = "none")+
  theme_bw()+
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor=element_blank())+
  ggtitle("DTM with Hillshade")+
  coord_quickmap()

Key Points
  1. Continuous data ranges can be grouped into categories using mutate() and cut().

  2. Use built-in terrain.colors() or set your preferred color scheme manually.

  3. Layer rasters on top of one another by using the alpha aesthetic.

LS0tDQp0aXRsZTogIiAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY3NzOiBzdHlsZS5jc3MNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQoNCmxpYnJhcnkodGVycmEpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KDQpgYGANCg0KIyMjIyMgKipEYXRhY2FycGVudHJpZXMgMDEqKg0KDQpJbnRyb2R1Y3Rpb24gdG8gR2Vvc3BhdGlhbCBSYXN0ZXIgYW5kIFZlY3RvciBEYXRhIHdpdGggUiANCg0KPGJyPg0KDQoqKk1hcmtkb3duIEF1dGhvcjoqKiBKZXNzaWUgQmVsbA0KDQp8DQp8DQoNCiMjIyMgKipJLiBSYXN0ZXIgRGF0YSoqIHsudGFic2V0fQ0KDQpgYGB7ciByZWFkIGluIGRhdGF9DQoNCiNmaXJzdCwgcHJldmlldyB0aGUgbWV0YWRhdGEuIEl0IGlzIGlkZWFsIHRvIGRvIHRoaXMgYmVmb3JlIGltcG9ydGluZyB5b3VyIGRhdGEuDQoNCmJlZm9yZXJlYWRpbmdkYXRhIDwtIGRlc2NyaWJlKCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvSEFSVi9EU00vSEFSVl9kc21Dcm9wLnRpZiIpDQoNCg0KI3lvdSBjYW4gc3RvcmUgdGhlIGluZm9ybWF0aW9uIGFib3ZlIGZyb20gdGhlIHRpZiBmaWxlIGJ5IHVzaW5nIHRoZSBuZXh0IGNvZGUNCg0KSEFSVl9kc21Dcm9wX2luZm8gPC0gY2FwdHVyZS5vdXRwdXQoZGVzY3JpYmUoImRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9IQVJWL0RTTS9IQVJWX2RzbUNyb3AudGlmIikpDQoNCmBgYA0KDQojIyMjIyAqKk1ldGFkYXRhKioNCg0KKipBOiBOb21lbmNsYXR1cmUqKg0KDQpVc2UgdGhlIGZvbGxvd2luZyBub21lbmNsYXR1cmUgZm9yIG5hbWluZyB5b3VyIHN0cmluZ3MsIGV0Yy4uLg0KDQpbKipmaWxldHlwZSoqXXtzdHlsZT0iY29sb3I6I2E1OGFmZiJ9IF8gPHNwYW4gc3R5bGU9ImNvbG9yOiMwMGJmYzQiPiAqKmxvY2F0aW9uKiogPC9zcGFuPg0KDQoNCg0KICAqIFtGaWxlIFR5cGUgPSBEaWdpdGFsIFN1cmZhY2UgTW9kZWwgKERTTSlde3N0eWxlPSJjb2xvcjojYTU4YWZmIn0NCiAgKiA8c3BhbiBzdHlsZT0iY29sb3I6IzAwYmZjNCI+IExvY2F0aW9uID0gSGFydmFyZCBGb3Jlc3QgPC9zcGFuPg0KICANCnwgICZyYXJyOyBbKipEU00qKl17c3R5bGU9ImNvbG9yOiNhNThhZmYifV88c3BhbiBzdHlsZT0iY29sb3I6IzAwYmZjNCI+KipIQVJWKio8L3NwYW4+DQoNCnwNCnwNCg0KKipCLiBTdW1tYXJpZXMqKiANCg0KVXNlIHN1bW1hcnkocmFzdGVyKSB0byBnZXQgZGF0YSByYW5nZSBzdGF0aXN0aWNzLiBUaGlzIGZ1bmN0aW9uIHdpbGwgb25seSBzdW1tYXJpemUgYSAqcmFuZG9tKiAxMDAsMDAwIGNlbGxzIGZyb20gdGhlIHJhc3RlciB0aG91Z2guIFRvIGdldCBhbGwgY2VsbHMgaW4gc3VtbWFyeSBzdGF0LCB1c2UgdGhlIGZvbGxvd2luZyBmdW5jdGlvbnMgdG9nZXRoZXI6IA0KDQpbKipzdW1tYXJ5Kipde3N0eWxlPSJjb2xvcjojNTNiZTAyIn0oWyoqdmFsdWVzKipde3N0eWxlPSJjb2xvcjojZGI3MmZiIn0oWyoqRFNNKipde3N0eWxlPSJjb2xvcjojYTU4YWZmIn1fPHNwYW4gc3R5bGU9ImNvbG9yOiMwMGJmYzQiPioqSEFSVioqPC9zcGFuPikNCg0KDQoNCmBgYHtyIGV4cGxvcmUgcmFzdGVyfQ0KDQojIHJlYWQgaW4gdGhlIHJhc3RlciB1c2luZyByYXN0KCkgZnVuY3Rpb24NCg0KI25hbWluZyBiZWxvdyBpcyBkYXRhdHlwZV9IQVJWLiBIQVJWIGlzIGJlY2F1c2UgdGhlIGRhdGEgaXMgZnJvbSBIYXJ2YXJkIEZvcmVzdC4gVGhlIERTTSBiZWNhdXNlIHRoZSBkYXRhIGZpbGV0eXBlIGlzIERTTQ0KDQpEU01fSEFSViA8LSByYXN0KCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvSEFSVi9EU00vSEFSVl9kc21Dcm9wLnRpZiIpDQoNCkRTTV9IQVJWDQoNCmBgYA0KDQoNCiMjIyMjICoqVmlzdWFsaXplKioNCg0KKipBOiBUZXJyYSBQYWNrYWdlKioNCg0KWW91IGNhbiB1c2UgdGhlIHRlcnJhIHBhY2thZ2UgdG8gY29udmVydCB0aWYgdG8gZGF0YWZyYW1lLiBPbmNlIHlvdSBkbyB0aGlzIGNvbnZlcnNpb24sIHlvdXIgbWFwIHdpbGwgYmUgcGxvdGFibGUuIA0KDQpgYGB7ciB2aXN1YWxpemUgcmFzdGVyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93In0NCg0KRFNNX0hBUlZfZGYgPC0gYXMuZGF0YS5mcmFtZShEU01fSEFSViwgeHkgPSBUKQ0KDQpgYGANCg0KTm93IHlvdSBjYW4gdmlldyB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhIGFuZCBzZWUgdGhhdCBpdCBpcyBmb3JtYXR0ZWQgYXMgYSBkYXRhZnJhbWUuIA0KDQpgYGB7ciB2aWV3ZGF0YX0NCg0Kc3RyKERTTV9IQVJWX2RmKQ0KYGBgDQp8DQp8DQoNCioqQjogZ2dwbG90KCkqKg0KDQpOb3cgd2UgY2FuIHVzZSBnZ3Bsb3QgdG8gdmlldyB0aGUgZGF0YS4gDQoNCmBgYHtyIGdncGxvdCwgY2xhc3Muc291cmNlPSJmb2xkLXNob3ciLCBjYWNoZT1UUlVFfQ0KDQpnZ3Bsb3QoKSArDQogICAgZ2VvbV9yYXN0ZXIoZGF0YSA9IERTTV9IQVJWX2RmICwgYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IEhBUlZfZHNtQ3JvcCkpICsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQp8DQp8DQoNCioqQzogVGVycmEncyBwbG90IGZ1bmN0aW9uKioNCg0KYGBge3IgcGxvdHRlcnJhLCBjYWNoZT1UUlVFLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCnBsb3QoRFNNX0hBUlYpDQoNCmBgYA0KDQoNCiMjIyMjICoqQ1JTKioNCg0KKipBOiBDaGVjayBvdXQgcHJvamVjdCBzdHJpbmcqKg0KDQp8DQoNCldlIGNhbiB2aWV3IHRoZSBDUlMgc3RyaW5nIGFzc29jaWF0ZWQgd2l0aCBvdXIgUiBvYmplY3QgdXNpbmcgdGhlICoqY3JzKCkqKiBmdW5jdGlvbi4NCg0KYGBge3Igdmlld2NycywgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQ0KDQpjcnMoRFNNX0hBUlYsIHByb2ogPSBUKQ0KDQojdGhpcyBwcm9qZWN0IHN0cmluZyBoYXMgYSB6b25lIC0gc28gd2UgYXV0b21hdGljYWxseSBrbm93IGl0IHVzZXMgYSBVVE0gDQoNCiNvdXIgdW5pdHMgYXJlIGluIG1ldGVycw0KDQpgYGANCg0KIyMjIyMgKipNaW4gJiBNYXgqKg0KDQpJdCBpcyB1c2VmdWwgdG8ga25vdyB0aGUgbWluaW11bSBvciBtYXhpbXVtIHZhbHVlcyBvZiBhIHJhc3RlciBkYXRhc2V0LiBJbiB0aGlzIGNhc2UsIGdpdmVuIHdlIGFyZSB3b3JraW5nIHdpdGggZWxldmF0aW9uIGRhdGEsIHRoZXNlIHZhbHVlcyByZXByZXNlbnQgdGhlIG1pbi9tYXggZWxldmF0aW9uIHJhbmdlIGF0IG91ciBzaXRlLg0KDQpSYXN0ZXIgc3RhdGlzdGljcyBhcmUgb2Z0ZW4gY2FsY3VsYXRlZCBhbmQgZW1iZWRkZWQgaW4gYSBHZW9USUZGIGZvciB1cy4gV2UgY2FuIHZpZXcgdGhlc2UgdmFsdWVzOg0KDQpgYGB7ciBtaW5tYXgsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCm1pbm1heChEU01fSEFSVikNCg0KYGBgDQoNCklmIHRoZSBtaW4gYW5kIG1heCBoYXZlIG5vdCBiZWVuIGNhbHVsY2F0ZWQgeWV0LCB3ZSBjYW4gZG8gdGhhdCBieSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6IA0KDQpgYGB7ciBjYWxjIG1pbm1heCwgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQ0KDQpEU01fSEFSViA8LSBzZXRNaW5NYXgoRFNNX0hBUlYpDQoNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlIGVsZXZhdGlvbiBhdCBvdXIgc2l0ZSByYW5nZXMgZnJvbSAzMDUuMDcwMDA3MyBtIHRvIDQxNi4wNjk5NzY4IG0uDQoNCiMjIyMjICoqQmFuZHMqKg0KDQoqKkE6IEhvdyBtYW55IGJhbmRzPyoqDQoNClRvIGNoZWNrIGhvdyBtYW55IGJhbmRzIHlvdXIgcmFzdGVyIGRhdGFzZXQgY29udGFpbnMsIHVzZSB0aGUgKipubHkoKSoqIGZ1bmN0aW9uLg0KDQpgYGB7ciBubHksIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0Kbmx5cihEU01fSEFSVikNCg0KYGBgDQoNCnwNCnwNCg0KKipCOiBNdWx0aS1iYW5kKioNCg0KUmFzdGVyIGRhdGEgY2FuIGFsc28gYmUgbXVsdGktYmFuZCwgbWVhbmluZyB0aGF0IG9uZSByYXN0ZXIgZmlsZSBjb250YWlucyBkYXRhIGZvciBtb3JlIHRoYW4gb25lIHZhcmlhYmxlIG9yIHRpbWUgcGVyaW9kIGZvciBlYWNoIGNlbGwuIEJ5IGRlZmF1bHQgdGhlIHJhc3RlcigpIGZ1bmN0aW9uIG9ubHkgaW1wb3J0cyB0aGUgZmlyc3QgYmFuZCBpbiBhIHJhc3RlciByZWdhcmRsZXNzIG9mIHdoZXRoZXIgaXQgaGFzIG9uZSBvciBtb3JlIGJhbmRzLiBKdW1wIHRvIGEgbGF0ZXIgZXBpc29kZSBpbiB0aGlzIHNlcmllcyBmb3IgaW5mb3JtYXRpb24gb24gd29ya2luZyB3aXRoIG11bHRpLWJhbmQgcmFzdGVyczogIldvcmsgd2l0aCBNdWx0aS1iYW5kIFJhc3RlcnMgaW4gUiIuDQoNCg0KIyMjIyMgKipNaXNzaW5nIERhdGEqKg0KDQpUbyBoaWdobGlnaHQgTkEgdmFsdWVzIGluIGdncGxvdCwgYWx0ZXIgdGhlIHNjYWxlX2ZpbGxfKigpIGxheWVyIHRvIGNvbnRhaW4gYSBjb2xvdXIgaW5zdHJ1Y3Rpb24gZm9yIE5BIHZhbHVlcywgbGlrZSBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYS52YWx1ZSA9ICdkZWVwcGluaycpLiANCg0KRmluZCB3aGF0IHRoZSBubyBkYXRhIHZhbHVlIGlzIHdpdGhpbiBhIHRpZiBieSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6DQoNCmBgYHtyIGRlc2NyaWJlLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCm1vcmVkZXNjcmliZSA8LSBkZXNjcmliZSgiZGF0YS9ORU9OLURTLUFpcmJvcm5lLVJlbW90ZS1TZW5zaW5nL0hBUlYvRFNNL0hBUlZfZHNtQ3JvcC50aWYiKQ0KDQpzaG93ZmlsZXNvdXJjZSA8LSBzb3VyY2VzKERTTV9IQVJWKQ0KYGBgDQp8DQp8DQpPdXRwdXQgb2YgZGVzY3JpYmUgcHJpbnRzIHRoaW5ncyBsaWtlOiANCg0KIFsxXSAiRHJpdmVyOiBHVGlmZi9HZW9USUZGIiAgICAgICAgICAgIA0KIFsyXSAiRmlsZXM6IGRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9IQVJWL0RTTS9IQVJWX2RzbUNyb3AudGlmIiAgICAgICAgICAgICAgICAgICAgIA0KIA0KIFszXSAiU2l6ZSBpcyAxNjk3LCAxMzY3IiAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiBbNF0gIkNvb3JkaW5hdGUgU3lzdGVtIGlzOiIgICAgICAgICAgICAgICAgICAgICAgICAgIA0KIFs1XSAiUFJPSkNSU1tcIldHUyA4NCAvIFVUTSB6b25lIDE4TlwiLCINCiANCiBbNl0gIiAgICANCg0KIyMjIyMjICoqQmFkIERhdGEqKg0KDQpTb21ldGltZXMgYSByYXN0ZXLigJlzIG1ldGFkYXRhIHdpbGwgdGVsbCB1cyB0aGUgcmFuZ2Ugb2YgZXhwZWN0ZWQgdmFsdWVzIGZvciBhIHJhc3Rlci4gVmFsdWVzIG91dHNpZGUgb2YgdGhpcyByYW5nZSBhcmUgc3VzcGVjdCBhbmQgd2UgbmVlZCB0byBjb25zaWRlciB0aGF0IHdoZW4gd2UgYW5hbHl6ZSB0aGUgZGF0YS4gU29tZXRpbWVzLCB3ZSBuZWVkIHRvIHVzZSBzb21lIGNvbW1vbiBzZW5zZSBhbmQgc2NpZW50aWZpYyBpbnNpZ2h0IGFzIHdlIGV4YW1pbmUgdGhlIGRhdGEgLSBqdXN0IGFzIHdlIHdvdWxkIGZvciBmaWVsZCBkYXRhIHRvIGlkZW50aWZ5IHF1ZXN0aW9uYWJsZSB2YWx1ZXMuDQoNClBsb3R0aW5nIGRhdGEgd2l0aCBhcHByb3ByaWF0ZSBoaWdobGlnaHRpbmcgY2FuIGhlbHAgcmV2ZWFsIHBhdHRlcm5zIGluIGJhZCB2YWx1ZXMgYW5kIG1heSBzdWdnZXN0IGEgc29sdXRpb24uIA0KDQoNCiMjIyMjIyAqKkhpc3RvZ3JhbXMqKg0KDQpXZSBjYW4gZXhwbG9yZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHZhbHVlcyBjb250YWluZWQgd2l0aGluIG91ciByYXN0ZXIgdXNpbmcgdGhlIGdlb21faGlzdG9ncmFtKCkgZnVuY3Rpb24gd2hpY2ggcHJvZHVjZXMgYSBoaXN0b2dyYW0uIEhpc3RvZ3JhbXMgYXJlIG9mdGVuIHVzZWZ1bCBpbiBpZGVudGlmeWluZyBvdXRsaWVycyBhbmQgYmFkIGRhdGEgdmFsdWVzIGluIG91ciByYXN0ZXIgZGF0YS4NCg0KYGBge3IgaGlzdH0NCg0KZ2dwbG90KCkgKw0KICAgIGdlb21faGlzdG9ncmFtKGRhdGEgPSBEU01fSEFSVl9kZiwgYWVzKEhBUlZfZHNtQ3JvcCksIGJpbnMgPSA0MCkNCg0KYGBgDQoNCk5vdGUgdGhhdCB0aGUgc2hhcGUgb2YgdGhpcyBoaXN0b2dyYW0gbG9va3Mgc2ltaWxhciB0byB0aGUgcHJldmlvdXMgb25lIHRoYXQgd2FzIGNyZWF0ZWQgdXNpbmcgdGhlIGRlZmF1bHQgb2YgMzAgYmlucy4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBlbGV2YXRpb24gdmFsdWVzIGZvciBvdXIgRGlnaXRhbCBTdXJmYWNlIE1vZGVsIChEU00pIGxvb2tzIHJlYXNvbmFibGUuIEl0IGlzIGxpa2VseSB0aGVyZSBhcmUgbm8gYmFkIGRhdGEgdmFsdWVzIGluIHRoaXMgcGFydGljdWxhciByYXN0ZXIuDQoNCmBgYHtyIGNoYWxsZW5nZX0NCnByYWN0aWNlIDwtIGRlc2NyaWJlKCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvSEFSVi9EU00vSEFSVl9EU01oaWxsLnRpZiIpDQpgYGANCg0KDQojIyMjICoqSUkuIFBsb3R0aW5nIFJhc3RlcnMqKiB7LnRhYnNldH0NCg0KDQojIyMjIyAqKlNldHRpbmcgQnJlYWtzKioNCg0KKipBOiBVc2luZyBEYXRhIEJyZWFrcyoqDQoNCkluIHRoZSBwcmV2aW91cyBlcGlzb2RlLCB3ZSB2aWV3ZWQgb3VyIGRhdGEgdXNpbmcgYSBjb250aW51b3VzIGNvbG9yIHJhbXAuIEZvciBjbGFyaXR5IGFuZCB2aXNpYmlsaXR5IG9mIHRoZSBwbG90LCB3ZSBtYXkgcHJlZmVyIHRvIHZpZXcgdGhlIGRhdGEg4oCcc3ltYm9saXplZOKAnSBvciBjb2xvcmVkIGFjY29yZGluZyB0byByYW5nZXMgb2YgdmFsdWVzLiBUaGlzIGlzIGNvbXBhcmFibGUgdG8gYSDigJxjbGFzc2lmaWVk4oCdIG1hcC4gDQoNCnwNCg0KVG8gZG8gdGhpcywgd2UgbmVlZCB0byB0ZWxsIGdncGxvdCBob3cgbWFueSBncm91cHMgdG8gYnJlYWsgb3VyIGRhdGEgaW50bywgYW5kIHdoZXJlIHRob3NlIGJyZWFrcyBzaG91bGQgYmUuIFRvIG1ha2UgdGhlc2UgZGVjaXNpb25zLCBpdCBpcyB1c2VmdWwgdG8gZmlyc3QgZXhwbG9yZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhIHVzaW5nIGEgYmFyIHBsb3QuIFRvIGJlZ2luIHdpdGgsIHdlIHdpbGwgdXNlIGRwbHly4oCZcyAqKm11dGF0ZSgpKiogZnVuY3Rpb24gY29tYmluZWQgd2l0aCAqKmN1dCgpKiogdG8gc3BsaXQgdGhlIGRhdGEgaW50byAzIGJpbnMuDQoNCmBgYHtyIGN1dCwgY2FjaGU9VH0NCg0KRFNNX0hBUlZfZGZfbXV0YXRlZCA8LSBEU01fSEFSVl9kZiAlPiUNCiAgbXV0YXRlKGZjdF9lbGV2YXRpb24gPSBjdXQoSEFSVl9kc21Dcm9wLCBicmVha3MgPSA2KSkNCg0KDQpnZ3Bsb3QoZGF0YSA9IERTTV9IQVJWX2RmX211dGF0ZWQsIGFlcyhmY3RfZWxldmF0aW9uLCBmaWxsPWZjdF9lbGV2YXRpb24pKSArDQogICAgZ2VvbV9iYXIoc2hvdy5sZWdlbmQgPSBGKQ0KDQpwbG90KERTTV9IQVJWX2RmX211dGF0ZWQkSEFSVl9kc21Dcm9wKQ0KDQoNCmBgYA0KDQp8DQp8DQoNCklmIHdlIHdhbnQgdG8ga25vdyB0aGUgY3V0b2ZmIHZhbHVlcyBmb3IgdGhlIGdyb3Vwcywgd2UgY2FuIGFzayBmb3IgdGhlIHVuaXF1ZSB2YWx1ZXMgb2YgZmN0X2VsZXZhdGlvbjoNCg0KYGBge3IgdW5pcXVlLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCnVuaXF1ZShEU01fSEFSVl9kZl9tdXRhdGVkJGZjdF9lbGV2YXRpb24pDQpgYGANCg0KfA0KfCANCg0KQW5kIHdlIGNhbiBnZXQgdGhlIGNvdW50IG9mIHZhbHVlcyBpbiBlYWNoIGdyb3VwIHVzaW5nIGRwbHly4oCZcyAqKmdyb3VwX2J5KCkqKiBhbmQgKipjb3VudCgpKiogZnVuY3Rpb25zOg0KDQpgYGB7ciBncm91cGJ5LCBjYWNoZT1UUlVFLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCkRTTV9IQVJWX2RmX211dGF0ZWQgJT4lDQogIGdyb3VwX2J5KGZjdF9lbGV2YXRpb24pICU+JQ0KICBjb3VudCgpDQoNCmBgYA0KDQoNCnwNCnwNCg0KIyMjIyMgKipNYWtlIGl0IGNvbG9yZnVsISoqDQoNClVzZSBncm91cHMgd2l0aCBkaWZmZXJlbnQgY29sb3JzIHRvIG1ha2UgY3V0ZSBtYXBzIQ0KDQpgYGB7ciBwcmV0dHkgcGxvdHMgb2YgeW91ciBjb2xvciBjaG9pY2UsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KZ2dwbG90KCkrDQogIGdlb21fcmFzdGVyKGRhdGEgPSBEU01fSEFSVl9kZl9tdXRhdGVkLCBhZXMoeCwgeSwgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSsNCiAgY29vcmRfcXVpY2ttYXAoKQ0KDQoNCmBgYA0KDQoNCg0KYGBge3IgbW9yZSBicmVha3MgYW5kIGNvbG9ycywgY2FjaGU9VFJVRX0NCg0KRFNNX0hBUlZfZGZfbXV0YXRlZCA8LSBEU01fSEFSVl9kZiAlPiUNCiAgICAgICAgICAgICAgICBtdXRhdGUoZmN0X2VsZXZhdGlvbiA9IGN1dChIQVJWX2RzbUNyb3AsIGJyZWFrcyA9IDEwKSkNCg0KDQpnZ3Bsb3QoZGF0YSA9IERTTV9IQVJWX2RmX211dGF0ZWQsIGFlcyhmY3RfZWxldmF0aW9uLCBmaWxsPWZjdF9lbGV2YXRpb24pKSArDQogICAgZ2VvbV9iYXIoc2hvdy5sZWdlbmQgPSBGKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX0hBUlZfZGZfbXV0YXRlZCwgYWVzKHgsIHksIGZpbGwgPSBmY3RfZWxldmF0aW9uKSkrDQogIGNvb3JkX3F1aWNrbWFwKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KDQpgYGANCg0KVGhlIHBsb3RzIGFib3ZlIHVzZSBnZ3Bsb3RzIGRlZmF1bHQgY29sb3JzIC0tIHdlIGNhbiB1c2Ugb3VyIG93biBjb2xvciBwYWxsZXRlcyANCg0KYGBge3IgY3VzdG9tIHBhbGV0dGUsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KbXlfY29sIDwtIHRlcnJhaW4uY29sb3JzKDEwKQ0KDQpnZ3Bsb3QoKSArDQogZ2VvbV9yYXN0ZXIoZGF0YSA9IERTTV9IQVJWX2RmX211dGF0ZWQgLCBhZXMoeCA9IHgsIHkgPSB5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gZmN0X2VsZXZhdGlvbikpICsgDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdGVycmFpbi5jb2xvcnMoMTApKSArIA0KICAgIGNvb3JkX3F1aWNrbWFwKCkNCg0KYGBgDQoNCk9yIHdlIGNhbiBhbHNvIHR1cm4gb2ZmIHRoZSBsYWJlbHMgb2YgYm90aCBheGVzIGJ5IHBhc3NpbmcgZWxlbWVudF9ibGFuaygpIHRvIHRoZSByZWxldmFudCBwYXJ0IG9mIHRoZSB0aGVtZSgpIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCg0KZ2dwbG90KCkgKw0KIGdlb21fcmFzdGVyKGRhdGEgPSBEU01fSEFSVl9kZl9tdXRhdGVkICwgYWVzKHggPSB4LCB5ID0geSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSArIA0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbCwgbmFtZSA9ICJFbGV2YXRpb24iKSArDQogICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQoNCiMjIyMjICoqQXhlcyBMYWJlbHMqKg0KDQoNCk9yIHdlIGNhbiBhbHNvIHR1cm4gb2ZmIHRoZSBsYWJlbHMgb2YgYm90aCBheGVzIGJ5IHBhc3NpbmcgKiplbGVtZW50X2JsYW5rKCkqKiB0byB0aGUgcmVsZXZhbnQgcGFydCBvZiB0aGUgKip0aGVtZSgpKiogZnVuY3Rpb24uDQoNCmBgYHtyIHR1cm4gb2ZmIGxhYmVsc30NCg0KZ2dwbG90KCkgKw0KIGdlb21fcmFzdGVyKGRhdGEgPSBEU01fSEFSVl9kZl9tdXRhdGVkICwgYWVzKHggPSB4LCB5ID0geSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSArIA0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbCwgbmFtZSA9ICJFbGV2YXRpb24iKSArDQogICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQp8DQp8DQoNCiMjIyMjICoqQ2hhbGxlbmdlKioNCg0KDQoqKkNoYWxsZW5nZSEgVXNpbmcgQ3VzdG9tIEJyZWFrcyoqDQoNCjEuIFNpeCBjbGFzc2lmaWVkIHJhbmdlcyBvZiB2YWx1ZXMgKGJyZWFrIHBvaW50cykgdGhhdCBhcmUgZXZlbmx5IGRpdmlkZWQgYW1vbmcgdGhlIHJhbmdlIG9mIHBpeGVsIHZhbHVlcy4NCg0KMi4gQXhpcyBsYWJlbHMuDQoNCjMuIEEgcGxvdCB0aXRsZS4NCg0KYGBge3IgY2hhbGxlbmdlIDF9DQpEU01fSEFSVl9kZl9tdXRhdGVkIDwtIERTTV9IQVJWX2RmICU+JQ0KICAgICAgICAgICAgICAgIG11dGF0ZShmY3RfZWxldmF0aW9uID0gY3V0KEhBUlZfZHNtQ3JvcCwgYnJlYWtzID0gNikpDQoNCm15X2NvbCA8LSB0ZXJyYWluLmNvbG9ycyg2KQ0KDQpnZ3Bsb3QoKSArIA0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX0hBUlZfZGZfbXV0YXRlZCwgYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSArIA0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbCwgbmFtZSA9ICJFbGV2YXRpb24iKSArDQogIHhsYWIoIlVUTSBFYXN0aW5nIENvb3JkaW5hdGUgKG0pIikrDQogIHlsYWIoIlVUTSBOb3J0aGluZyBDb29yZGluYXRlIChtKSIpKw0KICBnZ3RpdGxlKCJDbGFzc2lmaWVkIEVsZXZhdGlvbiBNYXAgLSBORU9OIEhhcnZhcmQgRm9yZXN0IEZpZWxkIFNpdGUiKSsNCiAgICB0aGVtZSgpICsgDQogICAgY29vcmRfcXVpY2ttYXAoKQ0KYGBgDQoNCg0KIyMjIyAqKklJSS4gTGF5ZXJpbmcgUmFzdGVycyoqIHsudGFic2V0fQ0KDQojIyMjIyAqKkFkZGluZyBIaWxsc2hhZGUqKg0KDQp8DQoNCldlIGNhbiBsYXllciBhIHJhc3RlciBvbiB0b3Agb2YgYSBoaWxsc2hhZGUgcmFzdGVyIGZvciB0aGUgc2FtZSBhcmVhLCBhbmQgdXNlIGEgdHJhbnNwYXJlbmN5IGZhY3RvciB0byBjcmVhdGUgYSAzLWRpbWVuc2lvbmFsIHNoYWRlZCBlZmZlY3QuIEEgaGlsbHNoYWRlIGlzIGEgcmFzdGVyIHRoYXQgbWFwcyB0aGUgc2hhZG93cyBhbmQgdGV4dHVyZSB0aGF0IHlvdSB3b3VsZCBzZWUgZnJvbSBhYm92ZSB3aGVuIHZpZXdpbmcgdGVycmFpbi4gV2Ugd2lsbCBhZGQgYSBjdXN0b20gY29sb3IsIG1ha2luZyB0aGUgcGxvdCBncmV5Lg0KfA0KYGBge3IgaGlsbHNoYWRlLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCkRTTV9oaWxsX0hBUlYgPC0gcmFzdCgnZGF0YS9ORU9OLURTLUFpcmJvcm5lLVJlbW90ZS1TZW5zaW5nL0hBUlYvRFNNL0hBUlZfRFNNaGlsbC50aWYnKQ0KDQoNCkRTTV9oaWxsX0hBUlYNCmBgYA0KDQojIyMjIyAqKkNvbnZlcnQgdG8gREYqKg0KDQpgYGB7ciwgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQ0KDQpEU01faGlsbF9IQVJWX2RmIDwtIGFzLmRhdGEuZnJhbWUoRFNNX2hpbGxfSEFSViwgeHkgPSBUUlVFKSANCg0Kc3RyKERTTV9oaWxsX0hBUlZfZGYpDQoNCmBgYA0KDQojIyMjIyAqKlBsb3QgSGlsbHNoYWRlKioNCg0KYGBge3IgcGxvdCBoaWxsc2hhZGUsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KZ2dwbG90KCkgKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX2hpbGxfSEFSVl9kZiwgDQogICAgICAgICAgICAgIGFlcyh4PXgsIHk9eSwgYWxwaGE9SEFSVl9EU01oaWxsKSkrDQogICAgICBzY2FsZV9hbHBoYShyYW5nZT1jKDAuMTUsIDAuNjUpLCANCiAgICAgICAgICAgICAgICAgIGd1aWRlID0gIm5vbmUiKSsNCiAgY29vcmRfcXVpY2ttYXAoKQ0KDQpgYGANCg0KIyMjIyMgKipMYXllcmluZyoqDQoNCmBgYHtyIGxheWVyaW5nLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhPURTTV9IQVJWX2RmLCANCiAgICAgICAgICAgICAgYWVzKHg9eCwgeT15LCANCiAgICAgICAgICAgICAgICAgIGZpbGw9IEhBUlZfZHNtQ3JvcCkpKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX2hpbGxfSEFSVl9kZiwgDQogICAgICAgICAgICAgIGFlcyh4PXgsIHk9eSwgDQogICAgICAgICAgICAgICAgICBhbHBoYSA9IEhBUlZfRFNNaGlsbCkpKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpKw0KICBzY2FsZV9hbHBoYShyYW5nZT1jKDAuMTUsIDAuNjUpLCBndWlkZT0ibm9uZSIpICsNCiAgZ2d0aXRsZSgiRWxldmF0aW9uIHdpdGggaGlsbHNoYWRlIikgKw0KICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQojIyMjIyAqKkNoYWxsZW5nZSoqDQoNClVzZSB0aGUgZmlsZXMgaW4gdGhlIGRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9TSkVSLyBkaXJlY3RvcnkgdG8gY3JlYXRlIEJPVEggYSAqKkRpZ2l0YWwgVGVycmFpbiBNb2RlbCoqIG1hcCBhbmQgKipEaWdpdGFsIFN1cmZhY2UgTW9kZWwqKiBtYXAgb2YgdGhlIFNhbiBKb2FxdWluIEV4cGVyaW1lbnRhbCBSYW5nZSBmaWVsZCBzaXRlLg0KDQpNYWtlIHN1cmUgdG86DQoNCiogaW5jbHVkZSBoaWxsc2hhZGUgaW4gdGhlIG1hcHMNCg0KKiBsYWJlbCBheGVzIG9uIHRoZSBEU00gbWFwIGFuZCBleGNsdWRlIHRoZW0gZnJvbSB0aGUgRFRNIG1hcA0KDQoqIGluY2x1ZGUgYSB0aXRsZSBmb3IgZWFjaCBtYXANCg0KKiBleHBlcmltZW50IHdpdGggdmFyaW91cyBhbHBoYSB2YWx1ZXMgYW5kIGNvbG9yIHBhbGV0dGVzIHRvIHJlcHJlc2VudCB0aGUgZGF0YQ0KDQojIyMjIyMgRFNNDQoNCmBgYHtyIGNoYWxsZW5nZSAyLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCiNEU00gRGF0YQ0KDQpEU01fU0pFUiA8LSByYXN0KCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvU0pFUi9EU00vU0pFUl9kc21Dcm9wLnRpZiIpDQoNClJfZGYgPC0gYXMuZGF0YS5mcmFtZShEU01fU0pFUiwgeHk9VCkNCg0KIyBEU00gSGlsbHNoYWRlDQoNCkRTTV9oaWxsX1NKRVIgPC0gDQogIHJhc3QoImRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9TSkVSL0RTTS9TSkVSX2RzbUhpbGwudGlmIikNCg0KRFNNX2hpbGxfU0pFUl9kZiA8LSBhcy5kYXRhLmZyYW1lKERTTV9oaWxsX1NKRVIsIHh5PVQpDQoNCiNwbG90DQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhPVJfZGYsIGFlcyh4LCB5LCBmaWxsPVNKRVJfZHNtQ3JvcCwgYWxwaGE9MC44KSkrDQogIGdlb21fcmFzdGVyKGRhdGE9RFNNX2hpbGxfU0pFUl9kZiwgYWVzKHgsIHksIGFscGhhID0gU0pFUl9kc21IaWxsKSkrDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkrDQogIGd1aWRlcyhmaWxsPWd1aWRlX2NvbG9yYmFyKCkpKw0KICBzY2FsZV9hbHBoYShyYW5nZT1jKDAuNCwgMC43KSwgZ3VpZGUgPSAibm9uZSIpKw0KICB0aGVtZV9idygpKw0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSsNCiAgeGxhYigiVVRNIEVhc3RpbmcgQ29vcmRpbmF0ZSAobSkiKSsNCiAgeWxhYigiVVRNIE5vcnRoaW5nIENvb3JkaW5hdGUgKG0pIikrDQogIGdndGl0bGUoIkRTTSB3aXRoIEhpbGxzaGFkZSIpDQogIGNvb3JkX3F1aWNrbWFwKCkNCg0KYGBgDQoNCiMjIyMjIyBEVE0NCg0KYGBge3IgRFRNfQ0KDQpEVE1fU0pFUiA8LSByYXN0KCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvU0pFUi9EVE0vU0pFUl9kdG1Dcm9wLnRpZiIpDQoNCkRUTV9oaWxsX1NKRVIgPC0gcmFzdCgiZGF0YS9ORU9OLURTLUFpcmJvcm5lLVJlbW90ZS1TZW5zaW5nL1NKRVIvRFRNL1NKRVJfZHRtSGlsbC50aWYiKQ0KDQojZGF0YWZyYW1lIGVtDQoNCkRUTV9oaWxsX1NKRVJfZGYgPC0gYXMuZGF0YS5mcmFtZShEVE1faGlsbF9TSkVSLCB4eT1UKQ0KDQpEVE1fU0pFUl9kZiA8LSBhcy5kYXRhLmZyYW1lKERUTV9TSkVSLCB4eT1UKQ0KDQojZ2dwbG90IGl0DQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhPURUTV9TSkVSX2RmLCBhZXMoeCwgeSwgZmlsbD1TSkVSX2R0bUNyb3AsIGFscGhhPTIuMCkpKw0KICBnZW9tX3Jhc3RlcihkYXRhPURUTV9oaWxsX1NKRVJfZGYsIGFlcyh4LCB5LCBhbHBoYT1TSkVSX2R0bUhpbGwpKSsNCiAgeGxhYigiIikrDQogIHlsYWIoIiIpKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpKw0KICBndWlkZXMoZmlsbD1ndWlkZV9jb2xvcmJhcigpKSsNCiAgc2NhbGVfYWxwaGEocmFuZ2U9YygwLjQsIDAuNyksIGd1aWRlID0gIm5vbmUiKSsNCiAgdGhlbWVfYncoKSsNCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpKSsNCiAgZ2d0aXRsZSgiRFRNIHdpdGggSGlsbHNoYWRlIikrDQogIGNvb3JkX3F1aWNrbWFwKCkNCg0KYGBgDQoNCiMjIyMjICoqS2V5IFBvaW50cyoqDQoNCjEuIENvbnRpbnVvdXMgZGF0YSByYW5nZXMgY2FuIGJlIGdyb3VwZWQgaW50byBjYXRlZ29yaWVzIHVzaW5nICoqbXV0YXRlKCkqKiBhbmQgKipjdXQoKSoqLg0KDQoyLiBVc2UgYnVpbHQtaW4gdGVycmFpbi5jb2xvcnMoKSBvciBzZXQgeW91ciBwcmVmZXJyZWQgY29sb3Igc2NoZW1lIG1hbnVhbGx5Lg0KDQozLiBMYXllciByYXN0ZXJzIG9uIHRvcCBvZiBvbmUgYW5vdGhlciBieSB1c2luZyB0aGUgYWxwaGEgYWVzdGhldGljLg0KDQo=