Recently, I was doing work with pgRouting and ran into the problem of my network having not nearly enough nodes to get an accurate calculation for what I was doing, which at the time was creating catchment areas. I found a very helpful post by Paul Ramsey where he shows different ways of breaking up linestrings into segments using PostGIS. I adapted his last example here with st_segmentize
to add more vertices to the linestrings to ensure that the max length of the line segments was half a meter.
with routing as (
select row_number() over (order by geometry) as id,
st_segmentize(geometry,.5) as geometry
from streets
),
segments as (
select id,
st_astext(st_makeline(lag((pt).geom, 1, null) over (partition by id order by id, (pt).path), (pt).geom)) as geom
from (select id, st_dumppoints(geometry) as pt from routing) as dumps
)
select * from segments where geom is not null;
A recent update to sfnetworks
has made spatial network analysis in R significantly more straightforward and while working with this package, I encountered the same problem as I did with pgRouting: my network needed more nodes. Because of this, I thought it would be fun to try to implement Paul Ramsey’s solution in R using sf
and create a function called st_segments
to do the job. There is an existing package named nngeo
that has the functionst_segments
, though I found it to be slow and I wanted to create my own version of the function based on the SQL. Before getting started on anything, it would be best to get a small road network to work with.
library(sf)
library(mapview)
library(dplyr)
library(tigris)
midd = read_sf("https://opendata.arcgis.com/datasets/625b92329bb841cf8295d14553b67e1f_2.geojson") %>%
filter(COLLEGE == "MIDDLEBURY COLLEGE") %>%
st_transform(32145) %>% # Vermont State Plane Meters
st_union() %>%
st_centroid() %>%
st_buffer(5000) %>%
st_bbox %>%
st_as_sfc
streets = roads("vt", "addison county", progress_bar = F) %>% st_transform(32145) %>% st_intersection(midd) %>%
filter(!grepl("MULTI", as.character(st_geometry_type(.))))
mapview(streets, color = "#4C4B4C")
first_attempt = function(x, max_length){
library(sf)
library(dplyr)
library(purrr)
net = st_sf(x)
if (!missing(max_length)) {
net = st_segmentize(net, max_length)
}
net %>%
mutate(id = row_number()) %>%
st_cast("POINT") %>%
group_by(id) %>%
mutate(geom2 = lead(geometry)) %>%
filter(!st_is_empty(geom2)) %>%
ungroup() %>%
mutate(geom = map2(
geometry, geom2,
~ st_combine(c(.x, .y)) %>% st_cast("LINESTRING")
)) %>%
{
do.call(c, .$geom)
} %>%
st_set_crs(st_crs(x)) %>%
st_sf()
}
This was the first function I came up with and it is somewhat faithful to the original SQL. It utilizes dplyr
and purrr
and has similar steps and syntax. Just as in the SQL, the features are first given a unique ID and st_segmentize
is used to add vertices. The linestrings are then cast to points and rather than using lag
, lead
is used is used to create a new geometry column with leading point geometries grouped by unique id. Either lag
or lead
could have been used here, but for whatever reason it was easier for me to wrap my head around using lead
. The empty geometries in the new column are then removed, these being the last row of each group since lead
was used. After this, map2
is used to create a third geometry column where the points of the two existing geometry columns are combined and cast to a linestring by row. I also tried using st_union
rather than st_combine
here, though st_combine
seemed to work better, and excluding either from the function created unfavorable results such as a very long multilinestring geometry. The new column created with map2
was a not simple feature collection, but rather a list of geometry sets. Because of this, it was necessary to use do.call
to combine the geometry sets into a single geometry set. This new set had no CRS, so it had to be reset and the set is subsequently converted into a simple feature collection.
bench::mark(
nngeo = st_geometry(nngeo::st_segments(streets, progress = F)),
"first attempt " = st_geometry(first_attempt(streets)),
iterations = 5
) %>%
mutate(name = as.character(expression),
max = bench::as_bench_time(lapply(time, max))) %>%
select(name, min, median, max, iterations = n_itr, total_time)
This new function is undoubtedly faster than nngeo::st_segments
, though its result has no fields and it relies on dplyr
and purrr
.
second_attempt = function(x, max_length) {
library(sf)
library(dplyr)
library(purrr)
net = st_sf(x) %>% mutate(id = row_number())
no_geo = st_drop_geometry(net)
if (!missing(max_length)) {
net = st_segmentize(net, max_length)
}
net = net %>%
st_cast("POINT") %>%
group_by(id) %>%
mutate(geom2 = lead(geometry)) %>%
filter(!st_is_empty(geom2)) %>%
ungroup() %>%
suppressWarnings()
id = net$id
net %>%
mutate(geom = map2(geometry, geom2, ~ st_combine(c(.x, .y)) %>% st_cast("LINESTRING"))) %>%
{
do.call(c, .$geom)
} %>%
st_set_crs(st_crs(x)) %>%
st_sf() %>%
mutate(fid = id) %>%
left_join(no_geo, by = c("fid" = "id")) %>%
select(-fid)
}
This second attempt at st_segments
solves the problem of the function not retaining the fields of the input and suppresses a message from st_cast
, though it still relies heavily on dplyr
and purrr
.
bench::mark(
nngeo = st_geometry(nngeo::st_segments(streets, progress = F)),
"first attempt " = st_geometry(first_attempt(streets)),
"second attempt" = st_geometry(second_attempt(streets)),
iterations = 5
) %>%
mutate(name = as.character(expression),
max = bench::as_bench_time(lapply(time, max))) %>%
select(name, min, median, max, iterations = n_itr, total_time)
It is marginally slower than the first attempt, though this is expected since more is done here than the first attempt. nngeo::st_segments
demonstrates that that it is possible to implement the function using sf
and base R alone, but does this have to come at the cost of the function’s speed?
st_segments = function(x, max_length) {
# x: layer with linestrings/polygons
# max_length: optional parameter, max length of segments, unit from crs
library(sf)
net = st_sf(x)
net$fid = 1:nrow(net)
if (!missing(max_length)) {
net = st_segmentize(net, max_length)
}
coord = st_coordinates(net)
col = ncol(coord)
ids = coord[, col]
feat = unique(ids)
segments = list()
for (i in feat) {
coords = coord[ids == i,]
for (j in 1:(nrow(coords) - 1)) {
segments[[length(segments) + 1]] = st_as_binary(st_linestring(coords[j:(j + 1), 1:2]))
}
}
fid = matrix(nrow = (length(ids) - length(feat)), ncol = 1)
k = 0
for (i in feat) {
id = ids[ids == i]
for (j in seq_along(id[1:(length(id) - 1)])) {
fid[k + 1, ] = id[j]
k = k + 1
}
}
colnames(fid) = "fid"
fid = as.data.frame(fid)
fid = merge(fid,st_drop_geometry(net), all.x = T)[,-1, drop = F]
fid$geometry = st_set_crs(st_as_sfc(segments), st_crs(net))
st_sf(fid)
}
With st_segments
, my final(?) attempt at the function, I quickly found that this is not the case. st_segments
loops through a matrix of coordinates created by st_coordinates
and makes linestrings in similar fashion to how lead
was used in the first.st_coordinates
adds a unique ID for each feature and I used this ID in the loop to first filter the matrix and then create linestrings using the points that make up each feature in a nested loop. With each iteration of this nested loop, a line is created from the coordinates in one row and the row below it. And just as is done with filter(!st_is_empty(geom2))
, the last row of each group are excluded as they essentially have empty geometries. The linestrings created in the loop are converted to well-known binary and stored in a list. This list can be easily made to a simple feature collection using st_as_sfc
. There were other versions of this where I made linestrings well-known text and stored them in either a matrix or a list, though WKB seemed be be the better choice and less was needed to be done to convert it to a simple feature collection. The second loop removes one value from each of the groups in a matrix of feature IDs taken from the coordinate matrix, which is also similar to what is done with filter(!st_is_empty(geom2))
. This ID matrix is then used to join fields. This function solves the pseudo problem of depending on dplyr
and purrr
, and the result still has the original fields. st_segments
is also able to break polygons into linestrings, something which my first two attempts at the function were unable to do.
library(osmdata)
bbox = st_bbox(st_transform(midd,4326))
osm_bbox = matrix(data = bbox,nrow = 2,ncol = 2)
buildings= osmdata::opq(bbox) %>%
add_osm_feature(key = "building") %>%
osmdata_sf() %>%
.$osm_polygons %>%
select(1:5) %>%
filter(!is.na(name))
mapview(buildings, layer.name = "Polygons", col.regions = "#0D395F") + mapview(st_segments(buildings),layer.name = "Linestrings", color = "#4C4B4C")
Although st_segments
is an improvement in my books, I still need to to see if using only sf
and base R comes at cost.
bench::mark(
nngeo = st_geometry(nngeo::st_segments(streets, progress = F)),
"first attempt " = st_geometry(first_attempt(streets)),
"second attempt" = st_geometry(second_attempt(streets)),
final = st_geometry(st_segments(streets)),
iterations = 5
) %>%
mutate(name = as.character(expression),
max = bench::as_bench_time(lapply(time, max))) %>%
select(name, min, median, max, iterations = n_itr, total_time)
as it turns out, st_segements
is significantly faster than the function found in nngeo
as well as my first two attempts, and this increase in speed does not come at the cost of the fields as was seen in the first attempt. One issue, however, is that st_segments
can only take geometries that are either polygons or linestrings, though this can easily be fixed by using something like st_multipart_to_singleparts
beforehand or by incorporating it in st_segments
. I could call it quits here for now. Although I said that I was striving toward only using base R and sf
, the last loop and merge
can easily be done with dplyr
, data.table
, or the function by
, and possibly even done faster. Because of this, I went on to test if this was the case.
#### data.table ####
st_segments_dt = function(x, max_length) {
library(data.table)
net = x
net$id = 1:nrow(net)
if (!missing(max_length)) {
net = st_segmentize(net, max_length)
}
coord = st_coordinates(net)
col = ncol(coord)
ids = coord[, col]
feat = unique(ids)
segments = list()
for (i in feat) {
coords = coord[ids == i, ]
for (j in 1:(nrow(coords) - 1)) {
segments[[length(segments) + 1]] = st_as_binary(st_linestring(coords[j:(j + 1), 1:2]))
}
}
fid = as.data.table(ids)
colnames(fid) = "id"
setkey(fid, id)
fid = fid[fid[, .I[-1], by = id]$V1][as.data.table(st_drop_geometry(net)), on = "id"]
fid[, `:=`(geometry = st_as_sfc(segments, crs = st_crs(net)), id = NULL)]
st_sf(fid)
}
#### dplyr ####
st_segments_tbl = function(x, max_length) {
library(sf)
library(dplyr)
net = mutate(x, value = row_number())
if (!missing(max_length)) {
net = st_segmentize(net, max_length)
}
coord = st_coordinates(net)
col = ncol(coord)
ids = coord[, col]
feat = unique(ids)
segments = list()
for (i in feat) {
coords = coord[ids == i, ]
for (j in 1:(nrow(coords) - 1)) {
segments[[length(segments) + 1]] = st_as_binary(st_linestring(coords[j:(j + 1), 1:2]))
}
}
as_tibble(ids) %>%
group_by(value) %>%
slice(-1) %>%
ungroup %>%
left_join(st_drop_geometry(net), by = "value") %>%
select(-1) %>%
mutate(geometry = st_as_sfc(segments, crs = st_crs(net))) %>% st_sf()
}
#### using by ####
st_segments_by = function(x, max_length) {
library(sf)
net = x
net$fid = factor(1:nrow(net), levels = 1:nrow(net))
if (!missing(max_length)) {
net = st_segmentize(net, max_length)
}
coord = st_coordinates(net)
col = ncol(coord)
ids = coord[, col]
feat = unique(ids)
segments = list()
for (i in feat) {
coords = coord[ids == i, ]
for (j in 1:(nrow(coords) - 1)) {
segments[[length(segments) + 1]] = st_as_binary(st_linestring(coords[j:(j + 1), 1:2]))
}
}
fid = as.data.frame(ids)
colnames(fid) = "fid"
fid$fid = factor(fid$fid, levels = unique(fid$fid))
fid = do.call(rbind, by(fid, fid$fid, function(x) x[-1,, drop = F], simplify = F))
fid = merge(fid, st_drop_geometry(net), all.x = T)[, -1, drop = F]
fid$geometry = st_as_sfc(segments, crs = st_crs(net))
st_sf(fid)
}
These three functions are essentially the same save for how they deal with the absence of the second loop. What is not the same, however, is their speed.
bench::mark(
"base (st_segments)" = st_geometry(st_segments(streets,1)),
"base (using by)" = st_geometry(st_segments_by(streets,1)),
data.table = st_geometry(st_segments_dt(streets,1)),
dplyr = st_geometry(st_segments_tbl(streets,1)),
iterations = 5
) %>%
mutate(name = as.character(expression),
max = bench::as_bench_time(lapply(time, max))) %>%
select(name, min, median, max, iterations = n_itr, total_time)
This time around I used the second parameter of these functions to see how they handle somewhat larger datasets. Rather than breaking 490 linestrings into 7,423 line segments, the max length of each line segment is set to one meter in this benchmark and the number of features increases from 490 to 225,118. What is interesting to see with this benchmark is that st_segments
is comparable in speed to both the data.table
and dplyr
versions of the function and is slightly faster than when the last loop is replaced by by
. It is also surprising that in this situation, dplyr
is ever so slightly faster than data.table
, though this may be due to my inexperience with the latter and what was being done. I could try to see what speed improvement could be had if the both loops were replaced with data.table
, though that is for another time. I already saw with the first and second attempts that working completely within the tidyverse
slowed things down. Given the results of the benchmarks, I could just use st_segments
in its current state. In all, it was fun to take a break from things and spend time to solve a “simple yet tricky” problem in R.
LS0tDQp0aXRsZTogInN0X3NlZ21lbnRzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NClJlY2VudGx5LCBJIHdhcyBkb2luZyB3b3JrIHdpdGggcGdSb3V0aW5nIGFuZCByYW4gaW50byB0aGUgcHJvYmxlbSBvZiBteSBuZXR3b3JrIGhhdmluZyBub3QgbmVhcmx5IGVub3VnaCBub2RlcyB0byBnZXQgYW4gYWNjdXJhdGUgY2FsY3VsYXRpb24gZm9yIHdoYXQgSSB3YXMgZG9pbmcsIHdoaWNoIGF0IHRoZSB0aW1lIHdhcyBjcmVhdGluZyBjYXRjaG1lbnQgYXJlYXMuIEkgZm91bmQgYSB2ZXJ5IGhlbHBmdWwgcG9zdCBieSBbUGF1bCBSYW1zZXldKGh0dHA6Ly9ibG9nLmNsZXZlcmVsZXBoYW50LmNhLzIwMTUvMDIvYnJlYWtpbmctbGluZXN0cmluZy1pbnRvLXNlZ21lbnRzLmh0bWwpIHdoZXJlIGhlIHNob3dzIGRpZmZlcmVudCB3YXlzIG9mIGJyZWFraW5nIHVwIGxpbmVzdHJpbmdzIGludG8gc2VnbWVudHMgdXNpbmcgUG9zdEdJUy4gSSBhZGFwdGVkIGhpcyBsYXN0IGV4YW1wbGUgaGVyZSB3aXRoIGBzdF9zZWdtZW50aXplYCB0byBhZGQgbW9yZSB2ZXJ0aWNlcyB0byB0aGUgbGluZXN0cmluZ3MgdG8gZW5zdXJlIHRoYXQgdGhlIG1heCBsZW5ndGggb2YgdGhlIGxpbmUgc2VnbWVudHMgd2FzIGhhbGYgYSBtZXRlci4gDQoNCmBgYHtzcWwgYnJlYWtpbmcgdXAgbGluZXN0cmluZ3MgaW4gcG9zdGdpcywgZXZhbCA9IEZ9DQp3aXRoIHJvdXRpbmcgYXMgKA0KICBzZWxlY3Qgcm93X251bWJlcigpIG92ZXIgKG9yZGVyIGJ5IGdlb21ldHJ5KSBhcyBpZCwgIA0KICBzdF9zZWdtZW50aXplKGdlb21ldHJ5LC41KSBhcyBnZW9tZXRyeQ0KICBmcm9tIHN0cmVldHMNCiksDQpzZWdtZW50cyBhcyAoDQogIHNlbGVjdCBpZCwgDQogIHN0X2FzdGV4dChzdF9tYWtlbGluZShsYWcoKHB0KS5nZW9tLCAxLCBudWxsKSBvdmVyIChwYXJ0aXRpb24gYnkgaWQgb3JkZXIgYnkgaWQsIChwdCkucGF0aCksIChwdCkuZ2VvbSkpIGFzIGdlb20NCiAgZnJvbSAoc2VsZWN0IGlkLCBzdF9kdW1wcG9pbnRzKGdlb21ldHJ5KSBhcyBwdCBmcm9tIHJvdXRpbmcpIGFzIGR1bXBzDQogICkNCiAgc2VsZWN0ICogZnJvbSBzZWdtZW50cyB3aGVyZSBnZW9tIGlzIG5vdCBudWxsOw0KYGBgDQoNCkEgcmVjZW50IHVwZGF0ZSB0byBbYHNmbmV0d29ya3NgXShodHRwczovL2dpdGh1Yi5jb20vbHV1a3ZkbWVlci9zZm5ldHdvcmtzKSBoYXMgbWFkZSBzcGF0aWFsIG5ldHdvcmsgYW5hbHlzaXMgaW4gUiBzaWduaWZpY2FudGx5IG1vcmUgc3RyYWlnaHRmb3J3YXJkIGFuZCB3aGlsZSB3b3JraW5nIHdpdGggdGhpcyBwYWNrYWdlLCBJIGVuY291bnRlcmVkIHRoZSBzYW1lIHByb2JsZW0gYXMgSSBkaWQgd2l0aCBwZ1JvdXRpbmc6IG15IG5ldHdvcmsgbmVlZGVkIG1vcmUgbm9kZXMuIEJlY2F1c2Ugb2YgdGhpcywgSSB0aG91Z2h0IGl0IHdvdWxkIGJlIGZ1biB0byB0cnkgdG8gaW1wbGVtZW50IFBhdWwgUmFtc2V5J3Mgc29sdXRpb24gaW4gUiB1c2luZyBgc2ZgIGFuZCBjcmVhdGUgYSBmdW5jdGlvbiBjYWxsZWQgYHN0X3NlZ21lbnRzYCB0byBkbyB0aGUgam9iLiBUaGVyZSBpcyBhbiBleGlzdGluZyBwYWNrYWdlIG5hbWVkIFtgbm5nZW9gXShodHRwczovL2dpdGh1Yi5jb20vbWljaGFlbGRvcm1hbi9ubmdlbykgdGhhdCBoYXMgdGhlIGZ1bmN0aW9uYHN0X3NlZ21lbnRzYCwgdGhvdWdoIEkgZm91bmQgaXQgdG8gYmUgc2xvdyBhbmQgSSB3YW50ZWQgdG8gY3JlYXRlIG15IG93biB2ZXJzaW9uIG9mIHRoZSBmdW5jdGlvbiBiYXNlZCBvbiB0aGUgU1FMLiBCZWZvcmUgZ2V0dGluZyBzdGFydGVkIG9uIGFueXRoaW5nLCBpdCB3b3VsZCBiZSBiZXN0IHRvIGdldCBhIHNtYWxsIHJvYWQgbmV0d29yayB0byB3b3JrIHdpdGguIA0KDQpgYGB7ciBnZXR0aW5nIGEgcm9hZCBuZXR3b3JrLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OX0NCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KG1hcHZpZXcpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWdyaXMpDQoNCm1pZGQgPSByZWFkX3NmKCJodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvNjI1YjkyMzI5YmI4NDFjZjgyOTVkMTQ1NTNiNjdlMWZfMi5nZW9qc29uIikgJT4lIA0KICBmaWx0ZXIoQ09MTEVHRSA9PSAiTUlERExFQlVSWSBDT0xMRUdFIikgJT4lIA0KICBzdF90cmFuc2Zvcm0oMzIxNDUpICU+JSAjIFZlcm1vbnQgU3RhdGUgUGxhbmUgTWV0ZXJzDQogIHN0X3VuaW9uKCkgJT4lIA0KICBzdF9jZW50cm9pZCgpICU+JSANCiAgc3RfYnVmZmVyKDUwMDApICU+JSANCiAgc3RfYmJveCAlPiUgDQogIHN0X2FzX3NmYw0KDQpzdHJlZXRzID0gcm9hZHMoInZ0IiwgImFkZGlzb24gY291bnR5IiwgcHJvZ3Jlc3NfYmFyID0gRikgJT4lIHN0X3RyYW5zZm9ybSgzMjE0NSkgJT4lIHN0X2ludGVyc2VjdGlvbihtaWRkKSAlPiUNCiAgZmlsdGVyKCFncmVwbCgiTVVMVEkiLCBhcy5jaGFyYWN0ZXIoc3RfZ2VvbWV0cnlfdHlwZSguKSkpKQ0KDQptYXB2aWV3KHN0cmVldHMsIGNvbG9yID0gIiM0QzRCNEMiKQ0KYGBgDQogDQpgYGB7ciBmaXJzdCBhdHRlbXB0fQ0KIGZpcnN0X2F0dGVtcHQgPSBmdW5jdGlvbih4LCBtYXhfbGVuZ3RoKXsNCiAgbGlicmFyeShzZikNCiAgbGlicmFyeShkcGx5cikNCiAgbGlicmFyeShwdXJycikNCiAgIA0KICBuZXQgPSBzdF9zZih4KQ0KICANCiAgaWYgKCFtaXNzaW5nKG1heF9sZW5ndGgpKSB7DQogICAgbmV0ID0gc3Rfc2VnbWVudGl6ZShuZXQsIG1heF9sZW5ndGgpDQogIH0NCiAgDQogIG5ldCAlPiUgDQogICAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUNCiAgICBzdF9jYXN0KCJQT0lOVCIpICU+JQ0KICAgIGdyb3VwX2J5KGlkKSAlPiUNCiAgICBtdXRhdGUoZ2VvbTIgPSBsZWFkKGdlb21ldHJ5KSkgJT4lDQogICAgZmlsdGVyKCFzdF9pc19lbXB0eShnZW9tMikpICU+JQ0KICAgIHVuZ3JvdXAoKSAlPiUgDQogICAgbXV0YXRlKGdlb20gPSBtYXAyKA0KICAgICAgZ2VvbWV0cnksIGdlb20yLA0KICAgICAgfiBzdF9jb21iaW5lKGMoLngsIC55KSkgJT4lIHN0X2Nhc3QoIkxJTkVTVFJJTkciKQ0KICAgICkpICU+JQ0KICAgICAgew0KICAgICAgICBkby5jYWxsKGMsIC4kZ2VvbSkNCiAgICAgIH0gICU+JQ0KICAgICAgc3Rfc2V0X2NycyhzdF9jcnMoeCkpICU+JSANCiAgICBzdF9zZigpDQp9DQpgYGANClRoaXMgd2FzIHRoZSBmaXJzdCBmdW5jdGlvbiBJIGNhbWUgdXAgd2l0aCBhbmQgaXQgaXMgc29tZXdoYXQgZmFpdGhmdWwgdG8gdGhlIG9yaWdpbmFsIFNRTC4gSXQgdXRpbGl6ZXMgYGRwbHlyYCBhbmQgYHB1cnJyYCBhbmQgaGFzIHNpbWlsYXIgc3RlcHMgYW5kIHN5bnRheC4gSnVzdCBhcyBpbiB0aGUgU1FMLCB0aGUgZmVhdHVyZXMgYXJlIGZpcnN0IGdpdmVuIGEgdW5pcXVlIElEIGFuZCBgc3Rfc2VnbWVudGl6ZWAgaXMgdXNlZCB0byBhZGQgdmVydGljZXMuIFRoZSBsaW5lc3RyaW5ncyBhcmUgdGhlbiBjYXN0IHRvIHBvaW50cyBhbmQgcmF0aGVyIHRoYW4gdXNpbmcgYGxhZ2AsIGBsZWFkYCBpcyB1c2VkIGlzIHVzZWQgdG8gY3JlYXRlIGEgbmV3IGdlb21ldHJ5IGNvbHVtbiB3aXRoIGxlYWRpbmcgcG9pbnQgZ2VvbWV0cmllcyBncm91cGVkIGJ5IHVuaXF1ZSBpZC4gRWl0aGVyIGBsYWdgIG9yIGBsZWFkYCBjb3VsZCBoYXZlIGJlZW4gdXNlZCBoZXJlLCBidXQgZm9yIHdoYXRldmVyIHJlYXNvbiBpdCB3YXMgZWFzaWVyIGZvciBtZSB0byB3cmFwIG15IGhlYWQgYXJvdW5kIHVzaW5nIGBsZWFkYC4gVGhlIGVtcHR5IGdlb21ldHJpZXMgaW4gdGhlIG5ldyBjb2x1bW4gYXJlIHRoZW4gcmVtb3ZlZCwgdGhlc2UgYmVpbmcgdGhlIGxhc3Qgcm93IG9mIGVhY2ggZ3JvdXAgc2luY2UgYGxlYWRgIHdhcyB1c2VkLiBBZnRlciB0aGlzLCBgbWFwMmAgaXMgdXNlZCB0byBjcmVhdGUgYSB0aGlyZCBnZW9tZXRyeSBjb2x1bW4gd2hlcmUgdGhlIHBvaW50cyBvZiB0aGUgdHdvIGV4aXN0aW5nIGdlb21ldHJ5IGNvbHVtbnMgYXJlIGNvbWJpbmVkIGFuZCBjYXN0IHRvIGEgbGluZXN0cmluZyBieSByb3cuIEkgYWxzbyB0cmllZCB1c2luZyBgc3RfdW5pb25gIHJhdGhlciB0aGFuIGBzdF9jb21iaW5lYCBoZXJlLCB0aG91Z2ggYHN0X2NvbWJpbmVgIHNlZW1lZCB0byB3b3JrIGJldHRlciwgYW5kIGV4Y2x1ZGluZyBlaXRoZXIgZnJvbSB0aGUgZnVuY3Rpb24gY3JlYXRlZCB1bmZhdm9yYWJsZSByZXN1bHRzIHN1Y2ggYXMgYSB2ZXJ5IGxvbmcgbXVsdGlsaW5lc3RyaW5nIGdlb21ldHJ5LiBUaGUgbmV3IGNvbHVtbiBjcmVhdGVkIHdpdGggYG1hcDJgIHdhcyBhIG5vdCBzaW1wbGUgZmVhdHVyZSBjb2xsZWN0aW9uLCBidXQgcmF0aGVyIGEgbGlzdCBvZiBnZW9tZXRyeSBzZXRzLiBCZWNhdXNlIG9mIHRoaXMsIGl0IHdhcyBuZWNlc3NhcnkgdG8gdXNlIGBkby5jYWxsYCB0byBjb21iaW5lIHRoZSBnZW9tZXRyeSBzZXRzIGludG8gYSBzaW5nbGUgZ2VvbWV0cnkgc2V0LiBUaGlzIG5ldyBzZXQgaGFkIG5vIENSUywgc28gaXQgaGFkIHRvIGJlIHJlc2V0IGFuZCB0aGUgc2V0IGlzIHN1YnNlcXVlbnRseSBjb252ZXJ0ZWQgaW50byBhIHNpbXBsZSBmZWF0dXJlIGNvbGxlY3Rpb24uICAgDQoNCmBgYHtyIGZpcnN0IGJlbmNobWFyaywgd2FybmluZz1GLG1lc3NhZ2U9Rn0NCmJlbmNoOjptYXJrKA0KICBubmdlbyAgPSAgc3RfZ2VvbWV0cnkobm5nZW86OnN0X3NlZ21lbnRzKHN0cmVldHMsIHByb2dyZXNzID0gRikpLA0KICAiZmlyc3QgYXR0ZW1wdCAiID0gc3RfZ2VvbWV0cnkoZmlyc3RfYXR0ZW1wdChzdHJlZXRzKSksDQogIGl0ZXJhdGlvbnMgPSA1DQopICU+JQ0KICBtdXRhdGUobmFtZSA9IGFzLmNoYXJhY3RlcihleHByZXNzaW9uKSwNCiAgICAgICAgIG1heCA9IGJlbmNoOjphc19iZW5jaF90aW1lKGxhcHBseSh0aW1lLCBtYXgpKSkgJT4lDQogIHNlbGVjdChuYW1lLCBtaW4sIG1lZGlhbiwgbWF4LCBpdGVyYXRpb25zID0gbl9pdHIsIHRvdGFsX3RpbWUpDQpgYGANClRoaXMgbmV3IGZ1bmN0aW9uIGlzIHVuZG91YnRlZGx5IGZhc3RlciB0aGFuIGBubmdlbzo6c3Rfc2VnbWVudHNgLCB0aG91Z2ggaXRzIHJlc3VsdCBoYXMgbm8gZmllbGRzIGFuZCBpdCByZWxpZXMgb24gYGRwbHlyYCBhbmQgYHB1cnJyYC4gDQoNCmBgYHtyIHNlY29uZCBhdHRlbXB0fQ0Kc2Vjb25kX2F0dGVtcHQgPSBmdW5jdGlvbih4LCBtYXhfbGVuZ3RoKSB7DQogIGxpYnJhcnkoc2YpDQogIGxpYnJhcnkoZHBseXIpDQogIGxpYnJhcnkocHVycnIpDQogIA0KICBuZXQgPSBzdF9zZih4KSAlPiUgbXV0YXRlKGlkID0gcm93X251bWJlcigpKQ0KICBub19nZW8gPSBzdF9kcm9wX2dlb21ldHJ5KG5ldCkNCiAgDQogIGlmICghbWlzc2luZyhtYXhfbGVuZ3RoKSkgew0KICAgIG5ldCA9IHN0X3NlZ21lbnRpemUobmV0LCBtYXhfbGVuZ3RoKQ0KICB9DQogIA0KICBuZXQgPSBuZXQgJT4lDQogICAgc3RfY2FzdCgiUE9JTlQiKSAlPiUNCiAgICBncm91cF9ieShpZCkgJT4lDQogICAgbXV0YXRlKGdlb20yID0gbGVhZChnZW9tZXRyeSkpICU+JQ0KICAgIGZpbHRlcighc3RfaXNfZW1wdHkoZ2VvbTIpKSAlPiUNCiAgICB1bmdyb3VwKCkgJT4lDQogICAgc3VwcHJlc3NXYXJuaW5ncygpDQogIA0KICBpZCA9IG5ldCRpZA0KICANCiAgbmV0ICU+JQ0KICAgIG11dGF0ZShnZW9tID0gbWFwMihnZW9tZXRyeSwgZ2VvbTIsIH4gc3RfY29tYmluZShjKC54LCAueSkpICU+JSBzdF9jYXN0KCJMSU5FU1RSSU5HIikpKSAlPiUNCiAgICB7DQogICAgICBkby5jYWxsKGMsIC4kZ2VvbSkNCiAgICB9ICAlPiUNCiAgICBzdF9zZXRfY3JzKHN0X2Nycyh4KSkgJT4lDQogICAgc3Rfc2YoKSAlPiUNCiAgICBtdXRhdGUoZmlkID0gaWQpICU+JQ0KICAgIGxlZnRfam9pbihub19nZW8sIGJ5ID0gYygiZmlkIiA9ICJpZCIpKSAlPiUNCiAgICBzZWxlY3QoLWZpZCkgDQp9DQpgYGANCg0KVGhpcyBzZWNvbmQgYXR0ZW1wdCBhdCBgc3Rfc2VnbWVudHNgIHNvbHZlcyB0aGUgcHJvYmxlbSBvZiB0aGUgZnVuY3Rpb24gbm90IHJldGFpbmluZyB0aGUgZmllbGRzIG9mIHRoZSBpbnB1dCBhbmQgc3VwcHJlc3NlcyBhIG1lc3NhZ2UgZnJvbSBgc3RfY2FzdGAsIHRob3VnaCBpdCBzdGlsbCByZWxpZXMgaGVhdmlseSBvbiBgZHBseXJgIGFuZCBgcHVycnJgLiANCg0KYGBge3Igc2Vjb25kIGJlbmNobWFyaywgd2FybmluZz1GLG1lc3NhZ2U9Rn0NCmJlbmNoOjptYXJrKA0KICBubmdlbyAgPSAgc3RfZ2VvbWV0cnkobm5nZW86OnN0X3NlZ21lbnRzKHN0cmVldHMsIHByb2dyZXNzID0gRikpLA0KICAiZmlyc3QgYXR0ZW1wdCAiID0gc3RfZ2VvbWV0cnkoZmlyc3RfYXR0ZW1wdChzdHJlZXRzKSksDQogICJzZWNvbmQgYXR0ZW1wdCIgPSBzdF9nZW9tZXRyeShzZWNvbmRfYXR0ZW1wdChzdHJlZXRzKSksDQogIGl0ZXJhdGlvbnMgPSA1DQopICU+JQ0KICBtdXRhdGUobmFtZSA9IGFzLmNoYXJhY3RlcihleHByZXNzaW9uKSwNCiAgICAgICAgIG1heCA9IGJlbmNoOjphc19iZW5jaF90aW1lKGxhcHBseSh0aW1lLCBtYXgpKSkgJT4lDQogIHNlbGVjdChuYW1lLCBtaW4sIG1lZGlhbiwgbWF4LCBpdGVyYXRpb25zID0gbl9pdHIsIHRvdGFsX3RpbWUpDQpgYGANCkl0IGlzIG1hcmdpbmFsbHkgc2xvd2VyIHRoYW4gdGhlIGZpcnN0IGF0dGVtcHQsIHRob3VnaCB0aGlzIGlzIGV4cGVjdGVkIHNpbmNlIG1vcmUgaXMgZG9uZSBoZXJlIHRoYW4gdGhlIGZpcnN0IGF0dGVtcHQuIGBubmdlbzo6c3Rfc2VnbWVudHNgIGRlbW9uc3RyYXRlcyB0aGF0IHRoYXQgaXQgaXMgcG9zc2libGUgdG8gaW1wbGVtZW50IHRoZSBmdW5jdGlvbiB1c2luZyBgc2ZgIGFuZCBiYXNlIFIgYWxvbmUsIGJ1dCBkb2VzIHRoaXMgaGF2ZSB0byBjb21lIGF0IHRoZSBjb3N0IG9mIHRoZSBmdW5jdGlvbidzIHNwZWVkPw0KDQpgYGB7ciB1c2luZyBzdF9zZWdtZW50c30NCnN0X3NlZ21lbnRzID0gZnVuY3Rpb24oeCwgbWF4X2xlbmd0aCkgew0KICAjIHg6IGxheWVyIHdpdGggbGluZXN0cmluZ3MvcG9seWdvbnMNCiAgIyBtYXhfbGVuZ3RoOiBvcHRpb25hbCBwYXJhbWV0ZXIsIG1heCBsZW5ndGggb2Ygc2VnbWVudHMsIHVuaXQgZnJvbSBjcnMNCiAgbGlicmFyeShzZikNCiAgDQogIG5ldCA9IHN0X3NmKHgpDQogIG5ldCRmaWQgPSAxOm5yb3cobmV0KQ0KICANCiAgaWYgKCFtaXNzaW5nKG1heF9sZW5ndGgpKSB7DQogICAgbmV0ID0gc3Rfc2VnbWVudGl6ZShuZXQsIG1heF9sZW5ndGgpDQogIH0NCiAgDQogIGNvb3JkID0gc3RfY29vcmRpbmF0ZXMobmV0KQ0KICBjb2wgPSBuY29sKGNvb3JkKQ0KICBpZHMgPSBjb29yZFssIGNvbF0NCiAgZmVhdCA9IHVuaXF1ZShpZHMpDQogIHNlZ21lbnRzID0gbGlzdCgpDQogIA0KICBmb3IgKGkgaW4gZmVhdCkgew0KICAgIGNvb3JkcyA9IGNvb3JkW2lkcyA9PSBpLF0NCiAgICBmb3IgKGogaW4gMToobnJvdyhjb29yZHMpIC0gMSkpIHsNCiAgICAgIHNlZ21lbnRzW1tsZW5ndGgoc2VnbWVudHMpICsgMV1dID0gc3RfYXNfYmluYXJ5KHN0X2xpbmVzdHJpbmcoY29vcmRzW2o6KGogKyAxKSwgMToyXSkpDQogICAgfQ0KICB9DQogIA0KICBmaWQgPSBtYXRyaXgobnJvdyA9IChsZW5ndGgoaWRzKSAtIGxlbmd0aChmZWF0KSksIG5jb2wgPSAxKQ0KICBrID0gMA0KICANCiAgZm9yIChpIGluIGZlYXQpIHsNCiAgICBpZCA9IGlkc1tpZHMgPT0gaV0NCiAgICBmb3IgKGogaW4gc2VxX2Fsb25nKGlkWzE6KGxlbmd0aChpZCkgLSAxKV0pKSB7DQogICAgICBmaWRbayArIDEsIF0gPSBpZFtqXQ0KICAgICAgayA9IGsgKyAxDQogICAgfQ0KICB9DQogIA0KICBjb2xuYW1lcyhmaWQpID0gImZpZCINCiAgZmlkID0gYXMuZGF0YS5mcmFtZShmaWQpDQogIGZpZCA9IG1lcmdlKGZpZCxzdF9kcm9wX2dlb21ldHJ5KG5ldCksIGFsbC54ID0gVClbLC0xLCBkcm9wID0gRl0NCiAgZmlkJGdlb21ldHJ5ID0gc3Rfc2V0X2NycyhzdF9hc19zZmMoc2VnbWVudHMpLCBzdF9jcnMobmV0KSkNCiAgc3Rfc2YoZmlkKQ0KfQ0KYGBgDQoNCldpdGggYHN0X3NlZ21lbnRzYCwgbXkgZmluYWwoPykgYXR0ZW1wdCBhdCB0aGUgZnVuY3Rpb24sIEkgcXVpY2tseSBmb3VuZCB0aGF0IHRoaXMgaXMgbm90IHRoZSBjYXNlLiBgc3Rfc2VnbWVudHNgIGxvb3BzIHRocm91Z2ggYSBtYXRyaXggb2YgY29vcmRpbmF0ZXMgY3JlYXRlZCBieSBgc3RfY29vcmRpbmF0ZXNgIGFuZCBtYWtlcyBsaW5lc3RyaW5ncyBpbiBzaW1pbGFyIGZhc2hpb24gdG8gaG93IGBsZWFkYCB3YXMgdXNlZCBpbiB0aGUgZmlyc3QuYHN0X2Nvb3JkaW5hdGVzYCBhZGRzIGEgdW5pcXVlIElEIGZvciBlYWNoIGZlYXR1cmUgYW5kIEkgdXNlZCB0aGlzIElEIGluIHRoZSBsb29wIHRvIGZpcnN0IGZpbHRlciB0aGUgbWF0cml4IGFuZCB0aGVuIGNyZWF0ZSBsaW5lc3RyaW5ncyB1c2luZyB0aGUgcG9pbnRzIHRoYXQgbWFrZSB1cCBlYWNoIGZlYXR1cmUgaW4gYSBuZXN0ZWQgbG9vcC4gV2l0aCBlYWNoIGl0ZXJhdGlvbiBvZiB0aGlzIG5lc3RlZCBsb29wLCBhIGxpbmUgaXMgY3JlYXRlZCBmcm9tIHRoZSBjb29yZGluYXRlcyBpbiBvbmUgcm93IGFuZCB0aGUgcm93IGJlbG93IGl0LiBBbmQganVzdCBhcyBpcyBkb25lIHdpdGggYGZpbHRlcighc3RfaXNfZW1wdHkoZ2VvbTIpKWAsIHRoZSBsYXN0IHJvdyBvZiBlYWNoIGdyb3VwIGFyZSBleGNsdWRlZCBhcyB0aGV5IGVzc2VudGlhbGx5IGhhdmUgZW1wdHkgZ2VvbWV0cmllcy4gVGhlIGxpbmVzdHJpbmdzIGNyZWF0ZWQgaW4gdGhlIGxvb3AgYXJlIGNvbnZlcnRlZCB0byB3ZWxsLWtub3duIGJpbmFyeSBhbmQgc3RvcmVkIGluIGEgbGlzdC4gVGhpcyBsaXN0IGNhbiBiZSBlYXNpbHkgbWFkZSB0byBhIHNpbXBsZSBmZWF0dXJlIGNvbGxlY3Rpb24gdXNpbmcgW2BzdF9hc19zZmNgXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vc2YvcmVmZXJlbmNlL3N0X2FzX3NmYy5odG1sKS4gVGhlcmUgd2VyZSBvdGhlciB2ZXJzaW9ucyBvZiB0aGlzIHdoZXJlIEkgbWFkZSBsaW5lc3RyaW5ncyB3ZWxsLWtub3duIHRleHQgYW5kIHN0b3JlZCB0aGVtIGluIGVpdGhlciBhIG1hdHJpeCBvciBhIGxpc3QsIHRob3VnaCBXS0Igc2VlbWVkIGJlIGJlIHRoZSBiZXR0ZXIgY2hvaWNlIGFuZCBsZXNzIHdhcyBuZWVkZWQgdG8gYmUgZG9uZSB0byBjb252ZXJ0IGl0IHRvIGEgc2ltcGxlIGZlYXR1cmUgY29sbGVjdGlvbi4gVGhlIHNlY29uZCBsb29wIHJlbW92ZXMgb25lIHZhbHVlIGZyb20gZWFjaCBvZiB0aGUgZ3JvdXBzIGluIGEgbWF0cml4IG9mIGZlYXR1cmUgSURzIHRha2VuIGZyb20gdGhlIGNvb3JkaW5hdGUgbWF0cml4LCB3aGljaCBpcyBhbHNvICBzaW1pbGFyIHRvIHdoYXQgaXMgZG9uZSB3aXRoIGBmaWx0ZXIoIXN0X2lzX2VtcHR5KGdlb20yKSlgLiBUaGlzIElEIG1hdHJpeCBpcyB0aGVuIHVzZWQgdG8gam9pbiBmaWVsZHMuIFRoaXMgZnVuY3Rpb24gc29sdmVzIHRoZSBwc2V1ZG8gcHJvYmxlbSBvZiBkZXBlbmRpbmcgb24gYGRwbHlyYCAgYW5kIGBwdXJycmAsIGFuZCB0aGUgcmVzdWx0IHN0aWxsIGhhcyB0aGUgb3JpZ2luYWwgZmllbGRzLiBgc3Rfc2VnbWVudHNgIGlzIGFsc28gYWJsZSB0byBicmVhayBwb2x5Z29ucyBpbnRvIGxpbmVzdHJpbmdzLCBzb21ldGhpbmcgd2hpY2ggbXkgZmlyc3QgdHdvIGF0dGVtcHRzIGF0IHRoZSBmdW5jdGlvbiB3ZXJlIHVuYWJsZSB0byBkby4gDQoNCmBgYHtyIG9zbSBkZW1vbnN0cmF0aW9uLG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD05fQ0KbGlicmFyeShvc21kYXRhKQ0KDQpiYm94ID0gc3RfYmJveChzdF90cmFuc2Zvcm0obWlkZCw0MzI2KSkNCg0Kb3NtX2Jib3ggPSBtYXRyaXgoZGF0YSA9IGJib3gsbnJvdyA9IDIsbmNvbCA9IDIpDQoNCmJ1aWxkaW5ncz0gb3NtZGF0YTo6b3BxKGJib3gpICU+JSANCiAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICJidWlsZGluZyIpICU+JSANCiAgb3NtZGF0YV9zZigpICU+JSANCiAgLiRvc21fcG9seWdvbnMgJT4lIA0KICBzZWxlY3QoMTo1KSAlPiUgDQogIGZpbHRlcighaXMubmEobmFtZSkpDQoNCm1hcHZpZXcoYnVpbGRpbmdzLCBsYXllci5uYW1lID0gIlBvbHlnb25zIiwgY29sLnJlZ2lvbnMgPSAgIiMwRDM5NUYiKSArIG1hcHZpZXcoc3Rfc2VnbWVudHMoYnVpbGRpbmdzKSxsYXllci5uYW1lID0gIkxpbmVzdHJpbmdzIiwgY29sb3IgPSAiIzRDNEI0QyIpDQpgYGANCg0KQWx0aG91Z2ggYHN0X3NlZ21lbnRzYCBpcyBhbiBpbXByb3ZlbWVudCBpbiBteSBib29rcywgSSBzdGlsbCBuZWVkIHRvIHRvIHNlZSBpZiB1c2luZyBvbmx5IGBzZmAgYW5kIGJhc2UgUiBjb21lcyBhdCBjb3N0LiANCmBgYHtyIHN0X3NlZ2VtZW50cyBiZW5jaG1hcmssIG1lc3NhZ2U9Rix3YXJuaW5nPUZ9DQpiZW5jaDo6bWFyaygNCiAgbm5nZW8gPSAgc3RfZ2VvbWV0cnkobm5nZW86OnN0X3NlZ21lbnRzKHN0cmVldHMsIHByb2dyZXNzID0gRikpLA0KICAiZmlyc3QgYXR0ZW1wdCAiID0gc3RfZ2VvbWV0cnkoZmlyc3RfYXR0ZW1wdChzdHJlZXRzKSksDQogICJzZWNvbmQgYXR0ZW1wdCIgPSBzdF9nZW9tZXRyeShzZWNvbmRfYXR0ZW1wdChzdHJlZXRzKSksDQogIGZpbmFsID0gc3RfZ2VvbWV0cnkoc3Rfc2VnbWVudHMoc3RyZWV0cykpLA0KICBpdGVyYXRpb25zID0gNQ0KKSAlPiUgDQogIG11dGF0ZShuYW1lID0gYXMuY2hhcmFjdGVyKGV4cHJlc3Npb24pLA0KICAgICAgICAgbWF4ID0gYmVuY2g6OmFzX2JlbmNoX3RpbWUobGFwcGx5KHRpbWUsIG1heCkpKSAlPiUgDQogIHNlbGVjdChuYW1lLCBtaW4sIG1lZGlhbiwgbWF4LCBpdGVyYXRpb25zID0gbl9pdHIsIHRvdGFsX3RpbWUpDQpgYGANCmFzIGl0IHR1cm5zIG91dCwgYHN0X3NlZ2VtZW50c2AgaXMgc2lnbmlmaWNhbnRseSBmYXN0ZXIgdGhhbiB0aGUgZnVuY3Rpb24gZm91bmQgaW4gYG5uZ2VvYCBhcyB3ZWxsIGFzIG15IGZpcnN0IHR3byBhdHRlbXB0cywgYW5kIHRoaXMgaW5jcmVhc2UgaW4gc3BlZWQgZG9lcyBub3QgY29tZSBhdCB0aGUgY29zdCBvZiB0aGUgZmllbGRzIGFzIHdhcyBzZWVuIGluIHRoZSBmaXJzdCBhdHRlbXB0LiBPbmUgaXNzdWUsIGhvd2V2ZXIsIGlzIHRoYXQgYHN0X3NlZ21lbnRzYCBjYW4gb25seSB0YWtlIGdlb21ldHJpZXMgdGhhdCBhcmUgZWl0aGVyIHBvbHlnb25zIG9yIGxpbmVzdHJpbmdzLCB0aG91Z2ggdGhpcyBjYW4gZWFzaWx5IGJlIGZpeGVkIGJ5IHVzaW5nIHNvbWV0aGluZyBsaWtlIFtgc3RfbXVsdGlwYXJ0X3RvX3NpbmdsZXBhcnRzYF0oaHR0cHM6Ly9rdWZyZXUuZ2l0aHViLmlvL211c2luZ3Mvc3RfbXVsdGlwYXJ0X3RvX3NpbmdsZXBhcnRzL3N0X211bHRpcGFydF90b19zaW5nbGVwYXJ0cy5tZCkgYmVmb3JlaGFuZCBvciBieSBpbmNvcnBvcmF0aW5nIGl0IGluIGBzdF9zZWdtZW50c2AuIEkgY291bGQgY2FsbCBpdCBxdWl0cyBoZXJlIGZvciBub3cuIEFsdGhvdWdoIEkgc2FpZCB0aGF0IEkgd2FzIHN0cml2aW5nIHRvd2FyZCBvbmx5IHVzaW5nIGJhc2UgUiBhbmQgYHNmYCwgdGhlIGxhc3QgbG9vcCBhbmQgYG1lcmdlYCAgY2FuIGVhc2lseSBiZSBkb25lIHdpdGggYGRwbHlyYCwgYGRhdGEudGFibGVgLCBvciB0aGUgZnVuY3Rpb24gYGJ5YCwgYW5kIHBvc3NpYmx5IGV2ZW4gZG9uZSBmYXN0ZXIuIEJlY2F1c2Ugb2YgdGhpcywgSSB3ZW50IG9uIHRvIHRlc3QgaWYgdGhpcyB3YXMgdGhlIGNhc2UuIA0KDQpgYGB7ciB0aGUgYWx0ZXJuYXRpdmVzLCBtZXNzYWdlPUYsd2FybmluZz1GfQ0KIyMjIyBkYXRhLnRhYmxlICMjIyMNCnN0X3NlZ21lbnRzX2R0ID0gZnVuY3Rpb24oeCwgbWF4X2xlbmd0aCkgew0KICBsaWJyYXJ5KGRhdGEudGFibGUpDQogIA0KICBuZXQgPSB4DQogIG5ldCRpZCA9IDE6bnJvdyhuZXQpDQogIA0KICBpZiAoIW1pc3NpbmcobWF4X2xlbmd0aCkpIHsNCiAgICBuZXQgPSBzdF9zZWdtZW50aXplKG5ldCwgbWF4X2xlbmd0aCkNCiAgfQ0KICANCiAgY29vcmQgPSBzdF9jb29yZGluYXRlcyhuZXQpDQogIGNvbCA9IG5jb2woY29vcmQpDQogIGlkcyA9IGNvb3JkWywgY29sXQ0KICBmZWF0ID0gdW5pcXVlKGlkcykNCiAgc2VnbWVudHMgPSBsaXN0KCkNCiAgDQogIGZvciAoaSBpbiBmZWF0KSB7DQogICAgY29vcmRzID0gY29vcmRbaWRzID09IGksIF0NCiAgICBmb3IgKGogaW4gMToobnJvdyhjb29yZHMpIC0gMSkpIHsNCiAgICAgIHNlZ21lbnRzW1tsZW5ndGgoc2VnbWVudHMpICsgMV1dID0gc3RfYXNfYmluYXJ5KHN0X2xpbmVzdHJpbmcoY29vcmRzW2o6KGogKyAxKSwgMToyXSkpDQogICAgfQ0KICB9DQogIA0KICBmaWQgPSBhcy5kYXRhLnRhYmxlKGlkcykNCiAgY29sbmFtZXMoZmlkKSA9ICJpZCINCiAgc2V0a2V5KGZpZCwgaWQpDQogIGZpZCA9IGZpZFtmaWRbLCAuSVstMV0sIGJ5ID0gaWRdJFYxXVthcy5kYXRhLnRhYmxlKHN0X2Ryb3BfZ2VvbWV0cnkobmV0KSksIG9uID0gImlkIl0NCiAgZmlkWywgYDo9YChnZW9tZXRyeSA9IHN0X2FzX3NmYyhzZWdtZW50cywgY3JzID0gc3RfY3JzKG5ldCkpLCBpZCA9IE5VTEwpXQ0KICBzdF9zZihmaWQpDQp9DQoNCiMjIyMgZHBseXIgIyMjIw0Kc3Rfc2VnbWVudHNfdGJsID0gZnVuY3Rpb24oeCwgbWF4X2xlbmd0aCkgew0KICBsaWJyYXJ5KHNmKQ0KICBsaWJyYXJ5KGRwbHlyKQ0KICANCiAgbmV0ID0gbXV0YXRlKHgsIHZhbHVlID0gcm93X251bWJlcigpKQ0KICANCiAgaWYgKCFtaXNzaW5nKG1heF9sZW5ndGgpKSB7DQogICAgbmV0ID0gc3Rfc2VnbWVudGl6ZShuZXQsIG1heF9sZW5ndGgpDQogIH0NCiAgDQogIGNvb3JkID0gc3RfY29vcmRpbmF0ZXMobmV0KQ0KICBjb2wgPSBuY29sKGNvb3JkKQ0KICBpZHMgPSBjb29yZFssIGNvbF0NCiAgZmVhdCA9IHVuaXF1ZShpZHMpDQogIHNlZ21lbnRzID0gbGlzdCgpDQogIA0KICBmb3IgKGkgaW4gZmVhdCkgew0KICAgIGNvb3JkcyA9IGNvb3JkW2lkcyA9PSBpLCBdDQogICAgZm9yIChqIGluIDE6KG5yb3coY29vcmRzKSAtIDEpKSB7DQogICAgICBzZWdtZW50c1tbbGVuZ3RoKHNlZ21lbnRzKSArIDFdXSA9IHN0X2FzX2JpbmFyeShzdF9saW5lc3RyaW5nKGNvb3Jkc1tqOihqICsgMSksIDE6Ml0pKQ0KICAgIH0NCiAgfQ0KICANCiAgYXNfdGliYmxlKGlkcykgJT4lDQogICAgZ3JvdXBfYnkodmFsdWUpICU+JQ0KICAgIHNsaWNlKC0xKSAlPiUNCiAgICB1bmdyb3VwICU+JQ0KICAgIGxlZnRfam9pbihzdF9kcm9wX2dlb21ldHJ5KG5ldCksIGJ5ID0gInZhbHVlIikgJT4lDQogICAgc2VsZWN0KC0xKSAlPiUNCiAgICBtdXRhdGUoZ2VvbWV0cnkgPSBzdF9hc19zZmMoc2VnbWVudHMsIGNycyA9IHN0X2NycyhuZXQpKSkgJT4lIHN0X3NmKCkNCn0NCg0KIyMjIyB1c2luZyBieSAjIyMjDQpzdF9zZWdtZW50c19ieSA9IGZ1bmN0aW9uKHgsIG1heF9sZW5ndGgpIHsNCiAgbGlicmFyeShzZikNCiAgDQogIG5ldCA9IHgNCiAgbmV0JGZpZCA9IGZhY3RvcigxOm5yb3cobmV0KSwgbGV2ZWxzID0gMTpucm93KG5ldCkpDQogIA0KICBpZiAoIW1pc3NpbmcobWF4X2xlbmd0aCkpIHsNCiAgICBuZXQgPSBzdF9zZWdtZW50aXplKG5ldCwgbWF4X2xlbmd0aCkNCiAgfQ0KICANCiAgY29vcmQgPSBzdF9jb29yZGluYXRlcyhuZXQpDQogIGNvbCA9IG5jb2woY29vcmQpDQogIGlkcyA9IGNvb3JkWywgY29sXQ0KICBmZWF0ID0gdW5pcXVlKGlkcykNCiAgc2VnbWVudHMgPSBsaXN0KCkNCiAgDQogIGZvciAoaSBpbiBmZWF0KSB7DQogICAgY29vcmRzID0gY29vcmRbaWRzID09IGksIF0NCiAgICBmb3IgKGogaW4gMToobnJvdyhjb29yZHMpIC0gMSkpIHsNCiAgICAgIHNlZ21lbnRzW1tsZW5ndGgoc2VnbWVudHMpICsgMV1dID0gc3RfYXNfYmluYXJ5KHN0X2xpbmVzdHJpbmcoY29vcmRzW2o6KGogKyAxKSwgMToyXSkpDQogICAgfQ0KICB9DQogIA0KIGZpZCA9IGFzLmRhdGEuZnJhbWUoaWRzKQ0KIGNvbG5hbWVzKGZpZCkgPSAiZmlkIg0KIGZpZCRmaWQgPSBmYWN0b3IoZmlkJGZpZCwgbGV2ZWxzID0gdW5pcXVlKGZpZCRmaWQpKQ0KIGZpZCA9IGRvLmNhbGwocmJpbmQsIGJ5KGZpZCwgZmlkJGZpZCwgZnVuY3Rpb24oeCkgeFstMSwsIGRyb3AgPSBGXSwgc2ltcGxpZnkgPSBGKSkNCiBmaWQgPSBtZXJnZShmaWQsIHN0X2Ryb3BfZ2VvbWV0cnkobmV0KSwgYWxsLnggPSBUKVssIC0xLCBkcm9wID0gRl0NCiBmaWQkZ2VvbWV0cnkgPSBzdF9hc19zZmMoc2VnbWVudHMsIGNycyA9IHN0X2NycyhuZXQpKQ0KIHN0X3NmKGZpZCkNCn0NCmBgYA0KVGhlc2UgdGhyZWUgZnVuY3Rpb25zIGFyZSBlc3NlbnRpYWxseSB0aGUgc2FtZSBzYXZlIGZvciBob3cgdGhleSBkZWFsIHdpdGggdGhlIGFic2VuY2Ugb2YgdGhlIHNlY29uZCBsb29wLiBXaGF0IGlzIG5vdCB0aGUgc2FtZSwgaG93ZXZlciwgaXMgdGhlaXIgc3BlZWQuDQpgYGB7ciBhbHRzIGJlbmNobWFyayxtZXNzYWdlPUYsd2FybmluZz1GfQ0KYmVuY2g6Om1hcmsoDQogICJiYXNlIChzdF9zZWdtZW50cykiID0gc3RfZ2VvbWV0cnkoc3Rfc2VnbWVudHMoc3RyZWV0cywxKSksDQogICJiYXNlICh1c2luZyBieSkiID0gc3RfZ2VvbWV0cnkoc3Rfc2VnbWVudHNfYnkoc3RyZWV0cywxKSksDQogIGRhdGEudGFibGUgPSBzdF9nZW9tZXRyeShzdF9zZWdtZW50c19kdChzdHJlZXRzLDEpKSwNCiAgZHBseXIgPSBzdF9nZW9tZXRyeShzdF9zZWdtZW50c190Ymwoc3RyZWV0cywxKSksDQogIGl0ZXJhdGlvbnMgPSA1DQopICU+JQ0KICBtdXRhdGUobmFtZSA9IGFzLmNoYXJhY3RlcihleHByZXNzaW9uKSwNCiAgICAgICAgIG1heCA9IGJlbmNoOjphc19iZW5jaF90aW1lKGxhcHBseSh0aW1lLCBtYXgpKSkgJT4lDQogIHNlbGVjdChuYW1lLCBtaW4sIG1lZGlhbiwgbWF4LCBpdGVyYXRpb25zID0gbl9pdHIsIHRvdGFsX3RpbWUpDQpgYGANCg0KVGhpcyB0aW1lIGFyb3VuZCBJIHVzZWQgdGhlIHNlY29uZCBwYXJhbWV0ZXIgb2YgdGhlc2UgZnVuY3Rpb25zIHRvIHNlZSBob3cgdGhleSBoYW5kbGUgc29tZXdoYXQgbGFyZ2VyIGRhdGFzZXRzLiBSYXRoZXIgdGhhbiBicmVha2luZyA0OTAgbGluZXN0cmluZ3MgaW50byA3LDQyMyBsaW5lIHNlZ21lbnRzLCB0aGUgbWF4IGxlbmd0aCBvZiBlYWNoIGxpbmUgc2VnbWVudCBpcyBzZXQgdG8gb25lIG1ldGVyIGluIHRoaXMgYmVuY2htYXJrIGFuZCB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIGluY3JlYXNlcyBmcm9tIDQ5MCB0byAyMjUsMTE4LiBXaGF0IGlzIGludGVyZXN0aW5nIHRvIHNlZSB3aXRoIHRoaXMgYmVuY2htYXJrIGlzIHRoYXQgYHN0X3NlZ21lbnRzYCBpcyBjb21wYXJhYmxlIGluIHNwZWVkIHRvIGJvdGggdGhlIGBkYXRhLnRhYmxlYCBhbmQgYGRwbHlyYCB2ZXJzaW9ucyBvZiB0aGUgZnVuY3Rpb24gYW5kIGlzIHNsaWdodGx5IGZhc3RlciB0aGFuIHdoZW4gdGhlIGxhc3QgbG9vcCBpcyByZXBsYWNlZCBieSBgYnlgLiBJdCBpcyBhbHNvIHN1cnByaXNpbmcgIHRoYXQgaW4gdGhpcyBzaXR1YXRpb24sIGBkcGx5cmAgaXMgZXZlciBzbyBzbGlnaHRseSBmYXN0ZXIgdGhhbiBgZGF0YS50YWJsZWAsIHRob3VnaCB0aGlzIG1heSBiZSBkdWUgdG8gbXkgaW5leHBlcmllbmNlIHdpdGggdGhlIGxhdHRlciBhbmQgd2hhdCB3YXMgYmVpbmcgZG9uZS4gSSBjb3VsZCB0cnkgdG8gc2VlIHdoYXQgc3BlZWQgaW1wcm92ZW1lbnQgY291bGQgYmUgaGFkIGlmIHRoZSBib3RoIGxvb3BzIHdlcmUgcmVwbGFjZWQgd2l0aCBgZGF0YS50YWJsZWAsIHRob3VnaCB0aGF0IGlzIGZvciBhbm90aGVyIHRpbWUuIEkgYWxyZWFkeSBzYXcgd2l0aCB0aGUgZmlyc3QgYW5kIHNlY29uZCBhdHRlbXB0cyB0aGF0IHdvcmtpbmcgY29tcGxldGVseSB3aXRoaW4gdGhlIGB0aWR5dmVyc2VgIHNsb3dlZCB0aGluZ3MgZG93bi4gR2l2ZW4gdGhlIHJlc3VsdHMgb2YgdGhlIGJlbmNobWFya3MsIEkgY291bGQganVzdCB1c2UgYHN0X3NlZ21lbnRzYCBpbiBpdHMgY3VycmVudCBzdGF0ZS4gSW4gYWxsLCBpdCB3YXMgZnVuIHRvIHRha2UgYSBicmVhayBmcm9tIHRoaW5ncyBhbmQgc3BlbmQgdGltZSB0byBzb2x2ZSBhIFsic2ltcGxlIHlldCB0cmlja3kiXShodHRwOi8vYmxvZy5jbGV2ZXJlbGVwaGFudC5jYS8yMDE1LzAyL2JyZWFraW5nLWxpbmVzdHJpbmctaW50by1zZWdtZW50cy5odG1sKSBwcm9ibGVtIGluIFIuICAgIA0KDQo=