suppressWarnings(if(!require("pacman")) install.packages("pacman"))

pacman::p_load('tidyverse', 'sf', 'tmap', 'geojsonio', 'sp')

Based on Adam Dennet’s Australian Population Studies

Modernized version of Adam’s accompanying : https://rpubs.com/adam_dennett/376877

Setting up some spatial data

As the name suggests, to run a spatial interaction model, you are going to need some spatial data and some data on interactions (flows). Let’s start with some spatial data:

  • EPSG:4283 - Australia coordinates in degrees
library(sf)
# I/O for GeoJSON
library(geojsonio)

# Read geojson file as spatial object
aus <- geojson_read("https://www.dropbox.com/s/0fg80nzcxcsybii/GCCSA_2016_AUST_New.geojson?raw=1", what = "sp")




# Read data as simple features object and set coordinate reference system
aus_sf <- aus %>% 
  st_as_sf() %>% 
  st_set_crs(4283)

aus_sf %>% 
  head()
  • sf::as() - Methods to coerce sf to sp –>Spatial* and Spatial*DataFrame objects
# Now you may have noticed that the code order is a bit weird, so let's fix that and reorder
aus_sf1 <- aus_sf %>% 
  arrange(GCCSA_CODE)

# Now let's create an 'sp' object from our new ordered SF object
aus <- as_Spatial(aus_sf1)

Check your boundaries have downloaded ok

library(tmap)
tmap_mode("view")
#qtm(aus_sf)

aus_sf %>% 
  st_make_valid() %>% 
  tm_shape() +
  tm_borders(col = "black", lwd = 2, alpha = 0.4) +
  tm_fill(col = "gold", alpha = 0.1)

Calculating a distance matrix

In our spatial interaction model, space is one of the key predictor variables. In this example we will use a very simple Euclidean distance measure between the centroids of the Greater Capital City Statistical Areas as our measure of space.

Now, with some areas so huge, there are obvious potential issues with this (for example we could use the average distance to larger settlements in the noncity areas), however as this is just an example, we will proceed with a simple solution for now.

  • EPSG:3112 – Projected Australia coordinates in metres

  • sf::st_transform - transform coordinate in sf

library(sp)
# Use the spDists function to create a distance matrix
# First reproject into a projected (metres) coordinate system
aus_proj <- spTransform(aus, "+init=epsg:3112")

summary(aus_proj)
## Object of class SpatialPolygonsDataFrame
## Coordinates:
##        min      max
## x -2083066  2346598
## y -4973093 -1115948
## Is projected: TRUE 
## proj4string :
## [+proj=lcc +lat_0=0 +lon_0=134 +lat_1=-18 +lat_2=-36 +x_0=0 +y_0=0
## +ellps=GRS80 +units=m +no_defs]
## Data attributes:
##   GCCSA_CODE         GCC_CODE16         GCCSA_NAME         STATE_CODE       
##  Length:15          Length:15          Length:15          Length:15         
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##   STATE_NAME          AREA_SQKM      
##  Length:15          Min.   :   1695  
##  Class :character   1st Qu.:   4838  
##  Mode  :character   Median :  15842  
##                     Mean   : 512525  
##                     3rd Qu.: 884729  
##                     Max.   :2520230

Now let’s calculate the distances

dist <- spDists(aus_proj) %>%
  as_tibble() %>% 
  mutate(n = row_number())

dist %>% 
  slice_head(n = 5)
# Probably do the same in sf?

#aus_projection_sf = st_transform(aus_sf, "+init=epsg:3112")
#sf_dist = st_distance(aus_projection_sf)
# Pivot longer the data and make distance km
dist <- dist %>% 
  pivot_longer(!n, names_to = "var2", values_to = "dist") %>%
  mutate(dist = dist/1000)

dist %>% 
  slice_head(n = 5)

These distances are notionally in km - although you may notice that they are not 100% accurate. This is not a big problem for now as this is just an example, but for real applications, more accurate distances may be used.

Flow Data

The data we are going to use to test our spatial interaction models with is migration data from the 2011 Australian Census. The Australian Census has usual address indicator on Census night (UAICP) and address one year ago and 5 years ago indicators. From these, one year and 5 year migration transitions can be recorded - here we will use the 5 year transitions.

As well as flow data, there are additional data on unemployment rates, weekly income and the percentage of people living in rented accommodation for each origin and destination. We will use these as destination attractiveness / mass term and origin emissiveness / mass term proxies in the models which follow.

These data can be read straight into R with the following command:

#read in your Australian Migration Data
mdata <- read_csv("https://www.dropbox.com/s/wi3zxlq5pff1yda/AusMig2011.csv?raw=1", show_col_types = FALSE)

mdata %>% 
  slice_head(n = 5)

Now to finish, we need to add in our distance data that we generated earlier and create a new column of total flows which excludes flows that occur within areas (we could keep the within-area (intra-area) flows in, but they can cause problems so for now we will just exclude them).

# First create a new total column which excludes intra-zone totals
# Sets them to a very small number for reasons we will see later
mdata <- mdata %>% 
  mutate(
    flow_no_intra = case_when(
      Orig_code == Dest_code ~ 0,
      TRUE ~ Flow),
    
    offset = case_when(
      Orig_code == Dest_code ~ 1e-10,
      TRUE ~ 1))

mdata %>% 
  slice_head(n = 5)

Now we ordered our spatial data earlier so that our zones are in their code order. We can now easily join these data together with our flow data as they are in the correct order.

# and while we are here, rather than setting the intra zonal distances to 0
# we should set them to something small (most intrazonal moves won't occur over 0 distance)

mdata <- mdata %>% 
  bind_cols(dist %>% select(dist)) %>% 
  mutate(dist = case_when(
    dist == 0 ~ 5,
    TRUE ~ dist))

mdata %>% 
  slice_head(n = 5)

And this is what those flows look like on a map - quick and dirty style… Although first we’ll remove the intra-zonal flows.

# remove intra-zonal flows
mdatasub <- mdata %>% 
  filter(Orig_code != Dest_code)

Now let’s create a flow-line object and weight the lines according to the flow volumes

  • Work is needed to convert the OD data into ‘desire lines’. Desire lines are straight lines between the origin and destination and represent where people would go if they were not constrained by the route network (see Figure 3 from (Lovelace et al. 2017)).

  • Origin, destination,flow columns

# use the od2line function from Robin Lovelace's excellent stplanr package - remove all but the origin, destination and flow columns
library(stplanr)
mdatasub_skinny <- mdatasub %>% 
  select(Orig_code, Dest_code, Flow)
# Desire lines
travel_network <- od2line(flow = mdatasub_skinny, zones = aus)

# Converts flows to WGS84
travel_networkwgs <- spTransform(travel_network, "+init=epsg:4326")

# Set the line widths to some sensible values according to flow
w <- mdatasub_skinny$Flow / max(mdatasub_skinny$Flow) * 10

Make a map of the linestrings representing movement from one place to another.

library(leaflet)
# Plot in leaflet
leaflet() %>% 
  addTiles() %>% 
  addPolylines(
    data = travel_networkwgs,
    weight = w)

Or you can view your flows as a matrix…

  • rowwise: allows you to compute on a data frame a row-at-a-time

  • c_across() is designed to work with rowwise() to make it easy to perform row-wise aggregations.

# Pivot wider and find the total flow from a specific area
mdatasubmat <- mdata %>% 
  select(Orig_code, Dest_code, flow_no_intra) %>% 
  pivot_wider(names_from = Dest_code, values_from = flow_no_intra) %>%
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))) ) 

mdatasubmat

Okay, we’ve set everything up, now it’s…

Modellin’ Time!!

Population flows can be conceptualised as interactions between two entities – origins and destinations – which have different properties of emissivity and attractiveness (see Lee 1966 for the classic paper on this topic in relation to migration).

In explaining how to run and calibrate spatial interaction models in R, I will adopt the notation used by Taylor Oshan in his excellent primer for running spatial interation models in Python. The paper is well worth a read and can be found here: http://openjournals.wu.ac.at/region/paper_175/175.html

Below is the classic multiplicative gravity model:

  1. \[T_{ij} = k \frac{V_i^\mu W_j^\alpha}{d_{ij}^\beta}\]

This gravity model can be written in the form more familiar from Wilson’s 1971 paper - http://journals.sagepub.com/doi/abs/10.1068/a030001

\(T_{ij} = k V_i^\mu W_j^\alpha d_{ij}^-\beta\)

This model just says that the flows between an origin and destination are proportional to the product of the mass of the origin and destination and inversely proportional to the distance between them.

As origin and destination masses increase, flows increase, but as distance increases, flows decrease, and vice versa.

  • where TijTij is the transition or flow, TT, between origin ii (always the rows in a matrix) and destination jj (always the columns in a matrix). If you are not overly familiar with matrix notation, the ii and jj are just generic indexes to allow us to refer to any cell in the matrix more generally.

  • VV is a vector (a 1 dimensional matrix - or, if you like, a single line of numbers) of origin attributes which relate to the emissiveness of all origins in the dataset, ii - in our sample dataset, we have a vector of origin populations (which I have called vi1_origpop) and a vector of origin average salaries (which I have called vi2_origsal) in 2001

  • WW is a vector of desination of attributes relating to the attractivenss of all destinations in the dataset, jj - in our sample dataset, we have a vector of destination populations (which I have called wj1_destpop) and a vector of destination average salaries (which I have called wj2_destsal) in 2001

  • dd is a matrix of costs relating to the flows between ii and jj - in our case the cost is distance and it is called ‘dist’ in our dataset.

  • kk, μμ, αα and ββ are all model parameters to be estimated

kk is a constant of proportionality and leads to this particular model being more accurately described as a ‘total constrained’ model as all flows estimated by the model will sum to any observed flow data used to calibrate the parameters, where:

  1. \[k = \frac{T}{\sum_i \sum_jV_i^\mu W_j^\alpha d_{ij}^-\beta }\]

and TT is the sum of our matrix of observed flows or:

  1. \[T = \sum_i \sum_j T_{ij}\]

In plain language, this is just the sum of all observed flows divided by the sum of all of the other elements in the model.

Estimating Model Parameters

Now, it’s perfectly possible to produce some flow estimates by plugging some arbitrary or expected estimated values into our parameters. The parameters relate to the scaling effect / importance of the variables they are associated with.

Most simply, where the effects of origin and destination attributes on flows scale in a linear fashion (i.e. for a 1 unit increase in, say, population at origin, we might expect a 1 unit increase in flows of people from that origin, or for a halving in average salary at destination, we might expect a halving of commuters), μμ = 1 and αα = 1.

In Newton’s original gravity equation, ββ = -2 where the influence of distance on flows follows a power law - i.e. for a 1 unit increase in distance, we have a 1^-2 (1) unit decrease in flows, for a 2 unit increase in distance, we have 2^-2 (0.25 or 1/4) of the flows, for a 3 unit increase, 3^-2 (0.111) etc.

Let’s see if these parameters are a fair first guess

  1. \[ d_{ij}^\beta \ with\ \beta=-2 \]
#First plot the commuter flows against distance and then fit a model line with a ^-2 parameter
theme_set(theme_light())
library(scales)

mdata %>% 
  ggplot(mapping = aes(x = dist, y = Flow)) +
  geom_point() +
  geom_function(fun = ~.x^-2, color = "red", lwd = 1) +
  scale_y_continuous(name = "Migration Flow", labels = comma)

  1. \[ V_i^\mu \]
mdata %>% 
  ggplot(mapping = aes(x = vi1_origpop, y = Flow)) +
  geom_point() +
  geom_function(fun = ~.x^1, color = "red", lwd = 1) +
  scale_x_continuous(name = "Origin Population", labels = comma) +
  scale_y_continuous(name = "Migration Flow", labels = comma)

  1. \[ W_j^\alpha \]
mdata %>% 
  ggplot(mapping = aes(x = wj3_destmedinc, y = Flow)) +
  geom_point() +
  geom_function(fun = ~.x^1, color = "red", lwd = 1) +
  scale_x_continuous(name = "Destination Median income", labels = comma) +
  scale_y_continuous(name = "Migration Flow", labels = comma)

OK, so it looks like we’re not far off (well, destination income doesn’t look too promising as a predictor, but we’ll see how we get on…), so let’s see what flow estimates with these starting parameters look like.

# Set up some variables to hold our parameter values in:
mu <- 1
alpha <- 1
beta <- -2
k <- 1
T2 <- sum(mdatasub %>% pull(Flow))

Now let’s create some flow estimates using Equation 2 (multiplicative gravity mode) above… Begin by applying the parameters to the variables:

# Emmisivity, Atrractiveness, Distance
vi1_mu <- (mdatasub %>% pull(vi1_origpop))^mu
wj3_alpha <- (mdatasub %>% pull(wj3_destmedinc))^alpha
dist_beta <- (mdatasub %>% pull(dist))^beta

T1 <- vi1_mu*wj3_alpha*dist_beta
k <- T2/sum(T1)
k
## [1] 3.112867

Then, just as in Equation 2 above, just multiply everything together to get your flow estimates:

  • mdatasub : data without intra flows
# Run the model and store your flow estimates in a new column
mdatasub <- mdatasub %>% 
  mutate(unconstrainedEst1 = round(k*vi1_mu*wj3_alpha*dist_beta, 0))

# Check that the sum of these estimates makes sense
sum(mdatasub$unconstrainedEst1)
## [1] 1313520

Pivot wider the data and have a look at your handy work

mdatasubmat1 <- mdatasub %>% 
  select(Orig_code, Dest_code, unconstrainedEst1) %>% 
  pivot_wider(names_from = Dest_code, values_from = unconstrainedEst1, values_fill = 0) %>%
   relocate("1GSYD", .after = Orig_code) %>% 
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))) ) 

mdatasubmat1

How does the flow compare with the original?

mdatasubmat

How good is my model?

So, looking at the two little matrices above you can see that in some cases the flow estimates aren’t too bad, but in others they are pretty rubbish. Whilst it’s OK to eyeball small flow matrices like this, when you have much larger matrices, we need another solution…

Testing the “goodness-of-fit”.

Yes, that’s what it’s called - I know, it doesn’t sound correct, but goodness-of-fit is the correct term for checking how well your model estimates match up with your observed flows.

So how do we do it?

Well… there are a number of ways but perhaps the two most common are to look at the coefficient of determination (r2r2) or the Square Root of Mean Squared Error (RMSE). You’ve probably come across r2r2 before if you have fitted a linear regression model, but you may not have come across RMSE. There are other methods and they all do more or less the same thing, which is essentially to compare the modelled estimates with the real data. r2r2 is popular as it is quite intuitive and can be compared across models. RMSE is less intuitive, but some argue is better for comparing changes to the same model. Here’s we’ll do both…

R-Squared

\(r^2\) is the square of the correlation coefficient, \(r\)

For our sample data, we can calculate this very easily using a little function

rsqrd <- function(truth, estimate){
  r = cor(truth, estimate)
  R2 = r^2
  return(R2)
}

rsqrd(truth = mdatasub$Flow, estimate = mdatasub$unconstrainedEst1)
## [1] 0.1953717

Using this function we get a value of 0.195 or around 20%. This tells us that our model accounts for about 20% of the variation of flows in the system. Not bad, but not brilliant either.

Root Mean Squared Error (RMSE)

We can use a similar simple function to calculate the RMSE for our data

rMSE <- function(truth, estimate){
  res = (truth - estimate)^2
 RMSE = sqrt(mean(res))
  return(round(RMSE, 3))
}

rMSE(truth = mdatasub$Flow, estimate = mdatasub$unconstrainedEst1)
## [1] 25858.11

This can be loosely interpreted as, on average, the flows are off by 25, 858.

The figure that is produced by the RMSE calculation is far less intuitive than the r2r2 value and this is mainly because it very much depends on things like the units the data are in and the volume of data. It can’t be used to compare different models run using different data sets. However, it is good for assessing whether changes to the model result in improvements. The closer to 0 the RMSE value, the better the model.

So how can we start to improve our fit…?

Improving our model: 1 - Calibrating parameters

Now, the model we have run above is probably the most simple spatial interaction model we could have run and the results aren’t terrible, but they’re not great either.

One way that we can improve the fit of the model is by calibrating the parameters on the flow data that we have.

The traditional way that this has been done computationally is by using the goodness-of-fit statistics. If you have the requisite programming skills, you can write a computer algorithm that iteratively adjusts each parameter, runs the model, checks the goodness-of-fit and then starts all over again until the goodness-of-fit statistic is maximised.

This is partly why spatial interaction modelling was the preserve of specialists for so long as acquiring the requisite skills to write such computer programmes can be challenging!

However, since the early days of spatial interaction modelling, a number of useful developments have occurred… For a more detailed explanation, read the accompanying paper, but I will skate over them again here.

The mathematically minded among you may have noticed that if you take the logarithms of both sides of Equation 2, you end up with the following equation:

  1. \(\ln T_{ij} = k + \mu\ln V_i + \alpha\ln W_j - \beta \ln d_{ij}\)

Those of you who have played around with regression models in the past will realise that this is exactly that - a regression model.

And if you have played around with regression models you will be aware that there are various pieces of software available to run regressions (such as R) and calibrate the parameters for us, so we don’t have to be expert programmers to do this - yay!

Now, there are a couple of papers that are worth reading at this point. Perhaps the best is by Flowerdew and Aitkin (1982), titled “A METHOD OF FITTING THE GRAVITY MODEL BASED ON THE POISSON DISTRIBUTION” - the paper can be found here: http://onlinelibrary.wiley.com/doi/10.1111/j.1467-9787.1982.tb00744.x/abstract

One of the key points that Flowerdew and Aitkin make is that the model in Equation 5 (known as a log-normal model) has various problems associated with it which mean that the estimates produced might not be reliable. If you’d like to know more about these, read the paper (and also Wilson’s 1971 paper), but at this point it is worth just knowing that the way around many of these issues is to re-specify the model, not as a log-normal regression, but as a Poisson or negative binomial regression model.

Poisson regression

Again, I go into this in more detail in the accompanying paper, but the main theory (for non-experts like me anyway) behind the Poisson regression model is that the sorts of flows that spatial interaction models deal with (such as migration or commuting flows) relate to non-negative integer counts (you can’t have negative people moving between places and you can’t - normally, if they are alive - have fractions of people moving either).

As such, the continuous (normal) probabilty distributions which underpin standard regression models don’t hold. However, the discrtete probability distributions such as the Poisson distribution and the negative binomial distribution (of which the Poisson distribution is a special case - wikipedia it) do hold and so we can use these associations to model our flows.

At this point, it’s probably worth you looking at what a Poisson disribution looks like compared to a normal distribution, if you are not familiar.

Here’s a normal distribution:

# histogram with normal distribution of count 3000, a mean of 75 and a standard deviation of 5
tibble(ndist = rnorm(3000, mean = 10, sd = 5)) %>% 
  ggplot() +
  geom_histogram(aes(x = ndist), fill = "midnightblue", alpha = 0.7) +
  xlab("rnorm(3000, mean = 75, sd = 5)")

Now here’s a Poisson distribution with the same mean:

# histogram with a poisson distribution of count 3000, a mean of 75 
tibble(pdist = rpois(n = 3000, lambda = 75)) %>% 
  ggplot() +
  geom_histogram(aes(x = pdist), fill = "indianred", alpha = 0.7) +
  xlab("rpois(n = 3000, lambda = 75)")

Looks kind of similar doesn’t it! The thing with the Poisson distribution is, when the mean (λ - lambda) changes, so does the distribution. Normal distributions on the other hand retain their bell shape.

As the mean gets smaller (and this is often the case with flow data where small flows are very likely - have a look at the ‘Total’ column in your cdata dataframe, lots of small numbers aren’t there?) the distribution starts to look a lot more like a skewed or log-normal distrbution. They key thing is it’s not - it’s a Poisson distribution. Here’s a similar frequency distribution with a small mean:

# What about a lambda of 0.5?
tibble(pdist = rpois(n = 3000, lambda = 0.5)) %>% 
  ggplot() +
  geom_histogram(aes(x = pdist), fill = "indianred", alpha = 0.7, binwidth = 1) +
  xlab("rpois(n = 3000, lambda = 75)")

As far as we’re concerned, what this means is that if we are interested in all flows between all origins and destinations in our system, these flows will have a mean value of \(\lambda_{ij}\) and this will dictate the distribution. Here’s what the distrbution of our flows looks like:

# Distribution of flows
mdatasub %>% 
  ggplot(mapping = aes(x = Flow)) +
  geom_histogram(fill = "darkorange", alpha = 0.8)

Reveals a histogram which looks like a skewed normal or, more accurately, a Poisson distribution with a small mean.

So, what does all of this mean for our spatial interaction model?

Well the main thing it means is that Equation 5, for most sorts of spatial interaction models where we are modelling flows of people or whole things, is not correct.

By logging both sides of the equation in Equation 5, we are trying to get a situation where our \(T_{ij}\) flows can be modelled by using the values of our other variables such as distance, by using a straight line a bit like this:

mdatasub %>% 
  ggplot(mapping = aes(x = dist, y = log(Flow))) +
  geom_point() +
  geom_smooth(method = lm)

If you compare this graph with the graph above (the first scatter plot we drew in this practical exercise), it’s exactly the same data, but clearly by logging both the total and distance, we can get a bit closer to being able to fit a model estimate using a straight line.

What the Poisson distribution means is that the yy variable in our model is not logged as in the graph above, but it can still be modelled using something like the blue line - I hope that sort of makes sense. If not, don’t worry, just take it from me that this is good news.

The Poisson Regression Spatial Interaction Model

So, we can now re-specify Equation 5 as a Poisson Regression model. Instead of our independent variable being lnTij our dependent variable is now the mean of our Poisson distribution λij and the model becomes:

  1. \(\lambda_{ij} = \exp(k + \mu\ln V_i + \alpha\ln W_j - \beta \ln d_{ij})\)

What this model says is \(\lambda_{ij}\) (our independent variable - the estimate of \(T_{ij}\)) is logarithmically linked to (or modelled by) a linear combination of the logged independent variables in the model.

Now we have Equation 6 at our disposal, we can use a Poisson regression model to produce estimates of \(k\), \(\mu\), \(\alpha\) and \(\beta\) - or put another way, we can use the regression model to calibrate our parameters.

So, let’s have a go at doing it!!

It’s very straight forward to run a Poisson regression model in R using the glm (Generalised Linear Models) function. In practical terms, running a GLM model is no different to running a standard regression model using lm. If you want to find out more about glm, try the R help system ?glm or google it to find the function details. If you delve far enough into the depths of what GLM does, you will find that the parameters are calibrated though an ‘iteratively re-weighted least squares’ algorithm. This algorithm does exaxtly the sort of job I described earlier, it fits lots of lines to the data, continually adjusting the parameters and then seeing if it can minimise the error between the observed and expected values useing some goodness-of-fit measure is maximised/minimised.

These sorts of algorithms have been around for years and are very well established so it makes sense to make use of them rather than trying to re-invent the wheel ourselves. So here we go…

  • We are using glm to estimate the values of mu, alpha and beta
# Run the unconstrained model
uncon_sim <- glm(Flow ~ log(vi1_origpop) + log(wj3_destmedinc) + log(dist), family = poisson(link = "log"), na.action = na.exclude, data = mdatasub)

It’s a simple as that - runs in a matter of milliseconds. You should be able to see how the glm R code corresponds to Equation 6.

Total = \(T_{ij}\) = \(\lambda{ij}\)

~ means ‘is modelled by’

log(vi1_origpop) = \(ln{V_i}\)

log(wj2_destsal) = \(ln{W_j}\)

log(dist) = \(lnd_{ij}\)

family = poisson(link = "log") means that we are using a Poisson regression model (the link is always log with a Poisson model) where the left-hand side of the model equation is logarithmically linked to the variables on the right-hand side.

  • Try and do this in Tidymodels

So what comes out of the other end?

Well, we can use the summary() function to have a look at the model parameters:

summary(uncon_sim)
## 
## Call:
## glm(formula = Flow ~ log(vi1_origpop) + log(wj3_destmedinc) + 
##     log(dist), family = poisson(link = "log"), data = mdatasub, 
##     na.action = na.exclude)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -177.78   -54.49   -24.50     9.21   470.11  
## 
## Coefficients:
##                       Estimate Std. Error z value Pr(>|z|)    
## (Intercept)          7.1953790  0.0248852  289.14   <2e-16 ***
## log(vi1_origpop)     0.5903363  0.0009232  639.42   <2e-16 ***
## log(wj3_destmedinc) -0.1671417  0.0033663  -49.65   <2e-16 ***
## log(dist)           -0.8119316  0.0010157 -799.41   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 2750417  on 209  degrees of freedom
## Residual deviance: 1503573  on 206  degrees of freedom
## AIC: 1505580
## 
## Number of Fisher Scoring iterations: 5

We can see from the summary that the Poisson regression has calibrated all 4 parameters for us and these appear under the ‘estimate’ column:

$k$ (intercept) = 7.1953790

\(\mu\) = 0.5903363

\(\alpha\) = -0.1671417

and \(\beta\) = -0.8119316

We can also see from the other outputs that all variables are highly significant (***), with the z-scores revealing that distance has the most influence on the model (as we might have expected from the scatter plots we produced earlier which showed that distance had by far the strongest correlation with migration flows). — ?you take absolute values of z?

These parameters are not too far away from our initial guesses of μμ = 1, αα = 1 and ββ = -2, but how do the estimates compare?

One way to calculate the estimates is to plug all of the parameters back into Equation 6 like this:

mdatasub <- mdatasub %>% 
  mutate(unconstrainedEst2 = round(fitted(uncon_sim), 0)) 

sum(mdatasub$unconstrainedEst2)
## [1] 1313517
# Turn it into a little matrix and have a look at your handy work
mdatasubmat2 <- mdatasub %>% 
  select(Orig_code, Dest_code, unconstrainedEst2) %>% 
  pivot_wider(names_from = Dest_code, values_from = unconstrainedEst2, values_fill = 0) %>%
   relocate("1GSYD", .after = Orig_code) %>% 
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))) ) 

mdatasubmat2

And the $1,000,000 question - has calibrating the parameters improved the model…?

# Evaluate R^2
rsqrd(truth = mdatasub$Flow, estimate = mdatasub$unconstrainedEst2)
## [1] 0.3245418
# Evaluate RMSE
rMSE(truth = mdatasub$Flow, estimate = mdatasub$unconstrainedEst2)
## [1] 10789.17

Yes indeedy do!!

The \(r^2\) has improved from 0.20 to 0.32 and the RMSE has reduced from 25858.11 to 10789.17 so by calibrating our parameters using the Poisson Regression Model, we have markedly improved our model fit.

But we can do even better. We have just been playing with the unconstrained model, by adding constraints into the model we can both improve our fit further AND start to do cool things like esimate transport trip distributions from know information about people leaving an area, or in different contexts estimate the amount of money a shop is going to make from the available money that people in the surrounding area have to spend, or guess the number of migrants travelling between specific countries where we only know how many people in total leave one country and arrive in another.

Section 2 - Constrained Models

If we return to Alan Wilson’s 1971 paper, he introduces a full family of spatial interaction models of which the unconstrained model is just the start. And indeed since then, there have been all number of incremental advances and alternatives (such as Stewart Fotheringham’s Competing Destinations models, Pooler’s production/attraction/cost relaxed models, Stillwell’s origin/destination parameter specific models and mine and Alan’s own multi-level model (to name just a few).

In this section we will explore the rest of Wilson’s family - the Production (origin) Constrained Model; the Attraction (destination) constrained model; and the Doubly Constrained Model.

We will see how we can, again, use a Poisson regression model in R to calibrate these models and how, once calibrated, we can use the models in different contexts, such as Land Use Transportation Interaction (LUTI) modelling, retail modelling and migration modelling.

1. Production and Attraction Constrained Models

Wilson’s real contribution to the field was in noticing that the unconstrained gravity model was sub-optimal as it did not make use of all of the available data in the system we are studying.

If we recall the estimates from our unconstrained model, none of the estimates summed to the observed in and out-flow totals:

Our estimates did sum to the grand total of flows, but this is because we were really fitting a ‘total constrained’ model which used kk - our constant of proportionality - to ensure everything sort of added up (to within 1 commuter).

Where we have a full flow matrix to calibrate parameters, we can incorporate the row (origin) totals, column (destination) totals or both origina and destination totals to constrain our flow estimates to these known values.

As I outline in the accompanying paper, there are various reasons for wanting to do this, for example:

  1. If We are interested in flows of money into businesses or customers into shops, might have information on the amount of disposable income and shopping habits of the people living in different areas from loyalty card data. This is known information about our origins and so we could constrain our spatial interaction model to this known information - we can make the assumption that this level of disposable income remains the same. We can then use other information about the attractiveness of places these people might like to shop in (store size, variety / specialism of goods etc.), to estimate how much money a new store opening in the area might make, or if a new out-of-town shopping centre opens, how much it might affect the business of shops in the town centre. This is what is known in the literature as the ‘retail model’ and is perhaps the most common example of a Production (orign) Constrained Spatial Interaction Model

  2. We might be interested in understanding the impact of a large new employer in an area on the flows of traffic in the vicinity or on the demand for new worker accommodation nearby. A good example of where this might be the case is with large new infrastructure developments like new airports. For example, before the go-ahead for the new third runway at Heathrow was given, one option being considered was a new runway in the Thames Estuary. If a new airport was built here, what would be the potential impact on transport flows in the area and where might workers commute from? This sort of scenario could be tested with an Attraction (destination) Constrained Spatial Interaction Model where the number of new jobs in a destination is known (as well as jobs in the surrounding area) and the model could be used to estimate where the workers will be drawn from (and their likely travel-to-work patterns). This model is exactly the sort of model Land Use Transport Interaction (LUTI) model that was constructed by the Mechanicity Team in CASA - details here if you are interested…

  3. We might be interested in understanding the changing patterns of commuting or migration over time. Data from the Census allows us to know an accurate snap-shot of migrating and commuting patterns every 10 years. In these full data matrices, we know both the numbers of commuters/migrants leaving origins and arriving at destinations as well as the interactions between them. If we constrain our model estimates to this known information at origin and destination, we can examine various things, including:

    1. the ways that the patterns of commuting/migration differ from the model predictions - where we might get more migrant/commuter flows than we would expect

    2. how the model parameters vary over time - for example how does distance / cost of travel affect flows over time? Are people prepared to travel further or less far than before?

2. Production-constrained Model

  1. \(T_{ij} = A_i O_i W_j^\alpha d_{ij}^-\beta\)

where

\(O_{i} = \sum_j T_{ij}\)

and

\(A_i = \frac{1}{\sum_j W_j^\alpha d_{ij}^-\beta}\)

In the production-constrained model, OiOi does not have a parameter as it is a known constraint. AiAi is known as a balancing factor and is a vector of values which relate to each origin, ii, which do the equivalent job to kk in the unconstrained/total constrained model but ensure that flow estimates from each origin sum to the know totals, OiOi rather than just the overall total.

Now at this point, we could calculate all of the OiOis and the AiAis by hand for our sample system and then set about guessing/estimating the parameter values for the rest of the model, but as you might have already suspected from last time, we can use R and glm to make it really easy and do all of that for us - woo hoo!

We set about re-specifying the Production-Constrained model as a Poisson regression model in exactly the same way as we did before. We need to take logs of the right-hand side of equation and assume that these are logarithmially linked to the Poisson distributed mean (λijλij) of the TijTij variable. As such, Equation (1) becomes:

  1. \(\lambda_{ij} = exp(\mu_{i} + \alpha \ln W_j - \beta \ln d_{ij})\)

In Equation (4) μiμi is the equivalent of the vector of balancing factors AiAi, but in regression / log-linear modelling terminology can also be described as either dummy variables or fixed effects. In practical terms, what this means is that in our regression model, μiμi is modelled as a categorical predictor and therefore in the Poisson regression model, we don’t use the numeric values of OiOi, we use a categorical identifier for the origin. In terms of the origin/destination migration matrix shown in Table 3, rather than the flow of 204,828 migrants leaving Sydney (row 1) being used as a predictor, simply the code ‘1GSYD’ is used as a dummy variable.

Before giving it a whirl, it’s important to note in the code example below the use of ‘-1’ after the distance variable (thanks to Hadrien Salat in CASA for bringing this to my attention). The -1 serves the purpose of removing the intercept that by default, GLM will insert into the model. As was mentioned earlier, the vector of origin parameters will replace the intercept in this model. It also serves the purpose

# Run the production constrained SIM (the "-1" indicates no intercept in the regression model)
prodSim <- glm(Flow ~ Orig_code + log(wj3_destmedinc) + log(dist) - 1, na.action = na.exclude, family = poisson(link = "log"), data = mdatasub)

# Summary of model
summary(prodSim)
## 
## Call:
## glm(formula = Flow ~ Orig_code + log(wj3_destmedinc) + log(dist) - 
##     1, family = poisson(link = "log"), data = mdatasub, na.action = na.exclude)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -225.71   -54.10   -15.94    20.45   374.27  
## 
## Coefficients:
##                      Estimate Std. Error z value Pr(>|z|)    
## Orig_code1GSYD      19.541851   0.023767  822.22   <2e-16 ***
## Orig_code1RNSW      19.425497   0.023913  812.35   <2e-16 ***
## Orig_code2GMEL      18.875763   0.023243  812.12   <2e-16 ***
## Orig_code2RVIC      18.335242   0.022996  797.31   <2e-16 ***
## Orig_code3GBRI      19.856564   0.024063  825.20   <2e-16 ***
## Orig_code3RQLD      20.094898   0.024300  826.94   <2e-16 ***
## Orig_code4GADE      18.747938   0.023966  782.28   <2e-16 ***
## Orig_code4RSAU      18.324029   0.024407  750.75   <2e-16 ***
## Orig_code5GPER      20.010551   0.024631  812.43   <2e-16 ***
## Orig_code5RWAU      19.392751   0.024611  787.96   <2e-16 ***
## Orig_code6GHOB      16.802016   0.024282  691.97   <2e-16 ***
## Orig_code6RTAS      17.013981   0.023587  721.33   <2e-16 ***
## Orig_code7GDAR      18.607483   0.025012  743.93   <2e-16 ***
## Orig_code7RNTE      17.798856   0.025704  692.45   <2e-16 ***
## Orig_code8ACTE      17.796693   0.023895  744.79   <2e-16 ***
## log(wj3_destmedinc) -0.272640   0.003383  -80.59   <2e-16 ***
## log(dist)           -1.227679   0.001400 -876.71   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 23087017  on 210  degrees of freedom
## Residual deviance:  1207394  on 193  degrees of freedom
## AIC: 1209427
## 
## Number of Fisher Scoring iterations: 6

So, what do we have?

Well, there are the elements of the model output that should be familiar from the unconstrained model:

the α parameter related to the destination attractiveness: -0.272640

the β distance decay parameter: -1.227679

We can see from the standard outputs from the model that all of the explanatory variables are statistically significant (***) and the z-scores indicate that the destination salary is having the most influence on the model, with distance following closely behind. And then we have a series of parameters which are the vector of μi values associated with our origin constraints.

2.1 Model Estimates

Now at this point you will be wanting to know what affect the constraints have had on the estimates produced by the model, so let’s plug the parameters back into Equation 4 and take a look…

Create some Oi and Dj columns and store the total in and out flow matrix margins in them.

  • \(O_i\) represents the total flows from a given origin

  • \(D_j\) represents the total flows going to a particular destination

# Create some Oi and Dj columns in the dataframe and store row and column totals in them:
mdatasub <- mdatasub %>% 
  group_by(Orig_code) %>% 
  summarise(O_i = sum(Flow)) %>% 
  right_join(mdatasub) 

# Destination flows
mdatasub <-  mdatasub %>% 
  group_by(Dest_code) %>% 
  summarise(D_j = sum(Flow)) %>% 
  right_join(mdatasub) %>% 
  arrange(Orig_code) %>% 
  relocate(D_j, .after = O_i)

pull out the coefficient values for μi and store them back in the dataframe along with Oi and Dj

# Extract coefficients
alpha <- prodSim$coefficients[16]
beta <- prodSim$coefficients[17]

mdatasub <- prodSim %>% 
  pluck("coefficients") %>% 
  tibble(mu_i = .) %>% 
  slice(1:(n() - 2)) %>% 
  bind_cols(distinct(mdatasub, Orig_code)) %>% 
  right_join(mdatasub) %>% 
  relocate(mu_i, .after = D_j)

slice_head(mdatasub)

Now lets’s add the estimated flows by the Production constrained model

mdatasub <- mdatasub %>% 
  mutate(prodsimFitted = round(fitted(prodSim), 0))

2.2 Assessing the model output

So what do the outputs from our Production Constrained Model look like? How has the goodness-of-fit improved and how can we start to use this a bit like a retail model and assess the likely impacts of changing destination attractiveness etc.?

2.2.1 The flow matrix

Now we can create pivot table to turn paired list into matrix (and compute the margins as well)

mdatasubmat3 <- mdatasub %>% 
  select(Orig_code, Dest_code, prodsimFitted) %>% 
  pivot_wider(names_from = Dest_code, values_from = prodsimFitted, values_fill = 0) %>% 
  relocate("1GSYD", .after = Orig_code) %>% 
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))) ) 

mdatasubmat3

And compared with the original observed data?

mdatasubmat

Here it is very easy to see the Origin Constraints working. The sum across all destinations for each origin in the estimated matrix is exactly the same as the same sum across the observed matrix - \(\sum_j T_{ij} = \sum_j \lambda_{ij} = O_i\), but clearly, the same is not true when you sum across all origins for each destination - \(\sum_i T_{ij} \neq \sum_i \lambda_{ij} \neq D_j\)

2.2.2 How do the fits compare with the unconstrained model from last time?

# Evaluate R^2
rsqrd(truth = mdatasub$Flow, estimate = mdatasub$prodsimFitted)
## [1] 0.4345011
# Evaluate RMSE 
rMSE(truth = mdatasub$Flow, estimate = mdatasub$prodsimFitted)
## [1] 9872.693

Clearly by constraining our model estimates to known origin totals, the fit of the model has improved quite considerably - from around 0.32 in the unconstrained model to around 0.43 in this model. The RMSE has also dropped quite noticably.

2.2.3 A ‘what if…’ scenario

Now that we have calibrated our parameters and produced some estimates, we can start to play around with some what-if scenarios.

For example - What if the government invested lots of money in Tasmainia and average weekly salaries increased from 540.45 to 800.50 dollars a week? A far fetched scenario, but one that could make a good experiment.

First create create a new variable with these altered salaries:

mdatasub <- mdatasub %>% 
  mutate(wj3_destmedincScenario = case_when(
    wj3_destmedinc == 540.45 ~ 800.50,
    TRUE ~ wj3_destmedinc)
  )

Now let’s plug these new values into the model and see how this changes the flows in the system… \(\lambda_{ij} = exp(\mu_{i} + \alpha \ln W_j - \beta \ln d_{ij})\)

mdatasub <- mdatasub %>% 
  mutate(prodsimest2 = exp(mu_i + (alpha*log(wj3_destmedincScenario) + (beta*log(dist)))) %>% round(., 0))

#now we can create pivot table to turn paired list into matrix (and compute the margins as well)
mdatasubmat4 <- mdatasub %>% 
  select(Orig_code, Dest_code, prodsimest2) %>% 
  pivot_wider(names_from = Dest_code, values_from = prodsimest2, values_fill = 0) %>% 
  relocate("1GSYD", .after = Orig_code) %>% 
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))) )

mdatasubmat4

You will notice that by increasing the average salary in the rest of Tazmania, we’ve reduced the flows into this area (yes, I know, counterintuitively, but this is just an example), but have not reduced the flows into other zones - the original constraints are still working on the other zones. One way to get around this, now that we have calibrated our parameters, is to return to the multiplicative model in Equation 1 and run this model after calculating our own \(A_i\) balancing factors.

\(T_{ij} = A_i O_i W_j^\alpha d_{ij}^-\beta\)

# Calculate new wj^alpha and dij=j^beta
wj2_alpha <- mdatasub$wj3_destmedinc^alpha
dist_beta <- mdatasub$dist^beta

# Calculate the first stage  of the Ai values
mdatasub <- mdatasub %>% 
  mutate(Ai1 = wj2_alpha*dist_beta) %>% 
  group_by(Orig_code) %>% 
  # Find constraint of a given location
  summarize(A_i = sum(Ai1)) %>% 
  mutate(A_i = 1/A_i) %>%
  right_join(mdatasub)

So that is it for calculating your \(A_i\) values. Now that you have these, it’s very simple to plug everything back into Equation 1 and generate some estimates.

# To check everything works, recreate the original estimates
mdatasub <- mdatasub %>% 
  mutate(prodsimest3 = round(A_i*O_i*wj3_destmedinc^alpha*dist^beta, 0))

You should see that your new estimates are exactly the same as your first estimates. If they are not then something has gone wrong. Now we have this though, we can keep messing around with some new estimates and keep the constraints.

Remember, though, that you will need to recalculate \(A_i\) each time you want to create a new set of estimates. Let’s try with our new values for the destination salary in the rest of Tazmania:

wj3_alpha <- mdatasub$wj3_destmedincScenario^alpha
dist_beta <- mdatasub$dist^beta

# Calculate first stages of Ai values
mdatasub <- mdatasub %>% 
  mutate(Ai1 = wj3_alpha*dist_beta) %>% 
  group_by(Orig_code) %>% 
  # Find constraint of a given location
  summarize(A_i = sum(Ai1)) %>% 
  mutate(A_i = 1/A_i) %>%
  right_join(mdatasub %>% select(!starts_with("A")))

Now we have some new \(A_i\)s, let’s generate some new scenario flow estimates.

mdatasubmat5 <- mdatasub %>% 
  mutate(prodsimest4_scenario = round(A_i*O_i*wj3_alpha*dist_beta)) %>%
  select(Orig_code, Dest_code, prodsimest4_scenario) %>% 
  pivot_wider(names_from = "Dest_code", values_from = "prodsimest4_scenario", values_fill = 0) %>% 
  relocate("1GSYD", .after = Orig_code) %>% 
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))))

There are a number of things to note here. Firstly, flows into Tazmania have reduced, while flows into other regions have increased. Secondly, yes, I know this was a bad example, but try with some of the other variables for yourself.

Thirdly, Our origin constraints are now holding again.

3. Attraction-Constrained Model

The attraction constrained Model is virtually the same as the Production constrained model:

  1. \(T_{ij} = D_j B_j V_i^\mu d_{ij}^-\beta\)

where

  1. \(D_{j} = \sum_i T_{ij}\)

and

  1. \(B_j = \frac{1}{\sum_i V_i^\mu d_{ij}^-\beta}\)

I won’t dwell on the attraction constrained model, except to say that it can be run in R as you would expect:

  1. \(\lambda_{ij} = exp(\mu \ln V_i + \alpha_{i} - \beta \ln d_{ij})\)

or in R:

attrSim <- glm(Flow ~ Dest_code + log(vi1_origpop) + log(dist) - 1, family = poisson(link = "log"), data = mdatasub, na.action = na.exclude)

summary(attrSim)
## 
## Call:
## glm(formula = Flow ~ Dest_code + log(vi1_origpop) + log(dist) - 
##     1, family = poisson(link = "log"), data = mdatasub, na.action = na.exclude)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -138.69   -33.38   -10.47    11.72   293.39  
## 
## Coefficients:
##                    Estimate Std. Error z value Pr(>|z|)    
## Dest_code1GSYD    8.8262922  0.0176638   499.7   <2e-16 ***
## Dest_code1RNSW    9.1809447  0.0178316   514.9   <2e-16 ***
## Dest_code2GMEL    8.6716196  0.0170155   509.6   <2e-16 ***
## Dest_code2RVIC    8.0861927  0.0173840   465.1   <2e-16 ***
## Dest_code3GBRI    9.5462594  0.0183631   519.9   <2e-16 ***
## Dest_code3RQLD   10.1295722  0.0184672   548.5   <2e-16 ***
## Dest_code4GADE    8.3051406  0.0184018   451.3   <2e-16 ***
## Dest_code4RSAU    8.1438651  0.0188772   431.4   <2e-16 ***
## Dest_code5GPER    9.9664486  0.0190008   524.5   <2e-16 ***
## Dest_code5RWAU    9.3061908  0.0190006   489.8   <2e-16 ***
## Dest_code6GHOB    6.9737562  0.0186288   374.4   <2e-16 ***
## Dest_code6RTAS    7.1546249  0.0183673   389.5   <2e-16 ***
## Dest_code7GDAR    8.3972440  0.0199735   420.4   <2e-16 ***
## Dest_code7RNTE    7.4521232  0.0206128   361.5   <2e-16 ***
## Dest_code8ACTE    7.3585270  0.0181823   404.7   <2e-16 ***
## log(vi1_origpop)  0.5828662  0.0009556   610.0   <2e-16 ***
## log(dist)        -1.1820013  0.0015267  -774.2   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 23087017  on 210  degrees of freedom
## Residual deviance:   665984  on 193  degrees of freedom
## AIC: 668017
## 
## Number of Fisher Scoring iterations: 5

we can examine how the constraints hold for destinations this time:

# First round of the estimates
mdatasub %>% 
  mutate(attrsimFitted = round(fitted(attrSim))) -> mdatasub

# Pivot the table to turn paired list into matrix (and compute the margins as well)
mdatasubmat6 <- mdatasub %>% 
  select(Orig_code, Dest_code, attrsimFitted) %>% 
  pivot_wider(names_from = "Dest_code", values_from = "attrsimFitted", values_fill = 0) %>% 
  relocate("1GSYD", .after = Orig_code) %>% 
  rowwise(Orig_code) %>% 
  mutate(total_flow_frm_area = sum(c_across(where(is.numeric))))

mdatasubmat6

compared to …

mdatasubmat

and we can test the goodess of fit in exactly the same way as before:

# Evaluate R^2
rsqrd(truth = mdatasub$Flow, estimate = mdatasub$attrsimFitted)
## [1] 0.6550357
# Evaluate RMSE 
rMSE(truth = mdatasub$Flow, estimate = mdatasub$attrsimFitted)
## [1] 7714.627

OK, that’s where I’ll leave singly constrained models for now. There are, of course, plenty of things you could try out. For example:

  • You could try mapping the coefficients or the residual values from the model to see if there is any patterning in either the over or under prediction of flows.

  • You could try running your own version of a LUTI model by first calibrating the model parameters and plugging these into a multiplicative version of the model, adjusting the destination constraints to see which origins are likely to generate more trips.

4. Doubly Constrained Model

Now, the model in the family you have all been waiting for - the big boss, the daddy, the doubly constrained model!

Let’s begin with the formula:

  1. \(T_{ij} = A_i O_i B_j D_j d_{ij}^-\beta\)

where

  1. \(O_{i} = \sum_j T_{ij}\)

  2. \(D_{j} = \sum_i T_{ij}\)

and

  1. \(A_i = \frac{1}{\sum_j B_j D_j d_{ij}^-\beta}\)

  2. \(B_j = \frac{1}{\sum_i A_i O_i d_{ij}^-\beta}\)

Now, the astute will have noticed that the calculation of Ai relies on knowing Bj and the calculation of Bj relies on knowing Ai. A conundrum!! If I don’t know Ai how can I calcuate Bj and then in turn Ai and then Bj ad infinitum???!!

Well, I wrestled with that for a while until I came across this paper by Martyn Senior where he sketches out a very useful algorithm for iteratively arriving at values for Ai and Bj by setting each to equal 1 initially and then continuing to calculate each in turn until the difference between each value is small enough not to matter.

We will return to this later, but for now, we will once again use the awesome power of R to deal with all of this difficulty for us!

We can run the doubly constrained model in exactly the same way as we ran the singly constrained models:

  1. \(\lambda_{ij} = exp(\mu_i + \alpha_{i} - \beta \ln d_{ij})\)

The code below has changed a litte from the singly constrained models I have removed the ‘-1’ which means that an intercept will appear in the model again. This is not because I want an intercept as it makes the origin and destination coefficients harder to interpret - reference categories zones will appear and the coefficients will need to be compared with the intercept - rather the ‘-1’ cheat for removing the intercept only works with one factor level - here we have two (origins and destinations). For full details and an explanation for alternative ways for dealing with this, please visit here - https://stats.stackexchange.com/questions/215779/removing-intercept-from-glm-for-multiple-factorial-predictors-only-works-for-fir - for ease, here we will just continue with the intercept.

The reference level means that the origin and destination coefficients need to be interpreted in relation to a reference category. In this example, the first zone in the system is used (Sydney), with the direction and size of the coefficients referring to whether another origin or destination zone has a greater or lesser positive or negative effect on migration flows in the system when compared to Sydney. The estimates produced by the Doubly Constrained Model are the most accurate in the Wilson family of models (in this example, an R2 value of 0.87). However, there is a loss of flexibility when compared to the singly constrained models, as only alternatives to origin/destination interaction explanatory variables such as historic flows or something other than distance can be experimented with. The double constraints mean that origin- and destination-specific explanatory variables cannot be used.

# Run a doubly constrained model
doubSim <- glm(Flow ~ Orig_code + Dest_code + log(dist), family = poisson(link = log), na.action = na.exclude, data = mdatasub)

# Summary of model
summary(doubSim)
## 
## Call:
## glm(formula = Flow ~ Orig_code + Dest_code + log(dist), family = poisson(link = log), 
##     data = mdatasub, na.action = na.exclude)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -93.018  -26.703    0.021   19.046  184.179  
## 
## Coefficients:
##                 Estimate Std. Error  z value Pr(>|z|)    
## (Intercept)    20.208178   0.011308 1786.999   <2e-16 ***
## Orig_code1RNSW -0.122417   0.003463  -35.353   <2e-16 ***
## Orig_code2GMEL -0.455872   0.003741 -121.852   <2e-16 ***
## Orig_code2RVIC -1.434386   0.004511 -317.969   <2e-16 ***
## Orig_code3GBRI  0.241303   0.003597   67.091   <2e-16 ***
## Orig_code3RQLD  0.772753   0.003599  214.700   <2e-16 ***
## Orig_code4GADE -0.674261   0.004527 -148.936   <2e-16 ***
## Orig_code4RSAU -1.248974   0.005889 -212.091   <2e-16 ***
## Orig_code5GPER  0.742687   0.004668  159.118   <2e-16 ***
## Orig_code5RWAU -0.317806   0.005131  -61.943   <2e-16 ***
## Orig_code6GHOB -2.270736   0.008576 -264.767   <2e-16 ***
## Orig_code6RTAS -1.988784   0.007477 -265.981   <2e-16 ***
## Orig_code7GDAR -0.797620   0.007089 -112.513   <2e-16 ***
## Orig_code7RNTE -1.893522   0.008806 -215.022   <2e-16 ***
## Orig_code8ACTE -1.921309   0.005511 -348.631   <2e-16 ***
## Dest_code1RNSW  0.389478   0.003899   99.894   <2e-16 ***
## Dest_code2GMEL -0.007616   0.004244   -1.794   0.0727 .  
## Dest_code2RVIC -0.781258   0.004654 -167.854   <2e-16 ***
## Dest_code3GBRI  0.795909   0.004037  197.178   <2e-16 ***
## Dest_code3RQLD  1.516186   0.003918  386.955   <2e-16 ***
## Dest_code4GADE -0.331189   0.005232  -63.304   <2e-16 ***
## Dest_code4RSAU -0.627202   0.006032 -103.980   <2e-16 ***
## Dest_code5GPER  1.390114   0.005022  276.811   <2e-16 ***
## Dest_code5RWAU  0.367314   0.005362   68.509   <2e-16 ***
## Dest_code6GHOB -1.685934   0.008478 -198.859   <2e-16 ***
## Dest_code6RTAS -1.454819   0.007612 -191.112   <2e-16 ***
## Dest_code7GDAR -0.308516   0.007716  -39.986   <2e-16 ***
## Dest_code7RNTE -1.462020   0.009743 -150.060   <2e-16 ***
## Dest_code8ACTE -1.506283   0.005709 -263.866   <2e-16 ***
## log(dist)      -1.589102   0.001685 -942.842   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 2750417  on 209  degrees of freedom
## Residual deviance:  335759  on 180  degrees of freedom
## AIC: 337818
## 
## Number of Fisher Scoring iterations: 6

And the various flows and goodness-of-fit statistics?

mdatasub <- mdatasub %>% 
  mutate(doubsimFitted = round(fitted(doubSim)))

#now we can create pivot table to turn paired list into matrix (and compute the margins as well)
mdatasubmat7 <- mdatasub %>% 
  select(Orig_code, Dest_code, doubsimFitted) %>% 
  pivot_wider(names_from = Dest_code, values_from = doubsimFitted, values_fill = 0) %>% 
  relocate("1GSYD", .after = "Orig_code") %>% 
  rowwise(Orig_code) %>% 
  mutate(Flow_from_zone = sum(c_across(where(is.numeric))))

mdatasubmat7

compared to ..

mdatasubmat

and we can test the goodness-of-fit in exactly the same way as before:

# Evaluate R^2
rsqrd(truth = mdatasub$Flow, estimate = mdatasub$doubsimFitted)
## [1] 0.8662571
# Evaluate RMSE 
rMSE(truth = mdatasub$Flow, estimate = mdatasub$doubsimFitted)
## [1] 4877.799

So the goodness of fit has shot up and we can clearly see the origin and destination constraints working, and for most sets of flows, the model is now producing some good estimates. However, there are still some errors in the flows.

Is there anything more we can do? Yes, of course there is.

4.1 Tweaking our Models

4.1.1 Distance Decay

Now, all of the way through these practicals, we have assumed that the distance decay parameter follows a negative power law. Well, it doesn’t need to.

In Wilson’s original paper, he generalised the distance decay parameter to:

\(f(d_{ij})\)

Where $f$ represents some function of distance describing the rate at which the flow interactions change as distance increases. Lots of people have written about this, including Tayor (1971) and more recently Robin Lovelace in a transport context, here.

For the inverse power law that we have been using is one possible function of distance, the other common one that is used is the negative exponential function:

\(exp(-\beta d_{ij})\)

We can get a feel for how different distance decay parameters work by plotting some sample data (try different parameters):

xdist <-  seq(1, 20)
invpower2 <-  xdist^-2
negexp0.3 <- exp(-0.3*xdist) 

df <- tibble(xdist, invpower2, negexp0.3) %>% 
  pivot_longer(!xdist, names_to = "decay", values_to = "values")

df %>% 
  ggplot(mapping = aes(x = xdist, y = values, color = decay)) +
  geom_line(size = 1)

In this particular example with these parameters and 𝛽𝛽 values, the inverse power function has a far more rapid distance decay effect than the negative exponential function. In real life, what this means is that if the observed interactions drop off very rapidly with distance, then they might be more likely to follow an inverse power law. This might be the case when looking at trips to the local convenience store by walking, for example. On the other hand, if the effect of distance is less severe – for example, migration across the country for a new job – then the negative exponential function with a small value of \(𝛽\) function might be more appropriate. There is no hard and fast rule as to which function to pick. It will just come down to which fits the data better.

As Tayor Oshan points out in his excellent Primer what this means in our Poisson regression model is that we simply substitute − \(βln⁡dij\) for$ −βdi$ in our model:

# Run a doubly constrained SIM
doubSim1 <- glm(Flow ~ Orig_code + Dest_code + dist, family = poisson(link = "log"), na.action = na.exclude, data = mdatasub)

summary(doubSim1)
## 
## Call:
## glm(formula = Flow ~ Orig_code + Dest_code + dist, family = poisson(link = "log"), 
##     data = mdatasub, na.action = na.exclude)
## 
## Deviance Residuals: 
##      Min        1Q    Median        3Q       Max  
## -127.953   -31.964    -4.223    22.000   224.899  
## 
## Coefficients:
##                  Estimate Std. Error  z value Pr(>|z|)    
## (Intercept)     1.103e+01  4.029e-03 2737.291   <2e-16 ***
## Orig_code1RNSW -2.329e-01  3.365e-03  -69.212   <2e-16 ***
## Orig_code2GMEL -1.690e-01  3.437e-03  -49.177   <2e-16 ***
## Orig_code2RVIC -8.485e-01  4.000e-03 -212.105   <2e-16 ***
## Orig_code3GBRI  3.146e-02  3.528e-03    8.916   <2e-16 ***
## Orig_code3RQLD  5.454e-01  3.553e-03  153.532   <2e-16 ***
## Orig_code4GADE -9.537e-01  4.453e-03 -214.143   <2e-16 ***
## Orig_code4RSAU -1.525e+00  5.851e-03 -260.707   <2e-16 ***
## Orig_code5GPER  1.018e+00  5.249e-03  193.958   <2e-16 ***
## Orig_code5RWAU -7.128e-01  5.632e-03 -126.548   <2e-16 ***
## Orig_code6GHOB -2.065e+00  8.093e-03 -255.208   <2e-16 ***
## Orig_code6RTAS -1.876e+00  7.043e-03 -266.303   <2e-16 ***
## Orig_code7GDAR -6.602e-01  7.197e-03  -91.730   <2e-16 ***
## Orig_code7RNTE -2.069e+00  8.824e-03 -234.480   <2e-16 ***
## Orig_code8ACTE -1.771e+00  5.423e-03 -326.524   <2e-16 ***
## Dest_code1RNSW  3.090e-01  3.793e-03   81.473   <2e-16 ***
## Dest_code2GMEL  2.154e-01  4.000e-03   53.842   <2e-16 ***
## Dest_code2RVIC -2.193e-01  4.196e-03  -52.272   <2e-16 ***
## Dest_code3GBRI  5.674e-01  3.962e-03  143.209   <2e-16 ***
## Dest_code3RQLD  1.270e+00  3.853e-03  329.606   <2e-16 ***
## Dest_code4GADE -6.360e-01  5.150e-03 -123.501   <2e-16 ***
## Dest_code4RSAU -9.072e-01  5.982e-03 -151.663   <2e-16 ***
## Dest_code5GPER  1.692e+00  5.567e-03  304.000   <2e-16 ***
## Dest_code5RWAU  9.058e-03  5.795e-03    1.563    0.118    
## Dest_code6GHOB -1.543e+00  8.007e-03 -192.767   <2e-16 ***
## Dest_code6RTAS -1.408e+00  7.211e-03 -195.206   <2e-16 ***
## Dest_code7GDAR -1.465e-01  7.831e-03  -18.711   <2e-16 ***
## Dest_code7RNTE -1.631e+00  9.753e-03 -167.270   <2e-16 ***
## Dest_code8ACTE -1.281e+00  5.597e-03 -228.950   <2e-16 ***
## dist           -1.392e-03  1.858e-06 -749.415   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 2750417  on 209  degrees of freedom
## Residual deviance:  521907  on 180  degrees of freedom
## AIC: 523966
## 
## Number of Fisher Scoring iterations: 6
mdatasub <- mdatasub %>% 
  mutate(doubsimFitted1 = round(fitted(doubSim1)))

# Evaluate R^2
rsqrd(truth = mdatasub$Flow, estimate = mdatasub$doubsimFitted1)
## [1] 0.7591672
# Evaluate RMSE 
rMSE(truth = mdatasub$Flow, estimate = mdatasub$doubsimFitted1)
## [1] 6577.102

So, it would appear that in this case using a negative exponential function in our model results in a worse outcome than the initial inverse power law - this may not always be the case, so it is worth experimenting.

4.1.2 Distance Decay

Yes, the nice thing about doing all of this in a regression modelling framework is we can just keep adding predictor variables into the mix and seeing whether they have an effect.

You can’t add origin or destination specific predictors into a doubly constrained model like this, however, switching back to the singly constrained models, as many different origin or destination predictor variables can be added as seems reasonable (subject to the usual restrictions on high correlation)

In addition to variables relating to median income, there are variables on unemployment rate and the percentage of households living in rented accommodation. Experiment with these variables for origins and destinations to see whether the singly constrained models can be improved in any way.

kitchensinkSIM <- glm(Flow ~ Dest_code + vi1_origpop + vi2_origunemp + vi3_origmedinc + vi4_origpctrent -1, na.action = na.exclude, family = poisson(link = "log"), data = mdatasub)
#let's have a look at it's summary...
summary(kitchensinkSIM)
## 
## Call:
## glm(formula = Flow ~ Dest_code + vi1_origpop + vi2_origunemp + 
##     vi3_origmedinc + vi4_origpctrent - 1, family = poisson(link = "log"), 
##     data = mdatasub, na.action = na.exclude)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -157.58   -50.78   -25.36    -1.61   376.17  
## 
## Coefficients:
##                   Estimate Std. Error z value Pr(>|z|)    
## Dest_code1GSYD   8.508e+00  9.273e-03  917.53   <2e-16 ***
## Dest_code1RNSW   8.851e+00  9.302e-03  951.47   <2e-16 ***
## Dest_code2GMEL   8.661e+00  9.576e-03  904.37   <2e-16 ***
## Dest_code2RVIC   8.315e+00  9.551e-03  870.53   <2e-16 ***
## Dest_code3GBRI   8.651e+00  9.294e-03  930.72   <2e-16 ***
## Dest_code3RQLD   8.931e+00  9.276e-03  962.84   <2e-16 ***
## Dest_code4GADE   7.603e+00  9.972e-03  762.41   <2e-16 ***
## Dest_code4RSAU   7.185e+00  1.040e-02  691.08   <2e-16 ***
## Dest_code5GPER   8.116e+00  9.629e-03  842.84   <2e-16 ***
## Dest_code5RWAU   7.706e+00  9.868e-03  780.83   <2e-16 ***
## Dest_code6GHOB   6.444e+00  1.169e-02  551.42   <2e-16 ***
## Dest_code6RTAS   6.698e+00  1.112e-02  602.39   <2e-16 ***
## Dest_code7GDAR   6.593e+00  1.133e-02  581.73   <2e-16 ***
## Dest_code7RNTE   6.010e+00  1.280e-02  469.58   <2e-16 ***
## Dest_code8ACTE   7.340e+00  1.022e-02  718.15   <2e-16 ***
## vi1_origpop      4.240e-07  5.802e-10  730.74   <2e-16 ***
## vi2_origunemp    7.089e-02  1.375e-03   51.55   <2e-16 ***
## vi3_origmedinc   2.932e-04  7.138e-06   41.07   <2e-16 ***
## vi4_origpctrent -2.239e-02  2.265e-04  -98.84   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 23087017  on 210  degrees of freedom
## Residual deviance:  1441432  on 191  degrees of freedom
## AIC: 1443469
## 
## Number of Fisher Scoring iterations: 6
# First round of the estimates
mdatasub %>% 
  mutate(attrsimFitted2 = round(fitted(kitchensinkSIM))) -> mdatasub

and we can test the goodess of fit in exactly the same way as before:

# Evaluate R^2
rsqrd(truth = mdatasub$Flow, estimate = mdatasub$attrsimFitted2)
## [1] 0.3126191
# Evaluate RMSE 
rMSE(truth = mdatasub$Flow, estimate = mdatasub$attrsimFitted2)
## [1] 10883.35

5. Conclusions, further notes and ideas for additional activities

Hopefully you have now seen how it is extremely straight-forward to run and calibrate Wilson’s full family of Spatial Interaction Models in R using GLM and Poisson Regression.

5.1 Some Further Notes

Now might be the time to mention that despite everything I’ve shown you, there has been some discussion in the literature as to whether the Poisson Model is actually a misspecification, especially for modelling migration flows. If you have the stomach for it, this paper by Congdon goes into a lot of detail.

The issue is a thing called ‘overdispersion’ which, translated, essentially relates to the model not being able to capture all of the things that could be explaining the flows in the independent variables that are supplied to the model. The details are tedious and only really intelligible to those with a statistics background. If you want a starter, try here, but in practical terms, we can get around this problem by fitting a very similar sort of regression model called the negative binomial regression model.

If you wish, you can read up and experiment with this model - you can fit it in exactly the same way as the glm model but using a function called glm.nb which is part of the mass package. The negative binomial model has an extra parameter in the model for overdispersion. You you do try this, you will almost certainly discover that your results barely change - but hell, you might keep a pedantic reviewer at bay if you submit this to a journal (not that I’m speaking from experience or anything).

And some more comments

Another thing to note is that the example we used here had quite neat data. You will almost certainly run into problems if you have sparse data or predictors with 0s in them. If this happens, then you might need to either drop some rows in your data (if populated with 0s) or substitute 0s for very small numbers, much less than 1, but greater than 0 (this is because you can’t take the log of 0)

And another thing to note is that the models in this Australian example assumed that the flow data and predictors were all in and around the same order or magnitude. This is not necessarily the case, particularly with a 5 year migration transition and some large metropolitan areas. Where data that (such as population masses at origins and destinations) that are an order of magnitude different (i.e. populations about ten times larger in different locations) then the model estimates might be biased. Fortunately, there are packages available to help us with these problems as well. The robustbase package features a function called glmrob which will deal with this issues (again, your results probably won’t change much, but worth knowing).

Further Reading https://www.researchgate.net/publication/255576515_DEVELOPING_THE_SINGLY_CONSTRAINED_GRAVITY_MODEL_FOR_APPLICATION_IN_DEVELOPING_COUNTRIES

  • Abel G J (2010) Estimation of international migration flow tables in Europe: international migration flow tables. Journal of the Royal Statistical Society. Series A, Statistics in Society 173(4): 797–825.

  • Congdon P (1993) Approaches to modelling overdispersion in the analysis of migration. Environment and Planning A: Economy and Space 25(1): 1481–1510.

  • Congdon P (1988) Modelling migration flows between areas: an analysis for London using the census and OPCS Longitudinal Study. Regional Studies 23(2): 87–103.

  • Crymble A, Dennett A and Hitchcock T (2017) Modelling regional imbalances in English plebeian migration to late eighteenth-century London. The Economic History Review 71(3): 747-771.

  • Dennett A and Wilson A (2013) A multi-level spatial interaction modelling framework for estimating inter-regional migration in Europe. Environment and Planning A: Economy and Space 45(6): 1491–1507.

  • Erhardt G D and Dennett A (2017) Understanding the role and relevance of the census in a changing transportation data landscape. Paper presented at the Transportation Research Board Conference on Applying Census Data for Transportation, Kansas City, Missouri.

  • Flowerdew R (2010) Modelling migration with Poisson regression. In: Stillwell J, Duke-Williams O and Dennett A (eds) Technologies for Migration and Commuting Analysis: Spatial Interaction Data Applications. Hershey PA: IGI Global.

  • Flowerdew R (1982) Fitting the lognormal gravity model to heteroscedastic data. Geographical Analysis 14(3): 263–267.

  • Flowerdew R and Aitkin M (1982) A method of fitting the gravity model based on the Poisson distribution. Journal of Regional Science 22(2): 191–202.

  • Fotheringham A S (1983) A new set of spatial-interaction models: the theory of competing destinations.

  • Environment and Planning A: Economy and Space 15(1): 15–36. Fotheringham A S, Nakaya T, Yano K, Openshaw S and Ishikawa Y (2001) Hierarchical destination choice and spatial interaction modelling: a simulation experiment. Environment and Planning A: Economy and Space 33(5): 901–920.

  • Kim K and Cohen J E (2010) Determinants of international migration flows to and from industrialized countries: a panel data approach beyond gravity. International Migration Review 44(4): 899–932.

  • Lee E S (1966) A theory of migration. Demography 3(1): 47–57.

  • Lomax N and Norman P (2016) Estimating population attribute values in a table: “get me started in” iterative proportional fitting. The Professional Geographer 68(3): 451–461.

  • Lovelace R (2015) Estimating distance decay for the national propensity to cycle tool. https://www.slideshare.net/ITSLeeds/estimating-distance-decay-for-the-national-propensityto-cycle-tool.

  • Oshan T M (2016) A primer for working with the Spatial Interaction modeling (SpInt) module in the python spatial analysis library (PySAL). REGION 3: R11–R23.

  • Pooler J (1994) An extended family of spatial interaction models. Progress in Human Geography 18(1): 17–39.

  • Pooler J (1987) Modeling interprovincial migration using entropy-maximizing methods. The Canadian Geographer 31(1): 57–64.

  • Raymer J (2007) The estimation of international migration flows: a general technique focused on the origin–destination association structure. Environment and Planning A: Economy and Space 39(4): 985–995.

  • Raymer J and Abel G (2008) Methods to improve estimates of migration flows – the MIMOSA model for estimating international migration flows in the European Union. UNECE/Eurostat work session on migration statistics, Working Paper 8, Geneva.

  • Raymer J, Abel G and Smith P W (2007) Combining census and registration data to estimate detailed elderly migration flows in England and Wales. Journal of the Royal Statistical Society. Series A, Statistics in Society 170(4): 891–908.

  • Raymer J and Giulietti C (2010) Analysing structures of interregional migration in England. In: Stillwell J, Duke-Williams O and Dennett A (eds) Technologies for Migration and Commuting Analysis: Spatial Interaction Data Applications. Hershey PA: IGI Global.

  • Rees P (1977) The measurement of migration from census data and other sources. Environment and Planning A: Economy and Space 9(3): 257–280.

  • Rogers A and Raymer J (1998) The spatial focus of US interstate migration flows. Population, Space and Place 4(1): 63–80.

  • Senior M L (1979) From gravity modelling to entropy maximizing: a pedagogic guide. Progress in Human Geography 3(2): 175–210.

  • Shen J (2017) Modelling interregional migration in China in 2005–2010: the roles of regional attributes and spatial interaction effects in modelling error. Population, Space and Place 23(3): e2014.

  • Shen J (2015) Explaining interregional migration changes in China, 1985–2000, using a decomposition approach. Regional Studies 49(7): 1176–1192.

  • Stillwell J (1978) Interzonal migration: some historical tests of spatial-interaction models.

  • Environment and Planning A: Economy and Space 10(1): 1187–1200.

  • Taylor P J (1983) Distance decay in spatial interactions. Norwich: Geo Books.

  • Willekens F (1999) Modeling approaches to the indirect estimation of migration flows: from entropy to EM. Mathematical Population Studies 7(3): 239–278.

  • Wilson A (1971) A family of spatial interaction models, and associated developments. Environment and Planning A: Economy and Space 3(1): 1–32.

  • Zipf G K (1946) The P1 P2 / D hypothesis: on the intercity movement of persons. American Sociological Review 11(6): 677–686

LS0tDQp0aXRsZTogJ0ludGVybnNoaXAgU0lNIFByZXA6IE1vZGVsbGluZyBQb3B1bGF0aW9uIEZsb3dzIFVzaW5nIFNwYXRpYWwgSW50ZXJhY3Rpb24gTW9kZWxzJw0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNzczogc3R5bGVfNy5jc3MNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogZmxhdGx5DQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KDQpgYGANCg0KYGBge3J9DQpzdXBwcmVzc1dhcm5pbmdzKGlmKCFyZXF1aXJlKCJwYWNtYW4iKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikpDQoNCnBhY21hbjo6cF9sb2FkKCd0aWR5dmVyc2UnLCAnc2YnLCAndG1hcCcsICdnZW9qc29uaW8nLCAnc3AnKQ0KYGBgDQoNCkJhc2VkIG9uIEFkYW0gRGVubmV0J3MgW0F1c3RyYWxpYW4gUG9wdWxhdGlvbiBTdHVkaWVzXShodHRwczovL2RpZ2l0aXNlZC1jb2xsZWN0aW9ucy51bmltZWxiLmVkdS5hdS9iaXRzdHJlYW0vaGFuZGxlLzExMzQzLzIzMzU2NC9Nb2RlbGxpbmclMjBwb3B1bGF0aW9uJTIwZmxvd3MlMjB1c2luZyUyMHNwYXRpYWwlMjBpbnRlcmFjdGlvbiUyMG1vZGVscy5wZGY/c2VxdWVuY2U9MSZpc0FsbG93ZWQ9eSkNCg0KTW9kZXJuaXplZCB2ZXJzaW9uIG9mIEFkYW0ncyBhY2NvbXBhbnlpbmcgOiA8aHR0cHM6Ly9ycHVicy5jb20vYWRhbV9kZW5uZXR0LzM3Njg3Nz4NCg0KIyMgU2V0dGluZyB1cCBzb21lIHNwYXRpYWwgZGF0YQ0KDQpBcyB0aGUgbmFtZSBzdWdnZXN0cywgdG8gcnVuIGEgc3BhdGlhbCBpbnRlcmFjdGlvbiBtb2RlbCwgeW91IGFyZSBnb2luZyB0byBuZWVkIHNvbWUgc3BhdGlhbCBkYXRhIGFuZCBzb21lIGRhdGEgb24gaW50ZXJhY3Rpb25zIChmbG93cykuIExldCdzIHN0YXJ0IHdpdGggc29tZSBzcGF0aWFsIGRhdGE6DQoNCi0gICBFUFNHOjQyODMgLSBBdXN0cmFsaWEgY29vcmRpbmF0ZXMgaW4gZGVncmVlcw0KDQpgYGB7cn0NCmxpYnJhcnkoc2YpDQojIEkvTyBmb3IgR2VvSlNPTg0KbGlicmFyeShnZW9qc29uaW8pDQoNCiMgUmVhZCBnZW9qc29uIGZpbGUgYXMgc3BhdGlhbCBvYmplY3QNCmF1cyA8LSBnZW9qc29uX3JlYWQoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvMGZnODBuemN4Y3N5YmlpL0dDQ1NBXzIwMTZfQVVTVF9OZXcuZ2VvanNvbj9yYXc9MSIsIHdoYXQgPSAic3AiKQ0KDQoNCg0KDQojIFJlYWQgZGF0YSBhcyBzaW1wbGUgZmVhdHVyZXMgb2JqZWN0IGFuZCBzZXQgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtDQphdXNfc2YgPC0gYXVzICU+JSANCiAgc3RfYXNfc2YoKSAlPiUgDQogIHN0X3NldF9jcnMoNDI4MykNCg0KYXVzX3NmICU+JSANCiAgaGVhZCgpDQpgYGANCg0KLSAgIHNmOjphcygpIC0gTWV0aG9kcyB0byBjb2VyY2Ugc2YgdG8gc3AgLS1cPlNwYXRpYWxcKiBhbmQgU3BhdGlhbFwqRGF0YUZyYW1lIG9iamVjdHMNCg0KYGBge3J9DQojIE5vdyB5b3UgbWF5IGhhdmUgbm90aWNlZCB0aGF0IHRoZSBjb2RlIG9yZGVyIGlzIGEgYml0IHdlaXJkLCBzbyBsZXQncyBmaXggdGhhdCBhbmQgcmVvcmRlcg0KYXVzX3NmMSA8LSBhdXNfc2YgJT4lIA0KICBhcnJhbmdlKEdDQ1NBX0NPREUpDQoNCiMgTm93IGxldCdzIGNyZWF0ZSBhbiAnc3AnIG9iamVjdCBmcm9tIG91ciBuZXcgb3JkZXJlZCBTRiBvYmplY3QNCmF1cyA8LSBhc19TcGF0aWFsKGF1c19zZjEpDQpgYGANCg0KQ2hlY2sgeW91ciBib3VuZGFyaWVzIGhhdmUgZG93bmxvYWRlZCBvaw0KDQotICAgc2Y6OnN0X21ha2VfdmFsaWQ6IG1ha2VzIGFuIGludmFsaWQgZ2VvbWV0cnkgdmFsaWQgLS0gUmVxdWlyZWQgcHJvYmFibHkgZHVlIHRvIHVwZGF0ZXMgaW4gc2YgcGFja2FnZXMuIFNlZTogPGh0dHBzOi8vZ2l0ZXJzLmNvbS9SYW1pS3Jpc3Bpbi9jb3JvbmF2aXJ1cy9pc3N1ZXMvOTU+DQoNCmBgYHtyfQ0KbGlicmFyeSh0bWFwKQ0KdG1hcF9tb2RlKCJ2aWV3IikNCiNxdG0oYXVzX3NmKQ0KDQphdXNfc2YgJT4lIA0KICBzdF9tYWtlX3ZhbGlkKCkgJT4lIA0KICB0bV9zaGFwZSgpICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAyLCBhbHBoYSA9IDAuNCkgKw0KICB0bV9maWxsKGNvbCA9ICJnb2xkIiwgYWxwaGEgPSAwLjEpDQpgYGANCg0KIyMgQ2FsY3VsYXRpbmcgYSBkaXN0YW5jZSBtYXRyaXgNCg0KSW4gb3VyIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWwsIHNwYWNlIGlzIG9uZSBvZiB0aGUga2V5IHByZWRpY3RvciB2YXJpYWJsZXMuIEluIHRoaXMgZXhhbXBsZSB3ZSB3aWxsIHVzZSBhIHZlcnkgc2ltcGxlIEV1Y2xpZGVhbiBkaXN0YW5jZSBtZWFzdXJlIGJldHdlZW4gKip0aGUgY2VudHJvaWRzIG9mIHRoZSBHcmVhdGVyIENhcGl0YWwgQ2l0eSBTdGF0aXN0aWNhbCBBcmVhcyoqIGFzIG91ciBtZWFzdXJlIG9mIHNwYWNlLg0KDQpOb3csIHdpdGggc29tZSBhcmVhcyBzbyBodWdlLCB0aGVyZSBhcmUgb2J2aW91cyBwb3RlbnRpYWwgaXNzdWVzIHdpdGggdGhpcyAoZm9yIGV4YW1wbGUgd2UgY291bGQgdXNlIHRoZSBhdmVyYWdlIGRpc3RhbmNlIHRvIGxhcmdlciBzZXR0bGVtZW50cyBpbiB0aGUgbm9uY2l0eSBhcmVhcyksIGhvd2V2ZXIgYXMgdGhpcyBpcyBqdXN0IGFuIGV4YW1wbGUsIHdlIHdpbGwgcHJvY2VlZCB3aXRoIGEgc2ltcGxlIHNvbHV0aW9uIGZvciBub3cuDQoNCi0gICBFUFNHOjMxMTIgLS0gUHJvamVjdGVkIEF1c3RyYWxpYSBjb29yZGluYXRlcyBpbiBtZXRyZXMNCg0KLSAgIHNmOjpzdF90cmFuc2Zvcm0gLSB0cmFuc2Zvcm0gY29vcmRpbmF0ZSBpbiBzZg0KDQpgYGB7cn0NCmxpYnJhcnkoc3ApDQojIFVzZSB0aGUgc3BEaXN0cyBmdW5jdGlvbiB0byBjcmVhdGUgYSBkaXN0YW5jZSBtYXRyaXgNCiMgRmlyc3QgcmVwcm9qZWN0IGludG8gYSBwcm9qZWN0ZWQgKG1ldHJlcykgY29vcmRpbmF0ZSBzeXN0ZW0NCmF1c19wcm9qIDwtIHNwVHJhbnNmb3JtKGF1cywgIitpbml0PWVwc2c6MzExMiIpDQoNCnN1bW1hcnkoYXVzX3Byb2opDQpgYGANCg0KTm93IGxldCdzIGNhbGN1bGF0ZSB0aGUgZGlzdGFuY2VzDQoNCmBgYHtyfQ0KZGlzdCA8LSBzcERpc3RzKGF1c19wcm9qKSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lIA0KICBtdXRhdGUobiA9IHJvd19udW1iZXIoKSkNCg0KZGlzdCAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQpgYGANCg0KYGBge3J9DQojIFByb2JhYmx5IGRvIHRoZSBzYW1lIGluIHNmPw0KDQojYXVzX3Byb2plY3Rpb25fc2YgPSBzdF90cmFuc2Zvcm0oYXVzX3NmLCAiK2luaXQ9ZXBzZzozMTEyIikNCiNzZl9kaXN0ID0gc3RfZGlzdGFuY2UoYXVzX3Byb2plY3Rpb25fc2YpDQpgYGANCg0KYGBge3J9DQojIFBpdm90IGxvbmdlciB0aGUgZGF0YSBhbmQgbWFrZSBkaXN0YW5jZSBrbQ0KZGlzdCA8LSBkaXN0ICU+JSANCiAgcGl2b3RfbG9uZ2VyKCFuLCBuYW1lc190byA9ICJ2YXIyIiwgdmFsdWVzX3RvID0gImRpc3QiKSAlPiUNCiAgbXV0YXRlKGRpc3QgPSBkaXN0LzEwMDApDQoNCmRpc3QgJT4lIA0KICBzbGljZV9oZWFkKG4gPSA1KQ0KYGBgDQoNClRoZXNlIGRpc3RhbmNlcyBhcmUgbm90aW9uYWxseSBpbiBrbSAtIGFsdGhvdWdoIHlvdSBtYXkgbm90aWNlIHRoYXQgdGhleSBhcmUgbm90IDEwMCUgYWNjdXJhdGUuIFRoaXMgaXMgbm90IGEgYmlnIHByb2JsZW0gZm9yIG5vdyBhcyB0aGlzIGlzIGp1c3QgYW4gZXhhbXBsZSwgYnV0IGZvciByZWFsIGFwcGxpY2F0aW9ucywgbW9yZSBhY2N1cmF0ZSBkaXN0YW5jZXMgbWF5IGJlIHVzZWQuDQoNCiMjIEZsb3cgRGF0YQ0KDQpUaGUgZGF0YSB3ZSBhcmUgZ29pbmcgdG8gdXNlIHRvIHRlc3Qgb3VyIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxzIHdpdGggaXMgbWlncmF0aW9uIGRhdGEgZnJvbSB0aGUgMjAxMSBBdXN0cmFsaWFuIENlbnN1cy4gVGhlIEF1c3RyYWxpYW4gQ2Vuc3VzIGhhcyB1c3VhbCBhZGRyZXNzIGluZGljYXRvciBvbiBDZW5zdXMgbmlnaHQgKFVBSUNQKSBhbmQgYWRkcmVzcyBvbmUgeWVhciBhZ28gYW5kIDUgeWVhcnMgYWdvIGluZGljYXRvcnMuIEZyb20gdGhlc2UsIG9uZSB5ZWFyIGFuZCA1IHllYXIgbWlncmF0aW9uIHRyYW5zaXRpb25zIGNhbiBiZSByZWNvcmRlZCAtIGhlcmUgd2Ugd2lsbCB1c2UgdGhlIDUgeWVhciB0cmFuc2l0aW9ucy4NCg0KQXMgd2VsbCBhcyBmbG93IGRhdGEsIHRoZXJlIGFyZSBhZGRpdGlvbmFsIGRhdGEgb24gdW5lbXBsb3ltZW50IHJhdGVzLCB3ZWVrbHkgaW5jb21lIGFuZCB0aGUgcGVyY2VudGFnZSBvZiBwZW9wbGUgbGl2aW5nIGluIHJlbnRlZCBhY2NvbW1vZGF0aW9uIGZvciBlYWNoIG9yaWdpbiBhbmQgZGVzdGluYXRpb24uIFdlIHdpbGwgdXNlIHRoZXNlIGFzIGRlc3RpbmF0aW9uIGF0dHJhY3RpdmVuZXNzIC8gbWFzcyB0ZXJtIGFuZCBvcmlnaW4gZW1pc3NpdmVuZXNzIC8gbWFzcyB0ZXJtIHByb3hpZXMgaW4gdGhlIG1vZGVscyB3aGljaCBmb2xsb3cuDQoNClRoZXNlIGRhdGEgY2FuIGJlIHJlYWQgc3RyYWlnaHQgaW50byBSIHdpdGggdGhlIGZvbGxvd2luZyBjb21tYW5kOg0KDQpgYGB7cn0NCiNyZWFkIGluIHlvdXIgQXVzdHJhbGlhbiBNaWdyYXRpb24gRGF0YQ0KbWRhdGEgPC0gcmVhZF9jc3YoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3Mvd2kzenhscTVwZmYxeWRhL0F1c01pZzIwMTEuY3N2P3Jhdz0xIiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkNCg0KbWRhdGEgJT4lIA0KICBzbGljZV9oZWFkKG4gPSA1KQ0KYGBgDQoNCk5vdyB0byBmaW5pc2gsIHdlIG5lZWQgdG8gYWRkIGluIG91ciBkaXN0YW5jZSBkYXRhIHRoYXQgd2UgZ2VuZXJhdGVkIGVhcmxpZXIgYW5kIGNyZWF0ZSBhIG5ldyBjb2x1bW4gb2YgdG90YWwgZmxvd3Mgd2hpY2ggZXhjbHVkZXMgZmxvd3MgdGhhdCBvY2N1ciB3aXRoaW4gYXJlYXMgKHdlIGNvdWxkIGtlZXAgdGhlIHdpdGhpbi1hcmVhIChpbnRyYS1hcmVhKSBmbG93cyBpbiwgYnV0IHRoZXkgY2FuIGNhdXNlIHByb2JsZW1zIHNvIGZvciBub3cgd2Ugd2lsbCBqdXN0IGV4Y2x1ZGUgdGhlbSkuDQoNCmBgYHtyfQ0KIyBGaXJzdCBjcmVhdGUgYSBuZXcgdG90YWwgY29sdW1uIHdoaWNoIGV4Y2x1ZGVzIGludHJhLXpvbmUgdG90YWxzDQojIFNldHMgdGhlbSB0byBhIHZlcnkgc21hbGwgbnVtYmVyIGZvciByZWFzb25zIHdlIHdpbGwgc2VlIGxhdGVyDQptZGF0YSA8LSBtZGF0YSAlPiUgDQogIG11dGF0ZSgNCiAgICBmbG93X25vX2ludHJhID0gY2FzZV93aGVuKA0KICAgICAgT3JpZ19jb2RlID09IERlc3RfY29kZSB+IDAsDQogICAgICBUUlVFIH4gRmxvdyksDQogICAgDQogICAgb2Zmc2V0ID0gY2FzZV93aGVuKA0KICAgICAgT3JpZ19jb2RlID09IERlc3RfY29kZSB+IDFlLTEwLA0KICAgICAgVFJVRSB+IDEpKQ0KDQptZGF0YSAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQpgYGANCg0KTm93IHdlIG9yZGVyZWQgb3VyIHNwYXRpYWwgZGF0YSBlYXJsaWVyIHNvIHRoYXQgb3VyIHpvbmVzIGFyZSBpbiB0aGVpciBjb2RlIG9yZGVyLiBXZSBjYW4gbm93IGVhc2lseSBqb2luIHRoZXNlIGRhdGEgdG9nZXRoZXIgd2l0aCBvdXIgZmxvdyBkYXRhIGFzIHRoZXkgYXJlIGluIHRoZSBjb3JyZWN0IG9yZGVyLg0KDQpgYGB7cn0NCiMgYW5kIHdoaWxlIHdlIGFyZSBoZXJlLCByYXRoZXIgdGhhbiBzZXR0aW5nIHRoZSBpbnRyYSB6b25hbCBkaXN0YW5jZXMgdG8gMA0KIyB3ZSBzaG91bGQgc2V0IHRoZW0gdG8gc29tZXRoaW5nIHNtYWxsIChtb3N0IGludHJhem9uYWwgbW92ZXMgd29uJ3Qgb2NjdXIgb3ZlciAwIGRpc3RhbmNlKQ0KDQptZGF0YSA8LSBtZGF0YSAlPiUgDQogIGJpbmRfY29scyhkaXN0ICU+JSBzZWxlY3QoZGlzdCkpICU+JSANCiAgbXV0YXRlKGRpc3QgPSBjYXNlX3doZW4oDQogICAgZGlzdCA9PSAwIH4gNSwNCiAgICBUUlVFIH4gZGlzdCkpDQoNCm1kYXRhICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCmBgYA0KDQpBbmQgdGhpcyBpcyB3aGF0IHRob3NlIGZsb3dzIGxvb2sgbGlrZSBvbiBhIG1hcCAtIHF1aWNrIGFuZCBkaXJ0eSBzdHlsZS4uLiBBbHRob3VnaCBmaXJzdCB3ZSdsbCByZW1vdmUgdGhlIGludHJhLXpvbmFsIGZsb3dzLg0KDQpgYGB7cn0NCiMgcmVtb3ZlIGludHJhLXpvbmFsIGZsb3dzDQptZGF0YXN1YiA8LSBtZGF0YSAlPiUgDQogIGZpbHRlcihPcmlnX2NvZGUgIT0gRGVzdF9jb2RlKQ0KYGBgDQoNCk5vdyBsZXQncyBjcmVhdGUgYSBmbG93LWxpbmUgb2JqZWN0IGFuZCB3ZWlnaHQgdGhlIGxpbmVzIGFjY29yZGluZyB0byB0aGUgZmxvdyB2b2x1bWVzDQoNCi0gICBXb3JrIGlzIG5lZWRlZCB0byBjb252ZXJ0IHRoZSBPRCBkYXRhIGludG8gJ2Rlc2lyZSBsaW5lcycuIERlc2lyZSBsaW5lcyBhcmUgc3RyYWlnaHQgbGluZXMgYmV0d2VlbiB0aGUgb3JpZ2luIGFuZCBkZXN0aW5hdGlvbiBhbmQgcmVwcmVzZW50IHdoZXJlIHBlb3BsZSB3b3VsZCBnbyBpZiB0aGV5IHdlcmUgbm90IGNvbnN0cmFpbmVkIGJ5IHRoZSByb3V0ZSBuZXR3b3JrIChzZWUgRmlndXJlIDMgZnJvbSAoTG92ZWxhY2UgZXQgYWwuIDIwMTcpKS4NCg0KLSAgIE9yaWdpbiwgZGVzdGluYXRpb24sZmxvdyBjb2x1bW5zDQoNCmBgYHtyfQ0KIyB1c2UgdGhlIG9kMmxpbmUgZnVuY3Rpb24gZnJvbSBSb2JpbiBMb3ZlbGFjZSdzIGV4Y2VsbGVudCBzdHBsYW5yIHBhY2thZ2UgLSByZW1vdmUgYWxsIGJ1dCB0aGUgb3JpZ2luLCBkZXN0aW5hdGlvbiBhbmQgZmxvdyBjb2x1bW5zDQpsaWJyYXJ5KHN0cGxhbnIpDQptZGF0YXN1Yl9za2lubnkgPC0gbWRhdGFzdWIgJT4lIA0KICBzZWxlY3QoT3JpZ19jb2RlLCBEZXN0X2NvZGUsIEZsb3cpDQojIERlc2lyZSBsaW5lcw0KdHJhdmVsX25ldHdvcmsgPC0gb2QybGluZShmbG93ID0gbWRhdGFzdWJfc2tpbm55LCB6b25lcyA9IGF1cykNCg0KIyBDb252ZXJ0cyBmbG93cyB0byBXR1M4NA0KdHJhdmVsX25ldHdvcmt3Z3MgPC0gc3BUcmFuc2Zvcm0odHJhdmVsX25ldHdvcmssICIraW5pdD1lcHNnOjQzMjYiKQ0KDQojIFNldCB0aGUgbGluZSB3aWR0aHMgdG8gc29tZSBzZW5zaWJsZSB2YWx1ZXMgYWNjb3JkaW5nIHRvIGZsb3cNCncgPC0gbWRhdGFzdWJfc2tpbm55JEZsb3cgLyBtYXgobWRhdGFzdWJfc2tpbm55JEZsb3cpICogMTANCmBgYA0KDQpNYWtlIGEgbWFwIG9mIHRoZSBsaW5lc3RyaW5ncyByZXByZXNlbnRpbmcgbW92ZW1lbnQgZnJvbSBvbmUgcGxhY2UgdG8gYW5vdGhlci4NCg0KYGBge3J9DQpsaWJyYXJ5KGxlYWZsZXQpDQojIFBsb3QgaW4gbGVhZmxldA0KbGVhZmxldCgpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUgDQogIGFkZFBvbHlsaW5lcygNCiAgICBkYXRhID0gdHJhdmVsX25ldHdvcmt3Z3MsDQogICAgd2VpZ2h0ID0gdykNCmBgYA0KDQpPciB5b3UgY2FuIHZpZXcgeW91ciBmbG93cyBhcyBhIG1hdHJpeC4uLg0KDQotICAgcm93d2lzZTogYWxsb3dzIHlvdSB0byBjb21wdXRlIG9uIGEgZGF0YSBmcmFtZSBhIHJvdy1hdC1hLXRpbWUNCg0KLSAgIGNfYWNyb3NzKCkgaXMgZGVzaWduZWQgdG8gd29yayB3aXRoIHJvd3dpc2UoKSB0byBtYWtlIGl0IGVhc3kgdG8gcGVyZm9ybSByb3ctd2lzZSBhZ2dyZWdhdGlvbnMuDQoNCmBgYHtyfQ0KIyBQaXZvdCB3aWRlciBhbmQgZmluZCB0aGUgdG90YWwgZmxvdyBmcm9tIGEgc3BlY2lmaWMgYXJlYQ0KbWRhdGFzdWJtYXQgPC0gbWRhdGEgJT4lIA0KICBzZWxlY3QoT3JpZ19jb2RlLCBEZXN0X2NvZGUsIGZsb3dfbm9faW50cmEpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IERlc3RfY29kZSwgdmFsdWVzX2Zyb20gPSBmbG93X25vX2ludHJhKSAlPiUNCiAgcm93d2lzZShPcmlnX2NvZGUpICU+JSANCiAgbXV0YXRlKHRvdGFsX2Zsb3dfZnJtX2FyZWEgPSBzdW0oY19hY3Jvc3Mod2hlcmUoaXMubnVtZXJpYykpKSApIA0KDQptZGF0YXN1Ym1hdA0KYGBgDQoNCk9rYXksIHdlJ3ZlIHNldCBldmVyeXRoaW5nIHVwLCBub3cgaXQncy4uLg0KDQojIyBNb2RlbGxpbicgVGltZSEhDQoNClBvcHVsYXRpb24gZmxvd3MgY2FuIGJlIGNvbmNlcHR1YWxpc2VkIGFzIGludGVyYWN0aW9ucyBiZXR3ZWVuIHR3byBlbnRpdGllcyAtLSBvcmlnaW5zIGFuZCBkZXN0aW5hdGlvbnMgLS0gd2hpY2ggaGF2ZSBkaWZmZXJlbnQgcHJvcGVydGllcyBvZiBlbWlzc2l2aXR5IGFuZCBhdHRyYWN0aXZlbmVzcyAoc2VlIExlZSAxOTY2IGZvciB0aGUgY2xhc3NpYyBwYXBlciBvbiB0aGlzIHRvcGljIGluIHJlbGF0aW9uIHRvIG1pZ3JhdGlvbikuDQoNCkluIGV4cGxhaW5pbmcgaG93IHRvIHJ1biBhbmQgY2FsaWJyYXRlIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxzIGluIFIsIEkgd2lsbCBhZG9wdCB0aGUgbm90YXRpb24gdXNlZCBieSBUYXlsb3IgT3NoYW4gaW4gaGlzIGV4Y2VsbGVudCBwcmltZXIgZm9yIHJ1bm5pbmcgc3BhdGlhbCBpbnRlcmF0aW9uIG1vZGVscyBpbiBQeXRob24uIFRoZSBwYXBlciBpcyB3ZWxsIHdvcnRoIGEgcmVhZCBhbmQgY2FuIGJlIGZvdW5kIGhlcmU6IDxodHRwOi8vb3BlbmpvdXJuYWxzLnd1LmFjLmF0L3JlZ2lvbi9wYXBlcl8xNzUvMTc1Lmh0bWw+DQoNCkJlbG93IGlzIHRoZSBjbGFzc2ljIG11bHRpcGxpY2F0aXZlIGdyYXZpdHkgbW9kZWw6DQoNCjEuICAkJFRfe2lqfSA9IGsgXGZyYWN7Vl9pXlxtdSBXX2peXGFscGhhfXtkX3tpan1eXGJldGF9JCQNCg0KVGhpcyBncmF2aXR5IG1vZGVsIGNhbiBiZSB3cml0dGVuIGluIHRoZSBmb3JtIG1vcmUgZmFtaWxpYXIgZnJvbSBXaWxzb24ncyAxOTcxIHBhcGVyIC0gPGh0dHA6Ly9qb3VybmFscy5zYWdlcHViLmNvbS9kb2kvYWJzLzEwLjEwNjgvYTAzMDAwMT4NCg0KJFRfe2lqfSA9IGsgVl9pXlxtdSBXX2peXGFscGhhIGRfe2lqfV4tXGJldGEkDQoNCioqVGhpcyBtb2RlbCBqdXN0IHNheXMgdGhhdCB0aGUgZmxvd3MgYmV0d2VlbiBhbiBvcmlnaW4gYW5kIGRlc3RpbmF0aW9uIGFyZSBwcm9wb3J0aW9uYWwgdG8gdGhlIHByb2R1Y3Qgb2YgdGhlIG1hc3Mgb2YgdGhlIG9yaWdpbiBhbmQgZGVzdGluYXRpb24gYW5kIGludmVyc2VseSBwcm9wb3J0aW9uYWwgdG8gdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlbS4qKg0KDQoqKkFzIG9yaWdpbiBhbmQgZGVzdGluYXRpb24gbWFzc2VzIGluY3JlYXNlLCBmbG93cyBpbmNyZWFzZSwgYnV0IGFzIGRpc3RhbmNlIGluY3JlYXNlcywgZmxvd3MgZGVjcmVhc2UsIGFuZCAqdmljZSB2ZXJzYSouKioNCg0KLSAgIHdoZXJlIFRpalRpaiBpcyB0aGUgdHJhbnNpdGlvbiBvciBmbG93LCBUVCwgYmV0d2VlbiBvcmlnaW4gaWkgKGFsd2F5cyB0aGUgcm93cyBpbiBhIG1hdHJpeCkgYW5kIGRlc3RpbmF0aW9uIGpqIChhbHdheXMgdGhlIGNvbHVtbnMgaW4gYSBtYXRyaXgpLiBJZiB5b3UgYXJlIG5vdCBvdmVybHkgZmFtaWxpYXIgd2l0aCBtYXRyaXggbm90YXRpb24sIHRoZSBpaSBhbmQgamogYXJlIGp1c3QgZ2VuZXJpYyBpbmRleGVzIHRvIGFsbG93IHVzIHRvIHJlZmVyIHRvIGFueSBjZWxsIGluIHRoZSBtYXRyaXggbW9yZSBnZW5lcmFsbHkuDQoNCi0gICBWViBpcyBhIHZlY3RvciAoYSAxIGRpbWVuc2lvbmFsIG1hdHJpeCAtIG9yLCBpZiB5b3UgbGlrZSwgYSBzaW5nbGUgbGluZSBvZiBudW1iZXJzKSBvZiBvcmlnaW4gYXR0cmlidXRlcyB3aGljaCByZWxhdGUgdG8gdGhlIGVtaXNzaXZlbmVzcyBvZiBhbGwgb3JpZ2lucyBpbiB0aGUgZGF0YXNldCwgaWkgLSBpbiBvdXIgc2FtcGxlIGRhdGFzZXQsIHdlIGhhdmUgYSB2ZWN0b3Igb2Ygb3JpZ2luIHBvcHVsYXRpb25zICh3aGljaCBJIGhhdmUgY2FsbGVkIHZpMV9vcmlncG9wKSBhbmQgYSB2ZWN0b3Igb2Ygb3JpZ2luIGF2ZXJhZ2Ugc2FsYXJpZXMgKHdoaWNoIEkgaGF2ZSBjYWxsZWQgdmkyX29yaWdzYWwpIGluIDIwMDENCg0KLSAgIFdXIGlzIGEgdmVjdG9yIG9mIGRlc2luYXRpb24gb2YgYXR0cmlidXRlcyByZWxhdGluZyB0byB0aGUgYXR0cmFjdGl2ZW5zcyBvZiBhbGwgZGVzdGluYXRpb25zIGluIHRoZSBkYXRhc2V0LCBqaiAtIGluIG91ciBzYW1wbGUgZGF0YXNldCwgd2UgaGF2ZSBhIHZlY3RvciBvZiBkZXN0aW5hdGlvbiBwb3B1bGF0aW9ucyAod2hpY2ggSSBoYXZlIGNhbGxlZCB3ajFfZGVzdHBvcCkgYW5kIGEgdmVjdG9yIG9mIGRlc3RpbmF0aW9uIGF2ZXJhZ2Ugc2FsYXJpZXMgKHdoaWNoIEkgaGF2ZSBjYWxsZWQgd2oyX2Rlc3RzYWwpIGluIDIwMDENCg0KLSAgIGRkIGlzIGEgbWF0cml4IG9mIGNvc3RzIHJlbGF0aW5nIHRvIHRoZSBmbG93cyBiZXR3ZWVuIGlpIGFuZCBqaiAtIGluIG91ciBjYXNlIHRoZSBjb3N0IGlzIGRpc3RhbmNlIGFuZCBpdCBpcyBjYWxsZWQgJ2Rpc3QnIGluIG91ciBkYXRhc2V0Lg0KDQotICAga2ssIM68zrwsIM6xzrEgYW5kIM6yzrIgYXJlIGFsbCBtb2RlbCBwYXJhbWV0ZXJzIHRvIGJlIGVzdGltYXRlZA0KDQprayBpcyBhIGNvbnN0YW50IG9mIHByb3BvcnRpb25hbGl0eSBhbmQgbGVhZHMgdG8gdGhpcyBwYXJ0aWN1bGFyIG1vZGVsIGJlaW5nIG1vcmUgYWNjdXJhdGVseSBkZXNjcmliZWQgYXMgYSAndG90YWwgY29uc3RyYWluZWQnIG1vZGVsIGFzIGFsbCBmbG93cyBlc3RpbWF0ZWQgYnkgdGhlIG1vZGVsIHdpbGwgc3VtIHRvIGFueSBvYnNlcnZlZCBmbG93IGRhdGEgdXNlZCB0byBjYWxpYnJhdGUgdGhlIHBhcmFtZXRlcnMsIHdoZXJlOg0KDQozLiAgJCRrID0gXGZyYWN7VH17XHN1bV9pIFxzdW1falZfaV5cbXUgIFdfal5cYWxwaGEgZF97aWp9Xi1cYmV0YSB9JCQNCg0KYW5kIFRUIGlzIHRoZSBzdW0gb2Ygb3VyIG1hdHJpeCBvZiBvYnNlcnZlZCBmbG93cyBvcjoNCg0KNC4gICQkVCA9IFxzdW1faSBcc3VtX2ogVF97aWp9JCQNCg0KSW4gcGxhaW4gbGFuZ3VhZ2UsIHRoaXMgaXMganVzdCB0aGUgc3VtIG9mIGFsbCBvYnNlcnZlZCBmbG93cyBkaXZpZGVkIGJ5IHRoZSBzdW0gb2YgYWxsIG9mIHRoZSBvdGhlciBlbGVtZW50cyBpbiB0aGUgbW9kZWwuDQoNCiMjIyAqKkVzdGltYXRpbmcgTW9kZWwgUGFyYW1ldGVycyoqDQoNCk5vdywgaXQncyBwZXJmZWN0bHkgcG9zc2libGUgdG8gcHJvZHVjZSBzb21lIGZsb3cgZXN0aW1hdGVzIGJ5IHBsdWdnaW5nIHNvbWUgYXJiaXRyYXJ5IG9yIGV4cGVjdGVkIGVzdGltYXRlZCB2YWx1ZXMgaW50byBvdXIgcGFyYW1ldGVycy4gVGhlIHBhcmFtZXRlcnMgcmVsYXRlIHRvIHRoZSBzY2FsaW5nIGVmZmVjdCAvIGltcG9ydGFuY2Ugb2YgdGhlIHZhcmlhYmxlcyB0aGV5IGFyZSBhc3NvY2lhdGVkIHdpdGguDQoNCk1vc3Qgc2ltcGx5LCB3aGVyZSB0aGUgZWZmZWN0cyBvZiBvcmlnaW4gYW5kIGRlc3RpbmF0aW9uIGF0dHJpYnV0ZXMgb24gZmxvd3Mgc2NhbGUgaW4gYSBsaW5lYXIgZmFzaGlvbiAoaS5lLsKgZm9yIGEgMSB1bml0IGluY3JlYXNlIGluLCBzYXksIHBvcHVsYXRpb24gYXQgb3JpZ2luLCB3ZSBtaWdodCBleHBlY3QgYSAxIHVuaXQgaW5jcmVhc2UgaW4gZmxvd3Mgb2YgcGVvcGxlIGZyb20gdGhhdCBvcmlnaW4sIG9yIGZvciBhIGhhbHZpbmcgaW4gYXZlcmFnZSBzYWxhcnkgYXQgZGVzdGluYXRpb24sIHdlIG1pZ2h0IGV4cGVjdCBhIGhhbHZpbmcgb2YgY29tbXV0ZXJzKSwgzrzOvCA9IDEgYW5kIM6xzrEgPSAxLg0KDQpJbiBOZXd0b24ncyBvcmlnaW5hbCBncmF2aXR5IGVxdWF0aW9uLCDOss6yID0gLTIgd2hlcmUgdGhlIGluZmx1ZW5jZSBvZiBkaXN0YW5jZSBvbiBmbG93cyBmb2xsb3dzIGEgcG93ZXIgbGF3IC0gaS5lLsKgZm9yIGEgMSB1bml0IGluY3JlYXNlIGluIGRpc3RhbmNlLCB3ZSBoYXZlIGEgMVxeLTIgKDEpIHVuaXQgZGVjcmVhc2UgaW4gZmxvd3MsIGZvciBhIDIgdW5pdCBpbmNyZWFzZSBpbiBkaXN0YW5jZSwgd2UgaGF2ZSAyXF4tMiAoMC4yNSBvciAxLzQpIG9mIHRoZSBmbG93cywgZm9yIGEgMyB1bml0IGluY3JlYXNlLCAzXF4tMiAoMC4xMTEpIGV0Yy4NCg0KTGV0J3Mgc2VlIGlmIHRoZXNlIHBhcmFtZXRlcnMgYXJlIGEgZmFpciBmaXJzdCBndWVzcw0KDQoxLiAgJCQNCiAgICBkX3tpan1eXGJldGEgXCB3aXRoXCAgXGJldGE9LTIgDQogICAgJCQNCg0KYGBge3J9DQojRmlyc3QgcGxvdCB0aGUgY29tbXV0ZXIgZmxvd3MgYWdhaW5zdCBkaXN0YW5jZSBhbmQgdGhlbiBmaXQgYSBtb2RlbCBsaW5lIHdpdGggYSBeLTIgcGFyYW1ldGVyDQp0aGVtZV9zZXQodGhlbWVfbGlnaHQoKSkNCmxpYnJhcnkoc2NhbGVzKQ0KDQptZGF0YSAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBkaXN0LCB5ID0gRmxvdykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9mdW5jdGlvbihmdW4gPSB+LnheLTIsIGNvbG9yID0gInJlZCIsIGx3ZCA9IDEpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiTWlncmF0aW9uIEZsb3ciLCBsYWJlbHMgPSBjb21tYSkNCmBgYA0KDQoyLiAgJCQNCiAgICBWX2leXG11DQogICAgJCQNCg0KYGBge3J9DQptZGF0YSAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSB2aTFfb3JpZ3BvcCwgeSA9IEZsb3cpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fZnVuY3Rpb24oZnVuID0gfi54XjEsIGNvbG9yID0gInJlZCIsIGx3ZCA9IDEpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWUgPSAiT3JpZ2luIFBvcHVsYXRpb24iLCBsYWJlbHMgPSBjb21tYSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJNaWdyYXRpb24gRmxvdyIsIGxhYmVscyA9IGNvbW1hKQ0KYGBgDQoNCjMuICAkJA0KICAgIFdfal5cYWxwaGENCiAgICAkJA0KDQpgYGB7cn0NCm1kYXRhICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHdqM19kZXN0bWVkaW5jLCB5ID0gRmxvdykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9mdW5jdGlvbihmdW4gPSB+LnheMSwgY29sb3IgPSAicmVkIiwgbHdkID0gMSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICJEZXN0aW5hdGlvbiBNZWRpYW4gaW5jb21lIiwgbGFiZWxzID0gY29tbWEpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiTWlncmF0aW9uIEZsb3ciLCBsYWJlbHMgPSBjb21tYSkNCg0KYGBgDQoNCk9LLCBzbyBpdCBsb29rcyBsaWtlIHdlJ3JlIG5vdCBmYXIgb2ZmICh3ZWxsLCBkZXN0aW5hdGlvbiBpbmNvbWUgZG9lc24ndCBsb29rIHRvbyBwcm9taXNpbmcgYXMgYSBwcmVkaWN0b3IsIGJ1dCB3ZSdsbCBzZWUgaG93IHdlIGdldCBvbi4uLiksIHNvIGxldCdzIHNlZSB3aGF0IGZsb3cgZXN0aW1hdGVzIHdpdGggdGhlc2Ugc3RhcnRpbmcgcGFyYW1ldGVycyBsb29rIGxpa2UuDQoNCmBgYHtyfQ0KIyBTZXQgdXAgc29tZSB2YXJpYWJsZXMgdG8gaG9sZCBvdXIgcGFyYW1ldGVyIHZhbHVlcyBpbjoNCm11IDwtIDENCmFscGhhIDwtIDENCmJldGEgPC0gLTINCmsgPC0gMQ0KVDIgPC0gc3VtKG1kYXRhc3ViICU+JSBwdWxsKEZsb3cpKQ0KDQpgYGANCg0KTm93IGxldCdzIGNyZWF0ZSBzb21lIGZsb3cgZXN0aW1hdGVzIHVzaW5nIEVxdWF0aW9uIDIgKG11bHRpcGxpY2F0aXZlIGdyYXZpdHkgbW9kZSkgYWJvdmUuLi4gQmVnaW4gYnkgYXBwbHlpbmcgdGhlIHBhcmFtZXRlcnMgdG8gdGhlIHZhcmlhYmxlczoNCg0KYGBge3J9DQojIEVtbWlzaXZpdHksIEF0cnJhY3RpdmVuZXNzLCBEaXN0YW5jZQ0KdmkxX211IDwtIChtZGF0YXN1YiAlPiUgcHVsbCh2aTFfb3JpZ3BvcCkpXm11DQp3ajNfYWxwaGEgPC0gKG1kYXRhc3ViICU+JSBwdWxsKHdqM19kZXN0bWVkaW5jKSleYWxwaGENCmRpc3RfYmV0YSA8LSAobWRhdGFzdWIgJT4lIHB1bGwoZGlzdCkpXmJldGENCg0KVDEgPC0gdmkxX211KndqM19hbHBoYSpkaXN0X2JldGENCmsgPC0gVDIvc3VtKFQxKQ0Kaw0KYGBgDQoNClRoZW4sIGp1c3QgYXMgaW4gRXF1YXRpb24gMiBhYm92ZSwganVzdCBtdWx0aXBseSBldmVyeXRoaW5nIHRvZ2V0aGVyIHRvIGdldCB5b3VyIGZsb3cgZXN0aW1hdGVzOg0KDQotICAgYG1kYXRhc3ViYCA6IGRhdGEgd2l0aG91dCBpbnRyYSBmbG93cw0KDQpgYGB7cn0NCiMgUnVuIHRoZSBtb2RlbCBhbmQgc3RvcmUgeW91ciBmbG93IGVzdGltYXRlcyBpbiBhIG5ldyBjb2x1bW4NCm1kYXRhc3ViIDwtIG1kYXRhc3ViICU+JSANCiAgbXV0YXRlKHVuY29uc3RyYWluZWRFc3QxID0gcm91bmQoayp2aTFfbXUqd2ozX2FscGhhKmRpc3RfYmV0YSwgMCkpDQoNCiMgQ2hlY2sgdGhhdCB0aGUgc3VtIG9mIHRoZXNlIGVzdGltYXRlcyBtYWtlcyBzZW5zZQ0Kc3VtKG1kYXRhc3ViJHVuY29uc3RyYWluZWRFc3QxKQ0KYGBgDQoNClBpdm90IHdpZGVyIHRoZSBkYXRhIGFuZCBoYXZlIGEgbG9vayBhdCB5b3VyIGhhbmR5IHdvcmsNCg0KYGBge3J9DQptZGF0YXN1Ym1hdDEgPC0gbWRhdGFzdWIgJT4lIA0KICBzZWxlY3QoT3JpZ19jb2RlLCBEZXN0X2NvZGUsIHVuY29uc3RyYWluZWRFc3QxKSAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBEZXN0X2NvZGUsIHZhbHVlc19mcm9tID0gdW5jb25zdHJhaW5lZEVzdDEsIHZhbHVlc19maWxsID0gMCkgJT4lDQogICByZWxvY2F0ZSgiMUdTWUQiLCAuYWZ0ZXIgPSBPcmlnX2NvZGUpICU+JSANCiAgcm93d2lzZShPcmlnX2NvZGUpICU+JSANCiAgbXV0YXRlKHRvdGFsX2Zsb3dfZnJtX2FyZWEgPSBzdW0oY19hY3Jvc3Mod2hlcmUoaXMubnVtZXJpYykpKSApIA0KDQptZGF0YXN1Ym1hdDENCmBgYA0KDQpIb3cgZG9lcyB0aGUgZmxvdyBjb21wYXJlIHdpdGggdGhlIG9yaWdpbmFsPw0KDQpgYGB7cn0NCm1kYXRhc3VibWF0DQpgYGANCg0KIyMgKipIb3cgZ29vZCBpcyBteSBtb2RlbD8qKg0KDQpTbywgbG9va2luZyBhdCB0aGUgdHdvIGxpdHRsZSBtYXRyaWNlcyBhYm92ZSB5b3UgY2FuIHNlZSB0aGF0IGluIHNvbWUgY2FzZXMgdGhlIGZsb3cgZXN0aW1hdGVzIGFyZW4ndCB0b28gYmFkLCBidXQgaW4gb3RoZXJzIHRoZXkgYXJlIHByZXR0eSBydWJiaXNoLiBXaGlsc3QgaXQncyBPSyB0byBleWViYWxsIHNtYWxsIGZsb3cgbWF0cmljZXMgbGlrZSB0aGlzLCB3aGVuIHlvdSBoYXZlIG11Y2ggbGFyZ2VyIG1hdHJpY2VzLCB3ZSBuZWVkIGFub3RoZXIgc29sdXRpb24uLi4NCg0KIyMjICoqVGVzdGluZyB0aGUgImdvb2RuZXNzLW9mLWZpdCIuKioNCg0KWWVzLCB0aGF0J3Mgd2hhdCBpdCdzIGNhbGxlZCAtIEkga25vdywgaXQgZG9lc24ndCBzb3VuZCBjb3JyZWN0LCBidXQgZ29vZG5lc3Mtb2YtZml0IGlzIHRoZSBjb3JyZWN0IHRlcm0gZm9yIGNoZWNraW5nIGhvdyB3ZWxsIHlvdXIgbW9kZWwgZXN0aW1hdGVzIG1hdGNoIHVwIHdpdGggeW91ciBvYnNlcnZlZCBmbG93cy4NCg0KU28gaG93IGRvIHdlIGRvIGl0Pw0KDQpXZWxsLi4uIHRoZXJlIGFyZSBhIG51bWJlciBvZiB3YXlzIGJ1dCBwZXJoYXBzIHRoZSB0d28gbW9zdCBjb21tb24gYXJlIHRvIGxvb2sgYXQgdGhlIGNvZWZmaWNpZW50IG9mIGRldGVybWluYXRpb24gKHIycjIpIG9yIHRoZSBTcXVhcmUgUm9vdCBvZiBNZWFuIFNxdWFyZWQgRXJyb3IgKFJNU0UpLiBZb3UndmUgcHJvYmFibHkgY29tZSBhY3Jvc3MgcjJyMiBiZWZvcmUgaWYgeW91IGhhdmUgZml0dGVkIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIGJ1dCB5b3UgbWF5IG5vdCBoYXZlIGNvbWUgYWNyb3NzIFJNU0UuIFRoZXJlIGFyZSBvdGhlciBtZXRob2RzIGFuZCB0aGV5IGFsbCBkbyBtb3JlIG9yIGxlc3MgdGhlIHNhbWUgdGhpbmcsIHdoaWNoIGlzIGVzc2VudGlhbGx5IHRvIGNvbXBhcmUgdGhlIG1vZGVsbGVkIGVzdGltYXRlcyB3aXRoIHRoZSByZWFsIGRhdGEuIHIycjIgaXMgcG9wdWxhciBhcyBpdCBpcyBxdWl0ZSBpbnR1aXRpdmUgYW5kIGNhbiBiZSBjb21wYXJlZCBhY3Jvc3MgbW9kZWxzLiBSTVNFIGlzIGxlc3MgaW50dWl0aXZlLCBidXQgc29tZSBhcmd1ZSBpcyBiZXR0ZXIgZm9yIGNvbXBhcmluZyBjaGFuZ2VzIHRvIHRoZSBzYW1lIG1vZGVsLiBIZXJlJ3Mgd2UnbGwgZG8gYm90aC4uLg0KDQojIyMjICoqUi1TcXVhcmVkKioNCg0KJHJeMiQgaXMgdGhlIHNxdWFyZSBvZiB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQsICRyJA0KDQpGb3Igb3VyIHNhbXBsZSBkYXRhLCB3ZSBjYW4gY2FsY3VsYXRlIHRoaXMgdmVyeSBlYXNpbHkgdXNpbmcgYSBsaXR0bGUgZnVuY3Rpb24NCg0KYGBge3J9DQpyc3FyZCA8LSBmdW5jdGlvbih0cnV0aCwgZXN0aW1hdGUpew0KICByID0gY29yKHRydXRoLCBlc3RpbWF0ZSkNCiAgUjIgPSByXjINCiAgcmV0dXJuKFIyKQ0KfQ0KDQpyc3FyZCh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkdW5jb25zdHJhaW5lZEVzdDEpDQpgYGANCg0KVXNpbmcgdGhpcyBmdW5jdGlvbiB3ZSBnZXQgYSB2YWx1ZSBvZiAwLjE5NSBvciBhcm91bmQgMjAlLiBUaGlzIHRlbGxzIHVzIHRoYXQgb3VyIG1vZGVsIGFjY291bnRzIGZvciBhYm91dCAyMCUgb2YgdGhlIHZhcmlhdGlvbiBvZiBmbG93cyBpbiB0aGUgc3lzdGVtLiBOb3QgYmFkLCBidXQgbm90IGJyaWxsaWFudCBlaXRoZXIuDQoNCiMjIyMgKipSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkqKg0KDQpXZSBjYW4gdXNlIGEgc2ltaWxhciBzaW1wbGUgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZSBSTVNFIGZvciBvdXIgZGF0YQ0KDQpgYGB7cn0NCnJNU0UgPC0gZnVuY3Rpb24odHJ1dGgsIGVzdGltYXRlKXsNCiAgcmVzID0gKHRydXRoIC0gZXN0aW1hdGUpXjINCiBSTVNFID0gc3FydChtZWFuKHJlcykpDQogIHJldHVybihyb3VuZChSTVNFLCAzKSkNCn0NCg0Kck1TRSh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkdW5jb25zdHJhaW5lZEVzdDEpDQpgYGANCg0KVGhpcyBjYW4gYmUgbG9vc2VseSBpbnRlcnByZXRlZCBhcywgb24gYXZlcmFnZSwgdGhlIGZsb3dzIGFyZSBvZmYgYnkgMjUsIDg1OC4NCg0KVGhlIGZpZ3VyZSB0aGF0IGlzIHByb2R1Y2VkIGJ5IHRoZSBSTVNFIGNhbGN1bGF0aW9uIGlzIGZhciBsZXNzIGludHVpdGl2ZSB0aGFuIHRoZSByMnIyIHZhbHVlIGFuZCB0aGlzIGlzIG1haW5seSBiZWNhdXNlIGl0IHZlcnkgbXVjaCBkZXBlbmRzIG9uIHRoaW5ncyBsaWtlIHRoZSB1bml0cyB0aGUgZGF0YSBhcmUgaW4gYW5kIHRoZSB2b2x1bWUgb2YgZGF0YS4gSXQgY2FuJ3QgYmUgdXNlZCB0byBjb21wYXJlIGRpZmZlcmVudCBtb2RlbHMgcnVuIHVzaW5nIGRpZmZlcmVudCBkYXRhIHNldHMuIEhvd2V2ZXIsIGl0IGlzIGdvb2QgZm9yIGFzc2Vzc2luZyB3aGV0aGVyIGNoYW5nZXMgdG8gdGhlIG1vZGVsIHJlc3VsdCBpbiBpbXByb3ZlbWVudHMuIFRoZSBjbG9zZXIgdG8gMCB0aGUgUk1TRSB2YWx1ZSwgdGhlIGJldHRlciB0aGUgbW9kZWwuDQoNClNvIGhvdyBjYW4gd2Ugc3RhcnQgdG8gaW1wcm92ZSBvdXIgZml0Li4uPw0KDQojIyBJbXByb3Zpbmcgb3VyIG1vZGVsOiAxIC0gQ2FsaWJyYXRpbmcgcGFyYW1ldGVycw0KDQpOb3csIHRoZSBtb2RlbCB3ZSBoYXZlIHJ1biBhYm92ZSBpcyBwcm9iYWJseSB0aGUgbW9zdCBzaW1wbGUgc3BhdGlhbCBpbnRlcmFjdGlvbiBtb2RlbCB3ZSBjb3VsZCBoYXZlIHJ1biBhbmQgdGhlIHJlc3VsdHMgYXJlbid0IHRlcnJpYmxlLCBidXQgdGhleSdyZSBub3QgZ3JlYXQgZWl0aGVyLg0KDQpPbmUgd2F5IHRoYXQgd2UgY2FuIGltcHJvdmUgdGhlIGZpdCBvZiB0aGUgbW9kZWwgaXMgYnkgY2FsaWJyYXRpbmcgdGhlIHBhcmFtZXRlcnMgb24gdGhlIGZsb3cgZGF0YSB0aGF0IHdlIGhhdmUuDQoNClRoZSB0cmFkaXRpb25hbCB3YXkgdGhhdCB0aGlzIGhhcyBiZWVuIGRvbmUgY29tcHV0YXRpb25hbGx5IGlzIGJ5IHVzaW5nIHRoZSBnb29kbmVzcy1vZi1maXQgc3RhdGlzdGljcy4gSWYgeW91IGhhdmUgdGhlIHJlcXVpc2l0ZSBwcm9ncmFtbWluZyBza2lsbHMsIHlvdSBjYW4gd3JpdGUgYSBjb21wdXRlciBhbGdvcml0aG0gdGhhdCBpdGVyYXRpdmVseSBhZGp1c3RzIGVhY2ggcGFyYW1ldGVyLCBydW5zIHRoZSBtb2RlbCwgY2hlY2tzIHRoZSBnb29kbmVzcy1vZi1maXQgYW5kIHRoZW4gc3RhcnRzIGFsbCBvdmVyIGFnYWluIHVudGlsIHRoZSBnb29kbmVzcy1vZi1maXQgc3RhdGlzdGljIGlzIG1heGltaXNlZC4NCg0KVGhpcyBpcyBwYXJ0bHkgd2h5IHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxsaW5nIHdhcyB0aGUgcHJlc2VydmUgb2Ygc3BlY2lhbGlzdHMgZm9yIHNvIGxvbmcgYXMgYWNxdWlyaW5nIHRoZSByZXF1aXNpdGUgc2tpbGxzIHRvIHdyaXRlIHN1Y2ggY29tcHV0ZXIgcHJvZ3JhbW1lcyBjYW4gYmUgY2hhbGxlbmdpbmchDQoNCkhvd2V2ZXIsIHNpbmNlIHRoZSBlYXJseSBkYXlzIG9mIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxsaW5nLCBhIG51bWJlciBvZiB1c2VmdWwgZGV2ZWxvcG1lbnRzIGhhdmUgb2NjdXJyZWQuLi4gRm9yIGEgbW9yZSBkZXRhaWxlZCBleHBsYW5hdGlvbiwgcmVhZCB0aGUgYWNjb21wYW55aW5nIHBhcGVyLCBidXQgSSB3aWxsIHNrYXRlIG92ZXIgdGhlbSBhZ2FpbiBoZXJlLg0KDQpUaGUgbWF0aGVtYXRpY2FsbHkgbWluZGVkIGFtb25nIHlvdSBtYXkgaGF2ZSBub3RpY2VkIHRoYXQgaWYgeW91IHRha2UgdGhlIGxvZ2FyaXRobXMgb2YgYm90aCBzaWRlcyBvZiBFcXVhdGlvbiAyLCB5b3UgZW5kIHVwIHdpdGggdGhlIGZvbGxvd2luZyBlcXVhdGlvbjoNCg0KNS4gICRcbG4gVF97aWp9ID0gayArIFxtdVxsbiBWX2kgKyBcYWxwaGFcbG4gV19qIC0gXGJldGEgXGxuIGRfe2lqfSQNCg0KVGhvc2Ugb2YgeW91IHdobyBoYXZlIHBsYXllZCBhcm91bmQgd2l0aCByZWdyZXNzaW9uIG1vZGVscyBpbiB0aGUgcGFzdCB3aWxsIHJlYWxpc2UgdGhhdCB0aGlzIGlzIGV4YWN0bHkgdGhhdCAtIGEgcmVncmVzc2lvbiBtb2RlbC4NCg0KQW5kIGlmIHlvdSBoYXZlIHBsYXllZCBhcm91bmQgd2l0aCByZWdyZXNzaW9uIG1vZGVscyB5b3Ugd2lsbCBiZSBhd2FyZSB0aGF0IHRoZXJlIGFyZSB2YXJpb3VzIHBpZWNlcyBvZiBzb2Z0d2FyZSBhdmFpbGFibGUgdG8gcnVuIHJlZ3Jlc3Npb25zIChzdWNoIGFzIFIpIGFuZCBjYWxpYnJhdGUgdGhlIHBhcmFtZXRlcnMgZm9yIHVzLCBzbyB3ZSBkb24ndCBoYXZlIHRvIGJlIGV4cGVydCBwcm9ncmFtbWVycyB0byBkbyB0aGlzIC0geWF5IQ0KDQpOb3csIHRoZXJlIGFyZSBhIGNvdXBsZSBvZiBwYXBlcnMgdGhhdCBhcmUgd29ydGggcmVhZGluZyBhdCB0aGlzIHBvaW50LiBQZXJoYXBzIHRoZSBiZXN0IGlzIGJ5IEZsb3dlcmRldyBhbmQgQWl0a2luICgxOTgyKSwgdGl0bGVkICJBIE1FVEhPRCBPRiBGSVRUSU5HIFRIRSBHUkFWSVRZIE1PREVMIEJBU0VEIE9OIFRIRSBQT0lTU09OIERJU1RSSUJVVElPTiIgLSB0aGUgcGFwZXIgY2FuIGJlIGZvdW5kIGhlcmU6IDxodHRwOi8vb25saW5lbGlicmFyeS53aWxleS5jb20vZG9pLzEwLjExMTEvai4xNDY3LTk3ODcuMTk4Mi50YjAwNzQ0LngvYWJzdHJhY3Q+DQoNCk9uZSBvZiB0aGUga2V5IHBvaW50cyB0aGF0IEZsb3dlcmRldyBhbmQgQWl0a2luIG1ha2UgaXMgdGhhdCB0aGUgbW9kZWwgaW4gRXF1YXRpb24gNSAoa25vd24gYXMgYSBsb2ctbm9ybWFsIG1vZGVsKSBoYXMgdmFyaW91cyBwcm9ibGVtcyBhc3NvY2lhdGVkIHdpdGggaXQgd2hpY2ggbWVhbiB0aGF0IHRoZSBlc3RpbWF0ZXMgcHJvZHVjZWQgbWlnaHQgbm90IGJlIHJlbGlhYmxlLiBJZiB5b3UnZCBsaWtlIHRvIGtub3cgbW9yZSBhYm91dCB0aGVzZSwgcmVhZCB0aGUgcGFwZXIgKGFuZCBhbHNvIFdpbHNvbidzIDE5NzEgcGFwZXIpLCBidXQgYXQgdGhpcyBwb2ludCBpdCBpcyB3b3J0aCBqdXN0IGtub3dpbmcgdGhhdCB0aGUgd2F5IGFyb3VuZCBtYW55IG9mIHRoZXNlIGlzc3VlcyBpcyB0byByZS1zcGVjaWZ5IHRoZSBtb2RlbCwgbm90IGFzIGEgbG9nLW5vcm1hbCByZWdyZXNzaW9uLCBidXQgYXMgYSBQb2lzc29uIG9yIG5lZ2F0aXZlIGJpbm9taWFsIHJlZ3Jlc3Npb24gbW9kZWwuDQoNCiMjIyAqKlBvaXNzb24gcmVncmVzc2lvbioqDQoNCkFnYWluLCBJIGdvIGludG8gdGhpcyBpbiBtb3JlIGRldGFpbCBpbiB0aGUgYWNjb21wYW55aW5nIHBhcGVyLCBidXQgdGhlIG1haW4gdGhlb3J5IChmb3Igbm9uLWV4cGVydHMgbGlrZSBtZSBhbnl3YXkpIGJlaGluZCB0aGUgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsIGlzIHRoYXQgdGhlIHNvcnRzIG9mIGZsb3dzIHRoYXQgc3BhdGlhbCBpbnRlcmFjdGlvbiBtb2RlbHMgZGVhbCB3aXRoIChzdWNoIGFzIG1pZ3JhdGlvbiBvciBjb21tdXRpbmcgZmxvd3MpIHJlbGF0ZSB0byBub24tbmVnYXRpdmUgaW50ZWdlciBjb3VudHMgKHlvdSBjYW4ndCBoYXZlIG5lZ2F0aXZlIHBlb3BsZSBtb3ZpbmcgYmV0d2VlbiBwbGFjZXMgYW5kIHlvdSBjYW4ndCAtIG5vcm1hbGx5LCBpZiB0aGV5IGFyZSBhbGl2ZSAtIGhhdmUgZnJhY3Rpb25zIG9mIHBlb3BsZSBtb3ZpbmcgZWl0aGVyKS4NCg0KQXMgc3VjaCwgdGhlIGNvbnRpbnVvdXMgKG5vcm1hbCkgcHJvYmFiaWx0eSBkaXN0cmlidXRpb25zIHdoaWNoIHVuZGVycGluIHN0YW5kYXJkIHJlZ3Jlc3Npb24gbW9kZWxzIGRvbid0IGhvbGQuIEhvd2V2ZXIsIHRoZSBkaXNjcnRldGUgcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9ucyBzdWNoIGFzIHRoZSBQb2lzc29uIGRpc3RyaWJ1dGlvbiBhbmQgdGhlIG5lZ2F0aXZlIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiAob2Ygd2hpY2ggdGhlIFBvaXNzb24gZGlzdHJpYnV0aW9uIGlzIGEgc3BlY2lhbCBjYXNlIC0gd2lraXBlZGlhIGl0KSBkbyBob2xkIGFuZCBzbyB3ZSBjYW4gdXNlIHRoZXNlIGFzc29jaWF0aW9ucyB0byBtb2RlbCBvdXIgZmxvd3MuDQoNCkF0IHRoaXMgcG9pbnQsIGl0J3MgcHJvYmFibHkgd29ydGggeW91IGxvb2tpbmcgYXQgd2hhdCBhIFBvaXNzb24gZGlzcmlidXRpb24gbG9va3MgbGlrZSBjb21wYXJlZCB0byBhIG5vcm1hbCBkaXN0cmlidXRpb24sIGlmIHlvdSBhcmUgbm90IGZhbWlsaWFyLg0KDQpIZXJlJ3MgYSBub3JtYWwgZGlzdHJpYnV0aW9uOg0KDQpgYGB7cn0NCiMgaGlzdG9ncmFtIHdpdGggbm9ybWFsIGRpc3RyaWJ1dGlvbiBvZiBjb3VudCAzMDAwLCBhIG1lYW4gb2YgNzUgYW5kIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDUNCnRpYmJsZShuZGlzdCA9IHJub3JtKDMwMDAsIG1lYW4gPSAxMCwgc2QgPSA1KSkgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gbmRpc3QpLCBmaWxsID0gIm1pZG5pZ2h0Ymx1ZSIsIGFscGhhID0gMC43KSArDQogIHhsYWIoInJub3JtKDMwMDAsIG1lYW4gPSA3NSwgc2QgPSA1KSIpDQpgYGANCg0KTm93IGhlcmUncyBhIFBvaXNzb24gZGlzdHJpYnV0aW9uIHdpdGggdGhlIHNhbWUgbWVhbjoNCg0KYGBge3J9DQojIGhpc3RvZ3JhbSB3aXRoIGEgcG9pc3NvbiBkaXN0cmlidXRpb24gb2YgY291bnQgMzAwMCwgYSBtZWFuIG9mIDc1IA0KdGliYmxlKHBkaXN0ID0gcnBvaXMobiA9IDMwMDAsIGxhbWJkYSA9IDc1KSkgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gcGRpc3QpLCBmaWxsID0gImluZGlhbnJlZCIsIGFscGhhID0gMC43KSArDQogIHhsYWIoInJwb2lzKG4gPSAzMDAwLCBsYW1iZGEgPSA3NSkiKQ0KYGBgDQoNCkxvb2tzIGtpbmQgb2Ygc2ltaWxhciBkb2Vzbid0IGl0ISBUaGUgdGhpbmcgd2l0aCB0aGUgUG9pc3NvbiBkaXN0cmlidXRpb24gaXMsIHcqKmhlbiB0aGUgbWVhbiAoKirOuyAqKi0gbGFtYmRhKSBjaGFuZ2VzLCBzbyBkb2VzIHRoZSBkaXN0cmlidXRpb24qKi4gTm9ybWFsIGRpc3RyaWJ1dGlvbnMgb24gdGhlIG90aGVyIGhhbmQgKipyZXRhaW4gdGhlaXIgYmVsbCBzaGFwZS4qKg0KDQpBcyB0aGUgbWVhbiBnZXRzIHNtYWxsZXIgKGFuZCB0aGlzIGlzIG9mdGVuIHRoZSBjYXNlIHdpdGggZmxvdyBkYXRhIHdoZXJlIHNtYWxsIGZsb3dzIGFyZSB2ZXJ5IGxpa2VseSAtIGhhdmUgYSBsb29rIGF0IHRoZSAnVG90YWwnIGNvbHVtbiBpbiB5b3VyIGNkYXRhIGRhdGFmcmFtZSwgbG90cyBvZiBzbWFsbCBudW1iZXJzIGFyZW4ndCB0aGVyZT8pIHRoZSBkaXN0cmlidXRpb24gc3RhcnRzIHRvIGxvb2sgYSBsb3QgbW9yZSBsaWtlIGEgc2tld2VkIG9yIGxvZy1ub3JtYWwgZGlzdHJidXRpb24uIFRoZXkga2V5IHRoaW5nIGlzIGl0J3Mgbm90IC0gaXQncyBhIFBvaXNzb24gZGlzdHJpYnV0aW9uLiBIZXJlJ3MgYSBzaW1pbGFyIGZyZXF1ZW5jeSBkaXN0cmlidXRpb24gd2l0aCBhIHNtYWxsIG1lYW46DQoNCmBgYHtyfQ0KIyBXaGF0IGFib3V0IGEgbGFtYmRhIG9mIDAuNT8NCnRpYmJsZShwZGlzdCA9IHJwb2lzKG4gPSAzMDAwLCBsYW1iZGEgPSAwLjUpKSAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBwZGlzdCksIGZpbGwgPSAiaW5kaWFucmVkIiwgYWxwaGEgPSAwLjcsIGJpbndpZHRoID0gMSkgKw0KICB4bGFiKCJycG9pcyhuID0gMzAwMCwgbGFtYmRhID0gNzUpIikNCmBgYA0KDQpBcyBmYXIgYXMgd2UncmUgY29uY2VybmVkLCB3aGF0IHRoaXMgbWVhbnMgaXMgdGhhdCBpZiB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBhbGwgZmxvd3MgYmV0d2VlbiBhbGwgb3JpZ2lucyBhbmQgZGVzdGluYXRpb25zIGluIG91ciBzeXN0ZW0sIHRoZXNlIGZsb3dzIHdpbGwgaGF2ZSBhIG1lYW4gdmFsdWUgb2YgJFxsYW1iZGFfe2lqfSQgYW5kIHRoaXMgd2lsbCBkaWN0YXRlIHRoZSBkaXN0cmlidXRpb24uIEhlcmUncyB3aGF0IHRoZSBkaXN0cmJ1dGlvbiBvZiBvdXIgZmxvd3MgbG9va3MgbGlrZToNCg0KYGBge3J9DQojIERpc3RyaWJ1dGlvbiBvZiBmbG93cw0KbWRhdGFzdWIgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gRmxvdykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJkYXJrb3JhbmdlIiwgYWxwaGEgPSAwLjgpDQpgYGANCg0KUmV2ZWFscyBhIGhpc3RvZ3JhbSB3aGljaCBsb29rcyBsaWtlIGEgc2tld2VkIG5vcm1hbCBvciwgbW9yZSBhY2N1cmF0ZWx5LCBhICoqUG9pc3NvbiBkaXN0cmlidXRpb24gd2l0aCBhIHNtYWxsIG1lYW4qKi4NCg0KU28sIHdoYXQgZG9lcyBhbGwgb2YgdGhpcyBtZWFuIGZvciBvdXIgc3BhdGlhbCBpbnRlcmFjdGlvbiBtb2RlbD8NCg0KV2VsbCB0aGUgbWFpbiB0aGluZyBpdCBtZWFucyBpcyB0aGF0IEVxdWF0aW9uIDUsIGZvciBtb3N0IHNvcnRzIG9mIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxzIHdoZXJlIHdlIGFyZSBtb2RlbGxpbmcgZmxvd3Mgb2YgcGVvcGxlIG9yIHdob2xlIHRoaW5ncywgaXMgbm90IGNvcnJlY3QuDQoNCkJ5IGxvZ2dpbmcgYm90aCBzaWRlcyBvZiB0aGUgZXF1YXRpb24gaW4gRXF1YXRpb24gNSwgd2UgYXJlIHRyeWluZyB0byBnZXQgYSBzaXR1YXRpb24gd2hlcmUgb3VyICRUX3tpan0kIGZsb3dzIGNhbiBiZSBtb2RlbGxlZCBieSB1c2luZyB0aGUgdmFsdWVzIG9mIG91ciBvdGhlciB2YXJpYWJsZXMgc3VjaCBhcyBkaXN0YW5jZSwgYnkgdXNpbmcgYSBzdHJhaWdodCBsaW5lIGEgYml0IGxpa2UgdGhpczoNCg0KYGBge3J9DQptZGF0YXN1YiAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBkaXN0LCB5ID0gbG9nKEZsb3cpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSBsbSkNCmBgYA0KDQpJZiB5b3UgY29tcGFyZSB0aGlzIGdyYXBoIHdpdGggdGhlIGdyYXBoIGFib3ZlICh0aGUgZmlyc3Qgc2NhdHRlciBwbG90IHdlIGRyZXcgaW4gdGhpcyBwcmFjdGljYWwgZXhlcmNpc2UpLCBpdCdzIGV4YWN0bHkgdGhlIHNhbWUgZGF0YSwgYnV0IGNsZWFybHkgYnkgbG9nZ2luZyBib3RoIHRoZSB0b3RhbCBhbmQgZGlzdGFuY2UsIHdlIGNhbiBnZXQgYSBiaXQgY2xvc2VyIHRvIGJlaW5nIGFibGUgdG8gZml0IGEgbW9kZWwgZXN0aW1hdGUgdXNpbmcgYSBzdHJhaWdodCBsaW5lLg0KDQpXaGF0IHRoZSBQb2lzc29uIGRpc3RyaWJ1dGlvbiBtZWFucyBpcyB0aGF0IHRoZSB5eSB2YXJpYWJsZSBpbiBvdXIgbW9kZWwgaXMgbm90IGxvZ2dlZCBhcyBpbiB0aGUgZ3JhcGggYWJvdmUsIGJ1dCBpdCBjYW4gc3RpbGwgYmUgbW9kZWxsZWQgdXNpbmcgc29tZXRoaW5nIGxpa2UgdGhlIGJsdWUgbGluZSAtIEkgaG9wZSB0aGF0IHNvcnQgb2YgbWFrZXMgc2Vuc2UuIElmIG5vdCwgZG9uJ3Qgd29ycnksIGp1c3QgdGFrZSBpdCBmcm9tIG1lIHRoYXQgdGhpcyBpcyBnb29kIG5ld3MuDQoNCiMjIFRoZSBQb2lzc29uIFJlZ3Jlc3Npb24gU3BhdGlhbCBJbnRlcmFjdGlvbiBNb2RlbA0KDQpTbywgd2UgY2FuIG5vdyByZS1zcGVjaWZ5IEVxdWF0aW9uIDUgYXMgYSBQb2lzc29uIFJlZ3Jlc3Npb24gbW9kZWwuIEluc3RlYWQgb2Ygb3VyIGluZGVwZW5kZW50IHZhcmlhYmxlIGJlaW5nIGxuVGlqIG91ciBkZXBlbmRlbnQgdmFyaWFibGUgaXMgbm93IHRoZSBtZWFuIG9mIG91ciBQb2lzc29uIGRpc3RyaWJ1dGlvbiDOu2lqIGFuZCB0aGUgbW9kZWwgYmVjb21lczoNCg0KNi4gICRcbGFtYmRhX3tpan0gPSBcZXhwKGsgKyBcbXVcbG4gVl9pICsgXGFscGhhXGxuIFdfaiAtIFxiZXRhIFxsbiBkX3tpan0pJA0KDQpXaGF0IHRoaXMgbW9kZWwgc2F5cyBpcyAkXGxhbWJkYV97aWp9JCAob3VyIGluZGVwZW5kZW50IHZhcmlhYmxlIC0gdGhlIGVzdGltYXRlIG9mICRUX3tpan0kKSBpcyAqbG9nYXJpdGhtaWNhbGx5IGxpbmtlZCogdG8gKG9yIG1vZGVsbGVkIGJ5KSBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiB0aGUgbG9nZ2VkIGluZGVwZW5kZW50IHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwuDQoNCk5vdyB3ZSBoYXZlIEVxdWF0aW9uIDYgYXQgb3VyIGRpc3Bvc2FsLCB3ZSBjYW4gdXNlIGEgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsIHRvIHByb2R1Y2UgZXN0aW1hdGVzIG9mICRrJCwgJFxtdSQsICRcYWxwaGEkIGFuZCAkXGJldGEkIC0gb3IgcHV0IGFub3RoZXIgd2F5LCB3ZSBjYW4gdXNlIHRoZSByZWdyZXNzaW9uIG1vZGVsIHRvIGNhbGlicmF0ZSBvdXIgcGFyYW1ldGVycy4NCg0KU28sIGxldCdzIGhhdmUgYSBnbyBhdCBkb2luZyBpdCEhDQoNCkl0J3MgdmVyeSBzdHJhaWdodCBmb3J3YXJkIHRvIHJ1biBhIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCBpbiBSIHVzaW5nIHRoZSBgZ2xtYCAoR2VuZXJhbGlzZWQgTGluZWFyIE1vZGVscykgZnVuY3Rpb24uIEluIHByYWN0aWNhbCB0ZXJtcywgcnVubmluZyBhIEdMTSBtb2RlbCBpcyBubyBkaWZmZXJlbnQgdG8gcnVubmluZyBhIHN0YW5kYXJkIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgYGxtYC4gSWYgeW91IHdhbnQgdG8gZmluZCBvdXQgbW9yZSBhYm91dCBnbG0sIHRyeSB0aGUgUiBoZWxwIHN5c3RlbSBgP2dsbWAgb3IgZ29vZ2xlIGl0IHRvIGZpbmQgdGhlIGZ1bmN0aW9uIGRldGFpbHMuIElmIHlvdSBkZWx2ZSBmYXIgZW5vdWdoIGludG8gdGhlIGRlcHRocyBvZiB3aGF0IEdMTSBkb2VzLCB5b3Ugd2lsbCBmaW5kIHRoYXQgdGhlIHBhcmFtZXRlcnMgYXJlIGNhbGlicmF0ZWQgdGhvdWdoIGFuICoqJ2l0ZXJhdGl2ZWx5IHJlLXdlaWdodGVkIGxlYXN0IHNxdWFyZXMnIGFsZ29yaXRobSoqLiBUaGlzIGFsZ29yaXRobSBkb2VzIGV4YXh0bHkgdGhlIHNvcnQgb2Ygam9iIEkgZGVzY3JpYmVkIGVhcmxpZXIsIGl0IGZpdHMgbG90cyBvZiBsaW5lcyB0byB0aGUgZGF0YSwgY29udGludWFsbHkgYWRqdXN0aW5nIHRoZSBwYXJhbWV0ZXJzIGFuZCB0aGVuIHNlZWluZyBpZiBpdCBjYW4gbWluaW1pc2UgdGhlIGVycm9yIGJldHdlZW4gdGhlIG9ic2VydmVkIGFuZCBleHBlY3RlZCB2YWx1ZXMgdXNlaW5nIHNvbWUgZ29vZG5lc3Mtb2YtZml0IG1lYXN1cmUgaXMgbWF4aW1pc2VkL21pbmltaXNlZC4NCg0KVGhlc2Ugc29ydHMgb2YgYWxnb3JpdGhtcyBoYXZlIGJlZW4gYXJvdW5kIGZvciB5ZWFycyBhbmQgYXJlIHZlcnkgd2VsbCBlc3RhYmxpc2hlZCBzbyBpdCBtYWtlcyBzZW5zZSB0byBtYWtlIHVzZSBvZiB0aGVtIHJhdGhlciB0aGFuIHRyeWluZyB0byByZS1pbnZlbnQgdGhlIHdoZWVsIG91cnNlbHZlcy4gU28gaGVyZSB3ZSBnby4uLg0KDQotICAgV2UgYXJlIHVzaW5nIGdsbSB0byBlc3RpbWF0ZSB0aGUgdmFsdWVzIG9mIG11LCBhbHBoYSBhbmQgYmV0YQ0KDQpgYGB7cn0NCiMgUnVuIHRoZSB1bmNvbnN0cmFpbmVkIG1vZGVsDQp1bmNvbl9zaW0gPC0gZ2xtKEZsb3cgfiBsb2codmkxX29yaWdwb3ApICsgbG9nKHdqM19kZXN0bWVkaW5jKSArIGxvZyhkaXN0KSwgZmFtaWx5ID0gcG9pc3NvbihsaW5rID0gImxvZyIpLCBuYS5hY3Rpb24gPSBuYS5leGNsdWRlLCBkYXRhID0gbWRhdGFzdWIpDQpgYGANCg0KSXQncyBhIHNpbXBsZSBhcyB0aGF0IC0gcnVucyBpbiBhIG1hdHRlciBvZiBtaWxsaXNlY29uZHMuIFlvdSBzaG91bGQgYmUgYWJsZSB0byBzZWUgaG93IHRoZSBgZ2xtYCBSIGNvZGUgY29ycmVzcG9uZHMgdG8gRXF1YXRpb24gNi4NCg0KYFRvdGFsYCA9ICRUX3tpan0kID0gJFxsYW1iZGF7aWp9JA0KDQpgfmAgbWVhbnMgJ2lzIG1vZGVsbGVkIGJ5Jw0KDQpgbG9nKHZpMV9vcmlncG9wKWAgPSAkbG57Vl9pfSQNCg0KYGxvZyh3ajJfZGVzdHNhbClgID0gJGxue1dfan0kDQoNCmBsb2coZGlzdClgID0gJGxuZF97aWp9JA0KDQpgZmFtaWx5ID0gcG9pc3NvbihsaW5rID0gImxvZyIpYCBtZWFucyB0aGF0IHdlIGFyZSB1c2luZyBhIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCAodGhlIGxpbmsgaXMgYWx3YXlzIGxvZyB3aXRoIGEgUG9pc3NvbiBtb2RlbCkgd2hlcmUgdGhlIGxlZnQtaGFuZCBzaWRlIG9mIHRoZSBtb2RlbCBlcXVhdGlvbiBpcyBsb2dhcml0aG1pY2FsbHkgbGlua2VkIHRvIHRoZSB2YXJpYWJsZXMgb24gdGhlIHJpZ2h0LWhhbmQgc2lkZS4NCg0KLSAgIFRyeSBhbmQgZG8gdGhpcyBpbiBUaWR5bW9kZWxzDQoNClNvIHdoYXQgY29tZXMgb3V0IG9mIHRoZSBvdGhlciBlbmQ/DQoNCldlbGwsIHdlIGNhbiB1c2UgdGhlIGBzdW1tYXJ5KClgIGZ1bmN0aW9uIHRvIGhhdmUgYSBsb29rIGF0IHRoZSBtb2RlbCBwYXJhbWV0ZXJzOg0KDQpgYGB7cn0NCnN1bW1hcnkodW5jb25fc2ltKQ0KYGBgDQoNCldlIGNhbiBzZWUgZnJvbSB0aGUgc3VtbWFyeSB0aGF0IHRoZSBQb2lzc29uIHJlZ3Jlc3Npb24gaGFzIGNhbGlicmF0ZWQgYWxsIDQgcGFyYW1ldGVycyBmb3IgdXMgYW5kIHRoZXNlIGFwcGVhciB1bmRlciB0aGUgJ2VzdGltYXRlJyBjb2x1bW46DQoNClwka1wkIChpbnRlcmNlcHQpID0gNy4xOTUzNzkwDQoNCiRcbXUkID0gMC41OTAzMzYzDQoNCiRcYWxwaGEkID0gLTAuMTY3MTQxNw0KDQphbmQgJFxiZXRhJCA9IC0wLjgxMTkzMTYNCg0KV2UgY2FuIGFsc28gc2VlIGZyb20gdGhlIG90aGVyIG91dHB1dHMgdGhhdCBhbGwgdmFyaWFibGVzIGFyZSBoaWdobHkgc2lnbmlmaWNhbnQgKFwqXCpcKiksIHdpdGggdGhlIHotc2NvcmVzIHJldmVhbGluZyB0aGF0IGRpc3RhbmNlIGhhcyB0aGUgbW9zdCBpbmZsdWVuY2Ugb24gdGhlIG1vZGVsIChhcyB3ZSBtaWdodCBoYXZlIGV4cGVjdGVkIGZyb20gdGhlIHNjYXR0ZXIgcGxvdHMgd2UgcHJvZHVjZWQgZWFybGllciB3aGljaCBzaG93ZWQgdGhhdCBkaXN0YW5jZSBoYWQgYnkgZmFyIHRoZSBzdHJvbmdlc3QgY29ycmVsYXRpb24gd2l0aCBtaWdyYXRpb24gZmxvd3MpLiAtLS0gP3lvdSB0YWtlIGFic29sdXRlIHZhbHVlcyBvZiB6Pw0KDQpUaGVzZSBwYXJhbWV0ZXJzIGFyZSBub3QgdG9vIGZhciBhd2F5IGZyb20gb3VyIGluaXRpYWwgZ3Vlc3NlcyBvZiDOvM68ID0gMSwgzrHOsSA9IDEgYW5kIM6yzrIgPSAtMiwgYnV0IGhvdyBkbyB0aGUgZXN0aW1hdGVzIGNvbXBhcmU/DQoNCk9uZSB3YXkgdG8gY2FsY3VsYXRlIHRoZSBlc3RpbWF0ZXMgaXMgdG8gcGx1ZyBhbGwgb2YgdGhlIHBhcmFtZXRlcnMgYmFjayBpbnRvIEVxdWF0aW9uIDYgbGlrZSB0aGlzOg0KDQpgYGB7cn0NCm1kYXRhc3ViIDwtIG1kYXRhc3ViICU+JSANCiAgbXV0YXRlKHVuY29uc3RyYWluZWRFc3QyID0gcm91bmQoZml0dGVkKHVuY29uX3NpbSksIDApKSANCg0Kc3VtKG1kYXRhc3ViJHVuY29uc3RyYWluZWRFc3QyKQ0KYGBgDQoNCmBgYHtyfQ0KIyBUdXJuIGl0IGludG8gYSBsaXR0bGUgbWF0cml4IGFuZCBoYXZlIGEgbG9vayBhdCB5b3VyIGhhbmR5IHdvcmsNCm1kYXRhc3VibWF0MiA8LSBtZGF0YXN1YiAlPiUgDQogIHNlbGVjdChPcmlnX2NvZGUsIERlc3RfY29kZSwgdW5jb25zdHJhaW5lZEVzdDIpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IERlc3RfY29kZSwgdmFsdWVzX2Zyb20gPSB1bmNvbnN0cmFpbmVkRXN0MiwgdmFsdWVzX2ZpbGwgPSAwKSAlPiUNCiAgIHJlbG9jYXRlKCIxR1NZRCIsIC5hZnRlciA9IE9yaWdfY29kZSkgJT4lIA0KICByb3d3aXNlKE9yaWdfY29kZSkgJT4lIA0KICBtdXRhdGUodG90YWxfZmxvd19mcm1fYXJlYSA9IHN1bShjX2Fjcm9zcyh3aGVyZShpcy5udW1lcmljKSkpICkgDQoNCm1kYXRhc3VibWF0Mg0KYGBgDQoNCkFuZCB0aGUgXCQxLDAwMCwwMDAgcXVlc3Rpb24gLSBoYXMgY2FsaWJyYXRpbmcgdGhlIHBhcmFtZXRlcnMgaW1wcm92ZWQgdGhlIG1vZGVsLi4uPw0KDQpgYGB7cn0NCiMgRXZhbHVhdGUgUl4yDQpyc3FyZCh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkdW5jb25zdHJhaW5lZEVzdDIpDQoNCiMgRXZhbHVhdGUgUk1TRQ0Kck1TRSh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkdW5jb25zdHJhaW5lZEVzdDIpDQpgYGANCg0KWWVzIGluZGVlZHkgZG8hIQ0KDQpUaGUgJHJeMiQgaGFzIGltcHJvdmVkIGZyb20gMC4yMCB0byAwLjMyIGFuZCB0aGUgUk1TRSBoYXMgcmVkdWNlZCBmcm9tIDI1ODU4LjExIHRvIDEwNzg5LjE3IHNvIGJ5IGNhbGlicmF0aW5nIG91ciBwYXJhbWV0ZXJzIHVzaW5nIHRoZSBQb2lzc29uIFJlZ3Jlc3Npb24gTW9kZWwsIHdlIGhhdmUgbWFya2VkbHkgaW1wcm92ZWQgb3VyIG1vZGVsIGZpdC4NCg0KQnV0IHdlIGNhbiBkbyBldmVuIGJldHRlci4gV2UgaGF2ZSBqdXN0IGJlZW4gcGxheWluZyB3aXRoIHRoZSB1bmNvbnN0cmFpbmVkIG1vZGVsLCBieSBhZGRpbmcgY29uc3RyYWludHMgaW50byB0aGUgbW9kZWwgd2UgY2FuIGJvdGggaW1wcm92ZSBvdXIgZml0IGZ1cnRoZXIgQU5EIHN0YXJ0IHRvIGRvIGNvb2wgdGhpbmdzIGxpa2UgZXNpbWF0ZSB0cmFuc3BvcnQgdHJpcCBkaXN0cmlidXRpb25zIGZyb20ga25vdyBpbmZvcm1hdGlvbiBhYm91dCBwZW9wbGUgbGVhdmluZyBhbiBhcmVhLCBvciBpbiBkaWZmZXJlbnQgY29udGV4dHMgZXN0aW1hdGUgdGhlIGFtb3VudCBvZiBtb25leSBhIHNob3AgaXMgZ29pbmcgdG8gbWFrZSBmcm9tIHRoZSBhdmFpbGFibGUgbW9uZXkgdGhhdCBwZW9wbGUgaW4gdGhlIHN1cnJvdW5kaW5nIGFyZWEgaGF2ZSB0byBzcGVuZCwgb3IgZ3Vlc3MgdGhlIG51bWJlciBvZiBtaWdyYW50cyB0cmF2ZWxsaW5nIGJldHdlZW4gc3BlY2lmaWMgY291bnRyaWVzIHdoZXJlIHdlIG9ubHkga25vdyBob3cgbWFueSBwZW9wbGUgaW4gdG90YWwgbGVhdmUgb25lIGNvdW50cnkgYW5kIGFycml2ZSBpbiBhbm90aGVyLg0KDQojIyAqKlNlY3Rpb24gMiAtIENvbnN0cmFpbmVkIE1vZGVscyoqDQoNCklmIHdlIHJldHVybiB0byBbQWxhbiBXaWxzb24ncyAxOTcxIHBhcGVyXShodHRwOi8vam91cm5hbHMuc2FnZXB1Yi5jb20vZG9pL2Ficy8xMC4xMDY4L2EwMzAwMDEpLCBoZSBpbnRyb2R1Y2VzIGEgZnVsbCAqZmFtaWx5KiBvZiBzcGF0aWFsIGludGVyYWN0aW9uIG1vZGVscyBvZiB3aGljaCB0aGUgdW5jb25zdHJhaW5lZCBtb2RlbCBpcyBqdXN0IHRoZSBzdGFydC4gQW5kIGluZGVlZCBzaW5jZSB0aGVuLCB0aGVyZSBoYXZlIGJlZW4gYWxsIG51bWJlciBvZiBpbmNyZW1lbnRhbCBhZHZhbmNlcyBhbmQgYWx0ZXJuYXRpdmVzIChzdWNoIGFzIFtTdGV3YXJ0IEZvdGhlcmluZ2hhbSdzIENvbXBldGluZyBEZXN0aW5hdGlvbnMgbW9kZWxzXShodHRwczovL3d3dy5yZXNlYXJjaGdhdGUubmV0L3B1YmxpY2F0aW9uLzIzNTM3MTE3X0FfTmV3X1NldF9vZl9TcGF0aWFsLUludGVyYWN0aW9uX01vZGVsc19UaGVfVGhlb3J5X29mX0NvbXBldGluZ19EZXN0aW5hdGlvbnMpLCBbUG9vbGVyJ3MgcHJvZHVjdGlvbi9hdHRyYWN0aW9uL2Nvc3QgcmVsYXhlZCBtb2RlbHNdKGh0dHA6Ly9qb3VybmFscy5zYWdlcHViLmNvbS9kb2kvYWJzLzEwLjExNzcvMDMwOTEzMjU5NDAxODAwMTAyKSwgW1N0aWxsd2VsbCdzIG9yaWdpbi9kZXN0aW5hdGlvbiBwYXJhbWV0ZXIgc3BlY2lmaWMgbW9kZWxzXShodHRwOi8vam91cm5hbHMuc2FnZXB1Yi5jb20vZG9pL3BkZi8xMC4xMDY4L2ExMDExODcpIGFuZCBbbWluZSBhbmQgQWxhbidzIG93biBtdWx0aS1sZXZlbCBtb2RlbF0oaHR0cDovL2pvdXJuYWxzLnNhZ2VwdWIuY29tL2RvaS9wZGYvMTAuMTA2OC9hNDUzOTgpICh0byBuYW1lIGp1c3QgYSBmZXcpLg0KDQpJbiB0aGlzIHNlY3Rpb24gd2Ugd2lsbCBleHBsb3JlIHRoZSByZXN0IG9mIFdpbHNvbidzIGZhbWlseSAtIHRoZSBQcm9kdWN0aW9uIChvcmlnaW4pIENvbnN0cmFpbmVkIE1vZGVsOyB0aGUgQXR0cmFjdGlvbiAoZGVzdGluYXRpb24pIGNvbnN0cmFpbmVkIG1vZGVsOyBhbmQgdGhlIERvdWJseSBDb25zdHJhaW5lZCBNb2RlbC4NCg0KV2Ugd2lsbCBzZWUgaG93IHdlIGNhbiwgYWdhaW4sIHVzZSBhIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCBpbiBSIHRvIGNhbGlicmF0ZSB0aGVzZSBtb2RlbHMgYW5kIGhvdywgb25jZSBjYWxpYnJhdGVkLCB3ZSBjYW4gdXNlIHRoZSBtb2RlbHMgaW4gZGlmZmVyZW50IGNvbnRleHRzLCBzdWNoIGFzIExhbmQgVXNlIFRyYW5zcG9ydGF0aW9uIEludGVyYWN0aW9uIChMVVRJKSBtb2RlbGxpbmcsIHJldGFpbCBtb2RlbGxpbmcgYW5kIG1pZ3JhdGlvbiBtb2RlbGxpbmcuDQoNCiMjICoqMS4gUHJvZHVjdGlvbiBhbmQgQXR0cmFjdGlvbiBDb25zdHJhaW5lZCBNb2RlbHMqKg0KDQpXaWxzb24ncyByZWFsIGNvbnRyaWJ1dGlvbiB0byB0aGUgZmllbGQgd2FzIGluIG5vdGljaW5nIHRoYXQgdGhlIHVuY29uc3RyYWluZWQgZ3Jhdml0eSBtb2RlbCB3YXMgc3ViLW9wdGltYWwgYXMgaXQgZGlkIG5vdCBtYWtlIHVzZSBvZiBhbGwgb2YgdGhlIGF2YWlsYWJsZSBkYXRhIGluIHRoZSBzeXN0ZW0gd2UgYXJlIHN0dWR5aW5nLg0KDQpJZiB3ZSByZWNhbGwgdGhlIGVzdGltYXRlcyBmcm9tIG91ciB1bmNvbnN0cmFpbmVkIG1vZGVsLCBub25lIG9mIHRoZSBlc3RpbWF0ZXMgc3VtbWVkIHRvIHRoZSBvYnNlcnZlZCBpbiBhbmQgb3V0LWZsb3cgdG90YWxzOg0KDQpPdXIgZXN0aW1hdGVzIGRpZCBzdW0gdG8gdGhlIGdyYW5kIHRvdGFsIG9mIGZsb3dzLCBidXQgdGhpcyBpcyBiZWNhdXNlIHdlIHdlcmUgcmVhbGx5IGZpdHRpbmcgYSAndG90YWwgY29uc3RyYWluZWQnIG1vZGVsIHdoaWNoIHVzZWQga2sgLSBvdXIgY29uc3RhbnQgb2YgcHJvcG9ydGlvbmFsaXR5IC0gdG8gZW5zdXJlIGV2ZXJ5dGhpbmcgc29ydCBvZiBhZGRlZCB1cCAodG8gd2l0aGluIDEgY29tbXV0ZXIpLg0KDQpXaGVyZSB3ZSBoYXZlIGEgZnVsbCBmbG93IG1hdHJpeCB0byBjYWxpYnJhdGUgcGFyYW1ldGVycywgd2UgY2FuIGluY29ycG9yYXRlIHRoZSByb3cgKG9yaWdpbikgdG90YWxzLCBjb2x1bW4gKGRlc3RpbmF0aW9uKSB0b3RhbHMgb3IgYm90aCBvcmlnaW5hIGFuZCBkZXN0aW5hdGlvbiB0b3RhbHMgdG8gKmNvbnN0cmFpbiogb3VyIGZsb3cgZXN0aW1hdGVzIHRvIHRoZXNlIGtub3duIHZhbHVlcy4NCg0KQXMgSSBvdXRsaW5lIGluIHRoZSBhY2NvbXBhbnlpbmcgcGFwZXIsIHRoZXJlIGFyZSB2YXJpb3VzIHJlYXNvbnMgZm9yIHdhbnRpbmcgdG8gZG8gdGhpcywgZm9yIGV4YW1wbGU6DQoNCjEuICBJZiBXZSBhcmUgaW50ZXJlc3RlZCBpbiBmbG93cyBvZiBtb25leSBpbnRvIGJ1c2luZXNzZXMgb3IgY3VzdG9tZXJzIGludG8gc2hvcHMsIG1pZ2h0IGhhdmUgaW5mb3JtYXRpb24gb24gdGhlIGFtb3VudCBvZiBkaXNwb3NhYmxlIGluY29tZSBhbmQgc2hvcHBpbmcgaGFiaXRzIG9mIHRoZSBwZW9wbGUgbGl2aW5nIGluIGRpZmZlcmVudCBhcmVhcyBmcm9tIGxveWFsdHkgY2FyZCBkYXRhLiBUaGlzIGlzIGtub3duIGluZm9ybWF0aW9uIGFib3V0IG91ciBvcmlnaW5zIGFuZCBzbyB3ZSBjb3VsZCAqY29uc3RyYWluKiBvdXIgc3BhdGlhbCBpbnRlcmFjdGlvbiBtb2RlbCB0byB0aGlzIGtub3duIGluZm9ybWF0aW9uIC0gd2UgY2FuIG1ha2UgdGhlIGFzc3VtcHRpb24gdGhhdCB0aGlzIGxldmVsIG9mIGRpc3Bvc2FibGUgaW5jb21lIHJlbWFpbnMgdGhlIHNhbWUuIFdlIGNhbiB0aGVuIHVzZSBvdGhlciBpbmZvcm1hdGlvbiBhYm91dCB0aGUgYXR0cmFjdGl2ZW5lc3Mgb2YgcGxhY2VzIHRoZXNlIHBlb3BsZSBtaWdodCBsaWtlIHRvIHNob3AgaW4gKHN0b3JlIHNpemUsIHZhcmlldHkgLyBzcGVjaWFsaXNtIG9mIGdvb2RzIGV0Yy4pLCB0byBlc3RpbWF0ZSBob3cgbXVjaCBtb25leSBhIG5ldyBzdG9yZSBvcGVuaW5nIGluIHRoZSBhcmVhIG1pZ2h0IG1ha2UsIG9yIGlmIGEgbmV3IG91dC1vZi10b3duIHNob3BwaW5nIGNlbnRyZSBvcGVucywgaG93IG11Y2ggaXQgbWlnaHQgYWZmZWN0IHRoZSBidXNpbmVzcyBvZiBzaG9wcyBpbiB0aGUgdG93biBjZW50cmUuIFRoaXMgaXMgd2hhdCBpcyBrbm93biBpbiB0aGUgbGl0ZXJhdHVyZSBhcyB0aGUgJ3JldGFpbCBtb2RlbCcgYW5kIGlzIHBlcmhhcHMgdGhlIG1vc3QgY29tbW9uIGV4YW1wbGUgb2YgYSAqKlByb2R1Y3Rpb24gKG9yaWduKSBDb25zdHJhaW5lZCBTcGF0aWFsIEludGVyYWN0aW9uIE1vZGVsKioNCg0KMi4gIFdlIG1pZ2h0IGJlIGludGVyZXN0ZWQgaW4gdW5kZXJzdGFuZGluZyB0aGUgaW1wYWN0IG9mIGEgbGFyZ2UgbmV3IGVtcGxveWVyIGluIGFuIGFyZWEgb24gdGhlIGZsb3dzIG9mIHRyYWZmaWMgaW4gdGhlIHZpY2luaXR5IG9yIG9uIHRoZSBkZW1hbmQgZm9yIG5ldyB3b3JrZXIgYWNjb21tb2RhdGlvbiBuZWFyYnkuIEEgZ29vZCBleGFtcGxlIG9mIHdoZXJlIHRoaXMgbWlnaHQgYmUgdGhlIGNhc2UgaXMgd2l0aCBsYXJnZSBuZXcgaW5mcmFzdHJ1Y3R1cmUgZGV2ZWxvcG1lbnRzIGxpa2UgbmV3IGFpcnBvcnRzLiBGb3IgZXhhbXBsZSwgYmVmb3JlIHRoZSBnby1haGVhZCBmb3IgdGhlIG5ldyB0aGlyZCBydW53YXkgYXQgSGVhdGhyb3cgd2FzIGdpdmVuLCBvbmUgb3B0aW9uIGJlaW5nIGNvbnNpZGVyZWQgd2FzIGEgbmV3IHJ1bndheSBpbiB0aGUgVGhhbWVzIEVzdHVhcnkuIElmIGEgbmV3IGFpcnBvcnQgd2FzIGJ1aWx0IGhlcmUsIHdoYXQgd291bGQgYmUgdGhlIHBvdGVudGlhbCBpbXBhY3Qgb24gdHJhbnNwb3J0IGZsb3dzIGluIHRoZSBhcmVhIGFuZCB3aGVyZSBtaWdodCB3b3JrZXJzIGNvbW11dGUgZnJvbT8gVGhpcyBzb3J0IG9mIHNjZW5hcmlvIGNvdWxkIGJlIHRlc3RlZCB3aXRoIGFuICoqQXR0cmFjdGlvbiAoZGVzdGluYXRpb24pIENvbnN0cmFpbmVkIFNwYXRpYWwgSW50ZXJhY3Rpb24gTW9kZWwqKiB3aGVyZSB0aGUgbnVtYmVyIG9mIG5ldyBqb2JzIGluIGEgZGVzdGluYXRpb24gaXMga25vd24gKGFzIHdlbGwgYXMgam9icyBpbiB0aGUgc3Vycm91bmRpbmcgYXJlYSkgYW5kIHRoZSBtb2RlbCBjb3VsZCBiZSB1c2VkIHRvIGVzdGltYXRlIHdoZXJlIHRoZSB3b3JrZXJzIHdpbGwgYmUgZHJhd24gZnJvbSAoYW5kIHRoZWlyIGxpa2VseSB0cmF2ZWwtdG8td29yayBwYXR0ZXJucykuIFRoaXMgbW9kZWwgaXMgZXhhY3RseSB0aGUgc29ydCBvZiBtb2RlbCBMYW5kIFVzZSBUcmFuc3BvcnQgSW50ZXJhY3Rpb24gKExVVEkpIG1vZGVsIHRoYXQgd2FzIGNvbnN0cnVjdGVkIGJ5IHRoZSBNZWNoYW5pY2l0eSBUZWFtIGluIENBU0EgLSBkZXRhaWxzIFtoZXJlXShodHRwOi8vd3d3Lm1lY2hhbmljaXR5LmluZm8vcmVzZWFyY2gvbGFuZC11c2UtdHJhbnNwb3J0LWludGVyYWN0aW9uLW1vZGVsbGluZy8jdHJhbnNwb3J0KSBpZiB5b3UgYXJlIGludGVyZXN0ZWQuLi4NCg0KMy4gIFdlIG1pZ2h0IGJlIGludGVyZXN0ZWQgaW4gdW5kZXJzdGFuZGluZyB0aGUgY2hhbmdpbmcgcGF0dGVybnMgb2YgY29tbXV0aW5nIG9yIG1pZ3JhdGlvbiBvdmVyIHRpbWUuIERhdGEgZnJvbSB0aGUgQ2Vuc3VzIGFsbG93cyB1cyB0byBrbm93IGFuIGFjY3VyYXRlIHNuYXAtc2hvdCBvZiBtaWdyYXRpbmcgYW5kIGNvbW11dGluZyBwYXR0ZXJucyBldmVyeSAxMCB5ZWFycy4gSW4gdGhlc2UgZnVsbCBkYXRhIG1hdHJpY2VzLCB3ZSBrbm93IGJvdGggdGhlIG51bWJlcnMgb2YgY29tbXV0ZXJzL21pZ3JhbnRzIGxlYXZpbmcgb3JpZ2lucyBhbmQgYXJyaXZpbmcgYXQgZGVzdGluYXRpb25zIGFzIHdlbGwgYXMgdGhlIGludGVyYWN0aW9ucyBiZXR3ZWVuIHRoZW0uIElmIHdlIGNvbnN0cmFpbiBvdXIgbW9kZWwgZXN0aW1hdGVzIHRvIHRoaXMga25vd24gaW5mb3JtYXRpb24gYXQgb3JpZ2luIGFuZCBkZXN0aW5hdGlvbiwgd2UgY2FuIGV4YW1pbmUgdmFyaW91cyB0aGluZ3MsIGluY2x1ZGluZzoNCg0KICAgIDEuICB0aGUgd2F5cyB0aGF0IHRoZSBwYXR0ZXJucyBvZiBjb21tdXRpbmcvbWlncmF0aW9uIGRpZmZlciBmcm9tIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAtIHdoZXJlIHdlIG1pZ2h0IGdldCBtb3JlIG1pZ3JhbnQvY29tbXV0ZXIgZmxvd3MgdGhhbiB3ZSB3b3VsZCBleHBlY3QNCg0KICAgIDIuICBob3cgdGhlIG1vZGVsIHBhcmFtZXRlcnMgdmFyeSBvdmVyIHRpbWUgLSBmb3IgZXhhbXBsZSBob3cgZG9lcyBkaXN0YW5jZSAvIGNvc3Qgb2YgdHJhdmVsIGFmZmVjdCBmbG93cyBvdmVyIHRpbWU/IEFyZSBwZW9wbGUgcHJlcGFyZWQgdG8gdHJhdmVsIGZ1cnRoZXIgb3IgbGVzcyBmYXIgdGhhbiBiZWZvcmU/DQoNCiMjICoqMi4gUHJvZHVjdGlvbi1jb25zdHJhaW5lZCBNb2RlbCoqDQoNCjEuICAkVF97aWp9ID0gQV9pIE9faSBXX2peXGFscGhhIGRfe2lqfV4tXGJldGEkDQoNCndoZXJlDQoNCiRPX3tpfSA9IFxzdW1faiBUX3tpan0kDQoNCmFuZA0KDQokQV9pID0gXGZyYWN7MX17XHN1bV9qIFdfal5cYWxwaGEgZF97aWp9Xi1cYmV0YX0kDQoNCkluIHRoZSBwcm9kdWN0aW9uLWNvbnN0cmFpbmVkIG1vZGVsLCBPaU9pIGRvZXMgbm90IGhhdmUgYSBwYXJhbWV0ZXIgYXMgaXQgaXMgYSBrbm93biBjb25zdHJhaW50LiBBaUFpIGlzIGtub3duIGFzIGEgKmJhbGFuY2luZyBmYWN0b3IqIGFuZCBpcyBhIHZlY3RvciBvZiB2YWx1ZXMgd2hpY2ggcmVsYXRlIHRvIGVhY2ggb3JpZ2luLCBpaSwgd2hpY2ggZG8gdGhlIGVxdWl2YWxlbnQgam9iIHRvIGtrIGluIHRoZSB1bmNvbnN0cmFpbmVkL3RvdGFsIGNvbnN0cmFpbmVkIG1vZGVsIGJ1dCBlbnN1cmUgdGhhdCBmbG93IGVzdGltYXRlcyBmcm9tIGVhY2ggb3JpZ2luIHN1bSB0byB0aGUga25vdyB0b3RhbHMsIE9pT2kgcmF0aGVyIHRoYW4ganVzdCB0aGUgb3ZlcmFsbCB0b3RhbC4NCg0KTm93IGF0IHRoaXMgcG9pbnQsIHdlIGNvdWxkIGNhbGN1bGF0ZSBhbGwgb2YgdGhlIE9pT2lzIGFuZCB0aGUgQWlBaXMgYnkgaGFuZCBmb3Igb3VyIHNhbXBsZSBzeXN0ZW0gYW5kIHRoZW4gc2V0IGFib3V0IGd1ZXNzaW5nL2VzdGltYXRpbmcgdGhlIHBhcmFtZXRlciB2YWx1ZXMgZm9yIHRoZSByZXN0IG9mIHRoZSBtb2RlbCwgYnV0IGFzIHlvdSBtaWdodCBoYXZlIGFscmVhZHkgc3VzcGVjdGVkIGZyb20gbGFzdCB0aW1lLCB3ZSBjYW4gdXNlIFIgYW5kIGBnbG1gIHRvIG1ha2UgaXQgcmVhbGx5IGVhc3kgYW5kIGRvIGFsbCBvZiB0aGF0IGZvciB1cyAtIHdvbyBob28hDQoNCldlIHNldCBhYm91dCByZS1zcGVjaWZ5aW5nIHRoZSBQcm9kdWN0aW9uLUNvbnN0cmFpbmVkIG1vZGVsIGFzIGEgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsIGluIGV4YWN0bHkgdGhlIHNhbWUgd2F5IGFzIHdlIGRpZCBiZWZvcmUuIFdlIG5lZWQgdG8gdGFrZSBsb2dzIG9mIHRoZSByaWdodC1oYW5kIHNpZGUgb2YgZXF1YXRpb24gYW5kIGFzc3VtZSB0aGF0IHRoZXNlIGFyZSBsb2dhcml0aG1pYWxseSBsaW5rZWQgdG8gdGhlIFBvaXNzb24gZGlzdHJpYnV0ZWQgbWVhbiAozrtpas67aWopIG9mIHRoZSBUaWpUaWogdmFyaWFibGUuIEFzIHN1Y2gsIEVxdWF0aW9uICgxKSBiZWNvbWVzOg0KDQo0LiAgJFxsYW1iZGFfe2lqfSA9IGV4cChcbXVfe2l9ICsgXGFscGhhIFxsbiBXX2ogLSBcYmV0YSBcbG4gZF97aWp9KSQNCg0KSW4gRXF1YXRpb24gKDQpIM68ac68aSBpcyB0aGUgZXF1aXZhbGVudCBvZiB0aGUgdmVjdG9yIG9mIGJhbGFuY2luZyBmYWN0b3JzIEFpQWksIGJ1dCBpbiByZWdyZXNzaW9uIC8gbG9nLWxpbmVhciBtb2RlbGxpbmcgdGVybWlub2xvZ3kgY2FuIGFsc28gYmUgZGVzY3JpYmVkIGFzIGVpdGhlciAqKmR1bW15IHZhcmlhYmxlcyoqIG9yICoqZml4ZWQgZWZmZWN0cyoqLiBJbiBwcmFjdGljYWwgdGVybXMsIHdoYXQgdGhpcyBtZWFucyBpcyB0aGF0IGluIG91ciByZWdyZXNzaW9uIG1vZGVsLCDOvGnOvGkgaXMgbW9kZWxsZWQgYXMgYSBbY2F0ZWdvcmljYWwgcHJlZGljdG9yXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9DYXRlZ29yaWNhbF92YXJpYWJsZSkgYW5kIHRoZXJlZm9yZSBpbiB0aGUgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsLCB3ZSBkb24ndCB1c2UgdGhlIG51bWVyaWMgdmFsdWVzIG9mIE9pT2ksIHdlIHVzZSBhIGNhdGVnb3JpY2FsIGlkZW50aWZpZXIgZm9yIHRoZSBvcmlnaW4uIEluIHRlcm1zIG9mIHRoZSBvcmlnaW4vZGVzdGluYXRpb24gbWlncmF0aW9uIG1hdHJpeCBzaG93biBpbiBUYWJsZSAzLCByYXRoZXIgdGhhbiB0aGUgZmxvdyBvZiAyMDQsODI4IG1pZ3JhbnRzIGxlYXZpbmcgU3lkbmV5IChyb3cgMSkgYmVpbmcgdXNlZCBhcyBhIHByZWRpY3Rvciwgc2ltcGx5IHRoZSBjb2RlICcxR1NZRCcgaXMgdXNlZCBhcyBhIGR1bW15IHZhcmlhYmxlLg0KDQpCZWZvcmUgZ2l2aW5nIGl0IGEgd2hpcmwsIGl0J3MgaW1wb3J0YW50IHRvIG5vdGUgaW4gdGhlIGNvZGUgZXhhbXBsZSBiZWxvdyB0aGUgdXNlIG9mICctMScgYWZ0ZXIgdGhlIGRpc3RhbmNlIHZhcmlhYmxlICh0aGFua3MgdG8gSGFkcmllbiBTYWxhdCBpbiBDQVNBIGZvciBicmluZ2luZyB0aGlzIHRvIG15IGF0dGVudGlvbikuIFRoZSAtMSBzZXJ2ZXMgdGhlIHB1cnBvc2Ugb2YgcmVtb3ZpbmcgdGhlIGludGVyY2VwdCB0aGF0IGJ5IGRlZmF1bHQsIEdMTSB3aWxsIGluc2VydCBpbnRvIHRoZSBtb2RlbC4gQXMgd2FzIG1lbnRpb25lZCBlYXJsaWVyLCB0aGUgdmVjdG9yIG9mIG9yaWdpbiBwYXJhbWV0ZXJzIHdpbGwgcmVwbGFjZSB0aGUgaW50ZXJjZXB0IGluIHRoaXMgbW9kZWwuIEl0IGFsc28gc2VydmVzIHRoZSBwdXJwb3NlDQoNCmBgYHtyfQ0KIyBSdW4gdGhlIHByb2R1Y3Rpb24gY29uc3RyYWluZWQgU0lNICh0aGUgIi0xIiBpbmRpY2F0ZXMgbm8gaW50ZXJjZXB0IGluIHRoZSByZWdyZXNzaW9uIG1vZGVsKQ0KcHJvZFNpbSA8LSBnbG0oRmxvdyB+IE9yaWdfY29kZSArIGxvZyh3ajNfZGVzdG1lZGluYykgKyBsb2coZGlzdCkgLSAxLCBuYS5hY3Rpb24gPSBuYS5leGNsdWRlLCBmYW1pbHkgPSBwb2lzc29uKGxpbmsgPSAibG9nIiksIGRhdGEgPSBtZGF0YXN1YikNCg0KIyBTdW1tYXJ5IG9mIG1vZGVsDQpzdW1tYXJ5KHByb2RTaW0pDQpgYGANCg0KU28sIHdoYXQgZG8gd2UgaGF2ZT8NCg0KV2VsbCwgdGhlcmUgYXJlIHRoZSBlbGVtZW50cyBvZiB0aGUgbW9kZWwgb3V0cHV0IHRoYXQgc2hvdWxkIGJlIGZhbWlsaWFyIGZyb20gdGhlIHVuY29uc3RyYWluZWQgbW9kZWw6DQoNCnRoZSDOsSBwYXJhbWV0ZXIgcmVsYXRlZCB0byB0aGUgZGVzdGluYXRpb24gYXR0cmFjdGl2ZW5lc3M6IC0wLjI3MjY0MA0KDQp0aGUgzrIgZGlzdGFuY2UgZGVjYXkgcGFyYW1ldGVyOiAtMS4yMjc2NzkNCg0KV2UgY2FuIHNlZSBmcm9tIHRoZSBzdGFuZGFyZCBvdXRwdXRzIGZyb20gdGhlIG1vZGVsIHRoYXQgYWxsIG9mIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgKFwqXCpcKikgYW5kIHRoZSB6LXNjb3JlcyBpbmRpY2F0ZSB0aGF0IHRoZSBkZXN0aW5hdGlvbiBzYWxhcnkgaXMgaGF2aW5nIHRoZSBtb3N0IGluZmx1ZW5jZSBvbiB0aGUgbW9kZWwsIHdpdGggZGlzdGFuY2UgZm9sbG93aW5nIGNsb3NlbHkgYmVoaW5kLiBBbmQgdGhlbiB3ZSBoYXZlIGEgc2VyaWVzIG9mIHBhcmFtZXRlcnMgd2hpY2ggYXJlIHRoZSB2ZWN0b3Igb2YgzrxpIHZhbHVlcyBhc3NvY2lhdGVkIHdpdGggb3VyIG9yaWdpbiBjb25zdHJhaW50cy4NCg0KIyMjICoqMi4xIE1vZGVsIEVzdGltYXRlcyoqDQoNCk5vdyBhdCB0aGlzIHBvaW50IHlvdSB3aWxsIGJlIHdhbnRpbmcgdG8ga25vdyB3aGF0IGFmZmVjdCB0aGUgY29uc3RyYWludHMgaGF2ZSBoYWQgb24gdGhlIGVzdGltYXRlcyBwcm9kdWNlZCBieSB0aGUgbW9kZWwsIHNvIGxldCdzIHBsdWcgdGhlIHBhcmFtZXRlcnMgYmFjayBpbnRvIEVxdWF0aW9uIDQgYW5kIHRha2UgYSBsb29rLi4uDQoNCkNyZWF0ZSBzb21lIE9pIGFuZCBEaiBjb2x1bW5zIGFuZCBzdG9yZSB0aGUgdG90YWwgaW4gYW5kIG91dCBmbG93IG1hdHJpeCBtYXJnaW5zIGluIHRoZW0uDQoNCi0gICAkT19pJCByZXByZXNlbnRzIHRoZSB0b3RhbCBmbG93cyBmcm9tIGEgZ2l2ZW4gb3JpZ2luDQoNCi0gICAkRF9qJCByZXByZXNlbnRzIHRoZSB0b3RhbCBmbG93cyBnb2luZyB0byBhIHBhcnRpY3VsYXIgZGVzdGluYXRpb24NCg0KYGBge3J9DQojIENyZWF0ZSBzb21lIE9pIGFuZCBEaiBjb2x1bW5zIGluIHRoZSBkYXRhZnJhbWUgYW5kIHN0b3JlIHJvdyBhbmQgY29sdW1uIHRvdGFscyBpbiB0aGVtOg0KbWRhdGFzdWIgPC0gbWRhdGFzdWIgJT4lIA0KICBncm91cF9ieShPcmlnX2NvZGUpICU+JSANCiAgc3VtbWFyaXNlKE9faSA9IHN1bShGbG93KSkgJT4lIA0KICByaWdodF9qb2luKG1kYXRhc3ViKSANCg0KIyBEZXN0aW5hdGlvbiBmbG93cw0KbWRhdGFzdWIgPC0gIG1kYXRhc3ViICU+JSANCiAgZ3JvdXBfYnkoRGVzdF9jb2RlKSAlPiUgDQogIHN1bW1hcmlzZShEX2ogPSBzdW0oRmxvdykpICU+JSANCiAgcmlnaHRfam9pbihtZGF0YXN1YikgJT4lIA0KICBhcnJhbmdlKE9yaWdfY29kZSkgJT4lIA0KICByZWxvY2F0ZShEX2osIC5hZnRlciA9IE9faSkNCmBgYA0KDQpwdWxsIG91dCB0aGUgY29lZmZpY2llbnQgdmFsdWVzIGZvciDOvGkgYW5kIHN0b3JlIHRoZW0gYmFjayBpbiB0aGUgZGF0YWZyYW1lIGFsb25nIHdpdGggT2kgYW5kIERqXA0KDQpgYGB7cn0NCiMgRXh0cmFjdCBjb2VmZmljaWVudHMNCmFscGhhIDwtIHByb2RTaW0kY29lZmZpY2llbnRzWzE2XQ0KYmV0YSA8LSBwcm9kU2ltJGNvZWZmaWNpZW50c1sxN10NCg0KbWRhdGFzdWIgPC0gcHJvZFNpbSAlPiUgDQogIHBsdWNrKCJjb2VmZmljaWVudHMiKSAlPiUgDQogIHRpYmJsZShtdV9pID0gLikgJT4lIA0KICBzbGljZSgxOihuKCkgLSAyKSkgJT4lIA0KICBiaW5kX2NvbHMoZGlzdGluY3QobWRhdGFzdWIsIE9yaWdfY29kZSkpICU+JSANCiAgcmlnaHRfam9pbihtZGF0YXN1YikgJT4lIA0KICByZWxvY2F0ZShtdV9pLCAuYWZ0ZXIgPSBEX2opDQoNCnNsaWNlX2hlYWQobWRhdGFzdWIpDQpgYGANCg0KTm93IGxldHMncyBhZGQgdGhlIGVzdGltYXRlZCBmbG93cyBieSB0aGUgUHJvZHVjdGlvbiBjb25zdHJhaW5lZCBtb2RlbA0KDQpgYGB7cn0NCm1kYXRhc3ViIDwtIG1kYXRhc3ViICU+JSANCiAgbXV0YXRlKHByb2RzaW1GaXR0ZWQgPSByb3VuZChmaXR0ZWQocHJvZFNpbSksIDApKQ0KYGBgDQoNCiMjIyAqKjIuMiBBc3Nlc3NpbmcgdGhlIG1vZGVsIG91dHB1dCoqDQoNClNvIHdoYXQgZG8gdGhlIG91dHB1dHMgZnJvbSBvdXIgUHJvZHVjdGlvbiBDb25zdHJhaW5lZCBNb2RlbCBsb29rIGxpa2U/IEhvdyBoYXMgdGhlIGdvb2RuZXNzLW9mLWZpdCBpbXByb3ZlZCBhbmQgaG93IGNhbiB3ZSBzdGFydCB0byB1c2UgdGhpcyBhIGJpdCBsaWtlIGEgcmV0YWlsIG1vZGVsIGFuZCBhc3Nlc3MgdGhlIGxpa2VseSBpbXBhY3RzIG9mIGNoYW5naW5nIGRlc3RpbmF0aW9uIGF0dHJhY3RpdmVuZXNzIGV0Yy4/DQoNCiMjIyMgKioyLjIuMSBUaGUgZmxvdyBtYXRyaXgqKg0KDQpOb3cgd2UgY2FuIGNyZWF0ZSBwaXZvdCB0YWJsZSB0byB0dXJuIHBhaXJlZCBsaXN0IGludG8gbWF0cml4IChhbmQgY29tcHV0ZSB0aGUgbWFyZ2lucyBhcyB3ZWxsKQ0KDQpgYGB7cn0NCm1kYXRhc3VibWF0MyA8LSBtZGF0YXN1YiAlPiUgDQogIHNlbGVjdChPcmlnX2NvZGUsIERlc3RfY29kZSwgcHJvZHNpbUZpdHRlZCkgJT4lIA0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gRGVzdF9jb2RlLCB2YWx1ZXNfZnJvbSA9IHByb2RzaW1GaXR0ZWQsIHZhbHVlc19maWxsID0gMCkgJT4lIA0KICByZWxvY2F0ZSgiMUdTWUQiLCAuYWZ0ZXIgPSBPcmlnX2NvZGUpICU+JSANCiAgcm93d2lzZShPcmlnX2NvZGUpICU+JSANCiAgbXV0YXRlKHRvdGFsX2Zsb3dfZnJtX2FyZWEgPSBzdW0oY19hY3Jvc3Mod2hlcmUoaXMubnVtZXJpYykpKSApIA0KDQptZGF0YXN1Ym1hdDMNCmBgYA0KDQpBbmQgY29tcGFyZWQgd2l0aCB0aGUgb3JpZ2luYWwgb2JzZXJ2ZWQgZGF0YT8NCg0KYGBge3J9DQptZGF0YXN1Ym1hdA0KYGBgDQoNCkhlcmUgaXQgaXMgdmVyeSBlYXN5IHRvIHNlZSB0aGUgT3JpZ2luIENvbnN0cmFpbnRzIHdvcmtpbmcuIFRoZSBzdW0gYWNyb3NzIGFsbCBkZXN0aW5hdGlvbnMgZm9yIGVhY2ggb3JpZ2luIGluIHRoZSBlc3RpbWF0ZWQgbWF0cml4IGlzIGV4YWN0bHkgdGhlIHNhbWUgYXMgdGhlIHNhbWUgc3VtIGFjcm9zcyB0aGUgb2JzZXJ2ZWQgbWF0cml4IC0gJFxzdW1faiBUX3tpan0gPSBcc3VtX2ogXGxhbWJkYV97aWp9ID0gT19pJCwgYnV0IGNsZWFybHksIHRoZSBzYW1lIGlzIG5vdCB0cnVlIHdoZW4geW91IHN1bSBhY3Jvc3MgYWxsIG9yaWdpbnMgZm9yIGVhY2ggZGVzdGluYXRpb24gLSAkXHN1bV9pIFRfe2lqfSBcbmVxIFxzdW1faSBcbGFtYmRhX3tpan0gXG5lcSBEX2okDQoNCiMjIyMgKioyLjIuMiBIb3cgZG8gdGhlIGZpdHMgY29tcGFyZSB3aXRoIHRoZSB1bmNvbnN0cmFpbmVkIG1vZGVsIGZyb20gbGFzdCB0aW1lPyoqDQoNCmBgYHtyfQ0KIyBFdmFsdWF0ZSBSXjINCnJzcXJkKHRydXRoID0gbWRhdGFzdWIkRmxvdywgZXN0aW1hdGUgPSBtZGF0YXN1YiRwcm9kc2ltRml0dGVkKQ0KICAgICAgICANCiMgRXZhbHVhdGUgUk1TRSANCnJNU0UodHJ1dGggPSBtZGF0YXN1YiRGbG93LCBlc3RpbWF0ZSA9IG1kYXRhc3ViJHByb2RzaW1GaXR0ZWQpDQpgYGANCg0KQ2xlYXJseSBieSBjb25zdHJhaW5pbmcgb3VyIG1vZGVsIGVzdGltYXRlcyB0byBrbm93biBvcmlnaW4gdG90YWxzLCB0aGUgZml0IG9mIHRoZSBtb2RlbCBoYXMgaW1wcm92ZWQgcXVpdGUgY29uc2lkZXJhYmx5IC0gZnJvbSBhcm91bmQgMC4zMiBpbiB0aGUgdW5jb25zdHJhaW5lZCBtb2RlbCB0byBhcm91bmQgMC40MyBpbiB0aGlzIG1vZGVsLiBUaGUgUk1TRSBoYXMgYWxzbyBkcm9wcGVkIHF1aXRlIG5vdGljYWJseS4NCg0KIyMjIyAqKjIuMi4zIEEgJ3doYXQgaWYuLi4nIHNjZW5hcmlvKioNCg0KTm93IHRoYXQgd2UgaGF2ZSBjYWxpYnJhdGVkIG91ciBwYXJhbWV0ZXJzIGFuZCBwcm9kdWNlZCBzb21lIGVzdGltYXRlcywgd2UgY2FuIHN0YXJ0IHRvIHBsYXkgYXJvdW5kIHdpdGggc29tZSB3aGF0LWlmIHNjZW5hcmlvcy4NCg0KRm9yIGV4YW1wbGUgLSBXaGF0IGlmIHRoZSBnb3Zlcm5tZW50IGludmVzdGVkIGxvdHMgb2YgbW9uZXkgaW4gVGFzbWFpbmlhIGFuZCBhdmVyYWdlIHdlZWtseSBzYWxhcmllcyBpbmNyZWFzZWQgZnJvbSA1NDAuNDUgdG8gODAwLjUwIGRvbGxhcnMgYSB3ZWVrPyBBIGZhciBmZXRjaGVkIHNjZW5hcmlvLCBidXQgb25lIHRoYXQgY291bGQgbWFrZSBhIGdvb2QgZXhwZXJpbWVudC4NCg0KRmlyc3QgY3JlYXRlIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSB3aXRoIHRoZXNlIGFsdGVyZWQgc2FsYXJpZXM6XA0KDQpgYGB7cn0NCm1kYXRhc3ViIDwtIG1kYXRhc3ViICU+JSANCiAgbXV0YXRlKHdqM19kZXN0bWVkaW5jU2NlbmFyaW8gPSBjYXNlX3doZW4oDQogICAgd2ozX2Rlc3RtZWRpbmMgPT0gNTQwLjQ1IH4gODAwLjUwLA0KICAgIFRSVUUgfiB3ajNfZGVzdG1lZGluYykNCiAgKQ0KDQpgYGANCg0KTm93IGxldCdzIHBsdWcgdGhlc2UgbmV3IHZhbHVlcyBpbnRvIHRoZSBtb2RlbCBhbmQgc2VlIGhvdyB0aGlzIGNoYW5nZXMgdGhlIGZsb3dzIGluIHRoZSBzeXN0ZW0uLi4gJFxsYW1iZGFfe2lqfSA9IGV4cChcbXVfe2l9ICsgXGFscGhhIFxsbiBXX2ogLSBcYmV0YSBcbG4gZF97aWp9KSQNCg0KYGBge3J9DQptZGF0YXN1YiA8LSBtZGF0YXN1YiAlPiUgDQogIG11dGF0ZShwcm9kc2ltZXN0MiA9IGV4cChtdV9pICsgKGFscGhhKmxvZyh3ajNfZGVzdG1lZGluY1NjZW5hcmlvKSArIChiZXRhKmxvZyhkaXN0KSkpKSAlPiUgcm91bmQoLiwgMCkpDQoNCiNub3cgd2UgY2FuIGNyZWF0ZSBwaXZvdCB0YWJsZSB0byB0dXJuIHBhaXJlZCBsaXN0IGludG8gbWF0cml4IChhbmQgY29tcHV0ZSB0aGUgbWFyZ2lucyBhcyB3ZWxsKQ0KbWRhdGFzdWJtYXQ0IDwtIG1kYXRhc3ViICU+JSANCiAgc2VsZWN0KE9yaWdfY29kZSwgRGVzdF9jb2RlLCBwcm9kc2ltZXN0MikgJT4lIA0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gRGVzdF9jb2RlLCB2YWx1ZXNfZnJvbSA9IHByb2RzaW1lc3QyLCB2YWx1ZXNfZmlsbCA9IDApICU+JSANCiAgcmVsb2NhdGUoIjFHU1lEIiwgLmFmdGVyID0gT3JpZ19jb2RlKSAlPiUgDQogIHJvd3dpc2UoT3JpZ19jb2RlKSAlPiUgDQogIG11dGF0ZSh0b3RhbF9mbG93X2ZybV9hcmVhID0gc3VtKGNfYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpKSkgKQ0KDQptZGF0YXN1Ym1hdDQNCmBgYA0KDQpZb3Ugd2lsbCBub3RpY2UgdGhhdCBieSBpbmNyZWFzaW5nIHRoZSBhdmVyYWdlIHNhbGFyeSBpbiB0aGUgcmVzdCBvZiBUYXptYW5pYSwgd2UndmUgcmVkdWNlZCB0aGUgZmxvd3MgaW50byB0aGlzIGFyZWEgKHllcywgSSBrbm93LCBjb3VudGVyaW50dWl0aXZlbHksIGJ1dCB0aGlzIGlzIGp1c3QgYW4gZXhhbXBsZSksIGJ1dCBoYXZlIG5vdCByZWR1Y2VkIHRoZSBmbG93cyBpbnRvIG90aGVyIHpvbmVzIC0gdGhlIG9yaWdpbmFsIGNvbnN0cmFpbnRzIGFyZSBzdGlsbCB3b3JraW5nIG9uIHRoZSBvdGhlciB6b25lcy4gT25lIHdheSB0byBnZXQgYXJvdW5kIHRoaXMsIG5vdyB0aGF0IHdlIGhhdmUgY2FsaWJyYXRlZCBvdXIgcGFyYW1ldGVycywgaXMgdG8gcmV0dXJuIHRvIHRoZSBtdWx0aXBsaWNhdGl2ZSBtb2RlbCBpbiBFcXVhdGlvbiAxIGFuZCBydW4gdGhpcyBtb2RlbCBhZnRlciBjYWxjdWxhdGluZyBvdXIgb3duICRBX2kkIGJhbGFuY2luZyBmYWN0b3JzLg0KDQokVF97aWp9ID0gQV9pIE9faSBXX2peXGFscGhhIGRfe2lqfV4tXGJldGEkDQoNCmBgYHtyfQ0KIyBDYWxjdWxhdGUgbmV3IHdqXmFscGhhIGFuZCBkaWo9al5iZXRhDQp3ajJfYWxwaGEgPC0gbWRhdGFzdWIkd2ozX2Rlc3RtZWRpbmNeYWxwaGENCmRpc3RfYmV0YSA8LSBtZGF0YXN1YiRkaXN0XmJldGENCg0KIyBDYWxjdWxhdGUgdGhlIGZpcnN0IHN0YWdlICBvZiB0aGUgQWkgdmFsdWVzDQptZGF0YXN1YiA8LSBtZGF0YXN1YiAlPiUgDQogIG11dGF0ZShBaTEgPSB3ajJfYWxwaGEqZGlzdF9iZXRhKSAlPiUgDQogIGdyb3VwX2J5KE9yaWdfY29kZSkgJT4lIA0KICAjIEZpbmQgY29uc3RyYWludCBvZiBhIGdpdmVuIGxvY2F0aW9uDQogIHN1bW1hcml6ZShBX2kgPSBzdW0oQWkxKSkgJT4lIA0KICBtdXRhdGUoQV9pID0gMS9BX2kpICU+JQ0KICByaWdodF9qb2luKG1kYXRhc3ViKQ0KYGBgDQoNClNvIHRoYXQgaXMgaXQgZm9yIGNhbGN1bGF0aW5nIHlvdXIgJEFfaSQgdmFsdWVzLiBOb3cgdGhhdCB5b3UgaGF2ZSB0aGVzZSwgaXQncyB2ZXJ5IHNpbXBsZSB0byBwbHVnIGV2ZXJ5dGhpbmcgYmFjayBpbnRvIEVxdWF0aW9uIDEgYW5kIGdlbmVyYXRlIHNvbWUgZXN0aW1hdGVzLg0KDQpgYGB7cn0NCiMgVG8gY2hlY2sgZXZlcnl0aGluZyB3b3JrcywgcmVjcmVhdGUgdGhlIG9yaWdpbmFsIGVzdGltYXRlcw0KbWRhdGFzdWIgPC0gbWRhdGFzdWIgJT4lIA0KICBtdXRhdGUocHJvZHNpbWVzdDMgPSByb3VuZChBX2kqT19pKndqM19kZXN0bWVkaW5jXmFscGhhKmRpc3ReYmV0YSwgMCkpDQpgYGANCg0KWW91IHNob3VsZCBzZWUgdGhhdCB5b3VyIG5ldyBlc3RpbWF0ZXMgYXJlIGV4YWN0bHkgdGhlIHNhbWUgYXMgeW91ciBmaXJzdCBlc3RpbWF0ZXMuIElmIHRoZXkgYXJlIG5vdCB0aGVuIHNvbWV0aGluZyBoYXMgZ29uZSB3cm9uZy4gTm93IHdlIGhhdmUgdGhpcyB0aG91Z2gsIHdlIGNhbiBrZWVwIG1lc3NpbmcgYXJvdW5kIHdpdGggc29tZSBuZXcgZXN0aW1hdGVzIGFuZCBrZWVwIHRoZSBjb25zdHJhaW50cy4NCg0KKipSZW1lbWJlciwgdGhvdWdoLCB0aGF0IHlvdSB3aWxsIG5lZWQgdG8gcmVjYWxjdWxhdGUqKiAkQV9pJCBlYWNoIHRpbWUgeW91IHdhbnQgdG8gY3JlYXRlIGEgbmV3IHNldCBvZiBlc3RpbWF0ZXMuIExldCdzIHRyeSB3aXRoIG91ciBuZXcgdmFsdWVzIGZvciB0aGUgZGVzdGluYXRpb24gc2FsYXJ5IGluIHRoZSByZXN0IG9mIFRhem1hbmlhOg0KDQpgYGB7cn0NCndqM19hbHBoYSA8LSBtZGF0YXN1YiR3ajNfZGVzdG1lZGluY1NjZW5hcmlvXmFscGhhDQpkaXN0X2JldGEgPC0gbWRhdGFzdWIkZGlzdF5iZXRhDQoNCiMgQ2FsY3VsYXRlIGZpcnN0IHN0YWdlcyBvZiBBaSB2YWx1ZXMNCm1kYXRhc3ViIDwtIG1kYXRhc3ViICU+JSANCiAgbXV0YXRlKEFpMSA9IHdqM19hbHBoYSpkaXN0X2JldGEpICU+JSANCiAgZ3JvdXBfYnkoT3JpZ19jb2RlKSAlPiUgDQogICMgRmluZCBjb25zdHJhaW50IG9mIGEgZ2l2ZW4gbG9jYXRpb24NCiAgc3VtbWFyaXplKEFfaSA9IHN1bShBaTEpKSAlPiUgDQogIG11dGF0ZShBX2kgPSAxL0FfaSkgJT4lDQogIHJpZ2h0X2pvaW4obWRhdGFzdWIgJT4lIHNlbGVjdCghc3RhcnRzX3dpdGgoIkEiKSkpDQoNCmBgYA0KDQpOb3cgd2UgaGF2ZSBzb21lIG5ldyAkQV9pJHMsIGxldCdzIGdlbmVyYXRlIHNvbWUgbmV3IHNjZW5hcmlvIGZsb3cgZXN0aW1hdGVzLg0KDQpgYGB7cn0NCm1kYXRhc3VibWF0NSA8LSBtZGF0YXN1YiAlPiUgDQogIG11dGF0ZShwcm9kc2ltZXN0NF9zY2VuYXJpbyA9IHJvdW5kKEFfaSpPX2kqd2ozX2FscGhhKmRpc3RfYmV0YSkpICU+JQ0KICBzZWxlY3QoT3JpZ19jb2RlLCBEZXN0X2NvZGUsIHByb2RzaW1lc3Q0X3NjZW5hcmlvKSAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAiRGVzdF9jb2RlIiwgdmFsdWVzX2Zyb20gPSAicHJvZHNpbWVzdDRfc2NlbmFyaW8iLCB2YWx1ZXNfZmlsbCA9IDApICU+JSANCiAgcmVsb2NhdGUoIjFHU1lEIiwgLmFmdGVyID0gT3JpZ19jb2RlKSAlPiUgDQogIHJvd3dpc2UoT3JpZ19jb2RlKSAlPiUgDQogIG11dGF0ZSh0b3RhbF9mbG93X2ZybV9hcmVhID0gc3VtKGNfYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpKSkpDQpgYGANCg0KVGhlcmUgYXJlIGEgbnVtYmVyIG9mIHRoaW5ncyB0byBub3RlIGhlcmUuIEZpcnN0bHksIGZsb3dzIGludG8gVGF6bWFuaWEgaGF2ZSByZWR1Y2VkLCB3aGlsZSBmbG93cyBpbnRvIG90aGVyIHJlZ2lvbnMgaGF2ZSBpbmNyZWFzZWQuIFNlY29uZGx5LCB5ZXMsIEkga25vdyB0aGlzIHdhcyBhIGJhZCBleGFtcGxlLCBidXQgdHJ5IHdpdGggc29tZSBvZiB0aGUgb3RoZXIgdmFyaWFibGVzIGZvciB5b3Vyc2VsZi4NCg0KVGhpcmRseSwgT3VyIG9yaWdpbiBjb25zdHJhaW50cyBhcmUgbm93IGhvbGRpbmcgYWdhaW4uDQoNCiMjIDMuICoqQXR0cmFjdGlvbi1Db25zdHJhaW5lZCBNb2RlbCoqDQoNClRoZSBhdHRyYWN0aW9uIGNvbnN0cmFpbmVkIE1vZGVsIGlzIHZpcnR1YWxseSB0aGUgc2FtZSBhcyB0aGUgUHJvZHVjdGlvbiBjb25zdHJhaW5lZCBtb2RlbDoNCg0KNS4gICRUX3tpan0gPSBEX2ogQl9qIFZfaV5cbXUgZF97aWp9Xi1cYmV0YSQNCg0Kd2hlcmUNCg0KNi4gICREX3tqfSA9IFxzdW1faSBUX3tpan0kDQoNCmFuZA0KDQo3LiAgJEJfaiA9IFxmcmFjezF9e1xzdW1faSBWX2leXG11IGRfe2lqfV4tXGJldGF9JA0KDQpJIHdvbid0IGR3ZWxsIG9uIHRoZSBhdHRyYWN0aW9uIGNvbnN0cmFpbmVkIG1vZGVsLCBleGNlcHQgdG8gc2F5IHRoYXQgaXQgY2FuIGJlIHJ1biBpbiBSIGFzIHlvdSB3b3VsZCBleHBlY3Q6DQoNCjguICAkXGxhbWJkYV97aWp9ID0gZXhwKFxtdSBcbG4gVl9pICsgXGFscGhhX3tpfSAtIFxiZXRhIFxsbiBkX3tpan0pJA0KDQpvciBpbiBSOg0KDQpgYGB7cn0NCmF0dHJTaW0gPC0gZ2xtKEZsb3cgfiBEZXN0X2NvZGUgKyBsb2codmkxX29yaWdwb3ApICsgbG9nKGRpc3QpIC0gMSwgZmFtaWx5ID0gcG9pc3NvbihsaW5rID0gImxvZyIpLCBkYXRhID0gbWRhdGFzdWIsIG5hLmFjdGlvbiA9IG5hLmV4Y2x1ZGUpDQoNCnN1bW1hcnkoYXR0clNpbSkNCmBgYA0KDQp3ZSBjYW4gZXhhbWluZSBob3cgdGhlIGNvbnN0cmFpbnRzIGhvbGQgZm9yIGRlc3RpbmF0aW9ucyB0aGlzIHRpbWU6DQoNCmBgYHtyfQ0KIyBGaXJzdCByb3VuZCBvZiB0aGUgZXN0aW1hdGVzDQptZGF0YXN1YiAlPiUgDQogIG11dGF0ZShhdHRyc2ltRml0dGVkID0gcm91bmQoZml0dGVkKGF0dHJTaW0pKSkgLT4gbWRhdGFzdWINCg0KIyBQaXZvdCB0aGUgdGFibGUgdG8gdHVybiBwYWlyZWQgbGlzdCBpbnRvIG1hdHJpeCAoYW5kIGNvbXB1dGUgdGhlIG1hcmdpbnMgYXMgd2VsbCkNCm1kYXRhc3VibWF0NiA8LSBtZGF0YXN1YiAlPiUgDQogIHNlbGVjdChPcmlnX2NvZGUsIERlc3RfY29kZSwgYXR0cnNpbUZpdHRlZCkgJT4lIA0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gIkRlc3RfY29kZSIsIHZhbHVlc19mcm9tID0gImF0dHJzaW1GaXR0ZWQiLCB2YWx1ZXNfZmlsbCA9IDApICU+JSANCiAgcmVsb2NhdGUoIjFHU1lEIiwgLmFmdGVyID0gT3JpZ19jb2RlKSAlPiUgDQogIHJvd3dpc2UoT3JpZ19jb2RlKSAlPiUgDQogIG11dGF0ZSh0b3RhbF9mbG93X2ZybV9hcmVhID0gc3VtKGNfYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpKSkpDQoNCm1kYXRhc3VibWF0Ng0KYGBgDQoNCmNvbXBhcmVkIHRvIC4uLg0KDQpgYGB7cn0NCm1kYXRhc3VibWF0DQpgYGANCg0KYW5kIHdlIGNhbiB0ZXN0IHRoZSBnb29kZXNzIG9mIGZpdCBpbiBleGFjdGx5IHRoZSBzYW1lIHdheSBhcyBiZWZvcmU6DQoNCmBgYHtyfQ0KIyBFdmFsdWF0ZSBSXjINCnJzcXJkKHRydXRoID0gbWRhdGFzdWIkRmxvdywgZXN0aW1hdGUgPSBtZGF0YXN1YiRhdHRyc2ltRml0dGVkKQ0KICAgICAgICANCiMgRXZhbHVhdGUgUk1TRSANCnJNU0UodHJ1dGggPSBtZGF0YXN1YiRGbG93LCBlc3RpbWF0ZSA9IG1kYXRhc3ViJGF0dHJzaW1GaXR0ZWQpDQpgYGANCg0KT0ssIHRoYXQncyB3aGVyZSBJJ2xsIGxlYXZlIHNpbmdseSBjb25zdHJhaW5lZCBtb2RlbHMgZm9yIG5vdy4gVGhlcmUgYXJlLCBvZiBjb3Vyc2UsIHBsZW50eSBvZiB0aGluZ3MgeW91IGNvdWxkIHRyeSBvdXQuIEZvciBleGFtcGxlOg0KDQotICAgWW91IGNvdWxkIHRyeSBtYXBwaW5nIHRoZSBjb2VmZmljaWVudHMgb3IgdGhlIHJlc2lkdWFsIHZhbHVlcyBmcm9tIHRoZSBtb2RlbCB0byBzZWUgaWYgdGhlcmUgaXMgYW55IHBhdHRlcm5pbmcgaW4gZWl0aGVyIHRoZSBvdmVyIG9yIHVuZGVyIHByZWRpY3Rpb24gb2YgZmxvd3MuDQoNCi0gICBZb3UgY291bGQgdHJ5IHJ1bm5pbmcgeW91ciBvd24gdmVyc2lvbiBvZiBhIExVVEkgbW9kZWwgYnkgZmlyc3QgY2FsaWJyYXRpbmcgdGhlIG1vZGVsIHBhcmFtZXRlcnMgYW5kIHBsdWdnaW5nIHRoZXNlIGludG8gYSBtdWx0aXBsaWNhdGl2ZSB2ZXJzaW9uIG9mIHRoZSBtb2RlbCwgYWRqdXN0aW5nIHRoZSBkZXN0aW5hdGlvbiBjb25zdHJhaW50cyB0byBzZWUgd2hpY2ggb3JpZ2lucyBhcmUgbGlrZWx5IHRvIGdlbmVyYXRlIG1vcmUgdHJpcHMuDQoNCiMjIDQuICoqRG91Ymx5IENvbnN0cmFpbmVkIE1vZGVsKioNCg0KTm93LCB0aGUgbW9kZWwgaW4gdGhlIGZhbWlseSB5b3UgaGF2ZSBhbGwgYmVlbiB3YWl0aW5nIGZvciAtIHRoZSBiaWcgYm9zcywgdGhlIGRhZGR5LCB0aGUgZG91Ymx5IGNvbnN0cmFpbmVkIG1vZGVsIQ0KDQpMZXQncyBiZWdpbiB3aXRoIHRoZSBmb3JtdWxhOg0KDQo5LiAgJFRfe2lqfSA9IEFfaSBPX2kgQl9qIERfaiBkX3tpan1eLVxiZXRhJA0KDQp3aGVyZQ0KDQoxMC4gJE9fe2l9ID0gXHN1bV9qIFRfe2lqfSQNCg0KMTEuICREX3tqfSA9IFxzdW1faSBUX3tpan0kDQoNCmFuZA0KDQoxMi4gJEFfaSA9IFxmcmFjezF9e1xzdW1faiBCX2ogRF9qIGRfe2lqfV4tXGJldGF9JA0KDQoxMy4gJEJfaiA9IFxmcmFjezF9e1xzdW1faSBBX2kgT19pIGRfe2lqfV4tXGJldGF9JA0KDQpOb3csIHRoZSBhc3R1dGUgd2lsbCBoYXZlIG5vdGljZWQgdGhhdCB0aGUgY2FsY3VsYXRpb24gb2YgQWkgcmVsaWVzIG9uIGtub3dpbmcgQmogYW5kIHRoZSBjYWxjdWxhdGlvbiBvZiBCaiByZWxpZXMgb24ga25vd2luZyBBaS4gQSBjb251bmRydW0hISBJZiBJIGRvbid0IGtub3cgQWkgaG93IGNhbiBJIGNhbGN1YXRlIEJqIGFuZCB0aGVuIGluIHR1cm4gQWkgYW5kIHRoZW4gQmogYWQgaW5maW5pdHVtPz8/ISENCg0KV2VsbCwgSSB3cmVzdGxlZCB3aXRoIHRoYXQgZm9yIGEgd2hpbGUgdW50aWwgSSBjYW1lIGFjcm9zcyB0aGlzIHBhcGVyIGJ5IFtNYXJ0eW4gU2VuaW9yXShodHRwOi8vam91cm5hbHMuc2FnZXB1Yi5jb20vZG9pL2Ficy8xMC4xMTc3LzAzMDkxMzI1NzkwMDMwMDIxOCkgd2hlcmUgaGUgc2tldGNoZXMgb3V0IGEgdmVyeSB1c2VmdWwgYWxnb3JpdGhtIGZvciBpdGVyYXRpdmVseSBhcnJpdmluZyBhdCB2YWx1ZXMgZm9yIEFpIGFuZCBCaiBieSBzZXR0aW5nIGVhY2ggdG8gZXF1YWwgMSBpbml0aWFsbHkgYW5kIHRoZW4gY29udGludWluZyB0byBjYWxjdWxhdGUgZWFjaCBpbiB0dXJuIHVudGlsIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gZWFjaCB2YWx1ZSBpcyBzbWFsbCBlbm91Z2ggbm90IHRvIG1hdHRlci4NCg0KV2Ugd2lsbCByZXR1cm4gdG8gdGhpcyBsYXRlciwgYnV0IGZvciBub3csIHdlIHdpbGwgb25jZSBhZ2FpbiB1c2UgdGhlIGF3ZXNvbWUgcG93ZXIgb2YgUiB0byBkZWFsIHdpdGggYWxsIG9mIHRoaXMgZGlmZmljdWx0eSBmb3IgdXMhDQoNCldlIGNhbiBydW4gdGhlIGRvdWJseSBjb25zdHJhaW5lZCBtb2RlbCBpbiBleGFjdGx5IHRoZSBzYW1lIHdheSBhcyB3ZSByYW4gdGhlIHNpbmdseSBjb25zdHJhaW5lZCBtb2RlbHM6DQoNCjE1LiAkXGxhbWJkYV97aWp9ID0gZXhwKFxtdV9pICsgXGFscGhhX3tpfSAtIFxiZXRhIFxsbiBkX3tpan0pJA0KDQpUaGUgY29kZSBiZWxvdyBoYXMgY2hhbmdlZCBhIGxpdHRlIGZyb20gdGhlIHNpbmdseSBjb25zdHJhaW5lZCBtb2RlbHMgSSBoYXZlIHJlbW92ZWQgdGhlICctMScgd2hpY2ggbWVhbnMgdGhhdCBhbiBpbnRlcmNlcHQgd2lsbCBhcHBlYXIgaW4gdGhlIG1vZGVsIGFnYWluLiBUaGlzIGlzIG5vdCBiZWNhdXNlIEkgd2FudCBhbiBpbnRlcmNlcHQgYXMgaXQgbWFrZXMgdGhlIG9yaWdpbiBhbmQgZGVzdGluYXRpb24gY29lZmZpY2llbnRzIGhhcmRlciB0byBpbnRlcnByZXQgLSByZWZlcmVuY2UgY2F0ZWdvcmllcyB6b25lcyB3aWxsIGFwcGVhciBhbmQgdGhlIGNvZWZmaWNpZW50cyB3aWxsIG5lZWQgdG8gYmUgY29tcGFyZWQgd2l0aCB0aGUgaW50ZXJjZXB0IC0gcmF0aGVyIHRoZSAnLTEnIGNoZWF0IGZvciByZW1vdmluZyB0aGUgaW50ZXJjZXB0IG9ubHkgd29ya3Mgd2l0aCBvbmUgZmFjdG9yIGxldmVsIC0gaGVyZSB3ZSBoYXZlIHR3byAob3JpZ2lucyBhbmQgZGVzdGluYXRpb25zKS4gRm9yIGZ1bGwgZGV0YWlscyBhbmQgYW4gZXhwbGFuYXRpb24gZm9yIGFsdGVybmF0aXZlIHdheXMgZm9yIGRlYWxpbmcgd2l0aCB0aGlzLCBwbGVhc2UgdmlzaXQgaGVyZSAtIDxodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8yMTU3NzkvcmVtb3ZpbmctaW50ZXJjZXB0LWZyb20tZ2xtLWZvci1tdWx0aXBsZS1mYWN0b3JpYWwtcHJlZGljdG9ycy1vbmx5LXdvcmtzLWZvci1maXI+IC0gZm9yIGVhc2UsIGhlcmUgd2Ugd2lsbCBqdXN0IGNvbnRpbnVlIHdpdGggdGhlIGludGVyY2VwdC4NCg0KVGhlIHJlZmVyZW5jZSBsZXZlbCBtZWFucyB0aGF0IHRoZSBvcmlnaW4gYW5kIGRlc3RpbmF0aW9uIGNvZWZmaWNpZW50cyBuZWVkIHRvIGJlIGludGVycHJldGVkIGluIHJlbGF0aW9uIHRvIGEgcmVmZXJlbmNlIGNhdGVnb3J5LiBJbiB0aGlzIGV4YW1wbGUsIHRoZSBmaXJzdCB6b25lIGluIHRoZSBzeXN0ZW0gaXMgdXNlZCAoU3lkbmV5KSwgd2l0aCB0aGUgZGlyZWN0aW9uIGFuZCBzaXplIG9mIHRoZSBjb2VmZmljaWVudHMgcmVmZXJyaW5nIHRvIHdoZXRoZXIgYW5vdGhlciBvcmlnaW4gb3IgZGVzdGluYXRpb24gem9uZSBoYXMgYSBncmVhdGVyIG9yIGxlc3NlciBwb3NpdGl2ZSBvciBuZWdhdGl2ZSBlZmZlY3Qgb24gbWlncmF0aW9uIGZsb3dzIGluIHRoZSBzeXN0ZW0gd2hlbiBjb21wYXJlZCB0byBTeWRuZXkuIFRoZSBlc3RpbWF0ZXMgcHJvZHVjZWQgYnkgdGhlIERvdWJseSBDb25zdHJhaW5lZCBNb2RlbCBhcmUgdGhlIG1vc3QgYWNjdXJhdGUgaW4gdGhlIFdpbHNvbiBmYW1pbHkgb2YgbW9kZWxzIChpbiB0aGlzIGV4YW1wbGUsIGFuIFIyIHZhbHVlIG9mIDAuODcpLiBIb3dldmVyLCB0aGVyZSBpcyBhIGxvc3Mgb2YgZmxleGliaWxpdHkgd2hlbiBjb21wYXJlZCB0byB0aGUgc2luZ2x5IGNvbnN0cmFpbmVkIG1vZGVscywgYXMgb25seSBhbHRlcm5hdGl2ZXMgdG8gb3JpZ2luL2Rlc3RpbmF0aW9uIGludGVyYWN0aW9uIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyBzdWNoIGFzIGhpc3RvcmljIGZsb3dzIG9yIHNvbWV0aGluZyBvdGhlciB0aGFuIGRpc3RhbmNlIGNhbiBiZSBleHBlcmltZW50ZWQgd2l0aC4gVGhlIGRvdWJsZSBjb25zdHJhaW50cyBtZWFuIHRoYXQgb3JpZ2luLSBhbmQgZGVzdGluYXRpb24tc3BlY2lmaWMgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGNhbm5vdCBiZSB1c2VkLg0KDQpgYGB7cn0NCiMgUnVuIGEgZG91Ymx5IGNvbnN0cmFpbmVkIG1vZGVsDQpkb3ViU2ltIDwtIGdsbShGbG93IH4gT3JpZ19jb2RlICsgRGVzdF9jb2RlICsgbG9nKGRpc3QpLCBmYW1pbHkgPSBwb2lzc29uKGxpbmsgPSBsb2cpLCBuYS5hY3Rpb24gPSBuYS5leGNsdWRlLCBkYXRhID0gbWRhdGFzdWIpDQoNCiMgU3VtbWFyeSBvZiBtb2RlbA0Kc3VtbWFyeShkb3ViU2ltKQ0KYGBgDQoNCkFuZCB0aGUgdmFyaW91cyBmbG93cyBhbmQgZ29vZG5lc3Mtb2YtZml0IHN0YXRpc3RpY3M/DQoNCmBgYHtyfQ0KbWRhdGFzdWIgPC0gbWRhdGFzdWIgJT4lIA0KICBtdXRhdGUoZG91YnNpbUZpdHRlZCA9IHJvdW5kKGZpdHRlZChkb3ViU2ltKSkpDQoNCiNub3cgd2UgY2FuIGNyZWF0ZSBwaXZvdCB0YWJsZSB0byB0dXJuIHBhaXJlZCBsaXN0IGludG8gbWF0cml4IChhbmQgY29tcHV0ZSB0aGUgbWFyZ2lucyBhcyB3ZWxsKQ0KbWRhdGFzdWJtYXQ3IDwtIG1kYXRhc3ViICU+JSANCiAgc2VsZWN0KE9yaWdfY29kZSwgRGVzdF9jb2RlLCBkb3Vic2ltRml0dGVkKSAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBEZXN0X2NvZGUsIHZhbHVlc19mcm9tID0gZG91YnNpbUZpdHRlZCwgdmFsdWVzX2ZpbGwgPSAwKSAlPiUgDQogIHJlbG9jYXRlKCIxR1NZRCIsIC5hZnRlciA9ICJPcmlnX2NvZGUiKSAlPiUgDQogIHJvd3dpc2UoT3JpZ19jb2RlKSAlPiUgDQogIG11dGF0ZShGbG93X2Zyb21fem9uZSA9IHN1bShjX2Fjcm9zcyh3aGVyZShpcy5udW1lcmljKSkpKQ0KDQptZGF0YXN1Ym1hdDcNCmBgYA0KDQpjb21wYXJlZCB0byAuLg0KDQpgYGB7cn0NCm1kYXRhc3VibWF0DQpgYGANCg0KYW5kIHdlIGNhbiB0ZXN0IHRoZSBnb29kbmVzcy1vZi1maXQgaW4gZXhhY3RseSB0aGUgc2FtZSB3YXkgYXMgYmVmb3JlOg0KDQpgYGB7cn0NCiMgRXZhbHVhdGUgUl4yDQpyc3FyZCh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkZG91YnNpbUZpdHRlZCkNCiAgICAgICAgDQojIEV2YWx1YXRlIFJNU0UgDQpyTVNFKHRydXRoID0gbWRhdGFzdWIkRmxvdywgZXN0aW1hdGUgPSBtZGF0YXN1YiRkb3Vic2ltRml0dGVkKQ0KYGBgDQoNClNvIHRoZSBnb29kbmVzcyBvZiBmaXQgaGFzIHNob3QgdXAgYW5kIHdlIGNhbiBjbGVhcmx5IHNlZSB0aGUgb3JpZ2luIGFuZCBkZXN0aW5hdGlvbiBjb25zdHJhaW50cyB3b3JraW5nLCBhbmQgZm9yIG1vc3Qgc2V0cyBvZiBmbG93cywgdGhlIG1vZGVsIGlzIG5vdyBwcm9kdWNpbmcgc29tZSBnb29kIGVzdGltYXRlcy4gSG93ZXZlciwgdGhlcmUgYXJlIHN0aWxsIHNvbWUgZXJyb3JzIGluIHRoZSBmbG93cy4NCg0KSXMgdGhlcmUgYW55dGhpbmcgbW9yZSB3ZSBjYW4gZG8/IFllcywgb2YgY291cnNlIHRoZXJlIGlzLg0KDQojIyMgKio0LjEgVHdlYWtpbmcgb3VyIE1vZGVscyoqDQoNCiMjIyMgKio0LjEuMSBEaXN0YW5jZSBEZWNheSoqDQoNCk5vdywgYWxsIG9mIHRoZSB3YXkgdGhyb3VnaCB0aGVzZSBwcmFjdGljYWxzLCB3ZSBoYXZlIGFzc3VtZWQgdGhhdCB0aGUgZGlzdGFuY2UgZGVjYXkgcGFyYW1ldGVyIGZvbGxvd3MgYSBuZWdhdGl2ZSBwb3dlciBsYXcuIFdlbGwsIGl0IGRvZXNuJ3QgbmVlZCB0by4NCg0KSW4gW1dpbHNvbidzIG9yaWdpbmFsIHBhcGVyXShodHRwOi8vam91cm5hbHMuc2FnZXB1Yi5jb20vZG9pL2Ficy8xMC4xMDY4L2EwMzAwMDEpLCBoZSBnZW5lcmFsaXNlZCB0aGUgZGlzdGFuY2UgZGVjYXkgcGFyYW1ldGVyIHRvOg0KDQokZihkX3tpan0pJA0KDQpXaGVyZSBcJGZcJCByZXByZXNlbnRzIHNvbWUgZnVuY3Rpb24gb2YgZGlzdGFuY2UgZGVzY3JpYmluZyB0aGUgcmF0ZSBhdCB3aGljaCB0aGUgZmxvdyBpbnRlcmFjdGlvbnMgY2hhbmdlIGFzIGRpc3RhbmNlIGluY3JlYXNlcy4gTG90cyBvZiBwZW9wbGUgaGF2ZSB3cml0dGVuIGFib3V0IHRoaXMsIGluY2x1ZGluZyBbVGF5b3IgKDE5NzEpXShodHRwOi8vb25saW5lbGlicmFyeS53aWxleS5jb20vZG9pLzEwLjExMTEvai4xNTM4LTQ2MzIuMTk3MS50YjAwMzY0LngvZnVsbCkgYW5kIG1vcmUgcmVjZW50bHkgUm9iaW4gTG92ZWxhY2UgaW4gYSB0cmFuc3BvcnQgY29udGV4dCwgW2hlcmVdKGh0dHBzOi8vd3d3LnNsaWRlc2hhcmUubmV0L0lUU0xlZWRzL2VzdGltYXRpbmctZGlzdGFuY2UtZGVjYXktZm9yLXRoZS1uYXRpb25hbC1wcm9wZW5zaXR5LXRvLWN5Y2xlLXRvb2wpLg0KDQpGb3IgdGhlIGludmVyc2UgcG93ZXIgbGF3IHRoYXQgd2UgaGF2ZSBiZWVuIHVzaW5nIGlzIG9uZSBwb3NzaWJsZSBmdW5jdGlvbiBvZiBkaXN0YW5jZSwgdGhlIG90aGVyIGNvbW1vbiBvbmUgdGhhdCBpcyB1c2VkIGlzIHRoZSBuZWdhdGl2ZSBleHBvbmVudGlhbCBmdW5jdGlvbjoNCg0KJGV4cCgtXGJldGEgZF97aWp9KSQNCg0KV2UgY2FuIGdldCBhIGZlZWwgZm9yIGhvdyBkaWZmZXJlbnQgZGlzdGFuY2UgZGVjYXkgcGFyYW1ldGVycyB3b3JrIGJ5IHBsb3R0aW5nIHNvbWUgc2FtcGxlIGRhdGEgKHRyeSBkaWZmZXJlbnQgcGFyYW1ldGVycyk6DQoNCmBgYHtyfQ0KeGRpc3QgPC0gIHNlcSgxLCAyMCkNCmludnBvd2VyMiA8LSAgeGRpc3ReLTINCm5lZ2V4cDAuMyA8LSBleHAoLTAuMyp4ZGlzdCkgDQoNCmRmIDwtIHRpYmJsZSh4ZGlzdCwgaW52cG93ZXIyLCBuZWdleHAwLjMpICU+JSANCiAgcGl2b3RfbG9uZ2VyKCF4ZGlzdCwgbmFtZXNfdG8gPSAiZGVjYXkiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikNCg0KZGYgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0geGRpc3QsIHkgPSB2YWx1ZXMsIGNvbG9yID0gZGVjYXkpKSArDQogIGdlb21fbGluZShzaXplID0gMSkNCmBgYA0KDQpJbiB0aGlzIHBhcnRpY3VsYXIgZXhhbXBsZSB3aXRoIHRoZXNlIHBhcmFtZXRlcnMgYW5kIPCdm73wnZu9IHZhbHVlcywgdGhlIGludmVyc2UgcG93ZXIgZnVuY3Rpb24gaGFzIGEgZmFyIG1vcmUgcmFwaWQgZGlzdGFuY2UgZGVjYXkgZWZmZWN0IHRoYW4gdGhlIG5lZ2F0aXZlIGV4cG9uZW50aWFsIGZ1bmN0aW9uLiBJbiByZWFsIGxpZmUsIHdoYXQgdGhpcyBtZWFucyBpcyB0aGF0IGlmIHRoZSBvYnNlcnZlZCBpbnRlcmFjdGlvbnMgZHJvcCBvZmYgdmVyeSByYXBpZGx5IHdpdGggZGlzdGFuY2UsIHRoZW4gdGhleSBtaWdodCBiZSBtb3JlIGxpa2VseSB0byBmb2xsb3cgYW4gaW52ZXJzZSBwb3dlciBsYXcuIFRoaXMgbWlnaHQgYmUgdGhlIGNhc2Ugd2hlbiBsb29raW5nIGF0IHRyaXBzIHRvIHRoZSBsb2NhbCBjb252ZW5pZW5jZSBzdG9yZSBieSB3YWxraW5nLCBmb3IgZXhhbXBsZS4gT24gdGhlIG90aGVyIGhhbmQsIGlmIHRoZSBlZmZlY3Qgb2YgZGlzdGFuY2UgaXMgbGVzcyBzZXZlcmUgLS0gZm9yIGV4YW1wbGUsIG1pZ3JhdGlvbiBhY3Jvc3MgdGhlIGNvdW50cnkgZm9yIGEgbmV3IGpvYiAtLSB0aGVuIHRoZSBuZWdhdGl2ZSBleHBvbmVudGlhbCBmdW5jdGlvbiB3aXRoIGEgc21hbGwgdmFsdWUgb2YgJPCdm70kIGZ1bmN0aW9uIG1pZ2h0IGJlIG1vcmUgYXBwcm9wcmlhdGUuIFRoZXJlIGlzIG5vIGhhcmQgYW5kIGZhc3QgcnVsZSBhcyB0byB3aGljaCBmdW5jdGlvbiB0byBwaWNrLiBJdCB3aWxsIGp1c3QgY29tZSBkb3duIHRvIHdoaWNoIGZpdHMgdGhlIGRhdGEgYmV0dGVyLg0KDQpBcyBbVGF5b3IgT3NoYW4gcG9pbnRzIG91dCBpbiBoaXMgZXhjZWxsZW50IFByaW1lcl0oaHR0cDovL29wZW5qb3VybmFscy53dS5hYy5hdC9yZWdpb24vcGFwZXJfMTc1LzE3NS5odG1sKSB3aGF0IHRoaXMgbWVhbnMgaW4gb3VyIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCBpcyB0aGF0IHdlIHNpbXBseSBzdWJzdGl0dXRlIOKIkiAkzrJsbuKBoWRpaiQgZm9yXCQg4oiSzrJkaVwkIGluIG91ciBtb2RlbDoNCg0KYGBge3J9DQojIFJ1biBhIGRvdWJseSBjb25zdHJhaW5lZCBTSU0NCmRvdWJTaW0xIDwtIGdsbShGbG93IH4gT3JpZ19jb2RlICsgRGVzdF9jb2RlICsgZGlzdCwgZmFtaWx5ID0gcG9pc3NvbihsaW5rID0gImxvZyIpLCBuYS5hY3Rpb24gPSBuYS5leGNsdWRlLCBkYXRhID0gbWRhdGFzdWIpDQoNCnN1bW1hcnkoZG91YlNpbTEpDQpgYGANCg0KYGBge3J9DQptZGF0YXN1YiA8LSBtZGF0YXN1YiAlPiUgDQogIG11dGF0ZShkb3Vic2ltRml0dGVkMSA9IHJvdW5kKGZpdHRlZChkb3ViU2ltMSkpKQ0KDQojIEV2YWx1YXRlIFJeMg0KcnNxcmQodHJ1dGggPSBtZGF0YXN1YiRGbG93LCBlc3RpbWF0ZSA9IG1kYXRhc3ViJGRvdWJzaW1GaXR0ZWQxKQ0KICAgICAgICANCiMgRXZhbHVhdGUgUk1TRSANCnJNU0UodHJ1dGggPSBtZGF0YXN1YiRGbG93LCBlc3RpbWF0ZSA9IG1kYXRhc3ViJGRvdWJzaW1GaXR0ZWQxKQ0KYGBgDQoNClNvLCBpdCB3b3VsZCBhcHBlYXIgdGhhdCBpbiB0aGlzIGNhc2UgdXNpbmcgYSBuZWdhdGl2ZSBleHBvbmVudGlhbCBmdW5jdGlvbiBpbiBvdXIgbW9kZWwgcmVzdWx0cyBpbiBhIHdvcnNlIG91dGNvbWUgdGhhbiB0aGUgaW5pdGlhbCBpbnZlcnNlIHBvd2VyIGxhdyAtIHRoaXMgbWF5IG5vdCBhbHdheXMgYmUgdGhlIGNhc2UsIHNvIGl0IGlzIHdvcnRoIGV4cGVyaW1lbnRpbmcuDQoNCiMjIyMgKio0LjEuMiBEaXN0YW5jZSBEZWNheSoqDQoNClllcywgdGhlIG5pY2UgdGhpbmcgYWJvdXQgZG9pbmcgYWxsIG9mIHRoaXMgaW4gYSByZWdyZXNzaW9uIG1vZGVsbGluZyBmcmFtZXdvcmsgaXMgd2UgY2FuIGp1c3Qga2VlcCBhZGRpbmcgcHJlZGljdG9yIHZhcmlhYmxlcyBpbnRvIHRoZSBtaXggYW5kIHNlZWluZyB3aGV0aGVyIHRoZXkgaGF2ZSBhbiBlZmZlY3QuDQoNCllvdSBjYW4ndCBhZGQgb3JpZ2luIG9yIGRlc3RpbmF0aW9uIHNwZWNpZmljIHByZWRpY3RvcnMgaW50byBhIGRvdWJseSBjb25zdHJhaW5lZCBtb2RlbCBsaWtlIHRoaXMsIGhvd2V2ZXIsIHN3aXRjaGluZyBiYWNrIHRvIHRoZSBzaW5nbHkgY29uc3RyYWluZWQgbW9kZWxzLCBhcyBtYW55IGRpZmZlcmVudCBvcmlnaW4gb3IgZGVzdGluYXRpb24gcHJlZGljdG9yIHZhcmlhYmxlcyBjYW4gYmUgYWRkZWQgYXMgc2VlbXMgcmVhc29uYWJsZSAoc3ViamVjdCB0byB0aGUgdXN1YWwgcmVzdHJpY3Rpb25zIG9uIGhpZ2ggY29ycmVsYXRpb24pDQoNCkluIGFkZGl0aW9uIHRvIHZhcmlhYmxlcyByZWxhdGluZyB0byBtZWRpYW4gaW5jb21lLCB0aGVyZSBhcmUgdmFyaWFibGVzIG9uIHVuZW1wbG95bWVudCByYXRlIGFuZCB0aGUgcGVyY2VudGFnZSBvZiBob3VzZWhvbGRzIGxpdmluZyBpbiByZW50ZWQgYWNjb21tb2RhdGlvbi4gRXhwZXJpbWVudCB3aXRoIHRoZXNlIHZhcmlhYmxlcyBmb3Igb3JpZ2lucyBhbmQgZGVzdGluYXRpb25zIHRvIHNlZSB3aGV0aGVyIHRoZSBzaW5nbHkgY29uc3RyYWluZWQgbW9kZWxzIGNhbiBiZSBpbXByb3ZlZCBpbiBhbnkgd2F5Lg0KDQpgYGB7cn0NCmtpdGNoZW5zaW5rU0lNIDwtIGdsbShGbG93IH4gRGVzdF9jb2RlICsgdmkxX29yaWdwb3AgKyB2aTJfb3JpZ3VuZW1wICsgdmkzX29yaWdtZWRpbmMgKyB2aTRfb3JpZ3BjdHJlbnQgLTEsIG5hLmFjdGlvbiA9IG5hLmV4Y2x1ZGUsIGZhbWlseSA9IHBvaXNzb24obGluayA9ICJsb2ciKSwgZGF0YSA9IG1kYXRhc3ViKQ0KI2xldCdzIGhhdmUgYSBsb29rIGF0IGl0J3Mgc3VtbWFyeS4uLg0Kc3VtbWFyeShraXRjaGVuc2lua1NJTSkNCmBgYA0KDQpgYGB7cn0NCiMgRmlyc3Qgcm91bmQgb2YgdGhlIGVzdGltYXRlcw0KbWRhdGFzdWIgJT4lIA0KICBtdXRhdGUoYXR0cnNpbUZpdHRlZDIgPSByb3VuZChmaXR0ZWQoa2l0Y2hlbnNpbmtTSU0pKSkgLT4gbWRhdGFzdWINCg0KYGBgDQoNCmFuZCB3ZSBjYW4gdGVzdCB0aGUgZ29vZGVzcyBvZiBmaXQgaW4gZXhhY3RseSB0aGUgc2FtZSB3YXkgYXMgYmVmb3JlOg0KDQpgYGB7cn0NCiMgRXZhbHVhdGUgUl4yDQpyc3FyZCh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkYXR0cnNpbUZpdHRlZDIpDQogICAgICAgIA0KIyBFdmFsdWF0ZSBSTVNFIA0Kck1TRSh0cnV0aCA9IG1kYXRhc3ViJEZsb3csIGVzdGltYXRlID0gbWRhdGFzdWIkYXR0cnNpbUZpdHRlZDIpDQpgYGANCg0KIyMgKio1LiBDb25jbHVzaW9ucywgZnVydGhlciBub3RlcyBhbmQgaWRlYXMgZm9yIGFkZGl0aW9uYWwgYWN0aXZpdGllcyoqDQoNCkhvcGVmdWxseSB5b3UgaGF2ZSBub3cgc2VlbiBob3cgaXQgaXMgZXh0cmVtZWx5IHN0cmFpZ2h0LWZvcndhcmQgdG8gcnVuIGFuZCBjYWxpYnJhdGUgV2lsc29uJ3MgZnVsbCBmYW1pbHkgb2YgU3BhdGlhbCBJbnRlcmFjdGlvbiBNb2RlbHMgaW4gUiB1c2luZyBHTE0gYW5kIFBvaXNzb24gUmVncmVzc2lvbi4NCg0KIyMjICoqNS4xIFNvbWUgRnVydGhlciBOb3RlcyoqDQoNCk5vdyBtaWdodCBiZSB0aGUgdGltZSB0byBtZW50aW9uIHRoYXQgZGVzcGl0ZSBldmVyeXRoaW5nIEkndmUgc2hvd24geW91LCB0aGVyZSBoYXMgYmVlbiBzb21lIGRpc2N1c3Npb24gaW4gdGhlIGxpdGVyYXR1cmUgYXMgdG8gd2hldGhlciB0aGUgUG9pc3NvbiBNb2RlbCBpcyBhY3R1YWxseSBhIG1pc3NwZWNpZmljYXRpb24sIGVzcGVjaWFsbHkgZm9yIG1vZGVsbGluZyBtaWdyYXRpb24gZmxvd3MuIElmIHlvdSBoYXZlIHRoZSBzdG9tYWNoIGZvciBpdCwgW3RoaXMgcGFwZXIgYnkgQ29uZ2RvbiBnb2VzIGludG8gYSBsb3Qgb2YgZGV0YWlsXShodHRwOi8vam91cm5hbHMuc2FnZXB1Yi5jb20vZG9pL2Ficy8xMC4xMDY4L2EyNTE0ODEpLg0KDQpUaGUgaXNzdWUgaXMgYSB0aGluZyBjYWxsZWQgJ292ZXJkaXNwZXJzaW9uJyB3aGljaCwgdHJhbnNsYXRlZCwgZXNzZW50aWFsbHkgcmVsYXRlcyB0byB0aGUgbW9kZWwgbm90IGJlaW5nIGFibGUgdG8gY2FwdHVyZSBhbGwgb2YgdGhlIHRoaW5ncyB0aGF0IGNvdWxkIGJlIGV4cGxhaW5pbmcgdGhlIGZsb3dzIGluIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgdGhhdCBhcmUgc3VwcGxpZWQgdG8gdGhlIG1vZGVsLiBUaGUgZGV0YWlscyBhcmUgdGVkaW91cyBhbmQgb25seSByZWFsbHkgaW50ZWxsaWdpYmxlIHRvIHRob3NlIHdpdGggYSBzdGF0aXN0aWNzIGJhY2tncm91bmQuIElmIHlvdSB3YW50IGEgc3RhcnRlciwgW3RyeSBoZXJlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9PdmVyZGlzcGVyc2lvbiksIGJ1dCBpbiBwcmFjdGljYWwgdGVybXMsIHdlIGNhbiBnZXQgYXJvdW5kIHRoaXMgcHJvYmxlbSBieSBmaXR0aW5nIGEgdmVyeSBzaW1pbGFyIHNvcnQgb2YgcmVncmVzc2lvbiBtb2RlbCBjYWxsZWQgdGhlICpuZWdhdGl2ZSBiaW5vbWlhbCogcmVncmVzc2lvbiBtb2RlbC4NCg0KSWYgeW91IHdpc2gsIHlvdSBjYW4gcmVhZCB1cCBhbmQgZXhwZXJpbWVudCB3aXRoIHRoaXMgbW9kZWwgLSB5b3UgY2FuIGZpdCBpdCBpbiBleGFjdGx5IHRoZSBzYW1lIHdheSBhcyB0aGUgYGdsbWAgbW9kZWwgYnV0IHVzaW5nIGEgZnVuY3Rpb24gY2FsbGVkIGBnbG0ubmJgIHdoaWNoIGlzIHBhcnQgb2YgdGhlIGBtYXNzYCBwYWNrYWdlLiBUaGUgbmVnYXRpdmUgYmlub21pYWwgbW9kZWwgaGFzIGFuIGV4dHJhIHBhcmFtZXRlciBpbiB0aGUgbW9kZWwgZm9yIG92ZXJkaXNwZXJzaW9uLiBZb3UgeW91IGRvIHRyeSB0aGlzLCB5b3Ugd2lsbCBhbG1vc3QgY2VydGFpbmx5IGRpc2NvdmVyIHRoYXQgeW91ciByZXN1bHRzIGJhcmVseSBjaGFuZ2UgLSBidXQgaGVsbCwgeW91IG1pZ2h0IGtlZXAgYSBwZWRhbnRpYyByZXZpZXdlciBhdCBiYXkgaWYgeW91IHN1Ym1pdCB0aGlzIHRvIGEgam91cm5hbCAobm90IHRoYXQgSSdtIHNwZWFraW5nIGZyb20gZXhwZXJpZW5jZSBvciBhbnl0aGluZykuDQoNCiMjIyAqKkFuZCBzb21lIG1vcmUgY29tbWVudHMqKg0KDQpBbm90aGVyIHRoaW5nIHRvIG5vdGUgaXMgdGhhdCB0aGUgZXhhbXBsZSB3ZSB1c2VkIGhlcmUgaGFkIHF1aXRlIG5lYXQgZGF0YS4gWW91IHdpbGwgYWxtb3N0IGNlcnRhaW5seSBydW4gaW50byBwcm9ibGVtcyBpZiB5b3UgaGF2ZSBzcGFyc2UgZGF0YSBvciBwcmVkaWN0b3JzIHdpdGggMHMgaW4gdGhlbS4gSWYgdGhpcyBoYXBwZW5zLCB0aGVuIHlvdSBtaWdodCBuZWVkIHRvIGVpdGhlciBkcm9wIHNvbWUgcm93cyBpbiB5b3VyIGRhdGEgKGlmIHBvcHVsYXRlZCB3aXRoIDBzKSBvciBzdWJzdGl0dXRlIDBzIGZvciB2ZXJ5IHNtYWxsIG51bWJlcnMsIG11Y2ggbGVzcyB0aGFuIDEsIGJ1dCBncmVhdGVyIHRoYW4gMCAodGhpcyBpcyBiZWNhdXNlIHlvdSBjYW4ndCB0YWtlIHRoZSBsb2cgb2YgMCkNCg0KQW5kIGFub3RoZXIgdGhpbmcgdG8gbm90ZSBpcyB0aGF0IHRoZSBtb2RlbHMgaW4gdGhpcyBBdXN0cmFsaWFuIGV4YW1wbGUgYXNzdW1lZCB0aGF0IHRoZSBmbG93IGRhdGEgYW5kIHByZWRpY3RvcnMgd2VyZSBhbGwgaW4gYW5kIGFyb3VuZCB0aGUgc2FtZSBvcmRlciBvciBtYWduaXR1ZGUuIFRoaXMgaXMgbm90IG5lY2Vzc2FyaWx5IHRoZSBjYXNlLCBwYXJ0aWN1bGFybHkgd2l0aCBhIDUgeWVhciBtaWdyYXRpb24gdHJhbnNpdGlvbiBhbmQgc29tZSBsYXJnZSBtZXRyb3BvbGl0YW4gYXJlYXMuIFdoZXJlIGRhdGEgdGhhdCAoc3VjaCBhcyBwb3B1bGF0aW9uIG1hc3NlcyBhdCBvcmlnaW5zIGFuZCBkZXN0aW5hdGlvbnMpIHRoYXQgYXJlIGFuIG9yZGVyIG9mIG1hZ25pdHVkZSBkaWZmZXJlbnQgKGkuZS7CoHBvcHVsYXRpb25zIGFib3V0IHRlbiB0aW1lcyBsYXJnZXIgaW4gZGlmZmVyZW50IGxvY2F0aW9ucykgdGhlbiB0aGUgbW9kZWwgZXN0aW1hdGVzIG1pZ2h0IGJlIGJpYXNlZC4gRm9ydHVuYXRlbHksIHRoZXJlIGFyZSBwYWNrYWdlcyBhdmFpbGFibGUgdG8gaGVscCB1cyB3aXRoIHRoZXNlIHByb2JsZW1zIGFzIHdlbGwuIFRoZSBbYHJvYnVzdGJhc2VgIHBhY2thZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yb2J1c3RiYXNlL2luZGV4Lmh0bWwpIGZlYXR1cmVzIGEgZnVuY3Rpb24gY2FsbGVkIGBnbG1yb2JgIHdoaWNoIHdpbGwgZGVhbCB3aXRoIHRoaXMgaXNzdWVzIChhZ2FpbiwgeW91ciByZXN1bHRzIHByb2JhYmx5IHdvbid0IGNoYW5nZSBtdWNoLCBidXQgd29ydGgga25vd2luZykuDQoNCioqRnVydGhlciBSZWFkaW5nKiogPGh0dHBzOi8vd3d3LnJlc2VhcmNoZ2F0ZS5uZXQvcHVibGljYXRpb24vMjU1NTc2NTE1X0RFVkVMT1BJTkdfVEhFX1NJTkdMWV9DT05TVFJBSU5FRF9HUkFWSVRZX01PREVMX0ZPUl9BUFBMSUNBVElPTl9JTl9ERVZFTE9QSU5HX0NPVU5UUklFUz4NCg0KLSAgIEFiZWwgRyBKICgyMDEwKSBFc3RpbWF0aW9uIG9mIGludGVybmF0aW9uYWwgbWlncmF0aW9uIGZsb3cgdGFibGVzIGluIEV1cm9wZTogaW50ZXJuYXRpb25hbCBtaWdyYXRpb24gZmxvdyB0YWJsZXMuIEpvdXJuYWwgb2YgdGhlIFJveWFsIFN0YXRpc3RpY2FsIFNvY2lldHkuIFNlcmllcyBBLCBTdGF0aXN0aWNzIGluIFNvY2lldHkgMTczKDQpOiA3OTctLTgyNS4NCg0KLSAgIENvbmdkb24gUCAoMTk5MykgQXBwcm9hY2hlcyB0byBtb2RlbGxpbmcgb3ZlcmRpc3BlcnNpb24gaW4gdGhlIGFuYWx5c2lzIG9mIG1pZ3JhdGlvbi4gRW52aXJvbm1lbnQgYW5kIFBsYW5uaW5nIEE6IEVjb25vbXkgYW5kIFNwYWNlIDI1KDEpOiAxNDgxLS0xNTEwLg0KDQotICAgQ29uZ2RvbiBQICgxOTg4KSBNb2RlbGxpbmcgbWlncmF0aW9uIGZsb3dzIGJldHdlZW4gYXJlYXM6IGFuIGFuYWx5c2lzIGZvciBMb25kb24gdXNpbmcgdGhlIGNlbnN1cyBhbmQgT1BDUyBMb25naXR1ZGluYWwgU3R1ZHkuIFJlZ2lvbmFsIFN0dWRpZXMgMjMoMik6IDg3LS0xMDMuDQoNCi0gICBDcnltYmxlIEEsIERlbm5ldHQgQSBhbmQgSGl0Y2hjb2NrIFQgKDIwMTcpIE1vZGVsbGluZyByZWdpb25hbCBpbWJhbGFuY2VzIGluIEVuZ2xpc2ggcGxlYmVpYW4gbWlncmF0aW9uIHRvIGxhdGUgZWlnaHRlZW50aC1jZW50dXJ5IExvbmRvbi4gVGhlIEVjb25vbWljIEhpc3RvcnkgUmV2aWV3IDcxKDMpOiA3NDctNzcxLg0KDQotICAgRGVubmV0dCBBIGFuZCBXaWxzb24gQSAoMjAxMykgQSBtdWx0aS1sZXZlbCBzcGF0aWFsIGludGVyYWN0aW9uIG1vZGVsbGluZyBmcmFtZXdvcmsgZm9yIGVzdGltYXRpbmcgaW50ZXItcmVnaW9uYWwgbWlncmF0aW9uIGluIEV1cm9wZS4gRW52aXJvbm1lbnQgYW5kIFBsYW5uaW5nIEE6IEVjb25vbXkgYW5kIFNwYWNlIDQ1KDYpOiAxNDkxLS0xNTA3Lg0KDQotICAgRXJoYXJkdCBHIEQgYW5kIERlbm5ldHQgQSAoMjAxNykgVW5kZXJzdGFuZGluZyB0aGUgcm9sZSBhbmQgcmVsZXZhbmNlIG9mIHRoZSBjZW5zdXMgaW4gYSBjaGFuZ2luZyB0cmFuc3BvcnRhdGlvbiBkYXRhIGxhbmRzY2FwZS4gUGFwZXIgcHJlc2VudGVkIGF0IHRoZSBUcmFuc3BvcnRhdGlvbiBSZXNlYXJjaCBCb2FyZCBDb25mZXJlbmNlIG9uIEFwcGx5aW5nIENlbnN1cyBEYXRhIGZvciBUcmFuc3BvcnRhdGlvbiwgS2Fuc2FzIENpdHksIE1pc3NvdXJpLg0KDQotICAgRmxvd2VyZGV3IFIgKDIwMTApIE1vZGVsbGluZyBtaWdyYXRpb24gd2l0aCBQb2lzc29uIHJlZ3Jlc3Npb24uIEluOiBTdGlsbHdlbGwgSiwgRHVrZS1XaWxsaWFtcyBPIGFuZCBEZW5uZXR0IEEgKGVkcykgVGVjaG5vbG9naWVzIGZvciBNaWdyYXRpb24gYW5kIENvbW11dGluZyBBbmFseXNpczogU3BhdGlhbCBJbnRlcmFjdGlvbiBEYXRhIEFwcGxpY2F0aW9ucy4gSGVyc2hleSBQQTogSUdJIEdsb2JhbC4NCg0KLSAgIEZsb3dlcmRldyBSICgxOTgyKSBGaXR0aW5nIHRoZSBsb2dub3JtYWwgZ3Jhdml0eSBtb2RlbCB0byBoZXRlcm9zY2VkYXN0aWMgZGF0YS4gR2VvZ3JhcGhpY2FsIEFuYWx5c2lzIDE0KDMpOiAyNjMtLTI2Ny4NCg0KLSAgIEZsb3dlcmRldyBSIGFuZCBBaXRraW4gTSAoMTk4MikgQSBtZXRob2Qgb2YgZml0dGluZyB0aGUgZ3Jhdml0eSBtb2RlbCBiYXNlZCBvbiB0aGUgUG9pc3NvbiBkaXN0cmlidXRpb24uIEpvdXJuYWwgb2YgUmVnaW9uYWwgU2NpZW5jZSAyMigyKTogMTkxLS0yMDIuDQoNCi0gICBGb3RoZXJpbmdoYW0gQSBTICgxOTgzKSBBIG5ldyBzZXQgb2Ygc3BhdGlhbC1pbnRlcmFjdGlvbiBtb2RlbHM6IHRoZSB0aGVvcnkgb2YgY29tcGV0aW5nIGRlc3RpbmF0aW9ucy4NCg0KLSAgIEVudmlyb25tZW50IGFuZCBQbGFubmluZyBBOiBFY29ub215IGFuZCBTcGFjZSAxNSgxKTogMTUtLTM2Lg0KICAgIEZvdGhlcmluZ2hhbSBBIFMsIE5ha2F5YSBULCBZYW5vIEssIE9wZW5zaGF3IFMgYW5kIElzaGlrYXdhIFkgKDIwMDEpIEhpZXJhcmNoaWNhbCBkZXN0aW5hdGlvbiBjaG9pY2UgYW5kIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxsaW5nOiBhIHNpbXVsYXRpb24gZXhwZXJpbWVudC4gRW52aXJvbm1lbnQgYW5kIFBsYW5uaW5nIEE6IEVjb25vbXkgYW5kIFNwYWNlIDMzKDUpOiA5MDEtLTkyMC4NCg0KLSAgIEtpbSBLIGFuZCBDb2hlbiBKIEUgKDIwMTApIERldGVybWluYW50cyBvZiBpbnRlcm5hdGlvbmFsIG1pZ3JhdGlvbiBmbG93cyB0byBhbmQgZnJvbSBpbmR1c3RyaWFsaXplZCBjb3VudHJpZXM6IGEgcGFuZWwgZGF0YSBhcHByb2FjaCBiZXlvbmQgZ3Jhdml0eS4gSW50ZXJuYXRpb25hbCBNaWdyYXRpb24gUmV2aWV3IDQ0KDQpOiA4OTktLTkzMi4NCg0KLSAgIExlZSBFIFMgKDE5NjYpIEEgdGhlb3J5IG9mIG1pZ3JhdGlvbi4gRGVtb2dyYXBoeSAzKDEpOiA0Ny0tNTcuDQoNCi0gICBMb21heCBOIGFuZCBOb3JtYW4gUCAoMjAxNikgRXN0aW1hdGluZyBwb3B1bGF0aW9uIGF0dHJpYnV0ZSB2YWx1ZXMgaW4gYSB0YWJsZTogImdldCBtZSBzdGFydGVkIGluIiBpdGVyYXRpdmUgcHJvcG9ydGlvbmFsIGZpdHRpbmcuIFRoZSBQcm9mZXNzaW9uYWwgR2VvZ3JhcGhlciA2OCgzKTogNDUxLS00NjEuDQoNCi0gICBMb3ZlbGFjZSBSICgyMDE1KSBFc3RpbWF0aW5nIGRpc3RhbmNlIGRlY2F5IGZvciB0aGUgbmF0aW9uYWwgcHJvcGVuc2l0eSB0byBjeWNsZSB0b29sLiA8aHR0cHM6Ly93d3cuc2xpZGVzaGFyZS5uZXQvSVRTTGVlZHMvZXN0aW1hdGluZy1kaXN0YW5jZS1kZWNheS1mb3ItdGhlLW5hdGlvbmFsLXByb3BlbnNpdHl0by1jeWNsZS10b29sLj4NCg0KLSAgIE9zaGFuIFQgTSAoMjAxNikgQSBwcmltZXIgZm9yIHdvcmtpbmcgd2l0aCB0aGUgU3BhdGlhbCBJbnRlcmFjdGlvbiBtb2RlbGluZyAoU3BJbnQpIG1vZHVsZSBpbiB0aGUgcHl0aG9uIHNwYXRpYWwgYW5hbHlzaXMgbGlicmFyeSAoUHlTQUwpLiBSRUdJT04gMzogUjExLS1SMjMuDQoNCi0gICBQb29sZXIgSiAoMTk5NCkgQW4gZXh0ZW5kZWQgZmFtaWx5IG9mIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWxzLiBQcm9ncmVzcyBpbiBIdW1hbiBHZW9ncmFwaHkgMTgoMSk6IDE3LS0zOS4NCg0KLSAgIFBvb2xlciBKICgxOTg3KSBNb2RlbGluZyBpbnRlcnByb3ZpbmNpYWwgbWlncmF0aW9uIHVzaW5nIGVudHJvcHktbWF4aW1pemluZyBtZXRob2RzLiBUaGUgQ2FuYWRpYW4gR2VvZ3JhcGhlciAzMSgxKTogNTctLTY0Lg0KDQotICAgUmF5bWVyIEogKDIwMDcpIFRoZSBlc3RpbWF0aW9uIG9mIGludGVybmF0aW9uYWwgbWlncmF0aW9uIGZsb3dzOiBhIGdlbmVyYWwgdGVjaG5pcXVlIGZvY3VzZWQgb24gdGhlIG9yaWdpbi0tZGVzdGluYXRpb24gYXNzb2NpYXRpb24gc3RydWN0dXJlLiBFbnZpcm9ubWVudCBhbmQgUGxhbm5pbmcgQTogRWNvbm9teSBhbmQgU3BhY2UgMzkoNCk6IDk4NS0tOTk1Lg0KDQotICAgUmF5bWVyIEogYW5kIEFiZWwgRyAoMjAwOCkgTWV0aG9kcyB0byBpbXByb3ZlIGVzdGltYXRlcyBvZiBtaWdyYXRpb24gZmxvd3MgLS0gdGhlIE1JTU9TQSBtb2RlbCBmb3IgZXN0aW1hdGluZyBpbnRlcm5hdGlvbmFsIG1pZ3JhdGlvbiBmbG93cyBpbiB0aGUgRXVyb3BlYW4gVW5pb24uIFVORUNFL0V1cm9zdGF0IHdvcmsgc2Vzc2lvbiBvbiBtaWdyYXRpb24gc3RhdGlzdGljcywgV29ya2luZyBQYXBlciA4LCBHZW5ldmEuDQoNCi0gICBSYXltZXIgSiwgQWJlbCBHIGFuZCBTbWl0aCBQIFcgKDIwMDcpIENvbWJpbmluZyBjZW5zdXMgYW5kIHJlZ2lzdHJhdGlvbiBkYXRhIHRvIGVzdGltYXRlIGRldGFpbGVkIGVsZGVybHkgbWlncmF0aW9uIGZsb3dzIGluIEVuZ2xhbmQgYW5kIFdhbGVzLiBKb3VybmFsIG9mIHRoZSBSb3lhbCBTdGF0aXN0aWNhbCBTb2NpZXR5LiBTZXJpZXMgQSwgU3RhdGlzdGljcyBpbiBTb2NpZXR5IDE3MCg0KTogODkxLS05MDguDQoNCi0gICBSYXltZXIgSiBhbmQgR2l1bGlldHRpIEMgKDIwMTApIEFuYWx5c2luZyBzdHJ1Y3R1cmVzIG9mIGludGVycmVnaW9uYWwgbWlncmF0aW9uIGluIEVuZ2xhbmQuIEluOiBTdGlsbHdlbGwgSiwgRHVrZS1XaWxsaWFtcyBPIGFuZCBEZW5uZXR0IEEgKGVkcykgVGVjaG5vbG9naWVzIGZvciBNaWdyYXRpb24gYW5kIENvbW11dGluZyBBbmFseXNpczogU3BhdGlhbCBJbnRlcmFjdGlvbiBEYXRhIEFwcGxpY2F0aW9ucy4gSGVyc2hleSBQQTogSUdJIEdsb2JhbC4NCg0KLSAgIFJlZXMgUCAoMTk3NykgVGhlIG1lYXN1cmVtZW50IG9mIG1pZ3JhdGlvbiBmcm9tIGNlbnN1cyBkYXRhIGFuZCBvdGhlciBzb3VyY2VzLiBFbnZpcm9ubWVudCBhbmQgUGxhbm5pbmcgQTogRWNvbm9teSBhbmQgU3BhY2UgOSgzKTogMjU3LS0yODAuDQoNCi0gICBSb2dlcnMgQSBhbmQgUmF5bWVyIEogKDE5OTgpIFRoZSBzcGF0aWFsIGZvY3VzIG9mIFVTIGludGVyc3RhdGUgbWlncmF0aW9uIGZsb3dzLiBQb3B1bGF0aW9uLCBTcGFjZSBhbmQgUGxhY2UgNCgxKTogNjMtLTgwLg0KDQotICAgU2VuaW9yIE0gTCAoMTk3OSkgRnJvbSBncmF2aXR5IG1vZGVsbGluZyB0byBlbnRyb3B5IG1heGltaXppbmc6IGEgcGVkYWdvZ2ljIGd1aWRlLiBQcm9ncmVzcyBpbiBIdW1hbiBHZW9ncmFwaHkgMygyKTogMTc1LS0yMTAuDQoNCi0gICBTaGVuIEogKDIwMTcpIE1vZGVsbGluZyBpbnRlcnJlZ2lvbmFsIG1pZ3JhdGlvbiBpbiBDaGluYSBpbiAyMDA1LS0yMDEwOiB0aGUgcm9sZXMgb2YgcmVnaW9uYWwgYXR0cmlidXRlcyBhbmQgc3BhdGlhbCBpbnRlcmFjdGlvbiBlZmZlY3RzIGluIG1vZGVsbGluZyBlcnJvci4gUG9wdWxhdGlvbiwgU3BhY2UgYW5kIFBsYWNlIDIzKDMpOiBlMjAxNC4NCg0KLSAgIFNoZW4gSiAoMjAxNSkgRXhwbGFpbmluZyBpbnRlcnJlZ2lvbmFsIG1pZ3JhdGlvbiBjaGFuZ2VzIGluIENoaW5hLCAxOTg1LS0yMDAwLCB1c2luZyBhIGRlY29tcG9zaXRpb24gYXBwcm9hY2guIFJlZ2lvbmFsIFN0dWRpZXMgNDkoNyk6IDExNzYtLTExOTIuDQoNCi0gICBTdGlsbHdlbGwgSiAoMTk3OCkgSW50ZXJ6b25hbCBtaWdyYXRpb246IHNvbWUgaGlzdG9yaWNhbCB0ZXN0cyBvZiBzcGF0aWFsLWludGVyYWN0aW9uIG1vZGVscy4NCg0KLSAgIEVudmlyb25tZW50IGFuZCBQbGFubmluZyBBOiBFY29ub215IGFuZCBTcGFjZSAxMCgxKTogMTE4Ny0tMTIwMC4NCg0KLSAgIFRheWxvciBQIEogKDE5ODMpIERpc3RhbmNlIGRlY2F5IGluIHNwYXRpYWwgaW50ZXJhY3Rpb25zLiBOb3J3aWNoOiBHZW8gQm9va3MuDQoNCi0gICBXaWxsZWtlbnMgRiAoMTk5OSkgTW9kZWxpbmcgYXBwcm9hY2hlcyB0byB0aGUgaW5kaXJlY3QgZXN0aW1hdGlvbiBvZiBtaWdyYXRpb24gZmxvd3M6IGZyb20gZW50cm9weSB0byBFTS4gTWF0aGVtYXRpY2FsIFBvcHVsYXRpb24gU3R1ZGllcyA3KDMpOiAyMzktLTI3OC4NCg0KLSAgIFdpbHNvbiBBICgxOTcxKSBBIGZhbWlseSBvZiBzcGF0aWFsIGludGVyYWN0aW9uIG1vZGVscywgYW5kIGFzc29jaWF0ZWQgZGV2ZWxvcG1lbnRzLiBFbnZpcm9ubWVudCBhbmQgUGxhbm5pbmcgQTogRWNvbm9teSBhbmQgU3BhY2UgMygxKTogMS0tMzIuDQoNCi0gICBaaXBmIEcgSyAoMTk0NikgVGhlIFAxIFAyIC8gRCBoeXBvdGhlc2lzOiBvbiB0aGUgaW50ZXJjaXR5IG1vdmVtZW50IG9mIHBlcnNvbnMuIEFtZXJpY2FuIFNvY2lvbG9naWNhbCBSZXZpZXcgMTEoNik6IDY3Ny0tNjg2DQo=