Dataset
While we have seen that biological data can be obtained from sites such as Dryad and GBIF, we need to obtain data with specific variables that might not be available on those sites. So to start this exercise, we will download a *.csv file containing information for Ophiophagus hannah (King Cobra) from movebank.org that contains the appropriate variable to track movement from radio transmitter data. Because this data has to be downloaded we will need to go through the process of importing this information using the read.csv()
command. Once we have imported the data we can view the structure.
If forked, this data will be available in the Data folder within the repository. Additionally, ESRI Shapefile and Google KMZ formats will be included if you are interested in working with those data types.
Import Dataset with read.csv
data <- read.csv("./Data/ophiophagus_hannah.csv")
Notice that this dataset contains more than species information and x,y coordinates. While that information is important, this dataset also contains time and date information (timestamp), sensor type, and UTM data. In order to be sure that the dataset contains no outliers, we can plot the data using ggplot
and plotly
to interactively view the data.
qaqc_plot <- ggplot() + geom_point(data=data,
aes(utm.easting,utm.northing,
color=individual.local.identifier)) +
labs(x="Easting", y="Northing") +
guides(color=guide_legend("Identifier"))
ggplotly(qaqc_plot)
With plotly
, similar to leaflet
, we have the ability to examine the spread of the data points and additional information from various columns. From this plot we can see that there are two individuals, OPHA1 and OPHA2, that were tracked for this study and have very little overlap in their apparent range.
While we could continue with the current dataset, any analysis would calculate home range for the entire population rather than the individual.
So here we will create a function and use the lapply
command to apply a function over a list or vector dataset. Specifically, this function will take the original dataset, split it into separate files based on the individual identifier, and create new *.csv files using the identifier as the filename.
lapply(split(data, data$individual.local.identifier),
function(x)write.csv(x, file = paste(x$individual.local.identifier[1],".csv", sep = ""), row.names = FALSE))
The anatomy of the function is as follows:
lapply()
, apply the function over a list
split()
, separates the data
function()
, compose a series of steps to be applied to the data
write.csv()
, write a csv file
paste()
, create a character string to be used for the file name
If you examine the root folder you will now find the addition of two new *.csv files: OPHA1 and OPHA2. We will use these files to run our home range analyses. Alternatively, if you are provided a number of individual *.csv files and need to quickly import the data into separate data frames you could write a for loop that would look something like this:
list <- gsub("\\.csv$","", list.files(pattern="\\.csv$"))
for(i in list){
assign(i, read.csv(paste(i, ".csv", sep="")))
}
All of the *.csv files in your root directory will now be available in your global environment. However, since we used a looping function to create individual *.csv files, we will need to make a list of those individual files created in the previous section to refer back to during the analysis when necessary.
files <- list.files(path = ".", pattern = "[OPHA]+[0-9]+", full.names = TRUE)
In the list.files
command above, path = "."
informs the locations, in this case the root directory, pattern =
describes the way the files are named, in this case OPHA followed by a number between 0-9, and full.names
describes how the files will be listed.
Analysis
To date, this will be one of the most code intensive exercises we have attempted. While the code itself is not difficult, the implementation is tedious due to the varying data types and analyses performed.
Imagery
As we have in previous exercises we will use raster imagery to provide additional spatial detail to the analysis. While this information can come from a number of different sources, we will again use openstreetmap
for this example. For those using Macs with java restrictions, use your preferred source for imagery.
In order to create a bounding box for the imagery we will use the min
and max
values from the coordinates in the dataset to compute those points. Although the data obtained from www.movebank.org contained both longitude/latitude and UTM values, this example will detail how to convert UTM to longitude/latitude coordinates in order to use openstreetmap
, and the raster imagery from longitude/latitude to UTM. This will be useful if you receive data only with UTM values. If your data contains longitude/latitude values there is no conversion required.
utm_points <- cbind(data$utm.easting, data$utm.northing)
utm_locations <- SpatialPoints(utm_points,
proj4string=CRS("+proj=utm +zone=47 +datum=WGS84"))
proj_lat.lon <- as.data.frame(spTransform(
utm_locations, CRS("+proj=longlat +datum=WGS84")))
colnames(proj_lat.lon) <- c("x","y")
raster <- openmap(c(max(proj_lat.lon$y)+0.01, min(proj_lat.lon$x)-0.01),
c(min(proj_lat.lon$y)-0.01, max(proj_lat.lon$x)+0.01),
type = "bing")
raster_utm <- openproj(raster,
projection = "+proj=utm +zone=47 +datum=WGS84 +units=m +no_defs")
In the script above, utm_point
is an x,y derived from the primary dataset, utm_locations
set the projection to UTM Zone 47, proj_lat.lon
converted the UTM points to longitude/latitude, raster
uses the min/max x,y data to create a bounding box to retrieve the aerial imagery, and raster_utm
reprojected the imagery back to UTM Zone 47 consistent with the location in Thailand. Now we can use autoplot.OpenStreetMap
to display the raster image file with the UTM locations as an overlay.
autoplot.OpenStreetMap(raster_utm, expand = TRUE) + theme_bw() +
theme(legend.position="bottom") +
theme(panel.border = element_rect(colour = "black", fill=NA, size=1)) +
geom_point(data=data, aes(utm.easting,utm.northing,
color=individual.local.identifier), size = 3, alpha = 0.8) +
theme(axis.title = element_text(face="bold")) + labs(x="Easting",
y="Northing") + guides(color=guide_legend("Identifier"))
Home Range Analysis
There are three basic types of home range analyses we will perform in this exercise: Minimum Convex Polygon (MCP), Kernel-Density Estimation (KDE), and Brownian Bridge Movement Model (BB). There are a number of different tuning parameters that can be applied these analyses, however in this exercise we will use the most basic versions of the analysis.
In the section above we use the lapply
command to loop a function used to separate the original dataset into individual files. This is a useful tool, however, when the function loops through dozens or even hundreds of files, the process can take a long period of time to complete. Using the pblapply
command adds a progress bar (i.e. pb) to the process which provides an estimated time for completion of the function. We will use a similar process to the one above, using the pblapply
command to run MCP analysis on the individual *.csv files.
Minimum Convex Polygon
A description of the steps within the following code will be discussed following the output. The process works by establishing the function and then running pblapply
referring back to the files we created in the dataset portion of this exercise.
mcp_raster <- function(filename){
data <- read.csv(file = filename)
x <- as.data.frame(data$utm.easting)
y <- as.data.frame(data$utm.northing)
xy <- c(x,y)
data.proj <- SpatialPointsDataFrame(xy,data, proj4string = CRS("+proj=utm +zone=12 +datum=WGS84 +units=m +no_defs"))
xy <- SpatialPoints(data.proj@coords)
mcp.out <- mcp(xy, percent=100, unout="ha")
mcp.points <- cbind((data.frame(xy)),data$individual.local.identifier)
colnames(mcp.points) <- c("x","y", "identifier")
mcp.poly <- fortify(mcp.out, region = "id")
units <- grid.text(paste(round(mcp.out@data$area,2),"ha"), x=0.85, y=0.95,
gp=gpar(fontface=4, col="white", cex=0.9), draw = FALSE)
mcp.plot <- autoplot.OpenStreetMap(raster_utm, expand = TRUE) + theme_bw() + theme(legend.position="none") +
theme(panel.border = element_rect(colour = "black", fill=NA, size=1)) +
geom_polygon(data=mcp.poly, aes(x=mcp.poly$long, y=mcp.poly$lat), alpha=0.8) +
geom_point(data=mcp.points, aes(x=x, y=y)) +
labs(x="Easting (m)", y="Northing (m)", title=mcp.points$identifier) +
theme(legend.position="none", plot.title = element_text(face = "bold", hjust = 0.5)) +
annotation_custom(units)
mcp.plot
}
pblapply(files, mcp_raster)
| | 0 % ~calculating
|=============================================| 100% elapsed=12s
[[1]]
[[2]]
The anatomy of the function above is as follows: mcp_raster <- function(filename){...
for each file listed from the project directory complete the following commands
read.csv()
, read the data from a given *.csv file
- xy, create a coordinate file of the easting and northing data
SpatialPointsDataFrame()
, project the data to UTM Zone 47
mcp()
, computes home range using the Minimum Convex Polygon estimator
fortify()
, turns a map into a data frame for plotting with ggplot2
grid.text()
, creates annotations for the map; in this case to show area
autoplot.OpenStreetMap()
, plotting for raster and vector data in ggplot2
pblapply()
, referencing the files list and function
The rest of the information can be found by examing the help menu for the various commands or looking at the display options for each portion of the script.
Kernel-Density Estimation
The same design above can be used to create KDE plots for each individual in the dataset. Because the process is essentially the same, only portions of the function that were not previously described will be discussed.
kde_raster <- function(filename){
data <- read.csv(file = filename)
x <- as.data.frame(data$utm.easting)
y <- as.data.frame(data$utm.northing)
xy <- c(x,y)
data.proj <- SpatialPointsDataFrame(xy,data, proj4string = CRS("+proj=utm +zone=47 +datum=WGS84 +units=m +no_defs"))
xy <- SpatialPoints(data.proj@coords)
kde<-kernelUD(xy, h="href", kern="bivnorm", grid=100)
ver <- getverticeshr(kde, 95)
kde.points <- cbind((data.frame(data.proj@coords)),data$individual.local.identifier)
colnames(kde.points) <- c("x","y","identifier")
kde.poly <- fortify(ver, region = "id")
units <- grid.text(paste(round(ver$area,2)," ha"), x=0.85, y=0.95,
gp=gpar(fontface=4, col="white", cex=0.9), draw = FALSE)
kde.plot <- autoplot.OpenStreetMap(raster_utm, expand = TRUE) + theme_bw() + theme(legend.position="none") +
theme(panel.border = element_rect(colour = "black", fill=NA, size=1)) +
geom_polygon(data=kde.poly, aes(x=kde.poly$long, y=kde.poly$lat), alpha = 0.8) +
geom_point(data=kde.points, aes(x=x, y=y)) +
labs(x="Easting (m)", y="Northing (m)", title=kde.points$identifier) +
theme(legend.position="none", plot.title = element_text(face = "bold", hjust = 0.5)) +
annotation_custom(units)
kde.plot
}
pblapply(files, kde_raster)
| | 0 % ~calculating
|======================= | 50% ~01s
|=============================================| 100% elapsed=01s
[[1]]
[[2]]
The anatomy of the function above is as follows: kde_raster <- function(filename){...
for each file listed from the project directory complete the following commands
kernelUD()
, estimation of kernel home-range
getverticeshr()
, extract home-range contour
The rest of the information can be found by examing the help menu for the various commands or looking at the display options for each portion of the script.
Brownian Bridge Movement
Although this appears to be working backwards, in the previous two examples you have seen how to create a function that can be looped to run the analysis for a list fo files. For this analysis, we will create a conventional script for a single individual. Portions of the script that were not previously described will be discussed following the analysis.
OPHA1 <- read.csv("OPHA1.csv")
date <- as.POSIXct(strptime(as.character(OPHA1$timestamp),"%Y-%m-%d %H:%M:%S", tz="Asia/Bangkok"))
OPHA1$date <- date
OPHA1.reloc <- cbind.data.frame(OPHA1$utm.easting, OPHA1$utm.northing,
as.vector(OPHA1$individual.local.identifier),
as.POSIXct(date))
colnames(OPHA1.reloc) <- c("x","y","id","date")
trajectory <- as.ltraj(OPHA1.reloc, date=date, id="OPHA1")
sig1 <- liker(trajectory, sig2 = 58, rangesig1 = c(0, 5), plotit = FALSE)
opha.traj <- kernelbb(trajectory, sig1 = .7908, sig2 = 58, grid = 100)
bb_ver <- getverticeshr(opha.traj, 95)
bb_poly <- fortify(bb_ver, region = "id",
proj4string = CRS("+proj=utm +zone=47+
datum=WGS84 +units=m +no_defs"))
colnames(bb_poly) <- c("x","y","order","hole","piece","id","group")
bb_image <- crop(opha.traj, bb_ver,
proj4string = CRS("+proj=utm +zone=47 +
datum=WGS84 +units=m +no_defs"))
bb_units <- grid.text(paste(round(bb_ver$area,2)," ha"), x=0.85, y=0.95,
gp=gpar(fontface=4, col="white", cex=0.9), draw = FALSE)
bb.plot <- autoplot.OpenStreetMap(raster_utm, expand = TRUE) + theme_bw() + theme(legend.position="none") +
theme(panel.border = element_rect(colour = "black", fill=NA, size=1)) +
geom_tile(data=bb_image,
aes(x=bb_image@coords[,1], y=bb_image@coords[,2],
fill = bb_image@data$ud)) +
geom_polygon(data=bb_poly, aes(x=x, y=y, group = group), color = "black", fill = NA) +
scale_fill_viridis_c(option = "inferno") + annotation_custom(bb_units) +
labs(x="Easting (m)", y="Northing (m)", title="OPHA1") +
theme(legend.position="none", plot.title = element_text(face = "bold", hjust = 0.5))
The anatomy of the script above is as follows:
as.POSIXct()
, manipulate objects to represent calendar dates and times
cbind.data.frame()
, used to combine columns avoiding factorization
as.ltraj
, convert data to trajectory class
liker()
, used to find the maximum likelihood estimation of the parameter sig1 in kernelbb()
scale_fill_viridis_c()
, create color scale for continuous data
The rest of the information can be found by examing the help menu for the various commands or looking at the display options for each portion of the script.
Try it yourself! Using the example script above, and following the example functions, create a looping function with pblapply
to run the BB analysis for each individual in the files list.
Animate Trajectory Data
The final analysis we will perform in this exercise is a visual animation of the relocation data. Using the data from above you can plot(trajectory)
and see a plot of the relocation information for an individual. However, we can use the move
and moveVis
packages to create an animation of the relocations.
We need to begin by creating a move object containing relocations (x,y), time and date information (time), a projection string, individual identifier (animal), and sensor type (sensor). See the description for move()
in the help menu for optional information.
opha.move <- move(x=OPHA1$location.long,
y=OPHA1$location.lat,
time=as.POSIXct(OPHA1$timestamp,
format="%Y-%m-%d %H:%M:%S", tz="Asia/Bangkok"),
proj=CRS("+proj=longlat +ellps=WGS84 +datum=WGS84"),
data=OPHA1, animal=OPHA1$individual.local.identifier,
sensor=OPHA1$sensor.type)
With this information, we can now create the timing, number of frames, and animation for the relocations. First, to understand the time lag between the relocations we can use median(timeLag(opha.move, unit = "mins"))
to calculate the median value. However, in the aling_move()
function ther is an option to set the uniform scale manually or by using a fixed resolution: min, max, or mean. For simplicity, we will use the max temporal resolution.
With the data now on a uniform time scale we can create the frames and animation for the relocations. For this step I will use a basemap from MapBox which require token access through the use of their API. To do this you need to register with MapBox and create an access token. Then create a .Renviron file in your project folder. Copy the token information from MapBox and create an object in the .Renviron file such as map_token = 'paste token here'
and add map_token = Sys.getenv('map_token')
to the script below. However using the get_maptypes()
script you can see there are various map services and map types that can be used. A simple output would be to use map_service = 'osm'
(OpenStreetMaps) and map_type = 'topographic'
or other map types available by viewing get_maptypes('owm')
. when using a basemap without token access the map_token
option can be removed from the script below.
Warning, the number of frames, frames/second, and output file type will determine how long this process will take and how large the output file will be.
frames <- frames_spatial(movement, path_colours = "red",
map_service = "mapbox",
map_type = "satellite",
map_token = Sys.getenv('map_token'),
alpha = 0.5) %>%
add_labels(x = "Longitude", y = "Latitude") %>%
add_northarrow() %>%
add_scalebar(distance = 2) %>%
add_timestamps(movement, type = "label") %>%
add_progress()
animate_frames(frames, fps = 5, overwrite = TRUE,
out_file = "./moveVis-2021fps.gif")
As you can see from the result above, even a ~12sec clip with <60 frames took nearly 5min to key, obtain base imagery, assign frames, and render. So keep that in mind when creating these animations. The output from this is stored in your root directory and can be used as an animation on other programs or in html documents.
LS0tDQp0aXRsZTogSG9tZSBSYW5nZSBBbmFseXNpcyA8YnI+PHNtYWxsPkFkdmFuY2VkIERhdGEgQW5hbHl0aWNzPC9zbWFsbD48L2JyPg0KYXV0aG9yOiAiQXVzdGluIFBlYXkgU3RhdGUgVW5pdmVyc2l0eSINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICByb3dzLnByaW50OiAxMA0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIGhpZ2hsaWdodDogYnJlZXplZGFyaw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IG5vDQogICAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHJvd3MucHJpbnQ6IDEwDQogICAgdGhlbWU6IGNvc21vDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCiAgbW9kZTogZ2ZtDQotLS0NCg0KYGBgez1odG1sfQ0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KaDEudGl0bGUgew0KICBmb250LXNpemU6IDQwcHg7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogQmxhY2s7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCg0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgZm9udC1zaXplOiAyNXB4Ow0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiAjRDAyMzQ5Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQoNCmJvZHkgew0KICBmb250LWZhbWlseTogSGVsdmV0aWNhOw0KICBmb250LXNpemU6IDEycHQ7DQp9DQoNCi56b29tIHsNCiAgdHJhbnNmb3JtLW9yaWdpbjogNDAlIDUwJSAwOw0KICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gLjJzOw0KICBtYXJnaW46IDAgYXV0bzsNCn0NCi56b29tIGltZ3sNCgl3aWR0aDphdXRvOw0KCWhlaWdodDphdXRvOwkNCn0NCi56b29tOmhvdmVyIHsNCiAgdHJhbnNmb3JtOiBzY2FsZSgyKTsNCn0NCg0KdGgsIHRkIHtwYWRkaW5nOiA1cHg7fQ0KDQo8L3N0eWxlPg0KYGBgDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoaXMgZXhlcmNpc2Ugd2lsbCBidWlsZCBvbiB0aGUgc2tpbGxzIGFjcXVpcmVkIGluIHRoZSBbTWFwcGluZyBCYXNpY3NdKGh0dHBzOi8vY2hyaXNtZ2VudHJ5LmdpdGh1Yi5pby9NYXBwaW5nLUJhc2ljcy8pIGFuZCBbU3BlY2llcyBEaXN0cmlidXRpb24gTW9kZWxpbmddKGh0dHBzOi8vY2hyaXNtZ2VudHJ5LmdpdGh1Yi5pby9EaXN0cmlidXRpb24tTWFwcy8pIGV4ZXJjaXNlcyB0byBwZXJmb3JtICoqSG9tZSBSYW5nZSBBbmFseXNpcyoqIGFuZCBtYXAgdGhlIHJlc3VsdHMuIFdlIHdpbGwgc3RhcnQgYnkgZXhhbWluaW5nIHRoZSBzdHJ1Y3R1cmUgb2YgZGF0YSB1c2VkIGluIGhvbWUgcmFuZ2UgYW5hbHlzZXMsIHdlIHdpbGwgbG9vayBhdCB3YXlzIHRvIHBlcmZvcm0gUUEvUUMgY2hlY2tzIG9uIHRoZSBsb2NhdGlvbiBkYXRhLCBhbmQgZmluYWxseSB2YXJpb3VzIHdheXMgdG8gdmlzdWFsaXplIHRoZSBkYXRhLiBPbmUgb2YgdGhlIG5ldyBza2lsbHMgd2Ugd2lsbCBkaXNjdXNzIGluIHRoaXMgZXhlcmNpc2UgaXMgdG8gY3JlYXRlIGZ1bmN0aW9ucyBhbmQgbG9vcHMgdG8gcmVwbGljYXRlIGFuYWx5c2VzLiANCg0KIyMgUGFja2FnZXMgdXNlZCBpbiB0aGlzIGV4ZXJjaXNlDQoNClRoZXJlIGFyZSBzZXZlcmFsIHNwZWNpYWx0eSBwYWNrYWdlcyB0aGF0IHdpbGwgYmUgdXNlZCBpbiB0aGlzIGV4ZXJjaXNlIGR1ZSB0byB0aGUgc3BlY2lmaWMgbmF0dXJlIG9mIHRoZSBhbmFseXNlcy4gU29tZSBvZiB0aGVzZSBwYWNrYWdlcyB5b3Ugd2lsbCBuZWVkIHRvIGluc3RhbGwgd2hpbGUgc2V2ZXJhbCBvdGhlcnMgd2UgaGF2ZSB1c2VkIGluIHByZXZpb3VzIGV4ZXJjaXNlcyBhbmQgc2hvdWxkIGFscmVhZHkgYmUgaW5zdGFsbGVkLg0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCnxgYGBhZGVoYWJpdGF0SFJgYGAgfCBgYGBkYXRhLnRhYmxlYGBgIHwgYGBgZ2dmb3J0aWZ5YGBgIHwgYGBgZ3JpZGBgYHwgYGBgbW92ZWBgYCB8DQpgYGBtb3ZlVmlzYGBgIHwgYGBgT3BlblN0cmVldE1hcGBgYCB8PGJyPjwvYnI+IHxgYGBwYmFwcGx5YGBgIHwNCmBgYHBsb3RseWBgYCB8IGBgYHJnZGFsYGBgIHwgYGBgc3BgYGAgfCBgYGB0aWR5dmVyc2VgYGAgfCBgYGB2aXJpZGlzYGBgIHwNCjwvcD4NCg0KVG8gYmVnaW4sIHdlIHdpbGwgaW5zdGFsbCB0aGUgZm9sbG93aW5nOg0KDQpgYGB7ciBQYWNrYWdlcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGFja2FnZXM8LWMoImFkZWhhYml0YXRIUiIsImRhdGEudGFibGUiLCJnZ2ZvcnRpZnkiLCJncmlkIiwibW92ZSIsIm1vdmVWaXMiLCJPcGVuU3RyZWV0TWFwIiwicGJhcHBseSIsInBsb3RseSIsInJnZGFsIiwic3AiLCJ0aWR5dmVyc2UiLCJ2aXJpZGlzIikNCnNhcHBseShwYWNrYWdlcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHk9VCkNCmBgYA0KDQojIERhdGFzZXQNCg0KV2hpbGUgd2UgaGF2ZSBzZWVuIHRoYXQgYmlvbG9naWNhbCBkYXRhIGNhbiBiZSBvYnRhaW5lZCBmcm9tIHNpdGVzIHN1Y2ggYXMgW0RyeWFkXShodHRwczovL2RhdGFkcnlhZC5vcmcvc2VhcmNoKSBhbmQgW0dCSUZdKGh0dHBzOi8vd3d3LmdiaWYub3JnLyksIHdlIG5lZWQgdG8gb2J0YWluIGRhdGEgd2l0aCBzcGVjaWZpYyB2YXJpYWJsZXMgdGhhdCBtaWdodCBub3QgYmUgYXZhaWxhYmxlIG9uIHRob3NlIHNpdGVzLiBTbyB0byBzdGFydCB0aGlzIGV4ZXJjaXNlLCB3ZSB3aWxsIGRvd25sb2FkIGEgXCouY3N2IGZpbGUgY29udGFpbmluZyBpbmZvcm1hdGlvbiBmb3IgKk9waGlvcGhhZ3VzIGhhbm5haCogKEtpbmcgQ29icmEpIGZyb20gW21vdmViYW5rLm9yZ10oaHR0cHM6Ly93d3cubW92ZWJhbmsub3JnL3BhbmVsX2VtYmVkZGVkX21vdmViYW5rX3dlYmFwcD9nd3RfZnJhZ21lbnQ9cGFnZSUzRHNlYXJjaF9tYXBfbGlua2VkJTJDaW5kaXZpZHVhbElkcyUzRDU1NjU3NDM2NiUyQjU1NjU3NDM3NyUyQ2xhdCUzRDE0LjU0NjQ5MTk2ODQ2NDEzMyUyQ2xvbiUzRDEwMS45NDg2OTI3OTI2MTQ5NSUyQ3olM0QxMSkgdGhhdCBjb250YWlucyB0aGUgYXBwcm9wcmlhdGUgdmFyaWFibGUgdG8gdHJhY2sgbW92ZW1lbnQgZnJvbSByYWRpbyB0cmFuc21pdHRlciBkYXRhLiBCZWNhdXNlIHRoaXMgZGF0YSBoYXMgdG8gYmUgZG93bmxvYWRlZCB3ZSB3aWxsIG5lZWQgdG8gZ28gdGhyb3VnaCB0aGUgcHJvY2VzcyBvZiBpbXBvcnRpbmcgdGhpcyBpbmZvcm1hdGlvbiB1c2luZyB0aGUgYGBgcmVhZC5jc3YoKWBgYCBjb21tYW5kLiBPbmNlIHdlIGhhdmUgaW1wb3J0ZWQgdGhlIGRhdGEgd2UgY2FuIHZpZXcgdGhlIHN0cnVjdHVyZS4gDQoNCj4gSWYgZm9ya2VkLCB0aGlzIGRhdGEgd2lsbCBiZSBhdmFpbGFibGUgaW4gdGhlIERhdGEgZm9sZGVyIHdpdGhpbiB0aGUgcmVwb3NpdG9yeS4gQWRkaXRpb25hbGx5LCBFU1JJIFNoYXBlZmlsZSBhbmQgR29vZ2xlIEtNWiBmb3JtYXRzIHdpbGwgYmUgaW5jbHVkZWQgaWYgeW91IGFyZSBpbnRlcmVzdGVkIGluIHdvcmtpbmcgd2l0aCB0aG9zZSBkYXRhIHR5cGVzLg0KDQo8ZGV0YWlscz48c3VtbWFyeT48YmlnPkltcG9ydCBEYXRhc2V0IHdpdGggYGBgcmVhZC5jc3ZgYGA8L2JpZz48L3N1bW1hcnk+DQpgYGB7ciBkYXRhLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkYXRhIDwtIHJlYWQuY3N2KCIuL0RhdGEvb3BoaW9waGFndXNfaGFubmFoLmNzdiIpDQpgYGANCjwvZGV0YWlscz4NCmBgYHtyIHN0cnVjdHVyZSwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KaGVhZChkYXRhKQ0KYGBgDQoNCk5vdGljZSB0aGF0IHRoaXMgZGF0YXNldCBjb250YWlucyBtb3JlIHRoYW4gc3BlY2llcyBpbmZvcm1hdGlvbiBhbmQgeCx5IGNvb3JkaW5hdGVzLiBXaGlsZSB0aGF0IGluZm9ybWF0aW9uIGlzIGltcG9ydGFudCwgdGhpcyBkYXRhc2V0IGFsc28gY29udGFpbnMgdGltZSBhbmQgZGF0ZSBpbmZvcm1hdGlvbiAodGltZXN0YW1wKSwgc2Vuc29yIHR5cGUsIGFuZCBVVE0gZGF0YS4gSW4gb3JkZXIgdG8gYmUgc3VyZSB0aGF0IHRoZSBkYXRhc2V0IGNvbnRhaW5zIG5vIG91dGxpZXJzLCB3ZSBjYW4gcGxvdCB0aGUgZGF0YSB1c2luZyBgYGBnZ3Bsb3RgYGAgYW5kIGBgYHBsb3RseWBgYCB0byBpbnRlcmFjdGl2ZWx5IHZpZXcgdGhlIGRhdGEuDQoNCmBgYHtyIHBsb3RseSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcWFxY19wbG90IDwtIGdncGxvdCgpICsgZ2VvbV9wb2ludChkYXRhPWRhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXModXRtLmVhc3RpbmcsdXRtLm5vcnRoaW5nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9aW5kaXZpZHVhbC5sb2NhbC5pZGVudGlmaWVyKSkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgbGFicyh4PSJFYXN0aW5nIiwgeT0iTm9ydGhpbmciKSArDQogICAgICAgICAgICAgICAgICAgICAgICBndWlkZXMoY29sb3I9Z3VpZGVfbGVnZW5kKCJJZGVudGlmaWVyIikpDQoNCmdncGxvdGx5KHFhcWNfcGxvdCkNCmBgYA0KDQpXaXRoIGBgYHBsb3RseWBgYCwgc2ltaWxhciB0byBgYGBsZWFmbGV0YGBgLCB3ZSBoYXZlIHRoZSBhYmlsaXR5IHRvIGV4YW1pbmUgdGhlIHNwcmVhZCBvZiB0aGUgZGF0YSBwb2ludHMgYW5kIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gZnJvbSB2YXJpb3VzIGNvbHVtbnMuIEZyb20gdGhpcyBwbG90IHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgdHdvIGluZGl2aWR1YWxzLCBPUEhBMSBhbmQgT1BIQTIsIHRoYXQgd2VyZSB0cmFja2VkIGZvciB0aGlzIHN0dWR5IGFuZCBoYXZlIHZlcnkgbGl0dGxlIG92ZXJsYXAgaW4gdGhlaXIgYXBwYXJlbnQgcmFuZ2UuDQoNCldoaWxlIHdlIGNvdWxkIGNvbnRpbnVlIHdpdGggdGhlIGN1cnJlbnQgZGF0YXNldCwgYW55IGFuYWx5c2lzIHdvdWxkIGNhbGN1bGF0ZSBob21lIHJhbmdlIGZvciB0aGUgZW50aXJlIHBvcHVsYXRpb24gcmF0aGVyIHRoYW4gdGhlIGluZGl2aWR1YWwuIA0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCk1DUCBBbmFseXNpcyBFeGFtcGxlIHwgS0RFIEFuYWx5c2lzIEV4YW1wbGUNCi0gfCAtIA0KIVtdKC4vbWNwX2FsbC5wbmcgIk1DUCBmb3IgYWxsIHBvaW50cyIpIHwgIVtdKC4va2RlX2FsbC5wbmcgIktERSBmb3IgYWxsIHBvaW50cyIpDQo8L3A+DQoNClNvIGhlcmUgd2Ugd2lsbCBjcmVhdGUgYSBmdW5jdGlvbiBhbmQgdXNlIHRoZSBgYGBsYXBwbHlgYGAgY29tbWFuZCB0byBhcHBseSBhIGZ1bmN0aW9uIG92ZXIgYSBsaXN0IG9yIHZlY3RvciBkYXRhc2V0LiBTcGVjaWZpY2FsbHksIHRoaXMgZnVuY3Rpb24gd2lsbCB0YWtlIHRoZSBvcmlnaW5hbCAqZGF0YSpzZXQsIHNwbGl0IGl0IGludG8gc2VwYXJhdGUgZmlsZXMgYmFzZWQgb24gdGhlIGluZGl2aWR1YWwgaWRlbnRpZmllciwgYW5kIGNyZWF0ZSBuZXcgXCouY3N2IGZpbGVzIHVzaW5nIHRoZSBpZGVudGlmaWVyIGFzIHRoZSBmaWxlbmFtZS4gDQoNCmBgYHtyIGxhcHBseSBmdW5jdGlvbiwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmxhcHBseShzcGxpdChkYXRhLCBkYXRhJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllciksIA0KICAgICAgIGZ1bmN0aW9uKHgpd3JpdGUuY3N2KHgsIGZpbGUgPSBwYXN0ZSh4JGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllclsxXSwiLmNzdiIsIHNlcCA9ICIiKSwgcm93Lm5hbWVzID0gRkFMU0UpKQ0KYGBgDQoNClRoZSBhbmF0b215IG9mIHRoZSBmdW5jdGlvbiBpcyBhcyBmb2xsb3dzOg0KDQotIGBgYGxhcHBseSgpYGBgLCBhcHBseSB0aGUgZnVuY3Rpb24gb3ZlciBhIGxpc3QNCi0gYGBgc3BsaXQoKWBgYCwgc2VwYXJhdGVzIHRoZSBkYXRhIA0KLSBgYGBmdW5jdGlvbigpYGBgLCBjb21wb3NlIGEgc2VyaWVzIG9mIHN0ZXBzIHRvIGJlIGFwcGxpZWQgdG8gdGhlIGRhdGENCi0gYGBgd3JpdGUuY3N2KClgYGAsIHdyaXRlIGEgY3N2IGZpbGUNCi0gYGBgcGFzdGUoKWBgYCwgY3JlYXRlIGEgY2hhcmFjdGVyIHN0cmluZyB0byBiZSB1c2VkIGZvciB0aGUgZmlsZSBuYW1lDQoNCklmIHlvdSBleGFtaW5lIHRoZSByb290IGZvbGRlciB5b3Ugd2lsbCBub3cgZmluZCB0aGUgYWRkaXRpb24gb2YgdHdvIG5ldyBcKi5jc3YgZmlsZXM6IE9QSEExIGFuZCBPUEhBMi4gV2Ugd2lsbCB1c2UgdGhlc2UgZmlsZXMgdG8gcnVuIG91ciBob21lIHJhbmdlIGFuYWx5c2VzLiBBbHRlcm5hdGl2ZWx5LCBpZiB5b3UgYXJlIHByb3ZpZGVkIGEgbnVtYmVyIG9mIGluZGl2aWR1YWwgXCouY3N2IGZpbGVzIGFuZCBuZWVkIHRvIHF1aWNrbHkgaW1wb3J0IHRoZSBkYXRhIGludG8gc2VwYXJhdGUgZGF0YSBmcmFtZXMgeW91IGNvdWxkIHdyaXRlIGEgKipmb3IgbG9vcCoqIHRoYXQgd291bGQgbG9vayBzb21ldGhpbmcgbGlrZSB0aGlzOg0KDQpgYGANCmxpc3QgPC0gZ3N1YigiXFwuY3N2JCIsIiIsIGxpc3QuZmlsZXMocGF0dGVybj0iXFwuY3N2JCIpKQ0KDQpmb3IoaSBpbiBsaXN0KXsNCiAgYXNzaWduKGksIHJlYWQuY3N2KHBhc3RlKGksICIuY3N2Iiwgc2VwPSIiKSkpDQp9DQpgYGANCg0KQWxsIG9mIHRoZSBcKi5jc3YgZmlsZXMgaW4geW91ciByb290IGRpcmVjdG9yeSB3aWxsIG5vdyBiZSBhdmFpbGFibGUgaW4geW91ciBnbG9iYWwgZW52aXJvbm1lbnQuIEhvd2V2ZXIsIHNpbmNlIHdlIHVzZWQgYSBsb29waW5nIGZ1bmN0aW9uIHRvIGNyZWF0ZSBpbmRpdmlkdWFsIFwqLmNzdiBmaWxlcywgd2Ugd2lsbCBuZWVkIHRvIG1ha2UgYSBsaXN0IG9mIHRob3NlIGluZGl2aWR1YWwgZmlsZXMgY3JlYXRlZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbiB0byByZWZlciBiYWNrIHRvIGR1cmluZyB0aGUgYW5hbHlzaXMgd2hlbiBuZWNlc3NhcnkuDQoNCmBgYHtyIGxpc3QsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiLiIsIHBhdHRlcm4gPSAiW09QSEFdK1swLTldKyIsIGZ1bGwubmFtZXMgPSBUUlVFKQ0KYGBgDQoNCkluIHRoZSBgYGBsaXN0LmZpbGVzYGBgIGNvbW1hbmQgYWJvdmUsIGBgYHBhdGggPSAiLiJgYGAgaW5mb3JtcyB0aGUgbG9jYXRpb25zLCBpbiB0aGlzIGNhc2UgdGhlIHJvb3QgZGlyZWN0b3J5LCBgYGBwYXR0ZXJuID1gYGAgZGVzY3JpYmVzIHRoZSB3YXkgdGhlIGZpbGVzIGFyZSBuYW1lZCwgaW4gdGhpcyBjYXNlIE9QSEEgZm9sbG93ZWQgYnkgYSBudW1iZXIgYmV0d2VlbiAwLTksIGFuZCBgYGBmdWxsLm5hbWVzYGBgIGRlc2NyaWJlcyBob3cgdGhlIGZpbGVzIHdpbGwgYmUgbGlzdGVkLiANCg0KIyBBbmFseXNpcw0KDQpUbyBkYXRlLCB0aGlzIHdpbGwgYmUgb25lIG9mIHRoZSBtb3N0IGNvZGUgaW50ZW5zaXZlIGV4ZXJjaXNlcyB3ZSBoYXZlIGF0dGVtcHRlZC4gV2hpbGUgdGhlIGNvZGUgaXRzZWxmIGlzIG5vdCBkaWZmaWN1bHQsIHRoZSBpbXBsZW1lbnRhdGlvbiBpcyB0ZWRpb3VzIGR1ZSB0byB0aGUgdmFyeWluZyBkYXRhIHR5cGVzIGFuZCBhbmFseXNlcyBwZXJmb3JtZWQuDQoNCiMjIEltYWdlcnkNCg0KQXMgd2UgaGF2ZSBpbiBwcmV2aW91cyBleGVyY2lzZXMgd2Ugd2lsbCB1c2UgcmFzdGVyIGltYWdlcnkgdG8gcHJvdmlkZSBhZGRpdGlvbmFsIHNwYXRpYWwgZGV0YWlsIHRvIHRoZSBhbmFseXNpcy4gV2hpbGUgdGhpcyBpbmZvcm1hdGlvbiBjYW4gY29tZSBmcm9tIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBzb3VyY2VzLCB3ZSB3aWxsIGFnYWluIHVzZSBgYGBvcGVuc3RyZWV0bWFwYGBgIGZvciB0aGlzIGV4YW1wbGUuIEZvciB0aG9zZSB1c2luZyBNYWNzIHdpdGggamF2YSByZXN0cmljdGlvbnMsIHVzZSB5b3VyIHByZWZlcnJlZCBzb3VyY2UgZm9yIGltYWdlcnkuDQoNCkluIG9yZGVyIHRvIGNyZWF0ZSBhIGJvdW5kaW5nIGJveCBmb3IgdGhlIGltYWdlcnkgd2Ugd2lsbCB1c2UgdGhlIGBgYG1pbmBgYCBhbmQgYGBgbWF4YGBgIHZhbHVlcyBmcm9tIHRoZSBjb29yZGluYXRlcyBpbiB0aGUgKmRhdGEqc2V0IHRvIGNvbXB1dGUgdGhvc2UgcG9pbnRzLiBBbHRob3VnaCB0aGUgZGF0YSBvYnRhaW5lZCBmcm9tIFt3d3cubW92ZWJhbmsub3JnXShodHRwOi8vd3d3Lm1vdmViYW5rLm9yZykgY29udGFpbmVkIGJvdGggbG9uZ2l0dWRlL2xhdGl0dWRlIGFuZCBVVE0gdmFsdWVzLCB0aGlzIGV4YW1wbGUgd2lsbCBkZXRhaWwgaG93IHRvIGNvbnZlcnQgVVRNIHRvIGxvbmdpdHVkZS9sYXRpdHVkZSBjb29yZGluYXRlcyBpbiBvcmRlciB0byB1c2UgYGBgb3BlbnN0cmVldG1hcGBgYCwgYW5kIHRoZSByYXN0ZXIgaW1hZ2VyeSBmcm9tIGxvbmdpdHVkZS9sYXRpdHVkZSB0byBVVE0uIFRoaXMgd2lsbCBiZSB1c2VmdWwgaWYgeW91IHJlY2VpdmUgZGF0YSBvbmx5IHdpdGggVVRNIHZhbHVlcy4gSWYgeW91ciBkYXRhIGNvbnRhaW5zIGxvbmdpdHVkZS9sYXRpdHVkZSB2YWx1ZXMgdGhlcmUgaXMgbm8gY29udmVyc2lvbiByZXF1aXJlZC4NCg0KYGBge3IgaW1hZ2VyeSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQ0KdXRtX3BvaW50cyA8LSBjYmluZChkYXRhJHV0bS5lYXN0aW5nLCBkYXRhJHV0bS5ub3J0aGluZykNCnV0bV9sb2NhdGlvbnMgPC0gU3BhdGlhbFBvaW50cyh1dG1fcG9pbnRzLCANCiAgICAgICAgICAgICAgICAgcHJvajRzdHJpbmc9Q1JTKCIrcHJvaj11dG0gK3pvbmU9NDcgK2RhdHVtPVdHUzg0IikpDQpwcm9qX2xhdC5sb24gPC0gYXMuZGF0YS5mcmFtZShzcFRyYW5zZm9ybSgNCiAgICAgICAgICAgICAgICB1dG1fbG9jYXRpb25zLCBDUlMoIitwcm9qPWxvbmdsYXQgK2RhdHVtPVdHUzg0IikpKQ0KY29sbmFtZXMocHJval9sYXQubG9uKSA8LSBjKCJ4IiwieSIpDQpyYXN0ZXIgPC0gb3Blbm1hcChjKG1heChwcm9qX2xhdC5sb24keSkrMC4wMSwgbWluKHByb2pfbGF0LmxvbiR4KS0wLjAxKSwgDQogICAgICAgICAgICAgICAgICBjKG1pbihwcm9qX2xhdC5sb24keSktMC4wMSwgbWF4KHByb2pfbGF0LmxvbiR4KSswLjAxKSwgDQogICAgICAgICAgICAgICAgICB0eXBlID0gImJpbmciKQ0KcmFzdGVyX3V0bSA8LSBvcGVucHJvaihyYXN0ZXIsIA0KICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gIitwcm9qPXV0bSArem9uZT00NyArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKQ0KYGBgDQoNCkluIHRoZSBzY3JpcHQgYWJvdmUsIGBgYHV0bV9wb2ludGBgYCBpcyBhbiB4LHkgZGVyaXZlZCBmcm9tIHRoZSBwcmltYXJ5IGRhdGFzZXQsIGBgYHV0bV9sb2NhdGlvbnNgYGAgc2V0IHRoZSBwcm9qZWN0aW9uIHRvICoqVVRNIFpvbmUgNDcqKiwgYGBgcHJval9sYXQubG9uYGBgIGNvbnZlcnRlZCB0aGUgVVRNIHBvaW50cyB0byBsb25naXR1ZGUvbGF0aXR1ZGUsIGBgYHJhc3RlcmBgYCB1c2VzIHRoZSBtaW4vbWF4IHgseSBkYXRhIHRvIGNyZWF0ZSBhIGJvdW5kaW5nIGJveCB0byByZXRyaWV2ZSB0aGUgYWVyaWFsIGltYWdlcnksIGFuZCBgYGByYXN0ZXJfdXRtYGBgIHJlcHJvamVjdGVkIHRoZSBpbWFnZXJ5IGJhY2sgdG8gKipVVE0gWm9uZSA0NyoqIGNvbnNpc3RlbnQgd2l0aCB0aGUgbG9jYXRpb24gaW4gVGhhaWxhbmQuIE5vdyB3ZSBjYW4gdXNlIGBgYGF1dG9wbG90Lk9wZW5TdHJlZXRNYXBgYGAgdG8gZGlzcGxheSB0aGUgcmFzdGVyIGltYWdlIGZpbGUgd2l0aCB0aGUgVVRNIGxvY2F0aW9ucyBhcyBhbiBvdmVybGF5Lg0KDQpgYGB7ciBpbWFnZXJ5IHBsb3QsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89VFJVRSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9Nn0NCmF1dG9wbG90Lk9wZW5TdHJlZXRNYXAocmFzdGVyX3V0bSwgZXhwYW5kID0gVFJVRSkgKyB0aGVtZV9idygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTEpKSArDQogIGdlb21fcG9pbnQoZGF0YT1kYXRhLCBhZXModXRtLmVhc3RpbmcsdXRtLm5vcnRoaW5nLA0KICAgICAgICAgICAgIGNvbG9yPWluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllciksIHNpemUgPSAzLCBhbHBoYSA9IDAuOCkgKw0KICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2U9ImJvbGQiKSkgKyBsYWJzKHg9IkVhc3RpbmciLA0KICAgICAgICB5PSJOb3J0aGluZyIpICsgZ3VpZGVzKGNvbG9yPWd1aWRlX2xlZ2VuZCgiSWRlbnRpZmllciIpKQ0KYGBgDQoNCiMjIEhvbWUgUmFuZ2UgQW5hbHlzaXMNCg0KVGhlcmUgYXJlIHRocmVlIGJhc2ljIHR5cGVzIG9mIGhvbWUgcmFuZ2UgYW5hbHlzZXMgd2Ugd2lsbCBwZXJmb3JtIGluIHRoaXMgZXhlcmNpc2U6IE1pbmltdW0gQ29udmV4IFBvbHlnb24gKE1DUCksIEtlcm5lbC1EZW5zaXR5IEVzdGltYXRpb24gKEtERSksIGFuZCBCcm93bmlhbiBCcmlkZ2UgTW92ZW1lbnQgTW9kZWwgKEJCKS4gVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGRpZmZlcmVudCB0dW5pbmcgcGFyYW1ldGVycyB0aGF0IGNhbiBiZSBhcHBsaWVkIHRoZXNlIGFuYWx5c2VzLCBob3dldmVyIGluIHRoaXMgZXhlcmNpc2Ugd2Ugd2lsbCB1c2UgdGhlIG1vc3QgYmFzaWMgdmVyc2lvbnMgb2YgdGhlIGFuYWx5c2lzLiANCg0KSW4gdGhlIHNlY3Rpb24gYWJvdmUgd2UgdXNlIHRoZSBgYGBsYXBwbHlgYGAgY29tbWFuZCB0byBsb29wIGEgZnVuY3Rpb24gdXNlZCB0byBzZXBhcmF0ZSB0aGUgb3JpZ2luYWwgZGF0YXNldCBpbnRvIGluZGl2aWR1YWwgZmlsZXMuIFRoaXMgaXMgYSB1c2VmdWwgdG9vbCwgaG93ZXZlciwgd2hlbiB0aGUgZnVuY3Rpb24gbG9vcHMgdGhyb3VnaCBkb3plbnMgb3IgZXZlbiBodW5kcmVkcyBvZiBmaWxlcywgdGhlIHByb2Nlc3MgY2FuIHRha2UgYSBsb25nIHBlcmlvZCBvZiB0aW1lIHRvIGNvbXBsZXRlLiBVc2luZyB0aGUgYGBgcGJsYXBwbHlgYGAgY29tbWFuZCBhZGRzIGEgcHJvZ3Jlc3MgYmFyIChpLmUuICoqcGIqKikgdG8gdGhlIHByb2Nlc3Mgd2hpY2ggcHJvdmlkZXMgYW4gZXN0aW1hdGVkIHRpbWUgZm9yIGNvbXBsZXRpb24gb2YgdGhlIGZ1bmN0aW9uLiBXZSB3aWxsIHVzZSBhIHNpbWlsYXIgcHJvY2VzcyB0byB0aGUgb25lIGFib3ZlLCB1c2luZyB0aGUgYGBgcGJsYXBwbHlgYGAgY29tbWFuZCB0byBydW4gTUNQIGFuYWx5c2lzIG9uIHRoZSBpbmRpdmlkdWFsIFwqLmNzdiBmaWxlcy4gDQoNCiMjIyBNaW5pbXVtIENvbnZleCBQb2x5Z29uDQoNCkEgZGVzY3JpcHRpb24gb2YgdGhlIHN0ZXBzIHdpdGhpbiB0aGUgZm9sbG93aW5nIGNvZGUgd2lsbCBiZSBkaXNjdXNzZWQgZm9sbG93aW5nIHRoZSBvdXRwdXQuIFRoZSBwcm9jZXNzIHdvcmtzIGJ5IGVzdGFibGlzaGluZyB0aGUgZnVuY3Rpb24gYW5kIHRoZW4gcnVubmluZyBgYGBwYmxhcHBseWBgYCByZWZlcnJpbmcgYmFjayB0byB0aGUgKipmaWxlcyoqIHdlIGNyZWF0ZWQgaW4gdGhlIGRhdGFzZXQgcG9ydGlvbiBvZiB0aGlzIGV4ZXJjaXNlLg0KDQpgYGB7ciBNQ1AgcGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0KbWNwX3Jhc3RlciA8LSBmdW5jdGlvbihmaWxlbmFtZSl7DQogIGRhdGEgPC0gcmVhZC5jc3YoZmlsZSA9IGZpbGVuYW1lKQ0KICB4IDwtIGFzLmRhdGEuZnJhbWUoZGF0YSR1dG0uZWFzdGluZykNCiAgeSA8LSBhcy5kYXRhLmZyYW1lKGRhdGEkdXRtLm5vcnRoaW5nKQ0KICB4eSA8LSBjKHgseSkNCiAgZGF0YS5wcm9qIDwtIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoeHksZGF0YSwgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPXV0bSArem9uZT0xMiArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKSkNCiAgeHkgPC0gU3BhdGlhbFBvaW50cyhkYXRhLnByb2pAY29vcmRzKQ0KICBtY3Aub3V0IDwtIG1jcCh4eSwgcGVyY2VudD0xMDAsIHVub3V0PSJoYSIpDQogIG1jcC5wb2ludHMgPC0gY2JpbmQoKGRhdGEuZnJhbWUoeHkpKSxkYXRhJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllcikNCiAgY29sbmFtZXMobWNwLnBvaW50cykgPC0gYygieCIsInkiLCAiaWRlbnRpZmllciIpDQogIG1jcC5wb2x5IDwtIGZvcnRpZnkobWNwLm91dCwgcmVnaW9uID0gImlkIikNCiAgdW5pdHMgPC0gZ3JpZC50ZXh0KHBhc3RlKHJvdW5kKG1jcC5vdXRAZGF0YSRhcmVhLDIpLCJoYSIpLCB4PTAuODUsICB5PTAuOTUsDQogICAgICAgICAgICAgICAgICAgICBncD1ncGFyKGZvbnRmYWNlPTQsIGNvbD0id2hpdGUiLCBjZXg9MC45KSwgZHJhdyA9IEZBTFNFKQ0KICBtY3AucGxvdCA8LSBhdXRvcGxvdC5PcGVuU3RyZWV0TWFwKHJhc3Rlcl91dG0sIGV4cGFuZCA9IFRSVUUpICsgdGhlbWVfYncoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsNCiAgICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImJsYWNrIiwgZmlsbD1OQSwgc2l6ZT0xKSkgKw0KICAgIGdlb21fcG9seWdvbihkYXRhPW1jcC5wb2x5LCBhZXMoeD1tY3AucG9seSRsb25nLCB5PW1jcC5wb2x5JGxhdCksIGFscGhhPTAuOCkgKw0KICAgIGdlb21fcG9pbnQoZGF0YT1tY3AucG9pbnRzLCBhZXMoeD14LCB5PXkpKSArIA0KICAgIGxhYnMoeD0iRWFzdGluZyAobSkiLCB5PSJOb3J0aGluZyAobSkiLCB0aXRsZT1tY3AucG9pbnRzJGlkZW50aWZpZXIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkgKyANCiAgICBhbm5vdGF0aW9uX2N1c3RvbSh1bml0cykNCiAgbWNwLnBsb3QNCn0NCg0KcGJsYXBwbHkoZmlsZXMsIG1jcF9yYXN0ZXIpDQpgYGANCg0KVGhlIGFuYXRvbXkgb2YgdGhlIGZ1bmN0aW9uIGFib3ZlIGlzIGFzIGZvbGxvd3M6DQpgYGBtY3BfcmFzdGVyIDwtIGZ1bmN0aW9uKGZpbGVuYW1lKXsuLi5gYGAgZm9yIGVhY2ggKmZpbGUqIGxpc3RlZCBmcm9tIHRoZSBwcm9qZWN0IGRpcmVjdG9yeSBjb21wbGV0ZSB0aGUgZm9sbG93aW5nIGNvbW1hbmRzDQoNCi0gYGBgcmVhZC5jc3YoKWBgYCwgcmVhZCB0aGUgZGF0YSBmcm9tIGEgZ2l2ZW4gXCouY3N2IGZpbGUNCi0geHksIGNyZWF0ZSBhIGNvb3JkaW5hdGUgZmlsZSBvZiB0aGUgZWFzdGluZyBhbmQgbm9ydGhpbmcgZGF0YQ0KLSBgYGBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lKClgYGAsIHByb2plY3QgdGhlIGRhdGEgdG8gVVRNIFpvbmUgNDcNCi0gYGBgbWNwKClgYGAsIGNvbXB1dGVzIGhvbWUgcmFuZ2UgdXNpbmcgdGhlIE1pbmltdW0gQ29udmV4IFBvbHlnb24gZXN0aW1hdG9yDQotIGBgYGZvcnRpZnkoKWBgYCwgdHVybnMgYSBtYXAgaW50byBhIGRhdGEgZnJhbWUgZm9yIHBsb3R0aW5nIHdpdGggZ2dwbG90Mg0KLSBgYGBncmlkLnRleHQoKWBgYCwgY3JlYXRlcyBhbm5vdGF0aW9ucyBmb3IgdGhlIG1hcDsgaW4gdGhpcyBjYXNlIHRvIHNob3cgYXJlYQ0KLSBgYGBhdXRvcGxvdC5PcGVuU3RyZWV0TWFwKClgYGAsIHBsb3R0aW5nIGZvciByYXN0ZXIgYW5kIHZlY3RvciBkYXRhIGluIGdncGxvdDINCi0gYGBgcGJsYXBwbHkoKWBgYCwgcmVmZXJlbmNpbmcgdGhlIGZpbGVzIGxpc3QgYW5kIGZ1bmN0aW9uDQoNClRoZSByZXN0IG9mIHRoZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgYnkgZXhhbWluZyB0aGUgaGVscCBtZW51IGZvciB0aGUgdmFyaW91cyBjb21tYW5kcyBvciBsb29raW5nIGF0IHRoZSBkaXNwbGF5IG9wdGlvbnMgZm9yIGVhY2ggcG9ydGlvbiBvZiB0aGUgc2NyaXB0Lg0KDQojIyMgS2VybmVsLURlbnNpdHkgRXN0aW1hdGlvbg0KDQpUaGUgc2FtZSBkZXNpZ24gYWJvdmUgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIEtERSBwbG90cyBmb3IgZWFjaCBpbmRpdmlkdWFsIGluIHRoZSBkYXRhc2V0LiBCZWNhdXNlIHRoZSBwcm9jZXNzIGlzIGVzc2VudGlhbGx5IHRoZSBzYW1lLCBvbmx5IHBvcnRpb25zIG9mIHRoZSBmdW5jdGlvbiB0aGF0IHdlcmUgbm90IHByZXZpb3VzbHkgZGVzY3JpYmVkIHdpbGwgYmUgZGlzY3Vzc2VkLg0KDQpgYGB7ciBLREUgcGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0Ka2RlX3Jhc3RlciA8LSBmdW5jdGlvbihmaWxlbmFtZSl7DQogIGRhdGEgPC0gcmVhZC5jc3YoZmlsZSA9IGZpbGVuYW1lKQ0KICB4IDwtIGFzLmRhdGEuZnJhbWUoZGF0YSR1dG0uZWFzdGluZykNCiAgeSA8LSBhcy5kYXRhLmZyYW1lKGRhdGEkdXRtLm5vcnRoaW5nKQ0KICB4eSA8LSBjKHgseSkNCiAgZGF0YS5wcm9qIDwtIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoeHksZGF0YSwgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPXV0bSArem9uZT00NyArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKSkNCiAgeHkgPC0gU3BhdGlhbFBvaW50cyhkYXRhLnByb2pAY29vcmRzKQ0KICBrZGU8LWtlcm5lbFVEKHh5LCBoPSJocmVmIiwga2Vybj0iYml2bm9ybSIsIGdyaWQ9MTAwKQ0KICB2ZXIgPC0gZ2V0dmVydGljZXNocihrZGUsIDk1KQ0KICBrZGUucG9pbnRzIDwtIGNiaW5kKChkYXRhLmZyYW1lKGRhdGEucHJvakBjb29yZHMpKSxkYXRhJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllcikNCiAgY29sbmFtZXMoa2RlLnBvaW50cykgPC0gYygieCIsInkiLCJpZGVudGlmaWVyIikNCiAga2RlLnBvbHkgPC0gZm9ydGlmeSh2ZXIsIHJlZ2lvbiA9ICJpZCIpDQogIHVuaXRzIDwtIGdyaWQudGV4dChwYXN0ZShyb3VuZCh2ZXIkYXJlYSwyKSwiIGhhIiksIHg9MC44NSwgIHk9MC45NSwNCiAgICAgICAgICAgICAgICAgICAgIGdwPWdwYXIoZm9udGZhY2U9NCwgY29sPSJ3aGl0ZSIsIGNleD0wLjkpLCBkcmF3ID0gRkFMU0UpDQogIGtkZS5wbG90IDwtIGF1dG9wbG90Lk9wZW5TdHJlZXRNYXAocmFzdGVyX3V0bSwgZXhwYW5kID0gVFJVRSkgKyB0aGVtZV9idygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKw0KICAgIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTEpKSArDQogICAgZ2VvbV9wb2x5Z29uKGRhdGE9a2RlLnBvbHksIGFlcyh4PWtkZS5wb2x5JGxvbmcsIHk9a2RlLnBvbHkkbGF0KSwgYWxwaGEgPSAwLjgpICsNCiAgICBnZW9tX3BvaW50KGRhdGE9a2RlLnBvaW50cywgYWVzKHg9eCwgeT15KSkgKw0KICAgIGxhYnMoeD0iRWFzdGluZyAobSkiLCB5PSJOb3J0aGluZyAobSkiLCB0aXRsZT1rZGUucG9pbnRzJGlkZW50aWZpZXIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkgKyANCiAgICBhbm5vdGF0aW9uX2N1c3RvbSh1bml0cykNCiAga2RlLnBsb3QNCn0NCg0KcGJsYXBwbHkoZmlsZXMsIGtkZV9yYXN0ZXIpDQpgYGANCg0KVGhlIGFuYXRvbXkgb2YgdGhlIGZ1bmN0aW9uIGFib3ZlIGlzIGFzIGZvbGxvd3M6DQpgYGBrZGVfcmFzdGVyIDwtIGZ1bmN0aW9uKGZpbGVuYW1lKXsuLi5gYGAgZm9yIGVhY2ggKmZpbGUqIGxpc3RlZCBmcm9tIHRoZSBwcm9qZWN0IGRpcmVjdG9yeSBjb21wbGV0ZSB0aGUgZm9sbG93aW5nIGNvbW1hbmRzDQoNCi0gYGBga2VybmVsVUQoKWBgYCwgZXN0aW1hdGlvbiBvZiBrZXJuZWwgaG9tZS1yYW5nZQ0KLSBgYGBnZXR2ZXJ0aWNlc2hyKClgYGAsIGV4dHJhY3QgaG9tZS1yYW5nZSBjb250b3VyDQoNClRoZSByZXN0IG9mIHRoZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgYnkgZXhhbWluZyB0aGUgaGVscCBtZW51IGZvciB0aGUgdmFyaW91cyBjb21tYW5kcyBvciBsb29raW5nIGF0IHRoZSBkaXNwbGF5IG9wdGlvbnMgZm9yIGVhY2ggcG9ydGlvbiBvZiB0aGUgc2NyaXB0Lg0KDQojIyMgQnJvd25pYW4gQnJpZGdlIE1vdmVtZW50DQoNCkFsdGhvdWdoIHRoaXMgYXBwZWFycyB0byBiZSB3b3JraW5nIGJhY2t3YXJkcywgaW4gdGhlIHByZXZpb3VzIHR3byBleGFtcGxlcyB5b3UgaGF2ZSBzZWVuIGhvdyB0byBjcmVhdGUgYSBmdW5jdGlvbiB0aGF0IGNhbiBiZSBsb29wZWQgdG8gcnVuIHRoZSBhbmFseXNpcyBmb3IgYSBsaXN0IGZvIGZpbGVzLiBGb3IgdGhpcyBhbmFseXNpcywgd2Ugd2lsbCBjcmVhdGUgYSBjb252ZW50aW9uYWwgc2NyaXB0IGZvciBhIHNpbmdsZSBpbmRpdmlkdWFsLiBQb3J0aW9ucyBvZiB0aGUgc2NyaXB0IHRoYXQgd2VyZSBub3QgcHJldmlvdXNseSBkZXNjcmliZWQgd2lsbCBiZSBkaXNjdXNzZWQgZm9sbG93aW5nIHRoZSBhbmFseXNpcy4NCg0KYGBge3IgYmIgcGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0KT1BIQTEgPC0gcmVhZC5jc3YoIk9QSEExLmNzdiIpDQpkYXRlIDwtIGFzLlBPU0lYY3Qoc3RycHRpbWUoYXMuY2hhcmFjdGVyKE9QSEExJHRpbWVzdGFtcCksIiVZLSVtLSVkICVIOiVNOiVTIiwgdHo9IkFzaWEvQmFuZ2tvayIpKQ0KT1BIQTEkZGF0ZSA8LSBkYXRlDQpPUEhBMS5yZWxvYyA8LSBjYmluZC5kYXRhLmZyYW1lKE9QSEExJHV0bS5lYXN0aW5nLCBPUEhBMSR1dG0ubm9ydGhpbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLnZlY3RvcihPUEhBMSRpbmRpdmlkdWFsLmxvY2FsLmlkZW50aWZpZXIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5QT1NJWGN0KGRhdGUpKQ0KY29sbmFtZXMoT1BIQTEucmVsb2MpIDwtIGMoIngiLCJ5IiwiaWQiLCJkYXRlIikNCnRyYWplY3RvcnkgPC0gYXMubHRyYWooT1BIQTEucmVsb2MsIGRhdGU9ZGF0ZSwgaWQ9Ik9QSEExIikNCnNpZzEgPC0gbGlrZXIodHJhamVjdG9yeSwgc2lnMiA9IDU4LCByYW5nZXNpZzEgPSBjKDAsIDUpLCBwbG90aXQgPSBGQUxTRSkNCm9waGEudHJhaiA8LSBrZXJuZWxiYih0cmFqZWN0b3J5LCBzaWcxID0gLjc5MDgsIHNpZzIgPSA1OCwgZ3JpZCA9IDEwMCkNCmJiX3ZlciA8LSBnZXR2ZXJ0aWNlc2hyKG9waGEudHJhaiwgOTUpDQpiYl9wb2x5IDwtIGZvcnRpZnkoYmJfdmVyLCByZWdpb24gPSAiaWQiLCANCiAgICAgICAgICAgICAgICAgICBwcm9qNHN0cmluZyA9IENSUygiK3Byb2o9dXRtICt6b25lPTQ3Kw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdHVtPVdHUzg0ICt1bml0cz1tICtub19kZWZzIikpDQpjb2xuYW1lcyhiYl9wb2x5KSA8LSBjKCJ4IiwieSIsIm9yZGVyIiwiaG9sZSIsInBpZWNlIiwiaWQiLCJncm91cCIpDQpiYl9pbWFnZSA8LSBjcm9wKG9waGEudHJhaiwgYmJfdmVyLCANCiAgICAgICAgICAgICAgICAgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPXV0bSArem9uZT00NyArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdHVtPVdHUzg0ICt1bml0cz1tICtub19kZWZzIikpDQpiYl91bml0cyA8LSBncmlkLnRleHQocGFzdGUocm91bmQoYmJfdmVyJGFyZWEsMiksIiBoYSIpLCB4PTAuODUsICB5PTAuOTUsDQogICAgICAgICAgICAgICAgICAgZ3A9Z3Bhcihmb250ZmFjZT00LCBjb2w9IndoaXRlIiwgY2V4PTAuOSksIGRyYXcgPSBGQUxTRSkNCmJiLnBsb3QgPC0gYXV0b3Bsb3QuT3BlblN0cmVldE1hcChyYXN0ZXJfdXRtLCBleHBhbmQgPSBUUlVFKSArIHRoZW1lX2J3KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTEpKSArDQogIGdlb21fdGlsZShkYXRhPWJiX2ltYWdlLCANCiAgICAgICAgICAgIGFlcyh4PWJiX2ltYWdlQGNvb3Jkc1ssMV0sIHk9YmJfaW1hZ2VAY29vcmRzWywyXSwNCiAgICAgICAgICAgIGZpbGwgPSBiYl9pbWFnZUBkYXRhJHVkKSkgKw0KICBnZW9tX3BvbHlnb24oZGF0YT1iYl9wb2x5LCBhZXMoeD14LCB5PXksIGdyb3VwID0gZ3JvdXApLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSBOQSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiaW5mZXJubyIpICsgYW5ub3RhdGlvbl9jdXN0b20oYmJfdW5pdHMpICsNCiAgbGFicyh4PSJFYXN0aW5nIChtKSIsIHk9Ik5vcnRoaW5nIChtKSIsIHRpdGxlPSJPUEhBMSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSkpDQpiYi5wbG90DQpgYGANCg0KVGhlIGFuYXRvbXkgb2YgdGhlIHNjcmlwdCBhYm92ZSBpcyBhcyBmb2xsb3dzOg0KDQotIGBgYGFzLlBPU0lYY3QoKWBgYCwgbWFuaXB1bGF0ZSBvYmplY3RzIHRvIHJlcHJlc2VudCBjYWxlbmRhciBkYXRlcyBhbmQgdGltZXMNCi0gYGBgY2JpbmQuZGF0YS5mcmFtZSgpYGBgLCB1c2VkIHRvIGNvbWJpbmUgY29sdW1ucyBhdm9pZGluZyBmYWN0b3JpemF0aW9uDQotIGBgYGFzLmx0cmFqYGBgLCBjb252ZXJ0IGRhdGEgdG8gdHJhamVjdG9yeSBjbGFzcw0KLSBgYGBsaWtlcigpYGBgLCB1c2VkIHRvIGZpbmQgdGhlIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0aW9uIG9mIHRoZSBwYXJhbWV0ZXIgc2lnMSBpbiAqa2VybmVsYmIoKSoNCi0gYGBgc2NhbGVfZmlsbF92aXJpZGlzX2MoKWBgYCwgY3JlYXRlIGNvbG9yIHNjYWxlIGZvciBjb250aW51b3VzIGRhdGENCg0KVGhlIHJlc3Qgb2YgdGhlIGluZm9ybWF0aW9uIGNhbiBiZSBmb3VuZCBieSBleGFtaW5nIHRoZSBoZWxwIG1lbnUgZm9yIHRoZSB2YXJpb3VzIGNvbW1hbmRzIG9yIGxvb2tpbmcgYXQgdGhlIGRpc3BsYXkgb3B0aW9ucyBmb3IgZWFjaCBwb3J0aW9uIG9mIHRoZSBzY3JpcHQuDQoNCj4gVHJ5IGl0IHlvdXJzZWxmISBVc2luZyB0aGUgZXhhbXBsZSBzY3JpcHQgYWJvdmUsIGFuZCBmb2xsb3dpbmcgdGhlIGV4YW1wbGUgZnVuY3Rpb25zLCBjcmVhdGUgYSBsb29waW5nIGZ1bmN0aW9uIHdpdGggYGBgcGJsYXBwbHlgYGAgdG8gcnVuIHRoZSBCQiBhbmFseXNpcyBmb3IgZWFjaCBpbmRpdmlkdWFsIGluIHRoZSBmaWxlcyBsaXN0Lg0KDQojIyBBbmltYXRlIFRyYWplY3RvcnkgRGF0YQ0KDQpUaGUgZmluYWwgYW5hbHlzaXMgd2Ugd2lsbCBwZXJmb3JtIGluIHRoaXMgZXhlcmNpc2UgaXMgYSB2aXN1YWwgYW5pbWF0aW9uIG9mIHRoZSByZWxvY2F0aW9uIGRhdGEuIFVzaW5nIHRoZSBkYXRhIGZyb20gYWJvdmUgeW91IGNhbiBgYGBwbG90KHRyYWplY3RvcnkpYGBgIGFuZCBzZWUgYSBwbG90IG9mIHRoZSByZWxvY2F0aW9uIGluZm9ybWF0aW9uIGZvciBhbiBpbmRpdmlkdWFsLiBIb3dldmVyLCB3ZSBjYW4gdXNlIHRoZSBgYGBtb3ZlYGBgIGFuZCBgYGBtb3ZlVmlzYGBgIHBhY2thZ2VzIHRvIGNyZWF0ZSBhbiBhbmltYXRpb24gb2YgdGhlIHJlbG9jYXRpb25zLiANCg0KV2UgbmVlZCB0byBiZWdpbiBieSBjcmVhdGluZyBhICptb3ZlKiBvYmplY3QgY29udGFpbmluZyByZWxvY2F0aW9ucyAoeCx5KSwgdGltZSBhbmQgZGF0ZSBpbmZvcm1hdGlvbiAodGltZSksIGEgcHJvamVjdGlvbiBzdHJpbmcsIGluZGl2aWR1YWwgaWRlbnRpZmllciAoYW5pbWFsKSwgYW5kIHNlbnNvciB0eXBlIChzZW5zb3IpLiAqU2VlIHRoZSBkZXNjcmlwdGlvbiBmb3IgYGBgbW92ZSgpYGBgIGluIHRoZSBoZWxwIG1lbnUgZm9yIG9wdGlvbmFsIGluZm9ybWF0aW9uLioNCg0KYGBge3IgbW92ZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJ30NCm9waGEubW92ZSA8LSBtb3ZlKHg9T1BIQTEkbG9jYXRpb24ubG9uZywgDQogICAgICAgICAgICAgeT1PUEhBMSRsb2NhdGlvbi5sYXQsIA0KICAgICAgICAgICAgIHRpbWU9YXMuUE9TSVhjdChPUEhBMSR0aW1lc3RhbXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9IiVZLSVtLSVkICVIOiVNOiVTIiwgdHo9IkFzaWEvQmFuZ2tvayIpLCANCiAgICAgICAgICAgICBwcm9qPUNSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0IiksDQogICAgICAgICAgICAgZGF0YT1PUEhBMSwgYW5pbWFsPU9QSEExJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllciwgDQogICAgICAgICAgICAgc2Vuc29yPU9QSEExJHNlbnNvci50eXBlKQ0KYGBgDQoNCldpdGggdGhpcyBpbmZvcm1hdGlvbiwgd2UgY2FuIG5vdyBjcmVhdGUgdGhlIHRpbWluZywgbnVtYmVyIG9mIGZyYW1lcywgYW5kIGFuaW1hdGlvbiBmb3IgdGhlIHJlbG9jYXRpb25zLiBGaXJzdCwgdG8gdW5kZXJzdGFuZCB0aGUgdGltZSBsYWcgYmV0d2VlbiB0aGUgcmVsb2NhdGlvbnMgd2UgY2FuIHVzZSBgYGBtZWRpYW4odGltZUxhZyhvcGhhLm1vdmUsIHVuaXQgPSAibWlucyIpKWBgYCB0byBjYWxjdWxhdGUgdGhlIG1lZGlhbiB2YWx1ZS4gSG93ZXZlciwgaW4gdGhlIGBgYGFsaW5nX21vdmUoKWBgYCBmdW5jdGlvbiB0aGVyIGlzIGFuIG9wdGlvbiB0byBzZXQgdGhlIHVuaWZvcm0gc2NhbGUgbWFudWFsbHkgb3IgYnkgdXNpbmcgYSBmaXhlZCByZXNvbHV0aW9uOiBtaW4sIG1heCwgb3IgbWVhbi4gRm9yIHNpbXBsaWNpdHksIHdlIHdpbGwgdXNlIHRoZSAqKm1heCoqIHRlbXBvcmFsIHJlc29sdXRpb24uDQoNCmBgYHtyIG1vdmVtZW50LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnfQ0KbW92ZW1lbnQgPC0gYWxpZ25fbW92ZShvcGhhLm1vdmUsIHJlcyA9ICJtYXgiLCBkaWdpdCA9IDAsIHVuaXQgPSAic2VjcyIpDQpgYGANCg0KV2l0aCB0aGUgZGF0YSBub3cgb24gYSB1bmlmb3JtIHRpbWUgc2NhbGUgd2UgY2FuIGNyZWF0ZSB0aGUgZnJhbWVzIGFuZCBhbmltYXRpb24gZm9yIHRoZSByZWxvY2F0aW9ucy4gRm9yIHRoaXMgc3RlcCBJIHdpbGwgdXNlIGEgYmFzZW1hcCBmcm9tIFtNYXBCb3hdKGh0dHBzOi8vd3d3Lm1hcGJveC5jb20vKSB3aGljaCByZXF1aXJlIHRva2VuIGFjY2VzcyB0aHJvdWdoIHRoZSB1c2Ugb2YgdGhlaXIgQVBJLiBUbyBkbyB0aGlzIHlvdSBuZWVkIHRvIHJlZ2lzdGVyIHdpdGggTWFwQm94IGFuZCBjcmVhdGUgYW4gYWNjZXNzIHRva2VuLiBUaGVuIGNyZWF0ZSBhIC5SZW52aXJvbiBmaWxlIGluIHlvdXIgcHJvamVjdCBmb2xkZXIuIENvcHkgdGhlIHRva2VuIGluZm9ybWF0aW9uIGZyb20gTWFwQm94IGFuZCBjcmVhdGUgYW4gb2JqZWN0IGluIHRoZSAuUmVudmlyb24gZmlsZSBzdWNoIGFzIGBtYXBfdG9rZW4gPSAncGFzdGUgdG9rZW4gaGVyZSdgIGFuZCBhZGQgYG1hcF90b2tlbiA9ICBTeXMuZ2V0ZW52KCdtYXBfdG9rZW4nKWAgdG8gdGhlIHNjcmlwdCBiZWxvdy4gSG93ZXZlciB1c2luZyB0aGUgYGdldF9tYXB0eXBlcygpYCBzY3JpcHQgeW91IGNhbiBzZWUgdGhlcmUgYXJlIHZhcmlvdXMgbWFwIHNlcnZpY2VzIGFuZCBtYXAgdHlwZXMgdGhhdCBjYW4gYmUgdXNlZC4gQSBzaW1wbGUgb3V0cHV0IHdvdWxkIGJlIHRvIHVzZSBgbWFwX3NlcnZpY2UgPSAnb3NtJ2AgKE9wZW5TdHJlZXRNYXBzKSBhbmQgYG1hcF90eXBlID0gJ3RvcG9ncmFwaGljJ2Agb3Igb3RoZXIgbWFwIHR5cGVzIGF2YWlsYWJsZSBieSB2aWV3aW5nIGBnZXRfbWFwdHlwZXMoJ293bScpYC4gd2hlbiB1c2luZyBhIGJhc2VtYXAgd2l0aG91dCB0b2tlbiBhY2Nlc3MgdGhlIGBtYXBfdG9rZW5gIG9wdGlvbiBjYW4gYmUgcmVtb3ZlZCBmcm9tIHRoZSBzY3JpcHQgYmVsb3cuDQoNCj4gV2FybmluZywgdGhlIG51bWJlciBvZiBmcmFtZXMsIGZyYW1lcy9zZWNvbmQsIGFuZCBvdXRwdXQgZmlsZSB0eXBlIHdpbGwgZGV0ZXJtaW5lIGhvdyBsb25nIHRoaXMgcHJvY2VzcyB3aWxsIHRha2UgYW5kIGhvdyBsYXJnZSB0aGUgb3V0cHV0IGZpbGUgd2lsbCBiZS4NCg0KYGBge3IgcmVsb2MgZnJhbWVzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9DQpmcmFtZXMgPC0gZnJhbWVzX3NwYXRpYWwobW92ZW1lbnQsIHBhdGhfY29sb3VycyA9ICJyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1hcF9zZXJ2aWNlID0gIm1hcGJveCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1hcF90eXBlID0gInNhdGVsbGl0ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgbWFwX3Rva2VuID0gIFN5cy5nZXRlbnYoJ21hcF90b2tlbicpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSAlPiUgDQogIGFkZF9sYWJlbHMoeCA9ICJMb25naXR1ZGUiLCB5ID0gIkxhdGl0dWRlIikgJT4lDQogIGFkZF9ub3J0aGFycm93KCkgJT4lIA0KICBhZGRfc2NhbGViYXIoZGlzdGFuY2UgPSAyKSAlPiUgDQogIGFkZF90aW1lc3RhbXBzKG1vdmVtZW50LCB0eXBlID0gImxhYmVsIikgJT4lIA0KICBhZGRfcHJvZ3Jlc3MoKQ0KYGBgDQoNCmBgYHtyIGFuaW1hdGVkIHJlbG9jcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQ0KYW5pbWF0ZV9mcmFtZXMoZnJhbWVzLCBmcHMgPSA1LCBvdmVyd3JpdGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgb3V0X2ZpbGUgPSAiLi9tb3ZlVmlzLTIwMjFmcHMuZ2lmIikNCmBgYCAgICAgICAgICAgICAgIA0KDQohW10oLi9tb3ZlVmlzLTIwMjFmcHMuZ2lmICJSZWxvY2F0aW9uIEFuaW1hdGlvbiIpDQogICAgICAgICAgICAgICANCkFzIHlvdSBjYW4gc2VlIGZyb20gdGhlIHJlc3VsdCBhYm92ZSwgZXZlbiBhIH4xMnNlYyBjbGlwIHdpdGggPDYwIGZyYW1lcyB0b29rIG5lYXJseSA1bWluIHRvIGtleSwgb2J0YWluIGJhc2UgaW1hZ2VyeSwgYXNzaWduIGZyYW1lcywgYW5kIHJlbmRlci4gU28ga2VlcCB0aGF0IGluIG1pbmQgd2hlbiBjcmVhdGluZyB0aGVzZSBhbmltYXRpb25zLiBUaGUgb3V0cHV0IGZyb20gdGhpcyBpcyBzdG9yZWQgaW4geW91ciByb290IGRpcmVjdG9yeSBhbmQgY2FuIGJlIHVzZWQgYXMgYW4gYW5pbWF0aW9uIG9uIG90aGVyIHByb2dyYW1zIG9yIGluIGh0bWwgZG9jdW1lbnRzLg0KDQojIFlPVVIgVFVSTiENCg0KTm93IGl04oCZcyB5b3VyIHR1cm4hIEFsdGhvdWdoIHNvbWUgb2YgdGhpcyBtaWdodCBub3QgYmUgYXBwbGljYWJsZSB0byB5b3VyIHRoZXNpcyByZXNlYXJjaCwgdHJ5IHRoaXMgaW5mb3JtYXRpb24gb3V0IG9uIGEgZGF0YXNldCBvZiB5b3VyIGNob2ljZSBmcm9tIGFueSBvZiB0aGUgc291cmNlcyBhYm92ZS4gSWYgeW91IGNhbiBhcHBseSB0aGlzIHRvIHlvdXIgdGhlc2lzLCB0aGVuIGFkZCBpdCB0byB5b3VyIHdlYnNpdGUhIE90aGVyd2lzZSBjcmVhdGUgYSBuZXcgcmVwb3NpdG9yeSBmb3IgeW91ciBwcmVzZW50YXRpb24gb24gVGh1cnNkYXku