Introduction
This session demonstrates some of R’s capability for spatial analysis by showing how to create spatial weightings based on contiguity, nearest neighbours and by distance. Methods of inverse distance weighting are also introduced and used to produce Moran plots and tests of spatial dependency in data, including the results of regression analysis.
To get started:
if(!"sp" %in% installed.packages()) install.packages("sp")
if(!"sf" %in% installed.packages()) install.packages("sf")
rm(list=ls()) # Deletes everything in the workspace - use with care!
require(sp)
load("spatial-weights.RData")
ls()
This will load an R workspace containing two objects, combined.map and districts. The first is if class,
class(combined.map)
and contains (synthetic) information about land parcels in Beijing. Each land parcel is represented by a point centroid. The second is of class
class(districts)
and contains some information about districts within Beijing. The sp objects can be converted to spatial features using
require(sf)
st_as_sf(combined.map) -> sf_combined.map
st_as_sf(districts) -> sf_districts
and we can take a glimpse of the data:
if(!"tidyverse" %in% installed.packages()) install.packages("tidyverse")
require(tidyverse)
sf_combined.map %>%
glimpse()
sf_districts %>%
glimpse()
The variables are as follow:
LNPRICE: Log of the land price: RMB per square metre LNAREA: Log of the land parcel size DCBD: distance to the CBD on a log scale DELE: distance to the nearest elementary school on a log scale DRIVER: distance to the nearest river on a log scale DPARK: distance to the nearest park on a log scale Y0405: A dummy variable (1 or 0) indicating whether the land was sold in the period 2004-5 Y0607: A dummy variable indicating whether the land was sold in the period 2006-7 Y0809: A dummy variable indicating whether the land was sold in the period 2007-8 (All of the remaining land parcels were sold before 2004) SP_ID: An ID for the polygon (district) in which the land parcel is located POPDEN: the population density in each district. data source: the fifth census data in 2000. JOBDEN the jobden density in each district. data source: the fifth census data in 2000.
Identifying Contiguous neighbours
A contiguity matrix is one that identifies polygons that share boundaries and (in what is called the Queen’s case) corners too. In other words, it identifies neighbouring areas. To do this we use the poly2nb(…) function – polygons to an object of class neighbours - in the spdep (spatial dependency) library. A current problem is that spdep expects sp objects not sf ones! Undoubtedly this will change but, for now, the conversion of the sp object to an sf one was premature so we shall use the originals.
if(!"spdep" %in% installed.packages()) install.packages("spdep")
require(spdep)
contig <- poly2nb(districts)
summary(contig)
The summary of the neighbours object (which is of class nb) shows that there are 134 regions (districts) – the same number as when we glimpsed the data, above – with each being linked to 5.46 others, on average. There are two regions with no links and two regions with 10 links. The Queen’s case is assumed by default, see ?poly2nb.
It is helpful to learn a little more about the structure of the contiguity object. It is an object of class nb which is itself a type of list. The function str() is sometimes useful to take a look at the structure of an object in R.
class(contig)
typeof(contig)
str(contig)
If we look at the first item in the list, we obtain the following:
contig[[1]]
What this is showing is that district 1 has two contiguous neighbours, which are the 52nd and 54th entries in the list. Reciprocally,
contig[[52]]
contig[[54]]
which both include 1 (as they should – the relationship is symmetric: if \(a\) shares a border with \(b\) then \(b\) shares a border with \(a\)).
Going back to the summary above, region 20 has no links. However,
contig[[20]]
suggests that it actually has four. The confusion here is that polygon 20 in the original sp object (and the shapefile before that) is actually the 21st entry in the list. We can get a sense of this if we look at,
head(districts$SP_ID)
and note that the numbering begins with zero, not one. Note also that the tidyverse is not implemented with sp classes but is with sf. Hence, the following generates an error,
districts %>%
dplyr::select(SP_ID) %>%
print(n = 3)
but the following – with a conversion to sf - works fine:
districts %>%
st_as_sf(.) %>%
dplyr::select(SP_ID) %>%
print(n = 3)
In any case, if region 20 is actually the 21st in the list then the following should have zero neighbours,
contig[[21]]
… and it does! We can confirm this by plotting the source data,
plot(districts[-21,], col="red")
plot(districts[21,], col="blue", add=T)
Or, using the simple feature version of the object that was created earlier,
plot(districts[-21,0], col="red")
plot(districts[21,0], col="blue", add=T)
- If you are eagle-eyed, you may notice that the code block above includes a zero (e.g. [-21,0]) where as the code above does not (e.g. [21,]). Try deleting the 0 in the code block above and see what happens!
K nearest neighbours (knn)
It does not make sense sense to evaluate contiguity of point data because points are zero dimensional (and have no borders). Instead, we could find, for example, the six nearest neighbours to each point. There are various ways to do this, including using the RANN package:
if(!"RANN" %in% installed.packages()) install.packages("RANN")
require(RANN)
pts <- coordinates(combined.map)
knn6 <- nn2(pts, pts, k = 6)
Looking at the attributes of the object knn6, it shows that nn2 creates two outputs, one with name nn.idx, the other with name nn.dists.
attributes(knn6)
str(knn6)
If we are interested in identifying the six nearest neighbours to point 1 then they are,
knn6$nn.idx[1,]
and their distances from the point are
knn6$nn.dists[1,]
Note that the six nearest neighbours to point 1 include point 1 (and the six nearest neighbours to point 2 include point 2, etc.). This isn’t necessarily what is wanted and a problem that spdep’s knearneigh() function avoids. It also can work directly on the sp object:
knear6 <- knearneigh(combined.map, k=6)
attributes(knear6)
knear6$nn[1,]
However, unlike RANN (which, actually, it is using), it doesn’t retain the distances to the nearest neighbours.
Spatial dependency exists in a set of data if the value at one location is not independent of values at surrounding locations – if there is a geographical pattern in the data. Imagine we are interested in calculating the correlation between some variable (call it \(x\)) at each of the points and at each of the points’ closest neighbour. Using the simulated data about the price of land parcels in Beijing, we can calculate this correlation as follows:
x <- combined.map$LNPRICE
cor(x, x[knear6$nn[,1]])
Similarly, the correlation with the second nearest neighbour is
x <- combined.map$LNPRICE
cor(x, x[knear6$nn[,2]])
and the correlations from the first to sixth neighbour are
r <- sapply(1:6, function(i) {
cor(x, x[knear6$nn[,i]])
})
df <- data.frame(k = 1:6, r = r)
print(df)
(All this is a function where the variable \(i\) takes the values from 1 to 6, in turn, so, in the first instance, it is calculating, cor(x, x[knear6$nn[,1]]) then cor(x, x[knear6$nn[,2]]) and so forth up to cor(x, x[knear6$nn[,6]])
Plotting these,
if(!"ggplot2" %in% installed.packages()) install.packages("ggplot2")
require(ggplot2)
df %>%
ggplot(aes(x = k, y = r)) +
geom_line() +
xlab("kth nearest neighbour") +
ylab("Correlation, r")
What these values suggest is that even at the sixth nearest neighbour, the value of a land parcel at any given point tends to be similar to the value of the land parcels around it –- an example of positive spatial autocorrelation. However, the level of similarity is highest for the nearest neighbour and decreases the further the neighbour is away.
An issue is that the threshold of six nearest neighbours is purely arbitrary. An interesting question is how far – how many neighbours away – we can typically go from a point and still find a similarity in the land price values. One way to determine this would be to carry on with the calculations above, repeating the procedure until we get to, say, the 250th nearest neighbour.
knear250 <- knearneigh(combined.map, k=250)
r <- sapply(1:250, function(i) {
cor(x, x[knear250$nn[,i]])
})
data.frame(k = 1:250, r = r) %>%
ggplot(aes(x = k, y = r)) +
geom_line() +
geom_smooth(se = FALSE) +
xlab("kth nearest neighbour") +
ylab("Correlation, r")
Looking at the plot we find that the land prices become more dissimilar (less correlated) the further away we go from each point, dropping to zero correlation from after about the 200th nearest neighbour. The rate of decrease in the correlation is greatest to about the 35th neighbour, after which it begins to flatten.
We can also determine the p-values associated with each of these correlations and identify which are not significant at a 99% confidence:
pvals <- sapply(1:250, function(i) {
cor.test(x, x[knear250$nn[,i]])$p.value
})
which(pvals > 0.01)
It is from about the 100th neighbour that the correlations begin to become regularly insignificant. Whether this is usefully information or not is a moot point: a measure of statistical significance is really only an indirect measure of the sample size. It may be better to make a decision about the threshold at which the neighbours are not substantively correlated based on the actual correlations (the effect sizes) rather than their p-values. Whilst it remains a subjective choice, here we will here use the 35th neighbour as the limit, before which the correlations are typically equal to r = 0.20 or greater.
knear35 <- knearneigh(combined.map, k=35)
class(knear35)
The object is of class knn. To convert it to class nb (the same as the contiguity object from earlier),
knear35nb <- knn2nb(knear35)
knear35nb
Note that this is a non-symmetric list – just because \(a\) is one of the 35 nearest neighbours to \(b\), it does not follow that \(b\) must be one of the 35 nearest neighbours to \(a\).
Identifying neighbours by (physical) distance apart
It is also possible to identify the neighbours of points by their Euclidean or Great Circle distance apart using the function dnearneigh(). For example, if we wanted to identify all points between 100 and 1000 metres of each other:
d100to1000 <- dnearneigh(combined.map, 100, 1000)
d100to1000
- On average, how many regions are between 100 and 1000 metres of another?
Creating a Spatial Weights matrix
What we have done so far is use various methods to create a list of neighbours with flexibility to decide about what counts as a neighbour. The next stage is to convert that list into a spatial weights matrix so we can use it for various methods of spatial analysis. This extra stage of conversion may seem like an unnecessary additional chore. However, the creation of the spatial weights matrix allows us to define the strength of relationship between neighbours. For example, we may want to give more weight to neighbours that are located closer together and less weight to those that are further apart (decreasing to zero beyond a certain threshold). The nb object defines which are neighbours. Converting it to a spatial weights object says how strongly they are neighbours.
Creating a binary list of weights
We could create a simple binary ‘matrix’ from any of our existing lists of neighbours. In principle:
spcontig <- nb2listw(contig, style="B")
Note, however, the error message, which arises because two of the Chinese districts do not share a boundary with others. Correcting for this,
spcontig <- nb2listw(contig, style="B", zero.policy=T)
The same problem does not arise for the one hundred nearest neighbours (by definition, it cannot – each point has neighbours) but it does for the distance based list if empty sets are not permitted (they are permitted by setting zero.policy = T):
spknear35 <- nb2listw(knear35nb, style="B")
spd100to1000 <- nb2listw(d100to1000, style="B", zero.policy=T)
What we create are objects of class listw (a list of spatial weights). Looking at the first of these objects we can see how it has been constructed. It contains binary weights (style B); district 1 has two neighbours, districts 52 and 54; and both of those have been given a weight of one (all other districts therefore have a weight of zero with district 1). Similarly, district 2 has neighbours 3, 4, 6, 999 and 100, each with a weight of one.
class(spcontig)
typeof(spcontig)
names(spcontig)
spcontig$style
spcontig$neighbours[[1]]
spcontig$weights[[1]]
spcontig$neighbours[[2]]
spcontig$weights[[2]]
Creating a row-standardised list of weights
Using binary weights where (1) indicates two places are neighbours, and (0) indicates they are not, may create a problem when different places have different numbers of neighbours (as is the case for both the contiguity and distance-based approaches). Imagine a calculation where the result is in some way dependent upon the sum of the weights involved. All things being equal, we expect places with more neighbours to generate larger values of y simply because they have more non-zero values contributing to the sum. A way around this problem is to scale the weights so that for any one place they sum to one – a process known as row-standardisation and actually the default option:
spcontig <- nb2listw(contig, zero.policy=T)
names(spcontig)
spcontig$style
spcontig$neighbours[[1]]
spcontig$weights[[1]]
spcontig$neighbours[[2]]
spcontig$weights[[2]]
In the case of the contiguity matrix, district 1 still has neighbours 52 and 54, and district 2 still has neighbours 3, 4, 6, 99 and 100 but the weights are now row-standardised (style W) and in each case they sum to one.
Creating an inverse distance weighting (IDW)
A more ambitious undertaking is to decrease the weighting given to two points according to their distance apart, reducing to zero, for example, beyond the 35th nearest neighbour. To achieve this, we can use the RANN package from earlier to find the distances (another possibility is spDists – see ?spDists) then assign the weights based on the inverse of those distances.
pts <- coordinates(combined.map) # Get the centroid of each district
knn <- nn2(pts, pts, k = 36)
# Recall that using RANN includes each district as its own neighbour
d <- knn$nn.dists # The distances
d <- d[,-1] # Delete the first column of zero distances
idw <- 1/d # the inverse of distance
glist <- lapply(1:nrow(idw), function(i) idw[i,])
# This is just a way of converting the inverse distances into weights
knear <- knearneigh(as(combined.map, "SpatialPoints"), k=35)
# For compariability, this is also using the centroid of each district
knearnb <- knn2nb(knear) # Create a neighbourhood list
spknear35IDW <- nb2listw(knearnb, glist=glist)
# Combine the neighbourhood list with the inverse distance weighting
That isn’t the most efficient way of doing it but it appears to more-or-less work because if we look at the weights assigned to the 35 neighbours of the first district they are decreasing with distance:
spknear35IDW$weights[[1]]
Why more-or-less? Because the weights are row-standardised! This means that the inverse distance weighting is a function of the local distribution of points around each point not just how far away they are. For example, consider a point where all its neighbours are quite far from it. Using a strictly distance-based weighting each of those neighbours should receive a low weighting. However, once row standardisation is applied those low weights will be scaled upwards to sum to one. Reciprocally, imagine a point where all its neighbours are very close. Using a distance-based weighting those neighbours should receive a relatively high weighting; in effect, though, they will be scaled downwards by the row standardisation. This may sound undesirable and counter to the objectives of inverse distance weighting, and can be prevented by changing the weights style,
spknear35IDW <- nb2listw(knearnb, glist=glist, style = "C")
spknear35IDW$weights[[1]]
However, imagine the points sample across both urban and rural areas. The distances between points will most likely be smaller in the urban regions (where the density of points is greater, reflecting the greater population density), with greater distances between points in the rural regions. If row standardisation is not applied then the net result will be to give more weight to the urban parts of the region such that any subsequent calculation dependent upon the sum of the weights will be more strongly influenced by the urban areas than by the rural ones. Therefore careful though needs to be given to the style of weights to use.
Variants of IDW
Common forms of inverse distance weighting include the bisquare and Gaussian functions. Adapting the code above, a bisquare version is
pts <- coordinates(combined.map)
knn <- nn2(pts, pts, k = 36)
d <- knn$nn.dists
d <- d[,-1]
glist <- lapply(1:nrow(d), function(i) {
max.d <- d[i,35]
1 - (d[i,] / max.d)
# The above calculates the bisquare weighting
})
knear <- knearneigh(as(combined.map, "SpatialPoints"), k=35)
knearnb <- knn2nb(knear)
spknear35bisq <- nb2listw(knearnb, glist=glist, style="C")
… and a Gaussian version is
pts <- coordinates(combined.map)
knn <- nn2(pts, pts, k = 36)
d <- knn$nn.dists
d <- d[,-1]
glist <- lapply(1:nrow(d), function(i) {
max.d <- d[i,35]
exp(-0.5*d[i,]^2 / max.d^2)
# The above calculates the bisquare weighting
})
knear <- knearneigh(as(combined.map, "SpatialPoints"), k=35)
knearnb <- knn2nb(knear)
spknear35gaus <- nb2listw(knearnb, glist=glist, style="C")
A useful illustration of the types of weighting and how they differ can be found as Figure 1 of GWmodel: An R Package for Exploring Spatial Heterogeneity Using Geographically Weighted Models. Generally the shape (the rate of decay) matters less than the number of neighbours that are included.
Using Spatial Weights
Creating a spatially lagged variable
Once we have the spatial weights we can use them to create a spatially lagged variable. For example, if \(x_i\) is the value of the land parcel at point \(i\), then its spatial lag is the mean value of the land parcels that are the neighbours of \(i\), where those neighbours are defined by the spatial weights. More precisely, it is the weighted mean value if, for example, inverse distance weighting has been employed. It is straightforward to calculate the spatially lagged variable. For example,
x <- combined.map$LNPRICE
lagx <- lag.listw(spknear35gaus, x)
Having done so, the correlation between points and their neighbours can be calculated,
cor.test(x, lagx)
Here there is evidence of significant positive spatial autocorrelation -– that the land price at one point tends to be similar to the land prices of its neighbours. This can be seen if we plot the two variables on a scatter plot, although the relationship is also somewhat noisy and may not be linear.
data.frame(x = x, lagx = lagx) %>%
ggplot(aes(x=x, y=lagx)) +
geom_point(size = 0.5) +
geom_smooth(method = "lm", se = FALSE) +
geom_vline(xintercept = mean(x), linetype = "dotted") +
geom_hline(yintercept = mean(lagx), linetype = "dotted")
A Moran plot and test
The plot created above is known as a Moran plot. A more direct way of producing it is to use the moran.plot() function,
moran.plot(x, spknear35gaus)
which flags potential outliers / influential observations. To suppress their labelling, include the argument labels=F – try it!
The Moran coefficient and related test provide a measure of the spatial autocorrelation in the data, given the spatial weightings.
moran.test(x, spknear35gaus)
Essentially the Moran statistic is a correlation value, although it need not be exactly zero in the presence of no correlation (here the expected value is not zero but slightly negative) and need not be in the range -1 to +1. The interpretation though is that the price of the land parcels and their neighbours are positively correlated: there is a tendency for like-near-like values.
More strictly, we should acknowledge the note found under ?moran.test() that the derivation of the tests assumes the matrix is symmetric, which spknear35gaus is not. To correct for this we can use a helper function, listw2U(),
moran.test(x, listw2U(spknear35gaus))
What is the range of the Moran’s value?
XXXX observe that the potential range for a Moran’s value is dependent upon the spatial weights matrix. Using their code:
moran.range <- function(lw) {
wmat <- listw2mat(lw)
return(range(eigen((wmat + t(wmat))/2)$values))
}
moran.range(spknear35gaus)
moran.range(spknear35bisq)
moran.range(spcontig)
moran.range(nb2listw(contig, zero.policy = TRUE))
None of these inspire confidence that Moran values typically lie in the range -1 to +1 (either that or the method to determine the extremes is wrong but I have no evidence that is the case). Increasingly I wonder if it would be easier to use, e.g.
x <- combined.map$LNPRICE
r <- cor(x, lag.listw(spknear35gaus, x))
r
… which is just the Pearson correlation between the variable and its lagged equivalent and will lie in the range from -1 to +1. Calculating the significance of the value can be done by simulation. For example,
listw <- spknear35gaus
x <- combined.map$LNPRICE
nsims <- 1000
vals <- sapply(1:nsims, function(i) {
xx <- sample(x, length(x), replace = FALSE)
cor(xx, lag.listw(listw, xx, zero.policy = TRUE))
})
… calculates the correlation 1000 times with random re-arrangements of the data (where the same set of measurements is used, the neighbours remain the same as does their weighting, but the data are shuffled around geographically). If the actual correlation, \(r\), is greater than 99% of the randomly generated values then we have reason to believe that the positive spatial autocorrelation between the values and their neighbours (as defined using the particular spatial weights matrix) is ‘significant’ at above a 99% level.
r > quantile(vals, probs = 0.99)
Checking a regression model for spatially correlated errors
Assume we fit the regression model,
model1 <- lm(LNPRICE ~ DCBD + DELE + DRIVER + DPARK + POPDEN + JOBDEN + Y0405 + Y0607 + Y0809, data=combined.map)
An assumption of regression is that the residuals are supposed to be left over and random ‘noise’. If that is true, they should be independent of one another and not display any spatial pattern. Is that the case here? One way to check is by mapping them:
resids <- rstudent(model1)
resids <- cut(resids, breaks = c(-Inf, -2.58, -1, 1, 2.58, Inf))
combined.map$resids <- resids
require(tmap)
tmap_style("col_blind")
tm_shape(districts) +
tm_polygons() +
tm_shape(combined.map) +
tm_bubbles("resids", size = 0.05)
I would say that it is hard to judge. An alternative is to use a Moran’s test, ideally the version of the test intended for use on regression residuals.
lm.morantest(model1, listw2U(spknear35gaus))
# listw2U is used because the weights are not symmetrical
The estimated correlation is about 0.097. Not huge, perhaps but significant enough to question the assumption of independence. Note that this result is dependent on the spatial weightings. If we change them, then the results of the Moran test will change also. For example, using the bi-square weightings,
lm.morantest(model1, listw2U(spknear35bisq))
Here the change is slight, largely because the rate of decay of the inverse distance weighting matters less than the number of neighbours it decays to.
Localised Moran values
The Moran’s value is an example of a global statistic, which is one calculated for the whole study region. It is a single (an average) measure of spatial autocorrelation between neighbours. It can, however, by ‘unpacked’ to give a series of local Moran values – one for each location in the data:
localm <- localmoran(combined.map$LNPRICE, listw2U(spknear35gaus))
head(localm)
This produce a matrix, where the first column contains the local Moran values. A statistical test of significance also is included.
A positive local Moran value indicates a local cluster of alike values; a negative Moran value indicates a cluster of dissimilar values. What they do not distinguish between is whether, for example, high land price values are surrounded by other high land prices or whether low land prices are surrounded by other low land prices – both are examples of positive spatial autocorrelation (similar values clustering together). Equally, they do not distinguish between whether low prices are surrounded by high prices or high prices by low (both are examples of negative spatial autocorrelation).
To distinguish them, we need to refer to the original data (let’s call it \(x\)). If \(x\) is above the mean and the local Moran value is positive and significant then we might argue that it is a ‘hot spot’ of a high value surrounded by other high values: high-high. If \(x\) is below the mean and the local Moran value is positive and significant then we could say it is a ‘cold spot’ of a low value surrounded by other low values: low-low. If \(x\) is above the mean but the Moran value is negative and significant then high-low; if \(x\) is below the mean but the Moran value is negative and significant then low-high:
x <- combined.map$LNPRICE
localm <- localmoran(combined.map$LNPRICE, listw2U(spknear35gaus))
type <- rep(NA, times = nrow(combined.map))
type <- ifelse(x > mean(x) & localm[,1] > 0 & localm[,1] < 0.05,
"High - High", type)
type <- ifelse(x > mean(x) & localm[,1] < 0 & localm[,1] < 0.05,
"High - Low", type)
type <- ifelse(x < mean(x) & localm[,1] < 0 & localm[,1] < 0.05,
"Low - Low", type)
type <- ifelse(x < mean(x) & localm[,1] > 0 & localm[,1] < 0.05,
"Low - High", type)
combined.map$type <- type
sub.map <- combined.map[!is.na(type),]
tmap_style("cobalt")
tm_shape(districts) +
tm_polygons() +
tm_shape(sub.map) +
tm_bubbles("type", size = 0.05)
Moran values can also be calculated for regression residuals. For example,
localmr <- localmoran.exact(model1, nb = knn2nb(knearneigh(combined.map, k = 35)))
See ?localmoran.exact for further details.
LS0tCnRpdGxlOiAiTWFwcGluZyBBbmQgTW9kZWxsaW5nIEdlb2dyYXBoaWMgRGF0YSBJbiBSIgpzdWJ0aXRsZTogIlNlc3Npb24gNDogU3BhdGlhbCB3ZWlnaHRzIGFuZCBuZWlnaGJvdXJzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnRyb2R1Y3Rpb24KClRoaXMgc2Vzc2lvbiBkZW1vbnN0cmF0ZXMgc29tZSBvZiBSJ3MgY2FwYWJpbGl0eSBmb3Igc3BhdGlhbCBhbmFseXNpcyBieSBzaG93aW5nIGhvdyB0byBjcmVhdGUgc3BhdGlhbCB3ZWlnaHRpbmdzIGJhc2VkIG9uIGNvbnRpZ3VpdHksIG5lYXJlc3QgbmVpZ2hib3VycyBhbmQgYnkgZGlzdGFuY2UuIE1ldGhvZHMgb2YgaW52ZXJzZSBkaXN0YW5jZSB3ZWlnaHRpbmcgYXJlIGFsc28gaW50cm9kdWNlZCBhbmQgdXNlZCB0byBwcm9kdWNlIE1vcmFuIHBsb3RzIGFuZCB0ZXN0cyBvZiBzcGF0aWFsIGRlcGVuZGVuY3kgaW4gZGF0YSwgaW5jbHVkaW5nIHRoZSByZXN1bHRzIG9mIHJlZ3Jlc3Npb24gYW5hbHlzaXMuCgpUbyBnZXQgc3RhcnRlZDoKCmBgYHtyfQppZighInNwIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKSBpbnN0YWxsLnBhY2thZ2VzKCJzcCIpCmlmKCEic2YiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkpIGluc3RhbGwucGFja2FnZXMoInNmIikKcm0obGlzdD1scygpKSAjIERlbGV0ZXMgZXZlcnl0aGluZyBpbiB0aGUgd29ya3NwYWNlIC0gdXNlIHdpdGggY2FyZSEKcmVxdWlyZShzcCkKbG9hZCgic3BhdGlhbC13ZWlnaHRzLlJEYXRhIikKbHMoKQpgYGAKClRoaXMgd2lsbCBsb2FkIGFuIFIgd29ya3NwYWNlIGNvbnRhaW5pbmcgdHdvIG9iamVjdHMsIGNvbWJpbmVkLm1hcCBhbmQgZGlzdHJpY3RzLiBUaGUgZmlyc3QgaXMgaWYgY2xhc3MsCgpgYGB7cn0KY2xhc3MoY29tYmluZWQubWFwKQpgYGAKCmFuZCBjb250YWlucyAoc3ludGhldGljKSBpbmZvcm1hdGlvbiBhYm91dCBsYW5kIHBhcmNlbHMgaW4gQmVpamluZy4gRWFjaCBsYW5kIHBhcmNlbCBpcyByZXByZXNlbnRlZCBieSBhIHBvaW50IGNlbnRyb2lkLiBUaGUgc2Vjb25kIGlzIG9mIGNsYXNzCgpgYGB7cn0KY2xhc3MoZGlzdHJpY3RzKQpgYGAKCmFuZCBjb250YWlucyBzb21lIGluZm9ybWF0aW9uIGFib3V0IGRpc3RyaWN0cyB3aXRoaW4gQmVpamluZy4gVGhlIHNwIG9iamVjdHMgY2FuIGJlIGNvbnZlcnRlZCB0byBzcGF0aWFsIGZlYXR1cmVzIHVzaW5nCgpgYGB7cn0KcmVxdWlyZShzZikKc3RfYXNfc2YoY29tYmluZWQubWFwKSAtPiBzZl9jb21iaW5lZC5tYXAKc3RfYXNfc2YoZGlzdHJpY3RzKSAtPiBzZl9kaXN0cmljdHMKYGBgCgphbmQgd2UgY2FuIHRha2UgYSBnbGltcHNlIG9mIHRoZSBkYXRhOgoKYGBge3J9CmlmKCEidGlkeXZlcnNlIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKSBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQpyZXF1aXJlKHRpZHl2ZXJzZSkKc2ZfY29tYmluZWQubWFwICU+JQogIGdsaW1wc2UoKQpzZl9kaXN0cmljdHMgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKVGhlIHZhcmlhYmxlcyBhcmUgYXMgZm9sbG93OgoKTE5QUklDRTogTG9nIG9mIHRoZSBsYW5kIHByaWNlOiBSTUIgcGVyIHNxdWFyZSBtZXRyZSAKTE5BUkVBOiBMb2cgb2YgdGhlIGxhbmQgcGFyY2VsIHNpemUgCkRDQkQ6IGRpc3RhbmNlIHRvIHRoZSBDQkQgb24gYSBsb2cgc2NhbGUgCkRFTEU6IGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IGVsZW1lbnRhcnkgc2Nob29sIG9uIGEgbG9nIHNjYWxlIApEUklWRVI6IGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IHJpdmVyIG9uIGEgbG9nIHNjYWxlIApEUEFSSzogZGlzdGFuY2UgdG8gdGhlIG5lYXJlc3QgcGFyayBvbiBhIGxvZyBzY2FsZQpZMDQwNTogQSBkdW1teSB2YXJpYWJsZSAoMSBvciAwKSBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGxhbmQgd2FzIHNvbGQgaW4gdGhlIHBlcmlvZCAyMDA0LTUKWTA2MDc6IEEgZHVtbXkgdmFyaWFibGUgaW5kaWNhdGluZyB3aGV0aGVyIHRoZSBsYW5kIHdhcyBzb2xkIGluIHRoZSBwZXJpb2QgMjAwNi03ClkwODA5OiBBIGR1bW15IHZhcmlhYmxlIGluZGljYXRpbmcgd2hldGhlciB0aGUgbGFuZCB3YXMgc29sZCBpbiB0aGUgcGVyaW9kIDIwMDctOAooQWxsIG9mIHRoZSByZW1haW5pbmcgbGFuZCBwYXJjZWxzIHdlcmUgc29sZCBiZWZvcmUgMjAwNCkKU1BfSUQ6IEFuIElEIGZvciB0aGUgcG9seWdvbiAoZGlzdHJpY3QpIGluIHdoaWNoIHRoZSBsYW5kIHBhcmNlbCBpcyBsb2NhdGVkClBPUERFTjogdGhlIHBvcHVsYXRpb24gZGVuc2l0eSBpbiBlYWNoIGRpc3RyaWN0LiBkYXRhIHNvdXJjZTogdGhlIGZpZnRoIGNlbnN1cyBkYXRhIGluIDIwMDAuIApKT0JERU4gdGhlIGpvYmRlbiBkZW5zaXR5IGluIGVhY2ggZGlzdHJpY3QuIGRhdGEgc291cmNlOiB0aGUgZmlmdGggY2Vuc3VzIGRhdGEgaW4gMjAwMC4KCiMjIElkZW50aWZ5aW5nIENvbnRpZ3VvdXMgbmVpZ2hib3VycwoKQSBjb250aWd1aXR5IG1hdHJpeCBpcyBvbmUgdGhhdCBpZGVudGlmaWVzIHBvbHlnb25zIHRoYXQgc2hhcmUgYm91bmRhcmllcyBhbmQgKGluIHdoYXQgaXMgY2FsbGVkIHRoZSBRdWVlbidzIGNhc2UpIGNvcm5lcnMgdG9vLiBJbiBvdGhlciB3b3JkcywgaXQgaWRlbnRpZmllcyBuZWlnaGJvdXJpbmcgYXJlYXMuIFRvIGRvIHRoaXMgd2UgdXNlIHRoZSBwb2x5Mm5iKC4uLikgZnVuY3Rpb24g4oCTIHBvbHlnb25zIHRvIGFuIG9iamVjdCBvZiBjbGFzcyBuZWlnaGJvdXJzIC0gaW4gdGhlIHNwZGVwIChzcGF0aWFsIGRlcGVuZGVuY3kpIGxpYnJhcnkuIEEgY3VycmVudCBwcm9ibGVtIGlzIHRoYXQgc3BkZXAgZXhwZWN0cyBzcCBvYmplY3RzIG5vdCBzZiBvbmVzISBVbmRvdWJ0ZWRseSB0aGlzIHdpbGwgY2hhbmdlIGJ1dCwgZm9yIG5vdywgdGhlIGNvbnZlcnNpb24gb2YgdGhlIHNwIG9iamVjdCB0byBhbiBzZiBvbmUgd2FzIHByZW1hdHVyZSBzbyB3ZSBzaGFsbCB1c2UgdGhlIG9yaWdpbmFscy4KCmBgYHtyfQppZighInNwZGVwIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKSBpbnN0YWxsLnBhY2thZ2VzKCJzcGRlcCIpCnJlcXVpcmUoc3BkZXApCmNvbnRpZyA8LSBwb2x5Mm5iKGRpc3RyaWN0cykKc3VtbWFyeShjb250aWcpCmBgYAoKVGhlIHN1bW1hcnkgb2YgdGhlIG5laWdoYm91cnMgb2JqZWN0ICh3aGljaCBpcyBvZiBjbGFzcyBuYikgc2hvd3MgdGhhdCB0aGVyZSBhcmUgMTM0IHJlZ2lvbnMgKGRpc3RyaWN0cykgLS0gdGhlIHNhbWUgbnVtYmVyIGFzIHdoZW4gd2UgZ2xpbXBzZWQgdGhlIGRhdGEsIGFib3ZlIC0tIHdpdGggZWFjaCBiZWluZyBsaW5rZWQgdG8gNS40NiBvdGhlcnMsIG9uIGF2ZXJhZ2UuIFRoZXJlIGFyZSB0d28gcmVnaW9ucyB3aXRoIG5vIGxpbmtzIGFuZCB0d28gcmVnaW9ucyB3aXRoIDEwIGxpbmtzLiBUaGUgUXVlZW4ncyBjYXNlIGlzIGFzc3VtZWQgYnkgZGVmYXVsdCwgc2VlID9wb2x5Mm5iLgoKSXQgaXMgaGVscGZ1bCB0byBsZWFybiBhIGxpdHRsZSBtb3JlIGFib3V0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGNvbnRpZ3VpdHkgb2JqZWN0LiBJdCBpcyBhbiBvYmplY3Qgb2YgY2xhc3MgbmIgd2hpY2ggaXMgaXRzZWxmIGEgdHlwZSBvZiBsaXN0LiBUaGUgZnVuY3Rpb24gc3RyKCkgaXMgc29tZXRpbWVzIHVzZWZ1bCB0byB0YWtlIGEgbG9vayBhdCB0aGUgc3RydWN0dXJlIG9mIGFuIG9iamVjdCBpbiBSLgoKYGBge3J9CmNsYXNzKGNvbnRpZykKdHlwZW9mKGNvbnRpZykKc3RyKGNvbnRpZykKYGBgCgpJZiB3ZSBsb29rIGF0IHRoZSBmaXJzdCBpdGVtIGluIHRoZSBsaXN0LCB3ZSBvYnRhaW4gdGhlIGZvbGxvd2luZzoKCmBgYHtyfQpjb250aWdbWzFdXQpgYGAKCldoYXQgdGhpcyBpcyBzaG93aW5nIGlzIHRoYXQgZGlzdHJpY3QgMSBoYXMgdHdvIGNvbnRpZ3VvdXMgbmVpZ2hib3Vycywgd2hpY2ggYXJlIHRoZSA1Mm5kIGFuZCA1NHRoIGVudHJpZXMgaW4gdGhlIGxpc3QuIFJlY2lwcm9jYWxseSwKCmBgYHtyfQpjb250aWdbWzUyXV0KYGBgCgpgYGB7cn0KY29udGlnW1s1NF1dCmBgYAoKd2hpY2ggYm90aCBpbmNsdWRlIDEgKGFzIHRoZXkgc2hvdWxkIC0tIHRoZSByZWxhdGlvbnNoaXAgaXMgc3ltbWV0cmljOiBpZiAkYSQgc2hhcmVzIGEgYm9yZGVyIHdpdGggJGIkIHRoZW4gJGIkIHNoYXJlcyBhIGJvcmRlciB3aXRoICRhJCkuCgpHb2luZyBiYWNrIHRvIHRoZSBzdW1tYXJ5IGFib3ZlLCByZWdpb24gMjAgaGFzIG5vIGxpbmtzLiBIb3dldmVyLAoKYGBge3J9CmNvbnRpZ1tbMjBdXQpgYGAKIApzdWdnZXN0cyB0aGF0IGl0IGFjdHVhbGx5IGhhcyBmb3VyLiBUaGUgY29uZnVzaW9uIGhlcmUgaXMgdGhhdCBwb2x5Z29uIDIwIGluIHRoZSBvcmlnaW5hbCBzcCBvYmplY3QgKGFuZCB0aGUgc2hhcGVmaWxlIGJlZm9yZSB0aGF0KSBpcyBhY3R1YWxseSB0aGUgMjFzdCBlbnRyeSBpbiB0aGUgbGlzdC4gV2UgY2FuIGdldCBhIHNlbnNlIG9mIHRoaXMgaWYgd2UgbG9vayBhdCwKCmBgYHtyfQpoZWFkKGRpc3RyaWN0cyRTUF9JRCkKYGBgCgphbmQgbm90ZSB0aGF0IHRoZSBudW1iZXJpbmcgYmVnaW5zIHdpdGggemVybywgbm90IG9uZS4gTm90ZSBhbHNvIHRoYXQgdGhlIHRpZHl2ZXJzZSBpcyBub3QgaW1wbGVtZW50ZWQgd2l0aCBzcCBjbGFzc2VzIGJ1dCBpcyB3aXRoIHNmLiBIZW5jZSwgdGhlIGZvbGxvd2luZyBnZW5lcmF0ZXMgYW4gZXJyb3IsCgpgYGB7cn0KZGlzdHJpY3RzICU+JQogIGRwbHlyOjpzZWxlY3QoU1BfSUQpICU+JQogIHByaW50KG4gPSAzKQpgYGAKCmJ1dCB0aGUgZm9sbG93aW5nIC0tIHdpdGggYSBjb252ZXJzaW9uIHRvIHNmIC0gd29ya3MgZmluZToKCmBgYHtyfQpkaXN0cmljdHMgJT4lCiAgc3RfYXNfc2YoLikgJT4lCiAgZHBseXI6OnNlbGVjdChTUF9JRCkgJT4lCiAgcHJpbnQobiA9IDMpCmBgYAoKSW4gYW55IGNhc2UsIGlmIHJlZ2lvbiAyMCBpcyBhY3R1YWxseSB0aGUgMjFzdCBpbiB0aGUgbGlzdCB0aGVuIHRoZSBmb2xsb3dpbmcgc2hvdWxkIGhhdmUgemVybyBuZWlnaGJvdXJzLAoKYGBge3J9CmNvbnRpZ1tbMjFdXQpgYGAKCi4uLiBhbmQgaXQgZG9lcyEgV2UgY2FuIGNvbmZpcm0gdGhpcyBieSBwbG90dGluZyB0aGUgc291cmNlIGRhdGEsCgpgYGB7cn0KcGxvdChkaXN0cmljdHNbLTIxLF0sIGNvbD0icmVkIikKcGxvdChkaXN0cmljdHNbMjEsXSwgY29sPSJibHVlIiwgYWRkPVQpCmBgYAoKT3IsIHVzaW5nIHRoZSBzaW1wbGUgZmVhdHVyZSB2ZXJzaW9uIG9mIHRoZSBvYmplY3QgdGhhdCB3YXMgY3JlYXRlZCBlYXJsaWVyLAoKYGBge3J9CnBsb3QoZGlzdHJpY3RzWy0yMSwwXSwgY29sPSJyZWQiKQpwbG90KGRpc3RyaWN0c1syMSwwXSwgY29sPSJibHVlIiwgYWRkPVQpCmBgYAoKLSBJZiB5b3UgYXJlIGVhZ2xlLWV5ZWQsIHlvdSBtYXkgbm90aWNlIHRoYXQgdGhlIGNvZGUgYmxvY2sgYWJvdmUgaW5jbHVkZXMgYSB6ZXJvIChlLmcuIFstMjEsMF0pIHdoZXJlIGFzIHRoZSBjb2RlIGFib3ZlIGRvZXMgbm90IChlLmcuIFsyMSxdKS4gVHJ5IGRlbGV0aW5nIHRoZSAwIGluIHRoZSBjb2RlIGJsb2NrIGFib3ZlIGFuZCBzZWUgd2hhdCBoYXBwZW5zIQoKIyMjIEsgbmVhcmVzdCBuZWlnaGJvdXJzIChrbm4pCgpJdCBkb2VzIG5vdCBtYWtlIHNlbnNlIHNlbnNlIHRvIGV2YWx1YXRlIGNvbnRpZ3VpdHkgb2YgcG9pbnQgZGF0YSBiZWNhdXNlIHBvaW50cyBhcmUgemVybyBkaW1lbnNpb25hbCAoYW5kIGhhdmUgbm8gYm9yZGVycykuIEluc3RlYWQsIHdlIGNvdWxkIGZpbmQsIGZvciBleGFtcGxlLCB0aGUgc2l4IG5lYXJlc3QgbmVpZ2hib3VycyB0byBlYWNoIHBvaW50LiBUaGVyZSBhcmUgdmFyaW91cyB3YXlzIHRvIGRvIHRoaXMsIGluY2x1ZGluZyB1c2luZyB0aGUgUkFOTiBwYWNrYWdlOgoKYGBge3J9CmlmKCEiUkFOTiIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSkgaW5zdGFsbC5wYWNrYWdlcygiUkFOTiIpCnJlcXVpcmUoUkFOTikKcHRzIDwtIGNvb3JkaW5hdGVzKGNvbWJpbmVkLm1hcCkgCmtubjYgPC0gbm4yKHB0cywgcHRzLCBrID0gNikKYGBgCgpMb29raW5nIGF0IHRoZSBhdHRyaWJ1dGVzIG9mIHRoZSBvYmplY3Qga25uNiwgaXQgc2hvd3MgdGhhdCBubjIgY3JlYXRlcyB0d28gb3V0cHV0cywgb25lIHdpdGggbmFtZSBubi5pZHgsIHRoZSBvdGhlciB3aXRoIG5hbWUgbm4uZGlzdHMuIAoKYGBge3J9CmF0dHJpYnV0ZXMoa25uNikKc3RyKGtubjYpCmBgYAoKSWYgd2UgYXJlIGludGVyZXN0ZWQgaW4gaWRlbnRpZnlpbmcgdGhlIHNpeCBuZWFyZXN0IG5laWdoYm91cnMgdG8gcG9pbnQgMSB0aGVuIHRoZXkgYXJlLAoKYGBge3J9CmtubjYkbm4uaWR4WzEsXQpgYGAKCmFuZCB0aGVpciBkaXN0YW5jZXMgZnJvbSB0aGUgcG9pbnQgYXJlCgpgYGB7cn0Ka25uNiRubi5kaXN0c1sxLF0KYGBgCgpOb3RlIHRoYXQgdGhlIHNpeCBuZWFyZXN0IG5laWdoYm91cnMgdG8gcG9pbnQgMSBpbmNsdWRlIHBvaW50IDEgKGFuZCB0aGUgc2l4IG5lYXJlc3QgbmVpZ2hib3VycyB0byBwb2ludCAyIGluY2x1ZGUgcG9pbnQgMiwgZXRjLikuIFRoaXMgaXNuJ3QgbmVjZXNzYXJpbHkgd2hhdCBpcyB3YW50ZWQgYW5kIGEgcHJvYmxlbSB0aGF0IHNwZGVwJ3Mga25lYXJuZWlnaCgpIGZ1bmN0aW9uIGF2b2lkcy4gSXQgYWxzbyBjYW4gd29yayBkaXJlY3RseSBvbiB0aGUgc3Agb2JqZWN0OgoKYGBge3J9CmtuZWFyNiA8LSBrbmVhcm5laWdoKGNvbWJpbmVkLm1hcCwgaz02KQphdHRyaWJ1dGVzKGtuZWFyNikKa25lYXI2JG5uWzEsXQpgYGAKCkhvd2V2ZXIsIHVubGlrZSBSQU5OICh3aGljaCwgYWN0dWFsbHksIGl0IGlzIHVzaW5nKSwgaXQgZG9lc24ndCByZXRhaW4gdGhlIGRpc3RhbmNlcyB0byB0aGUgbmVhcmVzdCBuZWlnaGJvdXJzLgoKU3BhdGlhbCBkZXBlbmRlbmN5IGV4aXN0cyBpbiBhIHNldCBvZiBkYXRhIGlmIHRoZSB2YWx1ZSBhdCBvbmUgbG9jYXRpb24gaXMgbm90IGluZGVwZW5kZW50IG9mIHZhbHVlcyBhdCBzdXJyb3VuZGluZyBsb2NhdGlvbnMgLS0gaWYgdGhlcmUgaXMgYSBnZW9ncmFwaGljYWwgcGF0dGVybiBpbiB0aGUgZGF0YS4gSW1hZ2luZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBjYWxjdWxhdGluZyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBzb21lIHZhcmlhYmxlIChjYWxsIGl0ICR4JCkgYXQgZWFjaCBvZiB0aGUgcG9pbnRzIGFuZCBhdCBlYWNoIG9mIHRoZSBwb2ludHMnIGNsb3Nlc3QgbmVpZ2hib3VyLiBVc2luZyB0aGUgc2ltdWxhdGVkIGRhdGEgYWJvdXQgdGhlIHByaWNlIG9mIGxhbmQgcGFyY2VscyBpbiBCZWlqaW5nLCB3ZSBjYW4gY2FsY3VsYXRlIHRoaXMgY29ycmVsYXRpb24gYXMgZm9sbG93czoKCmBgYHtyfQp4IDwtIGNvbWJpbmVkLm1hcCRMTlBSSUNFCmNvcih4LCB4W2tuZWFyNiRublssMV1dKQpgYGAKClNpbWlsYXJseSwgdGhlIGNvcnJlbGF0aW9uIHdpdGggdGhlIHNlY29uZCBuZWFyZXN0IG5laWdoYm91ciBpcwoKYGBge3J9CnggPC0gY29tYmluZWQubWFwJExOUFJJQ0UKY29yKHgsIHhba25lYXI2JG5uWywyXV0pCmBgYAoKYW5kIHRoZSBjb3JyZWxhdGlvbnMgZnJvbSB0aGUgZmlyc3QgdG8gc2l4dGggbmVpZ2hib3VyIGFyZQoKYGBge3J9CnIgPC0gc2FwcGx5KDE6NiwgZnVuY3Rpb24oaSkgewogIGNvcih4LCB4W2tuZWFyNiRublssaV1dKQp9KQpkZiA8LSBkYXRhLmZyYW1lKGsgPSAxOjYsIHIgPSByKQpwcmludChkZikKYGBgCgooQWxsIHRoaXMgaXMgYSBmdW5jdGlvbiB3aGVyZSB0aGUgdmFyaWFibGUgJGkkIHRha2VzIHRoZSB2YWx1ZXMgZnJvbSAxIHRvIDYsIGluIHR1cm4sIHNvLCBpbiB0aGUgZmlyc3QgaW5zdGFuY2UsIGl0IGlzIGNhbGN1bGF0aW5nLCBjb3IoeCwgeFtrbmVhcjYkbm5bLDFdXSkgdGhlbiBjb3IoeCwgeFtrbmVhcjYkbm5bLDJdXSkgYW5kIHNvIGZvcnRoIHVwIHRvIGNvcih4LCB4W2tuZWFyNiRublssNl1dKQoKUGxvdHRpbmcgdGhlc2UsIAoKYGBge3J9CmlmKCEiZ2dwbG90MiIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpCnJlcXVpcmUoZ2dwbG90MikKZGYgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaywgeSA9IHIpKSArCiAgZ2VvbV9saW5lKCkgKwogIHhsYWIoImt0aCBuZWFyZXN0IG5laWdoYm91ciIpICsKICB5bGFiKCJDb3JyZWxhdGlvbiwgciIpCmBgYAoKV2hhdCB0aGVzZSB2YWx1ZXMgc3VnZ2VzdCBpcyB0aGF0IGV2ZW4gYXQgdGhlIHNpeHRoIG5lYXJlc3QgbmVpZ2hib3VyLCB0aGUgdmFsdWUgb2YgYSBsYW5kIHBhcmNlbCBhdCBhbnkgZ2l2ZW4gcG9pbnQgdGVuZHMgdG8gYmUgc2ltaWxhciB0byB0aGUgdmFsdWUgb2YgdGhlIGxhbmQgcGFyY2VscyBhcm91bmQgaXQg4oCTLSBhbiBleGFtcGxlIG9mIHBvc2l0aXZlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uLiBIb3dldmVyLCB0aGUgbGV2ZWwgb2Ygc2ltaWxhcml0eSBpcyBoaWdoZXN0IGZvciB0aGUgbmVhcmVzdCBuZWlnaGJvdXIgYW5kIGRlY3JlYXNlcyB0aGUgZnVydGhlciB0aGUgbmVpZ2hib3VyIGlzIGF3YXkuCgpBbiBpc3N1ZSBpcyB0aGF0IHRoZSB0aHJlc2hvbGQgb2Ygc2l4IG5lYXJlc3QgbmVpZ2hib3VycyBpcyBwdXJlbHkgYXJiaXRyYXJ5LiBBbiBpbnRlcmVzdGluZyBxdWVzdGlvbiBpcyBob3cgZmFyIOKAkyBob3cgbWFueSBuZWlnaGJvdXJzIGF3YXkg4oCTIHdlIGNhbiB0eXBpY2FsbHkgZ28gZnJvbSBhIHBvaW50IGFuZCBzdGlsbCBmaW5kIGEgc2ltaWxhcml0eSBpbiB0aGUgbGFuZCBwcmljZSB2YWx1ZXMuIE9uZSB3YXkgdG8gZGV0ZXJtaW5lIHRoaXMgd291bGQgYmUgdG8gY2Fycnkgb24gd2l0aCB0aGUgY2FsY3VsYXRpb25zIGFib3ZlLCByZXBlYXRpbmcgdGhlIHByb2NlZHVyZSB1bnRpbCB3ZSBnZXQgdG8sIHNheSwgdGhlIDI1MHRoIG5lYXJlc3QgbmVpZ2hib3VyLgoKYGBge3J9CmtuZWFyMjUwIDwtIGtuZWFybmVpZ2goY29tYmluZWQubWFwLCBrPTI1MCkKciA8LSBzYXBwbHkoMToyNTAsIGZ1bmN0aW9uKGkpIHsKICBjb3IoeCwgeFtrbmVhcjI1MCRublssaV1dKQp9KQpkYXRhLmZyYW1lKGsgPSAxOjI1MCwgciA9IHIpICU+JQogIGdncGxvdChhZXMoeCA9IGssIHkgPSByKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgeGxhYigia3RoIG5lYXJlc3QgbmVpZ2hib3VyIikgKwogIHlsYWIoIkNvcnJlbGF0aW9uLCByIikKYGBgCgpMb29raW5nIGF0IHRoZSBwbG90IHdlIGZpbmQgdGhhdCB0aGUgbGFuZCBwcmljZXMgYmVjb21lIG1vcmUgZGlzc2ltaWxhciAobGVzcyBjb3JyZWxhdGVkKSB0aGUgZnVydGhlciBhd2F5IHdlIGdvIGZyb20gZWFjaCBwb2ludCwgZHJvcHBpbmcgdG8gemVybyBjb3JyZWxhdGlvbiBmcm9tIGFmdGVyIGFib3V0IHRoZSAyMDB0aCBuZWFyZXN0IG5laWdoYm91ci4gVGhlIHJhdGUgb2YgZGVjcmVhc2UgaW4gdGhlIGNvcnJlbGF0aW9uIGlzIGdyZWF0ZXN0IHRvIGFib3V0IHRoZSAzNXRoIG5laWdoYm91ciwgYWZ0ZXIgd2hpY2ggaXQgYmVnaW5zIHRvIGZsYXR0ZW4uCgpXZSBjYW4gYWxzbyBkZXRlcm1pbmUgdGhlIHAtdmFsdWVzIGFzc29jaWF0ZWQgd2l0aCBlYWNoIG9mIHRoZXNlIGNvcnJlbGF0aW9ucyBhbmQgaWRlbnRpZnkgd2hpY2ggYXJlIG5vdCBzaWduaWZpY2FudCBhdCBhIDk5JSBjb25maWRlbmNlOgoKYGBge3J9CnB2YWxzIDwtIHNhcHBseSgxOjI1MCwgZnVuY3Rpb24oaSkgewogIGNvci50ZXN0KHgsIHhba25lYXIyNTAkbm5bLGldXSkkcC52YWx1ZQp9KQp3aGljaChwdmFscyA+IDAuMDEpCmBgYAoKSXQgaXMgZnJvbSBhYm91dCB0aGUgMTAwdGggbmVpZ2hib3VyIHRoYXQgdGhlIGNvcnJlbGF0aW9ucyBiZWdpbiB0byBiZWNvbWUgcmVndWxhcmx5IGluc2lnbmlmaWNhbnQuIFdoZXRoZXIgdGhpcyBpcyB1c2VmdWxseSBpbmZvcm1hdGlvbiBvciBub3QgaXMgYSBtb290IHBvaW50OiBhIG1lYXN1cmUgb2Ygc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIGlzIHJlYWxseSBvbmx5IGFuIGluZGlyZWN0IG1lYXN1cmUgb2YgdGhlIHNhbXBsZSBzaXplLiBJdCBtYXkgYmUgYmV0dGVyIHRvIG1ha2UgYSBkZWNpc2lvbiBhYm91dCB0aGUgdGhyZXNob2xkIGF0IHdoaWNoIHRoZSBuZWlnaGJvdXJzIGFyZSBub3Qgc3Vic3RhbnRpdmVseSBjb3JyZWxhdGVkIGJhc2VkIG9uIHRoZSBhY3R1YWwgY29ycmVsYXRpb25zICh0aGUgZWZmZWN0IHNpemVzKSByYXRoZXIgdGhhbiB0aGVpciBwLXZhbHVlcy4gV2hpbHN0IGl0IHJlbWFpbnMgYSBzdWJqZWN0aXZlIGNob2ljZSwgaGVyZSB3ZSB3aWxsIGhlcmUgdXNlIHRoZSAzNXRoIG5laWdoYm91ciBhcyB0aGUgbGltaXQsIGJlZm9yZSB3aGljaCB0aGUgY29ycmVsYXRpb25zIGFyZSB0eXBpY2FsbHkgZXF1YWwgdG8gciA9IDAuMjAgb3IgZ3JlYXRlci4KCmBgYHtyfQprbmVhcjM1IDwtIGtuZWFybmVpZ2goY29tYmluZWQubWFwLCBrPTM1KQpjbGFzcyhrbmVhcjM1KQpgYGAKClRoZSBvYmplY3QgaXMgb2YgY2xhc3Mga25uLiBUbyBjb252ZXJ0IGl0IHRvIGNsYXNzIG5iICh0aGUgc2FtZSBhcyB0aGUgY29udGlndWl0eSBvYmplY3QgZnJvbSBlYXJsaWVyKSwKCmBgYHtyfQprbmVhcjM1bmIgPC0ga25uMm5iKGtuZWFyMzUpCmtuZWFyMzVuYgpgYGAKCk5vdGUgdGhhdCB0aGlzIGlzIGEgbm9uLXN5bW1ldHJpYyBsaXN0IC0tIGp1c3QgYmVjYXVzZSAkYSQgaXMgb25lIG9mIHRoZSAzNSBuZWFyZXN0IG5laWdoYm91cnMgdG8gJGIkLCBpdCBkb2VzIG5vdCBmb2xsb3cgdGhhdCAkYiQgbXVzdCBiZSBvbmUgb2YgdGhlIDM1IG5lYXJlc3QgbmVpZ2hib3VycyB0byAkYSQuCgojIyMgSWRlbnRpZnlpbmcgbmVpZ2hib3VycyBieSAocGh5c2ljYWwpIGRpc3RhbmNlIGFwYXJ0CgpJdCBpcyBhbHNvIHBvc3NpYmxlIHRvIGlkZW50aWZ5IHRoZSBuZWlnaGJvdXJzIG9mIHBvaW50cyBieSB0aGVpciBFdWNsaWRlYW4gb3IgR3JlYXQgQ2lyY2xlIGRpc3RhbmNlIGFwYXJ0IHVzaW5nIHRoZSBmdW5jdGlvbiBkbmVhcm5laWdoKCkuIEZvciBleGFtcGxlLCBpZiB3ZSB3YW50ZWQgdG8gaWRlbnRpZnkgYWxsIHBvaW50cyBiZXR3ZWVuIDEwMCBhbmQgMTAwMCBtZXRyZXMgb2YgZWFjaCBvdGhlcjoKCmBgYHtyfQpkMTAwdG8xMDAwIDwtIGRuZWFybmVpZ2goY29tYmluZWQubWFwLCAxMDAsIDEwMDApCmQxMDB0bzEwMDAKYGBgCgotIE9uIGF2ZXJhZ2UsIGhvdyBtYW55IHJlZ2lvbnMgYXJlIGJldHdlZW4gMTAwIGFuZCAxMDAwIG1ldHJlcyBvZiBhbm90aGVyPyAKCiMjIENyZWF0aW5nIGEgU3BhdGlhbCBXZWlnaHRzIG1hdHJpeAoKV2hhdCB3ZSBoYXZlIGRvbmUgc28gZmFyIGlzIHVzZSB2YXJpb3VzIG1ldGhvZHMgdG8gY3JlYXRlIGEgbGlzdCBvZiBuZWlnaGJvdXJzIHdpdGggZmxleGliaWxpdHkgdG8gZGVjaWRlIGFib3V0IHdoYXQgY291bnRzIGFzIGEgbmVpZ2hib3VyLiBUaGUgbmV4dCBzdGFnZSBpcyB0byBjb252ZXJ0IHRoYXQgbGlzdCBpbnRvIGEgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeCBzbyB3ZSBjYW4gdXNlIGl0IGZvciB2YXJpb3VzIG1ldGhvZHMgb2Ygc3BhdGlhbCBhbmFseXNpcy4gVGhpcyBleHRyYSBzdGFnZSBvZiBjb252ZXJzaW9uIG1heSBzZWVtIGxpa2UgYW4gdW5uZWNlc3NhcnkgYWRkaXRpb25hbCBjaG9yZS4gSG93ZXZlciwgdGhlIGNyZWF0aW9uIG9mIHRoZSBzcGF0aWFsIHdlaWdodHMgbWF0cml4IGFsbG93cyB1cyB0byBkZWZpbmUgdGhlIHN0cmVuZ3RoIG9mIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG5laWdoYm91cnMuIEZvciBleGFtcGxlLCB3ZSBtYXkgd2FudCB0byBnaXZlIG1vcmUgd2VpZ2h0IHRvIG5laWdoYm91cnMgdGhhdCBhcmUgbG9jYXRlZCBjbG9zZXIgdG9nZXRoZXIgYW5kIGxlc3Mgd2VpZ2h0IHRvIHRob3NlIHRoYXQgYXJlIGZ1cnRoZXIgYXBhcnQgKGRlY3JlYXNpbmcgdG8gemVybyBiZXlvbmQgYSBjZXJ0YWluIHRocmVzaG9sZCkuIFRoZSBuYiBvYmplY3QgZGVmaW5lcyB3aGljaCBhcmUgbmVpZ2hib3Vycy4gQ29udmVydGluZyBpdCB0byBhIHNwYXRpYWwgd2VpZ2h0cyBvYmplY3Qgc2F5cyBob3cgc3Ryb25nbHkgdGhleSBhcmUgbmVpZ2hib3Vycy4KCiMjIyBDcmVhdGluZyBhIGJpbmFyeSBsaXN0IG9mIHdlaWdodHMKCldlIGNvdWxkIGNyZWF0ZSBhIHNpbXBsZSBiaW5hcnkgJ21hdHJpeCcgZnJvbSBhbnkgb2Ygb3VyIGV4aXN0aW5nIGxpc3RzIG9mIG5laWdoYm91cnMuIEluIHByaW5jaXBsZToKCmBgYHtyfQpzcGNvbnRpZyA8LSBuYjJsaXN0dyhjb250aWcsIHN0eWxlPSJCIikKYGBgCgpOb3RlLCBob3dldmVyLCB0aGUgZXJyb3IgbWVzc2FnZSwgd2hpY2ggYXJpc2VzIGJlY2F1c2UgdHdvIG9mIHRoZSBDaGluZXNlIGRpc3RyaWN0cyBkbyBub3Qgc2hhcmUgYSBib3VuZGFyeSB3aXRoIG90aGVycy4gQ29ycmVjdGluZyBmb3IgdGhpcywKCmBgYHtyfQpzcGNvbnRpZyA8LSBuYjJsaXN0dyhjb250aWcsIHN0eWxlPSJCIiwgemVyby5wb2xpY3k9VCkKYGBgCgpUaGUgc2FtZSBwcm9ibGVtIGRvZXMgbm90IGFyaXNlIGZvciB0aGUgb25lIGh1bmRyZWQgbmVhcmVzdCBuZWlnaGJvdXJzIChieSBkZWZpbml0aW9uLCBpdCBjYW5ub3Qg4oCTIGVhY2ggcG9pbnQgaGFzIG5laWdoYm91cnMpIGJ1dCBpdCBkb2VzIGZvciB0aGUgZGlzdGFuY2UgYmFzZWQgbGlzdCBpZiBlbXB0eSBzZXRzIGFyZSBub3QgcGVybWl0dGVkICh0aGV5IGFyZSBwZXJtaXR0ZWQgYnkgc2V0dGluZyB6ZXJvLnBvbGljeSA9IFQpOgoKYGBge3J9CnNwa25lYXIzNSA8LSBuYjJsaXN0dyhrbmVhcjM1bmIsIHN0eWxlPSJCIikKc3BkMTAwdG8xMDAwIDwtIG5iMmxpc3R3KGQxMDB0bzEwMDAsIHN0eWxlPSJCIiwgemVyby5wb2xpY3k9VCkKYGBgCgpXaGF0IHdlIGNyZWF0ZSBhcmUgb2JqZWN0cyBvZiBjbGFzcyBsaXN0dyAoYSBsaXN0IG9mIHNwYXRpYWwgd2VpZ2h0cykuIExvb2tpbmcgYXQgdGhlIGZpcnN0IG9mIHRoZXNlIG9iamVjdHMgd2UgY2FuIHNlZSBob3cgaXQgaGFzIGJlZW4gY29uc3RydWN0ZWQuIEl0IGNvbnRhaW5zIGJpbmFyeSB3ZWlnaHRzIChzdHlsZSBCKTsgZGlzdHJpY3QgMSBoYXMgdHdvIG5laWdoYm91cnMsIGRpc3RyaWN0cyA1MiBhbmQgNTQ7IGFuZCBib3RoIG9mIHRob3NlIGhhdmUgYmVlbiBnaXZlbiBhIHdlaWdodCBvZiBvbmUgKGFsbCBvdGhlciBkaXN0cmljdHMgdGhlcmVmb3JlIGhhdmUgYSB3ZWlnaHQgb2YgemVybyB3aXRoIGRpc3RyaWN0IDEpLiBTaW1pbGFybHksIGRpc3RyaWN0IDIgaGFzIG5laWdoYm91cnMgMywgNCwgNiwgOTk5IGFuZCAxMDAsIGVhY2ggd2l0aCBhIHdlaWdodCBvZiBvbmUuCgpgYGB7cn0KY2xhc3Moc3Bjb250aWcpCnR5cGVvZihzcGNvbnRpZykKbmFtZXMoc3Bjb250aWcpCnNwY29udGlnJHN0eWxlCnNwY29udGlnJG5laWdoYm91cnNbWzFdXQpzcGNvbnRpZyR3ZWlnaHRzW1sxXV0Kc3Bjb250aWckbmVpZ2hib3Vyc1tbMl1dCnNwY29udGlnJHdlaWdodHNbWzJdXQpgYGAKCiMjIyBDcmVhdGluZyBhIHJvdy1zdGFuZGFyZGlzZWQgbGlzdCBvZiB3ZWlnaHRzCgpVc2luZyBiaW5hcnkgd2VpZ2h0cyB3aGVyZSAoMSkgaW5kaWNhdGVzIHR3byBwbGFjZXMgYXJlIG5laWdoYm91cnMsIGFuZCAoMCkgaW5kaWNhdGVzIHRoZXkgYXJlIG5vdCwgbWF5IGNyZWF0ZSBhIHByb2JsZW0gd2hlbiBkaWZmZXJlbnQgcGxhY2VzIGhhdmUgZGlmZmVyZW50IG51bWJlcnMgb2YgbmVpZ2hib3VycyAoYXMgaXMgdGhlIGNhc2UgZm9yIGJvdGggdGhlIGNvbnRpZ3VpdHkgYW5kIGRpc3RhbmNlLWJhc2VkIGFwcHJvYWNoZXMpLiBJbWFnaW5lIGEgY2FsY3VsYXRpb24gd2hlcmUgdGhlIHJlc3VsdCBpcyBpbiBzb21lIHdheSBkZXBlbmRlbnQgdXBvbiB0aGUgc3VtIG9mIHRoZSB3ZWlnaHRzIGludm9sdmVkLiBBbGwgdGhpbmdzIGJlaW5nIGVxdWFsLCB3ZSBleHBlY3QgcGxhY2VzIHdpdGggbW9yZSBuZWlnaGJvdXJzIHRvIGdlbmVyYXRlIGxhcmdlciB2YWx1ZXMgb2YgeSBzaW1wbHkgYmVjYXVzZSB0aGV5IGhhdmUgbW9yZSBub24temVybyB2YWx1ZXMgY29udHJpYnV0aW5nIHRvIHRoZSBzdW0uIEEgd2F5IGFyb3VuZCB0aGlzIHByb2JsZW0gaXMgdG8gc2NhbGUgdGhlIHdlaWdodHMgc28gdGhhdCBmb3IgYW55IG9uZSBwbGFjZSB0aGV5IHN1bSB0byBvbmUg4oCTIGEgcHJvY2VzcyBrbm93biBhcyByb3ctc3RhbmRhcmRpc2F0aW9uIGFuZCBhY3R1YWxseSB0aGUgZGVmYXVsdCBvcHRpb246CgpgYGB7cn0Kc3Bjb250aWcgPC0gbmIybGlzdHcoY29udGlnLCB6ZXJvLnBvbGljeT1UKQpuYW1lcyhzcGNvbnRpZykKc3Bjb250aWckc3R5bGUKc3Bjb250aWckbmVpZ2hib3Vyc1tbMV1dCnNwY29udGlnJHdlaWdodHNbWzFdXQpzcGNvbnRpZyRuZWlnaGJvdXJzW1syXV0Kc3Bjb250aWckd2VpZ2h0c1tbMl1dCmBgYAoKSW4gdGhlIGNhc2Ugb2YgdGhlIGNvbnRpZ3VpdHkgbWF0cml4LCBkaXN0cmljdCAxIHN0aWxsIGhhcyBuZWlnaGJvdXJzIDUyIGFuZCA1NCwgYW5kIGRpc3RyaWN0IDIgc3RpbGwgaGFzIG5laWdoYm91cnMgMywgNCwgNiwgOTkgYW5kIDEwMCBidXQgdGhlIHdlaWdodHMgYXJlIG5vdyByb3ctc3RhbmRhcmRpc2VkIChzdHlsZSBXKSBhbmQgaW4gZWFjaCBjYXNlIHRoZXkgc3VtIHRvIG9uZS4KCiMjIyBDcmVhdGluZyBhbiBpbnZlcnNlIGRpc3RhbmNlIHdlaWdodGluZyAoSURXKQoKQSBtb3JlIGFtYml0aW91cyB1bmRlcnRha2luZyBpcyB0byBkZWNyZWFzZSB0aGUgd2VpZ2h0aW5nIGdpdmVuIHRvIHR3byBwb2ludHMgYWNjb3JkaW5nIHRvIHRoZWlyIGRpc3RhbmNlIGFwYXJ0LCByZWR1Y2luZyB0byB6ZXJvLCBmb3IgZXhhbXBsZSwgYmV5b25kIHRoZSAzNXRoIG5lYXJlc3QgbmVpZ2hib3VyLiBUbyBhY2hpZXZlIHRoaXMsIHdlIGNhbiB1c2UgdGhlIFJBTk4gcGFja2FnZSBmcm9tIGVhcmxpZXIgdG8gZmluZCB0aGUgZGlzdGFuY2VzIChhbm90aGVyIHBvc3NpYmlsaXR5IGlzIHNwRGlzdHMgLS0gc2VlID9zcERpc3RzKSB0aGVuIGFzc2lnbiB0aGUgd2VpZ2h0cyBiYXNlZCBvbiB0aGUgaW52ZXJzZSBvZiB0aG9zZSBkaXN0YW5jZXMuCgpgYGB7cn0KcHRzIDwtIGNvb3JkaW5hdGVzKGNvbWJpbmVkLm1hcCkgIyBHZXQgdGhlIGNlbnRyb2lkIG9mIGVhY2ggZGlzdHJpY3QKa25uIDwtIG5uMihwdHMsIHB0cywgayA9IDM2KQojIFJlY2FsbCB0aGF0IHVzaW5nIFJBTk4gaW5jbHVkZXMgZWFjaCBkaXN0cmljdCBhcyBpdHMgb3duIG5laWdoYm91cgpkIDwtIGtubiRubi5kaXN0cyAgICMgVGhlIGRpc3RhbmNlcwpkIDwtIGRbLC0xXSAgICAgICAgICMgRGVsZXRlIHRoZSBmaXJzdCBjb2x1bW4gb2YgemVybyBkaXN0YW5jZXMKaWR3IDwtIDEvZCAgICAgICAgICAjIHRoZSBpbnZlcnNlIG9mIGRpc3RhbmNlIApnbGlzdCA8LSBsYXBwbHkoMTpucm93KGlkdyksIGZ1bmN0aW9uKGkpIGlkd1tpLF0pCiMgVGhpcyBpcyBqdXN0IGEgd2F5IG9mIGNvbnZlcnRpbmcgdGhlIGludmVyc2UgZGlzdGFuY2VzIGludG8gd2VpZ2h0cwprbmVhciA8LSBrbmVhcm5laWdoKGFzKGNvbWJpbmVkLm1hcCwgIlNwYXRpYWxQb2ludHMiKSwgaz0zNSkKIyBGb3IgY29tcGFyaWFiaWxpdHksIHRoaXMgaXMgYWxzbyB1c2luZyB0aGUgY2VudHJvaWQgb2YgZWFjaCBkaXN0cmljdAprbmVhcm5iIDwtIGtubjJuYihrbmVhcikgICMgQ3JlYXRlIGEgbmVpZ2hib3VyaG9vZCBsaXN0CnNwa25lYXIzNUlEVyA8LSBuYjJsaXN0dyhrbmVhcm5iLCBnbGlzdD1nbGlzdCkKIyBDb21iaW5lIHRoZSBuZWlnaGJvdXJob29kIGxpc3Qgd2l0aCB0aGUgaW52ZXJzZSBkaXN0YW5jZSB3ZWlnaHRpbmcKYGBgCgpUaGF0IGlzbid0IHRoZSBtb3N0IGVmZmljaWVudCB3YXkgb2YgZG9pbmcgaXQgYnV0IGl0IGFwcGVhcnMgdG8gbW9yZS1vci1sZXNzIHdvcmsgYmVjYXVzZSBpZiB3ZSBsb29rIGF0IHRoZSB3ZWlnaHRzIGFzc2lnbmVkIHRvIHRoZSAzNSBuZWlnaGJvdXJzIG9mIHRoZSBmaXJzdCBkaXN0cmljdCB0aGV5IGFyZSBkZWNyZWFzaW5nIHdpdGggZGlzdGFuY2U6CgpgYGB7cn0Kc3BrbmVhcjM1SURXJHdlaWdodHNbWzFdXQpgYGAKCldoeSBtb3JlLW9yLWxlc3M/IEJlY2F1c2UgdGhlIHdlaWdodHMgYXJlIHJvdy1zdGFuZGFyZGlzZWQhIFRoaXMgbWVhbnMgdGhhdCB0aGUgaW52ZXJzZSBkaXN0YW5jZSB3ZWlnaHRpbmcgaXMgYSBmdW5jdGlvbiBvZiB0aGUgbG9jYWwgZGlzdHJpYnV0aW9uIG9mIHBvaW50cyBhcm91bmQgZWFjaCBwb2ludCBub3QganVzdCBob3cgZmFyIGF3YXkgdGhleSBhcmUuIEZvciBleGFtcGxlLCBjb25zaWRlciBhIHBvaW50IHdoZXJlIGFsbCBpdHMgbmVpZ2hib3VycyBhcmUgcXVpdGUgZmFyIGZyb20gaXQuIFVzaW5nIGEgc3RyaWN0bHkgZGlzdGFuY2UtYmFzZWQgd2VpZ2h0aW5nIGVhY2ggb2YgdGhvc2UgbmVpZ2hib3VycyBzaG91bGQgcmVjZWl2ZSBhIGxvdyB3ZWlnaHRpbmcuIEhvd2V2ZXIsIG9uY2Ugcm93IHN0YW5kYXJkaXNhdGlvbiBpcyBhcHBsaWVkIHRob3NlIGxvdyB3ZWlnaHRzIHdpbGwgYmUgc2NhbGVkIHVwd2FyZHMgdG8gc3VtIHRvIG9uZS4gUmVjaXByb2NhbGx5LCBpbWFnaW5lIGEgcG9pbnQgd2hlcmUgYWxsIGl0cyBuZWlnaGJvdXJzIGFyZSB2ZXJ5IGNsb3NlLiBVc2luZyBhIGRpc3RhbmNlLWJhc2VkIHdlaWdodGluZyB0aG9zZSBuZWlnaGJvdXJzIHNob3VsZCByZWNlaXZlIGEgcmVsYXRpdmVseSBoaWdoIHdlaWdodGluZzsgaW4gZWZmZWN0LCB0aG91Z2gsIHRoZXkgd2lsbCBiZSBzY2FsZWQgZG93bndhcmRzIGJ5IHRoZSByb3cgc3RhbmRhcmRpc2F0aW9uLiBUaGlzIG1heSBzb3VuZCB1bmRlc2lyYWJsZSBhbmQgY291bnRlciB0byB0aGUgb2JqZWN0aXZlcyBvZiBpbnZlcnNlIGRpc3RhbmNlIHdlaWdodGluZywgYW5kIGNhbiBiZSBwcmV2ZW50ZWQgYnkgY2hhbmdpbmcgdGhlIHdlaWdodHMgc3R5bGUsCgpgYGB7cn0Kc3BrbmVhcjM1SURXIDwtIG5iMmxpc3R3KGtuZWFybmIsIGdsaXN0PWdsaXN0LCBzdHlsZSA9ICJDIikKc3BrbmVhcjM1SURXJHdlaWdodHNbWzFdXQpgYGAKCkhvd2V2ZXIsIGltYWdpbmUgdGhlIHBvaW50cyBzYW1wbGUgYWNyb3NzIGJvdGggdXJiYW4gYW5kIHJ1cmFsIGFyZWFzLiBUaGUgZGlzdGFuY2VzIGJldHdlZW4gcG9pbnRzIHdpbGwgbW9zdCBsaWtlbHkgYmUgc21hbGxlciBpbiB0aGUgdXJiYW4gcmVnaW9ucyAod2hlcmUgdGhlIGRlbnNpdHkgb2YgcG9pbnRzIGlzIGdyZWF0ZXIsIHJlZmxlY3RpbmcgdGhlIGdyZWF0ZXIgcG9wdWxhdGlvbiBkZW5zaXR5KSwgd2l0aCBncmVhdGVyIGRpc3RhbmNlcyBiZXR3ZWVuIHBvaW50cyBpbiB0aGUgcnVyYWwgcmVnaW9ucy4gSWYgcm93IHN0YW5kYXJkaXNhdGlvbiBpcyBub3QgYXBwbGllZCB0aGVuIHRoZSBuZXQgcmVzdWx0IHdpbGwgYmUgdG8gZ2l2ZSBtb3JlIHdlaWdodCB0byB0aGUgdXJiYW4gcGFydHMgb2YgdGhlIHJlZ2lvbiBzdWNoIHRoYXQgYW55IHN1YnNlcXVlbnQgY2FsY3VsYXRpb24gZGVwZW5kZW50IHVwb24gdGhlIHN1bSBvZiB0aGUgd2VpZ2h0cyB3aWxsIGJlIG1vcmUgc3Ryb25nbHkgaW5mbHVlbmNlZCBieSB0aGUgdXJiYW4gYXJlYXMgdGhhbiBieSB0aGUgcnVyYWwgb25lcy4gVGhlcmVmb3JlIGNhcmVmdWwgdGhvdWdoIG5lZWRzIHRvIGJlIGdpdmVuIHRvIHRoZSBzdHlsZSBvZiB3ZWlnaHRzIHRvIHVzZS4KCiMjIyBWYXJpYW50cyBvZiBJRFcKCkNvbW1vbiBmb3JtcyBvZiBpbnZlcnNlIGRpc3RhbmNlIHdlaWdodGluZyBpbmNsdWRlIHRoZSBiaXNxdWFyZSBhbmQgR2F1c3NpYW4gZnVuY3Rpb25zLiBBZGFwdGluZyB0aGUgY29kZSBhYm92ZSwgYSBiaXNxdWFyZSB2ZXJzaW9uIGlzCgpgYGB7cn0KcHRzIDwtIGNvb3JkaW5hdGVzKGNvbWJpbmVkLm1hcCkKa25uIDwtIG5uMihwdHMsIHB0cywgayA9IDM2KQpkIDwtIGtubiRubi5kaXN0cwpkIDwtIGRbLC0xXSAgICAgICAgIApnbGlzdCA8LSBsYXBwbHkoMTpucm93KGQpLCBmdW5jdGlvbihpKSB7CiAgbWF4LmQgPC0gZFtpLDM1XQogIDEgLSAoZFtpLF0gLyBtYXguZCkKICAjIFRoZSBhYm92ZSBjYWxjdWxhdGVzIHRoZSBiaXNxdWFyZSB3ZWlnaHRpbmcKfSkKa25lYXIgPC0ga25lYXJuZWlnaChhcyhjb21iaW5lZC5tYXAsICJTcGF0aWFsUG9pbnRzIiksIGs9MzUpCmtuZWFybmIgPC0ga25uMm5iKGtuZWFyKQpzcGtuZWFyMzViaXNxIDwtIG5iMmxpc3R3KGtuZWFybmIsIGdsaXN0PWdsaXN0LCBzdHlsZT0iQyIpCmBgYAoKLi4uIGFuZCBhIEdhdXNzaWFuIHZlcnNpb24gaXMKCmBgYHtyfQpwdHMgPC0gY29vcmRpbmF0ZXMoY29tYmluZWQubWFwKQprbm4gPC0gbm4yKHB0cywgcHRzLCBrID0gMzYpCmQgPC0ga25uJG5uLmRpc3RzCmQgPC0gZFssLTFdICAgICAgICAgCmdsaXN0IDwtIGxhcHBseSgxOm5yb3coZCksIGZ1bmN0aW9uKGkpIHsKICBtYXguZCA8LSBkW2ksMzVdCiAgZXhwKC0wLjUqZFtpLF1eMiAvIG1heC5kXjIpCQogICMgVGhlIGFib3ZlIGNhbGN1bGF0ZXMgdGhlIGJpc3F1YXJlIHdlaWdodGluZwp9KQprbmVhciA8LSBrbmVhcm5laWdoKGFzKGNvbWJpbmVkLm1hcCwgIlNwYXRpYWxQb2ludHMiKSwgaz0zNSkKa25lYXJuYiA8LSBrbm4ybmIoa25lYXIpCnNwa25lYXIzNWdhdXMgPC0gbmIybGlzdHcoa25lYXJuYiwgZ2xpc3Q9Z2xpc3QsIHN0eWxlPSJDIikKYGBgCgpBIHVzZWZ1bCBpbGx1c3RyYXRpb24gb2YgdGhlIHR5cGVzIG9mIHdlaWdodGluZyBhbmQgaG93IHRoZXkgZGlmZmVyIGNhbiBiZSBmb3VuZCBhcyBGaWd1cmUgMSBvZiBbR1dtb2RlbDogQW4gUiBQYWNrYWdlIGZvciBFeHBsb3JpbmcgU3BhdGlhbCBIZXRlcm9nZW5laXR5IFVzaW5nIEdlb2dyYXBoaWNhbGx5IFdlaWdodGVkIE1vZGVsc10oaHR0cHM6Ly93d3cuanN0YXRzb2Z0Lm9yZy9hcnRpY2xlL3ZpZXcvdjA2M2kxNykuIEdlbmVyYWxseSB0aGUgc2hhcGUgKHRoZSByYXRlIG9mIGRlY2F5KSBtYXR0ZXJzIGxlc3MgdGhhbiB0aGUgbnVtYmVyIG9mIG5laWdoYm91cnMgdGhhdCBhcmUgaW5jbHVkZWQuCgojIyBVc2luZyBTcGF0aWFsIFdlaWdodHMKCiMjIyBDcmVhdGluZyBhIHNwYXRpYWxseSBsYWdnZWQgdmFyaWFibGUKCk9uY2Ugd2UgaGF2ZSB0aGUgc3BhdGlhbCB3ZWlnaHRzIHdlIGNhbiB1c2UgdGhlbSB0byBjcmVhdGUgYSBzcGF0aWFsbHkgbGFnZ2VkIHZhcmlhYmxlLiBGb3IgZXhhbXBsZSwgaWYgJHhfaSQgaXMgdGhlIHZhbHVlIG9mIHRoZSBsYW5kIHBhcmNlbCBhdCBwb2ludCAkaSQsIHRoZW4gaXRzIHNwYXRpYWwgbGFnIGlzIHRoZSBtZWFuIHZhbHVlIG9mIHRoZSBsYW5kIHBhcmNlbHMgdGhhdCBhcmUgdGhlIG5laWdoYm91cnMgb2YgJGkkLCB3aGVyZSB0aG9zZSBuZWlnaGJvdXJzIGFyZSBkZWZpbmVkIGJ5IHRoZSBzcGF0aWFsIHdlaWdodHMuIE1vcmUgcHJlY2lzZWx5LCBpdCBpcyB0aGUgd2VpZ2h0ZWQgbWVhbiB2YWx1ZSBpZiwgZm9yIGV4YW1wbGUsIGludmVyc2UgZGlzdGFuY2Ugd2VpZ2h0aW5nIGhhcyBiZWVuIGVtcGxveWVkLiBJdCBpcyBzdHJhaWdodGZvcndhcmQgdG8gY2FsY3VsYXRlIHRoZSBzcGF0aWFsbHkgbGFnZ2VkIHZhcmlhYmxlLiBGb3IgZXhhbXBsZSwKCmBgYHtyfQp4IDwtIGNvbWJpbmVkLm1hcCRMTlBSSUNFCmxhZ3ggPC0gbGFnLmxpc3R3KHNwa25lYXIzNWdhdXMsIHgpCmBgYAoKSGF2aW5nIGRvbmUgc28sIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHBvaW50cyBhbmQgdGhlaXIgbmVpZ2hib3VycyBjYW4gYmUgY2FsY3VsYXRlZCwKCmBgYHtyfQpjb3IudGVzdCh4LCBsYWd4KQpgYGAKCkhlcmUgdGhlcmUgaXMgZXZpZGVuY2Ugb2Ygc2lnbmlmaWNhbnQgcG9zaXRpdmUgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gLeKAkyB0aGF0IHRoZSBsYW5kIHByaWNlIGF0IG9uZSBwb2ludCB0ZW5kcyB0byBiZSBzaW1pbGFyIHRvIHRoZSBsYW5kIHByaWNlcyBvZiBpdHMgbmVpZ2hib3Vycy4gVGhpcyBjYW4gYmUgc2VlbiBpZiB3ZSBwbG90IHRoZSB0d28gdmFyaWFibGVzIG9uIGEgc2NhdHRlciBwbG90LCBhbHRob3VnaCB0aGUgcmVsYXRpb25zaGlwIGlzIGFsc28gc29tZXdoYXQgbm9pc3kgYW5kIG1heSBub3QgYmUgbGluZWFyLiAKCmBgYHtyfQpkYXRhLmZyYW1lKHggPSB4LCBsYWd4ID0gbGFneCkgJT4lCiAgZ2dwbG90KGFlcyh4PXgsIHk9bGFneCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjUpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbih4KSwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IG1lYW4obGFneCksIGxpbmV0eXBlID0gImRvdHRlZCIpCmBgYAoKIyMjIEEgTW9yYW4gcGxvdCBhbmQgdGVzdAoKVGhlIHBsb3QgY3JlYXRlZCBhYm92ZSBpcyBrbm93biBhcyBhIE1vcmFuIHBsb3QuIEEgbW9yZSBkaXJlY3Qgd2F5IG9mIHByb2R1Y2luZyBpdCBpcyB0byB1c2UgdGhlIG1vcmFuLnBsb3QoKSBmdW5jdGlvbiwKCmBgYHtyfQptb3Jhbi5wbG90KHgsIHNwa25lYXIzNWdhdXMpCmBgYAoKd2hpY2ggZmxhZ3MgcG90ZW50aWFsIG91dGxpZXJzIC8gaW5mbHVlbnRpYWwgb2JzZXJ2YXRpb25zLiBUbyBzdXBwcmVzcyB0aGVpciBsYWJlbGxpbmcsIGluY2x1ZGUgdGhlIGFyZ3VtZW50IGxhYmVscz1GIC0tIHRyeSBpdCEKClRoZSBNb3JhbiBjb2VmZmljaWVudCBhbmQgcmVsYXRlZCB0ZXN0IHByb3ZpZGUgYSBtZWFzdXJlIG9mIHRoZSBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBpbiB0aGUgZGF0YSwgZ2l2ZW4gdGhlIHNwYXRpYWwgd2VpZ2h0aW5ncy4KCmBgYHtyfQptb3Jhbi50ZXN0KHgsIHNwa25lYXIzNWdhdXMpCmBgYAoKRXNzZW50aWFsbHkgdGhlIE1vcmFuIHN0YXRpc3RpYyBpcyBhIGNvcnJlbGF0aW9uIHZhbHVlLCBhbHRob3VnaCBpdCBuZWVkIG5vdCBiZSBleGFjdGx5IHplcm8gaW4gdGhlIHByZXNlbmNlIG9mIG5vIGNvcnJlbGF0aW9uIChoZXJlIHRoZSBleHBlY3RlZCB2YWx1ZSBpcyBub3QgemVybyBidXQgc2xpZ2h0bHkgbmVnYXRpdmUpIGFuZCBuZWVkIG5vdCBiZSBpbiB0aGUgcmFuZ2UgLTEgdG8gKzEuIFRoZSBpbnRlcnByZXRhdGlvbiB0aG91Z2ggaXMgdGhhdCB0aGUgcHJpY2Ugb2YgdGhlIGxhbmQgcGFyY2VscyBhbmQgdGhlaXIgbmVpZ2hib3VycyBhcmUgcG9zaXRpdmVseSBjb3JyZWxhdGVkOiB0aGVyZSBpcyBhIHRlbmRlbmN5IGZvciBsaWtlLW5lYXItbGlrZSB2YWx1ZXMuCgpNb3JlIHN0cmljdGx5LCB3ZSBzaG91bGQgYWNrbm93bGVkZ2UgdGhlIG5vdGUgZm91bmQgdW5kZXIgP21vcmFuLnRlc3QoKSB0aGF0IHRoZSBkZXJpdmF0aW9uIG9mIHRoZSB0ZXN0cyBhc3N1bWVzIHRoZSBtYXRyaXggaXMgc3ltbWV0cmljLCB3aGljaCBzcGtuZWFyMzVnYXVzIGlzIG5vdC4gVG8gY29ycmVjdCBmb3IgdGhpcyB3ZSBjYW4gdXNlIGEgaGVscGVyIGZ1bmN0aW9uLCBsaXN0dzJVKCksCgpgYGB7cn0KbW9yYW4udGVzdCh4LCBsaXN0dzJVKHNwa25lYXIzNWdhdXMpKQpgYGAKCiMjIyBXaGF0IGlzIHRoZSByYW5nZSBvZiB0aGUgTW9yYW4ncyB2YWx1ZT8KClhYWFggb2JzZXJ2ZSB0aGF0IHRoZSBwb3RlbnRpYWwgcmFuZ2UgZm9yIGEgTW9yYW4ncyB2YWx1ZSBpcyBkZXBlbmRlbnQgdXBvbiB0aGUgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeC4gVXNpbmcgdGhlaXIgY29kZToKCmBgYHtyfQptb3Jhbi5yYW5nZSA8LSBmdW5jdGlvbihsdykgewogIHdtYXQgPC0gbGlzdHcybWF0KGx3KQogIHJldHVybihyYW5nZShlaWdlbigod21hdCArIHQod21hdCkpLzIpJHZhbHVlcykpCn0KbW9yYW4ucmFuZ2Uoc3BrbmVhcjM1Z2F1cykKbW9yYW4ucmFuZ2Uoc3BrbmVhcjM1YmlzcSkKbW9yYW4ucmFuZ2Uoc3Bjb250aWcpCm1vcmFuLnJhbmdlKG5iMmxpc3R3KGNvbnRpZywgemVyby5wb2xpY3kgPSBUUlVFKSkKYGBgCgpOb25lIG9mIHRoZXNlIGluc3BpcmUgY29uZmlkZW5jZSB0aGF0IE1vcmFuIHZhbHVlcyB0eXBpY2FsbHkgbGllIGluIHRoZSByYW5nZSAtMSB0byArMSAoZWl0aGVyIHRoYXQgb3IgdGhlIG1ldGhvZCB0byBkZXRlcm1pbmUgdGhlIGV4dHJlbWVzIGlzIHdyb25nIGJ1dCBJIGhhdmUgbm8gZXZpZGVuY2UgdGhhdCBpcyB0aGUgY2FzZSkuIEluY3JlYXNpbmdseSBJIHdvbmRlciBpZiBpdCB3b3VsZCBiZSBlYXNpZXIgdG8gdXNlLCBlLmcuCgpgYGB7cn0KeCA8LSBjb21iaW5lZC5tYXAkTE5QUklDRQpyIDwtIGNvcih4LCBsYWcubGlzdHcoc3BrbmVhcjM1Z2F1cywgeCkpCnIKYGBgCgouLi4gd2hpY2ggaXMganVzdCB0aGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB2YXJpYWJsZSBhbmQgaXRzIGxhZ2dlZCBlcXVpdmFsZW50IGFuZCB3aWxsIGxpZSBpbiB0aGUgcmFuZ2UgZnJvbSAtMSB0byArMS4gQ2FsY3VsYXRpbmcgdGhlIHNpZ25pZmljYW5jZSBvZiB0aGUgdmFsdWUgY2FuIGJlIGRvbmUgYnkgc2ltdWxhdGlvbi4gRm9yIGV4YW1wbGUsCgpgYGB7cn0KbGlzdHcgPC0gc3BrbmVhcjM1Z2F1cwp4IDwtIGNvbWJpbmVkLm1hcCRMTlBSSUNFCm5zaW1zIDwtIDEwMDAKCnZhbHMgPC0gc2FwcGx5KDE6bnNpbXMsIGZ1bmN0aW9uKGkpIHsKICB4eCA8LSBzYW1wbGUoeCwgbGVuZ3RoKHgpLCByZXBsYWNlID0gRkFMU0UpICAKICBjb3IoeHgsIGxhZy5saXN0dyhsaXN0dywgeHgsIHplcm8ucG9saWN5ID0gVFJVRSkpCn0pCmBgYAoKLi4uIGNhbGN1bGF0ZXMgdGhlIGNvcnJlbGF0aW9uIDEwMDAgdGltZXMgd2l0aCByYW5kb20gcmUtYXJyYW5nZW1lbnRzIG9mIHRoZSBkYXRhICh3aGVyZSB0aGUgc2FtZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGlzIHVzZWQsIHRoZSBuZWlnaGJvdXJzIHJlbWFpbiB0aGUgc2FtZSBhcyBkb2VzIHRoZWlyIHdlaWdodGluZywgYnV0IHRoZSBkYXRhIGFyZSBzaHVmZmxlZCBhcm91bmQgZ2VvZ3JhcGhpY2FsbHkpLiBJZiB0aGUgYWN0dWFsIGNvcnJlbGF0aW9uLCAkciQsIGlzIGdyZWF0ZXIgdGhhbiA5OSUgb2YgdGhlIHJhbmRvbWx5IGdlbmVyYXRlZCB2YWx1ZXMgdGhlbiB3ZSBoYXZlIHJlYXNvbiB0byBiZWxpZXZlIHRoYXQgdGhlIHBvc2l0aXZlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHZhbHVlcyBhbmQgdGhlaXIgbmVpZ2hib3VycyAoYXMgZGVmaW5lZCB1c2luZyB0aGUgcGFydGljdWxhciBzcGF0aWFsIHdlaWdodHMgbWF0cml4KSBpcyAnc2lnbmlmaWNhbnQnIGF0IGFib3ZlIGEgOTklIGxldmVsLgoKYGBge3J9CnIgPiBxdWFudGlsZSh2YWxzLCBwcm9icyA9IDAuOTkpCmBgYAoKIyMjIENoZWNraW5nIGEgcmVncmVzc2lvbiBtb2RlbCBmb3Igc3BhdGlhbGx5IGNvcnJlbGF0ZWQgZXJyb3JzCgpBc3N1bWUgd2UgZml0IHRoZSByZWdyZXNzaW9uIG1vZGVsLAoKYGBge3J9Cm1vZGVsMSA8LSBsbShMTlBSSUNFIH4gRENCRCArIERFTEUgKyBEUklWRVIgKyBEUEFSSyArIFBPUERFTiArIEpPQkRFTiArIFkwNDA1ICsgWTA2MDcgKyBZMDgwOSwgZGF0YT1jb21iaW5lZC5tYXApCmBgYAoKQW4gYXNzdW1wdGlvbiBvZiByZWdyZXNzaW9uIGlzIHRoYXQgdGhlIHJlc2lkdWFscyBhcmUgc3VwcG9zZWQgdG8gYmUgbGVmdCBvdmVyIGFuZCByYW5kb20gJ25vaXNlJy4gSWYgdGhhdCBpcyB0cnVlLCB0aGV5IHNob3VsZCBiZSBpbmRlcGVuZGVudCBvZiBvbmUgYW5vdGhlciBhbmQgbm90IGRpc3BsYXkgYW55IHNwYXRpYWwgcGF0dGVybi4gSXMgdGhhdCB0aGUgY2FzZSBoZXJlPyBPbmUgd2F5IHRvIGNoZWNrIGlzIGJ5IG1hcHBpbmcgdGhlbToKCmBgYHtyfQpyZXNpZHMgPC0gcnN0dWRlbnQobW9kZWwxKQpyZXNpZHMgPC0gY3V0KHJlc2lkcywgYnJlYWtzID0gYygtSW5mLCAtMi41OCwgLTEsIDEsIDIuNTgsIEluZikpCmNvbWJpbmVkLm1hcCRyZXNpZHMgPC0gcmVzaWRzCnJlcXVpcmUodG1hcCkKdG1hcF9zdHlsZSgiY29sX2JsaW5kIikKdG1fc2hhcGUoZGlzdHJpY3RzKSArCiAgdG1fcG9seWdvbnMoKSArCnRtX3NoYXBlKGNvbWJpbmVkLm1hcCkgKwogIHRtX2J1YmJsZXMoInJlc2lkcyIsIHNpemUgPSAwLjA1KQpgYGAKCkkgd291bGQgc2F5IHRoYXQgaXQgaXMgaGFyZCB0byBqdWRnZS4gQW4gYWx0ZXJuYXRpdmUgaXMgdG8gdXNlIGEgTW9yYW4ncyB0ZXN0LCBpZGVhbGx5IHRoZSB2ZXJzaW9uIG9mIHRoZSB0ZXN0IGludGVuZGVkIGZvciB1c2Ugb24gcmVncmVzc2lvbiByZXNpZHVhbHMuCgpgYGB7cn0KbG0ubW9yYW50ZXN0KG1vZGVsMSwgbGlzdHcyVShzcGtuZWFyMzVnYXVzKSkKIyBsaXN0dzJVIGlzIHVzZWQgYmVjYXVzZSB0aGUgd2VpZ2h0cyBhcmUgbm90IHN5bW1ldHJpY2FsCmBgYAoKVGhlIGVzdGltYXRlZCBjb3JyZWxhdGlvbiBpcyBhYm91dCAwLjA5Ny4gTm90IGh1Z2UsIHBlcmhhcHMgYnV0IHNpZ25pZmljYW50IGVub3VnaCB0byBxdWVzdGlvbiB0aGUgYXNzdW1wdGlvbiBvZiBpbmRlcGVuZGVuY2UuCk5vdGUgdGhhdCB0aGlzIHJlc3VsdCBpcyBkZXBlbmRlbnQgb24gdGhlIHNwYXRpYWwgd2VpZ2h0aW5ncy4gSWYgd2UgY2hhbmdlIHRoZW0sIHRoZW4gdGhlIHJlc3VsdHMgb2YgdGhlIE1vcmFuIHRlc3Qgd2lsbCBjaGFuZ2UgYWxzby4gRm9yIGV4YW1wbGUsIHVzaW5nIHRoZSBiaS1zcXVhcmUgd2VpZ2h0aW5ncywKCmBgYHtyfQpsbS5tb3JhbnRlc3QobW9kZWwxLCBsaXN0dzJVKHNwa25lYXIzNWJpc3EpKQpgYGAKCkhlcmUgdGhlIGNoYW5nZSBpcyBzbGlnaHQsIGxhcmdlbHkgYmVjYXVzZSB0aGUgcmF0ZSBvZiBkZWNheSBvZiB0aGUgaW52ZXJzZSBkaXN0YW5jZSB3ZWlnaHRpbmcgbWF0dGVycyBsZXNzIHRoYW4gdGhlIG51bWJlciBvZiBuZWlnaGJvdXJzIGl0IGRlY2F5cyB0by4KCiMjIExvY2FsaXNlZCBNb3JhbiB2YWx1ZXMKClRoZSBNb3JhbidzIHZhbHVlIGlzIGFuIGV4YW1wbGUgb2YgYSBnbG9iYWwgc3RhdGlzdGljLCB3aGljaCBpcyBvbmUgY2FsY3VsYXRlZCBmb3IgdGhlIHdob2xlIHN0dWR5IHJlZ2lvbi4gSXQgaXMgYSBzaW5nbGUgKGFuIGF2ZXJhZ2UpIG1lYXN1cmUgb2Ygc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYmV0d2VlbiBuZWlnaGJvdXJzLiBJdCBjYW4sIGhvd2V2ZXIsIGJ5ICd1bnBhY2tlZCcgdG8gZ2l2ZSBhIHNlcmllcyBvZiBsb2NhbCBNb3JhbiB2YWx1ZXMgLS0gb25lIGZvciBlYWNoIGxvY2F0aW9uIGluIHRoZSBkYXRhOgoKYGBge3J9CmxvY2FsbSA8LSBsb2NhbG1vcmFuKGNvbWJpbmVkLm1hcCRMTlBSSUNFLCBsaXN0dzJVKHNwa25lYXIzNWdhdXMpKQpoZWFkKGxvY2FsbSkKYGBgCgpUaGlzIHByb2R1Y2UgYSBtYXRyaXgsIHdoZXJlIHRoZSBmaXJzdCBjb2x1bW4gY29udGFpbnMgdGhlIGxvY2FsIE1vcmFuIHZhbHVlcy4gQSBzdGF0aXN0aWNhbCB0ZXN0IG9mIHNpZ25pZmljYW5jZSBhbHNvIGlzIGluY2x1ZGVkLgoKQSBwb3NpdGl2ZSBsb2NhbCBNb3JhbiB2YWx1ZSBpbmRpY2F0ZXMgYSBsb2NhbCBjbHVzdGVyIG9mIGFsaWtlIHZhbHVlczsgYSBuZWdhdGl2ZSBNb3JhbiB2YWx1ZSBpbmRpY2F0ZXMgYSBjbHVzdGVyIG9mIGRpc3NpbWlsYXIgdmFsdWVzLiBXaGF0IHRoZXkgZG8gbm90IGRpc3Rpbmd1aXNoIGJldHdlZW4gaXMgd2hldGhlciwgZm9yIGV4YW1wbGUsIGhpZ2ggbGFuZCBwcmljZSB2YWx1ZXMgYXJlIHN1cnJvdW5kZWQgYnkgb3RoZXIgaGlnaCBsYW5kIHByaWNlcyBvciB3aGV0aGVyIGxvdyBsYW5kIHByaWNlcyBhcmUgc3Vycm91bmRlZCBieSBvdGhlciBsb3cgbGFuZCBwcmljZXMgLS0gYm90aCBhcmUgZXhhbXBsZXMgb2YgcG9zaXRpdmUgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gKHNpbWlsYXIgdmFsdWVzIGNsdXN0ZXJpbmcgdG9nZXRoZXIpLiBFcXVhbGx5LCB0aGV5IGRvIG5vdCBkaXN0aW5ndWlzaCBiZXR3ZWVuIHdoZXRoZXIgbG93IHByaWNlcyBhcmUgc3Vycm91bmRlZCBieSBoaWdoIHByaWNlcyBvciBoaWdoIHByaWNlcyBieSBsb3cgKGJvdGggYXJlIGV4YW1wbGVzIG9mIG5lZ2F0aXZlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uKS4KClRvIGRpc3Rpbmd1aXNoIHRoZW0sIHdlIG5lZWQgdG8gcmVmZXIgdG8gdGhlIG9yaWdpbmFsIGRhdGEgKGxldCdzIGNhbGwgaXQgJHgkKS4gSWYgJHgkIGlzIGFib3ZlIHRoZSBtZWFuIGFuZCB0aGUgbG9jYWwgTW9yYW4gdmFsdWUgaXMgcG9zaXRpdmUgYW5kIHNpZ25pZmljYW50IHRoZW4gd2UgbWlnaHQgYXJndWUgdGhhdCBpdCBpcyBhICdob3Qgc3BvdCcgb2YgYSBoaWdoIHZhbHVlIHN1cnJvdW5kZWQgYnkgb3RoZXIgaGlnaCB2YWx1ZXM6IGhpZ2gtaGlnaC4gSWYgJHgkIGlzIGJlbG93IHRoZSBtZWFuIGFuZCB0aGUgbG9jYWwgTW9yYW4gdmFsdWUgaXMgcG9zaXRpdmUgYW5kIHNpZ25pZmljYW50IHRoZW4gd2UgY291bGQgc2F5IGl0IGlzIGEgJ2NvbGQgc3BvdCcgb2YgYSBsb3cgdmFsdWUgc3Vycm91bmRlZCBieSBvdGhlciBsb3cgdmFsdWVzOiBsb3ctbG93LiBJZiAkeCQgaXMgYWJvdmUgdGhlIG1lYW4gYnV0IHRoZSBNb3JhbiB2YWx1ZSBpcyBuZWdhdGl2ZSBhbmQgc2lnbmlmaWNhbnQgdGhlbiBoaWdoLWxvdzsgaWYgJHgkIGlzIGJlbG93IHRoZSBtZWFuIGJ1dCB0aGUgTW9yYW4gdmFsdWUgaXMgbmVnYXRpdmUgYW5kIHNpZ25pZmljYW50IHRoZW4gbG93LWhpZ2g6CgpgYGB7cn0KeCA8LSBjb21iaW5lZC5tYXAkTE5QUklDRQpsb2NhbG0gPC0gbG9jYWxtb3Jhbihjb21iaW5lZC5tYXAkTE5QUklDRSwgbGlzdHcyVShzcGtuZWFyMzVnYXVzKSkKdHlwZSA8LSByZXAoTkEsIHRpbWVzID0gbnJvdyhjb21iaW5lZC5tYXApKQp0eXBlIDwtIGlmZWxzZSh4ID4gbWVhbih4KSAmIGxvY2FsbVssMV0gPiAwICYgbG9jYWxtWywxXSA8IDAuMDUsCiAgICAgICAiSGlnaCAtIEhpZ2giLCB0eXBlKQp0eXBlIDwtIGlmZWxzZSh4ID4gbWVhbih4KSAmIGxvY2FsbVssMV0gPCAwICYgbG9jYWxtWywxXSA8IDAuMDUsCiAgICAgICAiSGlnaCAtIExvdyIsIHR5cGUpCnR5cGUgPC0gaWZlbHNlKHggPCBtZWFuKHgpICYgbG9jYWxtWywxXSA8IDAgJiBsb2NhbG1bLDFdIDwgMC4wNSwKICAgICAgICJMb3cgLSBMb3ciLCB0eXBlKQp0eXBlIDwtIGlmZWxzZSh4IDwgbWVhbih4KSAmIGxvY2FsbVssMV0gPiAwICYgbG9jYWxtWywxXSA8IDAuMDUsCiAgICAgICAiTG93IC0gSGlnaCIsIHR5cGUpCmNvbWJpbmVkLm1hcCR0eXBlIDwtIHR5cGUKc3ViLm1hcCA8LSBjb21iaW5lZC5tYXBbIWlzLm5hKHR5cGUpLF0KdG1hcF9zdHlsZSgiY29iYWx0IikKdG1fc2hhcGUoZGlzdHJpY3RzKSArCiAgdG1fcG9seWdvbnMoKSArCnRtX3NoYXBlKHN1Yi5tYXApICsKICB0bV9idWJibGVzKCJ0eXBlIiwgc2l6ZSA9IDAuMDUpCmBgYAoKTW9yYW4gdmFsdWVzIGNhbiBhbHNvIGJlIGNhbGN1bGF0ZWQgZm9yIHJlZ3Jlc3Npb24gcmVzaWR1YWxzLiBGb3IgZXhhbXBsZSwKCmBgYHtyfQpsb2NhbG1yIDwtIGxvY2FsbW9yYW4uZXhhY3QobW9kZWwxLCBuYiA9IGtubjJuYihrbmVhcm5laWdoKGNvbWJpbmVkLm1hcCwgayA9IDM1KSkpCmBgYAoKU2VlID9sb2NhbG1vcmFuLmV4YWN0IGZvciBmdXJ0aGVyIGRldGFpbHMuCgojIyBEaXNjdXNzaW9uIGFuZCBDb25jbHVzaW9uCgpBIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggaXMgYSB3YXkgb2YgZW5jb2RpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG5laWdoYm91cnMgYW5kIGlzIGEgbWFya2VyIG9mIHNwYXRpYWwgYW5hbHlzaXMuIFRoZSBkaWZmaWN1bHR5IGlzIHRoYXQgdGhlIHJlc3VsdHMgb2J0YWluZWQgYXJlIGEgZnVuY3Rpb24gb2YgdGhlIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggdXNlZCAtLSBjaGFuZ2UgdGhlIG1hdHJpeCBhbmQgeW91IHdpbGwgY2hhbmdlcyB0aGUgcmVzdWx0cyB0b28uIEhvdywgdGhlbiwgdG8gY2hvb3NlIHRoZSByaWdodCBtYXRyaXg/IEluIHByYWN0aWNlLCB0aGlzIGlzIG9mdGVuIGdpdmVuIHRvbyBsaXR0bGUgdGhvdWdodCBidXQsIGlkZWFsbHksIGl0IHNob3VsZCBiZSBiYXNlZCBvbiBzb21lIHRoZW9yZXRpY2FsIGJhc2lzIG9yIHRocm91Z2ggYW4gYW5hbHlzaXMgb2YgdGhlIHNjYWxlIG9mIHRoZSBnZW9ncmFwaGljYWwgcGF0dGVybmluZyBvZiB0aGUgZGF0YS4gTW9zdCBsaWtlbHkgdGhlcmUgd2lsbCBub3QgYmUgb25lIGNvcnJlY3QgY2hvaWNlIHNvIGFuIGVsZW1lbnQgb2YgdHJpYWwtYW5kLWVycm9yIGFuZCBzZW5zaXRpdml0eSBhbmFseXNpcyAoaG93IHNlbnNpdGl2ZSBhcmUgdGhlIHJlc3VsdHMgdG8gYSBjaGFuZ2UgaW4gdGhlIG1hdHJpeD8pIG1heSBiZSByZXF1aXJlZC4KCklmIHlvdSBoYXZlIHRpbWUgbGVmdCBvdmVyLCByZWFkIHRoZSBCaXZhbmQgYXJ0aWNsZSBmcm9tIHRoZSBmdXJ0aGVyIHJlYWRpbmcgYmVsb3cuCgojIyBGdXJ0aGVyIFJlYWRpbmcKCkJpdmFuZCBSLCAyMDE5LiBbQ3JlYXRpbmcgTmVpZ2hib3Vyc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NwZGVwL3ZpZ25ldHRlcy9uYi5wZGYpCgoK