1. Setup and background
Conservation planning problems have mostly been dividied into two categories. The minimum-set approach aims to minimize the amount of resources needed to achieve all targets, regardless of how the targets are defined (Underhill 1994). The maximum-coverage approach on the other hand aims at maximizing protection for a given budget (Camm et al. 1996). The quantity maximized can be anything from species richness, to productivity, to accessibility by the public, or any combination of factors i.e., whatever is deemed relevant by the analyst (Arponen et al. 2009). Both approaches are related and are useful for different planning circumstances. Furthermore, there are other types of related approaches, such as maximum utility approches (e.g. Moilanen and Arponen 2011 and Laitila and Moilanen 2012).
Many different heuristic and optimization have been developed over the year to solve minimum-set, maximum-coverage, and maximum utility type of problems. While comparing different methods is usually like comparing apples to oranges, especially from practical conservation planning point of view such comparisons can have use. Here, I compare three methods that fall into maximum-coverage category.
1.1 Zonation tutorial data
For testing purposes, we’re going to use the Zonation tutorial data. The dataset is fairly small and useful for the purpose of testing.
Let’s start by reading in the data and inspecting the individual species features (N=7). The values in each raster are in range [0, 1] and indicate the propabilty of occurrence.
# Set up directory path for the tutorial data
z_tutorial_dir <- "~/dev/git-data/zonation-tutorial"
z_tutorial_data_dir <- file.path(z_tutorial_dir, "data")
tutorial_files <- list.files(path = z_tutorial_data_dir, pattern = "species.+\\.tif",
full.names = TRUE)
sp_rasters <- raster::stack(tutorial_files)
# For some mysterious reason, minmax isn't set automatically for sp_rasters[[1]]
# (species1)
sp_rasters[[1]] <- setMinMax(sp_rasters[[1]])
# Generate template for other spatial data later on
nrows <- nrow(sp_rasters)
ncols <- ncol(sp_rasters)
template <- extent(sp_rasters) %>%
raster(nrows = nrows, ncols = ncols, vals = 1)
template[is.na(sp_rasters[[1]])] <- NA
levelplot(sp_rasters, margin = FALSE, scales = list(draw = FALSE),
col.regions = viridis, layout = c(3, 3))

1.2 Generate cost data
While we will not be using cost data as in actually considering cost data, but we need for as a constraint data for the Gurobi ILP optimization. The cost is 1.0 in each cell, basically meaning that it translates into number of cells. This way it is possible to link an aerial target (such as the top 10% of the landscape) into a budgetary constraint.
cost <- extent(sp_rasters) %>%
raster(nrows = nrows, ncols = ncols, vals = 1)
# Alternatively, cost data can be simulated
# cost <- gaussian_field(r, 20, mean = 1000, variance = 500) #%>%
# setNames("cost")
# Fill areas normalle NA (NoData) with cost of 0
cost[is.na(sp_rasters[[1]])] <- 0
levelplot(cost, main = "Cost", margin = FALSE, scales = list(draw = FALSE),
col.regions = viridis)

2. Prioritization methods
Here, we’re testing three different prioritization methods against each other at increasing level of complexity:
- Rarity-wighted richness
- Zonation (two methods: core-area Zonation and additive benefit function)
- Integer programming (ILP) using Gurobi solver
2.1 Rarity-Weighted Richness (RWR)
From Albuquerque and Beier (2015):
“Williams et al. (1996) proposed that the rarity value of a species can be characterized by the inverse of the number of sites or planning units in which it occurs. Thus if a species is found in only 1 site, the species would have the maximum rarity score of 1/1 = 1, and a species that occurs in 20 sites would have a rarity score of 1/20 = 0.05. Williams et al. also proposed that the rarity scores of all species in the site can be summed to yield a single RWR value for the site:
\[ \sum_{n}^{1} (1 / c) \]
where \(c_{i}\) is the number of sites occupied by species \(i\), and the values are summed for the \(n\) species that occur in that site."
While the formulation by Albuquerque and Beier (2015) applies to binary presence/absence data, it can be extended to data where the values in each cell is a quantitative measure, e.g. the probability of occurrence. Hence, the formula becomes:
\[ \sum_{n=1}^{n} \frac{o_{ij}}{\sum_{i} o_{i}} \]
where \(o_{ij}\) is the occurence level of feature \(i\) in cell \(j\) and \(\sum_{i} o_{i}\) is the sum of the occurrence levels of all cells of feature \(i\). Below, after ranking the values are rescaled to range [0, 1]. Values close to 0 are the lowest priority locations, values close to 1 the highest.
# Define a function for occurrence level normalization
ol_normalize <- function(x) {
min <- raster::minValue(x)
return((x - min) / raster::cellStats(x - min, "sum"))
}
# Occurrence level normalize all species rasters
sp_rasters_normalized <- ol_normalize(sp_rasters)
# Sum up all normalized occurence levels -> rarity-weighted richness
rwr <- sum(sp_rasters_normalized, na.rm = TRUE)
# Replace 0 with NA
rwr[rwr == 0] <- NA
# Rank all cell according the RWR
rwr_rank <- rank(raster::getValues(rwr), ties.method = "random", na.last = "keep")
# Scale ranks to range [0, 1]
rwr_rank <- rwr_rank / max(rwr_rank, na.rm = TRUE)
e <- raster::extent(sp_rasters)
# Transform ranks back to a raster
rwr_rank <- raster(e, nrows = nrow(rwr), ncols = ncol(rwr), vals = rwr_rank)
levelplot(rwr_rank, margin = FALSE, main = "RWR", scales = list(draw = FALSE),
par.settings = rasterTheme(region = z_colors_RdYlBu$colors),
at = z_colors_RdYlBu$values)

2.2 Zonation (CAZ + ABF)
The RWR priority rank from the prevoius section is compared against two cell-removal rules found in Zonation software: additive benefit function (ABF) and core-area Zonation (CAZ). Whereas ABF somewhat emphasizes the richness of features in any given cell, CAZ somewhat emphasizes rarity. Previously e.g. Albuquerque and Beier (2009) have compared RWR against CAZ variant of Zonation. If step-functions are used as the benefit function in ABF Zonation (which is typically not the case), it equals maximum coverage (Moilanen and Arponen 2011).
We’re not going to run the actual Zonation analysis, but assume that basic runs 01_core_area_zonation and 02_additive_benefit_function have been executed locally. Then, we just do load the results.
For additive benefit function, the \(z\) parameter has been set to 0.25 for all species.
caz_rank <- raster(file.path(z_tutorial_dir, "basic", "basic_output",
"01_core_area_zonation", "01_core_area_zonation.rank.asc"))
levelplot(caz_rank, margin = FALSE,
par.settings = rasterTheme(region = z_colors_RdYlBu$colors),
scales = list(draw = FALSE), at = z_colors_RdYlBu$values,
main = "Core-area Zonation")

result_dir <- "/home/jlehtoma/VirtualBox VMs/trusty64/zonation/zonation-tutorial"
abf_rank <- raster(file.path(result_dir, "basic", "basic_output",
"02_additive_benefit_function",
"output_02_additive_benefit_function.rank.compressed.tif"))
levelplot(abf_rank, margin = FALSE,
par.settings = rasterTheme(region = z_colors_RdYlBu$colors),
scales = list(draw = FALSE),
at = z_colors_RdYlBu$values, main = "Additive benefit function")

2.3 Prioritization based on integer linear programming (ILP) and Gurobi
For a thorough description of using the Gurobi solver for solving conservation planning problem, see Beyer et al. (2016). Most of the approach and code below are based on the excellent tutorials Integer Programming with Gurobi for Reserve Design and Field Guide to ILP Solvers in R for Conservation Prioritization by Matt Strimas-Mackey, and on the prioritizr package by the same person.
Below, the prioritizaton is done by solving the conservation planning problem in a form a maximum coverage problem (MCP), or in other words, find the set of planning units that maximizes the overall level of representation (as measured by the normalized occurrence levels) across a suite of conservation features, while keeping cost within a fixed budget. It is worth mentioning that the Zonation algorithm is implicitly aiming at a maximum coverage type solution; by minimizing marginal loss one maximizes conservation value remaining at any specific level of cell removal (Moilanen 2007).
Here, the cost - and hence the budget - is defined to to be constant (1.0) in all cells. Thus, the budget corresponds to a particular number of planning units (cells) and we can maximize, for example, feature representation for the top 10% of the landscape. By repeating the maximum coverage optimization for a range of budgets (5%, 10%, 15%, …, 100% of the landscape), it is possible to create a nested hierarchy of solutions. This hierarchical solutions resembles the rankings resulting from the RWR and Zonation above.
DISCLAIMER: at this point, it is unclear to me whether the approach described above and implemented below (“hierarchical optimization”) is legit. For one thing, strictly speaking the solutions probably are not guaranteed to be nested, especially for more complex problems.
# NAs (NoData) must be raplaced wiht 0s for GUROBI
sp_rasters_filled <- sp_rasters
sp_rasters_filled[is.na(sp_rasters_filled)] <- 0
sp_rasters_filled <- raster::stack(sp_rasters_filled)
# Solve the maximum coverage problem for a range of target budgets
budgets <- seq(0.05, 1.00, 0.05)
results_mc <- list()
for (b in budgets) {
b_cells <- b * raster::cellStats(cost, "sum")
mc_model <- prioritizr::maxcover_model(x = cost, features = sp_rasters_filled, budget = b_cells)
mc_results <- prioritize(mc_model, gap = 0.001)
##results <- protectr::gurobi_maxcoverage(cost, sp_rasters_filled, budget = b_cells)
results_mc[[as.character(b)]] <- mc_results
}
# Function to translate Gurobi solutions to raster objects based on a template
# raster
process_gurobi_results <- function(results, template_raster) {
result_raster <- template_raster
result_raster[] <- results$x
# Fill in the nodata
result_raster[is.na(template_raster)] <- NA
# Make this a categorical raster
result_raster <- ratify(result_raster)
rat <- levels(result_raster)[[1]]
# In some cases (most notably when budget is 100%) all cells might be
# selected. Check for this.
if (nrow(rat) == 2) {
rat$status <- c("Not Selected", "Selected")
} else {
rat$status <- c("Selected")
}
levels(result_raster) <- rat
return(result_raster)
}
# Process the whole list of maximum coverage solutions
mcp_ilp <- raster::stack(lapply(results_mc, process_gurobi_results, template))
names(mcp_ilp) <- paste0("percent", budgets*100)
# Plot a selection of solutions (10%, 20%, 50%)
levelplot(subset(mcp_ilp, c(2, 4, 10)), main = "Maximum coverage solution\n(gap = 0.1%)",
scales = list(draw = FALSE),
col.regions = c("grey70", "#d7191c"),
colorkey = list(space = "bottom", height = 1))

# Define a function for minmax-normalization
normalize <- function(x) {
min <- raster::minValue(x)
max <- raster::maxValue(x)
return((x - min) / (max - min))
}
# Sum up all the layers in the stack -> result is a selection frequency
mcp_ilp_hier <- sum(mcp_ilp, na.rm = TRUE)
# Replace 0s with NAs
mcp_ilp_hier[mcp_ilp_hier == 0] <- NA
# Normalize value into scale [0, 1]
mcp_ilp_hier <- normalize(mcp_ilp_hier)
levelplot(mcp_ilp_hier, margin = FALSE, scales = list(draw = FALSE),
par.settings = rasterTheme(region = z_colors_RdYlBu$colors),
at = z_colors_RdYlBu$values, main = "Gurobi MCP")

NOTE: the color scale above is not completely accurate, this needs to be fixed.
3. Comparison of the prioritizations
3.1 Spatial patterns
Let’s visually compare the top 10% solution patterns in RWR, Zonation and Gurobi solutions.
# Get the top 10% solutions for RWR and Zonation
rwr_top10 <- rwr_rank >= 0.9
# make this a categorical raster
rwr_top10 <- ratify(rwr_top10)
rat <- levels(rwr_top10)[[1]]
rat$status <- c("Not Selected", "Selected")
levels(rwr_top10) <- rat
abf_top10 <- abf_rank >= 0.9
# make this a categorical raster
abf_top10 <- ratify(abf_top10)
rat <- levels(abf_top10)[[1]]
rat$status <- c("Not Selected", "Selected")
levels(abf_top10) <- rat
p4 <- levelplot(rwr_top10, main = "RWR\ntop 10%",
scales = list(draw = FALSE),
col.regions = c("grey70", "#d7191c"),
colorkey = list(space = "bottom", height = 1))
p5 <- levelplot(abf_top10, main = "Zonation ABF\ntop 10%",
scales = list(draw = FALSE),
col.regions = c("grey70", "#d7191c"),
colorkey = list(space = "bottom", height = 1))
p6 <- levelplot(mcp_ilp[[1]], main = "Gurobi MCP\n10% budget",
scales = list(draw = FALSE),
col.regions = c("grey70", "#d7191c"),
colorkey = list(space = "bottom", height = 1))
grid.arrange(p4, p5, p6, nrow = 1, ncol = 3)

Let’s also check that the solutions above include the same amount of cells included in the top 10%.
cell_count <- lapply(c(rwr_top10, abf_top10, mcp_ilp[[2]]), function(x) table(raster::getValues(x)))
cell_count <- as.data.frame(do.call("rbind", cell_count))
names(cell_count) <- c("Low", "Top")
cell_count$Method <- c("RWR", "ABF", "MILP MCP")
cell_count <- dplyr::select(cell_count, Method, Low, Top)
knitr::kable(cell_count, align = c('r', 'c', 'c'), row.names = FALSE)
| RWR |
99253 |
11029 |
| ABF |
99253 |
11029 |
| MILP MCP |
99254 |
11028 |
3.2 Quantitative performance
# Reclassify rasters
reclass_raster <- function(x, rcl) {
assert_that(inherits(x, "RasterLayer"),
inherits(rcl, "data.frame"),
all(names(rcl) == c("from", "to", "value")))
raster_rcl <- raster::reclassify(x, rcl, include.lowest = TRUE)
raster_rcl <- ratify(raster_rcl)
rat_rcl <- levels(raster_rcl)[[1]]
rat_rcl$status <- rcl$value
levels(raster_rcl) <- rat_rcl
return(raster_rcl)
}
zonal_stats <- function(zonal_raster, values_stack, rcl, method) {
rcl_raster <- reclass_raster(zonal_raster, rcl)
rcl_zonal <- as.data.frame(raster::zonal(values_stack, rcl_raster, fun = 'sum'))
# Manually add zone 0, in which nothing has been gained yet
data_row <- rep(0, raster::nlayers(values_stack) + 1)
names(data_row) <- c("zone", names(values_stack))
rcl_zonal <- rbind(rcl_zonal, as.data.frame(t(data_row)))
rcl_zonal <- rcl_zonal %>%
# Sort by zone, Zone 1 = 100-90% ... Zone 10 = 10-0% of the landscape
arrange(zone) %>%
# Gather into long format keeping zone intact
gather(species, value, -zone) %>%
group_by(species) %>%
# Count cumulative value for distribution sum, that is zone1 + zone2 + ... + zone10
# Then take the inverse of this, which is 1 - dsum to show distribution remaining
mutate(dsum = cumsum(value),
drem = 1 - dsum) %>%
ungroup() %>%
# Finally, calculate mean distribution remaining per zone
group_by(zone) %>%
mutate(min_drem = min(drem),
mean_drem = mean(drem),
max_drem = max(drem),
method = method)
return(rcl_zonal)
}
# Reclassification table
rcl <- data.frame(from = seq(0, .9, 0.1),
to = seq(0.1, 1.0, 0.1),
value = 1:10)
rwr_zonal <- zonal_stats(rwr_rank, sp_rasters_normalized, rcl, "RWR")
abf_zonal <- zonal_stats(abf_rank, sp_rasters_normalized, rcl, "ABF")
caz_zonal <- zonal_stats(caz_rank, sp_rasters_normalized, rcl, "CAZ")
mcp_ilp_zonal <- zonal_stats(mcp_ilp_hier, sp_rasters_normalized, rcl, "ILP")
# Bind all zonal stats together
all_zonal <- dplyr::bind_rows(rwr_zonal, abf_zonal, caz_zonal, mcp_ilp_zonal) %>%
dplyr::select(zone, mean_drem, method)
ggplot(rwr_zonal, aes(x = zone, y = drem, color = species)) + geom_line() + ggtitle("RWR") +
ylab("Distribution remaining\n") + xlab("\n Landscape selected") +
scale_x_continuous(breaks = 0:10, labels = paste0(seq(100, 0, -10), "%"))
ggplot(abf_zonal, aes(x = zone, y = drem, color = species)) + geom_line() + ggtitle("ABF") +
ylab("Distribution remaining\n") + xlab("\n Landscape selected") +
scale_x_continuous(breaks = 0:10, labels = paste0(seq(100, 0, -10), "%"))
ggplot(caz_zonal, aes(x = zone, y = drem, color = species)) + geom_line() + ggtitle("CAZ") +
ylab("Distribution remaining\n") + xlab("\n Landscape selected") +
scale_x_continuous(breaks = 0:10, labels = paste0(seq(100, 0, -10), "%"))
ggplot(mcp_ilp_zonal, aes(x = zone, y = drem, color = species)) + geom_line() +
ggtitle("Gurobi MCP") + ylab("Distribution remaining\n") + xlab("\n Landscape selected") +
scale_x_continuous(breaks = 0:10, labels = paste0(seq(100, 0, -10), "%"))
ggplot(all_zonal, aes(x = zone, y = mean_drem, color = method)) + geom_line() +
ylab("Mean distribution remaining\n") + xlab("\n Landscape selected") +
ggtitle("Mean performance among the methods") +
scale_x_continuous(breaks = 0:10, labels = paste0(seq(100, 0, -10), "%"))





What is striking here, is that there is very little difference between the methods. CAZ - as expected - has on average a little lower performance, others are very similar.
LS0tCnRpdGxlOiBDb21wYXJpbmcgcHJpb3JpdGl6YXRpb24gbWV0aG9kcwpkYXRlOiAiSnVuZSAyMSwgMjAxNiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGFzc2VydHRoYXQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZ3Vyb2JpKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHByaW9yaXRpenIpCmxpYnJhcnkocmFzdGVyKQpsaWJyYXJ5KHJhc3RlclZpcykKbGlicmFyeSh0aWR5cikKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHpvbmF0b3IpCgojIERlZmluZSBjb2xvciBzY2hlbWVzIAp6X2NvbG9yc19zcGVjdHJhbCA8LSB6b25hdG9yOjp6bGVnZW5kKCdzcGVjdHJhbCcpCnpfY29sb3JzX1JkWWxCdSA8LSB6X2NvbG9yc19zcGVjdHJhbAp6X2NvbG9yc19SZFlsQnUkY29sb3JzIDwtIHJldihicmV3ZXIucGFsKDcsICJSZFlsQnUiKSkgIApgYGAKCiMjIDEuIFNldHVwIGFuZCBiYWNrZ3JvdW5kCgpDb25zZXJ2YXRpb24gcGxhbm5pbmcgcHJvYmxlbXMgaGF2ZSBtb3N0bHkgYmVlbiBkaXZpZGllZCBpbnRvIHR3byBjYXRlZ29yaWVzLiAqKlRoZSBtaW5pbXVtLXNldCoqIGFwcHJvYWNoICBhaW1zIHRvIG1pbmltaXplIHRoZSBhbW91bnQgb2YgcmVzb3VyY2VzIG5lZWRlZCB0byBhY2hpZXZlIGFsbCB0YXJnZXRzLCByZWdhcmRsZXNzIG9mIGhvdyB0aGUgdGFyZ2V0cyBhcmUgZGVmaW5lZCAoW1VuZGVyaGlsbCAxOTk0XShodHRwOi8vZHguZG9pLm9yZy8xMC4xMDE2LzAwMDYtMzIwNyg5NCk5MDMwMi02KSkuIFRoZSAqKm1heGltdW0tY292ZXJhZ2UqKiBhcHByb2FjaCBvbiB0aGUgb3RoZXIgaGFuZCBhaW1zIGF0IG1heGltaXppbmcgcHJvdGVjdGlvbiBmb3IgYSBnaXZlbiBidWRnZXQgKFtDYW1tIGV0IGFsLiAxOTk2XShodHRwOi8vZG9pLm9yZy8xMC4xMDE2LzAwMDYtMzIwNyg5NSkwMDEzMi04KSkuIFRoZSBxdWFudGl0eSBtYXhpbWl6ZWQgY2FuIGJlIGFueXRoaW5nIGZyb20gc3BlY2llcyByaWNobmVzcywgdG8gcHJvZHVjdGl2aXR5LCB0byBhY2Nlc3NpYmlsaXR5IGJ5IHRoZSBwdWJsaWMsIG9yIGFueSBjb21iaW5hdGlvbiBvZiBmYWN0b3JzIGkuZS4sIHdoYXRldmVyIGlzIGRlZW1lZCByZWxldmFudCBieSB0aGUgYW5hbHlzdCAoW0FycG9uZW4gZXQgYWwuIDIwMDldKGh0dHA6Ly9kb2kub3JnLzEwLjExMTEvai4xNTIzLTE3MzkuMjAxMC4wMTUzOS54KSkuIEJvdGggYXBwcm9hY2hlcyBhcmUgcmVsYXRlZCBhbmQgYXJlIHVzZWZ1bCBmb3IgZGlmZmVyZW50IHBsYW5uaW5nIGNpcmN1bXN0YW5jZXMuIEZ1cnRoZXJtb3JlLCB0aGVyZSBhcmUgb3RoZXIgdHlwZXMgb2YgcmVsYXRlZCBhcHByb2FjaGVzLCBzdWNoIGFzIG1heGltdW0gdXRpbGl0eSBhcHByb2NoZXMgKGUuZy4gW01vaWxhbmVuIGFuZCBBcnBvbmVuIDIwMTFdKGh0dHA6Ly9kb2kub3JnLzEwLjEwMTYvai5iaW9jb24uMjAxMC4wOS4wMDYpIGFuZCBbTGFpdGlsYSBhbmQgTW9pbGFuZW4gMjAxMl0oaHR0cDovL2RvaS5vcmcvMTAuMTAxNi9qLmVjb2xtb2RlbC4yMDEyLjA4LjAxMCkpLgoKTWFueSBkaWZmZXJlbnQgaGV1cmlzdGljIGFuZCBvcHRpbWl6YXRpb24gaGF2ZSBiZWVuIGRldmVsb3BlZCBvdmVyIHRoZSB5ZWFyIHRvIHNvbHZlIG1pbmltdW0tc2V0LCBtYXhpbXVtLWNvdmVyYWdlLCBhbmQgbWF4aW11bSB1dGlsaXR5IHR5cGUgb2YgcHJvYmxlbXMuIFdoaWxlIGNvbXBhcmluZyBkaWZmZXJlbnQgbWV0aG9kcyBpcyB1c3VhbGx5IGxpa2UgY29tcGFyaW5nIGFwcGxlcyB0byBvcmFuZ2VzLCBlc3BlY2lhbGx5IGZyb20gcHJhY3RpY2FsIGNvbnNlcnZhdGlvbiBwbGFubmluZyBwb2ludCBvZiB2aWV3IHN1Y2ggY29tcGFyaXNvbnMgY2FuIGhhdmUgdXNlLiBIZXJlLCBJIGNvbXBhcmUgdGhyZWUgbWV0aG9kcyB0aGF0IGZhbGwgaW50byBtYXhpbXVtLWNvdmVyYWdlIGNhdGVnb3J5LgoKIyMjIDEuMSBab25hdGlvbiB0dXRvcmlhbCBkYXRhCgpGb3IgdGVzdGluZyBwdXJwb3Nlcywgd2UncmUgZ29pbmcgdG8gdXNlIHRoZSBbWm9uYXRpb24gdHV0b3JpYWwgZGF0YV0oaHR0cHM6Ly9naXRodWIuY29tL2NiaWcvem9uYXRpb24tdHV0b3JpYWwpLiBUaGUgZGF0YXNldCBpcyBmYWlybHkgc21hbGwgYW5kIHVzZWZ1bCBmb3IgdGhlIHB1cnBvc2Ugb2YgdGVzdGluZy4gCgpMZXQncyBzdGFydCBieSByZWFkaW5nIGluIHRoZSBkYXRhIGFuZCBpbnNwZWN0aW5nIHRoZSBpbmRpdmlkdWFsIHNwZWNpZXMgZmVhdHVyZXMgKE49NykuIFRoZSB2YWx1ZXMgaW4gZWFjaCByYXN0ZXIgYXJlIGluIHJhbmdlIFswLCAxXSBhbmQgaW5kaWNhdGUgdGhlIHByb3BhYmlsdHkgb2Ygb2NjdXJyZW5jZS4KCmBgYHtyIHJlYWQtZmVhdHVyZSxlY2hvPVRSVUV9CiMgU2V0IHVwIGRpcmVjdG9yeSBwYXRoIGZvciB0aGUgdHV0b3JpYWwgZGF0YQp6X3R1dG9yaWFsX2RpciA8LSAifi9kZXYvZ2l0LWRhdGEvem9uYXRpb24tdHV0b3JpYWwiCnpfdHV0b3JpYWxfZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHpfdHV0b3JpYWxfZGlyLCAiZGF0YSIpCnR1dG9yaWFsX2ZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9IHpfdHV0b3JpYWxfZGF0YV9kaXIsIHBhdHRlcm4gPSAic3BlY2llcy4rXFwudGlmIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFRSVUUpCnNwX3Jhc3RlcnMgPC0gcmFzdGVyOjpzdGFjayh0dXRvcmlhbF9maWxlcykKIyBGb3Igc29tZSBteXN0ZXJpb3VzIHJlYXNvbiwgbWlubWF4IGlzbid0IHNldCBhdXRvbWF0aWNhbGx5IGZvciBzcF9yYXN0ZXJzW1sxXV0KIyAoc3BlY2llczEpCnNwX3Jhc3RlcnNbWzFdXSA8LSBzZXRNaW5NYXgoc3BfcmFzdGVyc1tbMV1dKQoKIyBHZW5lcmF0ZSB0ZW1wbGF0ZSBmb3Igb3RoZXIgc3BhdGlhbCBkYXRhIGxhdGVyIG9uCm5yb3dzIDwtIG5yb3coc3BfcmFzdGVycykKbmNvbHMgPC0gbmNvbChzcF9yYXN0ZXJzKQoKdGVtcGxhdGUgPC0gZXh0ZW50KHNwX3Jhc3RlcnMpICU+JSAKICAgICAgICAgICAgICByYXN0ZXIobnJvd3MgPSBucm93cywgbmNvbHMgPSBuY29scywgdmFscyA9IDEpCnRlbXBsYXRlW2lzLm5hKHNwX3Jhc3RlcnNbWzFdXSldIDwtIE5BCgpsZXZlbHBsb3Qoc3BfcmFzdGVycywgbWFyZ2luID0gRkFMU0UsIHNjYWxlcyA9IGxpc3QoZHJhdyA9IEZBTFNFKSwKICAgICAgICAgIGNvbC5yZWdpb25zID0gdmlyaWRpcywgbGF5b3V0ID0gYygzLCAzKSkKYGBgCgojIyMgMS4yIEdlbmVyYXRlIGNvc3QgZGF0YQoKV2hpbGUgd2Ugd2lsbCBub3QgYmUgdXNpbmcgY29zdCBkYXRhIGFzIGluIGFjdHVhbGx5IGNvbnNpZGVyaW5nIGNvc3QgZGF0YSwgYnV0IHdlIG5lZWQgZm9yIGFzIGEgY29uc3RyYWludCBkYXRhIGZvciB0aGUgR3Vyb2JpIElMUCBvcHRpbWl6YXRpb24uIFRoZSBjb3N0IGlzIGAxLjBgIGluIGVhY2ggY2VsbCwgYmFzaWNhbGx5IG1lYW5pbmcgdGhhdCBpdCB0cmFuc2xhdGVzIGludG8gbnVtYmVyIG9mIGNlbGxzLiBUaGlzIHdheSBpdCBpcyBwb3NzaWJsZSB0byBsaW5rIGFuIGFlcmlhbCB0YXJnZXQgKHN1Y2ggYXMgdGhlIHRvcCAxMCUgb2YgdGhlIGxhbmRzY2FwZSkgaW50byBhIGJ1ZGdldGFyeSBjb25zdHJhaW50LiAKCmBgYHtyIGdlbmVyYXRlLWNvc3QtZGF0YX0KCmNvc3QgPC0gZXh0ZW50KHNwX3Jhc3RlcnMpICU+JSAKICByYXN0ZXIobnJvd3MgPSBucm93cywgbmNvbHMgPSBuY29scywgdmFscyA9IDEpCiMgQWx0ZXJuYXRpdmVseSwgY29zdCBkYXRhIGNhbiBiZSBzaW11bGF0ZWQKIyBjb3N0IDwtIGdhdXNzaWFuX2ZpZWxkKHIsIDIwLCBtZWFuID0gMTAwMCwgdmFyaWFuY2UgPSA1MDApICMlPiUgCiMgIHNldE5hbWVzKCJjb3N0IikKCiMgRmlsbCBhcmVhcyBub3JtYWxsZSBOQSAoTm9EYXRhKSB3aXRoIGNvc3Qgb2YgMApjb3N0W2lzLm5hKHNwX3Jhc3RlcnNbWzFdXSldIDwtIDAKCmxldmVscGxvdChjb3N0LCBtYWluID0gIkNvc3QiLCBtYXJnaW4gPSBGQUxTRSwgc2NhbGVzID0gbGlzdChkcmF3ID0gRkFMU0UpLAogICAgICAgICAgY29sLnJlZ2lvbnMgPSB2aXJpZGlzKQpgYGAKCiMjIDIuIFByaW9yaXRpemF0aW9uIG1ldGhvZHMKCkhlcmUsIHdlJ3JlIHRlc3RpbmcgdGhyZWUgZGlmZmVyZW50IHByaW9yaXRpemF0aW9uIG1ldGhvZHMgYWdhaW5zdCBlYWNoIG90aGVyIGF0IGluY3JlYXNpbmcgbGV2ZWwgb2YgY29tcGxleGl0eToKCjEuIFJhcml0eS13aWdodGVkIHJpY2huZXNzCjIuIFpvbmF0aW9uICh0d28gbWV0aG9kczogY29yZS1hcmVhIFpvbmF0aW9uIGFuZCBhZGRpdGl2ZSBiZW5lZml0IGZ1bmN0aW9uKQozLiBJbnRlZ2VyIHByb2dyYW1taW5nIChJTFApIHVzaW5nIEd1cm9iaSBzb2x2ZXIKCiMjIyAyLjEgUmFyaXR5LVdlaWdodGVkIFJpY2huZXNzIChSV1IpCgpGcm9tIFtBbGJ1cXVlcnF1ZSBhbmQgQmVpZXIgKDIwMTUpXShodHRwOi8vZHgucGxvcy5vcmcvMTAuMTM3MS9qb3VybmFsLnBvbmUuMDExOTkwNSk6CgoiV2lsbGlhbXMgZXQgYWwuICgxOTk2KSBwcm9wb3NlZCB0aGF0IHRoZSByYXJpdHkgdmFsdWUgb2YgYSBzcGVjaWVzIGNhbiBiZSBjaGFyYWN0ZXJpemVkIGJ5IHRoZSBpbnZlcnNlIG9mIHRoZSBudW1iZXIgb2Ygc2l0ZXMgb3IgcGxhbm5pbmcgdW5pdHMgaW4gd2hpY2ggaXQgb2NjdXJzLiBUaHVzIGlmIGEgc3BlY2llcyBpcyBmb3VuZCBpbiBvbmx5IDEgc2l0ZSwgdGhlIHNwZWNpZXMgd291bGQgaGF2ZSB0aGUgbWF4aW11bSByYXJpdHkgc2NvcmUgb2YgYDEvMSA9IDFgLCBhbmQgYSBzcGVjaWVzIHRoYXQgb2NjdXJzIGluIDIwIHNpdGVzIHdvdWxkIGhhdmUgYSByYXJpdHkgc2NvcmUgb2YgYDEvMjAgPSAwLjA1YC4gV2lsbGlhbXMgZXQgYWwuIGFsc28gcHJvcG9zZWQgdGhhdCB0aGUgcmFyaXR5IHNjb3JlcyBvZiBhbGwgc3BlY2llcyBpbiB0aGUgc2l0ZSBjYW4gYmUgc3VtbWVkIHRvIHlpZWxkIGEgc2luZ2xlIFJXUiB2YWx1ZSBmb3IgdGhlIHNpdGU6CgokJCBcc3VtX3tufV57MX0gKDEgLyBjKSAkJAoKd2hlcmUgJGNfe2l9JCBpcyB0aGUgbnVtYmVyIG9mIHNpdGVzIG9jY3VwaWVkIGJ5IHNwZWNpZXMgJGkkLCBhbmQgdGhlIHZhbHVlcyBhcmUgc3VtbWVkIGZvciB0aGUgJG4kIHNwZWNpZXMgdGhhdCBvY2N1ciBpbiB0aGF0IHNpdGUuIgoKV2hpbGUgdGhlIGZvcm11bGF0aW9uIGJ5IEFsYnVxdWVycXVlIGFuZCBCZWllciAoMjAxNSkgYXBwbGllcyB0byBiaW5hcnkgcHJlc2VuY2UvYWJzZW5jZSBkYXRhLCBpdCBjYW4gYmUgZXh0ZW5kZWQgdG8gZGF0YSB3aGVyZSB0aGUgdmFsdWVzIGluIGVhY2ggY2VsbCBpcyBhIHF1YW50aXRhdGl2ZSBtZWFzdXJlLCBlLmcuIHRoZSBwcm9iYWJpbGl0eSBvZiBvY2N1cnJlbmNlLiBIZW5jZSwgdGhlIGZvcm11bGEgYmVjb21lczoKCiQkIFxzdW1fe249MX1ee259IFxmcmFje29fe2lqfX17XHN1bV97aX0gb197aX19ICAkJAoKd2hlcmUgJG9fe2lqfSQgaXMgdGhlICpvY2N1cmVuY2UgbGV2ZWwqIG9mIGZlYXR1cmUgJGkkIGluIGNlbGwgJGokIGFuZCAgJFxzdW1fe2l9IG9fe2l9JCBpcyB0aGUgc3VtIG9mIHRoZSBvY2N1cnJlbmNlIGxldmVscyBvZiBhbGwgY2VsbHMgb2YgZmVhdHVyZSAkaSQuIEJlbG93LCBhZnRlciByYW5raW5nIHRoZSB2YWx1ZXMgYXJlIHJlc2NhbGVkIHRvIHJhbmdlIFswLCAxXS4gVmFsdWVzIGNsb3NlIHRvIDAgYXJlIHRoZSBsb3dlc3QgcHJpb3JpdHkgbG9jYXRpb25zLCB2YWx1ZXMgY2xvc2UgdG8gMSB0aGUgaGlnaGVzdC4KCmBgYHtyIG5vcm1hbGl6ZS1ldmVyeXRoaW5nLGVjaG89VFJVRX0KIyBEZWZpbmUgYSBmdW5jdGlvbiBmb3Igb2NjdXJyZW5jZSBsZXZlbCBub3JtYWxpemF0aW9uCm9sX25vcm1hbGl6ZSA8LSBmdW5jdGlvbih4KSB7CiAgbWluIDwtIHJhc3Rlcjo6bWluVmFsdWUoeCkKICByZXR1cm4oKHggLSBtaW4pIC8gcmFzdGVyOjpjZWxsU3RhdHMoeCAtIG1pbiwgInN1bSIpKQp9CgoKIyBPY2N1cnJlbmNlIGxldmVsIG5vcm1hbGl6ZSBhbGwgc3BlY2llcyByYXN0ZXJzCnNwX3Jhc3RlcnNfbm9ybWFsaXplZCA8LSBvbF9ub3JtYWxpemUoc3BfcmFzdGVycykKIyBTdW0gdXAgYWxsIG5vcm1hbGl6ZWQgb2NjdXJlbmNlIGxldmVscyAtPiByYXJpdHktd2VpZ2h0ZWQgcmljaG5lc3MKcndyIDwtIHN1bShzcF9yYXN0ZXJzX25vcm1hbGl6ZWQsIG5hLnJtID0gVFJVRSkKIyBSZXBsYWNlIDAgd2l0aCBOQQpyd3JbcndyID09ICAwXSA8LSBOQQojIFJhbmsgYWxsIGNlbGwgYWNjb3JkaW5nIHRoZSBSV1IKcndyX3JhbmsgPC0gcmFuayhyYXN0ZXI6OmdldFZhbHVlcyhyd3IpLCB0aWVzLm1ldGhvZCA9ICJyYW5kb20iLCBuYS5sYXN0ID0gImtlZXAiKQojIFNjYWxlIHJhbmtzIHRvIHJhbmdlIFswLCAxXQpyd3JfcmFuayA8LSByd3JfcmFuayAvIG1heChyd3JfcmFuaywgbmEucm0gPSBUUlVFKQplIDwtIHJhc3Rlcjo6ZXh0ZW50KHNwX3Jhc3RlcnMpCiMgVHJhbnNmb3JtIHJhbmtzIGJhY2sgdG8gYSByYXN0ZXIKcndyX3JhbmsgPC0gcmFzdGVyKGUsIG5yb3dzID0gbnJvdyhyd3IpLCBuY29scyA9IG5jb2wocndyKSwgdmFscyA9IHJ3cl9yYW5rKQpgYGAKYGBge3IgcGxvdC1yd3IsIGVjaG89VFJVRX0KbGV2ZWxwbG90KHJ3cl9yYW5rLCBtYXJnaW4gPSBGQUxTRSwgbWFpbiA9ICJSV1IiLCBzY2FsZXMgPSBsaXN0KGRyYXcgPSBGQUxTRSksCiAgICAgICAgICAgICAgICAgcGFyLnNldHRpbmdzID0gcmFzdGVyVGhlbWUocmVnaW9uID0gel9jb2xvcnNfUmRZbEJ1JGNvbG9ycyksIAogICAgICAgICAgICAgICAgIGF0ID0gel9jb2xvcnNfUmRZbEJ1JHZhbHVlcykKYGBgCgojIyMgMi4yIFpvbmF0aW9uIChDQVogKyBBQkYpCgpUaGUgUldSIHByaW9yaXR5IHJhbmsgZnJvbSB0aGUgcHJldm9pdXMgc2VjdGlvbiBpcyBjb21wYXJlZCBhZ2FpbnN0IHR3byBjZWxsLXJlbW92YWwgcnVsZXMgZm91bmQgaW4gW1pvbmF0aW9uXShodHRwOi8vY2JpZy5pdC5oZWxzaW5raS5maS9zb2Z0d2FyZS96b25hdGlvbi8pIHNvZnR3YXJlOiBhZGRpdGl2ZSBiZW5lZml0IGZ1bmN0aW9uIChBQkYpIGFuZCBjb3JlLWFyZWEgWm9uYXRpb24gKENBWikuIFdoZXJlYXMgQUJGIHNvbWV3aGF0IGVtcGhhc2l6ZXMgdGhlIHJpY2huZXNzIG9mIGZlYXR1cmVzIGluIGFueSBnaXZlbiBjZWxsLCBDQVogc29tZXdoYXQgZW1waGFzaXplcyByYXJpdHkuIFByZXZpb3VzbHkgZS5nLiBbQWxidXF1ZXJxdWUgYW5kIEJlaWVyICgyMDA5KV0oaHR0cDovL2R4LnBsb3Mub3JnLzEwLjEzNzEvam91cm5hbC5wb25lLjAxMTk5MDUpIGhhdmUgY29tcGFyZWQgUldSIGFnYWluc3QgQ0FaIHZhcmlhbnQgb2YgWm9uYXRpb24uIElmIHN0ZXAtZnVuY3Rpb25zIGFyZSB1c2VkIGFzIHRoZSBiZW5lZml0IGZ1bmN0aW9uIGluIEFCRiBab25hdGlvbiAod2hpY2ggaXMgdHlwaWNhbGx5IG5vdCB0aGUgY2FzZSksIGl0IGVxdWFscyBtYXhpbXVtIGNvdmVyYWdlIChbTW9pbGFuZW4gYW5kIEFycG9uZW4gMjAxMV0oaHR0cDovL2RvaS5vcmcvMTAuMTAxNi9qLmJpb2Nvbi4yMDEwLjA5LjAwNikpLgoKV2UncmUgbm90IGdvaW5nIHRvIHJ1biB0aGUgYWN0dWFsIFpvbmF0aW9uIGFuYWx5c2lzLCBidXQgYXNzdW1lIHRoYXQgYmFzaWMgcnVucyBbMDFfY29yZV9hcmVhX3pvbmF0aW9uXShodHRwczovL2dpdGh1Yi5jb20vY2JpZy96b25hdGlvbi10dXRvcmlhbC90cmVlL21hc3Rlci9iYXNpYy8wMV9jb3JlX2FyZWFfem9uYXRpb24pIGFuZCBbMDJfYWRkaXRpdmVfYmVuZWZpdF9mdW5jdGlvbl0oaHR0cHM6Ly9naXRodWIuY29tL2NiaWcvem9uYXRpb24tdHV0b3JpYWwvdHJlZS9tYXN0ZXIvYmFzaWMvMDJfYWRkaXRpdmVfYmVuZWZpdF9mdW5jdGlvbikgaGF2ZSBiZWVuIGV4ZWN1dGVkIGxvY2FsbHkuIFRoZW4sIHdlIGp1c3QgZG8gbG9hZCB0aGUgcmVzdWx0cy4KCkZvciBhZGRpdGl2ZSBiZW5lZml0IGZ1bmN0aW9uLCB0aGUgJHokIHBhcmFtZXRlciBoYXMgYmVlbiBzZXQgdG8gMC4yNSBmb3IgYWxsIHNwZWNpZXMuCgpgYGB7ciByZWFkLWluLWNhei1yZXN1bHRzLCBlY2hvPVRSVUV9CmNhel9yYW5rIDwtIHJhc3RlcihmaWxlLnBhdGgoel90dXRvcmlhbF9kaXIsICJiYXNpYyIsICJiYXNpY19vdXRwdXQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMDFfY29yZV9hcmVhX3pvbmF0aW9uIiwgIjAxX2NvcmVfYXJlYV96b25hdGlvbi5yYW5rLmFzYyIpKQpsZXZlbHBsb3QoY2F6X3JhbmssIG1hcmdpbiA9IEZBTFNFLCAKICAgICAgICAgIHBhci5zZXR0aW5ncyA9IHJhc3RlclRoZW1lKHJlZ2lvbiA9IHpfY29sb3JzX1JkWWxCdSRjb2xvcnMpLCAKICAgICAgICAgIHNjYWxlcyA9IGxpc3QoZHJhdyA9IEZBTFNFKSwgYXQgPSB6X2NvbG9yc19SZFlsQnUkdmFsdWVzLCAKICAgICAgICAgIG1haW4gPSAiQ29yZS1hcmVhIFpvbmF0aW9uIikKYGBgCgpgYGB7ciByZWFkLWluLWFiZi1yZXN1bHRzLCBlY2hvPVRSVUV9CnJlc3VsdF9kaXIgPC0gIi9ob21lL2psZWh0b21hL1ZpcnR1YWxCb3ggVk1zL3RydXN0eTY0L3pvbmF0aW9uL3pvbmF0aW9uLXR1dG9yaWFsIgphYmZfcmFuayA8LSByYXN0ZXIoZmlsZS5wYXRoKHJlc3VsdF9kaXIsICJiYXNpYyIsICJiYXNpY19vdXRwdXQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMDJfYWRkaXRpdmVfYmVuZWZpdF9mdW5jdGlvbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJvdXRwdXRfMDJfYWRkaXRpdmVfYmVuZWZpdF9mdW5jdGlvbi5yYW5rLmNvbXByZXNzZWQudGlmIikpCmxldmVscGxvdChhYmZfcmFuaywgbWFyZ2luID0gRkFMU0UsCiAgICAgICAgICBwYXIuc2V0dGluZ3MgPSByYXN0ZXJUaGVtZShyZWdpb24gPSB6X2NvbG9yc19SZFlsQnUkY29sb3JzKSwgCiAgICAgICAgICBzY2FsZXMgPSBsaXN0KGRyYXcgPSBGQUxTRSksCiAgICAgICAgICBhdCA9IHpfY29sb3JzX1JkWWxCdSR2YWx1ZXMsIG1haW4gPSAiQWRkaXRpdmUgYmVuZWZpdCBmdW5jdGlvbiIpCmBgYAoKIyMjIDIuMyBQcmlvcml0aXphdGlvbiBiYXNlZCBvbiBpbnRlZ2VyIGxpbmVhciBwcm9ncmFtbWluZyAoSUxQKSBhbmQgR3Vyb2JpCgoKRm9yIGEgdGhvcm91Z2ggZGVzY3JpcHRpb24gb2YgdXNpbmcgW3RoZSBHdXJvYmkgc29sdmVyXShodHRwOi8vd3d3Lmd1cm9iaS5jb20vKSBmb3Igc29sdmluZyBjb25zZXJ2YXRpb24gcGxhbm5pbmcgcHJvYmxlbSwgc2VlIFtCZXllciBldCBhbC4gKDIwMTYpXShodHRwOi8vZHguZG9pLm9yZy8xMC4xMDE2L2ouZWNvbG1vZGVsLjIwMTYuMDIuMDA1KS4gTW9zdCBvZiB0aGUgYXBwcm9hY2ggYW5kIGNvZGUgYmVsb3cgYXJlIGJhc2VkIG9uIHRoZSBleGNlbGxlbnQgdHV0b3JpYWxzIFtJbnRlZ2VyIFByb2dyYW1taW5nIHdpdGggR3Vyb2JpIGZvciBSZXNlcnZlIERlc2lnbl0oaHR0cDovL3N0cmltYXMuY29tL3IvZ3Vyb2JpLykgYW5kIFtGaWVsZCBHdWlkZSB0byBJTFAgU29sdmVycyBpbiBSIGZvciBDb25zZXJ2YXRpb24gUHJpb3JpdGl6YXRpb25dKGh0dHA6Ly9zdHJpbWFzLmNvbS9wcmlvcml0aXphdGlvbi9pbHAtZmllbGQtZ3VpZGUvKSBieSBbTWF0dCBTdHJpbWFzLU1hY2tleV0oaHR0cDovL3N0cmltYXMuY29tL2Fib3V0LyksIGFuZCBvbiB0aGUgW3ByaW9yaXRpenIgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL21zdHJpbWFzL3ByaW9yaXRpenIpIGJ5IHRoZSBzYW1lIHBlcnNvbi4KCkJlbG93LCB0aGUgcHJpb3JpdGl6YXRvbiBpcyBkb25lIGJ5IHNvbHZpbmcgdGhlIGNvbnNlcnZhdGlvbiBwbGFubmluZyBwcm9ibGVtIGluIGEgZm9ybSBhICptYXhpbXVtIGNvdmVyYWdlIHByb2JsZW0qIF9fKE1DUClfXywgb3IgaW4gb3RoZXIgd29yZHMsIGZpbmQgdGhlIHNldCBvZiBwbGFubmluZyB1bml0cyB0aGF0IG1heGltaXplcyB0aGUgb3ZlcmFsbCBsZXZlbCBvZiByZXByZXNlbnRhdGlvbiAoYXMgbWVhc3VyZWQgYnkgdGhlIG5vcm1hbGl6ZWQgb2NjdXJyZW5jZSBsZXZlbHMpIGFjcm9zcyBhIHN1aXRlIG9mIGNvbnNlcnZhdGlvbiBmZWF0dXJlcywgd2hpbGUga2VlcGluZyBjb3N0IHdpdGhpbiBhIGZpeGVkIGJ1ZGdldC4gSXQgaXMgd29ydGggbWVudGlvbmluZyB0aGF0IHRoZSBab25hdGlvbiBhbGdvcml0aG0gaXMgaW1wbGljaXRseSBhaW1pbmcgYXQgYSBtYXhpbXVtIGNvdmVyYWdlIHR5cGUgc29sdXRpb247IGJ5IG1pbmltaXppbmcgbWFyZ2luYWwgbG9zcyBvbmUgbWF4aW1pemVzIGNvbnNlcnZhdGlvbiB2YWx1ZSByZW1haW5pbmcgYXQgYW55IHNwZWNpZmljIGxldmVsIG9mIGNlbGwgcmVtb3ZhbCAoW01vaWxhbmVuIDIwMDddKGh0dHA6Ly9keC5kb2kub3JnLzEwLjEwMTYvai5iaW9jb24uMjAwNi4wOS4wMDgpKS4KCkhlcmUsIHRoZSBjb3N0IC0gYW5kIGhlbmNlIHRoZSBidWRnZXQgLSBpcyBkZWZpbmVkIHRvIHRvIGJlIGNvbnN0YW50ICgxLjApIGluIGFsbCBjZWxscy4gVGh1cywgdGhlIGJ1ZGdldCBjb3JyZXNwb25kcyB0byBhIHBhcnRpY3VsYXIgbnVtYmVyIG9mIHBsYW5uaW5nIHVuaXRzIChjZWxscykgYW5kIHdlIGNhbiBtYXhpbWl6ZSwgZm9yIGV4YW1wbGUsIGZlYXR1cmUgcmVwcmVzZW50YXRpb24gZm9yIHRoZSB0b3AgMTAlIG9mIHRoZSBsYW5kc2NhcGUuIEJ5IHJlcGVhdGluZyB0aGUgbWF4aW11bSBjb3ZlcmFnZSBvcHRpbWl6YXRpb24gZm9yIGEgcmFuZ2Ugb2YgYnVkZ2V0cyAoNSUsIDEwJSwgMTUlLCAuLi4sIDEwMCUgb2YgdGhlIGxhbmRzY2FwZSksIGl0IGlzIHBvc3NpYmxlIHRvIGNyZWF0ZSBhIG5lc3RlZCBoaWVyYXJjaHkgb2Ygc29sdXRpb25zLiBUaGlzIGhpZXJhcmNoaWNhbCBzb2x1dGlvbnMgcmVzZW1ibGVzIHRoZSByYW5raW5ncyByZXN1bHRpbmcgZnJvbSB0aGUgUldSIGFuZCBab25hdGlvbiBhYm92ZS4KCl9fRElTQ0xBSU1FUjpfXyBhdCB0aGlzIHBvaW50LCBpdCBpcyB1bmNsZWFyIHRvIG1lIHdoZXRoZXIgdGhlIGFwcHJvYWNoIGRlc2NyaWJlZCBhYm92ZSBhbmQgaW1wbGVtZW50ZWQgYmVsb3cgKCJoaWVyYXJjaGljYWwgb3B0aW1pemF0aW9uIikgaXMgbGVnaXQuIEZvciBvbmUgdGhpbmcsIHN0cmljdGx5IHNwZWFraW5nIHRoZSBzb2x1dGlvbnMgcHJvYmFibHkgYXJlIG5vdCBndWFyYW50ZWVkIHRvIGJlIG5lc3RlZCwgZXNwZWNpYWxseSBmb3IgbW9yZSBjb21wbGV4IHByb2JsZW1zLiAKCmBgYHtyIG9wdGltaXplLGVjaG89VFJVRSxtZXNzYWdlPUZBTFNFfQojIE5BcyAoTm9EYXRhKSBtdXN0IGJlIHJhcGxhY2VkIHdpaHQgMHMgZm9yIEdVUk9CSQpzcF9yYXN0ZXJzX2ZpbGxlZCA8LSBzcF9yYXN0ZXJzCnNwX3Jhc3RlcnNfZmlsbGVkW2lzLm5hKHNwX3Jhc3RlcnNfZmlsbGVkKV0gPC0gMApzcF9yYXN0ZXJzX2ZpbGxlZCA8LSByYXN0ZXI6OnN0YWNrKHNwX3Jhc3RlcnNfZmlsbGVkKQoKIyBTb2x2ZSB0aGUgbWF4aW11bSBjb3ZlcmFnZSBwcm9ibGVtIGZvciBhIHJhbmdlIG9mIHRhcmdldCBidWRnZXRzCmJ1ZGdldHMgPC0gc2VxKDAuMDUsIDEuMDAsIDAuMDUpCnJlc3VsdHNfbWMgPC0gbGlzdCgpCmZvciAoYiBpbiBidWRnZXRzKSB7CiAgYl9jZWxscyA8LSBiICogcmFzdGVyOjpjZWxsU3RhdHMoY29zdCwgInN1bSIpCiAgbWNfbW9kZWwgPC0gcHJpb3JpdGl6cjo6bWF4Y292ZXJfbW9kZWwoeCA9IGNvc3QsIGZlYXR1cmVzID0gc3BfcmFzdGVyc19maWxsZWQsIGJ1ZGdldCA9IGJfY2VsbHMpCiAgbWNfcmVzdWx0cyA8LSBwcmlvcml0aXplKG1jX21vZGVsLCBnYXAgPSAwLjAwMSkKICAjI3Jlc3VsdHMgPC0gcHJvdGVjdHI6Omd1cm9iaV9tYXhjb3ZlcmFnZShjb3N0LCBzcF9yYXN0ZXJzX2ZpbGxlZCwgYnVkZ2V0ID0gYl9jZWxscykKICByZXN1bHRzX21jW1thcy5jaGFyYWN0ZXIoYildXSA8LSBtY19yZXN1bHRzCn0KYGBgCgpgYGB7ciBwcm9jZXNzLW1jcC1yZXN1bHRzfQojIEZ1bmN0aW9uIHRvIHRyYW5zbGF0ZSBHdXJvYmkgc29sdXRpb25zIHRvIHJhc3RlciBvYmplY3RzIGJhc2VkIG9uIGEgdGVtcGxhdGUgCiMgcmFzdGVyCnByb2Nlc3NfZ3Vyb2JpX3Jlc3VsdHMgPC0gZnVuY3Rpb24ocmVzdWx0cywgdGVtcGxhdGVfcmFzdGVyKSB7CiAgcmVzdWx0X3Jhc3RlciA8LSB0ZW1wbGF0ZV9yYXN0ZXIKICByZXN1bHRfcmFzdGVyW10gPC0gcmVzdWx0cyR4CiAgIyBGaWxsIGluIHRoZSBub2RhdGEKICByZXN1bHRfcmFzdGVyW2lzLm5hKHRlbXBsYXRlX3Jhc3RlcildIDwtIE5BCiAgIyBNYWtlIHRoaXMgYSBjYXRlZ29yaWNhbCByYXN0ZXIKICByZXN1bHRfcmFzdGVyIDwtIHJhdGlmeShyZXN1bHRfcmFzdGVyKQogIHJhdCA8LSBsZXZlbHMocmVzdWx0X3Jhc3RlcilbWzFdXQogICMgSW4gc29tZSBjYXNlcyAobW9zdCBub3RhYmx5IHdoZW4gYnVkZ2V0IGlzIDEwMCUpIGFsbCBjZWxscyBtaWdodCBiZSAKICAjIHNlbGVjdGVkLiBDaGVjayBmb3IgdGhpcy4KICBpZiAobnJvdyhyYXQpID09IDIpIHsKICAgIHJhdCRzdGF0dXMgPC0gYygiTm90IFNlbGVjdGVkIiwgIlNlbGVjdGVkIikKICB9IGVsc2UgewogICAgcmF0JHN0YXR1cyA8LSBjKCJTZWxlY3RlZCIpCiAgfQogIGxldmVscyhyZXN1bHRfcmFzdGVyKSA8LSByYXQKICByZXR1cm4ocmVzdWx0X3Jhc3RlcikKfQoKIyBQcm9jZXNzIHRoZSB3aG9sZSBsaXN0IG9mIG1heGltdW0gY292ZXJhZ2Ugc29sdXRpb25zCm1jcF9pbHAgPC0gcmFzdGVyOjpzdGFjayhsYXBwbHkocmVzdWx0c19tYywgcHJvY2Vzc19ndXJvYmlfcmVzdWx0cywgdGVtcGxhdGUpKQpuYW1lcyhtY3BfaWxwKSA8LSBwYXN0ZTAoInBlcmNlbnQiLCBidWRnZXRzKjEwMCkKYGBgCmBgYHtyIHBsb3QtbWNwLXJlc3VsdHN9CiMgUGxvdCBhIHNlbGVjdGlvbiBvZiBzb2x1dGlvbnMgKDEwJSwgMjAlLCA1MCUpCmxldmVscGxvdChzdWJzZXQobWNwX2lscCwgYygyLCA0LCAxMCkpLCBtYWluID0gIk1heGltdW0gY292ZXJhZ2Ugc29sdXRpb25cbihnYXAgPSAwLjElKSIsCiAgICAgICAgICBzY2FsZXMgPSBsaXN0KGRyYXcgPSBGQUxTRSksCiAgICAgICAgICBjb2wucmVnaW9ucyA9IGMoImdyZXk3MCIsICIjZDcxOTFjIiksCiAgICAgICAgICBjb2xvcmtleSA9IGxpc3Qoc3BhY2UgPSAiYm90dG9tIiwgaGVpZ2h0ID0gMSkpCmBgYAoKYGBge3IgbWNwLWhpZXJhcmNoeX0KIyBEZWZpbmUgYSBmdW5jdGlvbiBmb3IgbWlubWF4LW5vcm1hbGl6YXRpb24Kbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgpIHsKICBtaW4gPC0gcmFzdGVyOjptaW5WYWx1ZSh4KQogIG1heCA8LSByYXN0ZXI6Om1heFZhbHVlKHgpCiAgcmV0dXJuKCh4IC0gbWluKSAvIChtYXggLSBtaW4pKQp9CgojIFN1bSB1cCBhbGwgdGhlIGxheWVycyBpbiB0aGUgc3RhY2sgLT4gcmVzdWx0IGlzIGEgc2VsZWN0aW9uIGZyZXF1ZW5jeQptY3BfaWxwX2hpZXIgPC0gc3VtKG1jcF9pbHAsIG5hLnJtID0gVFJVRSkKIyBSZXBsYWNlIDBzIHdpdGggTkFzCm1jcF9pbHBfaGllclttY3BfaWxwX2hpZXIgPT0gMF0gPC0gTkEKIyBOb3JtYWxpemUgdmFsdWUgaW50byBzY2FsZSBbMCwgMV0KbWNwX2lscF9oaWVyIDwtIG5vcm1hbGl6ZShtY3BfaWxwX2hpZXIpCmxldmVscGxvdChtY3BfaWxwX2hpZXIsIG1hcmdpbiA9IEZBTFNFLCBzY2FsZXMgPSBsaXN0KGRyYXcgPSBGQUxTRSksCiAgICAgICAgICBwYXIuc2V0dGluZ3MgPSByYXN0ZXJUaGVtZShyZWdpb24gPSB6X2NvbG9yc19SZFlsQnUkY29sb3JzKSwgCiAgICAgICAgICBhdCA9IHpfY29sb3JzX1JkWWxCdSR2YWx1ZXMsIG1haW4gPSAiR3Vyb2JpIE1DUCIpCmBgYApfX05PVEVfXzogdGhlIGNvbG9yIHNjYWxlIGFib3ZlIGlzIG5vdCBjb21wbGV0ZWx5IGFjY3VyYXRlLCB0aGlzIG5lZWRzIHRvIGJlIGZpeGVkLgoKCiMjIDMuIENvbXBhcmlzb24gb2YgdGhlIHByaW9yaXRpemF0aW9ucwoKIyMjIDMuMSBTcGF0aWFsIHBhdHRlcm5zCgpMZXQncyB2aXN1YWxseSBjb21wYXJlIHRoZSB0b3AgMTAlIHNvbHV0aW9uIHBhdHRlcm5zIGluIFJXUiwgWm9uYXRpb24gYW5kIEd1cm9iaSBzb2x1dGlvbnMuCgpgYGB7ciBxdWFsaXRhdGl2ZSBzcGF0aWFsfQoKIyBHZXQgdGhlIHRvcCAxMCUgc29sdXRpb25zIGZvciBSV1IgYW5kIFpvbmF0aW9uCnJ3cl90b3AxMCA8LSByd3JfcmFuayA+PSAwLjkKIyBtYWtlIHRoaXMgYSBjYXRlZ29yaWNhbCByYXN0ZXIKcndyX3RvcDEwIDwtIHJhdGlmeShyd3JfdG9wMTApCnJhdCA8LSBsZXZlbHMocndyX3RvcDEwKVtbMV1dCnJhdCRzdGF0dXMgPC0gYygiTm90IFNlbGVjdGVkIiwgIlNlbGVjdGVkIikKbGV2ZWxzKHJ3cl90b3AxMCkgPC0gcmF0CgphYmZfdG9wMTAgPC0gYWJmX3JhbmsgPj0gMC45CiMgbWFrZSB0aGlzIGEgY2F0ZWdvcmljYWwgcmFzdGVyCmFiZl90b3AxMCA8LSByYXRpZnkoYWJmX3RvcDEwKQpyYXQgPC0gbGV2ZWxzKGFiZl90b3AxMClbWzFdXQpyYXQkc3RhdHVzIDwtIGMoIk5vdCBTZWxlY3RlZCIsICJTZWxlY3RlZCIpCmxldmVscyhhYmZfdG9wMTApIDwtIHJhdAoKcDQgPC0gbGV2ZWxwbG90KHJ3cl90b3AxMCwgbWFpbiA9ICJSV1JcbnRvcCAxMCUiLAogICAgICAgICAgc2NhbGVzID0gbGlzdChkcmF3ID0gRkFMU0UpLAogICAgICAgICAgY29sLnJlZ2lvbnMgPSBjKCJncmV5NzAiLCAiI2Q3MTkxYyIpLAogICAgICAgICAgY29sb3JrZXkgPSBsaXN0KHNwYWNlID0gImJvdHRvbSIsIGhlaWdodCA9IDEpKQpwNSA8LSBsZXZlbHBsb3QoYWJmX3RvcDEwLCBtYWluID0gIlpvbmF0aW9uIEFCRlxudG9wIDEwJSIsCiAgICAgICAgICBzY2FsZXMgPSBsaXN0KGRyYXcgPSBGQUxTRSksCiAgICAgICAgICBjb2wucmVnaW9ucyA9IGMoImdyZXk3MCIsICIjZDcxOTFjIiksCiAgICAgICAgICBjb2xvcmtleSA9IGxpc3Qoc3BhY2UgPSAiYm90dG9tIiwgaGVpZ2h0ID0gMSkpCnA2IDwtIGxldmVscGxvdChtY3BfaWxwW1sxXV0sIG1haW4gPSAiR3Vyb2JpIE1DUFxuMTAlIGJ1ZGdldCIsCiAgICAgICAgICBzY2FsZXMgPSBsaXN0KGRyYXcgPSBGQUxTRSksCiAgICAgICAgICBjb2wucmVnaW9ucyA9IGMoImdyZXk3MCIsICIjZDcxOTFjIiksCiAgICAgICAgICBjb2xvcmtleSA9IGxpc3Qoc3BhY2UgPSAiYm90dG9tIiwgaGVpZ2h0ID0gMSkpCgpncmlkLmFycmFuZ2UocDQsIHA1LCBwNiwgbnJvdyA9IDEsIG5jb2wgPSAzKQpgYGAKCkxldCdzIGFsc28gY2hlY2sgdGhhdCB0aGUgc29sdXRpb25zIGFib3ZlIGluY2x1ZGUgdGhlIHNhbWUgYW1vdW50IG9mIGNlbGxzIGluY2x1ZGVkIGluIHRoZSB0b3AgMTAlLgoKYGBge3IgdGFibGUtcmVzdWx0cyxyZXN1bHRzPSJhc2lzIn0KY2VsbF9jb3VudCA8LSBsYXBwbHkoYyhyd3JfdG9wMTAsIGFiZl90b3AxMCwgbWNwX2lscFtbMl1dKSwgZnVuY3Rpb24oeCkgdGFibGUocmFzdGVyOjpnZXRWYWx1ZXMoeCkpKSAKY2VsbF9jb3VudCA8LSBhcy5kYXRhLmZyYW1lKGRvLmNhbGwoInJiaW5kIiwgY2VsbF9jb3VudCkpCm5hbWVzKGNlbGxfY291bnQpIDwtIGMoIkxvdyIsICJUb3AiKQpjZWxsX2NvdW50JE1ldGhvZCA8LSBjKCJSV1IiLCAiQUJGIiwgIk1JTFAgTUNQIikKY2VsbF9jb3VudCA8LSBkcGx5cjo6c2VsZWN0KGNlbGxfY291bnQsIE1ldGhvZCwgTG93LCBUb3ApCmtuaXRyOjprYWJsZShjZWxsX2NvdW50LCBhbGlnbiA9IGMoJ3InLCAnYycsICdjJyksIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgojIyMgMy4yIFF1YW50aXRhdGl2ZSBwZXJmb3JtYW5jZQoKYGBge3IgcGVyZm9ybWFuY2V9CgojIFJlY2xhc3NpZnkgcmFzdGVycwpyZWNsYXNzX3Jhc3RlciA8LSBmdW5jdGlvbih4LCByY2wpIHsKICAKICAgYXNzZXJ0X3RoYXQoaW5oZXJpdHMoeCwgIlJhc3RlckxheWVyIiksCiAgICAgICAgICAgICAgIGluaGVyaXRzKHJjbCwgImRhdGEuZnJhbWUiKSwKICAgICAgICAgICAgICAgYWxsKG5hbWVzKHJjbCkgPT0gYygiZnJvbSIsICJ0byIsICJ2YWx1ZSIpKSkKICAKICByYXN0ZXJfcmNsIDwtIHJhc3Rlcjo6cmVjbGFzc2lmeSh4LCByY2wsIGluY2x1ZGUubG93ZXN0ID0gVFJVRSkKICByYXN0ZXJfcmNsIDwtIHJhdGlmeShyYXN0ZXJfcmNsKQogIHJhdF9yY2wgPC0gbGV2ZWxzKHJhc3Rlcl9yY2wpW1sxXV0KICByYXRfcmNsJHN0YXR1cyA8LSByY2wkdmFsdWUKICBsZXZlbHMocmFzdGVyX3JjbCkgPC0gcmF0X3JjbAogIHJldHVybihyYXN0ZXJfcmNsKQp9Cgp6b25hbF9zdGF0cyA8LSBmdW5jdGlvbih6b25hbF9yYXN0ZXIsIHZhbHVlc19zdGFjaywgcmNsLCBtZXRob2QpIHsKICByY2xfcmFzdGVyIDwtIHJlY2xhc3NfcmFzdGVyKHpvbmFsX3Jhc3RlciwgcmNsKQogIHJjbF96b25hbCA8LSBhcy5kYXRhLmZyYW1lKHJhc3Rlcjo6em9uYWwodmFsdWVzX3N0YWNrLCByY2xfcmFzdGVyLCBmdW4gPSAnc3VtJykpCiAgIyBNYW51YWxseSBhZGQgem9uZSAwLCBpbiB3aGljaCBub3RoaW5nIGhhcyBiZWVuIGdhaW5lZCB5ZXQKICBkYXRhX3JvdyA8LSByZXAoMCwgcmFzdGVyOjpubGF5ZXJzKHZhbHVlc19zdGFjaykgKyAxKQogIG5hbWVzKGRhdGFfcm93KSA8LSBjKCJ6b25lIiwgbmFtZXModmFsdWVzX3N0YWNrKSkKICByY2xfem9uYWwgPC0gcmJpbmQocmNsX3pvbmFsLCBhcy5kYXRhLmZyYW1lKHQoZGF0YV9yb3cpKSkKICByY2xfem9uYWwgPC0gcmNsX3pvbmFsICU+JSAKICAgICMgU29ydCBieSB6b25lLCBab25lIDEgPSAxMDAtOTAlIC4uLiBab25lIDEwID0gMTAtMCUgb2YgdGhlIGxhbmRzY2FwZQogICAgYXJyYW5nZSh6b25lKSAlPiUgCiAgICAjIEdhdGhlciBpbnRvIGxvbmcgZm9ybWF0IGtlZXBpbmcgem9uZSBpbnRhY3QKICAgIGdhdGhlcihzcGVjaWVzLCB2YWx1ZSwgLXpvbmUpICU+JQogICAgZ3JvdXBfYnkoc3BlY2llcykgJT4lIAogICAgIyBDb3VudCBjdW11bGF0aXZlIHZhbHVlIGZvciBkaXN0cmlidXRpb24gc3VtLCB0aGF0IGlzIHpvbmUxICsgem9uZTIgKyAuLi4gKyB6b25lMTAKICAgICMgVGhlbiB0YWtlIHRoZSBpbnZlcnNlIG9mIHRoaXMsIHdoaWNoIGlzIDEgLSAgZHN1bSB0byBzaG93IGRpc3RyaWJ1dGlvbiByZW1haW5pbmcKICAgIG11dGF0ZShkc3VtID0gY3Vtc3VtKHZhbHVlKSwKICAgICAgICAgICBkcmVtID0gMSAtIGRzdW0pICU+JSAKICAgIHVuZ3JvdXAoKSAlPiUgCiAgICAjIEZpbmFsbHksIGNhbGN1bGF0ZSBtZWFuIGRpc3RyaWJ1dGlvbiByZW1haW5pbmcgcGVyIHpvbmUgCiAgICBncm91cF9ieSh6b25lKSAlPiUgCiAgICBtdXRhdGUobWluX2RyZW0gPSBtaW4oZHJlbSksCiAgICAgICAgICAgbWVhbl9kcmVtID0gbWVhbihkcmVtKSwKICAgICAgICAgICBtYXhfZHJlbSA9IG1heChkcmVtKSwKICAgICAgICAgICBtZXRob2QgPSBtZXRob2QpCiAgcmV0dXJuKHJjbF96b25hbCkKfQoKIyBSZWNsYXNzaWZpY2F0aW9uIHRhYmxlCnJjbCA8LSBkYXRhLmZyYW1lKGZyb20gPSBzZXEoMCwgLjksIDAuMSksIAogICAgICAgICAgICAgICAgICB0byA9IHNlcSgwLjEsIDEuMCwgMC4xKSwgCiAgICAgICAgICAgICAgICAgIHZhbHVlID0gMToxMCkKCnJ3cl96b25hbCA8LSB6b25hbF9zdGF0cyhyd3JfcmFuaywgc3BfcmFzdGVyc19ub3JtYWxpemVkLCByY2wsICJSV1IiKQphYmZfem9uYWwgPC0gem9uYWxfc3RhdHMoYWJmX3JhbmssIHNwX3Jhc3RlcnNfbm9ybWFsaXplZCwgcmNsLCAiQUJGIikKY2F6X3pvbmFsIDwtIHpvbmFsX3N0YXRzKGNhel9yYW5rLCBzcF9yYXN0ZXJzX25vcm1hbGl6ZWQsIHJjbCwgIkNBWiIpCm1jcF9pbHBfem9uYWwgPC0gem9uYWxfc3RhdHMobWNwX2lscF9oaWVyLCBzcF9yYXN0ZXJzX25vcm1hbGl6ZWQsIHJjbCwgIklMUCIpCgojIEJpbmQgYWxsIHpvbmFsIHN0YXRzIHRvZ2V0aGVyCmFsbF96b25hbCA8LSBkcGx5cjo6YmluZF9yb3dzKHJ3cl96b25hbCwgYWJmX3pvbmFsLCBjYXpfem9uYWwsIG1jcF9pbHBfem9uYWwpICU+JSAKICBkcGx5cjo6c2VsZWN0KHpvbmUsIG1lYW5fZHJlbSwgbWV0aG9kKQoKZ2dwbG90KHJ3cl96b25hbCwgYWVzKHggPSB6b25lLCB5ID0gZHJlbSwgY29sb3IgPSBzcGVjaWVzKSkgKyBnZW9tX2xpbmUoKSArIGdndGl0bGUoIlJXUiIpICsKICB5bGFiKCJEaXN0cmlidXRpb24gcmVtYWluaW5nXG4iKSArIHhsYWIoIlxuIExhbmRzY2FwZSBzZWxlY3RlZCIpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDA6MTAsIGxhYmVscyA9IHBhc3RlMChzZXEoMTAwLCAwLCAtMTApLCAiJSIpKQpnZ3Bsb3QoYWJmX3pvbmFsLCBhZXMoeCA9IHpvbmUsIHkgPSBkcmVtLCBjb2xvciA9IHNwZWNpZXMpKSArIGdlb21fbGluZSgpICsgZ2d0aXRsZSgiQUJGIikgKwogIHlsYWIoIkRpc3RyaWJ1dGlvbiByZW1haW5pbmdcbiIpICsgeGxhYigiXG4gTGFuZHNjYXBlIHNlbGVjdGVkIikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDoxMCwgbGFiZWxzID0gcGFzdGUwKHNlcSgxMDAsIDAsIC0xMCksICIlIikpCmdncGxvdChjYXpfem9uYWwsIGFlcyh4ID0gem9uZSwgeSA9IGRyZW0sIGNvbG9yID0gc3BlY2llcykpICsgZ2VvbV9saW5lKCkgKyBnZ3RpdGxlKCJDQVoiKSArCiAgeWxhYigiRGlzdHJpYnV0aW9uIHJlbWFpbmluZ1xuIikgKyB4bGFiKCJcbiBMYW5kc2NhcGUgc2VsZWN0ZWQiKSArIAogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAwOjEwLCBsYWJlbHMgPSBwYXN0ZTAoc2VxKDEwMCwgMCwgLTEwKSwgIiUiKSkKZ2dwbG90KG1jcF9pbHBfem9uYWwsIGFlcyh4ID0gem9uZSwgeSA9IGRyZW0sIGNvbG9yID0gc3BlY2llcykpICsgZ2VvbV9saW5lKCkgKyAKICBnZ3RpdGxlKCJHdXJvYmkgTUNQIikgKyB5bGFiKCJEaXN0cmlidXRpb24gcmVtYWluaW5nXG4iKSArIHhsYWIoIlxuIExhbmRzY2FwZSBzZWxlY3RlZCIpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDA6MTAsIGxhYmVscyA9IHBhc3RlMChzZXEoMTAwLCAwLCAtMTApLCAiJSIpKQpnZ3Bsb3QoYWxsX3pvbmFsLCBhZXMoeCA9IHpvbmUsIHkgPSBtZWFuX2RyZW0sIGNvbG9yID0gbWV0aG9kKSkgKyBnZW9tX2xpbmUoKSArCiAgeWxhYigiTWVhbiBkaXN0cmlidXRpb24gcmVtYWluaW5nXG4iKSArIHhsYWIoIlxuIExhbmRzY2FwZSBzZWxlY3RlZCIpICsgCiAgZ2d0aXRsZSgiTWVhbiBwZXJmb3JtYW5jZSBhbW9uZyB0aGUgbWV0aG9kcyIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDoxMCwgbGFiZWxzID0gcGFzdGUwKHNlcSgxMDAsIDAsIC0xMCksICIlIikpCgpgYGAKCldoYXQgaXMgc3RyaWtpbmcgaGVyZSwgaXMgdGhhdCB0aGVyZSBpcyB2ZXJ5IGxpdHRsZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIG1ldGhvZHMuIENBWiAtIGFzIGV4cGVjdGVkIC0gaGFzIG9uIGF2ZXJhZ2UgYSBsaXR0bGUgbG93ZXIgcGVyZm9ybWFuY2UsIG90aGVycyBhcmUgdmVyeSBzaW1pbGFyLgoKIyMgNC4gTGl0ZXJhdHVyZSBjaXRlZAoKKyBBbGJ1cXVlcnF1ZSwgRi4gUy4sICYgQmVpZXIsIFAuICgyMDE1KS4gUmFyaXR5LVdlaWdodGVkIFJpY2huZXNzOiBBIFNpbXBsZSBhbmQgUmVsaWFibGUgQWx0ZXJuYXRpdmUgdG8gSW50ZWdlciBQcm9ncmFtbWluZyBhbmQgSGV1cmlzdGljIEFsZ29yaXRobXMgZm9yIE1pbmltdW0gU2V0IGFuZCBNYXhpbXVtIENvdmVyYWdlIFByb2JsZW1zIGluIENvbnNlcnZhdGlvbiBQbGFubmluZy4gUGxvcyBPbmUsIDEwKDMpLCBlMDExOTkwNS4gW2h0dHA6Ly9kb2kub3JnLzEwLjEzNzEvam91cm5hbC5wb25lLjAxMTk5MDVdKGh0dHA6Ly9kb2kub3JnLzEwLjEzNzEvam91cm5hbC5wb25lLjAxMTk5MDUpCisgQXJwb25lbiwgQS4sIENhYmV6YSwgTS4sIEVrbHVuZCwgSi4sIEt1amFsYSwgSC4sICYgTGVodG9tw6RraSwgSi4gKDIwMTApLiBDb3N0cyBvZiBpbnRlZ3JhdGluZyBlY29ub21pY3MgYW5kIGNvbnNlcnZhdGlvbiBwbGFubmluZy4gQ29uc2VydmF0aW9uIEJpb2xvZ3ksIDI0KDUpLCAxMTk44oCTMjA0LiBbaHR0cDovL2RvaS5vcmcvMTAuMTExMS9qLjE1MjMtMTczOS4yMDEwLjAxNTM5LnhdKGh0dHA6Ly9kb2kub3JnLzEwLjExMTEvai4xNTIzLTE3MzkuMjAxMC4wMTUzOS54KQorIEJleWVyLCBILiBMLiwgRHVqYXJkaW4sIFkuLCBXYXR0cywgTS4gRS4sICYgUG9zc2luZ2hhbSwgSC4gUC4gKDIwMTYpLiBTb2x2aW5nIGNvbnNlcnZhdGlvbiBwbGFubmluZyBwcm9ibGVtcyB3aXRoIGludGVnZXIgbGluZWFyIHByb2dyYW1taW5nLiBFY29sb2dpY2FsIE1vZGVsbGluZywgMzI4LCAxNOKAkzIyLiBbaHR0cDovL2R4LmRvaS5vcmcvMTAuMTAxNi9qLmVjb2xtb2RlbC4yMDE2LjAyLjAwNV0oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTAxNi9qLmVjb2xtb2RlbC4yMDE2LjAyLjAwNSkKKyBDYW1tLCBKLiBELiwgUG9sYXNreSwgUy4sIFNvbG93LCBBLiwgJiBDc3V0aSwgQi4gKDE5OTYpLiBBIG5vdGUgb24gb3B0aW1hbCBhbGdvcml0aG1zIGZvciByZXNlcnZlIHNpdGUgc2VsZWN0aW9uLiBCaW9sb2dpY2FsIENvbnNlcnZhdGlvbiwgNzgoMyksIDM1M+KAkzM1NS4gW2h0dHA6Ly9kb2kub3JnLzEwLjEwMTYvMDAwNi0zMjA3KDk1KTAwMTMyLThdKGh0dHA6Ly9kb2kub3JnLzEwLjEwMTYvMDAwNi0zMjA3KDk1KTAwMTMyLTgpCisgTGFpdGlsYSwgSi4sICYgTW9pbGFuZW4sIEEuICgyMDEyKS4gVXNlIG9mIG1hbnkgbG93LWxldmVsIGNvbnNlcnZhdGlvbiB0YXJnZXRzIHJlZHVjZXMgaGlnaC1sZXZlbCBjb25zZXJ2YXRpb24gcGVyZm9ybWFuY2UuIEVjb2xvZ2ljYWwgTW9kZWxsaW5nLCAyNDcsIDQw4oCTNDcuIFtodHRwOi8vZG9pLm9yZy8xMC4xMDE2L2ouZWNvbG1vZGVsLjIwMTIuMDguMDEwXShodHRwOi8vZG9pLm9yZy8xMC4xMDE2L2ouZWNvbG1vZGVsLjIwMTIuMDguMDEwKQorIE1vaWxhbmVuLCBBLiAoMjAwNykuIExhbmRzY2FwZSBab25hdGlvbiwgYmVuZWZpdCBmdW5jdGlvbnMgYW5kIHRhcmdldC1iYXNlZCBwbGFubmluZzogdW5pZnlpbmcgcmVzZXJ2ZSBzZWxlY3Rpb24gc3RyYXRlZ2llcy4gQmlvbG9naWNhbCBDb25zZXJ2YXRpb24sIDEzNCwgNTcx4oCTNTc5LgpbaHR0cDovL2R4LmRvaS5vcmcvMTAuMTAxNi9qLmJpb2Nvbi4yMDA2LjA5LjAwOF0oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTAxNi9qLmJpb2Nvbi4yMDA2LjA5LjAwOCkKKyBNb2lsYW5lbiwgQS4sICYgQXJwb25lbiwgQS4gKDIwMTEpLiBTZXR0aW5nIGNvbnNlcnZhdGlvbiB0YXJnZXRzIHVuZGVyIGJ1ZGdldGFyeSBjb25zdHJhaW50cy4gQmlvbG9naWNhbCBDb25zZXJ2YXRpb24sIDE0NCgxKSwgNjUw4oCTNjUzLiBbaHR0cDovL2RvaS5vcmcvMTAuMTAxNi9qLmJpb2Nvbi4yMDEwLjA5LjAwNl0oaHR0cDovL2RvaS5vcmcvMTAuMTAxNi9qLmJpb2Nvbi4yMDEwLjA5LjAwNikKKyBVbmRlcmhpbGwsIEwuIEcuIDE5OTQuIE9wdGltYWwgYW5kIHN1Ym9wdGltYWwgcmVzZXJ2ZSBzZWxlY3Rpb24gYWxnb3JpdGhtcy4gQmlvbG9naWNhbCBDb25zZXJ2YXRpb24gNzA6ODXigJM4Ny4gW2h0dHA6Ly9keC5kb2kub3JnLzEwLjEwMTYvMDAwNi0zMjA3KDk0KTkwMzAyLTZdKGh0dHA6Ly9keC5kb2kub3JnLzEwLjEwMTYvMDAwNi0zMjA3KDk0KTkwMzAyLTYpCg==