Based on Groningen Spatial Analysis Workshop - Session 6: Spatial Interaction Models By Chris Brunsdon and Lex Comber
What are spatial interaction models?
Spatial interaction models are mathematical models which , in general terms, deal with flows and movement between places, based on:
suppressWarnings(if(!require("pacman")) install.packages("pacman"))
## Loading required package: pacman
pacman::p_load('tidyverse', 'sf', 'tmap')
Download files, unzip them and read them in R.
- sf::st_read(): Read simple features or layers from file or database
library(here)
# Download file from the internet
#download.file("https://www.dropbox.com/s/8yz4r73v9zmugu7/groningen_polys.zip?dl=1","groningen_polys.zip")
# Extract files
unzip("groningen_polys.zip")
# Read simple features
gron_polys <- st_read(".", "groningen_polys")
# Download file
#download.file("https://www.dropbox.com/s/t6mjbycebknzjd4/shops_pts.zip?dl=1","shops_pts.zip")
# Extract files
unzip("shops_pts.zip")
# Read sf
gron_shops <- st_read(".", "shops_pts")
#download.file("https://www.dropbox.com/s/frriyfou3ujj998/groningen_pts.zip?dl=1","groningen_pts.zip")
unzip("groningen_pts.zip")
gron_pts <- st_read(".","groningen_pts")
Plot administrative areas in Groningen
- The first element always is
tm_shape()
, which specified the input shape object. Next, map layers, additional map elements, and overall layout can be customized. — Great like ggplot!
# View data set
gron_polys %>%
slice_head(n = 5)
# Set tmap mode to interactive viewing
tmap_mode(mode = "view")
## tmap mode set to interactive viewing
# Make map
gron_polys %>%
tm_shape() +
tm_borders(col = "black", lwd = 2, alpha = 0.7) +
tm_fill(col = "midnightblue", alpha = 0.2)
Plot the stores and their respective floor areas
## Legend for symbol sizes not available in view mode.
A third data set relates to the administrative areas, but as a set of points - this is useful to compute ‘crow fly’ distances between the administrative areas and the shops. These data sets are called:
gron_pts
- label points for the administrative areas
gron_polys
- polygons for the administrative areas
gron_shops
- point locations for the stores
Let’s view the gron_pts
data
The variables here are the population AANT_INW
- and the potential spending for each administrative unit Money
- this is a simple estimate of €20 per person. The polygons object contains the same information - it just uses polygon geometry instead of point.
Thinking about distances
The data we currently have tells you where the stores are, and where the population is located. It also tells you how much spending power is in each place, and how attractive each store is (measured by floor space). To predict interaction the final piece in the jigsaw is the distance between the stores and the population. In broad terms, the interaction between place \(i\) and store \(j\) (annotated here is \(I_{ij}\)) is modelled as
\[
I_{ij} = f(P_i,A_j,d_{ij})
\]
where \(P_i\) is the productiveness in place \(i\) (Money
),
\(A_j\) is the attractiveness of store \(j\) (Floorspace
)
and \(d_{ij}\) is the distance between the two.
So we need to compute the distances. One way of doing this is to extract the point coordinates from gron_pts
and gron_shops
and compute the straight line (crow fly) distances between each pair, using Pythagoras’ theorem. The following function does this.
st_coordinates {sf}: retrieve coordinates in matrix form
base::outer
: The outer product of the arrays X and Y is the array A with dimension c(dim(X), dim(Y))
\[
d_{ji} = \sqrt (shop_jx - place_ix)^2 +(shop_jy - place_iy)^2
\]
# The first step would be to extract the distances from each df
# Retrieve coordinates in matrix form
shop_coord <- st_coordinates(gron_shops)
rownames(shop_coord) <- gron_shops$shopcode
area_coord <- st_coordinates(gron_pts)
rownames(area_coord) <- gron_pts$BU_CODE
# Compute euclidean distance between each shop coordinate and place in KM (x/1000)
dmat <- sqrt(outer(shop_coord[, 1], area_coord[, 1], FUN = "-")^2 + outer(shop_coord[, 2], area_coord[, 2], FUN = "-")^2)/1000
# Putting this in a function
get_dist <- function(gron_shops, gron_pts){
shop_coord <- st_coordinates(gron_shops)
rownames(shop_coord) <- gron_shops$shopcode
area_coord <- st_coordinates(gron_pts)
rownames(area_coord) <- gron_pts$BU_CODE
dmat <- sqrt(outer(shop_coord[, 1], area_coord[, 1], FUN = "-")^2 + outer(shop_coord[, 2], area_coord[, 2], FUN = "-")^2)/1000
return(dmat)
}
Now we have a distance matrix, but to merge it with the rest of the information, it would be better to have this in tidy format - essentially in the format of a data frame, with three columns - the store ID, the administrative area ID and the distance between the two. This is useful because it may then be used as a look-up table for a given store, or a given administrative area, to find distances. To do this, first convert the matrix object into a data frame, and add a variable column based on the row name - this gives the shop code. Here we use tidyverse
-style pipelining to ‘mutate’ the data frame.
# Convert matrix to a tibble
dist_tab <- as_tibble(gron_shops) %>%
select(shopcode) %>%
bind_cols(as_tibble(dmat))
dist_tab %>%
slice_head(n = 5)
This is still not in the format mentioned earlier, as each origin has its own column. To re-shape the data into three columns the pivot_longer
command is needed. This takes data spread over several columns (here all of the origins) and ‘gathers’ them into a single column. The column names get a new variable, and the distance values are stacked.
- Basically putting all the places columns under 1 column instead of them being spread out in 108 columns.
dist_tab %>%
pivot_longer(!shopcode, names_to = "BU_CODE", values_to = "Dist") -> dist_tab
# View dist mat
dist_tab %>%
slice_head(n = 5)
Next, we want to join the attractiveness and productiveness variables onto this list, using database join-type commands that are provided by the left_join
function. First off, join the attractiveness information from gron_shops
:
- By default use a left_join: includes all rows in x.
dist_tab %>%
left_join(gron_shops, by = "shopcode") %>%
left_join(gron_pts, by = "BU_CODE") %>%
select(-starts_with("geometry")) -> dist_tab
# View data with all model variables
dist_tab %>%
slice_head(n = 5)
So now dist_tab
has a series of rows corresponding to each \(i,j\) pair, with the values of \(d_{ij}\) \(P_i\) and \(A_j\) among the variables -
- \(d_{ij}\) is
Dist
- \(P_i\) is
Money
- \(A_j\) is
Floorspace
We can now actually carry out the modelling.
Fitting the model
Before we noted that the spatial interaction model takes the form \(I_{ij} = f(P_i,A_j,d_{ij})\) - here, we will use a specific, multiplicative format:
\[
I_{ij} = \textsf{const}_j \times A_i^\alpha g(d_{ij})
\] where \(g\) is a decaying desirability function of distance. Note that the productiveness does not appear explicitly here - in fact it features as a constraint - essentially for each administrative area, the total money going to all of the stores must add up to the total money for that area. This is achieved by multiplying the attractiveness and distance-deterrence terms by a constant for each area - the constant re-scales the amount spent so that the total for the area is equal to the variable Money
for that area.
The form that \(g\) takes here will be that of exponential decay. Often this is written as \(e^{-\textsf{const} \times d_{ij}}\) but here we will use the form
\[
g(d_{ij}) = 2^{-d_{ij}/h}
\] where \(h\) is the distance half-life. That is, for every \(h\) kilometers between the store and the home location, the desirability of making the trip halves. Although this can be written in more conventional exponential form as \(e^{-\log(2)d_{ij}/h}\), the parameter \(h\) is more easily interpreted here. Thus the full model is
\[
I_{ij} = \textsf{const}_j A_i^\alpha 2^{-d_{ij}/h}
\]
Having arrived at the equation we now need to compute this and add a column to the dist_tab
data frame to model the \(I_{ij}\) values. These will be the estimated expenditure for the population of area \(j\) at store \(i\). For a starting point, assume a fairly strong distance deterrence with a distance half-life of 200m. We can change this later.
The first part of the model (before applying the production constraint) is to compute \(A_i^\alpha 2^{-d_ij/h}\). \(\alpha\) here allows for extra attractiveness of the larger stores. In this case it is assumed to be quite close to 1 - say 1.05 - so that larger stores have a slightly disproportionate increase in attractiveness, but the effect is fairly weak.
dist_tab %>%
mutate(Flow_Factor=(Floorspace^1.05)*2^(-Dist/0.2)) -> sim_tab
head(sim_tab)
This has the Flow_Factor
variable proportional to the cash flow between \(i\) and \(j\) but as yet it hasn’t been scaled. To do this, we first need to rescale the flow factors for each administrative area so that they sum to one. Then, they are each multiplied by the Money
variable for that area, so that the total modelled flow of cash equals the total money coming from that area. This is done by using the sum
function. If the sim_tab
data frame is first grouped by the administrative unit code, (using group_by(BU_CODE)
) then the sum function for each row applies to the sum of values for its specific BU_CODE
- rather than the sum for the entire column. Thus
sim_tab %>%
group_by(BU_CODE) %>%
mutate(Cash_Flow = (Flow_Factor/sum(Flow_Factor)) * Money) -> sim_tab
sim_tab %>%
slice_head()
Thus we now have a data frame whose rows are the details of each \(i\) and \(j\) combination, including the modelled cash flow. We can first check that the constraint has worked:
- unique(Money) could have worked too. Not sure why distinct() is not working
sim_tab %>%
group_by(BU_CODE) %>%
summarise(out_flow = sum(Cash_Flow), area_money = first(Money))
Here the summarise
function creates a summary data frame, where statistics such as the sum
or mean
are computed for each group. Thus Out_Flow
is the sum of all of the cash flows coming out of each administrative area. Note that the Money
variable is the same for each member of the group in sim_tab
so taking the first
is just a way of summarising several repeated values with a single incidence of this value. Thus the output shows that the summed modelled cash flows from each adminstrative area is the same as the Money
for that area. The constraint has worked. Phew!
Now we are happy with the model we may want to see what happens for different half-life distances. As with the data mungeing we can express the modelling process as a single pipeline. But to save time we can also put it into a function, with the half-life as an argument.
fit_sim <- function(h, dist_tab) {
dist_tab %>%
mutate(Flow_Factor=(Floorspace^1.05)*2^(-Dist/h)) %>%
group_by(BU_CODE) %>%
mutate(Cash_Flow = Money*Flow_Factor/sum(Flow_Factor)) -> res
return(res)
}
# Try out a halflife of 750m
fit_sim(h = 0.75, dist_tab = dist_tab) %>%
slice_head()
Mapping the results
We now have a data frame sim_tab
that tabulates the results of the Spatial Interaction Model. The next step is to investigate the geographical patterns associated with this. Suppose we are mainly concerned with the AH/Albert Heijn
group of stores. One starting point might be to discover the total cash flows predicted into each of the stores - this is a two-stage process - firstly filter out these stores (based on text patterns in the shop name) - and then group by the stores and sum over them using the summarise
function. The pipeline is as below:
sim_tab %>%
filter(str_detect(shop, pattern = "AH|Heijn")) %>%
group_by(shopcode) %>%
summarise(in_flow = sum(Cash_Flow)) %>%
arrange(desc(in_flow)) -> inflow_tab
inflow_tab
For now, it is enough to note that ‘AH|Heijn’ picks out shop names with either ‘AH’ or ‘Heijn’ in them. However, although this tabulates the influx of cash, it doesn’t show a map. To get this, firstly join this table to the gron_shops
geographical object and then use tmap
to create a map.
inflow_tab %>%
left_join(gron_shops, by = "shopcode") %>%
# To make shop name appear on the hover, seems like the first column appears?
relocate(shop) %>%
st_sf() %>%
tm_shape() +
tm_dots(size = "in_flow", col = "darkorchid", alpha = 0.5)
## Legend for symbol sizes not available in view mode.
This depends on the distance half life. Once again we can encapsulate all of this in a function with \(h\) as the argument, making it easier to explore:
map_inflow <- function(h, dist_tab){
fit_sim(h, dist_tab) %>%
filter(str_detect(shop, pattern = "AH|Heijn")) %>%
group_by(shopcode) %>%
summarise(in_flow = sum(Cash_Flow)) %>%
arrange(desc(in_flow)) %>%
left_join(gron_shops, by = "shopcode") %>%
relocate(shop) %>%
st_sf() %>%
tm_shape() +
tm_dots(size = "in_flow", col = "darkorchid", alpha = 0.5)
}
map_inflow(h = 0.5, dist_tab = dist_tab)
## Legend for symbol sizes not available in view mode.
The above shows the effect if \(h\) is 500m - now check it for 1km:
map_inflow(h = 1, dist_tab = dist_tab)
## Legend for symbol sizes not available in view mode.
In a nutshell, as the distance half life gets larger, people are willing to travel further to stores, and the more attractive larger stores get a greater share of the money - people will travel to a more favourable store even when it is a long way away.
- In fact, money going to smaller stores decreases with increase in half life
Catchment Areas
The idea of a catchment area here is to identify, for each administrative unit, what is the most attractive store. This is done by considering the cash flow out of each unit to each of the stores and seeing which store gets the greatest share of the total outflow from the unit. In R (and in tidyverse
terms) this is done using the rank
function. Rank takes a variable and for each row, returns the rank of the value for that row, out of all of the rows. The ranks are in ascending order, so for \(n\) items the largest has the value \(n\).
To find the ranks of the outflow shares on an administrative unit by adminstrative unit basis, group_by
is a helper function again. In this case it causes the rank function to return the relative ranks of the flow factors (\(A_i^\alpha 2^{-d_{ij}/h}\)) within each group. Finally, it would by useful to have ranks in descending order - you are interested in the maximum in each group, and so rather than trying to find ranks of \(n\) when you don’t know what \(n\) is, it is more useful to find ranks of 1. Changing the sign of the variable (by using desc()
) given to the rank function achieves this - essentially it flips the orders of the variables. Check the results here:
sim_tab %>%
group_by(BU_CODE) %>%
mutate(rank = row_number(desc(Flow_Factor))) %>%
filter(rank == 1) %>%
arrange(desc(Cash_Flow)) -> rank_tab
rank_tab %>%
slice_head()
There is now a unique row for each administrative unit, and the shop also appearing in that row is the one where most money flows - and this states which catchment area the administrative unit is in. To map these, firstly join this table to the groningen_polys
object:
rank_tab %>%
left_join(gron_polys) %>%
relocate(BU_CODE) %>%
st_sf() %>%
tm_shape() +
tm_polygons(col = "shopcode")
## Joining, by = c("BU_CODE", "AANT_INW", "Money")
We can further verify this by looking at the proportion which each shop serves:
rank_tab %>%
left_join(gron_polys) %>%
group_by(shopcode) %>%
summarise(n = n()) %>%
ungroup() %>%
arrange(desc(n))
## Joining, by = c("BU_CODE", "AANT_INW", "Money")
A further useful technique would be to create dissolved catchment polygons. This essentially involves removing the boundaries between administrative unit polygons assigned to the same store. This is done by applying a group_by
operation to a simple features object and then a summarise
- this merges all of the polygons in the same group. Here there is no summary variable, we just do this to make the merge:
rank_tab %>%
left_join(gron_polys) %>%
st_sf() %>%
group_by(shopcode) %>%
summarise() %>%
tm_shape() +
tm_polygons(col = "shopcode")
## Joining, by = c("BU_CODE", "AANT_INW", "Money")
As before - all of this could be made into a small number of larger pipelines and encapsulated in a function of \(h\):
map_catchments <- function(h, dist_tab) {
fit_sim(h, dist_tab) %>%
filter(str_detect(shop, pattern = "AH|Heijn")) %>%
group_by(BU_CODE) %>%
mutate(rank = row_number(desc(Flow_Factor))) %>%
filter(rank == 1) -> rank_tab
rank_tab %>%
left_join(gron_polys) %>%
st_sf() %>%
group_by(shopcode) %>%
summarise() %>%
tm_shape() +
tm_polygons(col = "shopcode")
}
map_catchments(0.1, dist_tab)
## Joining, by = c("BU_CODE", "AANT_INW", "Money")
Now you can again experiment with different \(h\) values - for example 750m:
map_catchments(0.75, dist_tab)
## Joining, by = c("BU_CODE", "AANT_INW", "Money")
Assessing the impact of a proposed store.
Suppose now a new store was proposed with a floor space of 5000 square meters. Let’s see where it would range:
gron_shops %>%
slice_max(order_by = Floorspace, n = 5)
Therefore a fairly large store!
How could the effect of this be allowed for? The main way to see the impact is to add the new store to the gron_shops
spatial layer, and to re-run the model with this extra store. This can then be compared to the original model.
To see this, firstly download a new spatial data object proposed
:
#download.file("https://www.dropbox.com/s/qjzxl0cr7ehtrti/proposed.zip?dl=1","proposed.zip")
unzip("proposed.zip")
proposed_shop <- st_read(".","proposed")
## Reading layer `proposed' from data source
## `C:\Users\ADMIN\Desktop\AI Kenya\Compressed\Internship_prep'
## using driver `ESRI Shapefile'
## Simple feature collection with 1 feature and 3 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: 235494.2 ymin: 580729.9 xmax: 235494.2 ymax: 580729.9
## Projected CRS: Double_Stereographic
Then, have a look at its location - the proposed store is in red.
gron_shops %>%
tm_shape() +
tm_dots(size = "Floorspace", col = "darkorchid", alpha = 0.5) +
tm_shape(proposed_shop) +
tm_dots(size = "Floorspace", col = "indianred", alpha = 0.7)
## Legend for symbol sizes not available in view mode.
To re-run the model, again using a function is useful. Below is a function that encapsulates all of the steps in producing a model, from the provision of a shops object and an administrative area object as well as \(h\). The function looks intimidating, but really it is just a combination of the operations done above - the data munging and model fitting.
new_model <- function(admin,shops,h) {
# Get the distance
dmat <- get_dist(shops,admin)
# Pivot longer the data
as_tibble(shops) %>%
select(shopcode) %>%
bind_cols(as_tibble(dmat)) %>%
pivot_longer(!shopcode, names_to = "BU_CODE", values_to = "Dist") %>%
left_join(shops, by = "shopcode") %>%
left_join(admin, by = "BU_CODE") %>%
select(-starts_with("geometry")) -> dist_tab
# Find flow factor
dist_tab %>%
mutate(Flow_Factor=(Floorspace^1.05)*2^(-Dist/h)) %>%
group_by(BU_CODE) %>%
mutate(Cash_Flow = Money*Flow_Factor/sum(Flow_Factor)) -> res
return(res)
}
test <- new_model(gron_pts,gron_shops,0.3)
head(test)
Here the function takes the inputs and returns a data frame with the fitted model values, in the format of sim_tab
used earlier.
To re-fit thew model with a new store, firstly create a new scenario by combining the new proposed store with the existing ones in Groningen:
all_shops <- proposed_shop %>%
bind_rows(gron_shops)
all_shops %>%
slice_head(n = 5)
Then update the simulation table:
new_sim_tab <- new_model(gron_pts, all_shops, 0.2)
new_sim_tab %>%
slice_head()
We can now compute the inflows to all of the stores.
new_sim_tab %>%
filter(str_detect(shop, "AH|Heijn|New")) %>%
group_by(shopcode) %>%
summarise(in_flow = sum(Cash_Flow)) %>%
arrange(desc(in_flow)) -> new_inflow_tab
new_inflow_tab %>%
slice_head(n = 5)
and then map the inflows:
new_inflow_tab %>%
left_join(all_shops, by = "shopcode") %>%
st_sf() %>%
tm_shape() +
tm_dots(size = "in_flow", col = "darkgreen", alpha = 0.5)
## Legend for symbol sizes not available in view mode.
If we join the original inflow_tab
to the new_inflow_tab
using the shop code as a merge key we obtain a table with new and old cash inflow estimates, respectively called inflow.x
and inflow.y
.
The proposed store, and so doesn’t have an original inflow. Here it makes sense to set this as zero:
new_inflow_tab %>%
left_join(inflow_tab, by = "shopcode") %>%
# Replace a 0 anywhere there may be an NA
mutate(across(everything(), ~replace_na(.x, 0))) %>%
# Compute difference
mutate(Diff = (in_flow.x - in_flow.y)) -> diff_inflow_tab
# Which stores have been impacted most by the new store
diff_inflow_tab %>%
slice_max(order_by = abs(Diff), n = 5)
Finally to map this as proportional symbols, since some values are positive and others are negative, create an absolute value of change column - and then map this
diff_inflow_tab %>%
mutate(abs_diff = abs(Diff)) %>%
left_join(all_shops, by = "shopcode") %>%
relocate(shop) %>%
st_sf() %>%
tm_shape() +
tm_dots(size = "abs_diff", col = "Diff")
## Variable(s) "Diff" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.
## Legend for symbol sizes not available in view mode.
This helps to identify the stores that will experience the most impact if a new store is opened as proposed.
LS0tDQp0aXRsZTogJ0ludGVybnNoaXAgcHJlcDogU3BhdGlhbCBJbnRlcmFjdGlvbiBNb2RlbHMnDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBzdHlsZV83LmNzcw0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICBoaWdobGlnaHQ6IGJyZWV6ZWRhcmsNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgY29kZV9kb3dubG9hZDogeWVzDQotLS0NCg0KQmFzZWQgb24gR3JvbmluZ2VuIFNwYXRpYWwgQW5hbHlzaXMgV29ya3Nob3AgLSBTZXNzaW9uIDY6IFNwYXRpYWwgSW50ZXJhY3Rpb24gTW9kZWxzIEJ5IENocmlzIEJydW5zZG9uIGFuZCBMZXggQ29tYmVyDQoNCiMjIFdoYXQgYXJlIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxzPw0KDQpTcGF0aWFsIGludGVyYWN0aW9uIG1vZGVscyBhcmUgbWF0aGVtYXRpY2FsIG1vZGVscyB3aGljaCAsIGluIGdlbmVyYWwgdGVybXMsIGRlYWwgd2l0aCBmbG93cyBhbmQgbW92ZW1lbnQgYmV0d2VlbiBwbGFjZXMsIGJhc2VkIG9uOg0KDQotICAgdGhlaXIgc3BhdGlhbCBzZXBhcmF0aW9uDQoNCi0gICB0aGVpciBjb21wbGVtZW50YXJpdHkNCg0KLSAgIHdoYXRldmVyIG90aGVyIGludGVydmVuaW5nIG9wcG9ydHVuaXRpZXMgb3Igc3BhdGlhbCBzdHJ1Y3R1cmFsIGVsZW1lbnRzIHNlcnZlIHRvIGF1Z21lbnQgb3IgZGltaW5pc2ggdGhlIGV4cGVjdGVkIGZsb3cNCg0KYGBge3J9DQpzdXBwcmVzc1dhcm5pbmdzKGlmKCFyZXF1aXJlKCJwYWNtYW4iKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikpDQoNCnBhY21hbjo6cF9sb2FkKCd0aWR5dmVyc2UnLCAnc2YnLCAndG1hcCcpDQpgYGANCg0KRG93bmxvYWQgZmlsZXMsIHVuemlwIHRoZW0gYW5kIHJlYWQgdGhlbSBpbiBSLg0KDQotICAgc2Y6OnN0X3JlYWQoKTogUmVhZCBzaW1wbGUgZmVhdHVyZXMgb3IgbGF5ZXJzIGZyb20gZmlsZSBvciBkYXRhYmFzZQ0KDQpgYGB7ciBnZXRwb2x5cywgZWNobz1ULCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmxpYnJhcnkoaGVyZSkNCiMgRG93bmxvYWQgZmlsZSBmcm9tIHRoZSBpbnRlcm5ldA0KI2Rvd25sb2FkLmZpbGUoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvOHl6NHI3M3Y5em11Z3U3L2dyb25pbmdlbl9wb2x5cy56aXA/ZGw9MSIsImdyb25pbmdlbl9wb2x5cy56aXAiKQ0KDQojIEV4dHJhY3QgZmlsZXMNCnVuemlwKCJncm9uaW5nZW5fcG9seXMuemlwIikNCg0KIyBSZWFkIHNpbXBsZSBmZWF0dXJlcw0KZ3Jvbl9wb2x5cyA8LSBzdF9yZWFkKCIuIiwgImdyb25pbmdlbl9wb2x5cyIpDQoNCiMgRG93bmxvYWQgZmlsZQ0KI2Rvd25sb2FkLmZpbGUoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvdDZtamJ5Y2Via256amQ0L3Nob3BzX3B0cy56aXA/ZGw9MSIsInNob3BzX3B0cy56aXAiKQ0KDQojIEV4dHJhY3QgZmlsZXMNCnVuemlwKCJzaG9wc19wdHMuemlwIikNCg0KIyBSZWFkIHNmIA0KZ3Jvbl9zaG9wcyA8LSBzdF9yZWFkKCIuIiwgInNob3BzX3B0cyIpDQoNCiNkb3dubG9hZC5maWxlKCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL2Zycml5Zm91M3Vqajk5OC9ncm9uaW5nZW5fcHRzLnppcD9kbD0xIiwiZ3JvbmluZ2VuX3B0cy56aXAiKQ0KdW56aXAoImdyb25pbmdlbl9wdHMuemlwIikNCmdyb25fcHRzIDwtIHN0X3JlYWQoIi4iLCJncm9uaW5nZW5fcHRzIikNCmBgYA0KDQpQbG90IGFkbWluaXN0cmF0aXZlIGFyZWFzIGluIEdyb25pbmdlbg0KDQotICAgVGhlIGZpcnN0IGVsZW1lbnQgYWx3YXlzIGlzIGB0bV9zaGFwZSgpYCwgd2hpY2ggc3BlY2lmaWVkIHRoZSBpbnB1dCBzaGFwZSBvYmplY3QuIE5leHQsIG1hcCBsYXllcnMsIGFkZGl0aW9uYWwgbWFwIGVsZW1lbnRzLCBhbmQgb3ZlcmFsbCBsYXlvdXQgY2FuIGJlIGN1c3RvbWl6ZWQuIC0tLSBHcmVhdCBsaWtlIGdncGxvdCENCg0KYGBge3J9DQojIFZpZXcgZGF0YSBzZXQNCmdyb25fcG9seXMgJT4lIA0KICBzbGljZV9oZWFkKG4gPSA1KQ0KDQojIFNldCB0bWFwIG1vZGUgdG8gaW50ZXJhY3RpdmUgdmlld2luZw0KdG1hcF9tb2RlKG1vZGUgPSAidmlldyIpDQoNCiMgTWFrZSBtYXANCmdyb25fcG9seXMgJT4lIA0KICB0bV9zaGFwZSgpICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAyLCBhbHBoYSA9IDAuNykgKw0KICB0bV9maWxsKGNvbCA9ICJtaWRuaWdodGJsdWUiLCBhbHBoYSA9IDAuMikNCiAgDQogIA0KDQpgYGANCg0KUGxvdCB0aGUgc3RvcmVzIGFuZCB0aGVpciByZXNwZWN0aXZlIGZsb29yIGFyZWFzDQoNCmBgYHtyIGVjaG89RkFMU0V9DQojIFZpZXcgZGF0YQ0KZ3Jvbl9zaG9wcyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQoNCiMgTWFrZSBtYXANCmdyb25fc2hvcHMgJT4lIA0KICB0bV9zaGFwZSgpICsNCiAgdG1fZG90cyhzaXplID0gIkZsb29yc3BhY2UiLCBjb2wgPSAibWlkbmlnaHRibHVlIiwgYWxwaGEgPSAwLjUpICsNCiAgdG1fc2hhcGUoZ3Jvbl9wb2x5cykgKw0KICB0bV9ib3JkZXJzKGNvbCA9ICJpbmRpYW5yZWQiLCBhbHBoYSA9IDAuNykgDQoNCmBgYA0KDQpBIHRoaXJkIGRhdGEgc2V0IHJlbGF0ZXMgdG8gdGhlIGFkbWluaXN0cmF0aXZlIGFyZWFzLCBidXQgYXMgYSBzZXQgb2YgcG9pbnRzIC0gdGhpcyBpcyB1c2VmdWwgdG8gY29tcHV0ZSAnY3JvdyBmbHknIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBhZG1pbmlzdHJhdGl2ZSBhcmVhcyBhbmQgdGhlIHNob3BzLiBUaGVzZSBkYXRhIHNldHMgYXJlIGNhbGxlZDoNCg0KLSAgIGBncm9uX3B0c2AgLSBsYWJlbCBwb2ludHMgZm9yIHRoZSBhZG1pbmlzdHJhdGl2ZSBhcmVhcw0KLSAgIGBncm9uX3BvbHlzYCAtIHBvbHlnb25zIGZvciB0aGUgYWRtaW5pc3RyYXRpdmUgYXJlYXMNCi0gICBgZ3Jvbl9zaG9wc2AgLSBwb2ludCBsb2NhdGlvbnMgZm9yIHRoZSBzdG9yZXMNCg0KTGV0J3MgdmlldyB0aGUgYGdyb25fcHRzYCBkYXRhDQoNCmBgYHtyfQ0KZ3Jvbl9wdHMNCmBgYA0KDQpUaGUgdmFyaWFibGVzIGhlcmUgYXJlIHRoZSBwb3B1bGF0aW9uIGBBQU5UX0lOV2AgLSBhbmQgdGhlIHBvdGVudGlhbCBzcGVuZGluZyBmb3IgZWFjaCBhZG1pbmlzdHJhdGl2ZSB1bml0IGBNb25leWAgLSB0aGlzIGlzIGEgc2ltcGxlIGVzdGltYXRlIG9mIOKCrDIwIHBlciBwZXJzb24uIFRoZSBwb2x5Z29ucyBvYmplY3QgY29udGFpbnMgdGhlIHNhbWUgaW5mb3JtYXRpb24gLSBpdCBqdXN0IHVzZXMgcG9seWdvbiBnZW9tZXRyeSBpbnN0ZWFkIG9mIHBvaW50Lg0KDQojIyBUaGlua2luZyBhYm91dCBkaXN0YW5jZXMNCg0KVGhlIGRhdGEgd2UgY3VycmVudGx5IGhhdmUgdGVsbHMgeW91IHdoZXJlIHRoZSBzdG9yZXMgYXJlLCBhbmQgd2hlcmUgdGhlIHBvcHVsYXRpb24gaXMgbG9jYXRlZC4gSXQgYWxzbyB0ZWxscyB5b3UgaG93IG11Y2ggc3BlbmRpbmcgcG93ZXIgaXMgaW4gZWFjaCBwbGFjZSwgYW5kIGhvdyBhdHRyYWN0aXZlIGVhY2ggc3RvcmUgaXMgKG1lYXN1cmVkIGJ5IGZsb29yIHNwYWNlKS4gVG8gcHJlZGljdCBpbnRlcmFjdGlvbiB0aGUgZmluYWwgcGllY2UgaW4gdGhlIGppZ3NhdyBpcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgc3RvcmVzIGFuZCB0aGUgcG9wdWxhdGlvbi4gSW4gYnJvYWQgdGVybXMsIHRoZSBpbnRlcmFjdGlvbiBiZXR3ZWVuIHBsYWNlICRpJCBhbmQgc3RvcmUgJGokIChhbm5vdGF0ZWQgaGVyZSBpcyAkSV97aWp9JCkgaXMgbW9kZWxsZWQgYXMNCg0KJCQNCklfe2lqfSA9IGYoUF9pLEFfaixkX3tpan0pDQokJA0KDQp3aGVyZSAkUF9pJCBpcyB0aGUgKnByb2R1Y3RpdmVuZXNzKiBpbiBwbGFjZSAkaSQgKGBNb25leWApLA0KDQokQV9qJCBpcyB0aGUgKmF0dHJhY3RpdmVuZXNzKiBvZiBzdG9yZSAkaiQgKGBGbG9vcnNwYWNlYCkNCg0KYW5kICRkX3tpan0kIGlzIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28uDQoNCioqU28gd2UgbmVlZCB0byBjb21wdXRlIHRoZSBkaXN0YW5jZXMqKi4gT25lIHdheSBvZiBkb2luZyB0aGlzIGlzIHRvIGV4dHJhY3QgdGhlIHBvaW50IGNvb3JkaW5hdGVzIGZyb20gYGdyb25fcHRzYCBhbmQgYGdyb25fc2hvcHNgIGFuZCBjb21wdXRlIHRoZSBzdHJhaWdodCBsaW5lIChjcm93IGZseSkgZGlzdGFuY2VzIGJldHdlZW4gZWFjaCBwYWlyLCB1c2luZyBQeXRoYWdvcmFzJyB0aGVvcmVtLiBUaGUgZm9sbG93aW5nIGZ1bmN0aW9uIGRvZXMgdGhpcy4NCg0KLSAgIHN0X2Nvb3JkaW5hdGVzIHtzZn06IHJldHJpZXZlIGNvb3JkaW5hdGVzIGluIG1hdHJpeCBmb3JtDQoNCi0gICBgYmFzZTo6b3V0ZXJgOiBUaGUgb3V0ZXIgcHJvZHVjdCBvZiB0aGUgYXJyYXlzIFggYW5kIFkgaXMgdGhlIGFycmF5IEEgd2l0aCBkaW1lbnNpb24gYGMoZGltKFgpLCBkaW0oWSkpYA0KDQokJA0KZF97aml9ID0gXHNxcnQgKHNob3BfanggLSBwbGFjZV9peCleMiArKHNob3BfankgLSBwbGFjZV9peSleMg0KJCQNCg0KYGBge3J9DQojIFRoZSBmaXJzdCBzdGVwIHdvdWxkIGJlIHRvIGV4dHJhY3QgdGhlIGRpc3RhbmNlcyBmcm9tIGVhY2ggZGYNCiMgUmV0cmlldmUgY29vcmRpbmF0ZXMgaW4gbWF0cml4IGZvcm0NCnNob3BfY29vcmQgPC0gc3RfY29vcmRpbmF0ZXMoZ3Jvbl9zaG9wcykNCnJvd25hbWVzKHNob3BfY29vcmQpIDwtIGdyb25fc2hvcHMkc2hvcGNvZGUNCmFyZWFfY29vcmQgPC0gc3RfY29vcmRpbmF0ZXMoZ3Jvbl9wdHMpDQpyb3duYW1lcyhhcmVhX2Nvb3JkKSA8LSBncm9uX3B0cyRCVV9DT0RFDQoNCiMgQ29tcHV0ZSBldWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiBlYWNoIHNob3AgY29vcmRpbmF0ZSBhbmQgcGxhY2UgaW4gS00gKHgvMTAwMCkNCmRtYXQgPC0gc3FydChvdXRlcihzaG9wX2Nvb3JkWywgMV0sIGFyZWFfY29vcmRbLCAxXSwgRlVOID0gIi0iKV4yICsgb3V0ZXIoc2hvcF9jb29yZFssIDJdLCBhcmVhX2Nvb3JkWywgMl0sIEZVTiA9ICItIileMikvMTAwMA0KDQoNCiMgUHV0dGluZyB0aGlzIGluIGEgZnVuY3Rpb24NCmdldF9kaXN0IDwtIGZ1bmN0aW9uKGdyb25fc2hvcHMsIGdyb25fcHRzKXsNCiAgc2hvcF9jb29yZCA8LSBzdF9jb29yZGluYXRlcyhncm9uX3Nob3BzKQ0KICByb3duYW1lcyhzaG9wX2Nvb3JkKSA8LSBncm9uX3Nob3BzJHNob3Bjb2RlDQogIGFyZWFfY29vcmQgPC0gc3RfY29vcmRpbmF0ZXMoZ3Jvbl9wdHMpDQogIHJvd25hbWVzKGFyZWFfY29vcmQpIDwtIGdyb25fcHRzJEJVX0NPREUNCiAgZG1hdCA8LSBzcXJ0KG91dGVyKHNob3BfY29vcmRbLCAxXSwgYXJlYV9jb29yZFssIDFdLCBGVU4gPSAiLSIpXjIgKyBvdXRlcihzaG9wX2Nvb3JkWywgMl0sIGFyZWFfY29vcmRbLCAyXSwgRlVOID0gIi0iKV4yKS8xMDAwDQogIA0KICByZXR1cm4oZG1hdCkNCn0NCmBgYA0KDQpOb3cgd2UgaGF2ZSBhIGRpc3RhbmNlIG1hdHJpeCwgYnV0IHRvIG1lcmdlIGl0IHdpdGggdGhlIHJlc3Qgb2YgdGhlIGluZm9ybWF0aW9uLCBpdCB3b3VsZCBiZSBiZXR0ZXIgdG8gaGF2ZSB0aGlzIGluICp0aWR5KiBmb3JtYXQgLSBlc3NlbnRpYWxseSBpbiB0aGUgZm9ybWF0IG9mIGEgZGF0YSBmcmFtZSwgd2l0aCB0aHJlZSBjb2x1bW5zIC0gdGhlIHN0b3JlIElELCB0aGUgYWRtaW5pc3RyYXRpdmUgYXJlYSBJRCBhbmQgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIHR3by4gVGhpcyBpcyB1c2VmdWwgYmVjYXVzZSBpdCBtYXkgdGhlbiBiZSB1c2VkIGFzIGEgbG9vay11cCB0YWJsZSBmb3IgYSBnaXZlbiBzdG9yZSwgb3IgYSBnaXZlbiBhZG1pbmlzdHJhdGl2ZSBhcmVhLCB0byBmaW5kIGRpc3RhbmNlcy4gVG8gZG8gdGhpcywgZmlyc3QgY29udmVydCB0aGUgbWF0cml4IG9iamVjdCBpbnRvIGEgZGF0YSBmcmFtZSwgYW5kIGFkZCBhIHZhcmlhYmxlIGNvbHVtbiBiYXNlZCBvbiB0aGUgcm93IG5hbWUgLSB0aGlzIGdpdmVzIHRoZSBzaG9wIGNvZGUuIEhlcmUgd2UgdXNlIGB0aWR5dmVyc2VgLXN0eWxlIHBpcGVsaW5pbmcgdG8gJ211dGF0ZScgdGhlIGRhdGEgZnJhbWUuDQoNCmBgYHtyfQ0KIyBDb252ZXJ0IG1hdHJpeCB0byBhIHRpYmJsZQ0KZGlzdF90YWIgPC0gYXNfdGliYmxlKGdyb25fc2hvcHMpICU+JSANCiAgc2VsZWN0KHNob3Bjb2RlKSAlPiUgDQogIGJpbmRfY29scyhhc190aWJibGUoZG1hdCkpDQoNCmRpc3RfdGFiICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCmBgYA0KDQpUaGlzIGlzIHN0aWxsIG5vdCBpbiB0aGUgZm9ybWF0IG1lbnRpb25lZCBlYXJsaWVyLCBhcyBlYWNoIG9yaWdpbiBoYXMgaXRzIG93biBjb2x1bW4uIFRvIHJlLXNoYXBlIHRoZSBkYXRhIGludG8gdGhyZWUgY29sdW1ucyB0aGUgW2BwaXZvdF9sb25nZXJgXShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3Bpdm90X2xvbmdlci5odG1sKSBjb21tYW5kIGlzIG5lZWRlZC4gVGhpcyB0YWtlcyBkYXRhIHNwcmVhZCBvdmVyIHNldmVyYWwgY29sdW1ucyAoaGVyZSBhbGwgb2YgdGhlIG9yaWdpbnMpIGFuZCAnZ2F0aGVycycgdGhlbSBpbnRvIGEgc2luZ2xlIGNvbHVtbi4gVGhlIGNvbHVtbiBuYW1lcyBnZXQgYSBuZXcgdmFyaWFibGUsIGFuZCB0aGUgZGlzdGFuY2UgdmFsdWVzIGFyZSBzdGFja2VkLg0KDQotICAgQmFzaWNhbGx5IHB1dHRpbmcgYWxsIHRoZSBwbGFjZXMgY29sdW1ucyB1bmRlciAxIGNvbHVtbiBpbnN0ZWFkIG9mIHRoZW0gYmVpbmcgc3ByZWFkIG91dCBpbiAxMDggY29sdW1ucy4NCg0KYGBge3IgcGl2b3RfbG9uZ2VyfQ0KZGlzdF90YWIgJT4lIA0KICBwaXZvdF9sb25nZXIoIXNob3Bjb2RlLCBuYW1lc190byA9ICJCVV9DT0RFIiwgdmFsdWVzX3RvID0gIkRpc3QiKSAtPiBkaXN0X3RhYg0KDQojIFZpZXcgZGlzdCBtYXQNCmRpc3RfdGFiICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCmBgYA0KDQpOZXh0LCB3ZSB3YW50IHRvIGpvaW4gdGhlIGF0dHJhY3RpdmVuZXNzIGFuZCBwcm9kdWN0aXZlbmVzcyB2YXJpYWJsZXMgb250byB0aGlzIGxpc3QsIHVzaW5nIGRhdGFiYXNlIGpvaW4tdHlwZSBjb21tYW5kcyB0aGF0IGFyZSBwcm92aWRlZCBieSB0aGUgYGxlZnRfam9pbmAgZnVuY3Rpb24uIEZpcnN0IG9mZiwgam9pbiB0aGUgYXR0cmFjdGl2ZW5lc3MgaW5mb3JtYXRpb24gZnJvbSBgZ3Jvbl9zaG9wc2AgOg0KDQotICAgQnkgZGVmYXVsdCB1c2UgYSBsZWZ0X2pvaW46IGluY2x1ZGVzIGFsbCByb3dzIGluIHguDQoNCmBgYHtyfQ0KZGlzdF90YWIgJT4lIA0KICBsZWZ0X2pvaW4oZ3Jvbl9zaG9wcywgYnkgPSAic2hvcGNvZGUiKSAlPiUgDQogIGxlZnRfam9pbihncm9uX3B0cywgYnkgPSAiQlVfQ09ERSIpICU+JSANCiAgc2VsZWN0KC1zdGFydHNfd2l0aCgiZ2VvbWV0cnkiKSkgLT4gZGlzdF90YWINCg0KIyBWaWV3IGRhdGEgd2l0aCBhbGwgbW9kZWwgdmFyaWFibGVzDQpkaXN0X3RhYiAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQpgYGANCg0KU28gbm93IGBkaXN0X3RhYmAgaGFzIGEgc2VyaWVzIG9mIHJvd3MgY29ycmVzcG9uZGluZyB0byBlYWNoICRpLGokIHBhaXIsIHdpdGggdGhlIHZhbHVlcyBvZiAkZF97aWp9JCAkUF9pJCBhbmQgJEFfaiQgYW1vbmcgdGhlIHZhcmlhYmxlcyAtDQoNCi0gICAkZF97aWp9JCBpcyBgRGlzdGANCi0gICAkUF9pJCBpcyBgTW9uZXlgDQotICAgJEFfaiQgaXMgYEZsb29yc3BhY2VgDQoNCldlIGNhbiBub3cgYWN0dWFsbHkgY2Fycnkgb3V0IHRoZSBtb2RlbGxpbmcuDQoNCiMjIEZpdHRpbmcgdGhlIG1vZGVsDQoNCkJlZm9yZSB3ZSBub3RlZCB0aGF0IHRoZSBzcGF0aWFsIGludGVyYWN0aW9uIG1vZGVsIHRha2VzIHRoZSBmb3JtICRJX3tpan0gPSBmKFBfaSxBX2osZF97aWp9KSQgLSBoZXJlLCB3ZSB3aWxsIHVzZSBhIHNwZWNpZmljLCBtdWx0aXBsaWNhdGl2ZSBmb3JtYXQ6DQoNCiQkDQpJX3tpan0gPSBcdGV4dHNme2NvbnN0fV9qIFx0aW1lcyBBX2leXGFscGhhIGcoZF97aWp9KSANCiQkIHdoZXJlICRnJCBpcyBhIGRlY2F5aW5nIGRlc2lyYWJpbGl0eSBmdW5jdGlvbiBvZiBkaXN0YW5jZS4gTm90ZSB0aGF0IHRoZSBwcm9kdWN0aXZlbmVzcyBkb2VzIG5vdCBhcHBlYXIgZXhwbGljaXRseSBoZXJlIC0gaW4gZmFjdCBpdCBmZWF0dXJlcyBhcyBhICoqY29uc3RyYWludCoqIC0gZXNzZW50aWFsbHkgZm9yIGVhY2ggYWRtaW5pc3RyYXRpdmUgYXJlYSwgdGhlIHRvdGFsIG1vbmV5IGdvaW5nIHRvIGFsbCBvZiB0aGUgc3RvcmVzIG11c3QgYWRkIHVwIHRvIHRoZSB0b3RhbCBtb25leSBmb3IgdGhhdCBhcmVhLiBUaGlzIGlzIGFjaGlldmVkIGJ5IG11bHRpcGx5aW5nIHRoZSBhdHRyYWN0aXZlbmVzcyBhbmQgZGlzdGFuY2UtZGV0ZXJyZW5jZSB0ZXJtcyBieSBhIGNvbnN0YW50IGZvciBlYWNoIGFyZWEgLSB0aGUgY29uc3RhbnQgcmUtc2NhbGVzIHRoZSBhbW91bnQgc3BlbnQgc28gdGhhdCB0aGUgdG90YWwgZm9yIHRoZSBhcmVhIGlzIGVxdWFsIHRvIHRoZSB2YXJpYWJsZSBgTW9uZXlgIGZvciB0aGF0IGFyZWEuDQoNClRoZSBmb3JtIHRoYXQgJGckIHRha2VzIGhlcmUgd2lsbCBiZSB0aGF0IG9mIGV4cG9uZW50aWFsIGRlY2F5LiBPZnRlbiB0aGlzIGlzIHdyaXR0ZW4gYXMgJGVeey1cdGV4dHNme2NvbnN0fSBcdGltZXMgZF97aWp9fSQgYnV0IGhlcmUgd2Ugd2lsbCB1c2UgdGhlIGZvcm0NCg0KJCQNCmcoZF97aWp9KSA9IDJeey1kX3tpan0vaH0NCiQkIHdoZXJlICRoJCBpcyB0aGUgZGlzdGFuY2UgaGFsZi1saWZlLiBUaGF0IGlzLCBmb3IgZXZlcnkgJGgkIGtpbG9tZXRlcnMgYmV0d2VlbiB0aGUgc3RvcmUgYW5kIHRoZSBob21lIGxvY2F0aW9uLCB0aGUgZGVzaXJhYmlsaXR5IG9mIG1ha2luZyB0aGUgdHJpcCBoYWx2ZXMuIEFsdGhvdWdoIHRoaXMgY2FuIGJlIHdyaXR0ZW4gaW4gbW9yZSBjb252ZW50aW9uYWwgZXhwb25lbnRpYWwgZm9ybSBhcyAkZV57LVxsb2coMilkX3tpan0vaH0kLCB0aGUgcGFyYW1ldGVyICRoJCBpcyBtb3JlIGVhc2lseSBpbnRlcnByZXRlZCBoZXJlLiBUaHVzIHRoZSBmdWxsIG1vZGVsIGlzDQoNCiQkDQpJX3tpan0gPSBcdGV4dHNme2NvbnN0fV9qIEFfaV5cYWxwaGEgMl57LWRfe2lqfS9ofQ0KJCQNCg0KSGF2aW5nIGFycml2ZWQgYXQgdGhlIGVxdWF0aW9uIHdlIG5vdyBuZWVkIHRvIGNvbXB1dGUgdGhpcyBhbmQgYWRkIGEgY29sdW1uIHRvIHRoZSBgZGlzdF90YWJgIGRhdGEgZnJhbWUgdG8gbW9kZWwgdGhlICRJX3tpan0kIHZhbHVlcy4gVGhlc2Ugd2lsbCBiZSB0aGUgZXN0aW1hdGVkIGV4cGVuZGl0dXJlIGZvciB0aGUgcG9wdWxhdGlvbiBvZiBhcmVhICRqJCBhdCBzdG9yZSAkaSQuIEZvciBhIHN0YXJ0aW5nIHBvaW50LCBhc3N1bWUgYSBmYWlybHkgc3Ryb25nIGRpc3RhbmNlIGRldGVycmVuY2Ugd2l0aCBhIGRpc3RhbmNlIGhhbGYtbGlmZSBvZiAyMDBtLiBXZSBjYW4gY2hhbmdlIHRoaXMgbGF0ZXIuDQoNClRoZSBmaXJzdCBwYXJ0IG9mIHRoZSBtb2RlbCAoYmVmb3JlIGFwcGx5aW5nIHRoZSBwcm9kdWN0aW9uIGNvbnN0cmFpbnQpIGlzIHRvIGNvbXB1dGUgJEFfaV5cYWxwaGEgMl57LWRfaWovaH0kLiAkXGFscGhhJCBoZXJlIGFsbG93cyBmb3IgZXh0cmEgYXR0cmFjdGl2ZW5lc3Mgb2YgdGhlIGxhcmdlciBzdG9yZXMuIEluIHRoaXMgY2FzZSBpdCBpcyBhc3N1bWVkIHRvIGJlIHF1aXRlIGNsb3NlIHRvIDEgLSBzYXkgMS4wNSAtIHNvIHRoYXQgbGFyZ2VyIHN0b3JlcyBoYXZlIGEgc2xpZ2h0bHkgZGlzcHJvcG9ydGlvbmF0ZSBpbmNyZWFzZSBpbiBhdHRyYWN0aXZlbmVzcywgYnV0IHRoZSBlZmZlY3QgaXMgZmFpcmx5IHdlYWsuDQoNCmBgYHtyIHBhcnQxfQ0KZGlzdF90YWIgJT4lIA0KICBtdXRhdGUoRmxvd19GYWN0b3I9KEZsb29yc3BhY2VeMS4wNSkqMl4oLURpc3QvMC4yKSkgLT4gc2ltX3RhYg0KaGVhZChzaW1fdGFiKQ0KYGBgDQoNClRoaXMgaGFzIHRoZSBgRmxvd19GYWN0b3JgIHZhcmlhYmxlIHByb3BvcnRpb25hbCB0byB0aGUgY2FzaCBmbG93IGJldHdlZW4gJGkkIGFuZCAkaiQgYnV0IGFzIHlldCBpdCBoYXNuJ3QgYmVlbiBzY2FsZWQuIFRvIGRvIHRoaXMsIHdlIGZpcnN0IG5lZWQgdG8gcmVzY2FsZSB0aGUgZmxvdyBmYWN0b3JzIGZvciBlYWNoIGFkbWluaXN0cmF0aXZlIGFyZWEgc28gdGhhdCB0aGV5IHN1bSB0byBvbmUuIFRoZW4sIHRoZXkgYXJlIGVhY2ggbXVsdGlwbGllZCBieSB0aGUgYE1vbmV5YCB2YXJpYWJsZSBmb3IgdGhhdCBhcmVhLCBzbyB0aGF0IHRoZSB0b3RhbCBtb2RlbGxlZCBmbG93IG9mIGNhc2ggZXF1YWxzIHRoZSB0b3RhbCBtb25leSBjb21pbmcgZnJvbSB0aGF0IGFyZWEuIFRoaXMgaXMgZG9uZSBieSB1c2luZyB0aGUgYHN1bWAgZnVuY3Rpb24uIElmIHRoZSBgc2ltX3RhYmAgZGF0YSBmcmFtZSBpcyBmaXJzdCBncm91cGVkIGJ5IHRoZSBhZG1pbmlzdHJhdGl2ZSB1bml0IGNvZGUsICh1c2luZyBgZ3JvdXBfYnkoQlVfQ09ERSlgKSB0aGVuIHRoZSBzdW0gZnVuY3Rpb24gZm9yIGVhY2ggcm93IGFwcGxpZXMgdG8gdGhlIHN1bSBvZiB2YWx1ZXMgZm9yIGl0cyBzcGVjaWZpYyBgQlVfQ09ERWAgLSByYXRoZXIgdGhhbiB0aGUgc3VtIGZvciB0aGUgZW50aXJlIGNvbHVtbi4gVGh1cw0KDQpgYGB7cn0NCnNpbV90YWIgJT4lIA0KICBncm91cF9ieShCVV9DT0RFKSAlPiUgDQogIG11dGF0ZShDYXNoX0Zsb3cgPSAoRmxvd19GYWN0b3Ivc3VtKEZsb3dfRmFjdG9yKSkgKiBNb25leSkgLT4gc2ltX3RhYg0KDQpzaW1fdGFiICU+JSANCiAgc2xpY2VfaGVhZCgpDQpgYGANCg0KVGh1cyB3ZSBub3cgaGF2ZSBhIGRhdGEgZnJhbWUgd2hvc2Ugcm93cyBhcmUgdGhlIGRldGFpbHMgb2YgZWFjaCAkaSQgYW5kICRqJCBjb21iaW5hdGlvbiwgaW5jbHVkaW5nIHRoZSBtb2RlbGxlZCBjYXNoIGZsb3cuIFdlIGNhbiBmaXJzdCBjaGVjayB0aGF0IHRoZSBjb25zdHJhaW50IGhhcyB3b3JrZWQ6DQoNCi0gICB1bmlxdWUoTW9uZXkpIGNvdWxkIGhhdmUgd29ya2VkIHRvby4gTm90IHN1cmUgd2h5IGRpc3RpbmN0KCkgaXMgbm90IHdvcmtpbmcNCg0KYGBge3J9DQpzaW1fdGFiICU+JSANCiAgZ3JvdXBfYnkoQlVfQ09ERSkgJT4lIA0KICBzdW1tYXJpc2Uob3V0X2Zsb3cgPSBzdW0oQ2FzaF9GbG93KSwgYXJlYV9tb25leSA9IGZpcnN0KE1vbmV5KSkNCmBgYA0KDQpIZXJlIHRoZSBgc3VtbWFyaXNlYCBmdW5jdGlvbiBjcmVhdGVzIGEgc3VtbWFyeSBkYXRhIGZyYW1lLCB3aGVyZSBzdGF0aXN0aWNzIHN1Y2ggYXMgdGhlIGBzdW1gIG9yIGBtZWFuYCBhcmUgY29tcHV0ZWQgZm9yIGVhY2ggZ3JvdXAuIFRodXMgYE91dF9GbG93YCBpcyB0aGUgc3VtIG9mIGFsbCBvZiB0aGUgY2FzaCBmbG93cyBjb21pbmcgb3V0IG9mIGVhY2ggYWRtaW5pc3RyYXRpdmUgYXJlYS4gTm90ZSB0aGF0IHRoZSBgTW9uZXlgIHZhcmlhYmxlIGlzIHRoZSBzYW1lIGZvciBlYWNoIG1lbWJlciBvZiB0aGUgZ3JvdXAgaW4gYHNpbV90YWJgIHNvIHRha2luZyB0aGUgYGZpcnN0YCBpcyBqdXN0IGEgd2F5IG9mIHN1bW1hcmlzaW5nIHNldmVyYWwgcmVwZWF0ZWQgdmFsdWVzIHdpdGggYSBzaW5nbGUgaW5jaWRlbmNlIG9mIHRoaXMgdmFsdWUuIFRodXMgdGhlIG91dHB1dCBzaG93cyB0aGF0IHRoZSBzdW1tZWQgbW9kZWxsZWQgY2FzaCBmbG93cyBmcm9tIGVhY2ggYWRtaW5zdHJhdGl2ZSBhcmVhIGlzIHRoZSBzYW1lIGFzIHRoZSBgTW9uZXlgIGZvciB0aGF0IGFyZWEuIFRoZSBjb25zdHJhaW50IGhhcyB3b3JrZWQuIFBoZXchDQoNCk5vdyB3ZSBhcmUgaGFwcHkgd2l0aCB0aGUgbW9kZWwgd2UgbWF5IHdhbnQgdG8gc2VlIHdoYXQgaGFwcGVucyBmb3IgZGlmZmVyZW50IGhhbGYtbGlmZSBkaXN0YW5jZXMuIEFzIHdpdGggdGhlIGRhdGEgbXVuZ2Vpbmcgd2UgY2FuIGV4cHJlc3MgdGhlIG1vZGVsbGluZyBwcm9jZXNzIGFzIGEgc2luZ2xlIHBpcGVsaW5lLiBCdXQgdG8gc2F2ZSB0aW1lIHdlIGNhbiBhbHNvIHB1dCBpdCBpbnRvIGEgZnVuY3Rpb24sIHdpdGggdGhlIGhhbGYtbGlmZSBhcyBhbiBhcmd1bWVudC4NCg0KYGBge3J9DQpmaXRfc2ltIDwtIGZ1bmN0aW9uKGgsIGRpc3RfdGFiKSB7DQogIGRpc3RfdGFiICU+JSANCiAgICBtdXRhdGUoRmxvd19GYWN0b3I9KEZsb29yc3BhY2VeMS4wNSkqMl4oLURpc3QvaCkpICAlPiUgDQogICAgZ3JvdXBfYnkoQlVfQ09ERSkgJT4lIA0KICAgIG11dGF0ZShDYXNoX0Zsb3cgPSBNb25leSpGbG93X0ZhY3Rvci9zdW0oRmxvd19GYWN0b3IpKSAtPiByZXMNCiAgcmV0dXJuKHJlcykNCn0NCg0KIyBUcnkgb3V0IGEgaGFsZmxpZmUgb2YgNzUwbQ0KZml0X3NpbShoID0gMC43NSwgZGlzdF90YWIgPSBkaXN0X3RhYikgJT4lIA0KICBzbGljZV9oZWFkKCkNCmBgYA0KDQojIyBNYXBwaW5nIHRoZSByZXN1bHRzDQoNCldlIG5vdyBoYXZlIGEgZGF0YSBmcmFtZSBgc2ltX3RhYmAgdGhhdCB0YWJ1bGF0ZXMgdGhlIHJlc3VsdHMgb2YgdGhlIFNwYXRpYWwgSW50ZXJhY3Rpb24gTW9kZWwuIFRoZSBuZXh0IHN0ZXAgaXMgdG8gaW52ZXN0aWdhdGUgdGhlIGdlb2dyYXBoaWNhbCBwYXR0ZXJucyBhc3NvY2lhdGVkIHdpdGggdGhpcy4gU3VwcG9zZSB3ZSBhcmUgbWFpbmx5IGNvbmNlcm5lZCB3aXRoIHRoZSBgQUgvQWxiZXJ0IEhlaWpuYCBncm91cCBvZiBzdG9yZXMuIE9uZSBzdGFydGluZyBwb2ludCBtaWdodCBiZSB0byBkaXNjb3ZlciB0aGUgdG90YWwgY2FzaCBmbG93cyBwcmVkaWN0ZWQgaW50byBlYWNoIG9mIHRoZSBzdG9yZXMgLSB0aGlzIGlzIGEgdHdvLXN0YWdlIHByb2Nlc3MgLSBmaXJzdGx5IGZpbHRlciBvdXQgdGhlc2Ugc3RvcmVzIChiYXNlZCBvbiB0ZXh0IHBhdHRlcm5zIGluIHRoZSBzaG9wIG5hbWUpIC0gYW5kIHRoZW4gZ3JvdXAgYnkgdGhlIHN0b3JlcyBhbmQgc3VtIG92ZXIgdGhlbSB1c2luZyB0aGUgYHN1bW1hcmlzZWAgZnVuY3Rpb24uIFRoZSBwaXBlbGluZSBpcyBhcyBiZWxvdzoNCg0KYGBge3J9DQpzaW1fdGFiICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3Qoc2hvcCwgcGF0dGVybiA9ICJBSHxIZWlqbiIpKSAlPiUgDQogIGdyb3VwX2J5KHNob3Bjb2RlKSAlPiUNCiAgc3VtbWFyaXNlKGluX2Zsb3cgPSBzdW0oQ2FzaF9GbG93KSkgJT4lIA0KICBhcnJhbmdlKGRlc2MoaW5fZmxvdykpIC0+IGluZmxvd190YWINCg0KaW5mbG93X3RhYg0KICANCmBgYA0KDQpGb3Igbm93LCBpdCBpcyBlbm91Z2ggdG8gbm90ZSB0aGF0ICdBSFx8SGVpam4nIHBpY2tzIG91dCBzaG9wIG5hbWVzIHdpdGggZWl0aGVyICdBSCcgb3IgJ0hlaWpuJyBpbiB0aGVtLiBIb3dldmVyLCBhbHRob3VnaCB0aGlzIHRhYnVsYXRlcyB0aGUgaW5mbHV4IG9mIGNhc2gsIGl0IGRvZXNuJ3Qgc2hvdyBhIG1hcC4gVG8gZ2V0IHRoaXMsIGZpcnN0bHkgam9pbiB0aGlzIHRhYmxlIHRvIHRoZSBgZ3Jvbl9zaG9wc2AgZ2VvZ3JhcGhpY2FsIG9iamVjdCBhbmQgdGhlbiB1c2UgYHRtYXBgIHRvIGNyZWF0ZSBhIG1hcC4NCg0KYGBge3J9DQppbmZsb3dfdGFiICU+JSANCiAgbGVmdF9qb2luKGdyb25fc2hvcHMsIGJ5ID0gInNob3Bjb2RlIikgJT4lDQogICMgVG8gbWFrZSBzaG9wIG5hbWUgYXBwZWFyIG9uIHRoZSBob3Zlciwgc2VlbXMgbGlrZSB0aGUgZmlyc3QgY29sdW1uIGFwcGVhcnM/DQogIHJlbG9jYXRlKHNob3ApICU+JSANCiAgc3Rfc2YoKSAlPiUgDQogIHRtX3NoYXBlKCkgKw0KICB0bV9kb3RzKHNpemUgPSAiaW5fZmxvdyIsIGNvbCA9ICJkYXJrb3JjaGlkIiwgYWxwaGEgPSAwLjUpDQpgYGANCg0KVGhpcyBkZXBlbmRzIG9uIHRoZSBkaXN0YW5jZSBoYWxmIGxpZmUuIE9uY2UgYWdhaW4gd2UgY2FuIGVuY2Fwc3VsYXRlIGFsbCBvZiB0aGlzIGluIGEgZnVuY3Rpb24gd2l0aCAkaCQgYXMgdGhlIGFyZ3VtZW50LCBtYWtpbmcgaXQgZWFzaWVyIHRvIGV4cGxvcmU6DQoNCmBgYHtyfQ0KbWFwX2luZmxvdyA8LSBmdW5jdGlvbihoLCBkaXN0X3RhYil7DQogIGZpdF9zaW0oaCwgZGlzdF90YWIpICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3Qoc2hvcCwgcGF0dGVybiA9ICJBSHxIZWlqbiIpKSAlPiUgDQogIGdyb3VwX2J5KHNob3Bjb2RlKSAlPiUNCiAgc3VtbWFyaXNlKGluX2Zsb3cgPSBzdW0oQ2FzaF9GbG93KSkgJT4lIA0KICBhcnJhbmdlKGRlc2MoaW5fZmxvdykpICU+JSANCiAgbGVmdF9qb2luKGdyb25fc2hvcHMsIGJ5ID0gInNob3Bjb2RlIikgJT4lDQogIHJlbG9jYXRlKHNob3ApICU+JSANCiAgc3Rfc2YoKSAlPiUgDQogIHRtX3NoYXBlKCkgKw0KICB0bV9kb3RzKHNpemUgPSAiaW5fZmxvdyIsIGNvbCA9ICJkYXJrb3JjaGlkIiwgYWxwaGEgPSAwLjUpDQogICAgDQp9DQoNCm1hcF9pbmZsb3coaCA9IDAuNSwgZGlzdF90YWIgPSBkaXN0X3RhYikNCmBgYA0KDQpUaGUgYWJvdmUgc2hvd3MgdGhlIGVmZmVjdCBpZiAkaCQgaXMgNTAwbSAtIG5vdyBjaGVjayBpdCBmb3IgMWttOg0KDQpgYGB7ciBjaGVja25leHR9DQptYXBfaW5mbG93KGggPSAxLCBkaXN0X3RhYiA9IGRpc3RfdGFiKQ0KYGBgDQoNCkluIGEgbnV0c2hlbGwsIGFzIHRoZSBkaXN0YW5jZSBoYWxmIGxpZmUgZ2V0cyBsYXJnZXIsIHBlb3BsZSBhcmUgd2lsbGluZyB0byB0cmF2ZWwgZnVydGhlciB0byBzdG9yZXMsIGFuZCB0aGUgbW9yZSBhdHRyYWN0aXZlIGxhcmdlciBzdG9yZXMgZ2V0IGEgZ3JlYXRlciBzaGFyZSBvZiB0aGUgbW9uZXkgLSBwZW9wbGUgd2lsbCB0cmF2ZWwgdG8gYSBtb3JlIGZhdm91cmFibGUgc3RvcmUgZXZlbiB3aGVuIGl0IGlzIGEgbG9uZyB3YXkgYXdheS4NCg0KLSAgIEluIGZhY3QsIG1vbmV5IGdvaW5nIHRvIHNtYWxsZXIgc3RvcmVzIGRlY3JlYXNlcyB3aXRoIGluY3JlYXNlIGluIGhhbGYgbGlmZQ0KDQojIyBDYXRjaG1lbnQgQXJlYXMNCg0KVGhlIGlkZWEgb2YgYSBjYXRjaG1lbnQgYXJlYSBoZXJlIGlzIHRvIGlkZW50aWZ5LCBmb3IgZWFjaCBhZG1pbmlzdHJhdGl2ZSB1bml0LCB3aGF0IGlzIHRoZSBtb3N0IGF0dHJhY3RpdmUgc3RvcmUuIFRoaXMgaXMgZG9uZSBieSBjb25zaWRlcmluZyB0aGUgY2FzaCBmbG93IG91dCBvZiBlYWNoIHVuaXQgdG8gZWFjaCBvZiB0aGUgc3RvcmVzIGFuZCBzZWVpbmcgd2hpY2ggc3RvcmUgZ2V0cyB0aGUgZ3JlYXRlc3Qgc2hhcmUgb2YgdGhlIHRvdGFsIG91dGZsb3cgZnJvbSB0aGUgdW5pdC4gSW4gUiAoYW5kIGluIGB0aWR5dmVyc2VgIHRlcm1zKSB0aGlzIGlzIGRvbmUgdXNpbmcgdGhlIGByYW5rYCBmdW5jdGlvbi4gUmFuayB0YWtlcyBhIHZhcmlhYmxlIGFuZCBmb3IgZWFjaCByb3csIHJldHVybnMgdGhlIHJhbmsgb2YgdGhlIHZhbHVlIGZvciB0aGF0IHJvdywgb3V0IG9mIGFsbCBvZiB0aGUgcm93cy4gVGhlIHJhbmtzIGFyZSBpbiBhc2NlbmRpbmcgb3JkZXIsIHNvIGZvciAkbiQgaXRlbXMgdGhlIGxhcmdlc3QgaGFzIHRoZSB2YWx1ZSAkbiQuDQoNClRvIGZpbmQgdGhlIHJhbmtzIG9mIHRoZSBvdXRmbG93IHNoYXJlcyBvbiBhbiBhZG1pbmlzdHJhdGl2ZSB1bml0IGJ5IGFkbWluc3RyYXRpdmUgdW5pdCBiYXNpcywgYGdyb3VwX2J5YCBpcyBhIGhlbHBlciBmdW5jdGlvbiBhZ2Fpbi4gSW4gdGhpcyBjYXNlIGl0IGNhdXNlcyB0aGUgcmFuayBmdW5jdGlvbiB0byByZXR1cm4gdGhlIHJlbGF0aXZlIHJhbmtzIG9mIHRoZSBmbG93IGZhY3RvcnMgKCRBX2leXGFscGhhIDJeey1kX3tpan0vaH0kKSB3aXRoaW4gZWFjaCBncm91cC4gRmluYWxseSwgaXQgd291bGQgYnkgdXNlZnVsIHRvIGhhdmUgcmFua3MgaW4gZGVzY2VuZGluZyBvcmRlciAtIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgbWF4aW11bSBpbiBlYWNoIGdyb3VwLCBhbmQgc28gcmF0aGVyIHRoYW4gdHJ5aW5nIHRvIGZpbmQgcmFua3Mgb2YgJG4kIHdoZW4geW91IGRvbid0IGtub3cgd2hhdCAkbiQgaXMsIGl0IGlzIG1vcmUgdXNlZnVsIHRvIGZpbmQgcmFua3Mgb2YgMS4gQ2hhbmdpbmcgdGhlIHNpZ24gb2YgdGhlIHZhcmlhYmxlIChieSB1c2luZyBgZGVzYygpYCkgZ2l2ZW4gdG8gdGhlIHJhbmsgZnVuY3Rpb24gYWNoaWV2ZXMgdGhpcyAtIGVzc2VudGlhbGx5IGl0IGZsaXBzIHRoZSBvcmRlcnMgb2YgdGhlIHZhcmlhYmxlcy4gQ2hlY2sgdGhlIHJlc3VsdHMgaGVyZToNCg0KYGBge3J9DQpzaW1fdGFiICU+JSANCiAgZ3JvdXBfYnkoQlVfQ09ERSkgJT4lIA0KICBtdXRhdGUocmFuayA9IHJvd19udW1iZXIoZGVzYyhGbG93X0ZhY3RvcikpKSAlPiUgDQogIGZpbHRlcihyYW5rID09IDEpICU+JSANCiAgYXJyYW5nZShkZXNjKENhc2hfRmxvdykpIC0+IHJhbmtfdGFiDQoNCnJhbmtfdGFiICU+JSANCiAgc2xpY2VfaGVhZCgpDQpgYGANCg0KVGhlcmUgaXMgbm93IGEgdW5pcXVlIHJvdyBmb3IgZWFjaCBhZG1pbmlzdHJhdGl2ZSB1bml0LCBhbmQgdGhlIHNob3AgYWxzbyBhcHBlYXJpbmcgaW4gdGhhdCByb3cgaXMgdGhlIG9uZSB3aGVyZSBtb3N0IG1vbmV5IGZsb3dzIC0gYW5kIHRoaXMgc3RhdGVzIHdoaWNoIGNhdGNobWVudCBhcmVhIHRoZSBhZG1pbmlzdHJhdGl2ZSB1bml0IGlzIGluLiBUbyBtYXAgdGhlc2UsIGZpcnN0bHkgam9pbiB0aGlzIHRhYmxlIHRvIHRoZSBgZ3JvbmluZ2VuX3BvbHlzYCBvYmplY3Q6DQoNCmBgYHtyfQ0KcmFua190YWIgJT4lIA0KICBsZWZ0X2pvaW4oZ3Jvbl9wb2x5cykgJT4lIA0KICByZWxvY2F0ZShCVV9DT0RFKSAlPiUNCiAgc3Rfc2YoKSAlPiUgDQogIHRtX3NoYXBlKCkgKw0KICB0bV9wb2x5Z29ucyhjb2wgPSAic2hvcGNvZGUiKQ0KDQpgYGANCg0KV2UgY2FuIGZ1cnRoZXIgdmVyaWZ5IHRoaXMgYnkgbG9va2luZyBhdCB0aGUgcHJvcG9ydGlvbiB3aGljaCBlYWNoIHNob3Agc2VydmVzOg0KDQpgYGB7cn0NCnJhbmtfdGFiICU+JSANCiAgbGVmdF9qb2luKGdyb25fcG9seXMpICU+JSANCiAgZ3JvdXBfYnkoc2hvcGNvZGUpICU+JSANCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgYXJyYW5nZShkZXNjKG4pKSANCg0KYGBgDQoNCkEgZnVydGhlciB1c2VmdWwgdGVjaG5pcXVlIHdvdWxkIGJlIHRvIGNyZWF0ZSBkaXNzb2x2ZWQgY2F0Y2htZW50IHBvbHlnb25zLiBUaGlzIGVzc2VudGlhbGx5IGludm9sdmVzIHJlbW92aW5nIHRoZSBib3VuZGFyaWVzIGJldHdlZW4gYWRtaW5pc3RyYXRpdmUgdW5pdCBwb2x5Z29ucyBhc3NpZ25lZCB0byB0aGUgc2FtZSBzdG9yZS4gVGhpcyBpcyBkb25lIGJ5IGFwcGx5aW5nIGEgYGdyb3VwX2J5YCBvcGVyYXRpb24gdG8gYSBzaW1wbGUgZmVhdHVyZXMgb2JqZWN0IGFuZCB0aGVuIGEgYHN1bW1hcmlzZWAgLSB0aGlzIG1lcmdlcyBhbGwgb2YgdGhlIHBvbHlnb25zIGluIHRoZSBzYW1lIGdyb3VwLiBIZXJlIHRoZXJlIGlzIG5vIHN1bW1hcnkgdmFyaWFibGUsIHdlIGp1c3QgZG8gdGhpcyB0byBtYWtlIHRoZSBtZXJnZToNCg0KYGBge3J9DQpyYW5rX3RhYiAlPiUgDQogIGxlZnRfam9pbihncm9uX3BvbHlzKSAlPiUgDQogIHN0X3NmKCkgJT4lIA0KICBncm91cF9ieShzaG9wY29kZSkgJT4lIA0KICBzdW1tYXJpc2UoKSAlPiUgDQogIHRtX3NoYXBlKCkgKw0KICB0bV9wb2x5Z29ucyhjb2wgPSAic2hvcGNvZGUiKQ0KYGBgDQoNCkFzIGJlZm9yZSAtIGFsbCBvZiB0aGlzIGNvdWxkIGJlIG1hZGUgaW50byBhIHNtYWxsIG51bWJlciBvZiBsYXJnZXIgcGlwZWxpbmVzIGFuZCBlbmNhcHN1bGF0ZWQgaW4gYSBmdW5jdGlvbiBvZiAkaCQ6DQoNCmBgYHtyIGNhdGNoZnVufQ0KbWFwX2NhdGNobWVudHMgPC0gZnVuY3Rpb24oaCwgZGlzdF90YWIpIHsNCiAgZml0X3NpbShoLCBkaXN0X3RhYikgJT4lDQogICAgZmlsdGVyKHN0cl9kZXRlY3Qoc2hvcCwgcGF0dGVybiA9ICJBSHxIZWlqbiIpKSAlPiUgDQogICAgZ3JvdXBfYnkoQlVfQ09ERSkgJT4lIA0KICAgIG11dGF0ZShyYW5rID0gcm93X251bWJlcihkZXNjKEZsb3dfRmFjdG9yKSkpICU+JSANCiAgICBmaWx0ZXIocmFuayA9PSAxKSAtPiByYW5rX3RhYg0KICByYW5rX3RhYiAlPiUgDQogIGxlZnRfam9pbihncm9uX3BvbHlzKSAlPiUgDQogIHN0X3NmKCkgJT4lIA0KICBncm91cF9ieShzaG9wY29kZSkgJT4lIA0KICBzdW1tYXJpc2UoKSAlPiUgDQogIHRtX3NoYXBlKCkgKw0KICB0bV9wb2x5Z29ucyhjb2wgPSAic2hvcGNvZGUiKQ0KfQ0KDQptYXBfY2F0Y2htZW50cygwLjEsIGRpc3RfdGFiKQ0KYGBgDQoNCk5vdyB5b3UgY2FuIGFnYWluIGV4cGVyaW1lbnQgd2l0aCBkaWZmZXJlbnQgJGgkIHZhbHVlcyAtIGZvciBleGFtcGxlIDc1MG06DQoNCmBgYHtyfQ0KbWFwX2NhdGNobWVudHMoMC43NSwgZGlzdF90YWIpDQpgYGANCg0KIyMgQXNzZXNzaW5nIHRoZSBpbXBhY3Qgb2YgYSBwcm9wb3NlZCBzdG9yZS4NCg0KU3VwcG9zZSBub3cgYSBuZXcgc3RvcmUgd2FzIHByb3Bvc2VkIHdpdGggYSBmbG9vciBzcGFjZSBvZiA1MDAwIHNxdWFyZSBtZXRlcnMuIExldCdzIHNlZSB3aGVyZSBpdCB3b3VsZCByYW5nZToNCg0KYGBge3J9DQpncm9uX3Nob3BzICU+JSANCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gRmxvb3JzcGFjZSwgbiA9IDUpDQpgYGANCg0KVGhlcmVmb3JlIGEgZmFpcmx5IGxhcmdlIHN0b3JlIQ0KDQpIb3cgY291bGQgdGhlIGVmZmVjdCBvZiB0aGlzIGJlIGFsbG93ZWQgZm9yPyBUaGUgbWFpbiB3YXkgdG8gc2VlIHRoZSBpbXBhY3QgaXMgdG8gYWRkIHRoZSBuZXcgc3RvcmUgdG8gdGhlIGBncm9uX3Nob3BzYCBzcGF0aWFsIGxheWVyLCBhbmQgdG8gcmUtcnVuIHRoZSBtb2RlbCB3aXRoIHRoaXMgZXh0cmEgc3RvcmUuIFRoaXMgY2FuIHRoZW4gYmUgY29tcGFyZWQgdG8gdGhlIG9yaWdpbmFsIG1vZGVsLg0KDQpUbyBzZWUgdGhpcywgZmlyc3RseSBkb3dubG9hZCBhIG5ldyBzcGF0aWFsIGRhdGEgb2JqZWN0IGBwcm9wb3NlZGA6DQoNCmBgYHtyIGdldHByb3AsIGNhY2hlPVRSVUV9DQojZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vcy9xanp4bDBjcjdlaHRydGkvcHJvcG9zZWQuemlwP2RsPTEiLCJwcm9wb3NlZC56aXAiKQ0KdW56aXAoInByb3Bvc2VkLnppcCIpDQpwcm9wb3NlZF9zaG9wIDwtIHN0X3JlYWQoIi4iLCJwcm9wb3NlZCIpDQpgYGANCg0KVGhlbiwgaGF2ZSBhIGxvb2sgYXQgaXRzIGxvY2F0aW9uIC0gdGhlIHByb3Bvc2VkIHN0b3JlIGlzIGluIHJlZC4NCg0KYGBge3J9DQpncm9uX3Nob3BzICU+JSANCiAgdG1fc2hhcGUoKSArDQogIHRtX2RvdHMoc2l6ZSA9ICJGbG9vcnNwYWNlIiwgY29sID0gImRhcmtvcmNoaWQiLCBhbHBoYSA9IDAuNSkgKw0KICB0bV9zaGFwZShwcm9wb3NlZF9zaG9wKSArDQogIHRtX2RvdHMoc2l6ZSA9ICJGbG9vcnNwYWNlIiwgY29sID0gImluZGlhbnJlZCIsIGFscGhhID0gMC43KQ0KYGBgDQoNClRvIHJlLXJ1biB0aGUgbW9kZWwsIGFnYWluIHVzaW5nIGEgZnVuY3Rpb24gaXMgdXNlZnVsLiBCZWxvdyBpcyBhIGZ1bmN0aW9uIHRoYXQgZW5jYXBzdWxhdGVzICphbGwqIG9mIHRoZSBzdGVwcyBpbiBwcm9kdWNpbmcgYSBtb2RlbCwgZnJvbSB0aGUgcHJvdmlzaW9uIG9mIGEgc2hvcHMgb2JqZWN0IGFuZCBhbiBhZG1pbmlzdHJhdGl2ZSBhcmVhIG9iamVjdCBhcyB3ZWxsIGFzICRoJC4gVGhlIGZ1bmN0aW9uIGxvb2tzIGludGltaWRhdGluZywgYnV0IHJlYWxseSBpdCBpcyBqdXN0IGEgY29tYmluYXRpb24gb2YgdGhlIG9wZXJhdGlvbnMgZG9uZSBhYm92ZSAtIHRoZSBkYXRhIG11bmdpbmcgYW5kIG1vZGVsIGZpdHRpbmcuDQoNCmBgYHtyIGZpdG1vZGVsZnVsbH0NCm5ld19tb2RlbCA8LSBmdW5jdGlvbihhZG1pbixzaG9wcyxoKSB7DQogICMgR2V0IHRoZSBkaXN0YW5jZQ0KICBkbWF0IDwtIGdldF9kaXN0KHNob3BzLGFkbWluKQ0KICANCiAgIyBQaXZvdCBsb25nZXIgdGhlIGRhdGENCiAgYXNfdGliYmxlKHNob3BzKSAlPiUgDQogIHNlbGVjdChzaG9wY29kZSkgJT4lIA0KICBiaW5kX2NvbHMoYXNfdGliYmxlKGRtYXQpKSAlPiUgDQogIHBpdm90X2xvbmdlcighc2hvcGNvZGUsIG5hbWVzX3RvID0gIkJVX0NPREUiLCB2YWx1ZXNfdG8gPSAiRGlzdCIpICU+JSANCiAgbGVmdF9qb2luKHNob3BzLCBieSA9ICJzaG9wY29kZSIpICU+JSANCiAgbGVmdF9qb2luKGFkbWluLCBieSA9ICJCVV9DT0RFIikgJT4lIA0KICBzZWxlY3QoLXN0YXJ0c193aXRoKCJnZW9tZXRyeSIpKSAtPiBkaXN0X3RhYg0KICANCiAgIyBGaW5kIGZsb3cgZmFjdG9yDQogIGRpc3RfdGFiICU+JSANCiAgbXV0YXRlKEZsb3dfRmFjdG9yPShGbG9vcnNwYWNlXjEuMDUpKjJeKC1EaXN0L2gpKSAlPiUgDQogIGdyb3VwX2J5KEJVX0NPREUpICU+JSANCiAgbXV0YXRlKENhc2hfRmxvdyA9IE1vbmV5KkZsb3dfRmFjdG9yL3N1bShGbG93X0ZhY3RvcikpIC0+IHJlcw0KICANCiAgDQogIHJldHVybihyZXMpIA0KfQ0KDQp0ZXN0IDwtIG5ld19tb2RlbChncm9uX3B0cyxncm9uX3Nob3BzLDAuMykNCmhlYWQodGVzdCkNCmBgYA0KDQpIZXJlIHRoZSBmdW5jdGlvbiB0YWtlcyB0aGUgaW5wdXRzIGFuZCByZXR1cm5zIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSBmaXR0ZWQgbW9kZWwgdmFsdWVzLCBpbiB0aGUgZm9ybWF0IG9mIGBzaW1fdGFiYCB1c2VkIGVhcmxpZXIuDQoNClRvIHJlLWZpdCB0aGV3IG1vZGVsIHdpdGggYSBuZXcgc3RvcmUsIGZpcnN0bHkgY3JlYXRlIGEgbmV3IHNjZW5hcmlvIGJ5IGNvbWJpbmluZyB0aGUgbmV3IHByb3Bvc2VkIHN0b3JlIHdpdGggdGhlIGV4aXN0aW5nIG9uZXMgaW4gR3JvbmluZ2VuOg0KDQpgYGB7ciBjb21iaW5lZF9zaG9wc30NCmFsbF9zaG9wcyA8LSBwcm9wb3NlZF9zaG9wICU+JSANCiAgYmluZF9yb3dzKGdyb25fc2hvcHMpDQoNCmFsbF9zaG9wcyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQpgYGANCg0KVGhlbiB1cGRhdGUgdGhlIHNpbXVsYXRpb24gdGFibGU6DQoNCmBgYHtyfQ0KbmV3X3NpbV90YWIgPC0gbmV3X21vZGVsKGdyb25fcHRzLCBhbGxfc2hvcHMsIDAuMikNCg0KbmV3X3NpbV90YWIgJT4lIA0KICBzbGljZV9oZWFkKCkNCmBgYA0KDQpXZSBjYW4gbm93IGNvbXB1dGUgdGhlIGluZmxvd3MgdG8gYWxsIG9mIHRoZSBzdG9yZXMuDQoNCmBgYHtyfQ0KbmV3X3NpbV90YWIgJT4lIA0KICBmaWx0ZXIoc3RyX2RldGVjdChzaG9wLCAiQUh8SGVpam58TmV3IikpICU+JSANCiAgZ3JvdXBfYnkoc2hvcGNvZGUpICU+JSANCiAgc3VtbWFyaXNlKGluX2Zsb3cgPSBzdW0oQ2FzaF9GbG93KSkgJT4lIA0KICBhcnJhbmdlKGRlc2MoaW5fZmxvdykpIC0+IG5ld19pbmZsb3dfdGFiDQoNCg0KbmV3X2luZmxvd190YWIgJT4lIA0KICBzbGljZV9oZWFkKG4gPSA1KQ0KYGBgDQoNCmFuZCB0aGVuIG1hcCB0aGUgaW5mbG93czoNCg0KYGBge3J9DQpuZXdfaW5mbG93X3RhYiAlPiUgDQogIGxlZnRfam9pbihhbGxfc2hvcHMsIGJ5ID0gInNob3Bjb2RlIikgJT4lIA0KICBzdF9zZigpICU+JSANCiAgdG1fc2hhcGUoKSArDQogIHRtX2RvdHMoc2l6ZSA9ICJpbl9mbG93IiwgY29sID0gImRhcmtncmVlbiIsIGFscGhhID0gMC41KQ0KYGBgDQoNCklmIHdlIGpvaW4gdGhlIG9yaWdpbmFsIGBpbmZsb3dfdGFiYCB0byB0aGUgYG5ld19pbmZsb3dfdGFiYCB1c2luZyB0aGUgc2hvcCBjb2RlIGFzIGEgbWVyZ2Uga2V5IHdlIG9idGFpbiBhIHRhYmxlIHdpdGggbmV3IGFuZCBvbGQgY2FzaCBpbmZsb3cgZXN0aW1hdGVzLCByZXNwZWN0aXZlbHkgY2FsbGVkIGBpbmZsb3cueGAgYW5kIGBpbmZsb3cueWAuDQoNClRoZSBwcm9wb3NlZCBzdG9yZSwgYW5kIHNvIGRvZXNuJ3QgaGF2ZSBhbiBvcmlnaW5hbCBpbmZsb3cuIEhlcmUgaXQgbWFrZXMgc2Vuc2UgdG8gc2V0IHRoaXMgYXMgemVybzoNCg0KYGBge3J9DQpuZXdfaW5mbG93X3RhYiAlPiUgDQogIGxlZnRfam9pbihpbmZsb3dfdGFiLCBieSA9ICJzaG9wY29kZSIpICU+JQ0KICAjIFJlcGxhY2UgYSAwIGFueXdoZXJlIHRoZXJlIG1heSBiZSBhbiBOQQ0KICBtdXRhdGUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgfnJlcGxhY2VfbmEoLngsIDApKSkgJT4lIA0KICAjIENvbXB1dGUgZGlmZmVyZW5jZQ0KICBtdXRhdGUoRGlmZiA9IChpbl9mbG93LnggLSBpbl9mbG93LnkpKSAtPiBkaWZmX2luZmxvd190YWINCg0KDQojIFdoaWNoIHN0b3JlcyBoYXZlIGJlZW4gaW1wYWN0ZWQgbW9zdCBieSB0aGUgbmV3IHN0b3JlDQpkaWZmX2luZmxvd190YWIgJT4lIA0KICBzbGljZV9tYXgob3JkZXJfYnkgPSBhYnMoRGlmZiksIG4gPSA1KQ0KYGBgDQoNCkZpbmFsbHkgdG8gbWFwIHRoaXMgYXMgcHJvcG9ydGlvbmFsIHN5bWJvbHMsIHNpbmNlIHNvbWUgdmFsdWVzIGFyZSBwb3NpdGl2ZSBhbmQgb3RoZXJzIGFyZSBuZWdhdGl2ZSwgY3JlYXRlIGFuIGFic29sdXRlIHZhbHVlIG9mIGNoYW5nZSBjb2x1bW4gLSBhbmQgdGhlbiBtYXAgdGhpcw0KDQpgYGB7cn0NCmRpZmZfaW5mbG93X3RhYiAlPiUgDQogIG11dGF0ZShhYnNfZGlmZiA9IGFicyhEaWZmKSkgJT4lIA0KICBsZWZ0X2pvaW4oYWxsX3Nob3BzLCBieSA9ICJzaG9wY29kZSIpICU+JSANCiAgcmVsb2NhdGUoc2hvcCkgJT4lIA0KICBzdF9zZigpICU+JSANCiAgdG1fc2hhcGUoKSArDQogIHRtX2RvdHMoc2l6ZSA9ICJhYnNfZGlmZiIsIGNvbCA9ICJEaWZmIikNCg0KYGBgDQoNClRoaXMgaGVscHMgdG8gaWRlbnRpZnkgdGhlIHN0b3JlcyB0aGF0IHdpbGwgZXhwZXJpZW5jZSB0aGUgbW9zdCBpbXBhY3QgaWYgYSBuZXcgc3RvcmUgaXMgb3BlbmVkIGFzIHByb3Bvc2VkLg0KDQojIyBSZXNvdXJjZXMNCg0KLSAgIFtNYWtpbmcgbWFwcyB3aXRoIFJdKGh0dHBzOi8vYm9va2Rvd24ub3JnL25pY29oYWhuL21ha2luZ19tYXBzX3dpdGhfcjUvZG9jcy9pbnRyb2R1Y3Rpb24uaHRtbCkNCg0KLSAgIFtHZW9jb21wdXRhdGlvbiB3aXRoIFJdKGh0dHBzOi8vZ2VvY29tcHIucm9iaW5sb3ZlbGFjZS5uZXQvYWR2LW1hcC5odG1sKQ0KDQotICAgW0VsZWdhbnQgYW5kIGluZm9ybWF0aXZlIG1hcHMgd2l0aCAqKnRtYXAqKl0oaHR0cHM6Ly9yLXRtYXAuZ2l0aHViLmlvL3RtYXAtYm9vay8pDQo=