1 Introduction

This exercise will build on the skills acquired in the Mapping Basics and Species Distribution Modeling exercises to perform Home Range Analysis and map the results. We will start by examining the structure of data used in home range analyses, we will look at ways to perform QA/QC checks on the location data, and finally various ways to visualize the data. One of the new skills we will discuss in this exercise is to create functions and loops to replicate analyses.

1.1 Packages used in this exercise

There are several specialty packages that will be used in this exercise due to the specific nature of the analyses. Some of these packages you will need to install while several others we have used in previous exercises and should already be installed.

|adehabitatHR | data.table | ggfortify | grid| move | moveVis | OpenStreetMap |
|pbapply | plotly | rgdal | sp | tidyverse | viridis |

To begin, we will install the following:

packages<-c("adehabitatHR","data.table","ggfortify","grid","move","moveVis","OpenStreetMap","pbapply","plotly","rgdal","sp","tidyverse","viridis")
sapply(packages, library, character.only=T)

2 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")
head(data)

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.

MCP Analysis Example KDE Analysis Example

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.

3 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.

3.1 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"))

3.2 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.

3.2.1 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.

3.2.2 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.

3.2.3 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.

3.3 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.

4 YOUR TURN!

Now it’s your turn! Although some of this might not be applicable to your thesis research, try this information out on a dataset of your choice from any of the sources above. If you can apply this to your thesis, then add it to your website! Otherwise create a new repository for your presentation on Thursday.

LS0tDQp0aXRsZTogSG9tZSBSYW5nZSBBbmFseXNpcyA8YnI+PHNtYWxsPkFkdmFuY2VkIERhdGEgQW5hbHl0aWNzPC9zbWFsbD48L2JyPg0KYXV0aG9yOiAiQXVzdGluIFBlYXkgU3RhdGUgVW5pdmVyc2l0eSINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICByb3dzLnByaW50OiAxMA0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIGhpZ2hsaWdodDogYnJlZXplZGFyaw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IG5vDQogICAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHJvd3MucHJpbnQ6IDEwDQogICAgdGhlbWU6IGNvc21vDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCiAgbW9kZTogZ2ZtDQotLS0NCg0KYGBgez1odG1sfQ0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KaDEudGl0bGUgew0KICBmb250LXNpemU6IDQwcHg7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogQmxhY2s7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCg0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgZm9udC1zaXplOiAyNXB4Ow0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiAjRDAyMzQ5Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQoNCmJvZHkgew0KICBmb250LWZhbWlseTogSGVsdmV0aWNhOw0KICBmb250LXNpemU6IDEycHQ7DQp9DQoNCi56b29tIHsNCiAgdHJhbnNmb3JtLW9yaWdpbjogNDAlIDUwJSAwOw0KICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gLjJzOw0KICBtYXJnaW46IDAgYXV0bzsNCn0NCi56b29tIGltZ3sNCgl3aWR0aDphdXRvOw0KCWhlaWdodDphdXRvOwkNCn0NCi56b29tOmhvdmVyIHsNCiAgdHJhbnNmb3JtOiBzY2FsZSgyKTsNCn0NCg0KdGgsIHRkIHtwYWRkaW5nOiA1cHg7fQ0KDQo8L3N0eWxlPg0KYGBgDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoaXMgZXhlcmNpc2Ugd2lsbCBidWlsZCBvbiB0aGUgc2tpbGxzIGFjcXVpcmVkIGluIHRoZSBbTWFwcGluZyBCYXNpY3NdKGh0dHBzOi8vY2hyaXNtZ2VudHJ5LmdpdGh1Yi5pby9NYXBwaW5nLUJhc2ljcy8pIGFuZCBbU3BlY2llcyBEaXN0cmlidXRpb24gTW9kZWxpbmddKGh0dHBzOi8vY2hyaXNtZ2VudHJ5LmdpdGh1Yi5pby9EaXN0cmlidXRpb24tTWFwcy8pIGV4ZXJjaXNlcyB0byBwZXJmb3JtICoqSG9tZSBSYW5nZSBBbmFseXNpcyoqIGFuZCBtYXAgdGhlIHJlc3VsdHMuIFdlIHdpbGwgc3RhcnQgYnkgZXhhbWluaW5nIHRoZSBzdHJ1Y3R1cmUgb2YgZGF0YSB1c2VkIGluIGhvbWUgcmFuZ2UgYW5hbHlzZXMsIHdlIHdpbGwgbG9vayBhdCB3YXlzIHRvIHBlcmZvcm0gUUEvUUMgY2hlY2tzIG9uIHRoZSBsb2NhdGlvbiBkYXRhLCBhbmQgZmluYWxseSB2YXJpb3VzIHdheXMgdG8gdmlzdWFsaXplIHRoZSBkYXRhLiBPbmUgb2YgdGhlIG5ldyBza2lsbHMgd2Ugd2lsbCBkaXNjdXNzIGluIHRoaXMgZXhlcmNpc2UgaXMgdG8gY3JlYXRlIGZ1bmN0aW9ucyBhbmQgbG9vcHMgdG8gcmVwbGljYXRlIGFuYWx5c2VzLiANCg0KIyMgUGFja2FnZXMgdXNlZCBpbiB0aGlzIGV4ZXJjaXNlDQoNClRoZXJlIGFyZSBzZXZlcmFsIHNwZWNpYWx0eSBwYWNrYWdlcyB0aGF0IHdpbGwgYmUgdXNlZCBpbiB0aGlzIGV4ZXJjaXNlIGR1ZSB0byB0aGUgc3BlY2lmaWMgbmF0dXJlIG9mIHRoZSBhbmFseXNlcy4gU29tZSBvZiB0aGVzZSBwYWNrYWdlcyB5b3Ugd2lsbCBuZWVkIHRvIGluc3RhbGwgd2hpbGUgc2V2ZXJhbCBvdGhlcnMgd2UgaGF2ZSB1c2VkIGluIHByZXZpb3VzIGV4ZXJjaXNlcyBhbmQgc2hvdWxkIGFscmVhZHkgYmUgaW5zdGFsbGVkLg0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCnxgYGBhZGVoYWJpdGF0SFJgYGAgfCBgYGBkYXRhLnRhYmxlYGBgIHwgYGBgZ2dmb3J0aWZ5YGBgIHwgYGBgZ3JpZGBgYHwgYGBgbW92ZWBgYCB8DQpgYGBtb3ZlVmlzYGBgIHwgYGBgT3BlblN0cmVldE1hcGBgYCB8PGJyPjwvYnI+IHxgYGBwYmFwcGx5YGBgIHwNCmBgYHBsb3RseWBgYCB8IGBgYHJnZGFsYGBgIHwgYGBgc3BgYGAgfCBgYGB0aWR5dmVyc2VgYGAgfCBgYGB2aXJpZGlzYGBgIHwNCjwvcD4NCg0KVG8gYmVnaW4sIHdlIHdpbGwgaW5zdGFsbCB0aGUgZm9sbG93aW5nOg0KDQpgYGB7ciBQYWNrYWdlcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGFja2FnZXM8LWMoImFkZWhhYml0YXRIUiIsImRhdGEudGFibGUiLCJnZ2ZvcnRpZnkiLCJncmlkIiwibW92ZSIsIm1vdmVWaXMiLCJPcGVuU3RyZWV0TWFwIiwicGJhcHBseSIsInBsb3RseSIsInJnZGFsIiwic3AiLCJ0aWR5dmVyc2UiLCJ2aXJpZGlzIikNCnNhcHBseShwYWNrYWdlcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHk9VCkNCmBgYA0KDQojIERhdGFzZXQNCg0KV2hpbGUgd2UgaGF2ZSBzZWVuIHRoYXQgYmlvbG9naWNhbCBkYXRhIGNhbiBiZSBvYnRhaW5lZCBmcm9tIHNpdGVzIHN1Y2ggYXMgW0RyeWFkXShodHRwczovL2RhdGFkcnlhZC5vcmcvc2VhcmNoKSBhbmQgW0dCSUZdKGh0dHBzOi8vd3d3LmdiaWYub3JnLyksIHdlIG5lZWQgdG8gb2J0YWluIGRhdGEgd2l0aCBzcGVjaWZpYyB2YXJpYWJsZXMgdGhhdCBtaWdodCBub3QgYmUgYXZhaWxhYmxlIG9uIHRob3NlIHNpdGVzLiBTbyB0byBzdGFydCB0aGlzIGV4ZXJjaXNlLCB3ZSB3aWxsIGRvd25sb2FkIGEgXCouY3N2IGZpbGUgY29udGFpbmluZyBpbmZvcm1hdGlvbiBmb3IgKk9waGlvcGhhZ3VzIGhhbm5haCogKEtpbmcgQ29icmEpIGZyb20gW21vdmViYW5rLm9yZ10oaHR0cHM6Ly93d3cubW92ZWJhbmsub3JnL3BhbmVsX2VtYmVkZGVkX21vdmViYW5rX3dlYmFwcD9nd3RfZnJhZ21lbnQ9cGFnZSUzRHNlYXJjaF9tYXBfbGlua2VkJTJDaW5kaXZpZHVhbElkcyUzRDU1NjU3NDM2NiUyQjU1NjU3NDM3NyUyQ2xhdCUzRDE0LjU0NjQ5MTk2ODQ2NDEzMyUyQ2xvbiUzRDEwMS45NDg2OTI3OTI2MTQ5NSUyQ3olM0QxMSkgdGhhdCBjb250YWlucyB0aGUgYXBwcm9wcmlhdGUgdmFyaWFibGUgdG8gdHJhY2sgbW92ZW1lbnQgZnJvbSByYWRpbyB0cmFuc21pdHRlciBkYXRhLiBCZWNhdXNlIHRoaXMgZGF0YSBoYXMgdG8gYmUgZG93bmxvYWRlZCB3ZSB3aWxsIG5lZWQgdG8gZ28gdGhyb3VnaCB0aGUgcHJvY2VzcyBvZiBpbXBvcnRpbmcgdGhpcyBpbmZvcm1hdGlvbiB1c2luZyB0aGUgYGBgcmVhZC5jc3YoKWBgYCBjb21tYW5kLiBPbmNlIHdlIGhhdmUgaW1wb3J0ZWQgdGhlIGRhdGEgd2UgY2FuIHZpZXcgdGhlIHN0cnVjdHVyZS4gDQoNCj4gSWYgZm9ya2VkLCB0aGlzIGRhdGEgd2lsbCBiZSBhdmFpbGFibGUgaW4gdGhlIERhdGEgZm9sZGVyIHdpdGhpbiB0aGUgcmVwb3NpdG9yeS4gQWRkaXRpb25hbGx5LCBFU1JJIFNoYXBlZmlsZSBhbmQgR29vZ2xlIEtNWiBmb3JtYXRzIHdpbGwgYmUgaW5jbHVkZWQgaWYgeW91IGFyZSBpbnRlcmVzdGVkIGluIHdvcmtpbmcgd2l0aCB0aG9zZSBkYXRhIHR5cGVzLg0KDQo8ZGV0YWlscz48c3VtbWFyeT48YmlnPkltcG9ydCBEYXRhc2V0IHdpdGggYGBgcmVhZC5jc3ZgYGA8L2JpZz48L3N1bW1hcnk+DQpgYGB7ciBkYXRhLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkYXRhIDwtIHJlYWQuY3N2KCIuL0RhdGEvb3BoaW9waGFndXNfaGFubmFoLmNzdiIpDQpgYGANCjwvZGV0YWlscz4NCmBgYHtyIHN0cnVjdHVyZSwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KaGVhZChkYXRhKQ0KYGBgDQoNCk5vdGljZSB0aGF0IHRoaXMgZGF0YXNldCBjb250YWlucyBtb3JlIHRoYW4gc3BlY2llcyBpbmZvcm1hdGlvbiBhbmQgeCx5IGNvb3JkaW5hdGVzLiBXaGlsZSB0aGF0IGluZm9ybWF0aW9uIGlzIGltcG9ydGFudCwgdGhpcyBkYXRhc2V0IGFsc28gY29udGFpbnMgdGltZSBhbmQgZGF0ZSBpbmZvcm1hdGlvbiAodGltZXN0YW1wKSwgc2Vuc29yIHR5cGUsIGFuZCBVVE0gZGF0YS4gSW4gb3JkZXIgdG8gYmUgc3VyZSB0aGF0IHRoZSBkYXRhc2V0IGNvbnRhaW5zIG5vIG91dGxpZXJzLCB3ZSBjYW4gcGxvdCB0aGUgZGF0YSB1c2luZyBgYGBnZ3Bsb3RgYGAgYW5kIGBgYHBsb3RseWBgYCB0byBpbnRlcmFjdGl2ZWx5IHZpZXcgdGhlIGRhdGEuDQoNCmBgYHtyIHBsb3RseSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcWFxY19wbG90IDwtIGdncGxvdCgpICsgZ2VvbV9wb2ludChkYXRhPWRhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXModXRtLmVhc3RpbmcsdXRtLm5vcnRoaW5nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9aW5kaXZpZHVhbC5sb2NhbC5pZGVudGlmaWVyKSkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgbGFicyh4PSJFYXN0aW5nIiwgeT0iTm9ydGhpbmciKSArDQogICAgICAgICAgICAgICAgICAgICAgICBndWlkZXMoY29sb3I9Z3VpZGVfbGVnZW5kKCJJZGVudGlmaWVyIikpDQoNCmdncGxvdGx5KHFhcWNfcGxvdCkNCmBgYA0KDQpXaXRoIGBgYHBsb3RseWBgYCwgc2ltaWxhciB0byBgYGBsZWFmbGV0YGBgLCB3ZSBoYXZlIHRoZSBhYmlsaXR5IHRvIGV4YW1pbmUgdGhlIHNwcmVhZCBvZiB0aGUgZGF0YSBwb2ludHMgYW5kIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gZnJvbSB2YXJpb3VzIGNvbHVtbnMuIEZyb20gdGhpcyBwbG90IHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgdHdvIGluZGl2aWR1YWxzLCBPUEhBMSBhbmQgT1BIQTIsIHRoYXQgd2VyZSB0cmFja2VkIGZvciB0aGlzIHN0dWR5IGFuZCBoYXZlIHZlcnkgbGl0dGxlIG92ZXJsYXAgaW4gdGhlaXIgYXBwYXJlbnQgcmFuZ2UuDQoNCldoaWxlIHdlIGNvdWxkIGNvbnRpbnVlIHdpdGggdGhlIGN1cnJlbnQgZGF0YXNldCwgYW55IGFuYWx5c2lzIHdvdWxkIGNhbGN1bGF0ZSBob21lIHJhbmdlIGZvciB0aGUgZW50aXJlIHBvcHVsYXRpb24gcmF0aGVyIHRoYW4gdGhlIGluZGl2aWR1YWwuIA0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCk1DUCBBbmFseXNpcyBFeGFtcGxlIHwgS0RFIEFuYWx5c2lzIEV4YW1wbGUNCi0gfCAtIA0KIVtdKC4vbWNwX2FsbC5wbmcgIk1DUCBmb3IgYWxsIHBvaW50cyIpIHwgIVtdKC4va2RlX2FsbC5wbmcgIktERSBmb3IgYWxsIHBvaW50cyIpDQo8L3A+DQoNClNvIGhlcmUgd2Ugd2lsbCBjcmVhdGUgYSBmdW5jdGlvbiBhbmQgdXNlIHRoZSBgYGBsYXBwbHlgYGAgY29tbWFuZCB0byBhcHBseSBhIGZ1bmN0aW9uIG92ZXIgYSBsaXN0IG9yIHZlY3RvciBkYXRhc2V0LiBTcGVjaWZpY2FsbHksIHRoaXMgZnVuY3Rpb24gd2lsbCB0YWtlIHRoZSBvcmlnaW5hbCAqZGF0YSpzZXQsIHNwbGl0IGl0IGludG8gc2VwYXJhdGUgZmlsZXMgYmFzZWQgb24gdGhlIGluZGl2aWR1YWwgaWRlbnRpZmllciwgYW5kIGNyZWF0ZSBuZXcgXCouY3N2IGZpbGVzIHVzaW5nIHRoZSBpZGVudGlmaWVyIGFzIHRoZSBmaWxlbmFtZS4gDQoNCmBgYHtyIGxhcHBseSBmdW5jdGlvbiwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmxhcHBseShzcGxpdChkYXRhLCBkYXRhJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllciksIA0KICAgICAgIGZ1bmN0aW9uKHgpd3JpdGUuY3N2KHgsIGZpbGUgPSBwYXN0ZSh4JGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllclsxXSwiLmNzdiIsIHNlcCA9ICIiKSwgcm93Lm5hbWVzID0gRkFMU0UpKQ0KYGBgDQoNClRoZSBhbmF0b215IG9mIHRoZSBmdW5jdGlvbiBpcyBhcyBmb2xsb3dzOg0KDQotIGBgYGxhcHBseSgpYGBgLCBhcHBseSB0aGUgZnVuY3Rpb24gb3ZlciBhIGxpc3QNCi0gYGBgc3BsaXQoKWBgYCwgc2VwYXJhdGVzIHRoZSBkYXRhIA0KLSBgYGBmdW5jdGlvbigpYGBgLCBjb21wb3NlIGEgc2VyaWVzIG9mIHN0ZXBzIHRvIGJlIGFwcGxpZWQgdG8gdGhlIGRhdGENCi0gYGBgd3JpdGUuY3N2KClgYGAsIHdyaXRlIGEgY3N2IGZpbGUNCi0gYGBgcGFzdGUoKWBgYCwgY3JlYXRlIGEgY2hhcmFjdGVyIHN0cmluZyB0byBiZSB1c2VkIGZvciB0aGUgZmlsZSBuYW1lDQoNCklmIHlvdSBleGFtaW5lIHRoZSByb290IGZvbGRlciB5b3Ugd2lsbCBub3cgZmluZCB0aGUgYWRkaXRpb24gb2YgdHdvIG5ldyBcKi5jc3YgZmlsZXM6IE9QSEExIGFuZCBPUEhBMi4gV2Ugd2lsbCB1c2UgdGhlc2UgZmlsZXMgdG8gcnVuIG91ciBob21lIHJhbmdlIGFuYWx5c2VzLiBBbHRlcm5hdGl2ZWx5LCBpZiB5b3UgYXJlIHByb3ZpZGVkIGEgbnVtYmVyIG9mIGluZGl2aWR1YWwgXCouY3N2IGZpbGVzIGFuZCBuZWVkIHRvIHF1aWNrbHkgaW1wb3J0IHRoZSBkYXRhIGludG8gc2VwYXJhdGUgZGF0YSBmcmFtZXMgeW91IGNvdWxkIHdyaXRlIGEgKipmb3IgbG9vcCoqIHRoYXQgd291bGQgbG9vayBzb21ldGhpbmcgbGlrZSB0aGlzOg0KDQpgYGANCmxpc3QgPC0gZ3N1YigiXFwuY3N2JCIsIiIsIGxpc3QuZmlsZXMocGF0dGVybj0iXFwuY3N2JCIpKQ0KDQpmb3IoaSBpbiBsaXN0KXsNCiAgYXNzaWduKGksIHJlYWQuY3N2KHBhc3RlKGksICIuY3N2Iiwgc2VwPSIiKSkpDQp9DQpgYGANCg0KQWxsIG9mIHRoZSBcKi5jc3YgZmlsZXMgaW4geW91ciByb290IGRpcmVjdG9yeSB3aWxsIG5vdyBiZSBhdmFpbGFibGUgaW4geW91ciBnbG9iYWwgZW52aXJvbm1lbnQuIEhvd2V2ZXIsIHNpbmNlIHdlIHVzZWQgYSBsb29waW5nIGZ1bmN0aW9uIHRvIGNyZWF0ZSBpbmRpdmlkdWFsIFwqLmNzdiBmaWxlcywgd2Ugd2lsbCBuZWVkIHRvIG1ha2UgYSBsaXN0IG9mIHRob3NlIGluZGl2aWR1YWwgZmlsZXMgY3JlYXRlZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbiB0byByZWZlciBiYWNrIHRvIGR1cmluZyB0aGUgYW5hbHlzaXMgd2hlbiBuZWNlc3NhcnkuDQoNCmBgYHtyIGxpc3QsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiLiIsIHBhdHRlcm4gPSAiW09QSEFdK1swLTldKyIsIGZ1bGwubmFtZXMgPSBUUlVFKQ0KYGBgDQoNCkluIHRoZSBgYGBsaXN0LmZpbGVzYGBgIGNvbW1hbmQgYWJvdmUsIGBgYHBhdGggPSAiLiJgYGAgaW5mb3JtcyB0aGUgbG9jYXRpb25zLCBpbiB0aGlzIGNhc2UgdGhlIHJvb3QgZGlyZWN0b3J5LCBgYGBwYXR0ZXJuID1gYGAgZGVzY3JpYmVzIHRoZSB3YXkgdGhlIGZpbGVzIGFyZSBuYW1lZCwgaW4gdGhpcyBjYXNlIE9QSEEgZm9sbG93ZWQgYnkgYSBudW1iZXIgYmV0d2VlbiAwLTksIGFuZCBgYGBmdWxsLm5hbWVzYGBgIGRlc2NyaWJlcyBob3cgdGhlIGZpbGVzIHdpbGwgYmUgbGlzdGVkLiANCg0KIyBBbmFseXNpcw0KDQpUbyBkYXRlLCB0aGlzIHdpbGwgYmUgb25lIG9mIHRoZSBtb3N0IGNvZGUgaW50ZW5zaXZlIGV4ZXJjaXNlcyB3ZSBoYXZlIGF0dGVtcHRlZC4gV2hpbGUgdGhlIGNvZGUgaXRzZWxmIGlzIG5vdCBkaWZmaWN1bHQsIHRoZSBpbXBsZW1lbnRhdGlvbiBpcyB0ZWRpb3VzIGR1ZSB0byB0aGUgdmFyeWluZyBkYXRhIHR5cGVzIGFuZCBhbmFseXNlcyBwZXJmb3JtZWQuDQoNCiMjIEltYWdlcnkNCg0KQXMgd2UgaGF2ZSBpbiBwcmV2aW91cyBleGVyY2lzZXMgd2Ugd2lsbCB1c2UgcmFzdGVyIGltYWdlcnkgdG8gcHJvdmlkZSBhZGRpdGlvbmFsIHNwYXRpYWwgZGV0YWlsIHRvIHRoZSBhbmFseXNpcy4gV2hpbGUgdGhpcyBpbmZvcm1hdGlvbiBjYW4gY29tZSBmcm9tIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBzb3VyY2VzLCB3ZSB3aWxsIGFnYWluIHVzZSBgYGBvcGVuc3RyZWV0bWFwYGBgIGZvciB0aGlzIGV4YW1wbGUuIEZvciB0aG9zZSB1c2luZyBNYWNzIHdpdGggamF2YSByZXN0cmljdGlvbnMsIHVzZSB5b3VyIHByZWZlcnJlZCBzb3VyY2UgZm9yIGltYWdlcnkuDQoNCkluIG9yZGVyIHRvIGNyZWF0ZSBhIGJvdW5kaW5nIGJveCBmb3IgdGhlIGltYWdlcnkgd2Ugd2lsbCB1c2UgdGhlIGBgYG1pbmBgYCBhbmQgYGBgbWF4YGBgIHZhbHVlcyBmcm9tIHRoZSBjb29yZGluYXRlcyBpbiB0aGUgKmRhdGEqc2V0IHRvIGNvbXB1dGUgdGhvc2UgcG9pbnRzLiBBbHRob3VnaCB0aGUgZGF0YSBvYnRhaW5lZCBmcm9tIFt3d3cubW92ZWJhbmsub3JnXShodHRwOi8vd3d3Lm1vdmViYW5rLm9yZykgY29udGFpbmVkIGJvdGggbG9uZ2l0dWRlL2xhdGl0dWRlIGFuZCBVVE0gdmFsdWVzLCB0aGlzIGV4YW1wbGUgd2lsbCBkZXRhaWwgaG93IHRvIGNvbnZlcnQgVVRNIHRvIGxvbmdpdHVkZS9sYXRpdHVkZSBjb29yZGluYXRlcyBpbiBvcmRlciB0byB1c2UgYGBgb3BlbnN0cmVldG1hcGBgYCwgYW5kIHRoZSByYXN0ZXIgaW1hZ2VyeSBmcm9tIGxvbmdpdHVkZS9sYXRpdHVkZSB0byBVVE0uIFRoaXMgd2lsbCBiZSB1c2VmdWwgaWYgeW91IHJlY2VpdmUgZGF0YSBvbmx5IHdpdGggVVRNIHZhbHVlcy4gSWYgeW91ciBkYXRhIGNvbnRhaW5zIGxvbmdpdHVkZS9sYXRpdHVkZSB2YWx1ZXMgdGhlcmUgaXMgbm8gY29udmVyc2lvbiByZXF1aXJlZC4NCg0KYGBge3IgaW1hZ2VyeSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQ0KdXRtX3BvaW50cyA8LSBjYmluZChkYXRhJHV0bS5lYXN0aW5nLCBkYXRhJHV0bS5ub3J0aGluZykNCnV0bV9sb2NhdGlvbnMgPC0gU3BhdGlhbFBvaW50cyh1dG1fcG9pbnRzLCANCiAgICAgICAgICAgICAgICAgcHJvajRzdHJpbmc9Q1JTKCIrcHJvaj11dG0gK3pvbmU9NDcgK2RhdHVtPVdHUzg0IikpDQpwcm9qX2xhdC5sb24gPC0gYXMuZGF0YS5mcmFtZShzcFRyYW5zZm9ybSgNCiAgICAgICAgICAgICAgICB1dG1fbG9jYXRpb25zLCBDUlMoIitwcm9qPWxvbmdsYXQgK2RhdHVtPVdHUzg0IikpKQ0KY29sbmFtZXMocHJval9sYXQubG9uKSA8LSBjKCJ4IiwieSIpDQpyYXN0ZXIgPC0gb3Blbm1hcChjKG1heChwcm9qX2xhdC5sb24keSkrMC4wMSwgbWluKHByb2pfbGF0LmxvbiR4KS0wLjAxKSwgDQogICAgICAgICAgICAgICAgICBjKG1pbihwcm9qX2xhdC5sb24keSktMC4wMSwgbWF4KHByb2pfbGF0LmxvbiR4KSswLjAxKSwgDQogICAgICAgICAgICAgICAgICB0eXBlID0gImJpbmciKQ0KcmFzdGVyX3V0bSA8LSBvcGVucHJvaihyYXN0ZXIsIA0KICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gIitwcm9qPXV0bSArem9uZT00NyArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKQ0KYGBgDQoNCkluIHRoZSBzY3JpcHQgYWJvdmUsIGBgYHV0bV9wb2ludGBgYCBpcyBhbiB4LHkgZGVyaXZlZCBmcm9tIHRoZSBwcmltYXJ5IGRhdGFzZXQsIGBgYHV0bV9sb2NhdGlvbnNgYGAgc2V0IHRoZSBwcm9qZWN0aW9uIHRvICoqVVRNIFpvbmUgNDcqKiwgYGBgcHJval9sYXQubG9uYGBgIGNvbnZlcnRlZCB0aGUgVVRNIHBvaW50cyB0byBsb25naXR1ZGUvbGF0aXR1ZGUsIGBgYHJhc3RlcmBgYCB1c2VzIHRoZSBtaW4vbWF4IHgseSBkYXRhIHRvIGNyZWF0ZSBhIGJvdW5kaW5nIGJveCB0byByZXRyaWV2ZSB0aGUgYWVyaWFsIGltYWdlcnksIGFuZCBgYGByYXN0ZXJfdXRtYGBgIHJlcHJvamVjdGVkIHRoZSBpbWFnZXJ5IGJhY2sgdG8gKipVVE0gWm9uZSA0NyoqIGNvbnNpc3RlbnQgd2l0aCB0aGUgbG9jYXRpb24gaW4gVGhhaWxhbmQuIE5vdyB3ZSBjYW4gdXNlIGBgYGF1dG9wbG90Lk9wZW5TdHJlZXRNYXBgYGAgdG8gZGlzcGxheSB0aGUgcmFzdGVyIGltYWdlIGZpbGUgd2l0aCB0aGUgVVRNIGxvY2F0aW9ucyBhcyBhbiBvdmVybGF5Lg0KDQpgYGB7ciBpbWFnZXJ5IHBsb3QsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89VFJVRSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9Nn0NCmF1dG9wbG90Lk9wZW5TdHJlZXRNYXAocmFzdGVyX3V0bSwgZXhwYW5kID0gVFJVRSkgKyB0aGVtZV9idygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTEpKSArDQogIGdlb21fcG9pbnQoZGF0YT1kYXRhLCBhZXModXRtLmVhc3RpbmcsdXRtLm5vcnRoaW5nLA0KICAgICAgICAgICAgIGNvbG9yPWluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllciksIHNpemUgPSAzLCBhbHBoYSA9IDAuOCkgKw0KICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2U9ImJvbGQiKSkgKyBsYWJzKHg9IkVhc3RpbmciLA0KICAgICAgICB5PSJOb3J0aGluZyIpICsgZ3VpZGVzKGNvbG9yPWd1aWRlX2xlZ2VuZCgiSWRlbnRpZmllciIpKQ0KYGBgDQoNCiMjIEhvbWUgUmFuZ2UgQW5hbHlzaXMNCg0KVGhlcmUgYXJlIHRocmVlIGJhc2ljIHR5cGVzIG9mIGhvbWUgcmFuZ2UgYW5hbHlzZXMgd2Ugd2lsbCBwZXJmb3JtIGluIHRoaXMgZXhlcmNpc2U6IE1pbmltdW0gQ29udmV4IFBvbHlnb24gKE1DUCksIEtlcm5lbC1EZW5zaXR5IEVzdGltYXRpb24gKEtERSksIGFuZCBCcm93bmlhbiBCcmlkZ2UgTW92ZW1lbnQgTW9kZWwgKEJCKS4gVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGRpZmZlcmVudCB0dW5pbmcgcGFyYW1ldGVycyB0aGF0IGNhbiBiZSBhcHBsaWVkIHRoZXNlIGFuYWx5c2VzLCBob3dldmVyIGluIHRoaXMgZXhlcmNpc2Ugd2Ugd2lsbCB1c2UgdGhlIG1vc3QgYmFzaWMgdmVyc2lvbnMgb2YgdGhlIGFuYWx5c2lzLiANCg0KSW4gdGhlIHNlY3Rpb24gYWJvdmUgd2UgdXNlIHRoZSBgYGBsYXBwbHlgYGAgY29tbWFuZCB0byBsb29wIGEgZnVuY3Rpb24gdXNlZCB0byBzZXBhcmF0ZSB0aGUgb3JpZ2luYWwgZGF0YXNldCBpbnRvIGluZGl2aWR1YWwgZmlsZXMuIFRoaXMgaXMgYSB1c2VmdWwgdG9vbCwgaG93ZXZlciwgd2hlbiB0aGUgZnVuY3Rpb24gbG9vcHMgdGhyb3VnaCBkb3plbnMgb3IgZXZlbiBodW5kcmVkcyBvZiBmaWxlcywgdGhlIHByb2Nlc3MgY2FuIHRha2UgYSBsb25nIHBlcmlvZCBvZiB0aW1lIHRvIGNvbXBsZXRlLiBVc2luZyB0aGUgYGBgcGJsYXBwbHlgYGAgY29tbWFuZCBhZGRzIGEgcHJvZ3Jlc3MgYmFyIChpLmUuICoqcGIqKikgdG8gdGhlIHByb2Nlc3Mgd2hpY2ggcHJvdmlkZXMgYW4gZXN0aW1hdGVkIHRpbWUgZm9yIGNvbXBsZXRpb24gb2YgdGhlIGZ1bmN0aW9uLiBXZSB3aWxsIHVzZSBhIHNpbWlsYXIgcHJvY2VzcyB0byB0aGUgb25lIGFib3ZlLCB1c2luZyB0aGUgYGBgcGJsYXBwbHlgYGAgY29tbWFuZCB0byBydW4gTUNQIGFuYWx5c2lzIG9uIHRoZSBpbmRpdmlkdWFsIFwqLmNzdiBmaWxlcy4gDQoNCiMjIyBNaW5pbXVtIENvbnZleCBQb2x5Z29uDQoNCkEgZGVzY3JpcHRpb24gb2YgdGhlIHN0ZXBzIHdpdGhpbiB0aGUgZm9sbG93aW5nIGNvZGUgd2lsbCBiZSBkaXNjdXNzZWQgZm9sbG93aW5nIHRoZSBvdXRwdXQuIFRoZSBwcm9jZXNzIHdvcmtzIGJ5IGVzdGFibGlzaGluZyB0aGUgZnVuY3Rpb24gYW5kIHRoZW4gcnVubmluZyBgYGBwYmxhcHBseWBgYCByZWZlcnJpbmcgYmFjayB0byB0aGUgKipmaWxlcyoqIHdlIGNyZWF0ZWQgaW4gdGhlIGRhdGFzZXQgcG9ydGlvbiBvZiB0aGlzIGV4ZXJjaXNlLg0KDQpgYGB7ciBNQ1AgcGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0KbWNwX3Jhc3RlciA8LSBmdW5jdGlvbihmaWxlbmFtZSl7DQogIGRhdGEgPC0gcmVhZC5jc3YoZmlsZSA9IGZpbGVuYW1lKQ0KICB4IDwtIGFzLmRhdGEuZnJhbWUoZGF0YSR1dG0uZWFzdGluZykNCiAgeSA8LSBhcy5kYXRhLmZyYW1lKGRhdGEkdXRtLm5vcnRoaW5nKQ0KICB4eSA8LSBjKHgseSkNCiAgZGF0YS5wcm9qIDwtIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoeHksZGF0YSwgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPXV0bSArem9uZT0xMiArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKSkNCiAgeHkgPC0gU3BhdGlhbFBvaW50cyhkYXRhLnByb2pAY29vcmRzKQ0KICBtY3Aub3V0IDwtIG1jcCh4eSwgcGVyY2VudD0xMDAsIHVub3V0PSJoYSIpDQogIG1jcC5wb2ludHMgPC0gY2JpbmQoKGRhdGEuZnJhbWUoeHkpKSxkYXRhJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllcikNCiAgY29sbmFtZXMobWNwLnBvaW50cykgPC0gYygieCIsInkiLCAiaWRlbnRpZmllciIpDQogIG1jcC5wb2x5IDwtIGZvcnRpZnkobWNwLm91dCwgcmVnaW9uID0gImlkIikNCiAgdW5pdHMgPC0gZ3JpZC50ZXh0KHBhc3RlKHJvdW5kKG1jcC5vdXRAZGF0YSRhcmVhLDIpLCJoYSIpLCB4PTAuODUsICB5PTAuOTUsDQogICAgICAgICAgICAgICAgICAgICBncD1ncGFyKGZvbnRmYWNlPTQsIGNvbD0id2hpdGUiLCBjZXg9MC45KSwgZHJhdyA9IEZBTFNFKQ0KICBtY3AucGxvdCA8LSBhdXRvcGxvdC5PcGVuU3RyZWV0TWFwKHJhc3Rlcl91dG0sIGV4cGFuZCA9IFRSVUUpICsgdGhlbWVfYncoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsNCiAgICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImJsYWNrIiwgZmlsbD1OQSwgc2l6ZT0xKSkgKw0KICAgIGdlb21fcG9seWdvbihkYXRhPW1jcC5wb2x5LCBhZXMoeD1tY3AucG9seSRsb25nLCB5PW1jcC5wb2x5JGxhdCksIGFscGhhPTAuOCkgKw0KICAgIGdlb21fcG9pbnQoZGF0YT1tY3AucG9pbnRzLCBhZXMoeD14LCB5PXkpKSArIA0KICAgIGxhYnMoeD0iRWFzdGluZyAobSkiLCB5PSJOb3J0aGluZyAobSkiLCB0aXRsZT1tY3AucG9pbnRzJGlkZW50aWZpZXIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkgKyANCiAgICBhbm5vdGF0aW9uX2N1c3RvbSh1bml0cykNCiAgbWNwLnBsb3QNCn0NCg0KcGJsYXBwbHkoZmlsZXMsIG1jcF9yYXN0ZXIpDQpgYGANCg0KVGhlIGFuYXRvbXkgb2YgdGhlIGZ1bmN0aW9uIGFib3ZlIGlzIGFzIGZvbGxvd3M6DQpgYGBtY3BfcmFzdGVyIDwtIGZ1bmN0aW9uKGZpbGVuYW1lKXsuLi5gYGAgZm9yIGVhY2ggKmZpbGUqIGxpc3RlZCBmcm9tIHRoZSBwcm9qZWN0IGRpcmVjdG9yeSBjb21wbGV0ZSB0aGUgZm9sbG93aW5nIGNvbW1hbmRzDQoNCi0gYGBgcmVhZC5jc3YoKWBgYCwgcmVhZCB0aGUgZGF0YSBmcm9tIGEgZ2l2ZW4gXCouY3N2IGZpbGUNCi0geHksIGNyZWF0ZSBhIGNvb3JkaW5hdGUgZmlsZSBvZiB0aGUgZWFzdGluZyBhbmQgbm9ydGhpbmcgZGF0YQ0KLSBgYGBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lKClgYGAsIHByb2plY3QgdGhlIGRhdGEgdG8gVVRNIFpvbmUgNDcNCi0gYGBgbWNwKClgYGAsIGNvbXB1dGVzIGhvbWUgcmFuZ2UgdXNpbmcgdGhlIE1pbmltdW0gQ29udmV4IFBvbHlnb24gZXN0aW1hdG9yDQotIGBgYGZvcnRpZnkoKWBgYCwgdHVybnMgYSBtYXAgaW50byBhIGRhdGEgZnJhbWUgZm9yIHBsb3R0aW5nIHdpdGggZ2dwbG90Mg0KLSBgYGBncmlkLnRleHQoKWBgYCwgY3JlYXRlcyBhbm5vdGF0aW9ucyBmb3IgdGhlIG1hcDsgaW4gdGhpcyBjYXNlIHRvIHNob3cgYXJlYQ0KLSBgYGBhdXRvcGxvdC5PcGVuU3RyZWV0TWFwKClgYGAsIHBsb3R0aW5nIGZvciByYXN0ZXIgYW5kIHZlY3RvciBkYXRhIGluIGdncGxvdDINCi0gYGBgcGJsYXBwbHkoKWBgYCwgcmVmZXJlbmNpbmcgdGhlIGZpbGVzIGxpc3QgYW5kIGZ1bmN0aW9uDQoNClRoZSByZXN0IG9mIHRoZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgYnkgZXhhbWluZyB0aGUgaGVscCBtZW51IGZvciB0aGUgdmFyaW91cyBjb21tYW5kcyBvciBsb29raW5nIGF0IHRoZSBkaXNwbGF5IG9wdGlvbnMgZm9yIGVhY2ggcG9ydGlvbiBvZiB0aGUgc2NyaXB0Lg0KDQojIyMgS2VybmVsLURlbnNpdHkgRXN0aW1hdGlvbg0KDQpUaGUgc2FtZSBkZXNpZ24gYWJvdmUgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIEtERSBwbG90cyBmb3IgZWFjaCBpbmRpdmlkdWFsIGluIHRoZSBkYXRhc2V0LiBCZWNhdXNlIHRoZSBwcm9jZXNzIGlzIGVzc2VudGlhbGx5IHRoZSBzYW1lLCBvbmx5IHBvcnRpb25zIG9mIHRoZSBmdW5jdGlvbiB0aGF0IHdlcmUgbm90IHByZXZpb3VzbHkgZGVzY3JpYmVkIHdpbGwgYmUgZGlzY3Vzc2VkLg0KDQpgYGB7ciBLREUgcGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0Ka2RlX3Jhc3RlciA8LSBmdW5jdGlvbihmaWxlbmFtZSl7DQogIGRhdGEgPC0gcmVhZC5jc3YoZmlsZSA9IGZpbGVuYW1lKQ0KICB4IDwtIGFzLmRhdGEuZnJhbWUoZGF0YSR1dG0uZWFzdGluZykNCiAgeSA8LSBhcy5kYXRhLmZyYW1lKGRhdGEkdXRtLm5vcnRoaW5nKQ0KICB4eSA8LSBjKHgseSkNCiAgZGF0YS5wcm9qIDwtIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoeHksZGF0YSwgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPXV0bSArem9uZT00NyArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKSkNCiAgeHkgPC0gU3BhdGlhbFBvaW50cyhkYXRhLnByb2pAY29vcmRzKQ0KICBrZGU8LWtlcm5lbFVEKHh5LCBoPSJocmVmIiwga2Vybj0iYml2bm9ybSIsIGdyaWQ9MTAwKQ0KICB2ZXIgPC0gZ2V0dmVydGljZXNocihrZGUsIDk1KQ0KICBrZGUucG9pbnRzIDwtIGNiaW5kKChkYXRhLmZyYW1lKGRhdGEucHJvakBjb29yZHMpKSxkYXRhJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllcikNCiAgY29sbmFtZXMoa2RlLnBvaW50cykgPC0gYygieCIsInkiLCJpZGVudGlmaWVyIikNCiAga2RlLnBvbHkgPC0gZm9ydGlmeSh2ZXIsIHJlZ2lvbiA9ICJpZCIpDQogIHVuaXRzIDwtIGdyaWQudGV4dChwYXN0ZShyb3VuZCh2ZXIkYXJlYSwyKSwiIGhhIiksIHg9MC44NSwgIHk9MC45NSwNCiAgICAgICAgICAgICAgICAgICAgIGdwPWdwYXIoZm9udGZhY2U9NCwgY29sPSJ3aGl0ZSIsIGNleD0wLjkpLCBkcmF3ID0gRkFMU0UpDQogIGtkZS5wbG90IDwtIGF1dG9wbG90Lk9wZW5TdHJlZXRNYXAocmFzdGVyX3V0bSwgZXhwYW5kID0gVFJVRSkgKyB0aGVtZV9idygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKw0KICAgIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTEpKSArDQogICAgZ2VvbV9wb2x5Z29uKGRhdGE9a2RlLnBvbHksIGFlcyh4PWtkZS5wb2x5JGxvbmcsIHk9a2RlLnBvbHkkbGF0KSwgYWxwaGEgPSAwLjgpICsNCiAgICBnZW9tX3BvaW50KGRhdGE9a2RlLnBvaW50cywgYWVzKHg9eCwgeT15KSkgKw0KICAgIGxhYnMoeD0iRWFzdGluZyAobSkiLCB5PSJOb3J0aGluZyAobSkiLCB0aXRsZT1rZGUucG9pbnRzJGlkZW50aWZpZXIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkgKyANCiAgICBhbm5vdGF0aW9uX2N1c3RvbSh1bml0cykNCiAga2RlLnBsb3QNCn0NCg0KcGJsYXBwbHkoZmlsZXMsIGtkZV9yYXN0ZXIpDQpgYGANCg0KVGhlIGFuYXRvbXkgb2YgdGhlIGZ1bmN0aW9uIGFib3ZlIGlzIGFzIGZvbGxvd3M6DQpgYGBrZGVfcmFzdGVyIDwtIGZ1bmN0aW9uKGZpbGVuYW1lKXsuLi5gYGAgZm9yIGVhY2ggKmZpbGUqIGxpc3RlZCBmcm9tIHRoZSBwcm9qZWN0IGRpcmVjdG9yeSBjb21wbGV0ZSB0aGUgZm9sbG93aW5nIGNvbW1hbmRzDQoNCi0gYGBga2VybmVsVUQoKWBgYCwgZXN0aW1hdGlvbiBvZiBrZXJuZWwgaG9tZS1yYW5nZQ0KLSBgYGBnZXR2ZXJ0aWNlc2hyKClgYGAsIGV4dHJhY3QgaG9tZS1yYW5nZSBjb250b3VyDQoNClRoZSByZXN0IG9mIHRoZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgYnkgZXhhbWluZyB0aGUgaGVscCBtZW51IGZvciB0aGUgdmFyaW91cyBjb21tYW5kcyBvciBsb29raW5nIGF0IHRoZSBkaXNwbGF5IG9wdGlvbnMgZm9yIGVhY2ggcG9ydGlvbiBvZiB0aGUgc2NyaXB0Lg0KDQojIyMgQnJvd25pYW4gQnJpZGdlIE1vdmVtZW50DQoNCkFsdGhvdWdoIHRoaXMgYXBwZWFycyB0byBiZSB3b3JraW5nIGJhY2t3YXJkcywgaW4gdGhlIHByZXZpb3VzIHR3byBleGFtcGxlcyB5b3UgaGF2ZSBzZWVuIGhvdyB0byBjcmVhdGUgYSBmdW5jdGlvbiB0aGF0IGNhbiBiZSBsb29wZWQgdG8gcnVuIHRoZSBhbmFseXNpcyBmb3IgYSBsaXN0IGZvIGZpbGVzLiBGb3IgdGhpcyBhbmFseXNpcywgd2Ugd2lsbCBjcmVhdGUgYSBjb252ZW50aW9uYWwgc2NyaXB0IGZvciBhIHNpbmdsZSBpbmRpdmlkdWFsLiBQb3J0aW9ucyBvZiB0aGUgc2NyaXB0IHRoYXQgd2VyZSBub3QgcHJldmlvdXNseSBkZXNjcmliZWQgd2lsbCBiZSBkaXNjdXNzZWQgZm9sbG93aW5nIHRoZSBhbmFseXNpcy4NCg0KYGBge3IgYmIgcGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0KT1BIQTEgPC0gcmVhZC5jc3YoIk9QSEExLmNzdiIpDQpkYXRlIDwtIGFzLlBPU0lYY3Qoc3RycHRpbWUoYXMuY2hhcmFjdGVyKE9QSEExJHRpbWVzdGFtcCksIiVZLSVtLSVkICVIOiVNOiVTIiwgdHo9IkFzaWEvQmFuZ2tvayIpKQ0KT1BIQTEkZGF0ZSA8LSBkYXRlDQpPUEhBMS5yZWxvYyA8LSBjYmluZC5kYXRhLmZyYW1lKE9QSEExJHV0bS5lYXN0aW5nLCBPUEhBMSR1dG0ubm9ydGhpbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLnZlY3RvcihPUEhBMSRpbmRpdmlkdWFsLmxvY2FsLmlkZW50aWZpZXIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5QT1NJWGN0KGRhdGUpKQ0KY29sbmFtZXMoT1BIQTEucmVsb2MpIDwtIGMoIngiLCJ5IiwiaWQiLCJkYXRlIikNCnRyYWplY3RvcnkgPC0gYXMubHRyYWooT1BIQTEucmVsb2MsIGRhdGU9ZGF0ZSwgaWQ9Ik9QSEExIikNCnNpZzEgPC0gbGlrZXIodHJhamVjdG9yeSwgc2lnMiA9IDU4LCByYW5nZXNpZzEgPSBjKDAsIDUpLCBwbG90aXQgPSBGQUxTRSkNCm9waGEudHJhaiA8LSBrZXJuZWxiYih0cmFqZWN0b3J5LCBzaWcxID0gLjc5MDgsIHNpZzIgPSA1OCwgZ3JpZCA9IDEwMCkNCmJiX3ZlciA8LSBnZXR2ZXJ0aWNlc2hyKG9waGEudHJhaiwgOTUpDQpiYl9wb2x5IDwtIGZvcnRpZnkoYmJfdmVyLCByZWdpb24gPSAiaWQiLCANCiAgICAgICAgICAgICAgICAgICBwcm9qNHN0cmluZyA9IENSUygiK3Byb2o9dXRtICt6b25lPTQ3Kw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdHVtPVdHUzg0ICt1bml0cz1tICtub19kZWZzIikpDQpjb2xuYW1lcyhiYl9wb2x5KSA8LSBjKCJ4IiwieSIsIm9yZGVyIiwiaG9sZSIsInBpZWNlIiwiaWQiLCJncm91cCIpDQpiYl9pbWFnZSA8LSBjcm9wKG9waGEudHJhaiwgYmJfdmVyLCANCiAgICAgICAgICAgICAgICAgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPXV0bSArem9uZT00NyArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdHVtPVdHUzg0ICt1bml0cz1tICtub19kZWZzIikpDQpiYl91bml0cyA8LSBncmlkLnRleHQocGFzdGUocm91bmQoYmJfdmVyJGFyZWEsMiksIiBoYSIpLCB4PTAuODUsICB5PTAuOTUsDQogICAgICAgICAgICAgICAgICAgZ3A9Z3Bhcihmb250ZmFjZT00LCBjb2w9IndoaXRlIiwgY2V4PTAuOSksIGRyYXcgPSBGQUxTRSkNCmJiLnBsb3QgPC0gYXV0b3Bsb3QuT3BlblN0cmVldE1hcChyYXN0ZXJfdXRtLCBleHBhbmQgPSBUUlVFKSArIHRoZW1lX2J3KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTEpKSArDQogIGdlb21fdGlsZShkYXRhPWJiX2ltYWdlLCANCiAgICAgICAgICAgIGFlcyh4PWJiX2ltYWdlQGNvb3Jkc1ssMV0sIHk9YmJfaW1hZ2VAY29vcmRzWywyXSwNCiAgICAgICAgICAgIGZpbGwgPSBiYl9pbWFnZUBkYXRhJHVkKSkgKw0KICBnZW9tX3BvbHlnb24oZGF0YT1iYl9wb2x5LCBhZXMoeD14LCB5PXksIGdyb3VwID0gZ3JvdXApLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSBOQSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiaW5mZXJubyIpICsgYW5ub3RhdGlvbl9jdXN0b20oYmJfdW5pdHMpICsNCiAgbGFicyh4PSJFYXN0aW5nIChtKSIsIHk9Ik5vcnRoaW5nIChtKSIsIHRpdGxlPSJPUEhBMSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSkpDQpiYi5wbG90DQpgYGANCg0KVGhlIGFuYXRvbXkgb2YgdGhlIHNjcmlwdCBhYm92ZSBpcyBhcyBmb2xsb3dzOg0KDQotIGBgYGFzLlBPU0lYY3QoKWBgYCwgbWFuaXB1bGF0ZSBvYmplY3RzIHRvIHJlcHJlc2VudCBjYWxlbmRhciBkYXRlcyBhbmQgdGltZXMNCi0gYGBgY2JpbmQuZGF0YS5mcmFtZSgpYGBgLCB1c2VkIHRvIGNvbWJpbmUgY29sdW1ucyBhdm9pZGluZyBmYWN0b3JpemF0aW9uDQotIGBgYGFzLmx0cmFqYGBgLCBjb252ZXJ0IGRhdGEgdG8gdHJhamVjdG9yeSBjbGFzcw0KLSBgYGBsaWtlcigpYGBgLCB1c2VkIHRvIGZpbmQgdGhlIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0aW9uIG9mIHRoZSBwYXJhbWV0ZXIgc2lnMSBpbiAqa2VybmVsYmIoKSoNCi0gYGBgc2NhbGVfZmlsbF92aXJpZGlzX2MoKWBgYCwgY3JlYXRlIGNvbG9yIHNjYWxlIGZvciBjb250aW51b3VzIGRhdGENCg0KVGhlIHJlc3Qgb2YgdGhlIGluZm9ybWF0aW9uIGNhbiBiZSBmb3VuZCBieSBleGFtaW5nIHRoZSBoZWxwIG1lbnUgZm9yIHRoZSB2YXJpb3VzIGNvbW1hbmRzIG9yIGxvb2tpbmcgYXQgdGhlIGRpc3BsYXkgb3B0aW9ucyBmb3IgZWFjaCBwb3J0aW9uIG9mIHRoZSBzY3JpcHQuDQoNCj4gVHJ5IGl0IHlvdXJzZWxmISBVc2luZyB0aGUgZXhhbXBsZSBzY3JpcHQgYWJvdmUsIGFuZCBmb2xsb3dpbmcgdGhlIGV4YW1wbGUgZnVuY3Rpb25zLCBjcmVhdGUgYSBsb29waW5nIGZ1bmN0aW9uIHdpdGggYGBgcGJsYXBwbHlgYGAgdG8gcnVuIHRoZSBCQiBhbmFseXNpcyBmb3IgZWFjaCBpbmRpdmlkdWFsIGluIHRoZSBmaWxlcyBsaXN0Lg0KDQojIyBBbmltYXRlIFRyYWplY3RvcnkgRGF0YQ0KDQpUaGUgZmluYWwgYW5hbHlzaXMgd2Ugd2lsbCBwZXJmb3JtIGluIHRoaXMgZXhlcmNpc2UgaXMgYSB2aXN1YWwgYW5pbWF0aW9uIG9mIHRoZSByZWxvY2F0aW9uIGRhdGEuIFVzaW5nIHRoZSBkYXRhIGZyb20gYWJvdmUgeW91IGNhbiBgYGBwbG90KHRyYWplY3RvcnkpYGBgIGFuZCBzZWUgYSBwbG90IG9mIHRoZSByZWxvY2F0aW9uIGluZm9ybWF0aW9uIGZvciBhbiBpbmRpdmlkdWFsLiBIb3dldmVyLCB3ZSBjYW4gdXNlIHRoZSBgYGBtb3ZlYGBgIGFuZCBgYGBtb3ZlVmlzYGBgIHBhY2thZ2VzIHRvIGNyZWF0ZSBhbiBhbmltYXRpb24gb2YgdGhlIHJlbG9jYXRpb25zLiANCg0KV2UgbmVlZCB0byBiZWdpbiBieSBjcmVhdGluZyBhICptb3ZlKiBvYmplY3QgY29udGFpbmluZyByZWxvY2F0aW9ucyAoeCx5KSwgdGltZSBhbmQgZGF0ZSBpbmZvcm1hdGlvbiAodGltZSksIGEgcHJvamVjdGlvbiBzdHJpbmcsIGluZGl2aWR1YWwgaWRlbnRpZmllciAoYW5pbWFsKSwgYW5kIHNlbnNvciB0eXBlIChzZW5zb3IpLiAqU2VlIHRoZSBkZXNjcmlwdGlvbiBmb3IgYGBgbW92ZSgpYGBgIGluIHRoZSBoZWxwIG1lbnUgZm9yIG9wdGlvbmFsIGluZm9ybWF0aW9uLioNCg0KYGBge3IgbW92ZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJ30NCm9waGEubW92ZSA8LSBtb3ZlKHg9T1BIQTEkbG9jYXRpb24ubG9uZywgDQogICAgICAgICAgICAgeT1PUEhBMSRsb2NhdGlvbi5sYXQsIA0KICAgICAgICAgICAgIHRpbWU9YXMuUE9TSVhjdChPUEhBMSR0aW1lc3RhbXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9IiVZLSVtLSVkICVIOiVNOiVTIiwgdHo9IkFzaWEvQmFuZ2tvayIpLCANCiAgICAgICAgICAgICBwcm9qPUNSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0IiksDQogICAgICAgICAgICAgZGF0YT1PUEhBMSwgYW5pbWFsPU9QSEExJGluZGl2aWR1YWwubG9jYWwuaWRlbnRpZmllciwgDQogICAgICAgICAgICAgc2Vuc29yPU9QSEExJHNlbnNvci50eXBlKQ0KYGBgDQoNCldpdGggdGhpcyBpbmZvcm1hdGlvbiwgd2UgY2FuIG5vdyBjcmVhdGUgdGhlIHRpbWluZywgbnVtYmVyIG9mIGZyYW1lcywgYW5kIGFuaW1hdGlvbiBmb3IgdGhlIHJlbG9jYXRpb25zLiBGaXJzdCwgdG8gdW5kZXJzdGFuZCB0aGUgdGltZSBsYWcgYmV0d2VlbiB0aGUgcmVsb2NhdGlvbnMgd2UgY2FuIHVzZSBgYGBtZWRpYW4odGltZUxhZyhvcGhhLm1vdmUsIHVuaXQgPSAibWlucyIpKWBgYCB0byBjYWxjdWxhdGUgdGhlIG1lZGlhbiB2YWx1ZS4gSG93ZXZlciwgaW4gdGhlIGBgYGFsaW5nX21vdmUoKWBgYCBmdW5jdGlvbiB0aGVyIGlzIGFuIG9wdGlvbiB0byBzZXQgdGhlIHVuaWZvcm0gc2NhbGUgbWFudWFsbHkgb3IgYnkgdXNpbmcgYSBmaXhlZCByZXNvbHV0aW9uOiBtaW4sIG1heCwgb3IgbWVhbi4gRm9yIHNpbXBsaWNpdHksIHdlIHdpbGwgdXNlIHRoZSAqKm1heCoqIHRlbXBvcmFsIHJlc29sdXRpb24uDQoNCmBgYHtyIG1vdmVtZW50LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnfQ0KbW92ZW1lbnQgPC0gYWxpZ25fbW92ZShvcGhhLm1vdmUsIHJlcyA9ICJtYXgiLCBkaWdpdCA9IDAsIHVuaXQgPSAic2VjcyIpDQpgYGANCg0KV2l0aCB0aGUgZGF0YSBub3cgb24gYSB1bmlmb3JtIHRpbWUgc2NhbGUgd2UgY2FuIGNyZWF0ZSB0aGUgZnJhbWVzIGFuZCBhbmltYXRpb24gZm9yIHRoZSByZWxvY2F0aW9ucy4gRm9yIHRoaXMgc3RlcCBJIHdpbGwgdXNlIGEgYmFzZW1hcCBmcm9tIFtNYXBCb3hdKGh0dHBzOi8vd3d3Lm1hcGJveC5jb20vKSB3aGljaCByZXF1aXJlIHRva2VuIGFjY2VzcyB0aHJvdWdoIHRoZSB1c2Ugb2YgdGhlaXIgQVBJLiBUbyBkbyB0aGlzIHlvdSBuZWVkIHRvIHJlZ2lzdGVyIHdpdGggTWFwQm94IGFuZCBjcmVhdGUgYW4gYWNjZXNzIHRva2VuLiBUaGVuIGNyZWF0ZSBhIC5SZW52aXJvbiBmaWxlIGluIHlvdXIgcHJvamVjdCBmb2xkZXIuIENvcHkgdGhlIHRva2VuIGluZm9ybWF0aW9uIGZyb20gTWFwQm94IGFuZCBjcmVhdGUgYW4gb2JqZWN0IGluIHRoZSAuUmVudmlyb24gZmlsZSBzdWNoIGFzIGBtYXBfdG9rZW4gPSAncGFzdGUgdG9rZW4gaGVyZSdgIGFuZCBhZGQgYG1hcF90b2tlbiA9ICBTeXMuZ2V0ZW52KCdtYXBfdG9rZW4nKWAgdG8gdGhlIHNjcmlwdCBiZWxvdy4gSG93ZXZlciB1c2luZyB0aGUgYGdldF9tYXB0eXBlcygpYCBzY3JpcHQgeW91IGNhbiBzZWUgdGhlcmUgYXJlIHZhcmlvdXMgbWFwIHNlcnZpY2VzIGFuZCBtYXAgdHlwZXMgdGhhdCBjYW4gYmUgdXNlZC4gQSBzaW1wbGUgb3V0cHV0IHdvdWxkIGJlIHRvIHVzZSBgbWFwX3NlcnZpY2UgPSAnb3NtJ2AgKE9wZW5TdHJlZXRNYXBzKSBhbmQgYG1hcF90eXBlID0gJ3RvcG9ncmFwaGljJ2Agb3Igb3RoZXIgbWFwIHR5cGVzIGF2YWlsYWJsZSBieSB2aWV3aW5nIGBnZXRfbWFwdHlwZXMoJ293bScpYC4gd2hlbiB1c2luZyBhIGJhc2VtYXAgd2l0aG91dCB0b2tlbiBhY2Nlc3MgdGhlIGBtYXBfdG9rZW5gIG9wdGlvbiBjYW4gYmUgcmVtb3ZlZCBmcm9tIHRoZSBzY3JpcHQgYmVsb3cuDQoNCj4gV2FybmluZywgdGhlIG51bWJlciBvZiBmcmFtZXMsIGZyYW1lcy9zZWNvbmQsIGFuZCBvdXRwdXQgZmlsZSB0eXBlIHdpbGwgZGV0ZXJtaW5lIGhvdyBsb25nIHRoaXMgcHJvY2VzcyB3aWxsIHRha2UgYW5kIGhvdyBsYXJnZSB0aGUgb3V0cHV0IGZpbGUgd2lsbCBiZS4NCg0KYGBge3IgcmVsb2MgZnJhbWVzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9DQpmcmFtZXMgPC0gZnJhbWVzX3NwYXRpYWwobW92ZW1lbnQsIHBhdGhfY29sb3VycyA9ICJyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1hcF9zZXJ2aWNlID0gIm1hcGJveCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1hcF90eXBlID0gInNhdGVsbGl0ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgbWFwX3Rva2VuID0gIFN5cy5nZXRlbnYoJ21hcF90b2tlbicpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSAlPiUgDQogIGFkZF9sYWJlbHMoeCA9ICJMb25naXR1ZGUiLCB5ID0gIkxhdGl0dWRlIikgJT4lDQogIGFkZF9ub3J0aGFycm93KCkgJT4lIA0KICBhZGRfc2NhbGViYXIoZGlzdGFuY2UgPSAyKSAlPiUgDQogIGFkZF90aW1lc3RhbXBzKG1vdmVtZW50LCB0eXBlID0gImxhYmVsIikgJT4lIA0KICBhZGRfcHJvZ3Jlc3MoKQ0KYGBgDQoNCmBgYHtyIGFuaW1hdGVkIHJlbG9jcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQ0KYW5pbWF0ZV9mcmFtZXMoZnJhbWVzLCBmcHMgPSA1LCBvdmVyd3JpdGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgb3V0X2ZpbGUgPSAiLi9tb3ZlVmlzLTIwMjFmcHMuZ2lmIikNCmBgYCAgICAgICAgICAgICAgIA0KDQohW10oLi9tb3ZlVmlzLTIwMjFmcHMuZ2lmICJSZWxvY2F0aW9uIEFuaW1hdGlvbiIpDQogICAgICAgICAgICAgICANCkFzIHlvdSBjYW4gc2VlIGZyb20gdGhlIHJlc3VsdCBhYm92ZSwgZXZlbiBhIH4xMnNlYyBjbGlwIHdpdGggPDYwIGZyYW1lcyB0b29rIG5lYXJseSA1bWluIHRvIGtleSwgb2J0YWluIGJhc2UgaW1hZ2VyeSwgYXNzaWduIGZyYW1lcywgYW5kIHJlbmRlci4gU28ga2VlcCB0aGF0IGluIG1pbmQgd2hlbiBjcmVhdGluZyB0aGVzZSBhbmltYXRpb25zLiBUaGUgb3V0cHV0IGZyb20gdGhpcyBpcyBzdG9yZWQgaW4geW91ciByb290IGRpcmVjdG9yeSBhbmQgY2FuIGJlIHVzZWQgYXMgYW4gYW5pbWF0aW9uIG9uIG90aGVyIHByb2dyYW1zIG9yIGluIGh0bWwgZG9jdW1lbnRzLg0KDQojIFlPVVIgVFVSTiENCg0KTm93IGl04oCZcyB5b3VyIHR1cm4hIEFsdGhvdWdoIHNvbWUgb2YgdGhpcyBtaWdodCBub3QgYmUgYXBwbGljYWJsZSB0byB5b3VyIHRoZXNpcyByZXNlYXJjaCwgdHJ5IHRoaXMgaW5mb3JtYXRpb24gb3V0IG9uIGEgZGF0YXNldCBvZiB5b3VyIGNob2ljZSBmcm9tIGFueSBvZiB0aGUgc291cmNlcyBhYm92ZS4gSWYgeW91IGNhbiBhcHBseSB0aGlzIHRvIHlvdXIgdGhlc2lzLCB0aGVuIGFkZCBpdCB0byB5b3VyIHdlYnNpdGUhIE90aGVyd2lzZSBjcmVhdGUgYSBuZXcgcmVwb3NpdG9yeSBmb3IgeW91ciBwcmVzZW50YXRpb24gb24gVGh1cnNkYXku