Introduction

This analysis aims to provide a simple example of how OpenTripPlanner can be used to calculate accessibility scores for a target population given a target municipal service. In this case, we will calculate accessibility scores based on the number of free public municipal facilities (libraries, community centres, recreation centres, and service centres) located within a 15-minute commute of the current senior population (age 65+) in Hamilton, Ontario. This analysis is based on the principles of aging-in-place and the 15-minute city.

This RPub is organized into five parts as follows:

To complete this analysis, we will be using the following data:

NOTE: For a great guide on accessibility analysis in R using OpenTripPlanner see Hussein Mahfouz’s excellent RPub here.

PART 1 - Setup Environment

1.1 - Load required library packages.

# for rpubs
library(rmarkdown)
library(knitr)

# for input / outputs
library(here)

# for data manipulation
library(tidyverse)
library(tidyselect)
library(magrittr)
library(janitor)

# for spatial data manipulation
library(sf)

# for OpenTripPlanner
library(osmextract)
library(opentripplanner)
library(RcppSimdJson)

# for plotting
library(ggsn)
library(ggspatial)
library(tmap)
library(tmaptools)
library(cowplot)

1.2 - Set preferences.

# set working directory for our inputs/outputs (optional)
#setwd()

# OTP requires coordinates to be in CRS 4326 (WGS 84) so we will set a common crs
common_crs = 4326

# set tmap view mode to static or interactive ("plot" or "view")
tmap_mode("plot")
tmap mode set to plotting

1.3 - Set functions.

Thank you again to Hussein Mahfouz’s RPub for this handy code.

# OTP requires latitude and longitude coordinates as numeric values in two separate columns
# we will use the function below split the geometry column of spatial data needed for OTP

sfc_as_cols <- function(x, names = c("lon","lat")) {
  stopifnot(inherits(x,"sf") && inherits(sf::st_geometry(x),"sfc_POINT"))
  ret <- sf::st_coordinates(x)
  ret <- tibble::as_tibble(ret)
  stopifnot(length(names) == ncol(ret))
  x <- x[ , !names(x) %in% names]
  ret <- setNames(ret,names)
  dplyr::bind_cols(x,ret)
}

PART 2 - Load and Prepare Data

To make this example as easy as possible, data has already been downloaded and pre-cleaned from the links above as follows:

2.1 - Load cleaned data.

# Census Division shapefile
cd21_ham <- sf::st_read(here::here("data/clean/cd21_ham.gpkg"),
                        stringsAsFactors = FALSE)
# Dissemination Area shapefile
da21_ham <- sf::st_read(here::here("data/clean/da21_ham.gpkg"),
                        stringsAsFactors = FALSE)
# Community Spaces shapefile
cspaces <- sf::st_read(here::here("data/clean/cspaces.gpkg"),
                       stringsAsFactors = FALSE)
# Dissemination Area data
da21_data <- read_csv(here::here("data/clean/da21_ham_srpop.csv"),
                      locale = locale(encoding = "latin1"),
                      na = "n/a")

Let’s have a look at our data.

# Census Division shapefile
head(cd21_ham)
Simple feature collection with 1 feature and 6 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -80.24849 ymin: 43.05052 xmax: -79.62221 ymax: 43.47106
Geodetic CRS:  WGS 84
  CDUID         DGUID   CDNAME CDTYPE LANDAREA PRUID
1  3525 2021A00033525 Hamilton    CDR  1118.31    35
                            geom
1 MULTIPOLYGON (((-79.79927 4...
# Dissemination Area shapefile
head(da21_ham)
Simple feature collection with 6 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -79.94766 ymin: 43.26654 xmax: -79.88898 ymax: 43.30781
Geodetic CRS:  WGS 84
     DAUID             DGUID                           geom
1 35250030 2021S051235250030 MULTIPOLYGON (((-79.92591 4...
2 35250031 2021S051235250031 MULTIPOLYGON (((-79.89038 4...
3 35250032 2021S051235250032 MULTIPOLYGON (((-79.93015 4...
4 35250033 2021S051235250033 MULTIPOLYGON (((-79.93278 4...
5 35250034 2021S051235250034 MULTIPOLYGON (((-79.94082 4...
6 35250035 2021S051235250035 MULTIPOLYGON (((-79.94382 4...
# Community Spaces shapefile
head(cspaces)
Simple feature collection with 6 features and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -79.98109 ymin: 43.1217 xmax: -79.80343 ymax: 43.39605
Geodetic CRS:  WGS 84
                NAME                       geom
1   Ancaster Library POINT (-79.97666 43.22531)
2     Barton Library POINT (-79.84122 43.25803)
3   Binbrook Library  POINT (-79.80343 43.1217)
4   Carlisle Library POINT (-79.98109 43.39605)
5 Concession Library POINT (-79.85137 43.24104)
6     Dundas Library POINT (-79.95495 43.26548)
# Dissemination Area data
head(da21_data)

2.2 - Dissemination Area Data

We will start preparing data by merging the Dissemination Area shapefile with the senior population data.

# merge DA data with DA shapefile on DGUID
da21_ham <- merge(x = da21_ham,
                  y = da21_ham_srpop,
                  by.x = "DGUID",
                  by.y = "DGUID")

# final data check to confirm merge
str(da21_ham)
Classes ‘sf’ and 'data.frame':  891 obs. of  6 variables:
 $ DGUID   : chr  "2021S051235250030" "2021S051235250031" "2021S051235250032" "2021S051235250033" ...
 $ DAUID   : chr  "35250030" "35250031" "35250032" "35250033" ...
 $ srpop21 : num  90 120 115 160 105 75 95 140 320 215 ...
 $ srpop26 : num  120 145 180 205 155 105 145 185 370 240 ...
 $ srpop31 : num  165 190 230 250 210 120 185 225 435 280 ...
 $ geometry:sfc_MULTIPOLYGON of length 891; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:188, 1:2] -79.9 -79.9 -79.9 -79.9 -79.9 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA
  ..- attr(*, "names")= chr [1:5] "DGUID" "DAUID" "srpop21" "srpop26" ...
# we will also make some statistical variables for later
srpop21total = sum(da21_ham_srpop$srpop21)
srpop26total = sum(da21_ham_srpop$srpop26)
srpop31total = sum(da21_ham_srpop$srpop31)

print(paste0("Hamilton's total senior population in 2021: ", srpop21total))
[1] "Hamilton's total senior population in 2021: 104260"
print(paste0("Hamilton's total senior population in 2026: ", srpop26total))
[1] "Hamilton's total senior population in 2026: 142190"
print(paste0("Hamilton's total senior population in 2031: ", srpop31total))
[1] "Hamilton's total senior population in 2031: 183095"

2.3 - Dissemination Area Centroids

Now we will create centroids from Dissemination Areas that we will use to query with OTP.

# create centroids of Hamilton's DA boundaries
# another name for centroid is 'geometric center' so we will use (gc) here
da21_ham_gc <- st_centroid(da21_ham)
# recall OTP requires latitude and longitude coordinates as numeric values in two separate columns
# we will use the function from above to split geometry and add results as new columns
da21_ham_gc <- sfc_as_cols(da21_ham_gc)

# confirm split with a final data check
#summary(da21_ham_gc)
str(da21_ham_gc)
Classes ‘sf’ and 'data.frame':  891 obs. of  8 variables:
 $ DGUID   : chr  "2021S051235250030" "2021S051235250031" "2021S051235250032" "2021S051235250033" ...
 $ DAUID   : chr  "35250030" "35250031" "35250032" "35250033" ...
 $ srpop21 : num  90 120 115 160 105 75 95 140 320 215 ...
 $ srpop26 : num  120 145 180 205 155 105 145 185 370 240 ...
 $ srpop31 : num  165 190 230 250 210 120 185 225 435 280 ...
 $ lon     : num  -79.9 -79.9 -79.9 -79.9 -79.9 ...
 $ lat     : num  43.3 43.3 43.3 43.3 43.3 ...
 $ geometry:sfc_POINT of length 891; first list element:  'XY' num  -79.9 43.3
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA
  ..- attr(*, "names")= chr [1:7] "DGUID" "DAUID" "srpop21" "srpop26" ...

2.4 - Community Spaces

Let’s have another look at our Community Spaces data.

# final data check
str(cspaces)
Classes ‘sf’ and 'data.frame':  97 obs. of  2 variables:
 $ NAME: chr  "Ancaster Library" "Barton Library" "Binbrook Library" "Carlisle Library" ...
 $ geom:sfc_POINT of length 97; first list element:  'XY' num  -80 43.2
 - attr(*, "sf_column")= chr "geom"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA
  ..- attr(*, "names")= chr "NAME"
# there are 97 cspaces, let's make a variable to store this
cspacestotal <- 97

2.5 - Plot

Finally, let’s take a look at the study area together with all our data.

# plot together
tm_shape(da21_ham) +
  tm_borders(col = "black", lwd = 0.1) +
tm_shape(da21_ham_gc) +
  tm_bubbles(col = "blue", scale = .3) +
tm_shape(cspaces) +
  tm_bubbles(col = "red", scale = .5)

Perfect!

PART 3a - Setup OpenTripPlanner (OTP)

Sections 3a, 3b, and 3c are based on the following OTP package vignettes:

NOTE: Java 8 is required to run OTP. Download Java 8 here.

3.1 Create the required OTP folder and data structure.

As outlined in the vignettes above, OTP requires a certain folder structure to run. An example folder structure is provided below for reference. We will recreate this structure step-by-step with the exception of Steps 6 and 8 which we will skip as they are not required for this analysis.

Step Example Description
/wd Working directory
1 /otp Top folder for storing all OTP data
2 /graphs Subfolder containing all graphs
3 /default Subfolder that acts as OTP router
4 osm.pbf Required OpenStreetMap road map
5 router-config.json Optional config file
6 build-config.json Optional config file
7 gtfs.zip Optional GTFS data
8 dem.tif Optional Elevation data

Step 1: Create the OTP folder.

# confirm correct version of Java (optional)
#otp_check_java()

# Step 1 - create otp folder
path_data <- file.path(here::here(),
                       "otp")
dir.create(path_data)
# download the required OTP jar file
path_otp <- otp_dl_jar(path_data, cache = FALSE)

Step 2: Create the graphs folder.

For some reason I could not sort out, OTP required the “graphs” folder to be created on my computer first through the demo function prior to being able to create my own graph or router. This function also downloads the “default” folder automatically

otp_dl_demo(path_data)

Step 3: Create the router folder.

dir.create(file.path(here::here("otp/graphs/hamilton")))

Step 4: Download OpenStreetMap (OSM) data.

This step uses the osmextract package and is based on the Introducing osmextract Vignette.

NOTE: Downloading OSM data can take a few minutes as the files can be quite large.
# geocode hamilton's location
hamilton = tmaptools::geocode_OSM("Hamilton, Canada")$coords

# Check osm data for hamilton's location
oe_match(hamilton, provider = "geofabrik")
The input place was matched with Ontario. 
$url
[1] "https://download.geofabrik.de/north-america/canada/ontario-latest.osm.pbf"

$file_size
[1] 762458320
oe_match(hamilton, provider = "openstreetmap_fr")
The input place was matched with Golden Horseshoe. 
$url
[1] "http://download.openstreetmap.fr/extracts/north-america/canada/ontario/golden_horseshoe-latest.osm.pbf"

$file_size
[1] 129940802

Here we can see that geofrabrik shows data is only available for the province, whereas openstreetmap_fr has data available at the regional level. We will download the data from the regional level as it has a significantly smaller file size.

# download osm data for Hamilton
oe_get("Hamilton", 
       provider = "openstreetmap_fr",
       download_directory = here::here("otp/graphs/hamilton"),
       boundary = cd21_ham,
       boundary_type = c("spat", "clipsrc"),
       download_only = TRUE, 
       skip_vectortranslate = TRUE)

Step 5: Configure the router.

The router-config file dictates how OTP will calculate route options. For example, the router-config file specifies the default walking speed from Point A to Point B. Since our analysis is on the senior population, and it is reasonable to assume that average walking speed decreases with age, we will configure our router to reflect this. But by how much? Luckily researchers at McMaster University in Hamilton have already completed the leg work for us here.

# make a config object
router_config <- otp_make_config("router")     

# check default walking speed
router_config$routingDefaults$walkSpeed
[1] 1.34

The default walking speed of OTP is 1.34 metres per second. Let’s see what the McMaster researchers found.

# let's create a dataframe of McMaster University's findings on senior walking speed
walk_speed <- data.frame("age" = c("60-69", "70-79", "80-89"),
                         "srmale" = c(1.34, 1.26, 0.97),
                         "srfemale" = c(1.24, 1.13, 0.94))
walk_speed
# now let's calculate the average senior walking speed
srmale_avg <- (sum(walk_speed$srmale)/3)
srfemale_avg <- (sum(walk_speed$srfemale)/3)
sravg <- ((srmale_avg + srfemale_avg)/2)
sravg
[1] 1.146667

The average senior walking speed is 1.15 metres per second. Let’s recalibrate OTP’s default walking speed.

# calibrate the otp router
router_config$routingDefaults$walkSpeed <- 1.15 

# confirm change
router_config$routingDefaults$walkSpeed
[1] 1.15
# Save the config file
otp_write_config(router_config,
                 dir = path_data,
                 router = "hamilton")

Step 7: Download the GTFS feed.

GTFS stands for General Transit Feed Specification and, according to gtfs.org, “is a data specification that allows public transit agencies to publish their transit data in a format that can be consumed by a wide variety of software applications”. If you use Google Maps to plan your route using transit, you have benefited from a GTFS feed.

Hamilton’s transit agency is called the Hamilton Street Railway (HSR), which ironically has not operated any rail related transit since 1951. Tom Luton maintains a neat website called Hamilton Transit History which is worth a look if interested. The hyperlink for HSR’s GTFS feed can be found here.

NOTE: The GTFS feed must be in a zipped folder with “gtfs” in the name.
url <- "https://googlehsrdocs.hamilton.ca/"  
destfile = here::here("otp",
                      "graphs",
                      "hamilton",
                      "ham_gtfs.zip")
download.file(url, destfile, mode="wb")
trying URL 'https://googlehsrdocs.hamilton.ca/'
Content type 'application/x-zip-compressed' length 8507622 bytes (8.1 MB)
downloaded 8.1 MB

Our OTP router is now ready!

PART 3b - Connect and Test OTP

Now that our data is in order and the OTP is configured, we can test our connection. A few public service announcements you should be aware of before we move any further:

  1. Building and querying OTP can be quite computationally intensive and several OTP functions have options to increase the amount memory or the number of cores your computer uses in order to speed up the process.

  2. It is entirely up to your discretion, given the size of your data and your computers abilty, whether to increase the memory or cores. For reference, this entire script uses the OSM defaults and runs in around five minutes from start to finish.

  3. Where applicable below, I have indicate how to increase memory and cores should you wish to do so.

NOTE: Once connected, OTP will automatically load an interactive web interface in your browser. You can play with the interface as you wish or ignore it entirely, it is not required for the analysis.

WARNING: Never set ‘ncores’ to more cores than are available on your computer. To check the number of cores on your computer, hold ctrl + shift + esc to bring up task manager. Click the performance tab and the number of cores should be indicated in the bottom right.

3.2 - Build the OTP graph.

# create the Hamilton OTP graph file
# optional memory bump up - see explanation above
log1 <- otp_build_graph(otp = path_otp, 
                        dir = path_data, 
                        memory = 2048,        #default
                        router = "hamilton")
# launch otp and load the hamilton graph (the server)
log2 <- otp_setup(otp = path_otp, 
                  dir = path_data,
                  router = "hamilton")

3.3 - Make the connection.

# connect to OTP from R
otpcon <- otp_connect(router = "hamilton")
Router http://localhost:8080/otp/routers/hamilton exists
NOTE: the OTP web interface will auto-load when ready at http://localhost:8080.

3.4 - Test the connection.

# test by getting a route from McMaster University to Tim Hortons Field
ham_test1 <- otp_plan(otpcon, 
                      fromPlace = c(-79.917054, 43.258032), 
                      toPlace = c(-79.830855, 43.251014))

  |                                                  | 0 % ~calculating  
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=00s  
# view the route
osm_ham <- read_osm(ham_test1, ext=1.1)
tm_shape(osm_ham) + tm_rgb() +
  tm_shape(ham_test1) +
  tm_lines(col = 'blue', lwd = 2, alpha = 0.8)

3.5 - Test the isochrone function.

# test by getting an isochrone for McMaster University
# optional ncores bump up - see explanation above
ham_test2  <- otp_isochrone(otpcon = otpcon,
                            fromPlace = c(-79.917151, 43.258050),
                            mode = c("WALK", "TRANSIT"),
                            date_time = as.POSIXct(strptime("2022-08-03 09:00", "%Y-%m-%d %H:%M")),
                            maxWalkDistance = 1000,
                            cutoffSec = (c(5, 10, 15) * 60),
                            ncores = 1)    #default

  |                                                  | 0 % ~calculating  
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=01s  
# convert to minutes for plotting
ham_test2$minutes = (ham_test2$time / 60)
# view the isochrone
osm_ham <- read_osm(ham_test2, ext=1.1)
tm_shape(osm_ham) + tm_rgb() +
  tm_shape(ham_test2) +
  tm_fill("minutes",
          breaks = c(0, 5.01, 10.01, 15.01),
          style = "fixed",
          palette = "-RdYlBu") +
  tm_borders()

Brilliant, it works!

PART 3c - Query OpenTripPlanner for Isochrones

3.6 - Query OTP.

We will now query OTP to get 15-minute isochrones from all 891 Dissemination Area centroids.

NOTE: The function below uses the default ncores setting of ncores = 1. Therefore, expect the function to take a few minutes to compute. You can also increase ncores where indicated below if desired, however, you should read the warning at the beginning of this section first.
# get isochrone polygons for each DA centroid via loop

# variable with number of DAs
nrows <- nrow(da21_ham_gc)

# empty list to store output
da21_ham_gc_iso <- list()

# get isochrone polygon for each DA centroid and store in da21_ham_iso list
# cutoffsec is the max time in seconds for the isochrone (15 minutes for this analysis)
# 
for (i in 1:nrows){
  da21_ham_gc_iso[[i]] <- otp_isochrone(otpcon = otpcon,
                                        fromPlace = c(da21_ham_gc$lon[i], da21_ham_gc$lat[i]),
                                        fromID = c(da21_ham_gc$DAUID[i]),
                                        mode = c("WALK", "TRANSIT"),
                                        date_time = as.POSIXct(strptime("2022-08-03 09:00", "%Y-%m-%d %H:%M")),
                                        maxWalkDistance = 1000,
                                        cutoffSec = (15 * 60),
                                        ncores = 1)}  #default
# check first record to confirm complete
head(da21_ham_gc_iso, 1)
[[1]]
Simple feature collection with 1 feature and 3 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -79.9254 ymin: 43.294 xmax: -79.9117 ymax: 43.3058
Geodetic CRS:  WGS 84
  id time fromPlace                       geometry
1  1  900  35250030 MULTIPOLYGON (((-79.9236 43...

3.7 - Clean the results.

OTP is typically not able to query from all centroids. There are ways to address this, but to keep things simple we will not go into that level of detail here.

# these would show as NA values - lets check
sum(is.na(da21_ham_gc_iso))
[1] 32

32 centroids from our 891 dissemination areas have zero values. We will remove these to facilitate cleaning.

# remove NA values
da21_ham_gc_iso <- da21_ham_gc_iso[da21_ham_gc_iso != "NA"]

# convert result to a single simple feature
da21_ham_gc_iso = do.call(rbind, da21_ham_gc_iso)

# select desired columns 
da21_ham_gc_iso <- da21_ham_gc_iso %>%
  dplyr::select(c(3, 4))

# rename columns to facilitate merger to make data whole again
da21_ham_gc_iso <- da21_ham_gc_iso %>%
  dplyr::rename("DAUID"= 1)

# check
head(da21_ham_gc_iso, 3)
Simple feature collection with 3 features and 1 field
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -79.941 ymin: 43.2761 xmax: -79.9056 ymax: 43.3058
Geodetic CRS:  WGS 84
     DAUID                       geometry
1 35250030 MULTIPOLYGON (((-79.9236 43...
2 35250031 MULTIPOLYGON (((-79.9198 43...
3 35250032 MULTIPOLYGON (((-79.9399 43...

3.8 - Practice plots.

Plot a single isochrone to see what it looks like.

# test plot a single isochrone
osm_ham <- read_osm(cd21_ham, ext=1.1)
tm_shape(osm_ham) + tm_rgb() +
  tm_shape(da21_ham_gc_iso[da21_ham_gc_iso$DAUID == "35250468", ]) +
  tm_fill(col = "#E5854B") +
  tm_borders()

Now let’s try plotting all the isochrones at once.

# test plot all isochrones
osm_ham <- read_osm(cd21_ham, ext=1.1)
tm_shape(osm_ham) + tm_rgb() +
  tm_shape(da21_ham_gc_iso) +
  tm_fill(col = "#E5854B") +
  tm_borders()

3.9 - Disconnect from OTP.

Make sure to disconnect from the OTP server.

NOTE: OTP will keep running in the background of your computer until you disconnect.
# otp no longer needed - stop connection to otp router
otp_stop(warn=FALSE)
[1] "SUCCESS: The process \"java.exe\" with PID 16532 has been terminated."

PART 4 - Calculate Accessibility Scores

We will calculate accessibility scores as follows:

Score Equation Indicator
Accessibility Score Number of community spaces reached per dissemination area divided by the total number of community spaces in the city Percentage of total community spaces in the city reachable per dissemination area
Average Accessibility Score Sum of accessibility scores for all dissemination areas divided by the total number dissemination areas in the city Average percentage of total community spaces in the city reachable per dissemination area
Weighted Accessibility Score Number of community spaces reachable per dissemination area multiplied by its senior population and then divided by the total number of community spaces in the city Percentage of total community spaces reachable per senior per dissemination area
Average Weighted Accessibility Score Sum of weighted accessibility scores for all dissemination areas divided by the total number dissemination areas in the city Average percentage of total community spaces reachable per senior per dissemination area

4.1 - Calculate Community Spaces Reached.

Determine the number of community spaces reached for each dissemination area isochrone and add it as a column to our data.

# perform intersection of 15 minute isochrones and cspaces, then add point count as numeric to each polygon
da21_ham_gc_iso_int <- da21_ham_gc_iso %>%
  mutate(cspaces_15 = (as.numeric(lengths(st_intersects(da21_ham_gc_iso, cspaces)))))

# confirm
da21_ham_gc_iso_int
Simple feature collection with 859 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -80.2156 ymin: 43.0702 xmax: -79.6186 ymax: 43.4248
Geodetic CRS:  WGS 84
First 10 features:
      DAUID                       geometry cspaces_15
1  35250030 MULTIPOLYGON (((-79.9236 43...          0
2  35250031 MULTIPOLYGON (((-79.9198 43...          0
3  35250032 MULTIPOLYGON (((-79.9399 43...          0
4  35250033 MULTIPOLYGON (((-79.9439 43...          0
5  35250034 MULTIPOLYGON (((-79.9495 43...          0
6  35250035 MULTIPOLYGON (((-79.9448 43...          2
7  35250036 MULTIPOLYGON (((-79.9598 43...          0
8  35250037 MULTIPOLYGON (((-79.9634 43...          2
9  35250038 MULTIPOLYGON (((-79.9629 43...          3
10 35250039 MULTIPOLYGON (((-79.9601 43...          2
# remove geometry first to facilitate merge
st_geometry(da21_ham_gc_iso_int) <- NULL

# confirm
da21_ham_gc_iso_int
# Merge back into the DA shape file on DGUID to plot results
da21_ham <- left_join(x = da21_ham,
                      y = da21_ham_gc_iso_int,
                      by.x = "DGUID",
                      by.y = "DGUID")
Joining, by = "DAUID"
# confirm
da21_ham
Simple feature collection with 891 features and 6 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -80.24849 ymin: 43.05052 xmax: -79.62221 ymax: 43.47106
Geodetic CRS:  WGS 84
First 10 features:
               DGUID    DAUID srpop21 srpop26 srpop31 cspaces_15
1  2021S051235250030 35250030      90     120     165          0
2  2021S051235250031 35250031     120     145     190          0
3  2021S051235250032 35250032     115     180     230          0
4  2021S051235250033 35250033     160     205     250          0
5  2021S051235250034 35250034     105     155     210          0
6  2021S051235250035 35250035      75     105     120          2
7  2021S051235250036 35250036      95     145     185          0
8  2021S051235250037 35250037     140     185     225          2
9  2021S051235250038 35250038     320     370     435          3
10 2021S051235250039 35250039     215     240     280          2
                         geometry
1  MULTIPOLYGON (((-79.92591 4...
2  MULTIPOLYGON (((-79.89038 4...
3  MULTIPOLYGON (((-79.93015 4...
4  MULTIPOLYGON (((-79.93278 4...
5  MULTIPOLYGON (((-79.94082 4...
6  MULTIPOLYGON (((-79.94382 4...
7  MULTIPOLYGON (((-79.94652 4...
8  MULTIPOLYGON (((-79.94968 4...
9  MULTIPOLYGON (((-79.95334 4...
10 MULTIPOLYGON (((-79.94972 4...

4.2 - Calculate Accessibility Scores.

Calculate accessibility score of each dissemination area by dividing the results from STEP 1 by the total # of community spaces in Hamilton.

# calcualte score (% of the total community spaces in Hamilton that are reachable from each DA) as new column
da21_ham <- da21_ham %>%
  mutate(DA_SSOS = ((da21_ham$cspaces_15 / cspacestotal) * 100))

# recall that we dropped all cases where OTP could not route from a DA centroid
# let's have a look
sum(is.na(da21_ham))
[1] 64

64 NA values. Let’s check how many distinct values are in the DAUID column to make sure we have 891 - one for each dissemination area.

# check for 891 unique values
n_distinct(da21_ham$DAUID)
[1] 891

891 values. Perfect, our data is whole again.

NOTE: The key assumption here is that if OTP can not route from a location, then there are no community spaces reachable from said location.
# now let's perform a master replace of "NA" with "0" for all other columns
da21_ham[is.na(da21_ham)] <- 0

# confirm
sum(is.na(da21_ham))
[1] 0

No more NA values!

4.3 - Calculate Weighted Accessibility Scores.

Calculate weighted accessibility scores of each dissemination area by multiplying the results from STEP 2 by the senior population of each dissemination area and then dividing by the total senior population in Hamilton.

# use mutate to calculate for all three time periods
# by multiplying (weighing) each DA SSOS by its senior population
# then dividing the result by total senior population in Hamilton
da21_ham <- da21_ham %>%
  mutate(DA_SSOSw = (((DA_SSOS * srpop21)/srpop21total)*100),
         DA_SSOSw_5y = (((DA_SSOS * srpop26)/srpop26total)*100),
         DA_SSOSw_10y = (((DA_SSOS * srpop31)/srpop31total)*100))

# use mutate to calculate difference between Weighted SSOS now and 5y /10y
da21_ham <- da21_ham %>%
  mutate(DA_SSOSw_5y_chg = (DA_SSOSw_5y - DA_SSOSw),
         DA_SSOSw_10y_chg = (DA_SSOSw_10y - DA_SSOSw))

# check dataframe
head(da21_ham)
Simple feature collection with 6 features and 12 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -79.94766 ymin: 43.26654 xmax: -79.88898 ymax: 43.30781
Geodetic CRS:  WGS 84
              DGUID    DAUID srpop21 srpop26 srpop31 cspaces_15
1 2021S051235250030 35250030      90     120     165          0
2 2021S051235250031 35250031     120     145     190          0
3 2021S051235250032 35250032     115     180     230          0
4 2021S051235250033 35250033     160     205     250          0
5 2021S051235250034 35250034     105     155     210          0
6 2021S051235250035 35250035      75     105     120          2
                        geometry  DA_SSOS  DA_SSOSw DA_SSOSw_5y
1 MULTIPOLYGON (((-79.92591 4... 0.000000 0.0000000   0.0000000
2 MULTIPOLYGON (((-79.89038 4... 0.000000 0.0000000   0.0000000
3 MULTIPOLYGON (((-79.93015 4... 0.000000 0.0000000   0.0000000
4 MULTIPOLYGON (((-79.93278 4... 0.000000 0.0000000   0.0000000
5 MULTIPOLYGON (((-79.94082 4... 0.000000 0.0000000   0.0000000
6 MULTIPOLYGON (((-79.94382 4... 2.061856 0.1483207   0.1522574
  DA_SSOSw_10y DA_SSOSw_5y_chg DA_SSOSw_10y_chg
1    0.0000000     0.000000000       0.00000000
2    0.0000000     0.000000000       0.00000000
3    0.0000000     0.000000000       0.00000000
4    0.0000000     0.000000000       0.00000000
5    0.0000000     0.000000000       0.00000000
6    0.1351335     0.003936721      -0.01318721

4.4 - Calculate Average Weighted Accessibility Score.

# use summarize
ham_scores_final <- da21_ham %>%
  st_drop_geometry() %>%
  summarise(DA_SSOS_avg = mean(DA_SSOS),
            DA_SSOSw_avg = mean(DA_SSOSw),
            DA_SSOSw_5y_avg = mean(DA_SSOSw_5y),
            DA_SSOSw_10y_avg = mean(DA_SSOSw_10y)) 

# Use mutate to calculate difference between Weighted SSOS now and 5y /10y
ham_scores_final <- ham_scores_final %>%
  mutate(DA_SSOSw_5y_chg = (DA_SSOSw_5y_avg - DA_SSOSw_avg),
         DA_SSOSw_10y_chg = (DA_SSOSw_10y_avg - DA_SSOSw_avg))

# check dataframe
ham_scores_final

PART 5 - Plot Results

5.1 - Number of Community Spaces Reached by Dissemination Area.

cspaces_reach <- tm_shape(da21_ham) +
  tm_fill("cspaces_15",
          style = "jenks",
          legend.hist = TRUE,
          title = "Community Spaces within 15 min.\n(Transit, max. 1000m walk, AM Peak)") +
  tm_layout(frame = FALSE,
            legend.outside = TRUE,
            legend.outside.position = 'bottom',
            legend.stack = 'horizontal',
            legend.title.fontface = 'bold',
            legend.hist.width = 1,
            legend.hist.height = 0.6) +
  tm_borders(col = "grey40", lwd = 0.1) +
  tm_legend(title.size=0.9,
            text.size = 0.6,
            position = c("left", "bottom")) +
  tm_shape(cd21_ham, color = 'black', fill = NA) +
  tm_borders(col = "black", lwd = 0.1) +
  tm_compass(north = 0, type = 'arrow', show.labels =0, position = c('right','bottom')) + 
  tm_scale_bar(color.dark = "black",
               position = c("left", "bottom"))
cspaces_reach

5.2 - Weighted Accessibility Score by Dissemination Area, 2021.

DA_SSOSw_plot <- tm_shape(da21_ham) +
  tm_polygons("DA_SSOSw", 
              style="jenks", 
              title="Weighted SSOS",
              border.alpha = 0) +
  tm_shape(cd21_ham, color = 'black', fill = NA) +
  tm_borders(col = "black", lwd = 0.1) +
  tm_layout(frame = FALSE) +
  tm_legend(position = c("right", "top")) +
  tm_compass(position = c('right','bottom')) + 
  tm_scale_bar(position = c("left", "bottom"), width = 0.15)
DA_SSOSw_plot

5.3 - Change in Weighted Accessibility Score by Dissemination Area, 2021 to 2026.

DA_SSOSw_5y_chg_plot <- tm_shape(da21_ham) +
  tm_polygons("DA_SSOSw_5y_chg", 
              style="jenks", 
              title="Five-Year\nChange (2026)",
              border.alpha = 0) +
  tm_shape(cd21_ham, color = 'black', fill = NA) +
  tm_borders(col = "black", lwd = 0.1) +
  tm_layout(frame = FALSE) +
  tm_legend(position = c("right", "top")) +
  tm_compass(position = c('right','bottom')) + 
  tm_scale_bar(position = c("left", "bottom"), width = 0.15)
DA_SSOSw_5y_chg_plot

5.4 - Change in Weighted Accessibility Score by Dissemination Area, 2021 to 2031.

DA_SSOSw_10y_chg_plot <- tm_shape(da21_ham) +
  tm_polygons("DA_SSOSw_10y_chg", 
              style="jenks", 
              title="Ten-Year\nChange (2031)",
              border.alpha = 0) +
  tm_shape(cd21_ham, color = 'black', fill = NA) +
  tm_borders(col = "black", lwd = 0.1) +
  tm_layout(frame = FALSE) +
  tm_legend(position = c("right", "top")) +
  tm_compass(position = c('right','bottom')) + 
  tm_scale_bar(position = c("left", "bottom"), width = 0.15)
DA_SSOSw_10y_chg_plot

Conclusion

Congratulations on making it to the end. Now run the same analysis, but this time, use a different facility data set and select a different population to study!

The figure below provides a nice visual summary of the analysis we just completed.

# 1 - DA Zones
DAplot <- ggplot() + 
  ggtitle("1) Load Dissemination Areas") +
  theme(plot.title = element_text(size = 10, hjust = 0, vjust = -1)) +
  geom_sf(data = da21_ham, color = 'grey', fill = NA, lwd = 0.3) +
  geom_sf(data = cd21_ham, color = 'black', fill = NA, lwd = 0.2) +
  blank()

# 2 - DA Centroids
DAGCplot <- ggplot() + 
  ggtitle("2) Calculate Centroids") +
  theme(plot.title = element_text(size = 10, hjust = 0, vjust = -1)) +
  geom_sf(data = da21_ham_gc, color = '#F8C471') +
  geom_sf(data = da21_ham, color = 'grey', fill = NA, lwd = 0.3) +
  geom_sf(data = cd21_ham, color = 'black', fill = NA, lwd = 0.2) +
  blank()

# 3 - 15min Centroid Isochrones
DAGCISOplot <- ggplot() + 
  ggtitle("3) Query OTP for 15min Isochrones") +
  theme(plot.title = element_text(size = 10, hjust = 0, vjust = -1)) +
  geom_sf(data = da21_ham_gc_iso, fill = '#F8C471', color = '#F8C471') +
  #geom_sf(data = da21_ham, color = 'grey', fill = NA, lwd = 0.3) +
  geom_sf(data = cd21_ham, color = 'black', fill = NA, lwd = 0.2) +
  blank()+
  annotation_scale(location = "bl",height = unit(0.1, "cm")) 

# 4 - Intersection Isochrones by Community Spaces
CSPACESplot <- ggplot() + 
  ggtitle("4) Intersect Community Spaces") +
  theme(plot.title = element_text(size = 10, hjust = 0, vjust = -1)) +
  geom_sf(data = da21_ham_gc_iso, fill = '#F8C471', color = '#F8C471') +
  #geom_sf(data = da21_ham, color = 'grey', fill = NA, lwd = 0.3) +
  geom_sf(data = cd21_ham, color = 'black', fill = NA, lwd = 0.2) +
  geom_sf(data = cspaces, color = '#A04000', shape=18, size=1.2) +
  blank()+
  annotation_north_arrow(location = "br", which_north = "true",
                         height = unit(0.8, "cm"),
                         width = unit(0.8, "cm"),
                         pad_y = unit(0.1, "in"),
                         style = north_arrow_fancy_orienteering)

# plot together
studyarea_method <- plot_grid(ncol = 2,
                              align = "hv",
                              DAplot, DAGCplot, DAGCISOplot, CSPACESplot,
                              label_size = 9) +
  theme(plot.title = element_text(hjust = 0.5, vjust = 1))
studyarea_method

LS0tDQp0aXRsZTogIlNlbmlvciBBY2Nlc3NpYmlsaXR5IGluIEhhbWlsdG9uLCBPbnRhcmlvIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQplZGl0b3Jfb3B0aW9uczogDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiA3Mg0KLS0tDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSkNCmBgYA0KDQojIyMgSW50cm9kdWN0aW9uDQoNClRoaXMgYW5hbHlzaXMgYWltcyB0byBwcm92aWRlIGEgc2ltcGxlIGV4YW1wbGUgb2YgaG93IE9wZW5UcmlwUGxhbm5lcg0KY2FuIGJlIHVzZWQgdG8gY2FsY3VsYXRlIGFjY2Vzc2liaWxpdHkgc2NvcmVzIGZvciBhIHRhcmdldCBwb3B1bGF0aW9uDQpnaXZlbiBhIHRhcmdldCBtdW5pY2lwYWwgc2VydmljZS4gSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIGNhbGN1bGF0ZQ0KYWNjZXNzaWJpbGl0eSBzY29yZXMgYmFzZWQgb24gdGhlIG51bWJlciBvZiBmcmVlIHB1YmxpYyBtdW5pY2lwYWwNCmZhY2lsaXRpZXMgKGxpYnJhcmllcywgY29tbXVuaXR5IGNlbnRyZXMsIHJlY3JlYXRpb24gY2VudHJlcywgYW5kDQpzZXJ2aWNlIGNlbnRyZXMpIGxvY2F0ZWQgd2l0aGluIGEgMTUtbWludXRlIGNvbW11dGUgb2YgdGhlIGN1cnJlbnQNCnNlbmlvciBwb3B1bGF0aW9uIChhZ2UgNjUrKSBpbiBIYW1pbHRvbiwgT250YXJpby4gVGhpcyBhbmFseXNpcyBpcyBiYXNlZA0Kb24gdGhlIHByaW5jaXBsZXMgb2YgYWdpbmctaW4tcGxhY2UgYW5kIHRoZSAxNS1taW51dGUgY2l0eS4NCg0KVGhpcyBSUHViIGlzIG9yZ2FuaXplZCBpbnRvIGZpdmUgcGFydHMgYXMgZm9sbG93czoNCg0KLSAgIFBBUlQgMSAtIFNldHVwIEVudmlyb25tZW50DQotICAgUEFSVCAyIC0gTG9hZCBhbmQgUHJlcGFyZSBEYXRhDQotICAgUEFSVCAzYSAtIFNldHVwIE9wZW5UcmlwUGxhbm5lcg0KLSAgIFBBUlQgM2IgLSBDb25uZWN0IGFuZCBUZXN0IE9wZW5UcmlwUGxhbm5lcg0KLSAgIFBBUlQgM2MgLSBRdWVyeSBPcGVuVHJpcFBsYW5uZXIgZm9yIElzb2Nocm9uZXMNCi0gICBQQVJUIDQgLSBDYWxjdWxhdGUgQWNjZXNzaWJpbGl0eSBTY29yZXMNCi0gICBQQVJUIDUgLSBQbG90IFJlc3VsdHMNCg0KVG8gY29tcGxldGUgdGhpcyBhbmFseXNpcywgd2Ugd2lsbCBiZSB1c2luZyB0aGUgZm9sbG93aW5nIGRhdGE6DQoNCi0gICBbQ2Vuc3VzDQogICAgRGl2aXNpb25dKGh0dHBzOi8vd3d3MTUwLnN0YXRjYW4uZ2MuY2EvbjEvcHViLzkyLTE5NS14LzIwMjEwMDEvZ2VvL2NkLWRyL2NkLWRyLWVuZy5odG0pDQogICAgYW5kIFtEaXNzZW1pbmF0aW9uDQogICAgQXJlYV0oaHR0cHM6Ly93d3cxNTAuc3RhdGNhbi5nYy5jYS9uMS9wdWIvOTItMTk1LXgvMjAyMTAwMS9nZW8vZGEtYWQvZGEtYWQtZW5nLmh0bSkNCiAgICBzaGFwZWZpbGVzIGZyb20gdGhlIDIwMjEgQ2FuYWRpYW4gQ2Vuc3VzIGRvd25sb2FkZWQgZnJvbSBTdGF0aXN0aWNzDQogICAgQ2FuYWRhDQogICAgW2hlcmUuXShodHRwczovL3d3dzEyLnN0YXRjYW4uZ2MuY2EvY2Vuc3VzLXJlY2Vuc2VtZW50LzIwMjEvZ2VvL3NpcC1waXMvYm91bmRhcnktbGltaXRlcy9pbmRleDIwMjEtZW5nLmNmbT95ZWFyPTIxKQ0KLSAgIFtDZW5zdXMgb2YNCiAgICBQb3B1bGF0aW9uXShodHRwczovL3d3dzEyLnN0YXRjYW4uZ2MuY2EvY2Vuc3VzLXJlY2Vuc2VtZW50L2luZGV4LWVuZy5jZm0/TU09MSkNCiAgICBkYXRhIGF0IHRoZSBEaXNzZW1pbmF0aW9uIEFyZWEgbGV2ZWwgZG93bmxvYWRlZCBmcm9tIFN0YXRpc3RpY3MNCiAgICBDYW5hZGENCiAgICBbaGVyZS5dKGh0dHBzOi8vd3d3MTUwLnN0YXRjYW4uZ2MuY2EvdDEvdGJsMS9lbi90di5hY3Rpb24/cGlkPTk4MTAwMDIzMDEpDQotICAgTGlicmFyeSwgQ29tbXVuaXR5IENlbnRyZSwgUmVjcmVhdGlvbiBDZW50cmUsIGFuZCBTZXJ2aWNlIENlbnRyZQ0KICAgIHNoYXBlZmlsZXMgZG93bmxvYWRlZCBmcm9tIE9wZW4gSGFtaWx0b24NCiAgICBbaGVyZS5dKGh0dHBzOi8vb3Blbi5oYW1pbHRvbi5jYS8pDQoNCnwgKioqTk9URToqKiBGb3IgYSBncmVhdCBndWlkZSBvbiBhY2Nlc3NpYmlsaXR5IGFuYWx5c2lzIGluIFIgdXNpbmcgT3BlblRyaXBQbGFubmVyIHNlZSBIdXNzZWluIE1haGZvdXoncyBleGNlbGxlbnQgUlB1YiBbaGVyZV0oaHR0cHM6Ly9ycHVicy5jb20vSHVzc2Vpbi1NYWhmb3V6L0FjY2Vzc2liaWxpdHktQW5hbHlzaXMtQ2Fpcm8pLioNCg0KIyMjIFBBUlQgMSAtIFNldHVwIEVudmlyb25tZW50DQoNCiMjIyMgMS4xIC0gTG9hZCByZXF1aXJlZCBsaWJyYXJ5IHBhY2thZ2VzLg0KDQpgYGB7cn0NCiMgZm9yIHJwdWJzDQpsaWJyYXJ5KHJtYXJrZG93bikNCmxpYnJhcnkoa25pdHIpDQoNCiMgZm9yIGlucHV0IC8gb3V0cHV0cw0KbGlicmFyeShoZXJlKQ0KDQojIGZvciBkYXRhIG1hbmlwdWxhdGlvbg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHlzZWxlY3QpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KbGlicmFyeShqYW5pdG9yKQ0KDQojIGZvciBzcGF0aWFsIGRhdGEgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHNmKQ0KDQojIGZvciBPcGVuVHJpcFBsYW5uZXINCmxpYnJhcnkob3NtZXh0cmFjdCkNCmxpYnJhcnkob3BlbnRyaXBwbGFubmVyKQ0KbGlicmFyeShSY3BwU2ltZEpzb24pDQoNCiMgZm9yIHBsb3R0aW5nDQpsaWJyYXJ5KGdnc24pDQpsaWJyYXJ5KGdnc3BhdGlhbCkNCmxpYnJhcnkodG1hcCkNCmxpYnJhcnkodG1hcHRvb2xzKQ0KbGlicmFyeShjb3dwbG90KQ0KDQpgYGANCg0KIyMjIyAxLjIgLSBTZXQgcHJlZmVyZW5jZXMuDQoNCmBgYHtyfQ0KIyBzZXQgd29ya2luZyBkaXJlY3RvcnkgZm9yIG91ciBpbnB1dHMvb3V0cHV0cyAob3B0aW9uYWwpDQojc2V0d2QoKQ0KDQojIE9UUCByZXF1aXJlcyBjb29yZGluYXRlcyB0byBiZSBpbiBDUlMgNDMyNiAoV0dTIDg0KSBzbyB3ZSB3aWxsIHNldCBhIGNvbW1vbiBjcnMNCmNvbW1vbl9jcnMgPSA0MzI2DQoNCiMgc2V0IHRtYXAgdmlldyBtb2RlIHRvIHN0YXRpYyBvciBpbnRlcmFjdGl2ZSAoInBsb3QiIG9yICJ2aWV3IikNCnRtYXBfbW9kZSgicGxvdCIpDQpgYGANCg0KIyMjIyAxLjMgLSBTZXQgZnVuY3Rpb25zLg0KDQpUaGFuayB5b3UgYWdhaW4gdG8gSHVzc2VpbiBNYWhmb3V6J3MNCltSUHViXShodHRwczovL3JwdWJzLmNvbS9IdXNzZWluLU1haGZvdXovQWNjZXNzaWJpbGl0eS1BbmFseXNpcy1DYWlybykNCmZvciB0aGlzIGhhbmR5IGNvZGUuDQoNCmBgYHtyfQ0KIyBPVFAgcmVxdWlyZXMgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBjb29yZGluYXRlcyBhcyBudW1lcmljIHZhbHVlcyBpbiB0d28gc2VwYXJhdGUgY29sdW1ucw0KIyB3ZSB3aWxsIHVzZSB0aGUgZnVuY3Rpb24gYmVsb3cgc3BsaXQgdGhlIGdlb21ldHJ5IGNvbHVtbiBvZiBzcGF0aWFsIGRhdGEgbmVlZGVkIGZvciBPVFANCg0Kc2ZjX2FzX2NvbHMgPC0gZnVuY3Rpb24oeCwgbmFtZXMgPSBjKCJsb24iLCJsYXQiKSkgew0KICBzdG9waWZub3QoaW5oZXJpdHMoeCwic2YiKSAmJiBpbmhlcml0cyhzZjo6c3RfZ2VvbWV0cnkoeCksInNmY19QT0lOVCIpKQ0KICByZXQgPC0gc2Y6OnN0X2Nvb3JkaW5hdGVzKHgpDQogIHJldCA8LSB0aWJibGU6OmFzX3RpYmJsZShyZXQpDQogIHN0b3BpZm5vdChsZW5ndGgobmFtZXMpID09IG5jb2wocmV0KSkNCiAgeCA8LSB4WyAsICFuYW1lcyh4KSAlaW4lIG5hbWVzXQ0KICByZXQgPC0gc2V0TmFtZXMocmV0LG5hbWVzKQ0KICBkcGx5cjo6YmluZF9jb2xzKHgscmV0KQ0KfQ0KYGBgDQoNCiMjIyBQQVJUIDIgLSBMb2FkIGFuZCBQcmVwYXJlIERhdGENCg0KVG8gbWFrZSB0aGlzIGV4YW1wbGUgYXMgZWFzeSBhcyBwb3NzaWJsZSwgZGF0YSBoYXMgYWxyZWFkeSBiZWVuDQpkb3dubG9hZGVkIGFuZCBwcmUtY2xlYW5lZCBmcm9tIHRoZSBsaW5rcyBhYm92ZSBhcyBmb2xsb3dzOg0KDQotICAgY2QyMV9oYW0gPSBDZW5zdXMgRGl2aXNpb24gc2hhcGVmaWxlIHJlZHVjZWQgdG8gQ2l0eSBvZiBIYW1pbHRvbg0KICAgIGJvdW5kYXJpZXMgYW5kIGNvbHVtbnMgbm90IG5lZWRlZCBmb3IgdGhpcyBhbmFseXNpcyByZW1vdmVkLg0KLSAgIGRhMjFfaGFtID0gRGlzc2VtaW5hdGlvbiBBcmVhIHNoYXBlZmlsZSByZWR1Y2VkIHRvIENpdHkgb2YgSGFtaWx0b24NCiAgICBib3VuZGFyaWVzIGFuZCBjb2x1bW5zIG5vdCBuZWVkZWQgZm9yIHRoaXMgYW5hbHlzaXMgcmVtb3ZlZC4NCi0gICBkYTIxX2RhdGEgPSBDZW5zdXMgb2YgUG9wdWxhdGlvbiBkYXRhIGJ5IERpc3NlbWluYXRpb24gQXJlYSByZWR1Y2VkDQogICAgdG8gQ2l0eSBvZiBIYW1pbHRvbiBib3VuZGFyaWVzIGFuZCB0aGUgc2VuaW9yIHBvcHVsYXRpb24gYWdlZCA2NSsgaW4NCiAgICAyMDIxLCAyMDI2LCBhbmQgMjAzMS4NCi0gICBjc3BhY2VzID0gTGlicmFyeSwgQ29tbXVuaXR5IENlbnRyZSwgUmVjcmVhdGlvbiBDZW50cmUsIGFuZCBTZXJ2aWNlDQogICAgQ2VudHJlIHNoYXBlZmlsZXMgbWVyZ2VkIGludG8gYSBzaW5nbGUgQ29tbXVuaXR5IFNwYWNlcyBzaGFwZWZpbGUuDQoNCiMjIyMgMi4xIC0gTG9hZCBjbGVhbmVkIGRhdGEuDQoNCmBgYHtyIHJlc3VsdHM9J2hpZGUnfQ0KIyBDZW5zdXMgRGl2aXNpb24gc2hhcGVmaWxlDQpjZDIxX2hhbSA8LSBzZjo6c3RfcmVhZChoZXJlOjpoZXJlKCJkYXRhL2NsZWFuL2NkMjFfaGFtLmdwa2ciKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCiMgRGlzc2VtaW5hdGlvbiBBcmVhIHNoYXBlZmlsZQ0KZGEyMV9oYW0gPC0gc2Y6OnN0X3JlYWQoaGVyZTo6aGVyZSgiZGF0YS9jbGVhbi9kYTIxX2hhbS5ncGtnIiksDQogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQojIENvbW11bml0eSBTcGFjZXMgc2hhcGVmaWxlDQpjc3BhY2VzIDwtIHNmOjpzdF9yZWFkKGhlcmU6OmhlcmUoImRhdGEvY2xlYW4vY3NwYWNlcy5ncGtnIiksDQogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCg0KIyBEaXNzZW1pbmF0aW9uIEFyZWEgZGF0YQ0KZGEyMV9kYXRhIDwtIHJlYWRfY3N2KGhlcmU6OmhlcmUoImRhdGEvY2xlYW4vZGEyMV9oYW1fc3Jwb3AuY3N2IiksDQogICAgICAgICAgICAgICAgICAgICAgbG9jYWxlID0gbG9jYWxlKGVuY29kaW5nID0gImxhdGluMSIpLA0KICAgICAgICAgICAgICAgICAgICAgIG5hID0gIm4vYSIpDQpgYGANCg0KTGV0J3MgaGF2ZSBhIGxvb2sgYXQgb3VyIGRhdGEuDQoNCmBgYHtyfQ0KIyBDZW5zdXMgRGl2aXNpb24gc2hhcGVmaWxlDQpoZWFkKGNkMjFfaGFtKQ0KIyBEaXNzZW1pbmF0aW9uIEFyZWEgc2hhcGVmaWxlDQpoZWFkKGRhMjFfaGFtKQ0KIyBDb21tdW5pdHkgU3BhY2VzIHNoYXBlZmlsZQ0KaGVhZChjc3BhY2VzKQ0KIyBEaXNzZW1pbmF0aW9uIEFyZWEgZGF0YQ0KaGVhZChkYTIxX2RhdGEpDQpgYGANCg0KIyMjIyAyLjIgLSBEaXNzZW1pbmF0aW9uIEFyZWEgRGF0YQ0KDQpXZSB3aWxsIHN0YXJ0IHByZXBhcmluZyBkYXRhIGJ5IG1lcmdpbmcgdGhlIERpc3NlbWluYXRpb24gQXJlYSBzaGFwZWZpbGUNCndpdGggdGhlIHNlbmlvciBwb3B1bGF0aW9uIGRhdGEuDQoNCmBgYHtyfQ0KIyBtZXJnZSBEQSBkYXRhIHdpdGggREEgc2hhcGVmaWxlIG9uIERHVUlEDQpkYTIxX2hhbSA8LSBtZXJnZSh4ID0gZGEyMV9oYW0sDQogICAgICAgICAgICAgICAgICB5ID0gZGEyMV9oYW1fc3Jwb3AsDQogICAgICAgICAgICAgICAgICBieS54ID0gIkRHVUlEIiwNCiAgICAgICAgICAgICAgICAgIGJ5LnkgPSAiREdVSUQiKQ0KDQojIGZpbmFsIGRhdGEgY2hlY2sgdG8gY29uZmlybSBtZXJnZQ0Kc3RyKGRhMjFfaGFtKQ0KDQojIHdlIHdpbGwgYWxzbyBtYWtlIHNvbWUgc3RhdGlzdGljYWwgdmFyaWFibGVzIGZvciBsYXRlcg0Kc3Jwb3AyMXRvdGFsID0gc3VtKGRhMjFfaGFtX3NycG9wJHNycG9wMjEpDQpzcnBvcDI2dG90YWwgPSBzdW0oZGEyMV9oYW1fc3Jwb3Akc3Jwb3AyNikNCnNycG9wMzF0b3RhbCA9IHN1bShkYTIxX2hhbV9zcnBvcCRzcnBvcDMxKQ0KDQpwcmludChwYXN0ZTAoIkhhbWlsdG9uJ3MgdG90YWwgc2VuaW9yIHBvcHVsYXRpb24gaW4gMjAyMTogIiwgc3Jwb3AyMXRvdGFsKSkNCnByaW50KHBhc3RlMCgiSGFtaWx0b24ncyB0b3RhbCBzZW5pb3IgcG9wdWxhdGlvbiBpbiAyMDI2OiAiLCBzcnBvcDI2dG90YWwpKQ0KcHJpbnQocGFzdGUwKCJIYW1pbHRvbidzIHRvdGFsIHNlbmlvciBwb3B1bGF0aW9uIGluIDIwMzE6ICIsIHNycG9wMzF0b3RhbCkpDQpgYGANCg0KIyMjIyAyLjMgLSBEaXNzZW1pbmF0aW9uIEFyZWEgQ2VudHJvaWRzDQoNCk5vdyB3ZSB3aWxsIGNyZWF0ZSBjZW50cm9pZHMgZnJvbSBEaXNzZW1pbmF0aW9uIEFyZWFzIHRoYXQgd2Ugd2lsbCB1c2UNCnRvIHF1ZXJ5IHdpdGggT1RQLg0KDQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCiMgY3JlYXRlIGNlbnRyb2lkcyBvZiBIYW1pbHRvbidzIERBIGJvdW5kYXJpZXMNCiMgYW5vdGhlciBuYW1lIGZvciBjZW50cm9pZCBpcyAnZ2VvbWV0cmljIGNlbnRlcicgc28gd2Ugd2lsbCB1c2UgKGdjKSBoZXJlDQpkYTIxX2hhbV9nYyA8LSBzdF9jZW50cm9pZChkYTIxX2hhbSkNCmBgYA0KDQpgYGB7cn0NCiMgcmVjYWxsIE9UUCByZXF1aXJlcyBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIGNvb3JkaW5hdGVzIGFzIG51bWVyaWMgdmFsdWVzIGluIHR3byBzZXBhcmF0ZSBjb2x1bW5zDQojIHdlIHdpbGwgdXNlIHRoZSBmdW5jdGlvbiBmcm9tIGFib3ZlIHRvIHNwbGl0IGdlb21ldHJ5IGFuZCBhZGQgcmVzdWx0cyBhcyBuZXcgY29sdW1ucw0KZGEyMV9oYW1fZ2MgPC0gc2ZjX2FzX2NvbHMoZGEyMV9oYW1fZ2MpDQoNCiMgY29uZmlybSBzcGxpdCB3aXRoIGEgZmluYWwgZGF0YSBjaGVjaw0KI3N1bW1hcnkoZGEyMV9oYW1fZ2MpDQpzdHIoZGEyMV9oYW1fZ2MpDQpgYGANCg0KIyMjIyAyLjQgLSBDb21tdW5pdHkgU3BhY2VzDQoNCkxldCdzIGhhdmUgYW5vdGhlciBsb29rIGF0IG91ciBDb21tdW5pdHkgU3BhY2VzIGRhdGEuDQoNCmBgYHtyfQ0KIyBmaW5hbCBkYXRhIGNoZWNrDQpzdHIoY3NwYWNlcykNCg0KIyB0aGVyZSBhcmUgOTcgY3NwYWNlcywgbGV0J3MgbWFrZSBhIHZhcmlhYmxlIHRvIHN0b3JlIHRoaXMNCmNzcGFjZXN0b3RhbCA8LSA5Nw0KYGBgDQoNCiMjIyMgMi41IC0gUGxvdA0KDQpGaW5hbGx5LCBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgc3R1ZHkgYXJlYSB0b2dldGhlciB3aXRoIGFsbCBvdXIgZGF0YS4NCg0KYGBge3IgZmlnLmFsaWduID0gImxlZnQiLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gN30NCiMgcGxvdCB0b2dldGhlcg0KdG1fc2hhcGUoZGEyMV9oYW0pICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAwLjEpICsNCnRtX3NoYXBlKGRhMjFfaGFtX2djKSArDQogIHRtX2J1YmJsZXMoY29sID0gImJsdWUiLCBzY2FsZSA9IC4zKSArDQp0bV9zaGFwZShjc3BhY2VzKSArDQogIHRtX2J1YmJsZXMoY29sID0gInJlZCIsIHNjYWxlID0gLjUpDQpgYGANCg0KUGVyZmVjdCENCg0KIyMjIFBBUlQgM2EgLSBTZXR1cCBPcGVuVHJpcFBsYW5uZXIgKE9UUCkNCg0KU2VjdGlvbnMgM2EsIDNiLCBhbmQgM2MgYXJlIGJhc2VkIG9uIHRoZSBmb2xsb3dpbmcgT1RQIHBhY2thZ2UNCnZpZ25ldHRlczoNCg0KLSAgIFtQcmVyZXF1aXNpdGVzXShodHRwczovL2RvY3Mucm9wZW5zY2kub3JnL29wZW50cmlwcGxhbm5lci9hcnRpY2xlcy9wcmVyZXF1aXNpdGVzLmh0bWwpDQotICAgW0dldHRpbmcNCiAgICBTdGFydGVkXShodHRwczovL2RvY3Mucm9wZW5zY2kub3JnL29wZW50cmlwcGxhbm5lci9hcnRpY2xlcy9vcGVudHJpcHBsYW5uZXIuaHRtbCkNCi0gICBbQWR2YW5jZWQNCiAgICBGZWF0dXJlc10oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9vcGVudHJpcHBsYW5uZXIvYXJ0aWNsZXMvYWR2YW5jZWRfZmVhdHVyZXMuaHRtbCkNCg0KfCAqKipOT1RFOioqIEphdmEgOCBpcyByZXF1aXJlZCB0byBydW4gT1RQLiBEb3dubG9hZCBKYXZhIDggW2hlcmUuXShodHRwczovL3d3dy5qYXZhLmNvbS9lbi9kb3dubG9hZC8pKg0KDQojIyMjIDMuMSBDcmVhdGUgdGhlIHJlcXVpcmVkIE9UUCBmb2xkZXIgYW5kIGRhdGEgc3RydWN0dXJlLg0KDQpBcyBvdXRsaW5lZCBpbiB0aGUgdmlnbmV0dGVzIGFib3ZlLCBPVFAgcmVxdWlyZXMgYSBjZXJ0YWluIGZvbGRlcg0Kc3RydWN0dXJlIHRvIHJ1bi4gQW4gZXhhbXBsZSBmb2xkZXIgc3RydWN0dXJlIGlzIHByb3ZpZGVkIGJlbG93IGZvcg0KcmVmZXJlbmNlLiBXZSB3aWxsIHJlY3JlYXRlIHRoaXMgc3RydWN0dXJlIHN0ZXAtYnktc3RlcCB3aXRoIHRoZQ0KZXhjZXB0aW9uIG9mIFN0ZXBzIDYgYW5kIDggd2hpY2ggd2Ugd2lsbCBza2lwIGFzIHRoZXkgYXJlIG5vdCByZXF1aXJlZA0KZm9yIHRoaXMgYW5hbHlzaXMuDQoNCnwgU3RlcCB8IEV4YW1wbGUgICAgICAgICAgICB8IERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwtLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgICAgICB8IC93ZCAgICAgICAgICAgICAgICB8IFdvcmtpbmcgZGlyZWN0b3J5ICAgICAgICAgICAgICAgICAgIHwNCnwgMSAgICB8IC9vdHAgICAgICAgICAgICAgICB8IFRvcCBmb2xkZXIgZm9yIHN0b3JpbmcgYWxsIE9UUCBkYXRhIHwNCnwgMiAgICB8IC9ncmFwaHMgICAgICAgICAgICB8IFN1YmZvbGRlciBjb250YWluaW5nIGFsbCBncmFwaHMgICAgIHwNCnwgMyAgICB8IC9kZWZhdWx0ICAgICAgICAgICB8IFN1YmZvbGRlciB0aGF0IGFjdHMgYXMgT1RQIHJvdXRlciAgIHwNCnwgNCAgICB8IG9zbS5wYmYgICAgICAgICAgICB8IFJlcXVpcmVkIE9wZW5TdHJlZXRNYXAgcm9hZCBtYXAgICAgIHwNCnwgNSAgICB8IHJvdXRlci1jb25maWcuanNvbiB8IE9wdGlvbmFsIGNvbmZpZyBmaWxlICAgICAgICAgICAgICAgIHwNCnwgNiAgICB8IGJ1aWxkLWNvbmZpZy5qc29uICB8IE9wdGlvbmFsIGNvbmZpZyBmaWxlICAgICAgICAgICAgICAgIHwNCnwgNyAgICB8IGd0ZnMuemlwICAgICAgICAgICB8IE9wdGlvbmFsIEdURlMgZGF0YSAgICAgICAgICAgICAgICAgIHwNCnwgOCAgICB8IGRlbS50aWYgICAgICAgICAgICB8IE9wdGlvbmFsIEVsZXZhdGlvbiBkYXRhICAgICAgICAgICAgIHwNCg0KKipTdGVwIDE6IENyZWF0ZSB0aGUgT1RQIGZvbGRlci4qKg0KDQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCiMgY29uZmlybSBjb3JyZWN0IHZlcnNpb24gb2YgSmF2YSAob3B0aW9uYWwpDQojb3RwX2NoZWNrX2phdmEoKQ0KDQojIFN0ZXAgMSAtIGNyZWF0ZSBvdHAgZm9sZGVyDQpwYXRoX2RhdGEgPC0gZmlsZS5wYXRoKGhlcmU6OmhlcmUoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIm90cCIpDQpkaXIuY3JlYXRlKHBhdGhfZGF0YSkNCg0KIyBkb3dubG9hZCB0aGUgcmVxdWlyZWQgT1RQIGphciBmaWxlDQpwYXRoX290cCA8LSBvdHBfZGxfamFyKHBhdGhfZGF0YSwgY2FjaGUgPSBGQUxTRSkNCmBgYA0KDQoqKlN0ZXAgMjogQ3JlYXRlIHRoZSBncmFwaHMgZm9sZGVyLioqDQoNCkZvciBzb21lIHJlYXNvbiBJIGNvdWxkIG5vdCBzb3J0IG91dCwgT1RQIHJlcXVpcmVkIHRoZSAiZ3JhcGhzIiBmb2xkZXINCnRvIGJlIGNyZWF0ZWQgb24gbXkgY29tcHV0ZXIgZmlyc3QgdGhyb3VnaCB0aGUgZGVtbyBmdW5jdGlvbiBwcmlvciB0bw0KYmVpbmcgYWJsZSB0byBjcmVhdGUgbXkgb3duIGdyYXBoIG9yIHJvdXRlci4gVGhpcyBmdW5jdGlvbiBhbHNvDQpkb3dubG9hZHMgdGhlICJkZWZhdWx0IiBmb2xkZXIgYXV0b21hdGljYWxseQ0KDQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCm90cF9kbF9kZW1vKHBhdGhfZGF0YSkNCmBgYA0KDQoqKlN0ZXAgMzogQ3JlYXRlIHRoZSByb3V0ZXIgZm9sZGVyLioqDQoNCmBgYHtyIHJlc3VsdHM9J2hpZGUnfQ0KZGlyLmNyZWF0ZShmaWxlLnBhdGgoaGVyZTo6aGVyZSgib3RwL2dyYXBocy9oYW1pbHRvbiIpKSkNCmBgYA0KDQoqKlN0ZXAgNDogRG93bmxvYWQgT3BlblN0cmVldE1hcCAoT1NNKSBkYXRhLioqDQoNClRoaXMgc3RlcCB1c2VzIHRoZSBbb3NtZXh0cmFjdA0KcGFja2FnZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL29zbWV4dHJhY3QvaW5kZXguaHRtbCkNCmFuZCBpcyBiYXNlZCBvbiB0aGUgW0ludHJvZHVjaW5nIG9zbWV4dHJhY3QNClZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvb3NtZXh0cmFjdC92aWduZXR0ZXMvb3NtZXh0cmFjdC5odG1sKS4NCg0KfCAqKipOT1RFOioqIERvd25sb2FkaW5nIE9TTSBkYXRhIGNhbiB0YWtlIGEgZmV3IG1pbnV0ZXMgYXMgdGhlIGZpbGVzIGNhbiBiZSBxdWl0ZSBsYXJnZS4qDQoNCmBgYHtyfQ0KIyBnZW9jb2RlIGhhbWlsdG9uJ3MgbG9jYXRpb24NCmhhbWlsdG9uID0gdG1hcHRvb2xzOjpnZW9jb2RlX09TTSgiSGFtaWx0b24sIENhbmFkYSIpJGNvb3Jkcw0KDQojIENoZWNrIG9zbSBkYXRhIGZvciBoYW1pbHRvbidzIGxvY2F0aW9uDQpvZV9tYXRjaChoYW1pbHRvbiwgcHJvdmlkZXIgPSAiZ2VvZmFicmlrIikNCm9lX21hdGNoKGhhbWlsdG9uLCBwcm92aWRlciA9ICJvcGVuc3RyZWV0bWFwX2ZyIikNCmBgYA0KDQpIZXJlIHdlIGNhbiBzZWUgdGhhdCBnZW9mcmFicmlrIHNob3dzIGRhdGEgaXMgb25seSBhdmFpbGFibGUgZm9yIHRoZQ0KcHJvdmluY2UsIHdoZXJlYXMgb3BlbnN0cmVldG1hcF9mciBoYXMgZGF0YSBhdmFpbGFibGUgYXQgdGhlIHJlZ2lvbmFsDQpsZXZlbC4gV2Ugd2lsbCBkb3dubG9hZCB0aGUgZGF0YSBmcm9tIHRoZSByZWdpb25hbCBsZXZlbCBhcyBpdCBoYXMgYQ0Kc2lnbmlmaWNhbnRseSBzbWFsbGVyIGZpbGUgc2l6ZS4NCg0KYGBge3IgcmVzdWx0cz0naGlkZSd9DQojIGRvd25sb2FkIG9zbSBkYXRhIGZvciBIYW1pbHRvbg0Kb2VfZ2V0KCJIYW1pbHRvbiIsIA0KICAgICAgIHByb3ZpZGVyID0gIm9wZW5zdHJlZXRtYXBfZnIiLA0KICAgICAgIGRvd25sb2FkX2RpcmVjdG9yeSA9IGhlcmU6OmhlcmUoIm90cC9ncmFwaHMvaGFtaWx0b24iKSwNCiAgICAgICBib3VuZGFyeSA9IGNkMjFfaGFtLA0KICAgICAgIGJvdW5kYXJ5X3R5cGUgPSBjKCJzcGF0IiwgImNsaXBzcmMiKSwNCiAgICAgICBkb3dubG9hZF9vbmx5ID0gVFJVRSwgDQogICAgICAgc2tpcF92ZWN0b3J0cmFuc2xhdGUgPSBUUlVFKQ0KYGBgDQoNCioqU3RlcCA1OiBDb25maWd1cmUgdGhlIHJvdXRlci4qKg0KDQpUaGUgcm91dGVyLWNvbmZpZyBmaWxlIGRpY3RhdGVzIGhvdyBPVFAgd2lsbCBjYWxjdWxhdGUgcm91dGUgb3B0aW9ucy4NCkZvciBleGFtcGxlLCB0aGUgcm91dGVyLWNvbmZpZyBmaWxlIHNwZWNpZmllcyB0aGUgZGVmYXVsdCB3YWxraW5nIHNwZWVkDQpmcm9tIFBvaW50IEEgdG8gUG9pbnQgQi4gU2luY2Ugb3VyIGFuYWx5c2lzIGlzIG9uIHRoZSBzZW5pb3IgcG9wdWxhdGlvbiwNCmFuZCBpdCBpcyByZWFzb25hYmxlIHRvIGFzc3VtZSB0aGF0IGF2ZXJhZ2Ugd2Fsa2luZyBzcGVlZCBkZWNyZWFzZXMgd2l0aA0KYWdlLCB3ZSB3aWxsIGNvbmZpZ3VyZSBvdXIgcm91dGVyIHRvIHJlZmxlY3QgdGhpcy4gQnV0IGJ5IGhvdyBtdWNoPw0KTHVja2lseSByZXNlYXJjaGVycyBhdCBNY01hc3RlciBVbml2ZXJzaXR5IGluIEhhbWlsdG9uIGhhdmUgYWxyZWFkeQ0KY29tcGxldGVkIHRoZSBsZWcgd29yayBmb3IgdXMNCltoZXJlLl0oaHR0cHM6Ly93d3cubWNtYXN0ZXJvcHRpbWFsYWdpbmcub3JnL2UtbGVhcm5pbmcvd2Fsa2luZy1zcGVlZC1pcy1pdC1hLXZpdGFsLXNpZ24pDQoNCmBgYHtyfQ0KIyBtYWtlIGEgY29uZmlnIG9iamVjdA0Kcm91dGVyX2NvbmZpZyA8LSBvdHBfbWFrZV9jb25maWcoInJvdXRlciIpICAgICANCg0KIyBjaGVjayBkZWZhdWx0IHdhbGtpbmcgc3BlZWQNCnJvdXRlcl9jb25maWckcm91dGluZ0RlZmF1bHRzJHdhbGtTcGVlZA0KYGBgDQoNClRoZSBkZWZhdWx0IHdhbGtpbmcgc3BlZWQgb2YgT1RQIGlzIDEuMzQgbWV0cmVzIHBlciBzZWNvbmQuIExldCdzIHNlZQ0Kd2hhdCB0aGUgTWNNYXN0ZXIgcmVzZWFyY2hlcnMgZm91bmQuDQoNCmBgYHtyfQ0KIyBsZXQncyBjcmVhdGUgYSBkYXRhZnJhbWUgb2YgTWNNYXN0ZXIgVW5pdmVyc2l0eSdzIGZpbmRpbmdzIG9uIHNlbmlvciB3YWxraW5nIHNwZWVkDQp3YWxrX3NwZWVkIDwtIGRhdGEuZnJhbWUoImFnZSIgPSBjKCI2MC02OSIsICI3MC03OSIsICI4MC04OSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJzcm1hbGUiID0gYygxLjM0LCAxLjI2LCAwLjk3KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAic3JmZW1hbGUiID0gYygxLjI0LCAxLjEzLCAwLjk0KSkNCndhbGtfc3BlZWQNCmBgYA0KDQpgYGB7cn0NCiMgbm93IGxldCdzIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBzZW5pb3Igd2Fsa2luZyBzcGVlZA0Kc3JtYWxlX2F2ZyA8LSAoc3VtKHdhbGtfc3BlZWQkc3JtYWxlKS8zKQ0Kc3JmZW1hbGVfYXZnIDwtIChzdW0od2Fsa19zcGVlZCRzcmZlbWFsZSkvMykNCnNyYXZnIDwtICgoc3JtYWxlX2F2ZyArIHNyZmVtYWxlX2F2ZykvMikNCnNyYXZnDQpgYGANCg0KVGhlIGF2ZXJhZ2Ugc2VuaW9yIHdhbGtpbmcgc3BlZWQgaXMgMS4xNSBtZXRyZXMgcGVyIHNlY29uZC4gTGV0J3MNCnJlY2FsaWJyYXRlIE9UUCdzIGRlZmF1bHQgd2Fsa2luZyBzcGVlZC4NCg0KYGBge3J9DQojIGNhbGlicmF0ZSB0aGUgb3RwIHJvdXRlcg0Kcm91dGVyX2NvbmZpZyRyb3V0aW5nRGVmYXVsdHMkd2Fsa1NwZWVkIDwtIDEuMTUgDQoNCiMgY29uZmlybSBjaGFuZ2UNCnJvdXRlcl9jb25maWckcm91dGluZ0RlZmF1bHRzJHdhbGtTcGVlZA0KYGBgDQoNCmBgYHtyIGVjaG89RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KIyBub3cgdGhhdCBPVFAgaXMgY2FsaWJyYXRlZCwgbGV0J3MgY29uZmlybSB0aGF0IHRoZSBuZXcgY29uZmlnIGZpbGUgaXMgdmFsaWQNCm90cF92YWxpZGF0ZV9jb25maWcocm91dGVyX2NvbmZpZykgICAgICAgICAgICANCmBgYA0KDQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCiMgU2F2ZSB0aGUgY29uZmlnIGZpbGUNCm90cF93cml0ZV9jb25maWcocm91dGVyX2NvbmZpZywNCiAgICAgICAgICAgICAgICAgZGlyID0gcGF0aF9kYXRhLA0KICAgICAgICAgICAgICAgICByb3V0ZXIgPSAiaGFtaWx0b24iKQ0KYGBgDQoNCioqU3RlcCA3OiBEb3dubG9hZCB0aGUgR1RGUyBmZWVkLioqDQoNCkdURlMgc3RhbmRzIGZvciBHZW5lcmFsIFRyYW5zaXQgRmVlZCBTcGVjaWZpY2F0aW9uIGFuZCwgYWNjb3JkaW5nIHRvDQpbZ3Rmcy5vcmddKGh0dHBzOi8vZ3Rmcy5vcmcvKSwgImlzIGEgZGF0YSBzcGVjaWZpY2F0aW9uIHRoYXQgYWxsb3dzDQpwdWJsaWMgdHJhbnNpdCBhZ2VuY2llcyB0byBwdWJsaXNoIHRoZWlyIHRyYW5zaXQgZGF0YSBpbiBhIGZvcm1hdCB0aGF0DQpjYW4gYmUgY29uc3VtZWQgYnkgYSB3aWRlIHZhcmlldHkgb2Ygc29mdHdhcmUgYXBwbGljYXRpb25zIi4gSWYgeW91IHVzZQ0KR29vZ2xlIE1hcHMgdG8gcGxhbiB5b3VyIHJvdXRlIHVzaW5nIHRyYW5zaXQsIHlvdSBoYXZlIGJlbmVmaXRlZCBmcm9tIGENCkdURlMgZmVlZC4NCg0KSGFtaWx0b24ncyB0cmFuc2l0IGFnZW5jeSBpcyBjYWxsZWQgdGhlIEhhbWlsdG9uIFN0cmVldCBSYWlsd2F5IChIU1IpLA0Kd2hpY2ggaXJvbmljYWxseSBoYXMgbm90IG9wZXJhdGVkIGFueSByYWlsIHJlbGF0ZWQgdHJhbnNpdCBzaW5jZSAxOTUxLg0KVG9tIEx1dG9uIG1haW50YWlucyBhIG5lYXQgd2Vic2l0ZSBjYWxsZWQgW0hhbWlsdG9uIFRyYW5zaXQNCkhpc3RvcnldKGh0dHA6Ly93d3cudHJhaW53ZWIub3JnL2hhbXRyYW5zaXRoaXN0L2luZGV4Lmh0bWwpIHdoaWNoIGlzDQp3b3J0aCBhIGxvb2sgaWYgaW50ZXJlc3RlZC4gVGhlIGh5cGVybGluayBmb3IgSFNSJ3MgR1RGUyBmZWVkIGNhbiBiZQ0KZm91bmQNCltoZXJlXShodHRwczovL29wZW4uaGFtaWx0b24uY2EvZG9jdW1lbnRzLzZlZWNjZjE3MmM4MjRjMmRiMDQ4NGFlYTU0ZWQ3ZmU0L2Fib3V0KS4NCg0KfCAqKipOT1RFOioqIFRoZSBHVEZTIGZlZWQgbXVzdCBiZSBpbiBhIHppcHBlZCBmb2xkZXIgd2l0aCAiZ3RmcyIgaW4gdGhlIG5hbWUuKg0KDQpgYGB7cn0NCnVybCA8LSAiaHR0cHM6Ly9nb29nbGVoc3Jkb2NzLmhhbWlsdG9uLmNhLyIgIA0KZGVzdGZpbGUgPSBoZXJlOjpoZXJlKCJvdHAiLA0KICAgICAgICAgICAgICAgICAgICAgICJncmFwaHMiLA0KICAgICAgICAgICAgICAgICAgICAgICJoYW1pbHRvbiIsDQogICAgICAgICAgICAgICAgICAgICAgImhhbV9ndGZzLnppcCIpDQpkb3dubG9hZC5maWxlKHVybCwgZGVzdGZpbGUsIG1vZGU9IndiIikNCmBgYA0KDQpPdXIgT1RQIHJvdXRlciBpcyBub3cgcmVhZHkhDQoNCiMjIyBQQVJUIDNiIC0gQ29ubmVjdCBhbmQgVGVzdCBPVFANCg0KTm93IHRoYXQgb3VyIGRhdGEgaXMgaW4gb3JkZXIgYW5kIHRoZSBPVFAgaXMgY29uZmlndXJlZCwgd2UgY2FuIHRlc3Qgb3VyDQpjb25uZWN0aW9uLiBBIGZldyBwdWJsaWMgc2VydmljZSBhbm5vdW5jZW1lbnRzIHlvdSBzaG91bGQgYmUgYXdhcmUgb2YNCmJlZm9yZSB3ZSBtb3ZlIGFueSBmdXJ0aGVyOg0KDQoxLiAgQnVpbGRpbmcgYW5kIHF1ZXJ5aW5nIE9UUCBjYW4gYmUgcXVpdGUgY29tcHV0YXRpb25hbGx5IGludGVuc2l2ZSBhbmQNCiAgICBzZXZlcmFsIE9UUCBmdW5jdGlvbnMgaGF2ZSBvcHRpb25zIHRvIGluY3JlYXNlIHRoZSBhbW91bnQgbWVtb3J5IG9yDQogICAgdGhlIG51bWJlciBvZiBjb3JlcyB5b3VyIGNvbXB1dGVyIHVzZXMgaW4gb3JkZXIgdG8gc3BlZWQgdXAgdGhlDQogICAgcHJvY2Vzcy4NCg0KMi4gIEl0IGlzIGVudGlyZWx5IHVwIHRvIHlvdXIgZGlzY3JldGlvbiwgZ2l2ZW4gdGhlIHNpemUgb2YgeW91ciBkYXRhDQogICAgYW5kIHlvdXIgY29tcHV0ZXJzIGFiaWx0eSwgd2hldGhlciB0byBpbmNyZWFzZSB0aGUgbWVtb3J5IG9yIGNvcmVzLg0KICAgIEZvciByZWZlcmVuY2UsIHRoaXMgZW50aXJlIHNjcmlwdCB1c2VzIHRoZSBPU00gZGVmYXVsdHMgYW5kIHJ1bnMgaW4NCiAgICBhcm91bmQgZml2ZSBtaW51dGVzIGZyb20gc3RhcnQgdG8gZmluaXNoLg0KDQozLiAgV2hlcmUgYXBwbGljYWJsZSBiZWxvdywgSSBoYXZlIGluZGljYXRlIGhvdyB0byBpbmNyZWFzZSBtZW1vcnkgYW5kDQogICAgY29yZXMgc2hvdWxkIHlvdSB3aXNoIHRvIGRvIHNvLg0KDQp8ICoqKk5PVEU6KiogT25jZSBjb25uZWN0ZWQsIE9UUCB3aWxsIGF1dG9tYXRpY2FsbHkgbG9hZCBhbiBpbnRlcmFjdGl2ZSB3ZWIgaW50ZXJmYWNlIGluIHlvdXIgYnJvd3Nlci4gWW91IGNhbiBwbGF5IHdpdGggdGhlIGludGVyZmFjZSBhcyB5b3Ugd2lzaCBvciBpZ25vcmUgaXQgZW50aXJlbHksIGl0IGlzIG5vdCByZXF1aXJlZCBmb3IgdGhlIGFuYWx5c2lzLioNCnwgDQoNCj4gKioqV0FSTklORzoqKiBOZXZlciBzZXQgJ25jb3JlcycgdG8gbW9yZSBjb3JlcyB0aGFuIGFyZSBhdmFpbGFibGUgb24NCj4geW91ciBjb21wdXRlci4gVG8gY2hlY2sgdGhlIG51bWJlciBvZiBjb3JlcyBvbiB5b3VyIGNvbXB1dGVyLCBob2xkDQo+IGN0cmwgKyBzaGlmdCArIGVzYyB0byBicmluZyB1cCB0YXNrIG1hbmFnZXIuIENsaWNrIHRoZSBwZXJmb3JtYW5jZSB0YWINCj4gYW5kIHRoZSBudW1iZXIgb2YgY29yZXMgc2hvdWxkIGJlIGluZGljYXRlZCBpbiB0aGUgYm90dG9tIHJpZ2h0LioNCg0KIyMjIyAzLjIgLSBCdWlsZCB0aGUgT1RQIGdyYXBoLg0KDQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCiMgY3JlYXRlIHRoZSBIYW1pbHRvbiBPVFAgZ3JhcGggZmlsZQ0KIyBvcHRpb25hbCBtZW1vcnkgYnVtcCB1cCAtIHNlZSBleHBsYW5hdGlvbiBhYm92ZQ0KbG9nMSA8LSBvdHBfYnVpbGRfZ3JhcGgob3RwID0gcGF0aF9vdHAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgZGlyID0gcGF0aF9kYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgIG1lbW9yeSA9IDIwNDgsICAgICAgICAjZGVmYXVsdA0KICAgICAgICAgICAgICAgICAgICAgICAgcm91dGVyID0gImhhbWlsdG9uIikNCg0KIyBsYXVuY2ggb3RwIGFuZCBsb2FkIHRoZSBoYW1pbHRvbiBncmFwaCAodGhlIHNlcnZlcikNCmxvZzIgPC0gb3RwX3NldHVwKG90cCA9IHBhdGhfb3RwLCANCiAgICAgICAgICAgICAgICAgIGRpciA9IHBhdGhfZGF0YSwNCiAgICAgICAgICAgICAgICAgIHJvdXRlciA9ICJoYW1pbHRvbiIpDQoNCmBgYA0KDQojIyMjIDMuMyAtIE1ha2UgdGhlIGNvbm5lY3Rpb24uDQoNCmBgYHtyfQ0KIyBjb25uZWN0IHRvIE9UUCBmcm9tIFINCm90cGNvbiA8LSBvdHBfY29ubmVjdChyb3V0ZXIgPSAiaGFtaWx0b24iKQ0KYGBgDQoNCnwgKioqTk9URToqKiB0aGUgT1RQIHdlYiBpbnRlcmZhY2Ugd2lsbCBhdXRvLWxvYWQgd2hlbiByZWFkeSBhdCA8aHR0cDovL2xvY2FsaG9zdDo4MDgwPi4qDQoNCiMjIyMgMy40IC0gVGVzdCB0aGUgY29ubmVjdGlvbi4NCg0KYGBge3J9DQojIHRlc3QgYnkgZ2V0dGluZyBhIHJvdXRlIGZyb20gTWNNYXN0ZXIgVW5pdmVyc2l0eSB0byBUaW0gSG9ydG9ucyBGaWVsZA0KaGFtX3Rlc3QxIDwtIG90cF9wbGFuKG90cGNvbiwgDQogICAgICAgICAgICAgICAgICAgICAgZnJvbVBsYWNlID0gYygtNzkuOTE3MDU0LCA0My4yNTgwMzIpLCANCiAgICAgICAgICAgICAgICAgICAgICB0b1BsYWNlID0gYygtNzkuODMwODU1LCA0My4yNTEwMTQpKQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMn0NCiMgdmlldyB0aGUgcm91dGUNCm9zbV9oYW0gPC0gcmVhZF9vc20oaGFtX3Rlc3QxLCBleHQ9MS4xKQ0KdG1fc2hhcGUob3NtX2hhbSkgKyB0bV9yZ2IoKSArDQogIHRtX3NoYXBlKGhhbV90ZXN0MSkgKw0KICB0bV9saW5lcyhjb2wgPSAnYmx1ZScsIGx3ZCA9IDIsIGFscGhhID0gMC44KQ0KYGBgDQoNCiMjIyMgMy41IC0gVGVzdCB0aGUgaXNvY2hyb25lIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCiMgdGVzdCBieSBnZXR0aW5nIGFuIGlzb2Nocm9uZSBmb3IgTWNNYXN0ZXIgVW5pdmVyc2l0eQ0KIyBvcHRpb25hbCBuY29yZXMgYnVtcCB1cCAtIHNlZSBleHBsYW5hdGlvbiBhYm92ZQ0KaGFtX3Rlc3QyICA8LSBvdHBfaXNvY2hyb25lKG90cGNvbiA9IG90cGNvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tUGxhY2UgPSBjKC03OS45MTcxNTEsIDQzLjI1ODA1MCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZSA9IGMoIldBTEsiLCAiVFJBTlNJVCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGVfdGltZSA9IGFzLlBPU0lYY3Qoc3RycHRpbWUoIjIwMjItMDgtMDMgMDk6MDAiLCAiJVktJW0tJWQgJUg6JU0iKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4V2Fsa0Rpc3RhbmNlID0gMTAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjdXRvZmZTZWMgPSAoYyg1LCAxMCwgMTUpICogNjApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb3JlcyA9IDEpICAgICNkZWZhdWx0DQoNCiMgY29udmVydCB0byBtaW51dGVzIGZvciBwbG90dGluZw0KaGFtX3Rlc3QyJG1pbnV0ZXMgPSAoaGFtX3Rlc3QyJHRpbWUgLyA2MCkNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDN9DQojIHZpZXcgdGhlIGlzb2Nocm9uZQ0Kb3NtX2hhbSA8LSByZWFkX29zbShoYW1fdGVzdDIsIGV4dD0xLjEpDQp0bV9zaGFwZShvc21faGFtKSArIHRtX3JnYigpICsNCiAgdG1fc2hhcGUoaGFtX3Rlc3QyKSArDQogIHRtX2ZpbGwoIm1pbnV0ZXMiLA0KICAgICAgICAgIGJyZWFrcyA9IGMoMCwgNS4wMSwgMTAuMDEsIDE1LjAxKSwNCiAgICAgICAgICBzdHlsZSA9ICJmaXhlZCIsDQogICAgICAgICAgcGFsZXR0ZSA9ICItUmRZbEJ1IikgKw0KICB0bV9ib3JkZXJzKCkNCmBgYA0KDQpCcmlsbGlhbnQsIGl0IHdvcmtzIQ0KDQojIyMgUEFSVCAzYyAtIFF1ZXJ5IE9wZW5UcmlwUGxhbm5lciBmb3IgSXNvY2hyb25lcw0KDQojIyMjIDMuNiAtIFF1ZXJ5IE9UUC4NCg0KV2Ugd2lsbCBub3cgcXVlcnkgT1RQIHRvIGdldCAxNS1taW51dGUgaXNvY2hyb25lcyBmcm9tIGFsbCA4OTENCkRpc3NlbWluYXRpb24gQXJlYSBjZW50cm9pZHMuDQoNCnwgKioqTk9URToqKiBUaGUgZnVuY3Rpb24gYmVsb3cgdXNlcyB0aGUgZGVmYXVsdCBuY29yZXMgc2V0dGluZyBvZiBuY29yZXMgPSAxLiBUaGVyZWZvcmUsIGV4cGVjdCB0aGUgZnVuY3Rpb24gdG8gdGFrZSBhIGZldyBtaW51dGVzIHRvIGNvbXB1dGUuIFlvdSBjYW4gYWxzbyBpbmNyZWFzZSBuY29yZXMgd2hlcmUgaW5kaWNhdGVkIGJlbG93IGlmIGRlc2lyZWQsIGhvd2V2ZXIsIHlvdSBzaG91bGQgcmVhZCB0aGUgd2FybmluZyBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoaXMgc2VjdGlvbiBmaXJzdC4qDQoNCmBgYHtyIHJlc3VsdHM9J2hpZGUnfQ0KIyBnZXQgaXNvY2hyb25lIHBvbHlnb25zIGZvciBlYWNoIERBIGNlbnRyb2lkIHZpYSBsb29wDQoNCiMgdmFyaWFibGUgd2l0aCBudW1iZXIgb2YgREFzDQpucm93cyA8LSBucm93KGRhMjFfaGFtX2djKQ0KDQojIGVtcHR5IGxpc3QgdG8gc3RvcmUgb3V0cHV0DQpkYTIxX2hhbV9nY19pc28gPC0gbGlzdCgpDQoNCiMgZ2V0IGlzb2Nocm9uZSBwb2x5Z29uIGZvciBlYWNoIERBIGNlbnRyb2lkIGFuZCBzdG9yZSBpbiBkYTIxX2hhbV9pc28gbGlzdA0KIyBjdXRvZmZzZWMgaXMgdGhlIG1heCB0aW1lIGluIHNlY29uZHMgZm9yIHRoZSBpc29jaHJvbmUgKDE1IG1pbnV0ZXMgZm9yIHRoaXMgYW5hbHlzaXMpDQojIA0KZm9yIChpIGluIDE6bnJvd3Mpew0KICBkYTIxX2hhbV9nY19pc29bW2ldXSA8LSBvdHBfaXNvY2hyb25lKG90cGNvbiA9IG90cGNvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tUGxhY2UgPSBjKGRhMjFfaGFtX2djJGxvbltpXSwgZGEyMV9oYW1fZ2MkbGF0W2ldKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tSUQgPSBjKGRhMjFfaGFtX2djJERBVUlEW2ldKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlID0gYygiV0FMSyIsICJUUkFOU0lUIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZV90aW1lID0gYXMuUE9TSVhjdChzdHJwdGltZSgiMjAyMi0wOC0wMyAwOTowMCIsICIlWS0lbS0lZCAlSDolTSIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhXYWxrRGlzdGFuY2UgPSAxMDAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1dG9mZlNlYyA9ICgxNSAqIDYwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29yZXMgPSAxKX0gICNkZWZhdWx0DQpgYGANCg0KYGBge3J9DQojIGNoZWNrIGZpcnN0IHJlY29yZCB0byBjb25maXJtIGNvbXBsZXRlDQpoZWFkKGRhMjFfaGFtX2djX2lzbywgMSkNCmBgYA0KDQojIyMjIDMuNyAtIENsZWFuIHRoZSByZXN1bHRzLg0KDQpPVFAgaXMgdHlwaWNhbGx5IG5vdCBhYmxlIHRvIHF1ZXJ5IGZyb20gYWxsIGNlbnRyb2lkcy4gVGhlcmUgYXJlIHdheXMgdG8NCmFkZHJlc3MgdGhpcywgYnV0IHRvIGtlZXAgdGhpbmdzIHNpbXBsZSB3ZSB3aWxsIG5vdCBnbyBpbnRvIHRoYXQgbGV2ZWwNCm9mIGRldGFpbCBoZXJlLg0KDQpgYGB7cn0NCiMgdGhlc2Ugd291bGQgc2hvdyBhcyBOQSB2YWx1ZXMgLSBsZXRzIGNoZWNrDQpzdW0oaXMubmEoZGEyMV9oYW1fZ2NfaXNvKSkNCmBgYA0KDQozMiBjZW50cm9pZHMgZnJvbSBvdXIgODkxIGRpc3NlbWluYXRpb24gYXJlYXMgaGF2ZSB6ZXJvIHZhbHVlcy4gV2Ugd2lsbA0KcmVtb3ZlIHRoZXNlIHRvIGZhY2lsaXRhdGUgY2xlYW5pbmcuDQoNCmBgYHtyfQ0KIyByZW1vdmUgTkEgdmFsdWVzDQpkYTIxX2hhbV9nY19pc28gPC0gZGEyMV9oYW1fZ2NfaXNvW2RhMjFfaGFtX2djX2lzbyAhPSAiTkEiXQ0KDQojIGNvbnZlcnQgcmVzdWx0IHRvIGEgc2luZ2xlIHNpbXBsZSBmZWF0dXJlDQpkYTIxX2hhbV9nY19pc28gPSBkby5jYWxsKHJiaW5kLCBkYTIxX2hhbV9nY19pc28pDQoNCiMgc2VsZWN0IGRlc2lyZWQgY29sdW1ucyANCmRhMjFfaGFtX2djX2lzbyA8LSBkYTIxX2hhbV9nY19pc28gJT4lDQogIGRwbHlyOjpzZWxlY3QoYygzLCA0KSkNCg0KIyByZW5hbWUgY29sdW1ucyB0byBmYWNpbGl0YXRlIG1lcmdlciB0byBtYWtlIGRhdGEgd2hvbGUgYWdhaW4NCmRhMjFfaGFtX2djX2lzbyA8LSBkYTIxX2hhbV9nY19pc28gJT4lDQogIGRwbHlyOjpyZW5hbWUoIkRBVUlEIj0gMSkNCg0KIyBjaGVjaw0KaGVhZChkYTIxX2hhbV9nY19pc28sIDMpDQpgYGANCg0KIyMjIyAzLjggLSBQcmFjdGljZSBwbG90cy4NCg0KUGxvdCBhIHNpbmdsZSBpc29jaHJvbmUgdG8gc2VlIHdoYXQgaXQgbG9va3MgbGlrZS4NCg0KYGBge3IgZmlnLmFsaWduID0gImxlZnQiLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gN30NCiMgdGVzdCBwbG90IGEgc2luZ2xlIGlzb2Nocm9uZQ0Kb3NtX2hhbSA8LSByZWFkX29zbShjZDIxX2hhbSwgZXh0PTEuMSkNCnRtX3NoYXBlKG9zbV9oYW0pICsgdG1fcmdiKCkgKw0KICB0bV9zaGFwZShkYTIxX2hhbV9nY19pc29bZGEyMV9oYW1fZ2NfaXNvJERBVUlEID09ICIzNTI1MDQ2OCIsIF0pICsNCiAgdG1fZmlsbChjb2wgPSAiI0U1ODU0QiIpICsNCiAgdG1fYm9yZGVycygpDQpgYGANCg0KTm93IGxldCdzIHRyeSBwbG90dGluZyBhbGwgdGhlIGlzb2Nocm9uZXMgYXQgb25jZS4NCg0KYGBge3IgZmlnLmFsaWduID0gImxlZnQiLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gN30NCiMgdGVzdCBwbG90IGFsbCBpc29jaHJvbmVzDQpvc21faGFtIDwtIHJlYWRfb3NtKGNkMjFfaGFtLCBleHQ9MS4xKQ0KdG1fc2hhcGUob3NtX2hhbSkgKyB0bV9yZ2IoKSArDQogIHRtX3NoYXBlKGRhMjFfaGFtX2djX2lzbykgKw0KICB0bV9maWxsKGNvbCA9ICIjRTU4NTRCIikgKw0KICB0bV9ib3JkZXJzKCkNCmBgYA0KDQojIyMjIDMuOSAtIERpc2Nvbm5lY3QgZnJvbSBPVFAuDQoNCk1ha2Ugc3VyZSB0byBkaXNjb25uZWN0IGZyb20gdGhlIE9UUCBzZXJ2ZXIuDQoNCnwgKioqTk9URToqKiBPVFAgd2lsbCBrZWVwIHJ1bm5pbmcgaW4gdGhlIGJhY2tncm91bmQgb2YgeW91ciBjb21wdXRlciB1bnRpbCB5b3UgZGlzY29ubmVjdC4qDQoNCmBgYHtyfQ0KIyBvdHAgbm8gbG9uZ2VyIG5lZWRlZCAtIHN0b3AgY29ubmVjdGlvbiB0byBvdHAgcm91dGVyDQpvdHBfc3RvcCh3YXJuPUZBTFNFKQ0KYGBgDQoNCiMjIyBQQVJUIDQgLSBDYWxjdWxhdGUgQWNjZXNzaWJpbGl0eSBTY29yZXMNCg0KV2Ugd2lsbCBjYWxjdWxhdGUgYWNjZXNzaWJpbGl0eSBzY29yZXMgYXMgZm9sbG93czoNCg0KfCBTY29yZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBFcXVhdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEluZGljYXRvciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgQWNjZXNzaWJpbGl0eSBTY29yZSAgICAgICAgICAgICAgICAgIHwgTnVtYmVyIG9mIGNvbW11bml0eSBzcGFjZXMgcmVhY2hlZCBwZXIgZGlzc2VtaW5hdGlvbiBhcmVhIGRpdmlkZWQgYnkgdGhlIHRvdGFsIG51bWJlciBvZiBjb21tdW5pdHkgc3BhY2VzIGluIHRoZSBjaXR5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBQZXJjZW50YWdlIG9mIHRvdGFsIGNvbW11bml0eSBzcGFjZXMgaW4gdGhlIGNpdHkgcmVhY2hhYmxlIHBlciBkaXNzZW1pbmF0aW9uIGFyZWEgICAgICAgICB8DQp8IEF2ZXJhZ2UgQWNjZXNzaWJpbGl0eSBTY29yZSAgICAgICAgICB8IFN1bSBvZiBhY2Nlc3NpYmlsaXR5IHNjb3JlcyBmb3IgYWxsIGRpc3NlbWluYXRpb24gYXJlYXMgZGl2aWRlZCBieSB0aGUgdG90YWwgbnVtYmVyIGRpc3NlbWluYXRpb24gYXJlYXMgaW4gdGhlIGNpdHkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgQXZlcmFnZSBwZXJjZW50YWdlIG9mIHRvdGFsIGNvbW11bml0eSBzcGFjZXMgaW4gdGhlIGNpdHkgcmVhY2hhYmxlIHBlciBkaXNzZW1pbmF0aW9uIGFyZWEgfA0KfCBXZWlnaHRlZCBBY2Nlc3NpYmlsaXR5IFNjb3JlICAgICAgICAgfCBOdW1iZXIgb2YgY29tbXVuaXR5IHNwYWNlcyByZWFjaGFibGUgcGVyIGRpc3NlbWluYXRpb24gYXJlYSBtdWx0aXBsaWVkIGJ5IGl0cyBzZW5pb3IgcG9wdWxhdGlvbiBhbmQgdGhlbiBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgY29tbXVuaXR5IHNwYWNlcyBpbiB0aGUgY2l0eSB8IFBlcmNlbnRhZ2Ugb2YgdG90YWwgY29tbXVuaXR5IHNwYWNlcyByZWFjaGFibGUgcGVyIHNlbmlvciBwZXIgZGlzc2VtaW5hdGlvbiBhcmVhICAgICAgICAgIHwNCnwgQXZlcmFnZSBXZWlnaHRlZCBBY2Nlc3NpYmlsaXR5IFNjb3JlIHwgU3VtIG9mIHdlaWdodGVkIGFjY2Vzc2liaWxpdHkgc2NvcmVzIGZvciBhbGwgZGlzc2VtaW5hdGlvbiBhcmVhcyBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgZGlzc2VtaW5hdGlvbiBhcmVhcyBpbiB0aGUgY2l0eSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBBdmVyYWdlIHBlcmNlbnRhZ2Ugb2YgdG90YWwgY29tbXVuaXR5IHNwYWNlcyByZWFjaGFibGUgcGVyIHNlbmlvciBwZXIgZGlzc2VtaW5hdGlvbiBhcmVhICB8DQoNCiMjIyMgNC4xIC0gQ2FsY3VsYXRlIENvbW11bml0eSBTcGFjZXMgUmVhY2hlZC4NCg0KRGV0ZXJtaW5lIHRoZSBudW1iZXIgb2YgY29tbXVuaXR5IHNwYWNlcyByZWFjaGVkIGZvciBlYWNoIGRpc3NlbWluYXRpb24NCmFyZWEgaXNvY2hyb25lIGFuZCBhZGQgaXQgYXMgYSBjb2x1bW4gdG8gb3VyIGRhdGEuDQoNCmBgYHtyfQ0KIyBwZXJmb3JtIGludGVyc2VjdGlvbiBvZiAxNSBtaW51dGUgaXNvY2hyb25lcyBhbmQgY3NwYWNlcywgdGhlbiBhZGQgcG9pbnQgY291bnQgYXMgbnVtZXJpYyB0byBlYWNoIHBvbHlnb24NCmRhMjFfaGFtX2djX2lzb19pbnQgPC0gZGEyMV9oYW1fZ2NfaXNvICU+JQ0KICBtdXRhdGUoY3NwYWNlc18xNSA9IChhcy5udW1lcmljKGxlbmd0aHMoc3RfaW50ZXJzZWN0cyhkYTIxX2hhbV9nY19pc28sIGNzcGFjZXMpKSkpKQ0KDQojIGNvbmZpcm0NCmRhMjFfaGFtX2djX2lzb19pbnQNCmBgYA0KDQpgYGB7cn0NCiMgcmVtb3ZlIGdlb21ldHJ5IGZpcnN0IHRvIGZhY2lsaXRhdGUgbWVyZ2UNCnN0X2dlb21ldHJ5KGRhMjFfaGFtX2djX2lzb19pbnQpIDwtIE5VTEwNCg0KIyBjb25maXJtDQpkYTIxX2hhbV9nY19pc29faW50DQpgYGANCg0KYGBge3J9DQojIE1lcmdlIGJhY2sgaW50byB0aGUgREEgc2hhcGUgZmlsZSBvbiBER1VJRCB0byBwbG90IHJlc3VsdHMNCmRhMjFfaGFtIDwtIGxlZnRfam9pbih4ID0gZGEyMV9oYW0sDQogICAgICAgICAgICAgICAgICAgICAgeSA9IGRhMjFfaGFtX2djX2lzb19pbnQsDQogICAgICAgICAgICAgICAgICAgICAgYnkueCA9ICJER1VJRCIsDQogICAgICAgICAgICAgICAgICAgICAgYnkueSA9ICJER1VJRCIpDQoNCiMgY29uZmlybQ0KZGEyMV9oYW0NCmBgYA0KDQojIyMjIDQuMiAtIENhbGN1bGF0ZSBBY2Nlc3NpYmlsaXR5IFNjb3Jlcy4NCg0KQ2FsY3VsYXRlIGFjY2Vzc2liaWxpdHkgc2NvcmUgb2YgZWFjaCBkaXNzZW1pbmF0aW9uIGFyZWEgYnkgZGl2aWRpbmcgdGhlDQpyZXN1bHRzIGZyb20gU1RFUCAxIGJ5IHRoZSB0b3RhbCBcIyBvZiBjb21tdW5pdHkgc3BhY2VzIGluIEhhbWlsdG9uLg0KDQpgYGB7cn0NCiMgY2FsY3VhbHRlIHNjb3JlICglIG9mIHRoZSB0b3RhbCBjb21tdW5pdHkgc3BhY2VzIGluIEhhbWlsdG9uIHRoYXQgYXJlIHJlYWNoYWJsZSBmcm9tIGVhY2ggREEpIGFzIG5ldyBjb2x1bW4NCmRhMjFfaGFtIDwtIGRhMjFfaGFtICU+JQ0KICBtdXRhdGUoREFfU1NPUyA9ICgoZGEyMV9oYW0kY3NwYWNlc18xNSAvIGNzcGFjZXN0b3RhbCkgKiAxMDApKQ0KDQojIHJlY2FsbCB0aGF0IHdlIGRyb3BwZWQgYWxsIGNhc2VzIHdoZXJlIE9UUCBjb3VsZCBub3Qgcm91dGUgZnJvbSBhIERBIGNlbnRyb2lkDQojIGxldCdzIGhhdmUgYSBsb29rDQpzdW0oaXMubmEoZGEyMV9oYW0pKQ0KYGBgDQoNCjY0IE5BIHZhbHVlcy4gTGV0J3MgY2hlY2sgaG93IG1hbnkgZGlzdGluY3QgdmFsdWVzIGFyZSBpbiB0aGUgREFVSUQNCmNvbHVtbiB0byBtYWtlIHN1cmUgd2UgaGF2ZSA4OTEgLSBvbmUgZm9yIGVhY2ggZGlzc2VtaW5hdGlvbiBhcmVhLg0KDQpgYGB7cn0NCiMgY2hlY2sgZm9yIDg5MSB1bmlxdWUgdmFsdWVzDQpuX2Rpc3RpbmN0KGRhMjFfaGFtJERBVUlEKQ0KYGBgDQoNCjg5MSB2YWx1ZXMuIFBlcmZlY3QsIG91ciBkYXRhIGlzIHdob2xlIGFnYWluLg0KDQp8ICoqTk9URToqKiBUaGUga2V5IGFzc3VtcHRpb24gaGVyZSBpcyB0aGF0IGlmIE9UUCBjYW4gbm90IHJvdXRlIGZyb20gYSBsb2NhdGlvbiwgdGhlbiB0aGVyZSBhcmUgbm8gY29tbXVuaXR5IHNwYWNlcyByZWFjaGFibGUgZnJvbSBzYWlkIGxvY2F0aW9uLg0KDQpgYGB7cn0NCiMgbm93IGxldCdzIHBlcmZvcm0gYSBtYXN0ZXIgcmVwbGFjZSBvZiAiTkEiIHdpdGggIjAiIGZvciBhbGwgb3RoZXIgY29sdW1ucw0KZGEyMV9oYW1baXMubmEoZGEyMV9oYW0pXSA8LSAwDQoNCiMgY29uZmlybQ0Kc3VtKGlzLm5hKGRhMjFfaGFtKSkNCmBgYA0KDQpObyBtb3JlIE5BIHZhbHVlcyENCg0KIyMjIyA0LjMgLSBDYWxjdWxhdGUgV2VpZ2h0ZWQgQWNjZXNzaWJpbGl0eSBTY29yZXMuDQoNCkNhbGN1bGF0ZSB3ZWlnaHRlZCBhY2Nlc3NpYmlsaXR5IHNjb3JlcyBvZiBlYWNoIGRpc3NlbWluYXRpb24gYXJlYSBieQ0KbXVsdGlwbHlpbmcgdGhlIHJlc3VsdHMgZnJvbSBTVEVQIDIgYnkgdGhlIHNlbmlvciBwb3B1bGF0aW9uIG9mIGVhY2gNCmRpc3NlbWluYXRpb24gYXJlYSBhbmQgdGhlbiBkaXZpZGluZyBieSB0aGUgdG90YWwgc2VuaW9yIHBvcHVsYXRpb24gaW4NCkhhbWlsdG9uLg0KDQpgYGB7cn0NCiMgdXNlIG11dGF0ZSB0byBjYWxjdWxhdGUgZm9yIGFsbCB0aHJlZSB0aW1lIHBlcmlvZHMNCiMgYnkgbXVsdGlwbHlpbmcgKHdlaWdoaW5nKSBlYWNoIERBIFNTT1MgYnkgaXRzIHNlbmlvciBwb3B1bGF0aW9uDQojIHRoZW4gZGl2aWRpbmcgdGhlIHJlc3VsdCBieSB0b3RhbCBzZW5pb3IgcG9wdWxhdGlvbiBpbiBIYW1pbHRvbg0KZGEyMV9oYW0gPC0gZGEyMV9oYW0gJT4lDQogIG11dGF0ZShEQV9TU09TdyA9ICgoKERBX1NTT1MgKiBzcnBvcDIxKS9zcnBvcDIxdG90YWwpKjEwMCksDQogICAgICAgICBEQV9TU09Td181eSA9ICgoKERBX1NTT1MgKiBzcnBvcDI2KS9zcnBvcDI2dG90YWwpKjEwMCksDQogICAgICAgICBEQV9TU09Td18xMHkgPSAoKChEQV9TU09TICogc3Jwb3AzMSkvc3Jwb3AzMXRvdGFsKSoxMDApKQ0KDQojIHVzZSBtdXRhdGUgdG8gY2FsY3VsYXRlIGRpZmZlcmVuY2UgYmV0d2VlbiBXZWlnaHRlZCBTU09TIG5vdyBhbmQgNXkgLzEweQ0KZGEyMV9oYW0gPC0gZGEyMV9oYW0gJT4lDQogIG11dGF0ZShEQV9TU09Td181eV9jaGcgPSAoREFfU1NPU3dfNXkgLSBEQV9TU09TdyksDQogICAgICAgICBEQV9TU09Td18xMHlfY2hnID0gKERBX1NTT1N3XzEweSAtIERBX1NTT1N3KSkNCg0KIyBjaGVjayBkYXRhZnJhbWUNCmhlYWQoZGEyMV9oYW0pDQpgYGANCg0KIyMjIyA0LjQgLSBDYWxjdWxhdGUgQXZlcmFnZSBXZWlnaHRlZCBBY2Nlc3NpYmlsaXR5IFNjb3JlLg0KDQpgYGB7cn0NCiMgdXNlIHN1bW1hcml6ZQ0KaGFtX3Njb3Jlc19maW5hbCA8LSBkYTIxX2hhbSAlPiUNCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQ0KICBzdW1tYXJpc2UoREFfU1NPU19hdmcgPSBtZWFuKERBX1NTT1MpLA0KICAgICAgICAgICAgREFfU1NPU3dfYXZnID0gbWVhbihEQV9TU09TdyksDQogICAgICAgICAgICBEQV9TU09Td181eV9hdmcgPSBtZWFuKERBX1NTT1N3XzV5KSwNCiAgICAgICAgICAgIERBX1NTT1N3XzEweV9hdmcgPSBtZWFuKERBX1NTT1N3XzEweSkpIA0KDQojIFVzZSBtdXRhdGUgdG8gY2FsY3VsYXRlIGRpZmZlcmVuY2UgYmV0d2VlbiBXZWlnaHRlZCBTU09TIG5vdyBhbmQgNXkgLzEweQ0KaGFtX3Njb3Jlc19maW5hbCA8LSBoYW1fc2NvcmVzX2ZpbmFsICU+JQ0KICBtdXRhdGUoREFfU1NPU3dfNXlfY2hnID0gKERBX1NTT1N3XzV5X2F2ZyAtIERBX1NTT1N3X2F2ZyksDQogICAgICAgICBEQV9TU09Td18xMHlfY2hnID0gKERBX1NTT1N3XzEweV9hdmcgLSBEQV9TU09Td19hdmcpKQ0KDQojIGNoZWNrIGRhdGFmcmFtZQ0KaGFtX3Njb3Jlc19maW5hbA0KYGBgDQoNCiMjIyBQQVJUIDUgLSBQbG90IFJlc3VsdHMNCg0KIyMjIyA1LjEgLSBOdW1iZXIgb2YgQ29tbXVuaXR5IFNwYWNlcyBSZWFjaGVkIGJ5IERpc3NlbWluYXRpb24gQXJlYS4NCg0KYGBge3IgZmlnLmFsaWduID0gImxlZnQiLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMTB9DQpjc3BhY2VzX3JlYWNoIDwtIHRtX3NoYXBlKGRhMjFfaGFtKSArDQogIHRtX2ZpbGwoImNzcGFjZXNfMTUiLA0KICAgICAgICAgIHN0eWxlID0gImplbmtzIiwNCiAgICAgICAgICBsZWdlbmQuaGlzdCA9IFRSVUUsDQogICAgICAgICAgdGl0bGUgPSAiQ29tbXVuaXR5IFNwYWNlcyB3aXRoaW4gMTUgbWluLlxuKFRyYW5zaXQsIG1heC4gMTAwMG0gd2FsaywgQU0gUGVhaykiKSArDQogIHRtX2xheW91dChmcmFtZSA9IEZBTFNFLA0KICAgICAgICAgICAgbGVnZW5kLm91dHNpZGUgPSBUUlVFLA0KICAgICAgICAgICAgbGVnZW5kLm91dHNpZGUucG9zaXRpb24gPSAnYm90dG9tJywNCiAgICAgICAgICAgIGxlZ2VuZC5zdGFjayA9ICdob3Jpem9udGFsJywNCiAgICAgICAgICAgIGxlZ2VuZC50aXRsZS5mb250ZmFjZSA9ICdib2xkJywNCiAgICAgICAgICAgIGxlZ2VuZC5oaXN0LndpZHRoID0gMSwNCiAgICAgICAgICAgIGxlZ2VuZC5oaXN0LmhlaWdodCA9IDAuNikgKw0KICB0bV9ib3JkZXJzKGNvbCA9ICJncmV5NDAiLCBsd2QgPSAwLjEpICsNCiAgdG1fbGVnZW5kKHRpdGxlLnNpemU9MC45LA0KICAgICAgICAgICAgdGV4dC5zaXplID0gMC42LA0KICAgICAgICAgICAgcG9zaXRpb24gPSBjKCJsZWZ0IiwgImJvdHRvbSIpKSArDQogIHRtX3NoYXBlKGNkMjFfaGFtLCBjb2xvciA9ICdibGFjaycsIGZpbGwgPSBOQSkgKw0KICB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIsIGx3ZCA9IDAuMSkgKw0KICB0bV9jb21wYXNzKG5vcnRoID0gMCwgdHlwZSA9ICdhcnJvdycsIHNob3cubGFiZWxzID0wLCBwb3NpdGlvbiA9IGMoJ3JpZ2h0JywnYm90dG9tJykpICsgDQogIHRtX3NjYWxlX2Jhcihjb2xvci5kYXJrID0gImJsYWNrIiwNCiAgICAgICAgICAgICAgIHBvc2l0aW9uID0gYygibGVmdCIsICJib3R0b20iKSkNCmNzcGFjZXNfcmVhY2gNCmBgYA0KDQojIyMjIDUuMiAtIFdlaWdodGVkIEFjY2Vzc2liaWxpdHkgU2NvcmUgYnkgRGlzc2VtaW5hdGlvbiBBcmVhLCAyMDIxLg0KDQpgYGB7ciBmaWcuYWxpZ24gPSAibGVmdCIsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA3fQ0KREFfU1NPU3dfcGxvdCA8LSB0bV9zaGFwZShkYTIxX2hhbSkgKw0KICB0bV9wb2x5Z29ucygiREFfU1NPU3ciLCANCiAgICAgICAgICAgICAgc3R5bGU9ImplbmtzIiwgDQogICAgICAgICAgICAgIHRpdGxlPSJXZWlnaHRlZCBTU09TIiwNCiAgICAgICAgICAgICAgYm9yZGVyLmFscGhhID0gMCkgKw0KICB0bV9zaGFwZShjZDIxX2hhbSwgY29sb3IgPSAnYmxhY2snLCBmaWxsID0gTkEpICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAwLjEpICsNCiAgdG1fbGF5b3V0KGZyYW1lID0gRkFMU0UpICsNCiAgdG1fbGVnZW5kKHBvc2l0aW9uID0gYygicmlnaHQiLCAidG9wIikpICsNCiAgdG1fY29tcGFzcyhwb3NpdGlvbiA9IGMoJ3JpZ2h0JywnYm90dG9tJykpICsgDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIiksIHdpZHRoID0gMC4xNSkNCkRBX1NTT1N3X3Bsb3QNCmBgYA0KDQojIyMjIDUuMyAtIENoYW5nZSBpbiBXZWlnaHRlZCBBY2Nlc3NpYmlsaXR5IFNjb3JlIGJ5IERpc3NlbWluYXRpb24gQXJlYSwgMjAyMSB0byAyMDI2Lg0KDQpgYGB7ciBmaWcuYWxpZ24gPSAibGVmdCIsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA3fQ0KREFfU1NPU3dfNXlfY2hnX3Bsb3QgPC0gdG1fc2hhcGUoZGEyMV9oYW0pICsNCiAgdG1fcG9seWdvbnMoIkRBX1NTT1N3XzV5X2NoZyIsIA0KICAgICAgICAgICAgICBzdHlsZT0iamVua3MiLCANCiAgICAgICAgICAgICAgdGl0bGU9IkZpdmUtWWVhclxuQ2hhbmdlICgyMDI2KSIsDQogICAgICAgICAgICAgIGJvcmRlci5hbHBoYSA9IDApICsNCiAgdG1fc2hhcGUoY2QyMV9oYW0sIGNvbG9yID0gJ2JsYWNrJywgZmlsbCA9IE5BKSArDQogIHRtX2JvcmRlcnMoY29sID0gImJsYWNrIiwgbHdkID0gMC4xKSArDQogIHRtX2xheW91dChmcmFtZSA9IEZBTFNFKSArDQogIHRtX2xlZ2VuZChwb3NpdGlvbiA9IGMoInJpZ2h0IiwgInRvcCIpKSArDQogIHRtX2NvbXBhc3MocG9zaXRpb24gPSBjKCdyaWdodCcsJ2JvdHRvbScpKSArIA0KICB0bV9zY2FsZV9iYXIocG9zaXRpb24gPSBjKCJsZWZ0IiwgImJvdHRvbSIpLCB3aWR0aCA9IDAuMTUpDQpEQV9TU09Td181eV9jaGdfcGxvdA0KYGBgDQoNCiMjIyMgNS40IC0gQ2hhbmdlIGluIFdlaWdodGVkIEFjY2Vzc2liaWxpdHkgU2NvcmUgYnkgRGlzc2VtaW5hdGlvbiBBcmVhLCAyMDIxIHRvIDIwMzEuDQoNCmBgYHtyIGZpZy5hbGlnbiA9ICJsZWZ0IiwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDd9DQpEQV9TU09Td18xMHlfY2hnX3Bsb3QgPC0gdG1fc2hhcGUoZGEyMV9oYW0pICsNCiAgdG1fcG9seWdvbnMoIkRBX1NTT1N3XzEweV9jaGciLCANCiAgICAgICAgICAgICAgc3R5bGU9ImplbmtzIiwgDQogICAgICAgICAgICAgIHRpdGxlPSJUZW4tWWVhclxuQ2hhbmdlICgyMDMxKSIsDQogICAgICAgICAgICAgIGJvcmRlci5hbHBoYSA9IDApICsNCiAgdG1fc2hhcGUoY2QyMV9oYW0sIGNvbG9yID0gJ2JsYWNrJywgZmlsbCA9IE5BKSArDQogIHRtX2JvcmRlcnMoY29sID0gImJsYWNrIiwgbHdkID0gMC4xKSArDQogIHRtX2xheW91dChmcmFtZSA9IEZBTFNFKSArDQogIHRtX2xlZ2VuZChwb3NpdGlvbiA9IGMoInJpZ2h0IiwgInRvcCIpKSArDQogIHRtX2NvbXBhc3MocG9zaXRpb24gPSBjKCdyaWdodCcsJ2JvdHRvbScpKSArIA0KICB0bV9zY2FsZV9iYXIocG9zaXRpb24gPSBjKCJsZWZ0IiwgImJvdHRvbSIpLCB3aWR0aCA9IDAuMTUpDQpEQV9TU09Td18xMHlfY2hnX3Bsb3QNCmBgYA0KDQojIyMgQ29uY2x1c2lvbg0KDQpDb25ncmF0dWxhdGlvbnMgb24gbWFraW5nIGl0IHRvIHRoZSBlbmQuIE5vdyBydW4gdGhlIHNhbWUgYW5hbHlzaXMsIGJ1dA0KdGhpcyB0aW1lLCB1c2UgYSBkaWZmZXJlbnQgZmFjaWxpdHkgZGF0YSBzZXQgYW5kIHNlbGVjdCBhIGRpZmZlcmVudA0KcG9wdWxhdGlvbiB0byBzdHVkeSENCg0KVGhlIGZpZ3VyZSBiZWxvdyBwcm92aWRlcyBhIG5pY2UgdmlzdWFsIHN1bW1hcnkgb2YgdGhlIGFuYWx5c2lzIHdlIGp1c3QNCmNvbXBsZXRlZC4NCg0KYGBge3IgZmlnLmFsaWduID0gImxlZnQiLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gN30NCiMgMSAtIERBIFpvbmVzDQpEQXBsb3QgPC0gZ2dwbG90KCkgKyANCiAgZ2d0aXRsZSgiMSkgTG9hZCBEaXNzZW1pbmF0aW9uIEFyZWFzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgaGp1c3QgPSAwLCB2anVzdCA9IC0xKSkgKw0KICBnZW9tX3NmKGRhdGEgPSBkYTIxX2hhbSwgY29sb3IgPSAnZ3JleScsIGZpbGwgPSBOQSwgbHdkID0gMC4zKSArDQogIGdlb21fc2YoZGF0YSA9IGNkMjFfaGFtLCBjb2xvciA9ICdibGFjaycsIGZpbGwgPSBOQSwgbHdkID0gMC4yKSArDQogIGJsYW5rKCkNCg0KIyAyIC0gREEgQ2VudHJvaWRzDQpEQUdDcGxvdCA8LSBnZ3Bsb3QoKSArIA0KICBnZ3RpdGxlKCIyKSBDYWxjdWxhdGUgQ2VudHJvaWRzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgaGp1c3QgPSAwLCB2anVzdCA9IC0xKSkgKw0KICBnZW9tX3NmKGRhdGEgPSBkYTIxX2hhbV9nYywgY29sb3IgPSAnI0Y4QzQ3MScpICsNCiAgZ2VvbV9zZihkYXRhID0gZGEyMV9oYW0sIGNvbG9yID0gJ2dyZXknLCBmaWxsID0gTkEsIGx3ZCA9IDAuMykgKw0KICBnZW9tX3NmKGRhdGEgPSBjZDIxX2hhbSwgY29sb3IgPSAnYmxhY2snLCBmaWxsID0gTkEsIGx3ZCA9IDAuMikgKw0KICBibGFuaygpDQoNCiMgMyAtIDE1bWluIENlbnRyb2lkIElzb2Nocm9uZXMNCkRBR0NJU09wbG90IDwtIGdncGxvdCgpICsgDQogIGdndGl0bGUoIjMpIFF1ZXJ5IE9UUCBmb3IgMTVtaW4gSXNvY2hyb25lcyIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGhqdXN0ID0gMCwgdmp1c3QgPSAtMSkpICsNCiAgZ2VvbV9zZihkYXRhID0gZGEyMV9oYW1fZ2NfaXNvLCBmaWxsID0gJyNGOEM0NzEnLCBjb2xvciA9ICcjRjhDNDcxJykgKw0KICAjZ2VvbV9zZihkYXRhID0gZGEyMV9oYW0sIGNvbG9yID0gJ2dyZXknLCBmaWxsID0gTkEsIGx3ZCA9IDAuMykgKw0KICBnZW9tX3NmKGRhdGEgPSBjZDIxX2hhbSwgY29sb3IgPSAnYmxhY2snLCBmaWxsID0gTkEsIGx3ZCA9IDAuMikgKw0KICBibGFuaygpKw0KICBhbm5vdGF0aW9uX3NjYWxlKGxvY2F0aW9uID0gImJsIixoZWlnaHQgPSB1bml0KDAuMSwgImNtIikpIA0KDQojIDQgLSBJbnRlcnNlY3Rpb24gSXNvY2hyb25lcyBieSBDb21tdW5pdHkgU3BhY2VzDQpDU1BBQ0VTcGxvdCA8LSBnZ3Bsb3QoKSArIA0KICBnZ3RpdGxlKCI0KSBJbnRlcnNlY3QgQ29tbXVuaXR5IFNwYWNlcyIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGhqdXN0ID0gMCwgdmp1c3QgPSAtMSkpICsNCiAgZ2VvbV9zZihkYXRhID0gZGEyMV9oYW1fZ2NfaXNvLCBmaWxsID0gJyNGOEM0NzEnLCBjb2xvciA9ICcjRjhDNDcxJykgKw0KICAjZ2VvbV9zZihkYXRhID0gZGEyMV9oYW0sIGNvbG9yID0gJ2dyZXknLCBmaWxsID0gTkEsIGx3ZCA9IDAuMykgKw0KICBnZW9tX3NmKGRhdGEgPSBjZDIxX2hhbSwgY29sb3IgPSAnYmxhY2snLCBmaWxsID0gTkEsIGx3ZCA9IDAuMikgKw0KICBnZW9tX3NmKGRhdGEgPSBjc3BhY2VzLCBjb2xvciA9ICcjQTA0MDAwJywgc2hhcGU9MTgsIHNpemU9MS4yKSArDQogIGJsYW5rKCkrDQogIGFubm90YXRpb25fbm9ydGhfYXJyb3cobG9jYXRpb24gPSAiYnIiLCB3aGljaF9ub3J0aCA9ICJ0cnVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSB1bml0KDAuOCwgImNtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSB1bml0KDAuOCwgImNtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgcGFkX3kgPSB1bml0KDAuMSwgImluIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGUgPSBub3J0aF9hcnJvd19mYW5jeV9vcmllbnRlZXJpbmcpDQoNCiMgcGxvdCB0b2dldGhlcg0Kc3R1ZHlhcmVhX21ldGhvZCA8LSBwbG90X2dyaWQobmNvbCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbiA9ICJodiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEQXBsb3QsIERBR0NwbG90LCBEQUdDSVNPcGxvdCwgQ1NQQUNFU3Bsb3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9zaXplID0gOSkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCB2anVzdCA9IDEpKQ0Kc3R1ZHlhcmVhX21ldGhvZA0KYGBgDQo=