Introduction

xtractomatic is an R package developed to subset and extract satellite and other oceanographic related data from a remote server. The program can extract data for a moving point in time along a user-supplied set of longitude, latitude and time points; in a 3D bounding box; or within a polygon (through time). The xtractomatic functions were originally developed for the marine biology tagging community, to match up environmental data available from satellites (sea-surface temperature, sea-surface chlorophyll, sea-surface height, sea-surface salinity, vector winds) to track data from various tagged animals or shiptracks (xtracto). The package has since been extended to include the routines that extract data a 3D bounding box (xtracto_3D) or within a polygon (xtractogon). The xtractomatic package accesses data that are served through the ERDDAP server at the NOAA/SWFSC Environmental Research Division in Santa Cruz, California. In order that the user not be overwhelmed when searching for datasets, only a selected number of the datasets on that ERDDAP server, those viewed as most useful, are supported. The ERDDAP server can also be directly accessed at http://coastwatch.pfeg.noaa.gov/erddap. ERDDAP is a simple to use yet powerful web data service developed by Bob Simons.

This is the third version of xtractomatic. The original version was created by Dave Foley for Matlab and was modified for R by Cindy Bessey and Dave Foley. That version used a precurser to ERDDAP called BobDAP. BobDAP was less flexible and provided access to fewer datasets than does ERDDAP. The second version of xtractomatic was written by Roy Mendelssohn to use ERDDAP rather than BobDAP. This version, also written by Roy Mendelssohn, has many improvements including access to many more datasets, improved speed, better error checking, crude search capabilites, and plotting routines, and is the first version as a true R package.

IMPORTANT - If you have used xtractomatic before

The code has undergone a major refactoring, in particular there is no longer separate code to handle bathymetry and other datasets without time, as well as the addition of some easy to use plot routines. In order to have a cleaner design to go with these changes, the order of the arguments to the functions has changed, “tpos” no longer has to be passed for a dataset without time, and “xlen” and “ylen” have default values (see next section). Also in xtracto() the return now includes the parameter name, so instead of one element being just “mean” it will be “mean sst” for example. The changes in the function arguments makes them closer to that in rerddapXtracto, see next section.

IMPORTANT - Likely last version of xtractomatic

This will likely be the last version of xtractomatic, except for bug fixes or changes needed to stay compatible with new versions of R, or changes in ERDDAP and the applications it uses. Development effort will be put into improving the rerddapXtracto package, with the intent that users will eventually migrate to rerddapXtracto. As mentioned above, the changes in the function arguments will help to simplify this migration.

The Main xtractomatic functions

There are three main data extraction functions in the xtractomatic package:

  • xtracto <- function(dtype, xpos, ypos, tpos = NA, xlen = 0., ylen = 0., verbose=FALSE)

  • xtracto_3D <- function(dtype, xpos, ypos, tpos = NA, verbose=FALSE)

  • xtractogon <- function(dtype, xpos, ypos, tpos = NA, verbose = FALSE)

There are two information functions in the xtractomatic package:

  • `searchData <- function(searchList= “varname:chl”)

  • getInfo <- function(dtype)

There are now two plotting functions that make use of the R package plotdap:

  • plotTrack <- function(resp, xcoord, ycoord, plotColor = 'viridis', name = NA, myFunc = NA, shape = 20, size = .5)

  • plotBBox <- function(resp, plotColor = 'viridis', time = NA, animate = FALSE, name = NA, myFunc = NA, maxpixels = 10000)

The dtype parameter in the data extraction routines specifies a combination of which dataset on the ERDDAP server to access, and as well as which parameter from that dataset. The first sections demonstrate how to use these functions in realistic settings. The Selecting a dataset section shows how to find the dtype or other parameters of interest from the available datasets.

Setting up

xtractomatic uses the httr, ncdf4 and sp packages, and these packages must be installed first or xtractomatic will fail to install.

install.packages("httr", dependencies = TRUE)
install.packages("ncdf4",dependencies = TRUE) 
install.packages("sp", dependencies = TRUE)

This development version of xtractomatic is available from https://github.com/rmendels/xtractomatic and can be installed from Github,

install.packages("devtools")
devtools::install_github("rmendels/xtractomatic", ref = "development")

In order to use the plot routines, you must have the package plotdap installed, as well as all of its dependencies, most importantly rerddap and sf (but note these are only needed if you are using the plotting routines. The latter two can be installed from CRAN:

install.packages("rerddap", dependencies = TRUE)
install.packages("sf", dependencies = TRUE)

The plotdap package can be installed from Github:

install.packages("devtools")
devtools::install_github('ropensci/plotdap', dependencies = TRUE)

Once installed, to use xtractomatic do

library("xtractomatic")

If the other libraries (httr, ncdf4, and sp) have been installed they will be found and do not need to be explicitly loaded.

Using the R code examples

Besides the xtractomatic packages, the examples below depend on DT, ggplot2, lubridate, mapdata, and reshape2. These can be loaded beforehand (assuming they have been installed):

library("DT")
library("ggplot2")
library("lubridate")
library("mapdata")
library("reshape2")

In order that the code snippets can be more stand-alone, the needed libraries are always required in the examples.

It should be emphasized that these other packages are used to manipulate and plot the data in R, other packages could be used as well. The use of xtractomatic does not depend on these other packages.

There are also several R functions defined within the document that are used in other code examples. These include chlaAvg, upwell, and plotUpwell.

Getting Started

The plotting functions are new, and there are some fine points that need to be understood if they are to be used properly, in particular plotBBox(). Both plotTrack() and plotBBox() rearrange the output so that the plotdap functions add_tabledap() and add_griddap() think that the output is from rerddap, and then make the appropriate plotdap call. When the data that is passed to add_griddap() has mutiple time periods, there are two options. The first option is to set the parameter “time” to a function that reduces the data to one dimension in the time coordinate (such as the mean), or else to set “time” equal to “identity” and set “animate” to be “TRUE” which will produce a time animation of the results. The function plotBBox() works the same way, except that the default function is mean(na.rm = TRUE). The following examples show how to use different features of the plotting functions:

An xtracto example

In this section we extract data along a trackline found in the Marlintag38606 dataset, which is the track of a tagged marlin in the Pacific Ocean (courtesy of Dr. Mike Musyl of the Pelagic Research Group LLC), and show some simple plots of the extracted data.

The Marlintag38606 dataset is loaded when you load the xtractomatic library. The structure of this dataframe is shown by:

require(xtractomatic)
str(Marlintag38606)
#> 'data.frame':    152 obs. of  7 variables:
#>  $ date  : Date, format: "2003-04-23" "2003-04-24" ...
#>  $ lon   : num  204 204 204 204 204 ...
#>  $ lat   : num  19.7 19.8 20.4 20.3 20.3 ...
#>  $ lowLon: num  204 204 204 204 204 ...
#>  $ higLon: num  204 204 204 204 204 ...
#>  $ lowLat: num  19.7 18.8 18.8 18.9 18.9 ...
#>  $ higLat: num  19.7 20.9 21.9 21.7 21.7 ...

The Marlintag38606 dataset consists of three variables, the date, longitude and latitude of the tagged fish, which are used as the xpos, ypos and tpos arguments in xtracto. The parameters xlen and ylen specify the spatial “radius” (actually a box, not a circle) around the point to search in for existing data. These can also be input as a time-dependent vector of values, but here a set value of 0.2 degrees is used. The example extracts SeaWiFS chlorophyll data, using a dataset that is an 8 day composite. This dataset is specified by the dtype of "swchla8day" in the xtracto function. In a later section it is explained how to access different satellite datasets other than the one shown here.

require(xtractomatic)

# First we will copy the Marlintag38606 data into a variable 
# called tagData  so that subsequent code will be more generic.  

tagData <- Marlintag38606
xpos <- tagData$lon
ypos <- tagData$lat
tpos <- tagData$date
swchl <- xtracto("swchla8day", xpos, ypos, tpos = tpos, , xlen = .2, ylen = .2)

This command can take a while to run depending on your internet speed and how busy is the server. With a fairly decent internet connection it took ~25 seconds. The default is to run in quiet mode, if you would prefer to see output, as confirmation that the script is running, use verbose=TRUE

When the extaction has completed the data.frame swchl will contain as many columns as data points in the input file (in this case 152) and will have 11 columns:

str(swchl)
#> 'data.frame':    152 obs. of  11 variables:
#>  $ mean chlorophyll  : num  0.073 NaN 0.074 0.0653 0.0403 ...
#>  $ stdev chlorophyll : num  NA NA 0.00709 0.00768 0.02278 ...
#>  $ n                 : int  1 0 16 4 7 9 4 3 0 6 ...
#>  $ satellite date    : chr  "2003-04-19T00:00:00Z" "2003-04-27T00:00:00Z" "2003-04-27T00:00:00Z" "2003-04-27T00:00:00Z" ...
#>  $ requested lon min : num  204 204 204 204 204 ...
#>  $ requested lon max : num  204 204 204 204 204 ...
#>  $ requested lat min : num  19.6 19.7 20.3 20.2 20.2 ...
#>  $ requested lat max : num  19.8 19.9 20.5 20.4 20.4 ...
#>  $ requested date    : num  12165 12166 12172 12173 12174 ...
#>  $ median chlorophyll: num  0.073 NA 0.073 0.0645 0.031 ...
#>  $ mad chlorophyll   : num  0 NA 0.00297 0.00741 0.0089 ...

Plotting the results

We plot the track line with the locations colored according to the mean of the satellite chlorophyll around that point. Positions where there was a tag location but no chlorophyll values are also shown.

require(plotdap)
#> Loading required package: plotdap
#> Loading required package: rerddap
require(rerddap)
plotTrack(swchl, xpos, ypos, plotColor = 'chlorophyll')
#> Loading required package: maps

Topography data

In addition to satellite data, xtractomatic can access topographic data. As an example we extract and plot the water depth (bathymetry) associated with the trackline.

Get the topography data along the track:

require("xtractomatic")
ylim <- c(15, 30)
xlim <- c(-160, -105)
topo <- xtracto("ETOPO180", tagData$lon, tagData$lat, xlen = .1, ylen = .1)

and plot the track:

require(plotdap)
require(rerddap)
topoPlot <- plotTrack(topo, xpos, ypos,  plotColor = 'density', name = 'Depth', size = .1)
topoPlot

Reading in tagged data

The above example uses data already converted to R format, but clearly the utility of the xtracto function lies in using it with one’s own dataset. This dataset was read in from a text file called Marlin-tag38606.txt, which is also made available when the xtractomatic package is loaded. To be able to refer to this file:


system.file("extdata", "Marlin-tag38606.txt", package = "xtractomatic")

The first 5 lines of the file can be viewed:

datafile <- system.file("extdata", "Marlin-tag38606.txt", package = "xtractomatic")
system(paste("head -n5 ", datafile))

and read in as follows:


Marlingtag38606 <- read.csv(datafile, head = TRUE, stringsAsFactors = FALSE, sep = "\t")
str(Marlingtag38606)
#> 'data.frame':    152 obs. of  7 variables:
#>  $ date  : chr  "4/23/2003" "4/24/2003" "4/30/2003" "5/1/2003" ...
#>  $ lon   : num  204 204 204 204 204 ...
#>  $ lat   : num  19.7 19.8 20.4 20.3 20.3 ...
#>  $ lowLon: num  204 204 204 204 204 ...
#>  $ higLon: num  204 204 204 204 204 ...
#>  $ lowLat: num  19.7 18.8 18.8 18.9 18.9 ...
#>  $ higLat: num  19.7 20.9 21.9 21.7 21.7 ...

The file Marlingtag38606.txt is a “tab” separated file, not a comma seperated file, so the sep option in the read.csv function above indicates that to the function, and stringsAsFactors = FALSE tells the function not to treat the dates as factors.

To be useful the date field, now formatted for example as 4/23/2003, needs to be converted to an R date field:

Marlintag38606$date <- as.Date(Marlintag38606$date, format='%m/%d/%Y')

The format of the date has to be given because the dates in the file are not in one of the formats recognized by as.Date. A small y (%y) indicates the year has 2 digits (97) while a capital Y (%Y) indicates the year has 4 digits (1997). If your file is in the EU format with commas as decimal points and semicolons as field separators then use read.csv2 rather than read.csv.

This dataset includes the range of the 95% confidence interval for both the latitude and the longitude, which are the last four columns of data. In the example above the search “radius” was read into xtracto was a constant value of 0.2 degrees, but a vector can also be read in. To use the limits given in the file, create the vectors as follows:

xrad <- abs(Marlintag38606$higLon - Marlintag38606$lowLon)
yrad <- abs(Marlintag38606$higLat - Marlintag38606$lowLat)

and then use these for the xlen and ylen inputs in xtracto:

xpos <- Marlintag38606$lon 
ypos <- Marlintag38606$lat 
tpos <- Marlintag38606$date
swchl <- xtracto(xpos, ypos, tpos, "swchla8day", xrad, yrad)

Using xtracto_3D

Comparing chlorophyll estimates from different satellites

This example extracts a time-series of monthly satellite chlorophyll data for the period 1998-2014 from three different satellites ( SeaWIFS, MODIS and VIIRS ), using the xtracto_3D function. The extract is for an area off the coast of California. xtracto_3D extracts data in a 3D bounding box where xpos has the minimum and maximum longitude, ypos has the minimum and maximum latitude, and tpos has the starting and ending time, given as “YYY-MM-DD”, describing the bounding box.

First we define the longitude, latitude and temporal boundaries of the box:

xpos <- c(235, 240)
ypos <- c(36, 39)
tpos <- c('1998-01-01', '2014-11-30') 

The extract will contain data at all of the longitudes, latitudes and times in the requested dataset that are within the given bounds.

If we run the xtracto_3D using the full temporal contraints defined above for SeaWiFS we get an error:

require(xtractomatic)
chlSeaWiFS <- xtracto_3D('swchlamday', xpos, ypos, tpos = tpos )
#> [1] "tpos  (time) has elements out of range of the dataset"
#> [1] "time range in tpos"
#> [1] "1998-01-01,2014-11-30"
#> [1] "time range in ERDDAP data"
#> [1] "1997-09-16,2010-12-16T12:00:00Z"
#> Error in xtracto_3D("swchlamday", xpos, ypos, tpos = tpos): Coordinates out of dataset bounds - see messages above

The error occurs because the time-range specified is outside of the time-bounds of the requested dataset ( SeaWIFS data stopped in 2010). The error message generated by xtracto_3D explains this, and gives the temporal boundaries of the dataset. A simple way to extract to the end of a dataset is to use “last” (see Taking advantage of “last times”):

require(xtractomatic)
tpos <- c("1998-01-16", "last")
SeaWiFS <- xtracto_3D('swchlamday', xpos, ypos, tpos = tpos)

A similar extract can be made for the MODIS dataset. We know that the time-range of the MODIS dataset is also smaller than that defined by tpos. We can determine the exact bounds of the MODIS dataset by using the getInfo function:

require(xtractomatic)
getInfo('mhchlamday')
#> List of 19
#>  $ dtypename       : chr "mhchlamday"
#>  $ datasetname     : chr "erdMH1chlamday"
#>  $ longname        : chr "Chlorophyll-a, Aqua MODIS, NPP, L3SMI, Global, Science Quality (Monthly Composite)"
#>  $ varname         : chr "chlorophyll"
#>  $ hasAlt          : logi FALSE
#>  $ latSouth        : logi FALSE
#>  $ lon360          : logi FALSE
#>  $ minLongitude    : num -180
#>  $ maxLongitude    : num 180
#>  $ longitudeSpacing: num 0.0417
#>  $ minLatitude     : num -90
#>  $ maxLatitude     : num 90
#>  $ latitudeSpacing : num -0.0417
#>  $ minAltitude     : num NA
#>  $ maxAltitude     : num NA
#>  $ minTime         : chr "2003-01-16"
#>  $ maxTime         : chr "2017-08-16"
#>  $ timeSpacing     : num NA
#>  $ infoURL         : chr "http://oceancolor.gsfc.nasa.gov/"

which gives us the min and max time of the dataset, which we can be used in the call (or use “last” as above):

require(xtractomatic)
tpos <- c('2003-01-16', "last")
MODIS <- xtracto_3D('mhchlamday', xpos, ypos, tpos = tpos)

Similarly we can extract data from the VIIRS dataset.

require(xtractomatic)
tpos <- c("2012-01-15", "last")
VIIRS <- xtracto_3D('erdVH3chlamday', xpos, ypos, tpos = tpos)

xtracto_3d returns a list of the form:

  • extract$data : num [longitude, latitude, time]
  • extract$varname : character string with the name of the variable
  • extract$datasetname: character string of the ERDDAP dataset ID
  • extract$latitude : num [latitude] latitudes of extract
  • extract$longitude : num [longitude] longitude of extact
  • extract$time : chr [time] times of extract
  • extract$altitude : num altitude of extract

in this case the varname is chla, the ERDDAP datasetID is erdVH3chlamday, and data contains the data on the grid defined by longitude, latitude and time. The data from the different satellites can be compared by mapping the values for a given time period, which we cna do using the function plotBBox().

We examine chlorophyll in June 2012 a period when VIIRS and MODIS both had data. Starting with VIIRS:

require("lubridate")
#> Loading required package: lubridate
#> 
#> Attaching package: 'lubridate'
#> The following object is masked from 'package:base':
#> 
#>     date
require("plotdap")
tempChla <- VIIRS
tempChla$data <- VIIRS$data[, , month(VIIRS$time) == 6 & year(VIIRS$time) == 2012]
tempChla$time <- VIIRS$time[month(VIIRS$time) == 6 & year(VIIRS$time) == 2012]
plotBBox(tempChla, plotColor = 'chlorophyll', maxpixels = 30000)

Chlorophyll values are highly skewed, with low values most places and times, and then for some locations very high values. For this reason log(chlorophyll) is usually plotted.

require("plotdap")
myFunc <- function(x) log(x)
plotBBox(tempChla, plotColor = 'chlorophyll', myFunc = myFunc, name = 'log(chla)', maxpixels = 30000)

And also for MODIS:

require("lubridate")
require("plotdap")
tempChla <- MODIS
tempChla$data <- MODIS$data[, , month(MODIS$time) == 6 & year(MODIS$time) == 2012]
tempChla$time <- MODIS$time[month(MODIS$time) == 6 & year(MODIS$time) == 2012]
myFunc <- function(x) log(x)
plotBBox(tempChla, plotColor = 'chlorophyll', myFunc = myFunc, maxpixels = 30000)

SeaWiFS stopped functioning at the end of 2010, so we look at June, 2010:

require("lubridate")
require("plotdap")
tempChla <- SeaWiFS
tempChla$data <- SeaWiFS$data[, , month(SeaWiFS$time) == 6 & year(SeaWiFS$time) == 2010]
tempChla$time <- SeaWiFS$time[month(SeaWiFS$time) == 6 & year(SeaWiFS$time) == 2010]
myFunc <- function(x) log(x)
plotBBox(tempChla, plotColor = 'chlorophyll', myFunc = myFunc, name = 'log(chla)', maxpixels = 30000)

We can also look at changes in log(chlorophyll) over time for a given region. We examine a higly productive region just north of San Francisco, with limits (38N, 38.5N) and (123.5W, 123W).

require(ggplot2)
#> Loading required package: ggplot2
require(mapdata)
xlim1 <- c(-123.5, -123) + 360
ylim1 <- c(38, 38.5)
w <- map_data("worldHires", ylim = c(37.5, 39), xlim = c(-123.5, -122.))
z <- ggplot() + geom_polygon(data = w, aes(x = long, y = lat, group = group), fill = "grey80") +
   theme_bw() +
   coord_fixed(1.3, xlim = c(-123.5, -122.), ylim = c(37.5, 39))
z + geom_rect(aes(xmin = -123.5, xmax = -123., ymin = 38., ymax = 38.5), colour = "black", alpha = 0.) +
  theme_bw() + ggtitle("Location of chla averaging")

We define a helper function chlaAvg which finds the latitudes and longitudes in the datasets that are closest to the given limits, extracts the subset that is within that region, and then averages over the region to create a time series.

chlaAvg <- function(longitude, latitude, chlaData, xlim, ylim, sTimes){
  xIndex <- xlim
  yIndex <- ylim
  yIndex[1] <- which.min(abs(latitude - ylim[1]))
  yIndex[2] <- which.min(abs(latitude - ylim[2]))
  xIndex[1] <- which.min(abs(longitude - xlim[1]))
  xIndex[2] <- which.min(abs(longitude - xlim[2]))
  tempData <- chlaData[xIndex[1]:xIndex[2], yIndex[1]:yIndex[2],]
  chlaAvg <- apply(tempData, 3, mean, na.rm = TRUE)
  chlaAvg <- data.frame(chla = chlaAvg, time = sTimes)
return(chlaAvg)
}

We use chlaAvg to extract the three time series over the region, combine them and plot the result.

xlim1 <- c(-123.5, -123) + 360
ylim1 <- c(38, 38.5)
# Get both the average, and the average of log transformed chl for each point in the time series 
SeaWiFSavg <- chlaAvg(SeaWiFS$longitude, SeaWiFS$latitude, SeaWiFS$data, xlim1, ylim1,SeaWiFS$time)
SeaWiFSlog <- chlaAvg(SeaWiFS$longitude, SeaWiFS$latitude, log(SeaWiFS$data), xlim1, ylim1, SeaWiFS$time)
# Run the same steps again for the MODIS and VIIRS datasets
MODISavg <- chlaAvg(MODIS$longitude, MODIS$latitude, MODIS$data, xlim1, ylim1, MODIS$time)
MODISlog <- chlaAvg(MODIS$longitude, MODIS$latitude, log(MODIS$data), xlim1, ylim1, MODIS$time)
# run the same for VIIRS
VIIRSavg <- chlaAvg(VIIRS$longitude, VIIRS$latitude, VIIRS$data, xlim1, ylim1, VIIRS$time)
VIIRSlog <- chlaAvg(VIIRS$longitude, VIIRS$latitude, log(VIIRS$data), xlim1, ylim1, VIIRS$time)

First the raw chla values:

require(ggplot2)
Chla <- rbind(VIIRSavg, MODISavg, SeaWiFSavg)
Chla$sat <- c(rep("VIIRS", nrow(VIIRSavg)), rep("MODIS", nrow(MODISavg)), rep("SeaWIF", nrow(SeaWiFSavg)))
Chla$sat <- as.factor(Chla$sat)
ggplot(data = Chla, aes(time, chla, colour = sat)) + geom_line(na.rm = TRUE) + theme_bw()

and then log(chla):

require(ggplot2)
logChla <- rbind(VIIRSlog, MODISlog, SeaWiFSlog)
logChla$sat <- c(rep("VIIRS", nrow(VIIRSlog)), rep("MODIS", nrow(MODISlog)), rep("SeaWIF",    nrow(SeaWiFSlog)))
logChla$sat <- as.factor(Chla$sat)
ggplot(data = logChla, aes(time, chla, colour = sat)) + geom_line(na.rm = TRUE)  + theme_bw()

Upwelling

ERD for many years has produced 6-hourly Bakun upwelling indices at 15 set locations. But suppose you want the values at a different location? To do this we define a function upwell that rotates Ekman transport values to obtain the offshore component (upwelling index), and also define a function plotUpwell to plot upwelling. xtracto_3D can download Ekman transport calculated from Fleet Numerical Meteorlogical and Oceanographic Center (FNMOC) pressure fields:

upwell <- function(ektrx, ektry, coast_angle){
   pi <- 3.1415927
   degtorad <- pi/180.
   alpha <- (360 - coast_angle) * degtorad
   s1 <- cos(alpha)
   t1 <- sin(alpha)
   s2 <- -1 * t1
   t2 <- s1
   perp <- s1 * ektrx + t1 * ektry
   para <- s2 * ektrx + t2 * ektry
return(perp/10)
}
plotUpwell <- function(upwelling, upDates){
require(ggplot2)
temp <- data.frame(upwelling = upwelling, time = upDates)
ggplot(temp, aes(time, upwelling)) + geom_line(na.rm = TRUE) + theme_bw()
}

We calculate 6-hourly upwelling at (37,-122) for the year 2005 and use the coast angle that ERD uses for (36N , 122W) which is 152 degrees.

require(xtractomatic)
xpos <- c(238, 238)
ypos <- c(37, 37)
tpos <- c("2005-01-01", "2005-12-31")
ektrx <- xtracto_3D("erdlasFnTran6ektrx", xpos, ypos, tpos = tpos)
ektry <- xtracto_3D("erdlasFnTran6ektry", xpos, ypos, tpos = tpos)
upwelling <- upwell(ektrx$data, ektry$data, 152)

A plot of the result:

plotUpwell(upwelling, as.Date(ektrx$time))

The one very low downwelling value distorts the plot, so values < -500 are set to NA and the data are replotted:

tempUpw <- upwelling
tempUpw[tempUpw < -500] <- NA
plotUpwell(tempUpw, as.Date(ektrx$time))

In 2005 there was a large die-off of sea animals and many felt that is was due to an anomalously delayed upwelling. Let’s compare upwelling in 2005 at that location with upwelling in 1977:

require(xtractomatic)
tpos <- c("1977-01-01", "1977-12-31")
ektrx77 <- xtracto_3D("erdlasFnTran6ektrx", xpos, ypos, tpos = tpos)
ektry77 <- xtracto_3D("erdlasFnTran6ektry", xpos, ypos, tpos = tpos)
upwelling77 <- upwell(ektrx77$data, ektry77$data, 152)
# remove the one big storm at the end
tempUpw <- upwelling77
tempUpw[tempUpw < -500] <- NA
plotUpwell(tempUpw, as.Date(ektrx77$time))

While we have not looked at all areas along the coast, at least for this location it is difficult to see upwelling in 2005 as being any more delayed than in 1977 when there was not the same affect on animals.

Comparisons of Wind Stress

A problem faced when using satellite based estimates of wind stress is that the QuikSCAT based estimates start in 1999-07-21 and end in 2009-11-19, while the ASCAT based estimates start in 2009-10-03 and are ongoing. A comparison of the data over the period of overlap would be of interest.

We examine north-south wind stress (tauy) over the period of overlap at (36N, 121W) (we go a little more offshore because the satellite data is not resolved close to the coast), and compare 3-day composites of tauy. In this region tauy is the main component of upwelling as the coastline runs almost north-south and the winds are mostly from the north or north-west.

require(xtractomatic)
xpos <- c(237, 237)
ypos <- c(36, 36)
tpos <- c("2009-10-04", "2009-11-19")
ascat <- xtracto_3D("erdQAstress3daytauy", xpos, ypos, tpos = tpos)
quikscat <- xtracto_3D("erdQSstress3daytauy", xpos, ypos, tpos = tpos)

Plotting the result

require(ggplot2)
ascatStress <- data.frame(stress = ascat$data, time = as.Date(ascat$time))
quikscatStress <- data.frame(stress = quikscat$data, time = as.Date(quikscat$time))
stressCompare <- rbind(ascatStress,quikscatStress)
stressCompare$sat <- c(rep("ascat", nrow(ascatStress)), rep("quikscat", nrow(quikscatStress)))
stressCompare$sat <- as.factor(stressCompare$sat)
ggplot(data = stressCompare, aes(time, stress, colour = sat)) + geom_line(na.rm = TRUE) + theme_bw()

While the two wind stress series are close, there is a significant difference in the negative (southward) value on October 28, that is there will be a signifcant difference in the estimated (positive) upwelling value. Combining the series into a longer series would be desirable, but this suggests that there could be problems with inter-year comparisons due to the different sources of the data.

Taking advantage of “last times”

The ERDDAP server understands a time of “last” as being the last time period available for that dataset, and permits arithmetic using “last”, that is “last-5” is a valid time and will be 5 time periods before the last time (note that since different datasets have different time periods this difference is dataset dependent). For example to extract the last six months of SST from POES AVHRR we look at the monthly agsstmday dataset:

require(xtractomatic)
xpos <- c(235, 240)
ypos <- c(36, 39)
tpos <- c("last-5", "last")
poes <- xtracto_3D("agsstamday", xpos, ypos, tpos = tpos)

The results can be animated using plotBBox():

require(plotdap)
plotBBox(poes, plotColor = 'temperature',  time = identity, animate = TRUE )
#> Warning: Ignoring unknown aesthetics: frame
#> Executing: 
#> convert -loop 0 -delay 100 Rplot1.png Rplot2.png Rplot3.png
#>     Rplot4.png Rplot5.png Rplot6.png 'ani.gif'
#> Output at: ani.gif

Extracting Multiple, non-consecutive time periods

What if we want to extract data from non-consecutive time periods, for example the last six Decembers for an area? SST was unusually warm off of Catalina Island in Dec 2014, and we show this by comparing the same day in December for six years, 2009-2014:


require(reshape2)
#> Loading required package: reshape2

allyears <- NULL
xpos <- c(238, 243)
ypos <- c(30, 35)
for (year in 2009:2014) {
  tpos <- rep(paste(year, '-12-20', sep = ""), 2)
  tmp <- xtracto_3D('jplMURSST41SST', xpos, ypos, tpos = tpos)
#ggplot is sensitve that the grid is absolutely regular
  tmp$latitude <- seq(range(tmp$latitude)[1], range(tmp$latitude)[2], length.out = length(tmp$latitude))
  tmp$longitude <- seq(range(tmp$longitude)[1], range(tmp$longitude)[2], length.out = length(tmp$longitude))  
  dimnames(tmp$data) <- list(long = tmp$longitude, lat = tmp$latitude) 
  ret <- melt(tmp$data, value.name = "sst")
  ret <- cbind(date = tmp$time, ret)
  allyears <- rbind(allyears, ret)
}
allyears$long <- allyears$long - 360 

Plotting the result using faceting in ggplot2:

require(ggplot2)
require(mapdata)
xpos <- xpos - 360
#long<-allyears$longitude-360
#lat<-allyears$latitude
coast <- map_data("worldHires", ylim = ypos, xlim = c(-123.5, -120.))

ggplot(data = allyears, aes(x = long, y = lat, fill = sst)) + 
  geom_polygon(data = coast, aes(x = long, y = lat, group = group), fill = "grey80") +
  geom_raster(interpolate = TRUE) +
  scale_fill_gradientn(colours = rev(rainbow(10)), na.value = NA) +
  theme_bw() +
  coord_fixed(1.3, xlim = xpos, ylim = ypos) + 
  facet_wrap(~date, nrow = 2)
#> Warning: Removed 238806 rows containing missing values (geom_raster).

This example uses the MUR (Multi-scale Ultra-high Resolution) SST, a high quality ultra-high resolution SST produced by the MISST project.

Using xtractogon

The xtractogon function extracts a time-series of satellite data that are within a user supplied polygon. As an example, the boundary points of the Monterey Bay National Marine Sanctuary are available in the mbnms dataset which are loaded with the xtractomatic package. The mbnms dataset containes two variables, Latitude and Longitude, which define the boundaries of the sanctuary:

require(xtractomatic)
str(mbnms)
#> 'data.frame':    6666 obs. of  2 variables:
#>  $ Longitude: num  -123 -123 -123 -123 -123 ...
#>  $ Latitude : num  37.9 37.9 37.9 37.9 37.9 ...

The example below extracts a time-series of monthly VIIRS chlorophyll data within the MBNMS.

require(xtractomatic)
tpos <- c("2014-09-01", "2014-10-01")
xpos <- mbnms$Longitude
ypos <- mbnms$Latitude
sanctchl <- xtractogon('erdVH3chlamday', xpos, ypos, tpos = tpos )
str(sanctchl)
#> [1] "2014-09-01" "2014-10-01"
#> List of 7
#>  $ data       : num [1:50, 1:57, 1:2] NA NA NA NA NA NA NA NA NA NA ...
#>  $ varname    : chr "chla"
#>  $ datasetname: chr "erdVH3chlamday"
#>  $ latitude   : num [1:57(1d)] 35.6 35.6 35.6 35.7 35.7 ...
#>  $ longitude  : num [1:50(1d)] -123 -123 -123 -123 -123 ...
#>  $ time       : POSIXlt[1:2], format: "2014-09-15" "2014-10-15"
#>  $ altitude   : logi NA

The extract (seestr(sanctchl)) contains two time periods of chlorophyll masked for data only in the sanctuary boundaries. The function plotBBox() can be used to animate the two time periods:

require("plotdap")
myFunc <- function(x)log(x)
plotBBox(sanctchl, plotColor = 'chlorophyll', myFunc = myFunc, name = 'log(chla)', time = identity, animate = TRUE)
#> Warning: Ignoring unknown aesthetics: frame
#> Executing: 
#> convert -loop 0 -delay 100 Rplot1.png Rplot2.png 'ani.gif'
#> Output at: ani.gif

The MBNMS is famous for containing the Monterey Canyon, which reaches depths of up to 3,600 m (11,800 ft) below surface level at its deepest. xtractogon can extract the bathymetry data for the MBNMS from the ETOPO dataset:

require(xtractomatic)
tpos <- c("2014-09-01", "2014-10-01")
#tpos <-as.Date(tpos)
xpos <- mbnms$Longitude
ypos <- mbnms$Latitude
bathy <- xtractogon('ETOPO180', xpos, ypos)
str(bathy)
#> [1] NA
#> List of 7
#>  $ data       : num [1:123, 1:141, 1] NA NA NA NA NA NA NA NA NA NA ...
#>  $ varname    : chr "altitude"
#>  $ datasetname: chr "etopo180"
#>  $ latitude   : num [1:141(1d)] 35.5 35.6 35.6 35.6 35.6 ...
#>  $ longitude  : num [1:123(1d)] -123 -123 -123 -123 -123 ...
#>  $ time       : logi NA
#>  $ altitude   : logi NA

Mapping the data to show the canyon:

require("plotdap")
plotBBox(bathy, plotColor = 'density', name = 'depth')
#> grid object contains more than 10000 pixels
#> increase `maxpixels` for a finer resolution

What happens when you request an extract

When you make an xtractomatic request, particularly for track data using the xtracto function, it is important to understand what is extracted, because the remote dataset requested likely will have a different temporal and spatial resolution then the local dataset.

Specifically, let longitude, latitude and time be the coordinate system of the remote ERDDAP dataset, and let xpos, ypos and tpos be the bounds of a request. Then the ERDDAP request is based on the nearest grid point of the ERDDAP dataset:

latitude[which.min(abs(latitude - ypos[1]))]  # minimum latitude
latitude[which.min(abs(latitude - ypos[2]))]  # maximum latitude
longitude[which.min(abs(longitude- xpos[1]))] # minimum longitude
longitude[which.min(abs(longitude - xpos[2]))] # maximum longitude
isotime[which.min(abs(time- tpos[1]))] # minimum time
isotime[which.min(abs(time - tpos[2]))] # maximum time

where tpos and time have been converted to an R date format so that it is a number rather than a string. For example, the FNMOC 6-hourly Ekman transports are on a 1-degree grid. A request for the data at a longitude of 220.2 and a latitude of 38.7 will return the result at a longtiude of 220 and a latitude of 39.

Selecting a dataset

As seen in the examples above, the functions xtractomatic, xtracto, xtracto_3D, and xtractogon all use the parameter dtype to specify the combination of dataset and parameter to use. The datasets accessed by xtractomatic are a subset of the almost 900 datasets available on ERD’s ERDDAP server at http://coastwatch.pfeg.noaa.gov/erddap/index.html. The datasets chosen to be accessible by xtractomatic are those that are the most useful to the majority of our users. This version of xtractomatic accesses 165 datasets. Information about a particular dataset is availble via the getInfo function. getInfo takes either the numbered value of a dataset, or the datatype name (the parameter dtypename), which is the first information returned by getInfo. getInfo does not return the number of the dataset, the use of the dtypename is encouraged rather than the number associated with the dataset. It is strongly recommended that dtype not be passed as a number, as the numbers can change and will not be used in the next version.

There are a large number of temporally composited datasets available. The temporal composite time interval is usually indicated in the dtypename (for example “phssta8day” is an 8-day composite), datasetname (for example “erdPHssta8day”) and longname (for example “SST, Pathfinder Ver 5.0, Day and Night, Global, Science Quality (8 Day Composite)”) variables associated with a dataset. The time interval between data points in the time-series is indicated by the timespacing parameter. For example note the difference in timespacing for dtypename=“mhchla8day” and dtypename=“mbchla8day”, which are both 8-day composites.

getInfo('mhchla8day') 
#> List of 19
#>  $ dtypename       : chr "mhchla8day"
#>  $ datasetname     : chr "erdMH1chla8day"
#>  $ longname        : chr "Chlorophyll-a, Aqua MODIS, NPP, L3SMI, Global, Science Quality (8 Day Composite)"
#>  $ varname         : chr "chlorophyll"
#>  $ hasAlt          : logi FALSE
#>  $ latSouth        : logi FALSE
#>  $ lon360          : logi FALSE
#>  $ minLongitude    : num -180
#>  $ maxLongitude    : num 180
#>  $ longitudeSpacing: num 0.0417
#>  $ minLatitude     : num -90
#>  $ maxLatitude     : num 90
#>  $ latitudeSpacing : num -0.0417
#>  $ minAltitude     : num NA
#>  $ maxAltitude     : num NA
#>  $ minTime         : chr "2003-01-05"
#>  $ maxTime         : chr "2017-09-10"
#>  $ timeSpacing     : num NA
#>  $ infoURL         : chr "http://oceancolor.gsfc.nasa.gov/"
getInfo('mbchla8day') 
#> List of 19
#>  $ dtypename       : chr "mbchla8day"
#>  $ datasetname     : chr "erdMBchla8day"
#>  $ longname        : chr "Chlorophyll-a, Aqua MODIS, NPP, Pacific Ocean (8 Day Composite)"
#>  $ varname         : chr "chlorophyll"
#>  $ hasAlt          : logi TRUE
#>  $ latSouth        : logi TRUE
#>  $ lon360          : logi TRUE
#>  $ minLongitude    : num 120
#>  $ maxLongitude    : num 320
#>  $ longitudeSpacing: num 0.025
#>  $ minLatitude     : num -45
#>  $ maxLatitude     : num 65
#>  $ latitudeSpacing : num 0.025
#>  $ minAltitude     : num 0
#>  $ maxAltitude     : num 0
#>  $ minTime         : chr "2005-12-29"
#>  $ maxTime         : chr "2017-09-29"
#>  $ timeSpacing     : num NA
#>  $ infoURL         : chr "http://coastwatch.pfeg.noaa.gov/infog/MB_chla_las.html"

The difference is that the latter dataset (mbchla8day) is a running 8-day composite, so there is a value for every day, while the former dataset (mhchla8day) calculates composites for non-overlapping 8-day periods.

The searchData function can be used to search for a subset of that satisfy certain criteria (note that the searchData has changed since the previous version). searchData requires a list of of entries of the form “searchType:searchString”, where each element of “the first item of”searchType“” has to be one of dtypename, datasetname, longname, varname, and “searchString” is the string to search for in that field. For example to find all of the datasets that have a varname that contains chl and a datasetname that contains mday (most monthly datasets have mday in their name):

mylist <- list('varname:chl', 'datasetname:mday')
searchResult <- searchData(mylist)

which returns among others the three chlorophyll datasets extracted in the xtracto_3D section. The search result can be easily perused by using View() or by:

require("DT")
#> Loading required package: DT
DT::datatable(searchResult)
<

If only doing the first search the code would be:

mylist <- list('varname:chl')
searchResult <- searchData(mylist)

searchData iteratively refines the matches in the order given by the list, thus it can be viewed as doing a logical “AND” on the matches.

The longname of a dataset often contains a lot of this information. Alternatively you can generate a list of the datasetname and longname of all of the datasets accessed by xtractomatic (we only show the first five lines of output):

cat(paste0(xtractomatic:::erddapStruct["datasetname", 1:5],': ' , xtractomatic:::erddapStruct["longname", 1:5],"\n"))
#> list(datasetname = "erdAGssta14day"): list(longname = "sst")
#>  list(datasetname = "erdAGssta1day"): list(longname = "SST, POES AVHRR, LAC, West US, Day and Night (14 Day Composite)")
#>  list(datasetname = "erdAGssta3day"): list(longname = "SST, POES AVHRR, GAC, Global, Day and Night (3 Day Composite)")
#>  list(datasetname = "erdAGssta8day"): list(longname = "SST, POES AVHRR, GAC, Global, Day and Night (8 Day Composite)")
#>  list(datasetname = "erdAGsstamday"): list(longname = "SST, POES AVHRR, GAC, Global, Day and Night (Monthly Composite)")

and then use getInfo with the datasetname to see all of the information on that dataset

Topographic datasets

xtractomatic can extract data from two topographic datasets (ETOPO) , with dtypename of “ETOPO180” and “ETOPO360”, which have longitudes ranging from -180 to 180 and 0 to 360 respectively. Currently the getInfo and searchData commands do not work with these two datasets, but xtracto, xtracto_3D, and xtractogon do.

What has Changed and Steps Going Forward

The earlier section covers the changes to the arguments to the functions. The examples should make clear how the function calls now work.

The function searchData() no longer uses a list of list, instead it uses a simple list where the each “searchType” and “searchString” are set off by a colon as in “searchType:searchString”.

In this and all past version of xtractomatic, information about the datasets was built into the functions. In the present case the dataset information is stored in a dataframe called erddapStruct which is auotmatically loaded when you loaded the package.

This has the disadvantage that it is tied to specific datasets and a specifc ERDDAP server, rather than working with any dataset served by any ERDDAP.

The dataset was specified by the dtype, which could be either a number or a character string, and references the information in that dataframe. The dtype names in this version for the most part are same, BUT dtype AS NUMBERS ARE NO LONGER SUPPORTED. If you have used xtractomatic before you need to check that the dtype information that you are passing is what you think it is. You can do this using the supplied getInfo() or searchData() functions or peruse the erddapStruct dataframe. There are many more datasets available in this version, and now “last” can be used as time to get the last time period available for that dataset.

Every ERDDAP dataset has an ERDDAP “dataset ID”, as well one or more variables associated with that “dataset ID”. Some datasets, such as the ASCAT winds have multiple variables associated with the dataset:

https://coastwatch.pfeg.noaa.gov/erddap/griddap/erdQAstress1day.html.

This information is contained in erddapStruct$datasetname and erddapStruct$varname. Since a single dataset may have more than one variable, dtype as character string is a way to have a unique dataset id and variable name in a single variable. A more general version of xtractomatic, called rerddapXtracto, is under development. rerddapXtracto will work with any gridded dataset on any ERDDAP by using the ROpenSci package rerddap. Information on rerddapXtracto can be found at:

https://github.com/rmendels/rerddapXtracto

LS0tCnRpdGxlOiAiVXNpbmcgdGhlIHh0cmFjdG9tYXRpYyByb3V0aW5lcyB2ZXJzaW9uIDMuNCIKYXV0aG9yOiAiUm95IE1lbmRlbHNzb2huIGFuZCBDYXJhIFdpbHNvbiIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IHJtYXJrZG93bjo6aHRtbF9ub3RlYm9vawotLS0KYGBge3IgaW5pdGlhbGl6ZSwgZWNobyA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoY29sbGFwc2UgPSBUUlVFLCBjb21tZW50ID0gIiM+IikKbGlicmFyeSh4dHJhY3RvbWF0aWMpCmBgYAoKIyMgSW50cm9kdWN0aW9uIAoKYHh0cmFjdG9tYXRpY2AgaXMgYW4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Ujwvc3Bhbj4gcGFja2FnZSBkZXZlbG9wZWQgdG8gc3Vic2V0IGFuZCBleHRyYWN0IHNhdGVsbGl0ZSBhbmQgb3RoZXIgb2NlYW5vZ3JhcGhpYyByZWxhdGVkIGRhdGEgZnJvbSBhIHJlbW90ZSBzZXJ2ZXIuIFRoZSBwcm9ncmFtIGNhbiBleHRyYWN0IGRhdGEgZm9yIGEgbW92aW5nIHBvaW50IGluIHRpbWUgYWxvbmcgYSB1c2VyLXN1cHBsaWVkIHNldCBvZiBsb25naXR1ZGUsIGxhdGl0dWRlIGFuZCB0aW1lIHBvaW50czsgaW4gYSAzRCBib3VuZGluZyBib3g7IG9yIHdpdGhpbiBhIHBvbHlnb24gKHRocm91Z2ggdGltZSkuICBUaGUgYHh0cmFjdG9tYXRpY2AgZnVuY3Rpb25zIHdlcmUgb3JpZ2luYWxseSBkZXZlbG9wZWQgZm9yIHRoZSBtYXJpbmUgYmlvbG9neSB0YWdnaW5nIGNvbW11bml0eSwgdG8gbWF0Y2ggdXAgZW52aXJvbm1lbnRhbCBkYXRhIGF2YWlsYWJsZSBmcm9tIHNhdGVsbGl0ZXMgKHNlYS1zdXJmYWNlIHRlbXBlcmF0dXJlLCBzZWEtc3VyZmFjZSBjaGxvcm9waHlsbCwgc2VhLXN1cmZhY2UgaGVpZ2h0LCBzZWEtc3VyZmFjZSBzYWxpbml0eSwgdmVjdG9yIHdpbmRzKSB0byB0cmFjayBkYXRhIGZyb20gdmFyaW91cyB0YWdnZWQgYW5pbWFscyBvciBzaGlwdHJhY2tzIChgeHRyYWN0b2ApLiBUaGUgcGFja2FnZSBoYXMgc2luY2UgYmVlbiBleHRlbmRlZCB0byBpbmNsdWRlIHRoZSByb3V0aW5lcyB0aGF0IGV4dHJhY3QgZGF0YSBhIDNEIGJvdW5kaW5nIGJveCAoYHh0cmFjdG9fM0RgKSBvciB3aXRoaW4gYSBwb2x5Z29uIChgeHRyYWN0b2dvbmApLiAgVGhlIGB4dHJhY3RvbWF0aWNgICBwYWNrYWdlIGFjY2Vzc2VzICBkYXRhIHRoYXQgYXJlIHNlcnZlZCB0aHJvdWdoIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+IHNlcnZlciBhdCB0aGUgTk9BQS9TV0ZTQyBFbnZpcm9ubWVudGFsIFJlc2VhcmNoIERpdmlzaW9uIGluIFNhbnRhIENydXosIENhbGlmb3JuaWEuIEluIG9yZGVyIHRoYXQgdGhlIHVzZXIgbm90IGJlIG92ZXJ3aGVsbWVkIHdoZW4gc2VhcmNoaW5nIGZvciBkYXRhc2V0cywgb25seSBhIHNlbGVjdGVkIG51bWJlciBvZiB0aGUgZGF0YXNldHMgb24gdGhhdCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+IHNlcnZlciwgdGhvc2Ugdmlld2VkIGFzIG1vc3QgdXNlZnVsLCBhcmUgc3VwcG9ydGVkLiBUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiBzZXJ2ZXIgY2FuIGFsc28gYmUgZGlyZWN0bHkgYWNjZXNzZWQgYXQgPGh0dHA6Ly9jb2FzdHdhdGNoLnBmZWcubm9hYS5nb3YvZXJkZGFwPi4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiBpcyBhIHNpbXBsZSB0byB1c2UgeWV0IHBvd2VyZnVsIHdlYiBkYXRhIHNlcnZpY2UgZGV2ZWxvcGVkIGJ5IEJvYiBTaW1vbnMuICAKClRoaXMgaXMgdGhlIHRoaXJkIHZlcnNpb24gb2YgYHh0cmFjdG9tYXRpY2AuICBUaGUgb3JpZ2luYWwgdmVyc2lvbiB3YXMgY3JlYXRlZCBieSBEYXZlIEZvbGV5IGZvciA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5NYXRsYWI8L3NwYW4+IGFuZCB3YXMgbW9kaWZpZWQgZm9yIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlI8L3NwYW4+IGJ5IENpbmR5IEJlc3NleSBhbmQgRGF2ZSBGb2xleS4gIFRoYXQgdmVyc2lvbiB1c2VkIGEgcHJlY3Vyc2VyIHRvIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkVSRERBUDwvc3Bhbj4gY2FsbGVkIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkJvYkRBUDwvc3Bhbj4uICA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5Cb2JEQVA8L3NwYW4+IHdhcyBsZXNzIGZsZXhpYmxlIGFuZCBwcm92aWRlZCBhY2Nlc3MgdG8gZmV3ZXIgZGF0YXNldHMgdGhhbiBkb2VzIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkVSRERBUDwvc3Bhbj4uICBUaGUgc2Vjb25kIHZlcnNpb24gb2YgeHRyYWN0b21hdGljIHdhcyB3cml0dGVuIGJ5IFJveSBNZW5kZWxzc29obiB0byB1c2UgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiByYXRoZXIgdGhhbiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5Cb2JEQVA8L3NwYW4+LiAgVGhpcyB2ZXJzaW9uLCBhbHNvIHdyaXR0ZW4gYnkgUm95IE1lbmRlbHNzb2huLCBoYXMgbWFueSBpbXByb3ZlbWVudHMgaW5jbHVkaW5nIGFjY2VzcyB0byBtYW55IG1vcmUgZGF0YXNldHMsICBpbXByb3ZlZCBzcGVlZCwgYmV0dGVyIGVycm9yIGNoZWNraW5nLCBjcnVkZSBzZWFyY2ggY2FwYWJpbGl0ZXMsIGFuZCBwbG90dGluZyByb3V0aW5lcywgYW5kIGlzIHRoZSBmaXJzdCB2ZXJzaW9uIGFzIGEgdHJ1ZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5SPC9zcGFuPiBwYWNrYWdlLgoKIyMjIElNUE9SVEFOVCAtIElmIHlvdSBoYXZlIHVzZWQgeHRyYWN0b21hdGljIGJlZm9yZSB7I3JlZmFjdG9yfQoKVGhlIGNvZGUgaGFzIHVuZGVyZ29uZSBhIG1ham9yIHJlZmFjdG9yaW5nLCAgaW4gcGFydGljdWxhciB0aGVyZSBpcyBubyBsb25nZXIgc2VwYXJhdGUgY29kZSB0byBoYW5kbGUgYmF0aHltZXRyeSBhbmQgb3RoZXIgZGF0YXNldHMgd2l0aG91dCB0aW1lLCBhcyB3ZWxsIGFzIHRoZSBhZGRpdGlvbiBvZiBzb21lIGVhc3kgdG8gdXNlIHBsb3Qgcm91dGluZXMuICBJbiBvcmRlciB0byBoYXZlIGEgY2xlYW5lciBkZXNpZ24gdG8gZ28gd2l0aCB0aGVzZSBjaGFuZ2VzLCB0aGUgb3JkZXIgb2YgdGhlIGFyZ3VtZW50cyB0byB0aGUgZnVuY3Rpb25zIGhhcyBjaGFuZ2VkLCAidHBvcyIgbm8gbG9uZ2VyIGhhcyB0byBiZSBwYXNzZWQgZm9yIGEgZGF0YXNldCB3aXRob3V0IHRpbWUsICBhbmQgInhsZW4iIGFuZCAieWxlbiIgaGF2ZSBkZWZhdWx0IHZhbHVlcyAgKHNlZSBuZXh0IHNlY3Rpb24pLiAgQWxzbyBpbiBgeHRyYWN0bygpYCB0aGUgcmV0dXJuIG5vdyBpbmNsdWRlcyB0aGUgcGFyYW1ldGVyIG5hbWUsICBzbyBpbnN0ZWFkIG9mIG9uZSBlbGVtZW50IGJlaW5nIGp1c3QgIm1lYW4iIGl0IHdpbGwgYmUgIm1lYW4gc3N0IiBmb3IgZXhhbXBsZS4gVGhlIGNoYW5nZXMgaW4gdGhlIGZ1bmN0aW9uIGFyZ3VtZW50cyBtYWtlcyB0aGVtIGNsb3NlciB0byB0aGF0IGluIGByZXJkZGFwWHRyYWN0b2AsIHNlZSBuZXh0IHNlY3Rpb24uCgojIyMgSU1QT1JUQU5UIC0gTGlrZWx5IGxhc3QgdmVyc2lvbiBvZiB4dHJhY3RvbWF0aWMKClRoaXMgd2lsbCBsaWtlbHkgYmUgdGhlIGxhc3QgdmVyc2lvbiBvZiBgeHRyYWN0b21hdGljYCwgZXhjZXB0IGZvciBidWcgZml4ZXMgb3IgY2hhbmdlcyBuZWVkZWQgdG8gc3RheSBjb21wYXRpYmxlIHdpdGggbmV3IHZlcnNpb25zIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlI8L3NwYW4+LCBvciBjaGFuZ2VzIGluIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkVSRERBUDwvc3Bhbj4gYW5kIHRoZSBhcHBsaWNhdGlvbnMgaXQgdXNlcy4gRGV2ZWxvcG1lbnQgZWZmb3J0IHdpbGwgYmUgcHV0IGludG8gaW1wcm92aW5nIHRoZSBgcmVyZGRhcFh0cmFjdG9gIHBhY2thZ2UsIHdpdGggdGhlIGludGVudCB0aGF0IHVzZXJzIHdpbGwgZXZlbnR1YWxseSBtaWdyYXRlIHRvIGByZXJkZGFwWHRyYWN0b2AuICBBcyBtZW50aW9uZWQgYWJvdmUsIHRoZSBjaGFuZ2VzIGluIHRoZSBmdW5jdGlvbiBhcmd1bWVudHMgd2lsbCBoZWxwIHRvIHNpbXBsaWZ5IHRoaXMgbWlncmF0aW9uLgoKCgoKIyMjIFRoZSBNYWluIHh0cmFjdG9tYXRpYyBmdW5jdGlvbnMKClRoZXJlIGFyZSB0aHJlZSBtYWluIGRhdGEgZXh0cmFjdGlvbiBmdW5jdGlvbnMgaW4gdGhlIGB4dHJhY3RvbWF0aWNgIHBhY2thZ2U6IAoKLSBgeHRyYWN0byA8LSBmdW5jdGlvbihkdHlwZSwgeHBvcywgeXBvcywgdHBvcyA9IE5BLCB4bGVuID0gMC4sIHlsZW4gPSAwLiwgdmVyYm9zZT1GQUxTRSlgCgotIGB4dHJhY3RvXzNEIDwtIGZ1bmN0aW9uKGR0eXBlLCB4cG9zLCB5cG9zLCB0cG9zID0gTkEsIHZlcmJvc2U9RkFMU0UpYAoKLSBgeHRyYWN0b2dvbiA8LSBmdW5jdGlvbihkdHlwZSwgeHBvcywgeXBvcywgdHBvcyA9IE5BLCAgdmVyYm9zZSA9IEZBTFNFKWAKCgpUaGVyZSBhcmUgdHdvIGluZm9ybWF0aW9uIGZ1bmN0aW9ucyBpbiB0aGUgYHh0cmFjdG9tYXRpY2AgcGFja2FnZTogCgotIGBzZWFyY2hEYXRhIDwtIGZ1bmN0aW9uKHNlYXJjaExpc3Q9ICJ2YXJuYW1lOmNobCIpICAKCi0gYGdldEluZm8gPC0gZnVuY3Rpb24oZHR5cGUpYAoKVGhlcmUgYXJlIG5vdyB0d28gcGxvdHRpbmcgZnVuY3Rpb25zIHRoYXQgbWFrZSB1c2Ugb2YgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlI8L3NwYW4+IHBhY2thZ2UgYHBsb3RkYXBgOgoKLSBgcGxvdFRyYWNrIDwtIGZ1bmN0aW9uKHJlc3AsIHhjb29yZCwgeWNvb3JkLCBwbG90Q29sb3IgPSAndmlyaWRpcycsIG5hbWUgPSBOQSwgbXlGdW5jID0gTkEsIHNoYXBlID0gMjAsIHNpemUgPSAuNSlgCgotIGBwbG90QkJveCA8LSBmdW5jdGlvbihyZXNwLCBwbG90Q29sb3IgPSAndmlyaWRpcycsIHRpbWUgPSBOQSwgYW5pbWF0ZSA9IEZBTFNFLCBuYW1lID0gTkEsIG15RnVuYyA9IE5BLCBtYXhwaXhlbHMgPSAxMDAwMClgCgpUaGUgYGR0eXBlYCBwYXJhbWV0ZXIgaW4gdGhlIGRhdGEgZXh0cmFjdGlvbiByb3V0aW5lcyBzcGVjaWZpZXMgYSBjb21iaW5hdGlvbiBvZiB3aGljaCBkYXRhc2V0IG9uIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+IHNlcnZlciB0byBhY2Nlc3MsIGFuZCBhcyB3ZWxsIGFzIHdoaWNoIHBhcmFtZXRlciBmcm9tIHRoYXQgZGF0YXNldC4gVGhlIGZpcnN0IHNlY3Rpb25zIGRlbW9uc3RyYXRlIGhvdyB0byB1c2UgdGhlc2UgZnVuY3Rpb25zIGluIHJlYWxpc3RpYyBzZXR0aW5ncy4gVGhlIFtTZWxlY3RpbmcgYSBkYXRhc2V0XSgjZGF0YXNldCkgc2VjdGlvbiBzaG93cyBob3cgdG8gZmluZCB0aGUgYGR0eXBlYCBvciBvdGhlciBwYXJhbWV0ZXJzIG9mIGludGVyZXN0IGZyb20gdGhlIGF2YWlsYWJsZSBkYXRhc2V0cy4KCiMjIFNldHRpbmcgdXAgCgpgeHRyYWN0b21hdGljYCB1c2VzIHRoZSBgaHR0cmAsIGBuY2RmNGAgYW5kIGBzcGAgcGFja2FnZXMsIGFuZCB0aGVzZSBwYWNrYWdlcyBtdXN0IGJlIGluc3RhbGxlZCBmaXJzdCBvciBgeHRyYWN0b21hdGljYCB3aWxsIGZhaWwgdG8gaW5zdGFsbC4KCmBgYHtyIGluc3RhbGwsZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygiaHR0ciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCmluc3RhbGwucGFja2FnZXMoIm5jZGY0IixkZXBlbmRlbmNpZXMgPSBUUlVFKSAKaW5zdGFsbC5wYWNrYWdlcygic3AiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQpgYGAKCgpUaGlzIGRldmVsb3BtZW50IHZlcnNpb24gb2YgYHh0cmFjdG9tYXRpY2AgaXMgYXZhaWxhYmxlIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL3JtZW5kZWxzL3h0cmFjdG9tYXRpYyBhbmQgY2FuIGJlIGluc3RhbGxlZCBmcm9tIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkdpdGh1Yjwvc3Bhbj4sCgpgYGB7ciBpbnN0YWxsR2l0LGV2YWw9RkFMU0V9Cmluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIikKZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJybWVuZGVscy94dHJhY3RvbWF0aWMiKQpgYGAKCkluIG9yZGVyIHRvIHVzZSB0aGUgcGxvdCByb3V0aW5lcywgIHlvdSBtdXN0IGhhdmUgdGhlIHBhY2thZ2UgYHBsb3RkYXBgIGluc3RhbGxlZCwgYXMgd2VsbCBhcyBhbGwgb2YgaXRzIGRlcGVuZGVuY2llcywgIG1vc3QgaW1wb3J0YW50bHkgYHJlcmRkYXBgIGFuZCBgc2ZgIChidXQgbm90ZSB0aGVzZSBhcmUgb25seSBuZWVkZWQgaWYgeW91IGFyZSB1c2luZyB0aGUgcGxvdHRpbmcgcm91dGluZXMuICAgVGhlIGxhdHRlciB0d28gY2FuIGJlIGluc3RhbGxlZCBmcm9tIENSQU46CgpgYGB7ciAsZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygicmVyZGRhcCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCmluc3RhbGwucGFja2FnZXMoInNmIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKYGBgCgpUaGUgYHBsb3RkYXBgIHBhY2thZ2UgY2FuIGJlIGluc3RhbGxlZCBmcm9tIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkdpdGh1Yjwvc3Bhbj46CgpgYGB7ciAsZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ3JvcGVuc2NpL3Bsb3RkYXAnLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQpgYGAKCgpPbmNlIGluc3RhbGxlZCwgdG8gdXNlIGB4dHJhY3RvbWF0aWNgIGRvCgpgYGB7ciBsb2FkLGV2YWw9RkFMU0V9CmxpYnJhcnkoInh0cmFjdG9tYXRpYyIpCmBgYAoKSWYgdGhlIG90aGVyIGxpYnJhcmllcyAgKGBodHRyYCwgYG5jZGY0YCwgYW5kIGBzcGApIGhhdmUgYmVlbiBpbnN0YWxsZWQgdGhleSB3aWxsIGJlIGZvdW5kIGFuZCBkbyBub3QgbmVlZCB0byBiZSBleHBsaWNpdGx5IGxvYWRlZC4KCiMjIyBVc2luZyB0aGUgUiBjb2RlIGV4YW1wbGVzCgpCZXNpZGVzIHRoZSBgeHRyYWN0b21hdGljYCBwYWNrYWdlcywgdGhlIGV4YW1wbGVzIGJlbG93IGRlcGVuZCBvbiBgRFRgLCBgZ2dwbG90MmAsICBgbHVicmlkYXRlYCwgYG1hcGRhdGFgLCBhbmQgIGByZXNoYXBlMmAuICBUaGVzZSBjYW4gYmUgbG9hZGVkIGJlZm9yZWhhbmQgKGFzc3VtaW5nIHRoZXkgaGF2ZSBiZWVuIGluc3RhbGxlZCk6CgpgYGB7ciBsb2FkcGFja3MsZXZhbD1GQUxTRX0KbGlicmFyeSgiRFQiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKbGlicmFyeSgibHVicmlkYXRlIikKbGlicmFyeSgibWFwZGF0YSIpCmxpYnJhcnkoInJlc2hhcGUyIikKYGBgCgpJbiBvcmRlciB0aGF0IHRoZSBjb2RlIHNuaXBwZXRzIGNhbiBiZSBtb3JlIHN0YW5kLWFsb25lLCB0aGUgbmVlZGVkIGxpYnJhcmllcyBhcmUgYWx3YXlzIGByZXF1aXJlZGAgaW4gdGhlIGV4YW1wbGVzLiAgCgoKSXQgc2hvdWxkIGJlIGVtcGhhc2l6ZWQgdGhhdCB0aGVzZSBvdGhlciBwYWNrYWdlcyBhcmUgdXNlZCB0byBtYW5pcHVsYXRlIGFuZCBwbG90IHRoZSBkYXRhIGluIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlI8L3NwYW4+LCBvdGhlciBwYWNrYWdlcyBjb3VsZCBiZSB1c2VkIGFzIHdlbGwuICBUaGUgdXNlIG9mIGB4dHJhY3RvbWF0aWNgIGRvZXMgbm90IGRlcGVuZCBvbiB0aGVzZSBvdGhlciBwYWNrYWdlcy4KClRoZXJlIGFyZSBhbHNvIHNldmVyYWwgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Ujwvc3Bhbj4gZnVuY3Rpb25zIGRlZmluZWQgd2l0aGluIHRoZSBkb2N1bWVudCB0aGF0IGFyZSB1c2VkIGluIG90aGVyIGNvZGUgZXhhbXBsZXMuICBUaGVzZSBpbmNsdWRlICBgY2hsYUF2Z2AsIGB1cHdlbGxgLCBhbmQgYHBsb3RVcHdlbGxgLgoKCgojIyBHZXR0aW5nIFN0YXJ0ZWQKClRoZSBwbG90dGluZyBmdW5jdGlvbnMgYXJlIG5ldywgYW5kIHRoZXJlIGFyZSBzb21lIGZpbmUgcG9pbnRzIHRoYXQgbmVlZCB0byBiZSB1bmRlcnN0b29kIGlmIHRoZXkgYXJlIHRvIGJlIHVzZWQgcHJvcGVybHksIGluIHBhcnRpY3VsYXIgYHBsb3RCQm94KClgLiBCb3RoIGBwbG90VHJhY2soKWAgYW5kIGBwbG90QkJveCgpYCByZWFycmFuZ2UgdGhlIG91dHB1dCBzbyB0aGF0IHRoZSBgcGxvdGRhcGAgZnVuY3Rpb25zIGBhZGRfdGFibGVkYXAoKWAgYW5kIGBhZGRfZ3JpZGRhcCgpYCB0aGluayB0aGF0IHRoZSBvdXRwdXQgaXMgZnJvbSBgcmVyZGRhcGAsIGFuZCB0aGVuIG1ha2UgdGhlIGFwcHJvcHJpYXRlIGBwbG90ZGFwYCBjYWxsLiBXaGVuIHRoZSBkYXRhIHRoYXQgaXMgcGFzc2VkIHRvIGBhZGRfZ3JpZGRhcCgpYCBoYXMgbXV0aXBsZSB0aW1lIHBlcmlvZHMsIHRoZXJlIGFyZSB0d28gb3B0aW9ucy4gVGhlIGZpcnN0IG9wdGlvbiBpcyB0byBzZXQgdGhlIHBhcmFtZXRlciDigJx0aW1l4oCdIHRvIGEgZnVuY3Rpb24gdGhhdCByZWR1Y2VzIHRoZSBkYXRhIHRvIG9uZSBkaW1lbnNpb24gaW4gdGhlIHRpbWUgY29vcmRpbmF0ZSAoc3VjaCBhcyB0aGUgbWVhbiksIG9yIGVsc2UgdG8gc2V0IOKAnHRpbWXigJ0gZXF1YWwgdG8g4oCcaWRlbnRpdHnigJ0gYW5kIHNldCDigJxhbmltYXRl4oCdIHRvIGJlIOKAnFRSVUXigJ0gd2hpY2ggd2lsbCBwcm9kdWNlIGEgdGltZSBhbmltYXRpb24gb2YgdGhlIHJlc3VsdHMuIFRoZSBmdW5jdGlvbiBgcGxvdEJCb3goKWAgd29ya3MgdGhlIHNhbWUgd2F5LCBleGNlcHQgdGhhdCB0aGUgZGVmYXVsdCBmdW5jdGlvbiBpcyBgbWVhbihuYS5ybSA9IFRSVUUpYC4gVGhlIGZvbGxvd2luZyBleGFtcGxlcyBzaG93IGhvdyB0byB1c2UgZGlmZmVyZW50IGZlYXR1cmVzIG9mIHRoZSBwbG90dGluZyBmdW5jdGlvbnM6CgotIFtTZXR0aW5nIHRoZSBjb2xvciBwYWxldHRlXSgjY29sb3JQYWxldHRlKSBzaG93cyBob3cgdG8gdXNlIHRoZSDigJxwbG90Q29sb3LigJ0gb3B0aW9uLiBUaGUg4oCccGxvdENvbG9y4oCdIHBhcmFtZXRlciBjYW4gYmUgdGhlIG5hbWUgb2YgYW55IG9mIHRoZSBjb2xvcnMgaW5jbHVkZWQgaW4gdGhlIHJlcmRkYXAgY29sb3IgcGFsbGV0ZS4gVGhlc2UgY29sb3JzIGFyZSBiYXNlZCBvbiB0aGUgY21vY2VhbiBjb2xvcm1hcHMgZGVzaWduZWQgYnkgS3Jpc3RlbiBUaHluZyAoc2VlIGh0dHA6Ly9tYXRwbG90bGliLm9yZy9jbW9jZWFuLyBhbmQgaHR0cHM6Ly9naXRodWIuY29tL21hdHBsb3RsaWIvY21vY2VhbiksIHdoaWNoIHdlcmUgaW5pdGFsbHkgZGV2ZWxvcGVkIGZvciBQeXRob24sIGJ1dCBhIHZlcnNpb24gb2YgdGhlIGNvbG9ybWFwcyBpcyB1c2VkIGluIHRoZSBgb2NlYCBwYWNrYWdlIGJ5IERhbiBLZWxsZXkgYW5kIENsYXJrIFJpY2hhcmRzIGFuZCB0aGF0IGlzIGFsc28gd2hhdCBpcyB1c2VkIGluIHJlcmRkYXAuCgotIFtQbG90IG9uZSB0aW1lIHBlcmlvZF0oI3Bsb3QxKSBzaG93cyBob3cgdG8gbWFuaXB1bGF0ZSBhbiBleGlzdGluZyBvdXRwdXQgZnJvbSBgcnh0cmFjdG9fM0QoKWAgb3IgYHJleHRyYWN0b2dvbigpYCB0byBwbG90IGp1c3Qgb25lIHRpbWUgcGVyaW9kLgoKLSBbVHJhbnNmb3JtIHRoZSBkYXRhXSgjdHJhbnNmb3JtKSBleGFtcGxlIHNob3dzIGhvdyB0byB1c2UgdGhlIOKAnG15RnVuY+KAnSBvcHRpb24gdG8gdHJhbnNmb3JtIHRoZSBkYXRhIGJlZm9yZSBwbG90dGluZy4gVGhlIGZ1bmN0aW9uIGhhcyB0byBiZSBhIGZ1bmN0aW9uIG9mIGEgc2luZ2xlIGFyZ3VtZW50LiBUaGlzIGV4YW1wbGUgYWxzbyBzaG93cyBob3cgdG8gdXNlIHRoZSDigJxuYW1l4oCdIG9wdGlvbiB0byBjaGFuZ2UgdGhlIG5hbWUgZGlzcGxheWVkIG9uIHRoZSBjb2xvciBiYXIuIEluIHRoaXMgZXhhbXBsZSwgd2Ugd2FudCBkZXB0aCB0byBnbyBkb3dud2FyZHMgaW4gdGhlIGNvbG9yYmFyLCBhbmQgdGhlIG5hbWUgZ2l2ZW4gY2hhbmdlZCBmcm9tIOKAnGFsdGl0dWRl4oCdLCB3aGljaCBpcyB0aGUgbmFtZSBvbiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+LCB0byB0aGUgbmFtZSDigJxEZXB0aOKAnS4KCi0gW05hbWVdKCNuYW1lKSBleGFtcGxlIHNob3dzIGhvdyB0byBjaGFuZ2UgdGhlIG5hbWUgb24gdGhlIGNvbG9yYmFyLgoKLSBbQW5pbWF0ZV0oI2FuaW1hdGUpIHNob3dzIGhvdyB0byBhbmltYXRlIGEgZ3JpZCB3aXRoIG11bHRpcGxlIHRpbWUgcGVyaW9kcy4KCiMjIyBBbiB4dHJhY3RvIGV4YW1wbGUgCgpJbiB0aGlzIHNlY3Rpb24gd2UgZXh0cmFjdCBkYXRhIGFsb25nIGEgdHJhY2tsaW5lIGZvdW5kIGluIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5NYXJsaW50YWczODYwNjwvc3Bhbj4gZGF0YXNldCwgd2hpY2ggaXMgdGhlIHRyYWNrIG9mIGEgdGFnZ2VkIG1hcmxpbiBpbiB0aGUgUGFjaWZpYyBPY2VhbiAoY291cnRlc3kgb2YgRHIuIE1pa2UgTXVzeWwgb2YgdGhlIFBlbGFnaWMgUmVzZWFyY2ggR3JvdXAgTExDKSwgYW5kIHNob3cgc29tZSBzaW1wbGUgcGxvdHMgb2YgdGhlIGV4dHJhY3RlZCBkYXRhLiAgCgpUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+TWFybGludGFnMzg2MDY8L3NwYW4+IGRhdGFzZXQgaXMgbG9hZGVkIHdoZW4geW91IGxvYWQgdGhlIGB4dHJhY3RvbWF0aWNgIGxpYnJhcnkuIFRoZSBzdHJ1Y3R1cmUgb2YgdGhpcyBkYXRhZnJhbWUgaXMgc2hvd24gYnk6IAoKYGBge3IgTWFybGluVGFnfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKc3RyKE1hcmxpbnRhZzM4NjA2KQpgYGAKClRoZSAgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+TWFybGludGFnMzg2MDY8L3NwYW4+IGRhdGFzZXQgY29uc2lzdHMgb2YgdGhyZWUgdmFyaWFibGVzLCB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+ZGF0ZTwvc3Bhbj4sIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmxvbmdpdHVkZTwvc3Bhbj4gYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmxhdGl0dWRlPC9zcGFuPiBvZiB0aGUgdGFnZ2VkIGZpc2gsIHdoaWNoIGFyZSB1c2VkIGFzIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj54cG9zPC9zcGFuPiwgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+eXBvczwvc3Bhbj4gYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPnRwb3M8L3NwYW4+IGFyZ3VtZW50cyBpbiBgeHRyYWN0b2AuIFRoZSBwYXJhbWV0ZXJzIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPnhsZW48L3NwYW4+IGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj55bGVuPC9zcGFuPiBzcGVjaWZ5IHRoZSBzcGF0aWFsIOKAnHJhZGl1c+KAnSAoYWN0dWFsbHkgYSBib3gsIG5vdCBhIGNpcmNsZSkgYXJvdW5kIHRoZSBwb2ludCB0byBzZWFyY2ggaW4gZm9yIGV4aXN0aW5nIGRhdGEuICBUaGVzZSBjYW4gYWxzbyBiZSBpbnB1dCBhcyBhIHRpbWUtZGVwZW5kZW50IHZlY3RvciBvZiB2YWx1ZXMsIGJ1dCBoZXJlIGEgc2V0IHZhbHVlIG9mIDAuMiBkZWdyZWVzIGlzIHVzZWQuICBUaGUgZXhhbXBsZSBleHRyYWN0cyA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5TZWFXaUZTIGNobG9yb3BoeWxsIGRhdGE8L3NwYW4+LCB1c2luZyBhIGRhdGFzZXQgdGhhdCBpcyBhbiA4IGRheSBjb21wb3NpdGUuIFRoaXMgZGF0YXNldCBpcyBzcGVjaWZpZWQgYnkgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmR0eXBlPC9zcGFuPiBvZiBgInN3Y2hsYThkYXkiYCBpbiB0aGUgYHh0cmFjdG9gIGZ1bmN0aW9uLiBJbiBhIGxhdGVyIHNlY3Rpb24gaXQgaXMgZXhwbGFpbmVkIGhvdyB0byBhY2Nlc3MgZGlmZmVyZW50IHNhdGVsbGl0ZSBkYXRhc2V0cyBvdGhlciB0aGFuIHRoZSBvbmUgc2hvd24gaGVyZS4gIAoKCmBgYHtyIGdldE1hcmxpbkNobCwgZXZhbCA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKCiMgRmlyc3Qgd2Ugd2lsbCBjb3B5IHRoZSBNYXJsaW50YWczODYwNiBkYXRhIGludG8gYSB2YXJpYWJsZSAKIyBjYWxsZWQgdGFnRGF0YSAgc28gdGhhdCBzdWJzZXF1ZW50IGNvZGUgd2lsbCBiZSBtb3JlIGdlbmVyaWMuICAKCnRhZ0RhdGEgPC0gTWFybGludGFnMzg2MDYKeHBvcyA8LSB0YWdEYXRhJGxvbgp5cG9zIDwtIHRhZ0RhdGEkbGF0CnRwb3MgPC0gdGFnRGF0YSRkYXRlCnN3Y2hsIDwtIHh0cmFjdG8oInN3Y2hsYThkYXkiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcywgLCB4bGVuID0gLjIsIHlsZW4gPSAuMikKYGBgCgpgYGB7ciBnZXRNYXJsaW5DaGwxLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCgojIEZpcnN0IHdlIHdpbGwgY29weSB0aGUgTWFybGludGFnMzg2MDYgZGF0YSBpbnRvIGEgdmFyaWFibGUgCiMgY2FsbGVkIHRhZ0RhdGEgIHNvIHRoYXQgc3Vic2VxdWVudCBjb2RlIHdpbGwgYmUgbW9yZSBnZW5lcmljLiAgCgp0YWdEYXRhIDwtIE1hcmxpbnRhZzM4NjA2Cnhwb3MgPC0gdGFnRGF0YSRsb24KeXBvcyA8LSB0YWdEYXRhJGxhdAp0cG9zIDwtIHRhZ0RhdGEkZGF0ZQpudW10cmllcyA8LSA1CnRyeW4gPC0gMApnb29kdHJ5IDwtIC0xCm9wdGlvbnMod2FybiA9IDIpCndoaWxlICgodHJ5biA8PSBudW10cmllcykgJiAoZ29vZHRyeSA9PSAtMSkpIHsKICAgICB0cnluIDwtIHRyeW4gKyAxCiAgICAgc3djaGwgPC0gdHJ5KHh0cmFjdG8oInN3Y2hsYThkYXkiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcywgIHhsZW4gPSAuMiwgeWxlbiA9IC4yKSkKICAgICBpZiAoIWNsYXNzKHN3Y2hsKSA9PSAidHJ5LWVycm9yIikgewogICAgICAgZ29vZHRyeSA8LSAxCiAgICAgfQogIH0KYGBgCgpUaGlzIGNvbW1hbmQgY2FuIHRha2UgYSB3aGlsZSB0byBydW4gZGVwZW5kaW5nIG9uIHlvdXIgaW50ZXJuZXQgc3BlZWQgYW5kIGhvdyBidXN5IGlzIHRoZSBzZXJ2ZXIuIFdpdGggYSBmYWlybHkgZGVjZW50IGludGVybmV0IGNvbm5lY3Rpb24gaXQgdG9vayB+MjUgc2Vjb25kcy4gIFRoZSBkZWZhdWx0IGlzIHRvIHJ1biBpbiBxdWlldCBtb2RlLCBpZiB5b3Ugd291bGQgcHJlZmVyIHRvIHNlZSBvdXRwdXQsIGFzIGNvbmZpcm1hdGlvbiB0aGF0IHRoZSBzY3JpcHQgaXMgcnVubmluZywgdXNlIGB2ZXJib3NlPVRSVUUgYAoKV2hlbiB0aGUgZXh0YWN0aW9uIGhhcyBjb21wbGV0ZWQgdGhlIGRhdGEuZnJhbWUgYHN3Y2hsYCB3aWxsIGNvbnRhaW4gYXMgbWFueSBjb2x1bW5zIGFzIGRhdGEgcG9pbnRzIGluIHRoZSBpbnB1dCBmaWxlIChpbiB0aGlzIGNhc2UgMTUyKSBhbmQgd2lsbCBoYXZlIDExIGNvbHVtbnM6IAoKYGBge3Igc3djaGxTdHJ9CnN0cihzd2NobCkKYGBgCgojIyMgUGxvdHRpbmcgdGhlIHJlc3VsdHMKCldlIHBsb3QgdGhlIHRyYWNrIGxpbmUgd2l0aCB0aGUgbG9jYXRpb25zIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBtZWFuIG9mIHRoZSBzYXRlbGxpdGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Y2hsb3JvcGh5bGw8L3NwYW4+IGFyb3VuZCB0aGF0IHBvaW50LiBQb3NpdGlvbnMgd2hlcmUgdGhlcmUgd2FzIGEgdGFnIGxvY2F0aW9uIGJ1dCBubyA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5jaGxvcm9waHlsbDwvc3Bhbj4gdmFsdWVzIGFyZSBhbHNvIHNob3duLgoKPHNwYW4gaWQ9ImNvbG9yUGFsZXR0ZSI+PC9zcGFuPgpgYGB7ciBtZWFudHJhY2tQbG90LCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDR9CnJlcXVpcmUocGxvdGRhcCkKcmVxdWlyZShyZXJkZGFwKQpwbG90VHJhY2soc3djaGwsIHhwb3MsIHlwb3MsIHBsb3RDb2xvciA9ICdjaGxvcm9waHlsbCcpCgpgYGAKCgojIyMgVG9wb2dyYXBoeSBkYXRhCgpJbiBhZGRpdGlvbiB0byBzYXRlbGxpdGUgZGF0YSwgYHh0cmFjdG9tYXRpY2AgY2FuIGFjY2VzcyB0b3BvZ3JhcGhpYyBkYXRhLiBBcyBhbiBleGFtcGxlIHdlIGV4dHJhY3QgYW5kIHBsb3QgdGhlIHdhdGVyIGRlcHRoIChiYXRoeW1ldHJ5KSBhc3NvY2lhdGVkIHdpdGggdGhlIHRyYWNrbGluZS4KCkdldCB0aGUgdG9wb2dyYXBoeSBkYXRhIGFsb25nIHRoZSB0cmFjazogCgpgYGB7ciB0b3BvdGFnLCBldmFsID0gRkFMU0V9CnJlcXVpcmUoInh0cmFjdG9tYXRpYyIpCnlsaW0gPC0gYygxNSwgMzApCnhsaW0gPC0gYygtMTYwLCAtMTA1KQp0b3BvIDwtIHh0cmFjdG8oIkVUT1BPMTgwIiwgdGFnRGF0YSRsb24sIHRhZ0RhdGEkbGF0LCB4bGVuID0gLjEsIHlsZW4gPSAuMSkKYGBgCgpgYGB7ciB0b3BvdGFnMSwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CnJlcXVpcmUoInh0cmFjdG9tYXRpYyIpCnlsaW0gPC0gYygxNSwgMzApCnhsaW0gPC0gYygtMTYwLCAtMTA1KQpudW10cmllcyA8LSA1CnRyeW4gPC0gMApnb29kdHJ5IDwtIC0xCm9wdGlvbnMod2FybiA9IDIpCndoaWxlICgodHJ5biA8PSBudW10cmllcykgJiAoZ29vZHRyeSA9PSAtMSkpIHsKICAgICB0cnluIDwtIHRyeW4gKyAxCiAgICAgdG9wbyA8LSB0cnkoeHRyYWN0bygiRVRPUE8xODAiLCB0YWdEYXRhJGxvbiwgdGFnRGF0YSRsYXQsIHhsZW4gPSAuMSwgeWxlbiA9IC4xKSwgc2lsZW50ID0gVFJVRSkKICAgICBpZiAoIWNsYXNzKHRvcG8pID09ICJ0cnktZXJyb3IiKSB7CiAgICAgICBnb29kdHJ5IDwtIDEKICAgICB9CiAgfQoKYGBgCgphbmQgcGxvdCB0aGUgdHJhY2s6Cgo8c3BhbiBpZD0ibmFtZSI+PC9zcGFuPgpgYGB7ciB0b3BvdGFnUGxvdCwgZmlnLmFsaWduID0gJ2NlbnRlcicsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSA0LCB3YXJuaW5nID0gRkFMU0V9CnJlcXVpcmUocGxvdGRhcCkKcmVxdWlyZShyZXJkZGFwKQp0b3BvUGxvdCA8LSBwbG90VHJhY2sodG9wbywgeHBvcywgeXBvcywgIHBsb3RDb2xvciA9ICdkZW5zaXR5JywgbmFtZSA9ICdEZXB0aCcsIHNpemUgPSAuMSkKdG9wb1Bsb3QKCmBgYAoKCiMjIFJlYWRpbmcgaW4gdGFnZ2VkIGRhdGEgCgpUaGUgYWJvdmUgZXhhbXBsZSB1c2VzIGRhdGEgYWxyZWFkeSBjb252ZXJ0ZWQgdG8gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Ujwvc3Bhbj4gZm9ybWF0LCBidXQgY2xlYXJseSB0aGUgdXRpbGl0eSBvZiB0aGUgYHh0cmFjdG9gIGZ1bmN0aW9uIGxpZXMgaW4gdXNpbmcgaXQgd2l0aCBvbmUncyBvd24gZGF0YXNldC4gIFRoaXMgZGF0YXNldCB3YXMgcmVhZCBpbiBmcm9tIGEgdGV4dCBmaWxlIGNhbGxlZCA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+TWFybGluLXRhZzM4NjA2LnR4dDwvc3Bhbj4sIHdoaWNoIGlzIGFsc28gbWFkZSBhdmFpbGFibGUgd2hlbiB0aGUgYHh0cmFjdG9tYXRpY2AgcGFja2FnZSBpcyBsb2FkZWQuICBUbyBiZSBhYmxlIHRvIHJlZmVyIHRvIHRoaXMgZmlsZTogCgpgYGB7ciBtYXJsaW5UeHQsIHJlc3VsdHMgPSAiaGlkZSJ9CgpzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJNYXJsaW4tdGFnMzg2MDYudHh0IiwgcGFja2FnZSA9ICJ4dHJhY3RvbWF0aWMiKQoKYGBgCgpUaGUgZmlyc3QgNSBsaW5lcyBvZiB0aGUgZmlsZSBjYW4gYmUgdmlld2VkOiAKYGBge3J9CmRhdGFmaWxlIDwtIHN5c3RlbS5maWxlKCJleHRkYXRhIiwgIk1hcmxpbi10YWczODYwNi50eHQiLCBwYWNrYWdlID0gInh0cmFjdG9tYXRpYyIpCnN5c3RlbShwYXN0ZSgiaGVhZCAtbjUgIiwgZGF0YWZpbGUpKQoKYGBgCgphbmQgcmVhZCBpbiBhcyBmb2xsb3dzOiAKCmBgYHtyfQoKTWFybGluZ3RhZzM4NjA2IDwtIHJlYWQuY3N2KGRhdGFmaWxlLCBoZWFkID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFLCBzZXAgPSAiXHQiKQpzdHIoTWFybGluZ3RhZzM4NjA2KQpgYGAKClRoZSBmaWxlIE1hcmxpbmd0YWczODYwNi50eHQgaXMgYSAidGFiIiBzZXBhcmF0ZWQgZmlsZSwgbm90IGEgY29tbWEgc2VwZXJhdGVkIGZpbGUsIHNvIHRoZSBgc2VwYCBvcHRpb24gaW4gdGhlIGByZWFkLmNzdmAgZnVuY3Rpb24gYWJvdmUgaW5kaWNhdGVzIHRoYXQgdG8gdGhlIGZ1bmN0aW9uLCBhbmQgYHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRWAgdGVsbHMgdGhlIGZ1bmN0aW9uIG5vdCB0byB0cmVhdCB0aGUgZGF0ZXMgYXMgZmFjdG9ycy4gCgpUbyBiZSB1c2VmdWwgdGhlIGRhdGUgZmllbGQsIG5vdyBmb3JtYXR0ZWQgZm9yIGV4YW1wbGUgYXMgYDQvMjMvMjAwM2AsIG5lZWRzIHRvIGJlIGNvbnZlcnRlZCB0byBhbiAgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Ujwvc3Bhbj4gZGF0ZSBmaWVsZDoKCmBgYHtyLCBldmFsID0gRkFMU0V9Ck1hcmxpbnRhZzM4NjA2JGRhdGUgPC0gYXMuRGF0ZShNYXJsaW50YWczODYwNiRkYXRlLCBmb3JtYXQ9JyVtLyVkLyVZJykKYGBgCgpUaGUgZm9ybWF0IG9mIHRoZSBkYXRlIGhhcyB0byBiZSBnaXZlbiBiZWNhdXNlIHRoZSBkYXRlcyBpbiB0aGUgZmlsZSBhcmUgbm90IGluIG9uZSBvZiB0aGUgZm9ybWF0cyByZWNvZ25pemVkIGJ5IGBhcy5EYXRlYC4gQSBzbWFsbCB5ICgleSkgaW5kaWNhdGVzIHRoZSB5ZWFyIGhhcyAyIGRpZ2l0cyAoOTcpIHdoaWxlIGEgY2FwaXRhbCBZICglWSkgaW5kaWNhdGVzIHRoZSB5ZWFyIGhhcyA0IGRpZ2l0cyAoMTk5NykuIElmIHlvdXIgZmlsZSBpcyBpbiB0aGUgRVUgZm9ybWF0IHdpdGggY29tbWFzIGFzIGRlY2ltYWwgcG9pbnRzIGFuZCBzZW1pY29sb25zIGFzIGZpZWxkIHNlcGFyYXRvcnMgdGhlbiB1c2UgcmVhZC5jc3YyIHJhdGhlciB0aGFuIHJlYWQuY3N2LiAgIAoKVGhpcyBkYXRhc2V0IGluY2x1ZGVzIHRoZSByYW5nZSBvZiB0aGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yIGJvdGggdGhlIGxhdGl0dWRlIGFuZCB0aGUgbG9uZ2l0dWRlLCB3aGljaCBhcmUgdGhlIGxhc3QgZm91ciBjb2x1bW5zIG9mIGRhdGEuICBJbiB0aGUgZXhhbXBsZSBhYm92ZSB0aGUgc2VhcmNoICJyYWRpdXMiIHdhcyByZWFkIGludG8geHRyYWN0byB3YXMgYSBjb25zdGFudCB2YWx1ZSBvZiAwLjIgZGVncmVlcywgYnV0IGEgdmVjdG9yIGNhbiBhbHNvIGJlIHJlYWQgaW4uICBUbyB1c2UgdGhlIGxpbWl0cyBnaXZlbiBpbiB0aGUgZmlsZSwgY3JlYXRlIHRoZSB2ZWN0b3JzIGFzIGZvbGxvd3M6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQp4cmFkIDwtIGFicyhNYXJsaW50YWczODYwNiRoaWdMb24gLSBNYXJsaW50YWczODYwNiRsb3dMb24pCnlyYWQgPC0gYWJzKE1hcmxpbnRhZzM4NjA2JGhpZ0xhdCAtIE1hcmxpbnRhZzM4NjA2JGxvd0xhdCkKYGBgCgphbmQgdGhlbiB1c2UgdGhlc2UgZm9yIHRoZSBgeGxlbmAgYW5kIGB5bGVuYCBpbnB1dHMgaW4gYHh0cmFjdG9gOiAKCmBgYHtyLCBldmFsID0gRkFMU0V9Cnhwb3MgPC0gTWFybGludGFnMzg2MDYkbG9uIAp5cG9zIDwtIE1hcmxpbnRhZzM4NjA2JGxhdCAKdHBvcyA8LSBNYXJsaW50YWczODYwNiRkYXRlCnN3Y2hsIDwtIHh0cmFjdG8oeHBvcywgeXBvcywgdHBvcywgInN3Y2hsYThkYXkiLCB4cmFkLCB5cmFkKQpgYGAKCiMjIFVzaW5nIGB4dHJhY3RvXzNEYCAKCiMjIyBDb21wYXJpbmcgY2hsb3JvcGh5bGwgZXN0aW1hdGVzIGZyb20gZGlmZmVyZW50IHNhdGVsbGl0ZXMKClRoaXMgZXhhbXBsZSBleHRyYWN0cyBhIHRpbWUtc2VyaWVzIG9mIG1vbnRobHkgc2F0ZWxsaXRlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmNobG9yb3BoeWxsPC9zcGFuPiBkYXRhIGZvciB0aGUgcGVyaW9kIDE5OTgtMjAxNCBmcm9tIHRocmVlIGRpZmZlcmVudCBzYXRlbGxpdGVzICggPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+U2VhV0lGUzwvc3Bhbj4sICA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5NT0RJUzwvc3Bhbj4gYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlZJSVJTPC9zcGFuPiApLCB1c2luZyB0aGUgYHh0cmFjdG9fM0RgIGZ1bmN0aW9uLiBUaGUgZXh0cmFjdCBpcyBmb3IgYW4gYXJlYSBvZmYgdGhlIGNvYXN0IG9mIENhbGlmb3JuaWEuICBgeHRyYWN0b18zRGAgZXh0cmFjdHMgZGF0YSBpbiBhIDNEIGJvdW5kaW5nIGJveCB3aGVyZSBgeHBvc2AgaGFzIHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmxvbmdpdHVkZTwvc3Bhbj4sICBgeXBvc2AgaGFzIHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmxhdGl0dWRlPC9zcGFuPiwgYW5kIGB0cG9zYCBoYXMgdGhlIHN0YXJ0aW5nIGFuZCBlbmRpbmcgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+dGltZTwvc3Bhbj4sIGdpdmVuIGFzICJZWVktTU0tREQiLCBkZXNjcmliaW5nIHRoZSBib3VuZGluZyBib3guCgpGaXJzdCB3ZSBkZWZpbmUgdGhlIGxvbmdpdHVkZSwgbGF0aXR1ZGUgYW5kIHRlbXBvcmFsIGJvdW5kYXJpZXMgb2YgdGhlIGJveDogCgpgYGB7ciBzZXRMaW1zM0R9IAp4cG9zIDwtIGMoMjM1LCAyNDApCnlwb3MgPC0gYygzNiwgMzkpCnRwb3MgPC0gYygnMTk5OC0wMS0wMScsICcyMDE0LTExLTMwJykgCmBgYAoKVGhlIGV4dHJhY3Qgd2lsbCBjb250YWluIGRhdGEgYXQgYWxsIG9mIHRoZSBsb25naXR1ZGVzLCBsYXRpdHVkZXMgYW5kIHRpbWVzIGluIHRoZSByZXF1ZXN0ZWQgZGF0YXNldCB0aGF0IGFyZSB3aXRoaW4gdGhlIGdpdmVuIGJvdW5kcy4KCklmIHdlIHJ1biB0aGUgYHh0cmFjdG9fM0RgIHVzaW5nIHRoZSBmdWxsIHRlbXBvcmFsIGNvbnRyYWludHMgZGVmaW5lZCBhYm92ZSBmb3IgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+U2VhV2lGUzwvc3Bhbj4gd2UgZ2V0IGFuIGVycm9yOiAKCmBgYHtyIFNlYVdpRlNCYWQsIGVycm9yID0gVFJVRSwgcHVybCA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKY2hsU2VhV2lGUyA8LSB4dHJhY3RvXzNEKCdzd2NobGFtZGF5JywgeHBvcywgeXBvcywgdHBvcyA9IHRwb3MgKQpgYGAKClRoZSBlcnJvciBvY2N1cnMgYmVjYXVzZSB0aGUgdGltZS1yYW5nZSBzcGVjaWZpZWQgaXMgb3V0c2lkZSBvZiB0aGUgdGltZS1ib3VuZHMgb2YgdGhlIHJlcXVlc3RlZCBkYXRhc2V0ICggPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+U2VhV0lGUzwvc3Bhbj4gZGF0YSBzdG9wcGVkIGluIDIwMTApLiBUaGUgZXJyb3IgbWVzc2FnZSBnZW5lcmF0ZWQgYnkgYHh0cmFjdG9fM0RgIGV4cGxhaW5zIHRoaXMsIGFuZCBnaXZlcyB0aGUgdGVtcG9yYWwgYm91bmRhcmllcyBvZiB0aGUgZGF0YXNldC4gQSBzaW1wbGUgd2F5IHRvIGV4dHJhY3QgdG8gdGhlIGVuZCBvZiBhIGRhdGFzZXQgaXMgdG8gdXNlICJsYXN0IiAoc2VlIFtUYWtpbmcgYWR2YW50YWdlIG9mICJsYXN0IHRpbWVzIl0oI2xhc3RUaW1lcykpOiAKCmBgYHtyIFNlYVdpRlMsIGV2YWwgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnRwb3MgPC0gYygiMTk5OC0wMS0xNiIsICJsYXN0IikKU2VhV2lGUyA8LSB4dHJhY3RvXzNEKCdzd2NobGFtZGF5JywgeHBvcywgeXBvcywgdHBvcyA9IHRwb3MpCmBgYAoKYGBge3IgU2VhV2lGUzEsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKdHBvcyA8LSBjKCIxOTk4LTAxLTE2IiwgImxhc3QiKQpudW10cmllcyA8LSA1CnRyeW4gPC0gMApnb29kdHJ5IDwtIC0xCm9wdGlvbnMod2FybiA9IDIpCndoaWxlICgodHJ5biA8PSBudW10cmllcykgJiAoZ29vZHRyeSA9PSAtMSkpIHsKICAgICB0cnluIDwtIHRyeW4gKyAxCiAgICAgU2VhV2lGUyA8LSB0cnkoeHRyYWN0b18zRCgnc3djaGxhbWRheScsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKSwgc2lsZW50ID0gVFJVRSkKICAgICBpZiAoIWNsYXNzKFNlYVdpRlMpID09ICJ0cnktZXJyb3IiKSB7CiAgICAgICBnb29kdHJ5IDwtIDEKICAgICB9CiAgfQpgYGAKCgpBIHNpbWlsYXIgZXh0cmFjdCBjYW4gYmUgbWFkZSBmb3IgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPk1PRElTPC9zcGFuPiBkYXRhc2V0LiBXZSBrbm93IHRoYXQgdGhlIHRpbWUtcmFuZ2Ugb2YgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPk1PRElTPC9zcGFuPiBkYXRhc2V0IGlzIGFsc28gc21hbGxlciB0aGFuIHRoYXQgZGVmaW5lZCBieSBgdHBvc2AuIFdlIGNhbiBkZXRlcm1pbmUgdGhlIGV4YWN0IGJvdW5kcyBvZiB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+TU9ESVM8L3NwYW4+IGRhdGFzZXQgYnkgdXNpbmcgdGhlIGBnZXRJbmZvYCBmdW5jdGlvbjogCgpgYGB7ciBNT0RJU2dldEluZm99CnJlcXVpcmUoeHRyYWN0b21hdGljKQpnZXRJbmZvKCdtaGNobGFtZGF5JykKYGBgCgp3aGljaCBnaXZlcyB1cyB0aGUgbWluIGFuZCBtYXggdGltZSBvZiB0aGUgZGF0YXNldCwgd2hpY2ggd2UgY2FuIGJlIHVzZWQgaW4gdGhlIGNhbGwgKG9yIHVzZSAibGFzdCIgYXMgYWJvdmUpOiAgIAoKYGBge3IgTU9ESVNjaGxhLCBldmFsID0gRkFMU0V9CnJlcXVpcmUoeHRyYWN0b21hdGljKQp0cG9zIDwtIGMoJzIwMDMtMDEtMTYnLCAibGFzdCIpCk1PRElTIDwtIHh0cmFjdG9fM0QoJ21oY2hsYW1kYXknLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcykKYGBgCgpgYGB7ciBNT0RJU2NobGExLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnRwb3MgPC0gYygnMjAwMy0wMS0xNicsICJsYXN0IikKbnVtdHJpZXMgPC0gNQp0cnluIDwtIDAKZ29vZHRyeSA8LSAtMQpvcHRpb25zKHdhcm4gPSAyKQp3aGlsZSAoKHRyeW4gPD0gbnVtdHJpZXMpICYgKGdvb2R0cnkgPT0gLTEpKSB7CiAgICAgdHJ5biA8LSB0cnluICsgMQogICAgIE1PRElTIDwtIHRyeSh4dHJhY3RvXzNEKCdtaGNobGFtZGF5JywgeHBvcywgeXBvcywgdHBvcyA9IHRwb3MpLCBzaWxlbnQgPSBUUlVFKQogICAgIGlmICghY2xhc3MoTU9ESVMpID09ICJ0cnktZXJyb3IiKSB7CiAgICAgICBnb29kdHJ5IDwtIDEKICAgICB9CiAgfQoKYGBgCgpTaW1pbGFybHkgd2UgY2FuIGV4dHJhY3QgZGF0YSBmcm9tIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5WSUlSUzwvc3Bhbj4gZGF0YXNldC4gCgpgYGB7ciBWSUlSU2NobGEsIGV2YWwgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnRwb3MgPC0gYygiMjAxMi0wMS0xNSIsICJsYXN0IikKVklJUlMgPC0geHRyYWN0b18zRCgnZXJkVkgzY2hsYW1kYXknLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcykKYGBgCgpgYGB7ciBWSUlSU2NobGExLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnRwb3MgPC0gYygiMjAxMi0wMS0xNSIsICJsYXN0IikKbnVtdHJpZXMgPC0gNQp0cnluIDwtIDAKZ29vZHRyeSA8LSAtMQpvcHRpb25zKHdhcm4gPSAyKQp3aGlsZSAoKHRyeW4gPD0gbnVtdHJpZXMpICYgKGdvb2R0cnkgPT0gLTEpKSB7CiAgICAgdHJ5biA8LSB0cnluICsgMQogICAgIFZJSVJTIDwtIHRyeSh4dHJhY3RvXzNEKCdlcmRWSDNjaGxhbWRheScsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKSwgc2lsZW50ID0gVFJVRSkKICAgICBpZiAoIWNsYXNzKFZJSVJTKSA9PSAidHJ5LWVycm9yIikgewogICAgICAgZ29vZHRyeSA8LSAxCiAgICAgfQogIH0KCmBgYAoKYHh0cmFjdG9fM2RgIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb3JtOgoKICogZXh0cmFjdCRkYXRhICAgICAgIDogbnVtIFtsb25naXR1ZGUsIGxhdGl0dWRlLCB0aW1lXSAKICogZXh0cmFjdCR2YXJuYW1lICAgIDogY2hhcmFjdGVyIHN0cmluZyB3aXRoIHRoZSBuYW1lIG9mIHRoZSB2YXJpYWJsZQogKiBleHRyYWN0JGRhdGFzZXRuYW1lOiBjaGFyYWN0ZXIgc3RyaW5nIG9mIHRoZSBFUkREQVAgZGF0YXNldCBJRAogKiBleHRyYWN0JGxhdGl0dWRlICAgOiBudW0gW2xhdGl0dWRlXSBsYXRpdHVkZXMgb2YgZXh0cmFjdAogKiBleHRyYWN0JGxvbmdpdHVkZSAgOiBudW0gW2xvbmdpdHVkZV0gbG9uZ2l0dWRlIG9mIGV4dGFjdAogKiBleHRyYWN0JHRpbWUgICAgICAgOiBjaHIgW3RpbWVdIHRpbWVzIG9mIGV4dHJhY3QKICogZXh0cmFjdCRhbHRpdHVkZSAgIDogbnVtIGFsdGl0dWRlIG9mIGV4dHJhY3QKCmluIHRoaXMgY2FzZSB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+dmFybmFtZTwvc3Bhbj4gaXMgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Y2hsYTwvc3Bhbj4sIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+IGBkYXRhc2V0SURgIGlzIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmVyZFZIM2NobGFtZGF5PC9zcGFuPiwgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmRhdGE8L3NwYW4+IGNvbnRhaW5zIHRoZSBkYXRhIG9uIHRoZSBncmlkIGRlZmluZWQgYnkgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+bG9uZ2l0dWRlPC9zcGFuPiwgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+bGF0aXR1ZGU8L3NwYW4+IGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj50aW1lPC9zcGFuPi4gIFRoZSBkYXRhIGZyb20gdGhlIGRpZmZlcmVudCBzYXRlbGxpdGVzIGNhbiBiZSBjb21wYXJlZCBieSBtYXBwaW5nIHRoZSB2YWx1ZXMgZm9yIGEgZ2l2ZW4gdGltZSBwZXJpb2QsIHdoaWNoIHdlIGNuYSBkbyB1c2luZyB0aGUgZnVuY3Rpb24gYHBsb3RCQm94KClgLgoKV2UgZXhhbWluZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5jaGxvcm9waHlsbDwvc3Bhbj4gaW4gSnVuZSAyMDEyIGEgcGVyaW9kIHdoZW4gVklJUlMgYW5kIE1PRElTIGJvdGggaGFkIGRhdGEuICBTdGFydGluZyB3aXRoIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlZJSVJTPC9zcGFuPjoKCjxzcGFuIGlkPSJwbG90MSI+PC9zcGFuPgpgYGB7ciBWSUlSU1Bsb3QsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSA1LCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KcmVxdWlyZSgibHVicmlkYXRlIikKcmVxdWlyZSgicGxvdGRhcCIpCnRlbXBDaGxhIDwtIFZJSVJTCnRlbXBDaGxhJGRhdGEgPC0gVklJUlMkZGF0YVssICwgbW9udGgoVklJUlMkdGltZSkgPT0gNiAmIHllYXIoVklJUlMkdGltZSkgPT0gMjAxMl0KdGVtcENobGEkdGltZSA8LSBWSUlSUyR0aW1lW21vbnRoKFZJSVJTJHRpbWUpID09IDYgJiB5ZWFyKFZJSVJTJHRpbWUpID09IDIwMTJdCnBsb3RCQm94KHRlbXBDaGxhLCBwbG90Q29sb3IgPSAnY2hsb3JvcGh5bGwnLCBtYXhwaXhlbHMgPSAzMDAwMCkKYGBgCgo8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5DaGxvcm9waHlsbDwvc3Bhbj4gdmFsdWVzIGFyZSBoaWdobHkgc2tld2VkLCB3aXRoIGxvdyB2YWx1ZXMgbW9zdCBwbGFjZXMgYW5kIHRpbWVzLCBhbmQgdGhlbiBmb3Igc29tZSBsb2NhdGlvbnMgdmVyeSBoaWdoIHZhbHVlcy4gIEZvciB0aGlzIHJlYXNvbiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5sb2coY2hsb3JvcGh5bGwpPC9zcGFuPiBpcyB1c3VhbGx5IHBsb3R0ZWQuCgo8c3BhbiBpZD0idHJhbnNmb3JtIj48L3NwYW4+CmBgYHtyIFZJSVJTTG9nUGxvdCwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDUsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpyZXF1aXJlKCJwbG90ZGFwIikKbXlGdW5jIDwtIGZ1bmN0aW9uKHgpIGxvZyh4KQpwbG90QkJveCh0ZW1wQ2hsYSwgcGxvdENvbG9yID0gJ2NobG9yb3BoeWxsJywgbXlGdW5jID0gbXlGdW5jLCBuYW1lID0gJ2xvZyhjaGxhKScsIG1heHBpeGVscyA9IDMwMDAwKQpgYGAKCgpBbmQgYWxzbyBmb3IgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+TU9ESVM8L3NwYW4+OgoKYGBge3IgTU9ESVNMb2dQbG90LCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnJlcXVpcmUoImx1YnJpZGF0ZSIpCnJlcXVpcmUoInBsb3RkYXAiKQp0ZW1wQ2hsYSA8LSBNT0RJUwp0ZW1wQ2hsYSRkYXRhIDwtIE1PRElTJGRhdGFbLCAsIG1vbnRoKE1PRElTJHRpbWUpID09IDYgJiB5ZWFyKE1PRElTJHRpbWUpID09IDIwMTJdCnRlbXBDaGxhJHRpbWUgPC0gTU9ESVMkdGltZVttb250aChNT0RJUyR0aW1lKSA9PSA2ICYgeWVhcihNT0RJUyR0aW1lKSA9PSAyMDEyXQpteUZ1bmMgPC0gZnVuY3Rpb24oeCkgbG9nKHgpCnBsb3RCQm94KHRlbXBDaGxhLCBwbG90Q29sb3IgPSAnY2hsb3JvcGh5bGwnLCBteUZ1bmMgPSBteUZ1bmMsIG1heHBpeGVscyA9IDMwMDAwKQoKYGBgCgo8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5TZWFXaUZTPC9zcGFuPiBzdG9wcGVkIGZ1bmN0aW9uaW5nIGF0IHRoZSBlbmQgb2YgMjAxMCwgc28gd2UgbG9vayBhdCBKdW5lLCAyMDEwOgoKYGBge3IgU2VhV2lGU0xvZ1Bsb3QsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSA1LCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KcmVxdWlyZSgibHVicmlkYXRlIikKcmVxdWlyZSgicGxvdGRhcCIpCnRlbXBDaGxhIDwtIFNlYVdpRlMKdGVtcENobGEkZGF0YSA8LSBTZWFXaUZTJGRhdGFbLCAsIG1vbnRoKFNlYVdpRlMkdGltZSkgPT0gNiAmIHllYXIoU2VhV2lGUyR0aW1lKSA9PSAyMDEwXQp0ZW1wQ2hsYSR0aW1lIDwtIFNlYVdpRlMkdGltZVttb250aChTZWFXaUZTJHRpbWUpID09IDYgJiB5ZWFyKFNlYVdpRlMkdGltZSkgPT0gMjAxMF0KbXlGdW5jIDwtIGZ1bmN0aW9uKHgpIGxvZyh4KQpwbG90QkJveCh0ZW1wQ2hsYSwgcGxvdENvbG9yID0gJ2NobG9yb3BoeWxsJywgbXlGdW5jID0gbXlGdW5jLCBuYW1lID0gJ2xvZyhjaGxhKScsIG1heHBpeGVscyA9IDMwMDAwKQpgYGAKCgoKV2UgY2FuIGFsc28gbG9vayBhdCBjaGFuZ2VzIGluIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmxvZyhjaGxvcm9waHlsbCk8L3NwYW4+IG92ZXIgdGltZSBmb3IgYSBnaXZlbiByZWdpb24uICBXZSBleGFtaW5lIGEgaGlnbHkgcHJvZHVjdGl2ZSByZWdpb24ganVzdCBub3J0aCBvZiBTYW4gRnJhbmNpc2NvLCB3aXRoIGxpbWl0cyAoMzhOLCAzOC41TikgYW5kICgxMjMuNVcsIDEyM1cpLgoKYGBge3IgY2hsYUF2Z3NldCwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDMsIGZpZy5hbGlnbj0nY2VudGVyJ30KcmVxdWlyZShnZ3Bsb3QyKQpyZXF1aXJlKG1hcGRhdGEpCnhsaW0xIDwtIGMoLTEyMy41LCAtMTIzKSArIDM2MAp5bGltMSA8LSBjKDM4LCAzOC41KQp3IDwtIG1hcF9kYXRhKCJ3b3JsZEhpcmVzIiwgeWxpbSA9IGMoMzcuNSwgMzkpLCB4bGltID0gYygtMTIzLjUsIC0xMjIuKSkKeiA8LSBnZ3Bsb3QoKSArIGdlb21fcG9seWdvbihkYXRhID0gdywgYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgZmlsbCA9ICJncmV5ODAiKSArCiAgIHRoZW1lX2J3KCkgKwogICBjb29yZF9maXhlZCgxLjMsIHhsaW0gPSBjKC0xMjMuNSwgLTEyMi4pLCB5bGltID0gYygzNy41LCAzOSkpCnogKyBnZW9tX3JlY3QoYWVzKHhtaW4gPSAtMTIzLjUsIHhtYXggPSAtMTIzLiwgeW1pbiA9IDM4LiwgeW1heCA9IDM4LjUpLCBjb2xvdXIgPSAiYmxhY2siLCBhbHBoYSA9IDAuKSArCiAgdGhlbWVfYncoKSArIGdndGl0bGUoIkxvY2F0aW9uIG9mIGNobGEgYXZlcmFnaW5nIikKYGBgCgpXZSBkZWZpbmUgYSBoZWxwZXIgZnVuY3Rpb24gYGNobGFBdmdgIHdoaWNoIGZpbmRzIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5sYXRpdHVkZXM8L3NwYW4+IGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5sb25naXR1ZGVzPC9zcGFuPiBpbiB0aGUgZGF0YXNldHMgdGhhdCBhcmUgY2xvc2VzdCB0byB0aGUgZ2l2ZW4gbGltaXRzLCBleHRyYWN0cyB0aGUgc3Vic2V0IHRoYXQgaXMgd2l0aGluIHRoYXQgcmVnaW9uLCBhbmQgdGhlbiBhdmVyYWdlcyBvdmVyIHRoZSByZWdpb24gdG8gY3JlYXRlIGEgdGltZSBzZXJpZXMuCgpgYGB7ciBjaGxhQXZnfQpjaGxhQXZnIDwtIGZ1bmN0aW9uKGxvbmdpdHVkZSwgbGF0aXR1ZGUsIGNobGFEYXRhLCB4bGltLCB5bGltLCBzVGltZXMpewogIHhJbmRleCA8LSB4bGltCiAgeUluZGV4IDwtIHlsaW0KICB5SW5kZXhbMV0gPC0gd2hpY2gubWluKGFicyhsYXRpdHVkZSAtIHlsaW1bMV0pKQogIHlJbmRleFsyXSA8LSB3aGljaC5taW4oYWJzKGxhdGl0dWRlIC0geWxpbVsyXSkpCiAgeEluZGV4WzFdIDwtIHdoaWNoLm1pbihhYnMobG9uZ2l0dWRlIC0geGxpbVsxXSkpCiAgeEluZGV4WzJdIDwtIHdoaWNoLm1pbihhYnMobG9uZ2l0dWRlIC0geGxpbVsyXSkpCiAgdGVtcERhdGEgPC0gY2hsYURhdGFbeEluZGV4WzFdOnhJbmRleFsyXSwgeUluZGV4WzFdOnlJbmRleFsyXSxdCiAgY2hsYUF2ZyA8LSBhcHBseSh0ZW1wRGF0YSwgMywgbWVhbiwgbmEucm0gPSBUUlVFKQogIGNobGFBdmcgPC0gZGF0YS5mcmFtZShjaGxhID0gY2hsYUF2ZywgdGltZSA9IHNUaW1lcykKcmV0dXJuKGNobGFBdmcpCn0KCmBgYAoKCgpXZSB1c2UgIGBjaGxhQXZnYCB0byBleHRyYWN0IHRoZSB0aHJlZSB0aW1lIHNlcmllcyBvdmVyIHRoZSByZWdpb24sIGNvbWJpbmUgdGhlbSBhbmQgcGxvdCB0aGUgcmVzdWx0LiAgCgpgYGB7ciBBdmVyYWdlQ2hsYX0KeGxpbTEgPC0gYygtMTIzLjUsIC0xMjMpICsgMzYwCnlsaW0xIDwtIGMoMzgsIDM4LjUpCiMgR2V0IGJvdGggdGhlIGF2ZXJhZ2UsIGFuZCB0aGUgYXZlcmFnZSBvZiBsb2cgdHJhbnNmb3JtZWQgY2hsIGZvciBlYWNoIHBvaW50IGluIHRoZSB0aW1lIHNlcmllcyAKU2VhV2lGU2F2ZyA8LSBjaGxhQXZnKFNlYVdpRlMkbG9uZ2l0dWRlLCBTZWFXaUZTJGxhdGl0dWRlLCBTZWFXaUZTJGRhdGEsIHhsaW0xLCB5bGltMSxTZWFXaUZTJHRpbWUpClNlYVdpRlNsb2cgPC0gY2hsYUF2ZyhTZWFXaUZTJGxvbmdpdHVkZSwgU2VhV2lGUyRsYXRpdHVkZSwgbG9nKFNlYVdpRlMkZGF0YSksIHhsaW0xLCB5bGltMSwgU2VhV2lGUyR0aW1lKQojIFJ1biB0aGUgc2FtZSBzdGVwcyBhZ2FpbiBmb3IgdGhlIE1PRElTIGFuZCBWSUlSUyBkYXRhc2V0cwpNT0RJU2F2ZyA8LSBjaGxhQXZnKE1PRElTJGxvbmdpdHVkZSwgTU9ESVMkbGF0aXR1ZGUsIE1PRElTJGRhdGEsIHhsaW0xLCB5bGltMSwgTU9ESVMkdGltZSkKTU9ESVNsb2cgPC0gY2hsYUF2ZyhNT0RJUyRsb25naXR1ZGUsIE1PRElTJGxhdGl0dWRlLCBsb2coTU9ESVMkZGF0YSksIHhsaW0xLCB5bGltMSwgTU9ESVMkdGltZSkKIyBydW4gdGhlIHNhbWUgZm9yIFZJSVJTClZJSVJTYXZnIDwtIGNobGFBdmcoVklJUlMkbG9uZ2l0dWRlLCBWSUlSUyRsYXRpdHVkZSwgVklJUlMkZGF0YSwgeGxpbTEsIHlsaW0xLCBWSUlSUyR0aW1lKQpWSUlSU2xvZyA8LSBjaGxhQXZnKFZJSVJTJGxvbmdpdHVkZSwgVklJUlMkbGF0aXR1ZGUsIGxvZyhWSUlSUyRkYXRhKSwgeGxpbTEsIHlsaW0xLCBWSUlSUyR0aW1lKQpgYGAKCkZpcnN0IHRoZSByYXcgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+Y2hsYTwvc3Bhbj4gdmFsdWVzOgoKYGBge3IgcGxvdENobGFUUywgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDQsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpyZXF1aXJlKGdncGxvdDIpCkNobGEgPC0gcmJpbmQoVklJUlNhdmcsIE1PRElTYXZnLCBTZWFXaUZTYXZnKQpDaGxhJHNhdCA8LSBjKHJlcCgiVklJUlMiLCBucm93KFZJSVJTYXZnKSksIHJlcCgiTU9ESVMiLCBucm93KE1PRElTYXZnKSksIHJlcCgiU2VhV0lGIiwgbnJvdyhTZWFXaUZTYXZnKSkpCkNobGEkc2F0IDwtIGFzLmZhY3RvcihDaGxhJHNhdCkKZ2dwbG90KGRhdGEgPSBDaGxhLCBhZXModGltZSwgY2hsYSwgY29sb3VyID0gc2F0KSkgKyBnZW9tX2xpbmUobmEucm0gPSBUUlVFKSArIHRoZW1lX2J3KCkKCmBgYAoKYW5kIHRoZW4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+bG9nKGNobGEpPC9zcGFuPjoKCmBgYHtyIHBsb3Rsb2dDaGxhVFMsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0LCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KcmVxdWlyZShnZ3Bsb3QyKQpsb2dDaGxhIDwtIHJiaW5kKFZJSVJTbG9nLCBNT0RJU2xvZywgU2VhV2lGU2xvZykKbG9nQ2hsYSRzYXQgPC0gYyhyZXAoIlZJSVJTIiwgbnJvdyhWSUlSU2xvZykpLCByZXAoIk1PRElTIiwgbnJvdyhNT0RJU2xvZykpLCByZXAoIlNlYVdJRiIsICAgIG5yb3coU2VhV2lGU2xvZykpKQpsb2dDaGxhJHNhdCA8LSBhcy5mYWN0b3IoQ2hsYSRzYXQpCmdncGxvdChkYXRhID0gbG9nQ2hsYSwgYWVzKHRpbWUsIGNobGEsIGNvbG91ciA9IHNhdCkpICsgZ2VvbV9saW5lKG5hLnJtID0gVFJVRSkgICsgdGhlbWVfYncoKQpgYGAKCgojIyMgVXB3ZWxsaW5nCgo8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkQ8L3NwYW4+IGZvciBtYW55IHllYXJzIGhhcyBwcm9kdWNlZCA2LWhvdXJseSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5CYWt1biB1cHdlbGxpbmcgaW5kaWNlczwvc3Bhbj4gYXQgMTUgc2V0IGxvY2F0aW9ucy4gQnV0IHN1cHBvc2UgeW91IHdhbnQgdGhlIHZhbHVlcyBhdCBhIGRpZmZlcmVudCBsb2NhdGlvbj8gIFRvIGRvIHRoaXMgd2UgZGVmaW5lIGEgZnVuY3Rpb24gYHVwd2VsbGAgdGhhdCByb3RhdGVzIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkVrbWFuIHRyYW5zcG9ydDwvc3Bhbj4gdmFsdWVzIHRvIG9idGFpbiB0aGUgb2Zmc2hvcmUgY29tcG9uZW50ICg8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj51cHdlbGxpbmcgaW5kZXg8L3NwYW4+KSwgYW5kIGFsc28gZGVmaW5lIGEgZnVuY3Rpb24gYHBsb3RVcHdlbGxgIHRvIHBsb3QgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+dXB3ZWxsaW5nPC9zcGFuPi4gYHh0cmFjdG9fM0RgIGNhbiBkb3dubG9hZCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkZSI+RWttYW4gdHJhbnNwb3J0PC9zcGFuPiBjYWxjdWxhdGVkIGZyb20gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RmxlZXQgTnVtZXJpY2FsIE1ldGVvcmxvZ2ljYWwgYW5kIE9jZWFub2dyYXBoaWMgQ2VudGVyIChGTk1PQyk8L3NwYW4+IHByZXNzdXJlIGZpZWxkczoKCgpgYGB7ciB1cENhbGN9CnVwd2VsbCA8LSBmdW5jdGlvbihla3RyeCwgZWt0cnksIGNvYXN0X2FuZ2xlKXsKICAgcGkgPC0gMy4xNDE1OTI3CiAgIGRlZ3RvcmFkIDwtIHBpLzE4MC4KICAgYWxwaGEgPC0gKDM2MCAtIGNvYXN0X2FuZ2xlKSAqIGRlZ3RvcmFkCiAgIHMxIDwtIGNvcyhhbHBoYSkKICAgdDEgPC0gc2luKGFscGhhKQogICBzMiA8LSAtMSAqIHQxCiAgIHQyIDwtIHMxCiAgIHBlcnAgPC0gczEgKiBla3RyeCArIHQxICogZWt0cnkKICAgcGFyYSA8LSBzMiAqIGVrdHJ4ICsgdDIgKiBla3RyeQpyZXR1cm4ocGVycC8xMCkKfQpgYGAKCmBgYHtyIHBsb3RVcHdlbGxpbmdGdW59CnBsb3RVcHdlbGwgPC0gZnVuY3Rpb24odXB3ZWxsaW5nLCB1cERhdGVzKXsKcmVxdWlyZShnZ3Bsb3QyKQp0ZW1wIDwtIGRhdGEuZnJhbWUodXB3ZWxsaW5nID0gdXB3ZWxsaW5nLCB0aW1lID0gdXBEYXRlcykKZ2dwbG90KHRlbXAsIGFlcyh0aW1lLCB1cHdlbGxpbmcpKSArIGdlb21fbGluZShuYS5ybSA9IFRSVUUpICsgdGhlbWVfYncoKQp9CmBgYAoKV2UgY2FsY3VsYXRlIDYtaG91cmx5IHVwd2VsbGluZyBhdCAoMzcsLTEyMikgZm9yIHRoZSB5ZWFyIDIwMDUgYW5kIHVzZSB0aGUgY29hc3QgYW5nbGUgdGhhdCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkQ8L3NwYW4+IHVzZXMgZm9yICgzNk4gLCAxMjJXKSB3aGljaCBpcyAxNTIgZGVncmVlcy4KCmBgYHtyIGdldFVwdywgZXZhbCA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKeHBvcyA8LSBjKDIzOCwgMjM4KQp5cG9zIDwtIGMoMzcsIDM3KQp0cG9zIDwtIGMoIjIwMDUtMDEtMDEiLCAiMjAwNS0xMi0zMSIpCmVrdHJ4IDwtIHh0cmFjdG9fM0QoImVyZGxhc0ZuVHJhbjZla3RyeCIsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKQpla3RyeSA8LSB4dHJhY3RvXzNEKCJlcmRsYXNGblRyYW42ZWt0cnkiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcykKdXB3ZWxsaW5nIDwtIHVwd2VsbChla3RyeCRkYXRhLCBla3RyeSRkYXRhLCAxNTIpCmBgYAoKYGBge3IgZ2V0VXB3MSwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CnJlcXVpcmUoeHRyYWN0b21hdGljKQp4cG9zIDwtIGMoMjM4LCAyMzgpCnlwb3MgPC0gYygzNywgMzcpCnRwb3MgPC0gYygiMjAwNS0wMS0wMSIsICIyMDA1LTEyLTMxIikKbnVtdHJpZXMgPC0gNQp0cnluIDwtIDAKZ29vZHRyeSA8LSAtMQpvcHRpb25zKHdhcm4gPSAyKQp3aGlsZSAoKHRyeW4gPD0gbnVtdHJpZXMpICYgKGdvb2R0cnkgPT0gLTEpKSB7CiAgICAgdHJ5biA8LSB0cnluICsgMQogICAgIGVrdHJ4IDwtIHRyeSh4dHJhY3RvXzNEKCJlcmRsYXNGblRyYW42ZWt0cngiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcyksIHNpbGVudCA9IFRSVUUpCiAgICAgZWt0cnkgPC0gdHJ5KHh0cmFjdG9fM0QoImVyZGxhc0ZuVHJhbjZla3RyeSIsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKSwgc2lsZW50ID0gVFJVRSkKICAgICBpZiAoKCFjbGFzcyhla3RyeCkgPT0gInRyeS1lcnJvciIpICYgKCFjbGFzcyhla3RyeSkgPT0gInRyeS1lcnJvciIpKSB7CiAgICAgICBnb29kdHJ5IDwtIDEKICAgICAgIHVwd2VsbGluZyA8LSB1cHdlbGwoZWt0cngkZGF0YSwgZWt0cnkkZGF0YSwgMTUyKQogICAgIH0KICB9CgpgYGAKCgpBIHBsb3Qgb2YgdGhlIHJlc3VsdDoKCmBgYHtyIHBsb3RVcHcxLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNCwgZmlnLmFsaWduID0gJ2NlbnRlcicsIHdhcm5pbmcgPSBGQUxTRX0KcGxvdFVwd2VsbCh1cHdlbGxpbmcsIGFzLkRhdGUoZWt0cngkdGltZSkpCmBgYAoKVGhlIG9uZSB2ZXJ5IGxvdyBkb3dud2VsbGluZyB2YWx1ZSBkaXN0b3J0cyB0aGUgcGxvdCwgc28gdmFsdWVzIDwgLTUwMCBhcmUgc2V0IHRvIE5BIGFuZCB0aGUgZGF0YSBhcmUgcmVwbG90dGVkOgogCmBgYHtyIHBsb3RVcHczLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNCwgZmlnLmFsaWduID0gJ2NlbnRlcicsIHdhcm5pbmcgPSBGQUxTRX0KdGVtcFVwdyA8LSB1cHdlbGxpbmcKdGVtcFVwd1t0ZW1wVXB3IDwgLTUwMF0gPC0gTkEKcGxvdFVwd2VsbCh0ZW1wVXB3LCBhcy5EYXRlKGVrdHJ4JHRpbWUpKQpgYGAKCkluIDIwMDUgdGhlcmUgd2FzIGEgbGFyZ2UgZGllLW9mZiBvZiBzZWEgYW5pbWFscyBhbmQgbWFueSBmZWx0IHRoYXQgaXMgd2FzIGR1ZSB0byBhbiBhbm9tYWxvdXNseSBkZWxheWVkIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPnVwd2VsbGluZzwvc3Bhbj4uICBMZXQncyBjb21wYXJlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPnVwd2VsbGluZzwvc3Bhbj4gaW4gMjAwNSBhdCB0aGF0IGxvY2F0aW9uIHdpdGggPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+dXB3ZWxsaW5nPC9zcGFuPiBpbiAxOTc3OgoKCgpgYGB7ciBwbG90VXB3NzcsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA0LCBmaWcuYWxpZ24gPSAnY2VudGVyJywgd2FybmluZyA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKdHBvcyA8LSBjKCIxOTc3LTAxLTAxIiwgIjE5NzctMTItMzEiKQpla3RyeDc3IDwtIHh0cmFjdG9fM0QoImVyZGxhc0ZuVHJhbjZla3RyeCIsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKQpla3RyeTc3IDwtIHh0cmFjdG9fM0QoImVyZGxhc0ZuVHJhbjZla3RyeSIsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKQp1cHdlbGxpbmc3NyA8LSB1cHdlbGwoZWt0cng3NyRkYXRhLCBla3RyeTc3JGRhdGEsIDE1MikKIyByZW1vdmUgdGhlIG9uZSBiaWcgc3Rvcm0gYXQgdGhlIGVuZAp0ZW1wVXB3IDwtIHVwd2VsbGluZzc3CnRlbXBVcHdbdGVtcFVwdyA8IC01MDBdIDwtIE5BCnBsb3RVcHdlbGwodGVtcFVwdywgYXMuRGF0ZShla3RyeDc3JHRpbWUpKQpgYGAKCgpgYGB7ciBwbG90VXB3NzcxLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA0LCBmaWcuYWxpZ24gPSAnY2VudGVyJywgd2FybmluZyA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKdHBvcyA8LSBjKCIxOTc3LTAxLTAxIiwgIjE5NzctMTItMzEiKQpudW10cmllcyA8LSA1CnRyeW4gPC0gMApnb29kdHJ5IDwtIC0xCm9wdGlvbnMod2FybiA9IDIpCndoaWxlICgodHJ5biA8PSBudW10cmllcykgJiAoZ29vZHRyeSA9PSAtMSkpIHsKICAgICB0cnluIDwtIHRyeW4gKyAxCiAgICAgZWt0cng3NyA8LSB0cnkoeHRyYWN0b18zRCgiZXJkbGFzRm5UcmFuNmVrdHJ4IiwgeHBvcywgeXBvcywgdHBvcyA9IHRwb3MpLCBzaWxlbnQgPSBUUlVFKQogICAgIGVrdHJ5NzcgPC0gdHJ5KHh0cmFjdG9fM0QoImVyZGxhc0ZuVHJhbjZla3RyeSIsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKSwgc2lsZW50ID0gVFJVRSkKICAgICBpZiAoKCFjbGFzcyhla3RyeDc3KSA9PSAidHJ5LWVycm9yIikgJiAoIWNsYXNzKGVrdHJ5NzcpID09ICJ0cnktZXJyb3IiKSkgewogICAgICAgZ29vZHRyeSA8LSAxCiAgICAgICB1cHdlbGxpbmc3NyA8LSB1cHdlbGwoZWt0cng3NyRkYXRhLCBla3RyeTc3JGRhdGEsIDE1MikKICAgICAgICMgcmVtb3ZlIHRoZSBvbmUgYmlnIHN0b3JtIGF0IHRoZSBlbmQKICAgICAgIHRlbXBVcHcgPC0gdXB3ZWxsaW5nNzcKICAgICAgIHRlbXBVcHdbdGVtcFVwdyA8IC01MDBdIDwtIE5BCiAgICAgICBwbG90VXB3ZWxsKHRlbXBVcHcsIGFzLkRhdGUoZWt0cng3NyR0aW1lKSkKICAgICB9CiAgfQoKYGBgCgpXaGlsZSB3ZSBoYXZlIG5vdCBsb29rZWQgYXQgYWxsIGFyZWFzIGFsb25nIHRoZSBjb2FzdCwgYXQgbGVhc3QgZm9yIHRoaXMgbG9jYXRpb24gaXQgaXMgZGlmZmljdWx0IHRvIHNlZSB1cHdlbGxpbmcgaW4gMjAwNSBhcyBiZWluZyBhbnkgbW9yZSBkZWxheWVkIHRoYW4gaW4gMTk3NyB3aGVuIHRoZXJlIHdhcyBub3QgdGhlIHNhbWUgYWZmZWN0IG9uIGFuaW1hbHMuCgojIyMgQ29tcGFyaXNvbnMgb2YgV2luZCBTdHJlc3MKCkEgcHJvYmxlbSBmYWNlZCB3aGVuIHVzaW5nIHNhdGVsbGl0ZSBiYXNlZCBlc3RpbWF0ZXMgb2YgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+d2luZCBzdHJlc3M8L3NwYW4+IGlzIHRoYXQgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlF1aWtTQ0FUPC9zcGFuPiBiYXNlZCBlc3RpbWF0ZXMgc3RhcnQgaW4gMTk5OS0wNy0yMSBhbmQgZW5kIGluIDIwMDktMTEtMTksIHdoaWxlIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5BU0NBVDwvc3Bhbj4gYmFzZWQgZXN0aW1hdGVzIHN0YXJ0IGluIDIwMDktMTAtMDMgYW5kIGFyZSBvbmdvaW5nLiBBIGNvbXBhcmlzb24gb2YgdGhlIGRhdGEgb3ZlciB0aGUgcGVyaW9kIG9mIG92ZXJsYXAgd291bGQgYmUgb2YgaW50ZXJlc3QuCgpXZSBleGFtaW5lIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPm5vcnRoLXNvdXRoIHdpbmQgc3RyZXNzICh0YXV5KTwvc3Bhbj4gb3ZlciB0aGUgcGVyaW9kIG9mIG92ZXJsYXAgYXQgKDM2TiwgMTIxVykgKHdlIGdvIGEgbGl0dGxlIG1vcmUgb2Zmc2hvcmUgYmVjYXVzZSB0aGUgc2F0ZWxsaXRlIGRhdGEgaXMgbm90IHJlc29sdmVkIGNsb3NlIHRvIHRoZSBjb2FzdCksIGFuZCBjb21wYXJlIDMtZGF5IGNvbXBvc2l0ZXMgb2YgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+dGF1eTwvc3Bhbj4uIEluIHRoaXMgcmVnaW9uIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPnRhdXk8L3NwYW4+IGlzIHRoZSBtYWluIGNvbXBvbmVudCBvZiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj51cHdlbGxpbmc8L3NwYW4+IGFzIHRoZSBjb2FzdGxpbmUgcnVucyBhbG1vc3Qgbm9ydGgtc291dGggYW5kIHRoZSB3aW5kcyBhcmUgbW9zdGx5IGZyb20gdGhlIG5vcnRoIG9yIG5vcnRoLXdlc3QuCgpgYGB7ciBnZXRTdHJlc3MxLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnhwb3MgPC0gYygyMzcsIDIzNykKeXBvcyA8LSBjKDM2LCAzNikKdHBvcyA8LSBjKCIyMDA5LTEwLTA0IiwgIjIwMDktMTEtMTkiKQpudW10cmllcyA8LSA1CnRyeW4gPC0gMApnb29kdHJ5IDwtIC0xCm9wdGlvbnMod2FybiA9IDIpCndoaWxlICgodHJ5biA8PSBudW10cmllcykgJiAoZ29vZHRyeSA9PSAtMSkpIHsKICAgICB0cnluIDwtIHRyeW4gKyAxCiAgICAgYXNjYXQgPC0gdHJ5KHh0cmFjdG9fM0QoImVyZFFBc3RyZXNzM2RheXRhdXkiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcyksIHNpbGVudCA9IFRSVUUpCiAgICAgcXVpa3NjYXQgPC0gdHJ5KHh0cmFjdG9fM0QoImVyZFFTc3RyZXNzM2RheXRhdXkiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcyksIHNpbGVudCA9IFRSVUUpCiAgICAgaWYgKCghY2xhc3MoYXNjYXQpID09ICJ0cnktZXJyb3IiKSAmICghY2xhc3MocXVpa3NjYXQpID09ICJ0cnktZXJyb3IiKSkgewogICAgICAgZ29vZHRyeSA8LSAxCiAgICAgfQogIH0KCmBgYAoKCmBgYHtyIGdldFN0cmVzcywgZXZhbCA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKeHBvcyA8LSBjKDIzNywgMjM3KQp5cG9zIDwtIGMoMzYsIDM2KQp0cG9zIDwtIGMoIjIwMDktMTAtMDQiLCAiMjAwOS0xMS0xOSIpCmFzY2F0IDwtIHh0cmFjdG9fM0QoImVyZFFBc3RyZXNzM2RheXRhdXkiLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcykKcXVpa3NjYXQgPC0geHRyYWN0b18zRCgiZXJkUVNzdHJlc3MzZGF5dGF1eSIsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKQpgYGAKClBsb3R0aW5nIHRoZSByZXN1bHQKCmBgYHtyIHBsb3RzdHJlc3MsIGZpZy53aWR0aCA9IDYsICBmaWcuaGVpZ2h0ID0gNCwgZmlnLmFsaWduID0gJ2NlbnRlcicsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZShnZ3Bsb3QyKQphc2NhdFN0cmVzcyA8LSBkYXRhLmZyYW1lKHN0cmVzcyA9IGFzY2F0JGRhdGEsIHRpbWUgPSBhcy5EYXRlKGFzY2F0JHRpbWUpKQpxdWlrc2NhdFN0cmVzcyA8LSBkYXRhLmZyYW1lKHN0cmVzcyA9IHF1aWtzY2F0JGRhdGEsIHRpbWUgPSBhcy5EYXRlKHF1aWtzY2F0JHRpbWUpKQpzdHJlc3NDb21wYXJlIDwtIHJiaW5kKGFzY2F0U3RyZXNzLHF1aWtzY2F0U3RyZXNzKQpzdHJlc3NDb21wYXJlJHNhdCA8LSBjKHJlcCgiYXNjYXQiLCBucm93KGFzY2F0U3RyZXNzKSksIHJlcCgicXVpa3NjYXQiLCBucm93KHF1aWtzY2F0U3RyZXNzKSkpCnN0cmVzc0NvbXBhcmUkc2F0IDwtIGFzLmZhY3RvcihzdHJlc3NDb21wYXJlJHNhdCkKZ2dwbG90KGRhdGEgPSBzdHJlc3NDb21wYXJlLCBhZXModGltZSwgc3RyZXNzLCBjb2xvdXIgPSBzYXQpKSArIGdlb21fbGluZShuYS5ybSA9IFRSVUUpICsgdGhlbWVfYncoKQpgYGAKCldoaWxlIHRoZSB0d28gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+d2luZCBzdHJlc3M8L3NwYW4+IHNlcmllcyBhcmUgY2xvc2UsIHRoZXJlIGlzIGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBpbiB0aGUgbmVnYXRpdmUgKHNvdXRod2FyZCkgdmFsdWUgb24gT2N0b2JlciAyOCwgdGhhdCBpcyB0aGVyZSB3aWxsIGJlIGEgc2lnbmlmY2FudCBkaWZmZXJlbmNlIGluIHRoZSBlc3RpbWF0ZWQgKHBvc2l0aXZlKSB1cHdlbGxpbmcgdmFsdWUuICBDb21iaW5pbmcgdGhlIHNlcmllcyBpbnRvIGEgbG9uZ2VyIHNlcmllcyB3b3VsZCBiZSBkZXNpcmFibGUsIGJ1dCB0aGlzIHN1Z2dlc3RzIHRoYXQgdGhlcmUgY291bGQgYmUgcHJvYmxlbXMgd2l0aCBpbnRlci15ZWFyIGNvbXBhcmlzb25zIGR1ZSB0byB0aGUgZGlmZmVyZW50IHNvdXJjZXMgb2YgdGhlIGRhdGEuCgojIyMgVGFraW5nIGFkdmFudGFnZSBvZiAibGFzdCB0aW1lcyIgeyNsYXN0VGltZXN9CgpUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiBzZXJ2ZXIgdW5kZXJzdGFuZHMgYSB0aW1lIG9mICJsYXN0IiBhcyBiZWluZyB0aGUgbGFzdCB0aW1lIHBlcmlvZCBhdmFpbGFibGUgZm9yIHRoYXQgZGF0YXNldCwgYW5kIHBlcm1pdHMgYXJpdGhtZXRpYyB1c2luZyAibGFzdCIsIHRoYXQgaXMgImxhc3QtNSIgaXMgYSB2YWxpZCB0aW1lIGFuZCB3aWxsIGJlIDUgdGltZSBwZXJpb2RzIGJlZm9yZSB0aGUgbGFzdCB0aW1lIChub3RlIHRoYXQgc2luY2UgZGlmZmVyZW50IGRhdGFzZXRzIGhhdmUgZGlmZmVyZW50IHRpbWUgcGVyaW9kcyB0aGlzIGRpZmZlcmVuY2UgaXMgZGF0YXNldCBkZXBlbmRlbnQpLiBGb3IgZXhhbXBsZSB0byBleHRyYWN0IHRoZSBsYXN0IHNpeCBtb250aHMgb2YgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+U1NUPC9zcGFuPiBmcm9tIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlBPRVMgQVZIUlI8L3NwYW4+IHdlICBsb29rIGF0IHRoZSBtb250aGx5IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmFnc3N0bWRheTwvc3Bhbj4gZGF0YXNldDoKCmBgYHtyIGdldFBvZXMxLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnhwb3MgPC0gYygyMzUsIDI0MCkKeXBvcyA8LSBjKDM2LCAzOSkKdHBvcyA8LSBjKCJsYXN0LTUiLCAibGFzdCIpCm51bXRyaWVzIDwtIDUKdHJ5biA8LSAwCmdvb2R0cnkgPC0gLTEKb3B0aW9ucyh3YXJuID0gMikKd2hpbGUgKCh0cnluIDw9IG51bXRyaWVzKSAmIChnb29kdHJ5ID09IC0xKSkgewogICAgIHRyeW4gPC0gdHJ5biArIDEKICAgICBwb2VzIDwtIHRyeSh4dHJhY3RvXzNEKCJhZ3NzdGFtZGF5IiwgeHBvcywgeXBvcywgdHBvcyA9IHRwb3MpLCBzaWxlbnQgPSBUUlVFKQogICAgIGlmICghY2xhc3MocG9lcykgPT0gInRyeS1lcnJvciIpIHsKICAgICAgIGdvb2R0cnkgPC0gMQogICAgIH0KICB9CgoKYGBgCgoKYGBge3IgZ2V0UG9lcywgZXZhbCA9IEZBTFNFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKeHBvcyA8LSBjKDIzNSwgMjQwKQp5cG9zIDwtIGMoMzYsIDM5KQp0cG9zIDwtIGMoImxhc3QtNSIsICJsYXN0IikKcG9lcyA8LSB4dHJhY3RvXzNEKCJhZ3NzdGFtZGF5IiwgeHBvcywgeXBvcywgdHBvcyA9IHRwb3MpCgpgYGAKClRoZSByZXN1bHRzIGNhbiBiZSBhbmltYXRlZCB1c2luZyBgcGxvdEJCb3goKWA6CgpgYGB7ciBwbG90UG9lcywgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gNCwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnJlcXVpcmUocGxvdGRhcCkKcGxvdEJCb3gocG9lcywgcGxvdENvbG9yID0gJ3RlbXBlcmF0dXJlJywgIHRpbWUgPSBpZGVudGl0eSwgYW5pbWF0ZSA9IFRSVUUgKQoKYGBgCgojIyMgRXh0cmFjdGluZyBNdWx0aXBsZSwgbm9uLWNvbnNlY3V0aXZlIHRpbWUgcGVyaW9kcwoKV2hhdCBpZiB3ZSB3YW50IHRvIGV4dHJhY3QgZGF0YSBmcm9tIG5vbi1jb25zZWN1dGl2ZSB0aW1lIHBlcmlvZHMsIGZvciBleGFtcGxlIHRoZSBsYXN0IHNpeCBEZWNlbWJlcnMgZm9yIGFuIGFyZWE/IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj5TU1Q8L3NwYW4+IHdhcyB1bnVzdWFsbHkgd2FybSBvZmYgb2YgQ2F0YWxpbmEgSXNsYW5kIGluIERlYyAyMDE0LCBhbmQgd2Ugc2hvdyB0aGlzIGJ5IGNvbXBhcmluZyB0aGUgc2FtZSBkYXkgaW4gRGVjZW1iZXIgZm9yIHNpeCB5ZWFycywgMjAwOS0yMDE0OgoKYGBge3IgZ2V0Q2F0YWxpbmF9CgpyZXF1aXJlKHJlc2hhcGUyKQoKYWxseWVhcnMgPC0gTlVMTAp4cG9zIDwtIGMoMjM4LCAyNDMpCnlwb3MgPC0gYygzMCwgMzUpCmZvciAoeWVhciBpbiAyMDA5OjIwMTQpIHsKICB0cG9zIDwtIHJlcChwYXN0ZSh5ZWFyLCAnLTEyLTIwJywgc2VwID0gIiIpLCAyKQogIHRtcCA8LSB4dHJhY3RvXzNEKCdqcGxNVVJTU1Q0MVNTVCcsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKQojZ2dwbG90IGlzIHNlbnNpdHZlIHRoYXQgdGhlIGdyaWQgaXMgYWJzb2x1dGVseSByZWd1bGFyCiAgdG1wJGxhdGl0dWRlIDwtIHNlcShyYW5nZSh0bXAkbGF0aXR1ZGUpWzFdLCByYW5nZSh0bXAkbGF0aXR1ZGUpWzJdLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHRtcCRsYXRpdHVkZSkpCiAgdG1wJGxvbmdpdHVkZSA8LSBzZXEocmFuZ2UodG1wJGxvbmdpdHVkZSlbMV0sIHJhbmdlKHRtcCRsb25naXR1ZGUpWzJdLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHRtcCRsb25naXR1ZGUpKSAgCiAgZGltbmFtZXModG1wJGRhdGEpIDwtIGxpc3QobG9uZyA9IHRtcCRsb25naXR1ZGUsIGxhdCA9IHRtcCRsYXRpdHVkZSkgCiAgcmV0IDwtIG1lbHQodG1wJGRhdGEsIHZhbHVlLm5hbWUgPSAic3N0IikKICByZXQgPC0gY2JpbmQoZGF0ZSA9IHRtcCR0aW1lLCByZXQpCiAgYWxseWVhcnMgPC0gcmJpbmQoYWxseWVhcnMsIHJldCkKfQphbGx5ZWFycyRsb25nIDwtIGFsbHllYXJzJGxvbmcgLSAzNjAgCmBgYAoKUGxvdHRpbmcgdGhlIHJlc3VsdCB1c2luZyBmYWNldGluZyBpbiBnZ3Bsb3QyOgoKCmBgYHtyIHBsb3RDYXRhbGluYSwgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gNCwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnJlcXVpcmUoZ2dwbG90MikKcmVxdWlyZShtYXBkYXRhKQp4cG9zIDwtIHhwb3MgLSAzNjAKI2xvbmc8LWFsbHllYXJzJGxvbmdpdHVkZS0zNjAKI2xhdDwtYWxseWVhcnMkbGF0aXR1ZGUKY29hc3QgPC0gbWFwX2RhdGEoIndvcmxkSGlyZXMiLCB5bGltID0geXBvcywgeGxpbSA9IGMoLTEyMy41LCAtMTIwLikpCgpnZ3Bsb3QoZGF0YSA9IGFsbHllYXJzLCBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGZpbGwgPSBzc3QpKSArIAogIGdlb21fcG9seWdvbihkYXRhID0gY29hc3QsIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGZpbGwgPSAiZ3JleTgwIikgKwogIGdlb21fcmFzdGVyKGludGVycG9sYXRlID0gVFJVRSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSByZXYocmFpbmJvdygxMCkpLCBuYS52YWx1ZSA9IE5BKSArCiAgdGhlbWVfYncoKSArCiAgY29vcmRfZml4ZWQoMS4zLCB4bGltID0geHBvcywgeWxpbSA9IHlwb3MpICsgCiAgZmFjZXRfd3JhcCh+ZGF0ZSwgbnJvdyA9IDIpCgpgYGAKClRoaXMgZXhhbXBsZSB1c2VzIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5NVVIgKE11bHRpLXNjYWxlIFVsdHJhLWhpZ2ggUmVzb2x1dGlvbik8L3NwYW4+IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlNTVDwvc3Bhbj4sIGEgaGlnaCBxdWFsaXR5IHVsdHJhLWhpZ2ggcmVzb2x1dGlvbiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5TU1Q8L3NwYW4+IHByb2R1Y2VkIGJ5IHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5NSVNTVDwvc3Bhbj4gcHJvamVjdC4KCiMjIFVzaW5nIGB4dHJhY3RvZ29uYCAKClRoZSBgeHRyYWN0b2dvbmAgZnVuY3Rpb24gZXh0cmFjdHMgYSB0aW1lLXNlcmllcyBvZiBzYXRlbGxpdGUgZGF0YSB0aGF0IGFyZSB3aXRoaW4gYSB1c2VyIHN1cHBsaWVkIHBvbHlnb24uIEFzIGFuIGV4YW1wbGUsIHRoZSBib3VuZGFyeSBwb2ludHMgb2YgdGhlICBNb250ZXJleSBCYXkgTmF0aW9uYWwgTWFyaW5lIFNhbmN0dWFyeSBhcmUgYXZhaWxhYmxlIGluIHRoZSBgbWJubXNgIGRhdGFzZXQgd2hpY2ggYXJlIGxvYWRlZCB3aXRoIHRoZSBgeHRyYWN0b21hdGljYCBwYWNrYWdlLiAgVGhlIGBtYm5tc2AgZGF0YXNldCBjb250YWluZXMgdHdvIHZhcmlhYmxlcywgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPkxhdGl0dWRlPC9zcGFuPiBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPkxvbmdpdHVkZTwvc3Bhbj4sIHdoaWNoIGRlZmluZSB0aGUgYm91bmRhcmllcyBvZiB0aGUgc2FuY3R1YXJ5OiAgCgpgYGB7ciBtYm5tc30KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnN0cihtYm5tcykKYGBgCgpUaGUgZXhhbXBsZSBiZWxvdyBleHRyYWN0cyBhIHRpbWUtc2VyaWVzICBvZiBtb250aGx5IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPlZJSVJTPC9zcGFuPiBjaGxvcm9waHlsbCBkYXRhIHdpdGhpbiB0aGUgTUJOTVMuIAogCiAKCiAKYGBge3IgbWJubXNDaGxhLCBldmFsID0gRkFMU0V9CnJlcXVpcmUoeHRyYWN0b21hdGljKQp0cG9zIDwtIGMoIjIwMTQtMDktMDEiLCAiMjAxNC0xMC0wMSIpCnhwb3MgPC0gbWJubXMkTG9uZ2l0dWRlCnlwb3MgPC0gbWJubXMkTGF0aXR1ZGUKc2FuY3RjaGwgPC0geHRyYWN0b2dvbignZXJkVkgzY2hsYW1kYXknLCB4cG9zLCB5cG9zLCB0cG9zID0gdHBvcyApCnN0cihzYW5jdGNobCkKYGBgCgpgYGB7ciBtYm5tc0NobGExLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVxdWlyZSh4dHJhY3RvbWF0aWMpCnRwb3MgPC0gYygiMjAxNC0wOS0wMSIsICIyMDE0LTEwLTAxIikKI3Rwb3MgPC1hcy5EYXRlKHRwb3MpCnhwb3MgPC0gbWJubXMkTG9uZ2l0dWRlCnlwb3MgPC0gbWJubXMkTGF0aXR1ZGUKbnVtdHJpZXMgPC0gNQp0cnluIDwtIDAKZ29vZHRyeSA8LSAtMQpvcHRpb25zKHdhcm4gPSAyKQp3aGlsZSAoKHRyeW4gPD0gbnVtdHJpZXMpICYgKGdvb2R0cnkgPT0gLTEpKSB7CiAgICAgdHJ5biA8LSB0cnluICsgMQogICAgIHNhbmN0Y2hsIDwtIHRyeSh4dHJhY3RvZ29uKCdlcmRWSDNjaGxhbWRheScsIHhwb3MsIHlwb3MsIHRwb3MgPSB0cG9zKSwgc2lsZW50ID0gVFJVRSkKICAgICBpZiAoIWNsYXNzKHNhbmN0Y2hsKSA9PSAidHJ5LWVycm9yIikgewogICAgICAgZ29vZHRyeSA8LSAxCiAgICAgICBzdHIoc2FuY3RjaGwpCiAgICAgfQogIH0KCmBgYAoKCgpUaGUgZXh0cmFjdCAoc2VlYHN0cihzYW5jdGNobClgKSBjb250YWlucyB0d28gdGltZSBwZXJpb2RzIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmNobG9yb3BoeWxsPC9zcGFuPiBtYXNrZWQgZm9yIGRhdGEgb25seSBpbiB0aGUgc2FuY3R1YXJ5IGJvdW5kYXJpZXMuICBUaGUgZnVuY3Rpb24gYHBsb3RCQm94KClgIGNhbiBiZSB1c2VkIHRvIGFuaW1hdGUgdGhlIHR3byB0aW1lIHBlcmlvZHM6IAoKPHNwYW4gaWQ9ImFuaW1hdGUiPjwvc3Bhbj4KYGBge3IgbWJubXNDaGxhUGxvdCwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDUsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpyZXF1aXJlKCJwbG90ZGFwIikKbXlGdW5jIDwtIGZ1bmN0aW9uKHgpbG9nKHgpCnBsb3RCQm94KHNhbmN0Y2hsLCBwbG90Q29sb3IgPSAnY2hsb3JvcGh5bGwnLCBteUZ1bmMgPSBteUZ1bmMsIG5hbWUgPSAnbG9nKGNobGEpJywgdGltZSA9IGlkZW50aXR5LCBhbmltYXRlID0gVFJVRSkKYGBgCgoKVGhlIE1CTk1TIGlzIGZhbW91cyBmb3IgY29udGFpbmluZyB0aGUgTW9udGVyZXkgQ2FueW9uLCB3aGljaCByZWFjaGVzIGRlcHRocyBvZiB1cCB0byAzLDYwMCBtICgxMSw4MDAgZnQpIGJlbG93IHN1cmZhY2UgbGV2ZWwgYXQgaXRzIGRlZXBlc3QuIGB4dHJhY3RvZ29uYCBjYW4gZXh0cmFjdCB0aGUgYmF0aHltZXRyeSBkYXRhIGZvciB0aGUgTUJOTVMgZnJvbSB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVRPUE88L3NwYW4+IGRhdGFzZXQ6CgpgYGB7ciBtYm5tc0JhdGh5LCBldmFsID0gRkFMU0V9CnJlcXVpcmUoeHRyYWN0b21hdGljKQp0cG9zIDwtIGMoIjIwMTQtMDktMDEiLCAiMjAxNC0xMC0wMSIpCiN0cG9zIDwtYXMuRGF0ZSh0cG9zKQp4cG9zIDwtIG1ibm1zJExvbmdpdHVkZQp5cG9zIDwtIG1ibm1zJExhdGl0dWRlCmJhdGh5IDwtIHh0cmFjdG9nb24oJ0VUT1BPMTgwJywgeHBvcywgeXBvcykKc3RyKGJhdGh5KQpgYGAKCmBgYHtyIG1ibm1zQmF0aHkxLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZXJyb3IgPSBUUlVFfQpyZXF1aXJlKHh0cmFjdG9tYXRpYykKdHBvcyA8LSBjKCIyMDE0LTEwLTAxIiwgIjIwMTQtMTAtMDEiKQp4cG9zIDwtIHh0cmFjdG9tYXRpYzo6bWJubXMkTG9uZ2l0dWRlCnlwb3MgPC0gIHh0cmFjdG9tYXRpYzo6bWJubXMkTGF0aXR1ZGUKbnVtdHJpZXMgPC0gNQp0cnluIDwtIDAKZ29vZHRyeSA8LSAtMQpvcHRpb25zKHdhcm4gPSAyKQp3aGlsZSAoKHRyeW4gPD0gbnVtdHJpZXMpICYgKGdvb2R0cnkgPT0gLTEpKSB7CiAgICAgdHJ5biA8LSB0cnluICsgMQogICAgIGJhdGh5IDwtIHRyeSh4dHJhY3RvZ29uKCdFVE9QTzE4MCcsIHhwb3MsIHlwb3MpLCBzaWxlbnQgPSBUUlVFKQogICAgIGlmICghY2xhc3MoYmF0aHkpID09ICJ0cnktZXJyb3IiKSB7CiAgICAgICBnb29kdHJ5IDwtIDEKICAgICAgIHN0cihiYXRoeSkKICAgICB9CiAgfQoKYGBgCgpNYXBwaW5nIHRoZSBkYXRhIHRvIHNob3cgdGhlIGNhbnlvbjoKCmBgYHtyIG1ibm1zQmF0aHlQbG90LCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnJlcXVpcmUoInBsb3RkYXAiKQpwbG90QkJveChiYXRoeSwgcGxvdENvbG9yID0gJ2RlbnNpdHknLCBuYW1lID0gJ2RlcHRoJykKYGBgCgoKIyMgV2hhdCBoYXBwZW5zIHdoZW4geW91IHJlcXVlc3QgYW4gZXh0cmFjdAoKV2hlbiB5b3UgbWFrZSBhbiBgeHRyYWN0b21hdGljYCByZXF1ZXN0LCBwYXJ0aWN1bGFybHkgZm9yIHRyYWNrIGRhdGEgdXNpbmcgdGhlIGB4dHJhY3RvYCBmdW5jdGlvbiwgaXQgaXMgaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgd2hhdCBpcyBleHRyYWN0ZWQsIGJlY2F1c2UgdGhlIHJlbW90ZSBkYXRhc2V0IHJlcXVlc3RlZCBsaWtlbHkgd2lsbCBoYXZlIGEgZGlmZmVyZW50IHRlbXBvcmFsIGFuZCBzcGF0aWFsIHJlc29sdXRpb24gdGhlbiB0aGUgbG9jYWwgZGF0YXNldC4KClNwZWNpZmljYWxseSwgbGV0IGBsb25naXR1ZGVgLCBgbGF0aXR1ZGVgIGFuZCBgdGltZWAgYmUgdGhlIGNvb3JkaW5hdGUgc3lzdGVtIG9mIHRoZSByZW1vdGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiBkYXRhc2V0LCBhbmQgbGV0IGB4cG9zYCwgYHlwb3NgIGFuZCBgdHBvc2AgYmUgdGhlIGJvdW5kcyBvZiBhIHJlcXVlc3QuICBUaGVuIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+IHJlcXVlc3QgaXMgYmFzZWQgb24gdGhlIG5lYXJlc3QgZ3JpZCBwb2ludCBvZiB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiBkYXRhc2V0OgoKYGBge3IgbmVhckdyaWQsZXZhbD1GQUxTRX0KbGF0aXR1ZGVbd2hpY2gubWluKGFicyhsYXRpdHVkZSAtIHlwb3NbMV0pKV0gICMgbWluaW11bSBsYXRpdHVkZQpsYXRpdHVkZVt3aGljaC5taW4oYWJzKGxhdGl0dWRlIC0geXBvc1syXSkpXSAgIyBtYXhpbXVtIGxhdGl0dWRlCmxvbmdpdHVkZVt3aGljaC5taW4oYWJzKGxvbmdpdHVkZS0geHBvc1sxXSkpXSAjIG1pbmltdW0gbG9uZ2l0dWRlCmxvbmdpdHVkZVt3aGljaC5taW4oYWJzKGxvbmdpdHVkZSAtIHhwb3NbMl0pKV0gIyBtYXhpbXVtIGxvbmdpdHVkZQppc290aW1lW3doaWNoLm1pbihhYnModGltZS0gdHBvc1sxXSkpXSAjIG1pbmltdW0gdGltZQppc290aW1lW3doaWNoLm1pbihhYnModGltZSAtIHRwb3NbMl0pKV0gIyBtYXhpbXVtIHRpbWUKYGBgCgp3aGVyZSAgYHRwb3NgIGFuZCBgdGltZWAgaGF2ZSBiZWVuIGNvbnZlcnRlZCB0byBhbiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5SPC9zcGFuPiBkYXRlIGZvcm1hdCBzbyB0aGF0IGl0IGlzIGEgbnVtYmVyIHJhdGhlciB0aGFuIGEgc3RyaW5nLiBGb3IgZXhhbXBsZSwgdGhlIEZOTU9DIDYtaG91cmx5IEVrbWFuIHRyYW5zcG9ydHMgYXJlIG9uIGEgMS1kZWdyZWUgZ3JpZC4gQSByZXF1ZXN0IGZvciB0aGUgZGF0YSBhdCBhIGxvbmdpdHVkZSBvZiAyMjAuMiBhbmQgYSBsYXRpdHVkZSBvZiAzOC43IHdpbGwgcmV0dXJuIHRoZSByZXN1bHQgYXQgYSBsb25ndGl1ZGUgb2YgMjIwIGFuZCBhIGxhdGl0dWRlIG9mIDM5LgoKIyMgU2VsZWN0aW5nIGEgZGF0YXNldCB7I2RhdGFzZXR9CgpBcyBzZWVuIGluIHRoZSBleGFtcGxlcyBhYm92ZSwgdGhlIGZ1bmN0aW9ucyAgYHh0cmFjdG9tYXRpY2AsIGB4dHJhY3RvYCwgYHh0cmFjdG9fM0RgLCBhbmQgYHh0cmFjdG9nb25gIGFsbCB1c2UgdGhlIHBhcmFtZXRlciA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5kdHlwZTwvc3Bhbj4gdG8gc3BlY2lmeSB0aGUgY29tYmluYXRpb24gb2YgZGF0YXNldCBhbmQgcGFyYW1ldGVyIHRvIHVzZS4gVGhlIGRhdGFzZXRzIGFjY2Vzc2VkIGJ5IGB4dHJhY3RvbWF0aWNgIGFyZSBhIHN1YnNldCBvZiB0aGUgYWxtb3N0IDkwMCBkYXRhc2V0cyBhdmFpbGFibGUgb24gRVJE4oCZcyBFUkREQVAgc2VydmVyIGF0IGh0dHA6Ly9jb2FzdHdhdGNoLnBmZWcubm9hYS5nb3YvZXJkZGFwL2luZGV4Lmh0bWwuIFRoZSBkYXRhc2V0cyBjaG9zZW4gdG8gYmUgYWNjZXNzaWJsZSBieSBgeHRyYWN0b21hdGljYCBhcmUgdGhvc2UgdGhhdCBhcmUgdGhlIG1vc3QgdXNlZnVsIHRvIHRoZSBtYWpvcml0eSBvZiBvdXIgdXNlcnMuIFRoaXMgdmVyc2lvbiBvZiBgeHRyYWN0b21hdGljYCBhY2Nlc3NlcyAxNjUgZGF0YXNldHMuIEluZm9ybWF0aW9uIGFib3V0IGEgcGFydGljdWxhciBkYXRhc2V0IGlzIGF2YWlsYmxlIHZpYSB0aGUgYGdldEluZm9gIGZ1bmN0aW9uLiBgZ2V0SW5mb2AgdGFrZXMgZWl0aGVyIHRoZSBudW1iZXJlZCB2YWx1ZSBvZiBhIGRhdGFzZXQsIG9yIHRoZSBkYXRhdHlwZSBuYW1lICh0aGUgcGFyYW1ldGVyIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmR0eXBlbmFtZTwvc3Bhbj4pLCB3aGljaCBpcyB0aGUgZmlyc3QgaW5mb3JtYXRpb24gcmV0dXJuZWQgYnkgYGdldEluZm9gLiBgZ2V0SW5mb2AgZG9lcyBub3QgcmV0dXJuIHRoZSBudW1iZXIgb2YgdGhlIGRhdGFzZXQsIHRoZSB1c2Ugb2YgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmR0eXBlbmFtZTwvc3Bhbj4gIGlzIGVuY291cmFnZWQgcmF0aGVyIHRoYW4gdGhlIG51bWJlciBhc3NvY2lhdGVkIHdpdGggdGhlIGRhdGFzZXQuIEl0IGlzIHN0cm9uZ2x5IHJlY29tbWVuZGVkIHRoYXQgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPmR0eXBlPC9zcGFuPiBub3QgYmUgcGFzc2VkIGFzIGEgbnVtYmVyLCBhcyB0aGUgbnVtYmVycyBjYW4gY2hhbmdlIGFuZCB3aWxsIG5vdCBiZSB1c2VkIGluIHRoZSBuZXh0IHZlcnNpb24uCgpUaGVyZSBhcmUgYSBsYXJnZSBudW1iZXIgb2YgdGVtcG9yYWxseSBjb21wb3NpdGVkIGRhdGFzZXRzIGF2YWlsYWJsZS4gVGhlIHRlbXBvcmFsIGNvbXBvc2l0ZSB0aW1lIGludGVydmFsIGlzIHVzdWFsbHkgaW5kaWNhdGVkIGluIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5kdHlwZW5hbWU8L3NwYW4+IChmb3IgZXhhbXBsZSAicGhzc3RhOGRheSIgaXMgYW4gOC1kYXkgY29tcG9zaXRlKSwgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+ZGF0YXNldG5hbWU8L3NwYW4+IChmb3IgZXhhbXBsZSAiZXJkUEhzc3RhOGRheSIpIGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5sb25nbmFtZTwvc3Bhbj4gKGZvciBleGFtcGxlICJTU1QsIFBhdGhmaW5kZXIgVmVyIDUuMCwgRGF5IGFuZCBOaWdodCwgR2xvYmFsLCBTY2llbmNlIFF1YWxpdHkgKDggRGF5IENvbXBvc2l0ZSkiKSB2YXJpYWJsZXMgYXNzb2NpYXRlZCB3aXRoIGEgZGF0YXNldC4gVGhlIHRpbWUgaW50ZXJ2YWwgYmV0d2VlbiBkYXRhIHBvaW50cyBpbiB0aGUgdGltZS1zZXJpZXMgaXMgaW5kaWNhdGVkIGJ5IHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj50aW1lc3BhY2luZzwvc3Bhbj4gcGFyYW1ldGVyLiBGb3IgZXhhbXBsZSBub3RlIHRoZSBkaWZmZXJlbmNlIGluIHRpbWVzcGFjaW5nIGZvciA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5kdHlwZW5hbWU8L3NwYW4+PSJtaGNobGE4ZGF5IiBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+ZHR5cGVuYW1lPC9zcGFuPj0ibWJjaGxhOGRheSIsIHdoaWNoIGFyZSBib3RoIDgtZGF5IGNvbXBvc2l0ZXMuCgpgYGB7ciB0aW1lc3BhY2luZ30KZ2V0SW5mbygnbWhjaGxhOGRheScpIApnZXRJbmZvKCdtYmNobGE4ZGF5JykgCmBgYAoKVGhlIGRpZmZlcmVuY2UgaXMgdGhhdCB0aGUgbGF0dGVyIGRhdGFzZXQgKGBtYmNobGE4ZGF5YCkgaXMgYSBydW5uaW5nIDgtZGF5IGNvbXBvc2l0ZSwgc28gdGhlcmUgaXMgYSB2YWx1ZSBmb3IgZXZlcnkgZGF5LCB3aGlsZSB0aGUgZm9ybWVyIGRhdGFzZXQgKGBtaGNobGE4ZGF5YCkgY2FsY3VsYXRlcyBjb21wb3NpdGVzIGZvciBub24tb3ZlcmxhcHBpbmcgOC1kYXkgcGVyaW9kcy4KClRoZSBgc2VhcmNoRGF0YWAgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gc2VhcmNoIGZvciBhIHN1YnNldCBvZiB0aGF0IHNhdGlzZnkgY2VydGFpbiBjcml0ZXJpYSAobm90ZSB0aGF0IHRoZSBgc2VhcmNoRGF0YWAgaGFzIGNoYW5nZWQgc2luY2UgdGhlIHByZXZpb3VzIHZlcnNpb24pLiBgc2VhcmNoRGF0YWAgcmVxdWlyZXMgYSBsaXN0IG9mIG9mIGVudHJpZXMgb2YgdGhlIGZvcm0gInNlYXJjaFR5cGU6c2VhcmNoU3RyaW5nIiwgd2hlcmUgZWFjaCBlbGVtZW50IG9mICJ0aGUgZmlyc3QgaXRlbSBvZiAic2VhcmNoVHlwZSIiIGhhcyB0byBiZSBvbmUgb2YgYGR0eXBlbmFtZSwgZGF0YXNldG5hbWUsIGxvbmduYW1lLCB2YXJuYW1lYCwgYW5kICJzZWFyY2hTdHJpbmciIGlzIHRoZSBzdHJpbmcgdG8gc2VhcmNoIGZvciBpbiB0aGF0IGZpZWxkLiBGb3IgZXhhbXBsZSB0byBmaW5kIGFsbCBvZiB0aGUgZGF0YXNldHMgdGhhdCBoYXZlIGEgYHZhcm5hbWVgIHRoYXQgY29udGFpbnMgYGNobGAgYW5kIGEgYGRhdGFzZXRuYW1lYCB0aGF0IGNvbnRhaW5zIGBtZGF5YCAobW9zdCBtb250aGx5IGRhdGFzZXRzIGhhdmUgYG1kYXlgIGluIHRoZWlyIG5hbWUpOgoKYGBge3Igc2VhcmNoRGF0YSB9Cm15bGlzdCA8LSBsaXN0KCd2YXJuYW1lOmNobCcsICdkYXRhc2V0bmFtZTptZGF5JykKc2VhcmNoUmVzdWx0IDwtIHNlYXJjaERhdGEobXlsaXN0KQpgYGAKCndoaWNoIHJldHVybnMgYW1vbmcgb3RoZXJzIHRoZSB0aHJlZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5jaGxvcm9waHlsbDwvc3Bhbj4gZGF0YXNldHMgZXh0cmFjdGVkIGluIHRoZSBgeHRyYWN0b18zRGAgc2VjdGlvbi4gVGhlIHNlYXJjaCByZXN1bHQgY2FuIGJlIGVhc2lseSBwZXJ1c2VkIGJ5IHVzaW5nIGBWaWV3KClgIG9yIGJ5OgoKYGBge3J9CnJlcXVpcmUoIkRUIikKRFQ6OmRhdGF0YWJsZShzZWFyY2hSZXN1bHQpCmBgYAoKCklmIG9ubHkgZG9pbmcgdGhlIGZpcnN0IHNlYXJjaCB0aGUgY29kZSB3b3VsZCBiZToKCmBgYHtyIHNlYXJjaERhdGEyLCBldmFsID0gRkFMU0UgfQpteWxpc3QgPC0gbGlzdCgndmFybmFtZTpjaGwnKQpzZWFyY2hSZXN1bHQgPC0gc2VhcmNoRGF0YShteWxpc3QpCmBgYAoKYHNlYXJjaERhdGFgIGl0ZXJhdGl2ZWx5IHJlZmluZXMgdGhlIG1hdGNoZXMgaW4gdGhlIG9yZGVyIGdpdmVuIGJ5IHRoZSBsaXN0LCB0aHVzIGl0IGNhbiBiZSB2aWV3ZWQgYXMgZG9pbmcgYSBsb2dpY2FsICJBTkQiIG9uIHRoZSBtYXRjaGVzLgoKVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPmxvbmduYW1lPC9zcGFuPiBvZiBhIGRhdGFzZXQgb2Z0ZW4gY29udGFpbnMgYSBsb3Qgb2YgdGhpcyBpbmZvcm1hdGlvbi4gQWx0ZXJuYXRpdmVseSB5b3UgY2FuIGdlbmVyYXRlIGEgbGlzdCBvZiB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+ZGF0YXNldG5hbWU8L3NwYW4+IGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5sb25nbmFtZTwvc3Bhbj4gb2YgYWxsIG9mIHRoZSBkYXRhc2V0cyBhY2Nlc3NlZCBieSBgeHRyYWN0b21hdGljYCAod2Ugb25seSBzaG93IHRoZSBmaXJzdCBmaXZlIGxpbmVzIG9mIG91dHB1dCk6CgpgYGB7ciBlcmRkYXBTdHJ1Y3R9CmNhdChwYXN0ZTAoeHRyYWN0b21hdGljOjo6ZXJkZGFwU3RydWN0WyJkYXRhc2V0bmFtZSIsIDE6NV0sJzogJyAsIHh0cmFjdG9tYXRpYzo6OmVyZGRhcFN0cnVjdFsibG9uZ25hbWUiLCAxOjVdLCJcbiIpKQpgYGAKCmFuZCB0aGVuIHVzZSBgZ2V0SW5mb2Agd2l0aCB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+ZGF0YXNldG5hbWU8L3NwYW4+IHRvIHNlZSBhbGwgb2YgdGhlIGluZm9ybWF0aW9uIG9uIHRoYXQgZGF0YXNldAoKIyMjIFRvcG9ncmFwaGljIGRhdGFzZXRzCgpgeHRyYWN0b21hdGljYCBjYW4gZXh0cmFjdCBkYXRhIGZyb20gdHdvIHRvcG9ncmFwaGljIGRhdGFzZXRzICg8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FVE9QTzwvc3Bhbj4pICAsIHdpdGggPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPmR0eXBlbmFtZTwvc3Bhbj4gb2YgIkVUT1BPMTgwIiBhbmQgIkVUT1BPMzYwIiwgd2hpY2ggaGF2ZSBsb25naXR1ZGVzIHJhbmdpbmcgZnJvbSAtMTgwIHRvIDE4MCBhbmQgMCB0byAzNjAgcmVzcGVjdGl2ZWx5LiBDdXJyZW50bHkgdGhlIGBnZXRJbmZvYCBhbmQgYHNlYXJjaERhdGFgIGNvbW1hbmRzIGRvIG5vdCB3b3JrIHdpdGggdGhlc2UgdHdvIGRhdGFzZXRzLCBidXQgYHh0cmFjdG9gLCBgeHRyYWN0b18zRGAsIGFuZCBgeHRyYWN0b2dvbmAgZG8uCgoKCiMjIFdoYXQgaGFzIENoYW5nZWQgYW5kIFN0ZXBzIEdvaW5nIEZvcndhcmQgeyNuZXd9CgpUaGUgW2VhcmxpZXIgc2VjdGlvbl0oI3JlZmFjdG9yKSBjb3ZlcnMgdGhlIGNoYW5nZXMgdG8gdGhlIGFyZ3VtZW50cyB0byB0aGUgZnVuY3Rpb25zLiAgVGhlIGV4YW1wbGVzIHNob3VsZCBtYWtlIGNsZWFyIGhvdyB0aGUgZnVuY3Rpb24gY2FsbHMgbm93IHdvcmsuCgpUaGUgZnVuY3Rpb24gYHNlYXJjaERhdGEoKWAgbm8gbG9uZ2VyIHVzZXMgYSBsaXN0IG9mIGxpc3QsIGluc3RlYWQgaXQgdXNlcyBhIHNpbXBsZSBsaXN0IHdoZXJlIHRoZSBlYWNoICJzZWFyY2hUeXBlIiBhbmQgInNlYXJjaFN0cmluZyIgYXJlIHNldCBvZmYgYnkgYSBjb2xvbiBhcyBpbiAic2VhcmNoVHlwZTpzZWFyY2hTdHJpbmciLgoKSW4gdGhpcyBhbmQgYWxsIHBhc3QgdmVyc2lvbiBvZiBgeHRyYWN0b21hdGljYCwgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRhdGFzZXRzIHdhcyBidWlsdCBpbnRvIHRoZSBmdW5jdGlvbnMuICBJbiB0aGUgcHJlc2VudCBjYXNlIHRoZSBkYXRhc2V0IGluZm9ybWF0aW9uIGlzIHN0b3JlZCBpbiBhIGRhdGFmcmFtZSBjYWxsZWQgYGVyZGRhcFN0cnVjdGAgd2hpY2ggaXMgYXVvdG1hdGljYWxseSBsb2FkZWQgd2hlbiB5b3UgbG9hZGVkIHRoZSBwYWNrYWdlLgoKVGhpcyBoYXMgdGhlIGRpc2FkdmFudGFnZSB0aGF0IGl0IGlzIHRpZWQgdG8gc3BlY2lmaWMgZGF0YXNldHMgYW5kIGEgc3BlY2lmYyA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5FUkREQVA8L3NwYW4+IHNlcnZlciwgcmF0aGVyIHRoYW4gd29ya2luZyB3aXRoIGFueSBkYXRhc2V0IHNlcnZlZCBieSBhbnkgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPi4gIAoKVGhlIGRhdGFzZXQgd2FzIHNwZWNpZmllZCBieSB0aGUgYGR0eXBlYCwgd2hpY2ggY291bGQgYmUgZWl0aGVyIGEgbnVtYmVyIG9yIGEgY2hhcmFjdGVyIHN0cmluZywgYW5kIHJlZmVyZW5jZXMgdGhlIGluZm9ybWF0aW9uIGluIHRoYXQgZGF0YWZyYW1lLiAgVGhlIGBkdHlwZWAgbmFtZXMgaW4gdGhpcyB2ZXJzaW9uIGZvciB0aGUgbW9zdCBwYXJ0IGFyZSBzYW1lLCAqKkJVVCBgZHR5cGVgIEFTICBOVU1CRVJTIEFSRSBOTyBMT05HRVIgU1VQUE9SVEVEKiouIElmIHlvdSBoYXZlIHVzZWQgYHh0cmFjdG9tYXRpY2AgYmVmb3JlIHlvdSBuZWVkIHRvIGNoZWNrIHRoYXQgdGhlIGBkdHlwZWAgaW5mb3JtYXRpb24gdGhhdCB5b3UgYXJlIHBhc3NpbmcgaXMgd2hhdCB5b3UgdGhpbmsgaXQgaXMuICBZb3UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIHN1cHBsaWVkIGBnZXRJbmZvKClgIG9yIGBzZWFyY2hEYXRhKClgIGZ1bmN0aW9ucyBvciBwZXJ1c2UgdGhlIGBlcmRkYXBTdHJ1Y3RgIGRhdGFmcmFtZS4KVGhlcmUgYXJlIG1hbnkgbW9yZSBkYXRhc2V0cyBhdmFpbGFibGUgaW4gdGhpcyB2ZXJzaW9uLCBhbmQgbm93ICJsYXN0IiBjYW4gYmUgdXNlZCBhcyB0aW1lIHRvIGdldCB0aGUgbGFzdCB0aW1lIHBlcmlvZCBhdmFpbGFibGUgZm9yIHRoYXQgZGF0YXNldC4KCkV2ZXJ5IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkVSRERBUDwvc3Bhbj4gZGF0YXNldCBoYXMgYW4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+RVJEREFQPC9zcGFuPiAiZGF0YXNldCBJRCIsIGFzIHdlbGwgb25lIG9yIG1vcmUgdmFyaWFibGVzIGFzc29jaWF0ZWQgd2l0aCB0aGF0ICJkYXRhc2V0IElEIi4gU29tZSBkYXRhc2V0cywgc3VjaCBhcyB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+QVNDQVQgd2luZHM8L3NwYW4+IGhhdmUgbXVsdGlwbGUgdmFyaWFibGVzIGFzc29jaWF0ZWQgd2l0aCB0aGUgZGF0YXNldDoKCjxodHRwczovL2NvYXN0d2F0Y2gucGZlZy5ub2FhLmdvdi9lcmRkYXAvZ3JpZGRhcC9lcmRRQXN0cmVzczFkYXkuaHRtbD4uCgpUaGlzIGluZm9ybWF0aW9uIGlzIGNvbnRhaW5lZCBpbiBgZXJkZGFwU3RydWN0JGRhdGFzZXRuYW1lYCBhbmQgYGVyZGRhcFN0cnVjdCR2YXJuYW1lYC4gU2luY2UgYSBzaW5nbGUgZGF0YXNldCBtYXkgaGF2ZSBtb3JlIHRoYW4gb25lIHZhcmlhYmxlLCAgYGR0eXBlYCBhcyBjaGFyYWN0ZXIgc3RyaW5nIGlzIGEgd2F5IHRvIGhhdmUgYSB1bmlxdWUgZGF0YXNldCBpZCBhbmQgdmFyaWFibGUgbmFtZSBpbiBhIHNpbmdsZSB2YXJpYWJsZS4gIEEgbW9yZSBnZW5lcmFsIHZlcnNpb24gb2YgYHh0cmFjdG9tYXRpY2AsICBjYWxsZWQgYHJlcmRkYXBYdHJhY3RvYCwgaXMgdW5kZXIgZGV2ZWxvcG1lbnQuICBgcmVyZGRhcFh0cmFjdG9gIHdpbGwgd29yayB3aXRoIGFueSBncmlkZGVkIGRhdGFzZXQgb24gYW55IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQiPkVSRERBUDwvc3Bhbj4gIGJ5IHVzaW5nIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5ST3BlblNjaTwvc3Bhbj4gcGFja2FnZSBgcmVyZGRhcGAuIEluZm9ybWF0aW9uIG9uIGByZXJkZGFwWHRyYWN0b2AgY2FuIGJlIGZvdW5kIGF0OgoKPGh0dHBzOi8vZ2l0aHViLmNvbS9ybWVuZGVscy9yZXJkZGFwWHRyYWN0bz4K