Autocorrelation Analysis

Homework #3


Markdown Author: Jessie Bell

Download this Rmd: Top right corner → Code → Download Rmd

🦜 Bird Richness in Mexico

About the birds

birdRichnessMexico.rds contains points of bird richness (# of species) at a bunch of points in Mexico. The column nSpecies is the number of bird species occurring within a km of that location.

Projection: North American Albers Equal Area Conic

Center: Mexico (SR-ORG:38)

Units: Kilometers (km)

Simple map of the data are shown below:

birds_sf <- readRDS("birdRichnessMexico.rds")

tmp_Mexico <- data.frame(st_coordinates(st_transform(birds_sf, crs=4326)), nSpecies=birds_sf$nSpecies) #started on Andy's code and then needed API key for google and have no time to mess.


tmap_mode("view")
tm_basemap(c(StreetMap = "OpenStreetMap",
  TopoMap = "OpenTopoMap"))+
tm_shape(birds_sf) +
  tm_symbols(col="nSpecies",alpha = 0.7, palette = rev(hcl.colors(5, "Magenta")))

In the map above, you can see that species richness seems to be highest in southern Mexico, in the northern half – species richness is greatest along the midwest, a bit off from the coast. After adding the topo layer into the tmap, I noticed that species richness increases in areas of lower elevation and nearest a mountain range.

Spatial Autocorrelation

I. Cloud Variogram

In order to determine which points in birdsRichnessMexico.rds are correlated, I will use the sf simple features to plug into a cloud variogram first.

birdVarCloud <- variogram(nSpecies~1, birds_sf, cloud=T)

plot(birdVarCloud, pch=20, cex=1.5, col="#ff9e9e", alpha=0.1, ylab=expression(Semivariance~(gamma)), xlab="Distance (km)", main = "Species Richness")

Figure 1: On the x-axis of the plot is the distance between the locations, and on the y-axis is the difference of their nSpecies values squared. That is, each dot in the variogram represents a pair of locations, not the individual locations on the map. This is a bit messy, the points from about 40,000 and above are not spatially autocorrelated. To view this more clearly, we can get an average model value for the points (essentially binning them).

II. Average Model Value Variogram
birdVar <- variogram(nSpecies~1, birds_sf, cloud = FALSE)
plot(birdVar,pch=20,cex=1.5,col="#8aff9c",
     ylab=expression(Semivariance~(gamma)),
     xlab="Distance (km)", main = "Bird Species Richness")
**Figure 2:** Average model values for autocorrelation of species richness. Here you can see that bird species richness in Mexico seems to be autocorrelated out to a distance of about 700 km, because γ levels off and is larger in number at that distance.

Figure 2: Average model values for autocorrelation of species richness. Here you can see that bird species richness in Mexico seems to be autocorrelated out to a distance of about 700 km, because γ levels off and is larger in number at that distance.

III. Moran’s I

First, the test:

d <- as.matrix(dist(st_coordinates(birds_sf)))
# make an empty weights matrix of the right dimensions
w <- matrix(0,ncol=ncol(d),nrow=nrow(d))
# set some values to 1
w[d>500 & d<=1000] <- 1
# convert a the weights matrix to a weights list object
# so spdep is happy
wList <- mat2listw(w)
# calculate I
moran.test(birds_sf$nSpecies,wList)
## 
##  Moran I test under randomisation
## 
## data:  birds_sf$nSpecies  
## weights: wList    
## 
## Moran I statistic standard deviate = 15.127, p-value < 2.2e-16
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##      1.451094e-01     -5.025126e-03      9.851016e-05

For this, I am going to use the ncf library. I am going to use the distance rule to determine how far out to go on this graph:

birdX <- st_coordinates(birds_sf)[,1]
birdY <- st_coordinates(birds_sf)[,2]
birdRichness <- birds_sf$nSpecies

max(birdX) - min(birdX)
## [1] 2808.024
## [1] 2808.024
max(birdY) - min(birdY)
## [1] 1824.946
## [1] 1824.946

birdD <- dist(cbind(birdX,birdY))
max(birdD)/3
## [1] 1037.547
birdI <- correlog(x=birdX, y=birdY, z=birdRichness,
                  increment=100, resamp=100, quiet=TRUE)

The furthest point-to-point distance is about 3100 km, and 1/3 of that distance is about 1000 km. I will only interpret distances of 1000 km in the following graphs.

IV. Discrete Correlogram
bird_df <- data.frame(n=birdI$n, 
           I=birdI$correlation, 
           d = birdI$mean.of.class, 
           p=birdI$p)


bird_df %>%
  mutate(Significant = p < 0.1) %>%
ggplot() +
  geom_hline(yintercept = 0,linetype="dashed") +
  geom_path(aes(x = d, y = I, group = 1, color=Significant),size=1) + 
  geom_point(aes(x = d, y = I, fill=Significant),size=5,pch=21) +
  lims(x=c(0,1000),y=c(-0.6,0.6)) +
  scale_fill_manual(values = c("#fd4a09","#aed601")) +
  scale_color_manual(values = c("#fd4a09","#aed601")) +
  labs(x="Distance (km)",y="Moran's I",
       title = "Autocorrelation of Bird Species",subtitle = "Critical value of p<0.01")+
  theme_classic()
**Figure 3:** Correlogram for bird richness in Mexico. You can see that there seems to be significant spatial autocorrelation at distances of 1000 km.

Figure 3: Correlogram for bird richness in Mexico. You can see that there seems to be significant spatial autocorrelation at distances of 1000 km.

V. Density Plot for funsies
ggplot(bird_df, aes(d, I))+
  stat_density_2d(aes(fill = ..level..), geom = "polygon")+
  scale_fill_distiller(palette= "Set2", direction=1)+
    geom_point(bird_df, mapping=aes(x=d, y=I), alpha=0.7, color="#fd4a09")+
      theme_classic()
**Figure 4:** Attempting to get a ggplotly like Andy's and will revisit this if I have more time.

Figure 4: Attempting to get a ggplotly like Andy’s and will revisit this if I have more time.

Interpret

Question: What can you learn by comparing the approaches? Maybe more importantly what are we missing with this approach? Where does this understanding of pattern help us with process and where is it lacking?

Answer: I had to skip ahead and come back to this because I didn’t know how to answer it. I think that this approach does not include directionality and we could benefit from including it (as we did in the next part).

Directional Variograms

birdVar <- variogram(nSpecies~1, data = birds_sf, alpha=c(0, 90))
plot(birdVar)

Put on a biogeography hat and explain what might be happening here.

Answer: I think that the anisotropy (the fact that there is higher species richness on the southern half of Mexico) of the data is because of the rain shadow falling on the eastern side of the Sierra Madras. The increase in rain likely allows an increase in plant diversity as well as an increase in bird diversity. You can see above that points in southern part of Mexico (90°) have higher spatial autocorrelation as well.

🧁 Andy’s Tutorial

Distance Matrices

Imagine we have two points with locations specified by x and y, we can find the distance between them using the Euclidian Distance:

\(D =\) \(\sqrt {(x_2 - x_1)^2 + (y_2 - y_1)^2}\)

Now, calculate pairwise distances for a set of points:

x <- c(.45, .375, .45, .678, .55)
y <- c(.02, .87, .47, .9, .55)

dat <- data.frame(x,y)


distancematrix <- dist(dat)

#          1         2
#2 0.8533024          
#3 0.4500000 0.4069705
#4 0.9090567 0.3044815
#5 0.5393515 0.3647259
#          3         4
#2                    
#3                    
#4 0.4867073          
#5 0.1280625 0.3726714

Here you can see that there are \(\frac{n\times(n-1)}2\) pairwise elements of D. \((5\times\frac{4}2 = 10)\) So there are 10 pairs of values. Thus…the distance between the first and second points can be calculated as follows:

Now let’s add some attributes to those points!

Meuse River

data(meuse.all)

meuse_df <- data.frame(Variables=colnames(meuse.all))



gt(head(meuse_df))|>
  opt_table_font()|>
  opt_table_outline(style="solid")|>
  tab_header(
    title = "Meuse River Variables")|>
  opt_stylize(style = 5, color = "cyan")|>
  tab_options(table.align='center', table.font.size = px(10))
Meuse River Variables
Variables
sample
x
y
cadmium
copper
lead

Transform the data into an sf object:

meuse_sf <- st_as_sf(meuse.all, coords = c("x","y")) |>
  st_set_crs(value = 28992)

And make a plot of the lead concentation in the soils.

ggplot(data=meuse_sf)+
  geom_sf(mapping=aes(fill=lead, size=lead), shape=21, alpha=0.6)+
  scale_fill_continuous(type="viridis", name="ppm")

tmap_mode('view')
tm_shape(meuse_sf) +
  tm_dots(col = "lead", palette = "viridis") #try tmap too here

Variograms

For determining spatial autocorrelation. Semivariograms can be used for both descriptive statistics and predictive statistics.

Semi-variance: how strongly does an observed observation (i.e. lead concentration) vary as a function of distance. The y axes on these are the Euclidean distance values between the points (in this case it is the lead concentration).

leadVarCloud <- variogram(lead~1, meuse_sf, cloud=T)

plot(leadVarCloud, pch=20, cex=1.5, col="plum", alpha=0.1, ylab=expression(Semivariance~(gamma)), xlab="Distance (m)", main = "Lead concentrations (ppm)")

Things that are more correlated in a semi-variogram are the smaller value points

The variogram cloud above (This shows majority of the noise (all the upper translucent points)), but has too much data on it to synthesize, so we average them into the next graph shown.

leadVar <- variogram(lead~1, meuse_sf, cloud=F)

plot(leadVar, pch=20, cex=1.5, col="darkmagenta", 
     ylab=expression(Semivariance~(gamma)), xlab="Distance (m)", main="Lead concentrations (ppm)")

This graph has way less noise than the cloud. You could say that the lead concentrations in this dataset are autocorlated out to a distance of about 750 m because \(\gamma\) is small at distances less than 750 m before tappering off a bit. We will build models for this soon. These models normalize the semivariogram into something that ranges from -1 to 1. This is called, Moran’s I.

Moran’s I

This has really nice probablity properties, which is why we use it.

\(I =\) \(\frac{N}{\Sigma_i \Sigma_jw_{ij}}\) \(\frac{\Sigma_i \Sigma_j w_{ij}(Z_i-\bar{Z})(Z_j - \bar{Z})}{\Sigma_i(Z_i-\bar{Z})^2}\)

\(Z\) is the variable being measured, and \(w\) is a matrix of weights (i.e. the inverse of the distance between points).

Weights

This is similar to the boolean yes/no when turning certain functions on or off. Elements that are further away from one another will have a smaller weight. You can transform Moran’s I into a Z score for hypothesis testing. This means that a Z-score of > 1.96 or < -1.96 would mean there is significant spatial autocorrelation with \(\alpha=0.05\)

spdep
# distance matrix 
d <- dist(st_coordinates(meuse_sf))
# inverse distance matrix
w <- as.matrix(1/d)
# convert a the weights matrix to a weights list object
# so spdep is happy
wList <- mat2listw(w)
# calculate I
moran.test(meuse_sf$lead,wList)
## 
##  Moran I test under randomisation
## 
## data:  meuse_sf$lead  
## weights: wList    
## 
## Moran I statistic standard deviate = 10.687, p-value < 2.2e-16
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##      0.1009726698     -0.0061349693      0.0001004419

These results indicate the lead data are positive (but very weakly) autocorrelated when we use \(W\) as inverse distances. As we saw previously though, values above 1000 m don’t actually have a lot of influence on one another. The relationship between distance and inverse distance is:

x <- as.vector(as.matrix((dist(st_coordinates((meuse_sf))))))


y <- as.vector(w)

ggplot()+
  geom_line(aes(x=x[x>0], y=y[x>0]), color="pink")+
  labs(x="Distance (m)", y="Inverse distance (W)")+
  lims(x=c(0,1500))+
  theme_classic()

Set Distance

Let’s look at Moran’s I when points are within 100 m of each other.

d <- as.matrix(dist(st_coordinates(meuse_sf)))
# make an empty weights matrix
w <- matrix(0,ncol=ncol(d),nrow=nrow(d))
# set some values to 1 using a logical mask of d<100
w[d<100] <- 1
# convert a the weights matrix to a weights list object
# so spdep is happy
wList <- mat2listw(w)
# calculate I
moran.test(meuse_sf$lead,wList)
## 
##  Moran I test under randomisation
## 
## data:  meuse_sf$lead  
## weights: wList    
## 
## Moran I statistic standard deviate = 9.4272, p-value < 2.2e-16
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##       0.784497747      -0.006134969       0.007033667

Note: The Moran’s I at smaller distances is quite high…indicating high autocorrelation for lead concentrations. Let’s try more distances. Let’s look at only points between 500 and 1000 m of one another.

# make an empty weights matrix of the right dimensions
w <- matrix(0,ncol=ncol(d),nrow=nrow(d))
# set some values to 1
w[d>500 & d<=1000] <- 1
# convert a the weights matrix to a weights list object
# so spdep is happy
wList <- mat2listw(w)
# calculate I
moran.test(meuse_sf$lead,wList)
## 
##  Moran I test under randomisation
## 
## data:  meuse_sf$lead  
## weights: wList    
## 
## Moran I statistic standard deviate = -6.6794, p-value = 1
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##     -0.1077445658     -0.0061349693      0.0002314141
Moran’s Correlogram

This shows Moran’s I as a correlogram by distances 0 to 1500 m in 200 m bins:

distanceInterval <- 200
distanceVector <- seq(0,2000,by=distanceInterval)
n <- length(distanceVector)
d <- as.matrix(dist(st_coordinates(meuse_sf)))
# make an object to hold results
res <- data.frame(midBin=rep(NA,n-1),I=rep(NA,n-1))
for(i in 2:n){
  w <- matrix(0,ncol=ncol(d),nrow=nrow(d))
  # set some values to 1
  w[d >= distanceVector[i-1] & d < distanceVector[i]] <- 1
  # convert a the weights matrix to a weights list object
  # so spdep is happy
  wList <- mat2listw(w)
  # calculate I
  res$I[i-1] <- moran.test(meuse_sf$lead,wList,zero.policy=TRUE)$estimate[1]
  # centered distance bin
  res$midBin[i-1] <- distanceVector[i] - distanceInterval/2
}
ggplot(data=res, mapping = aes(x=midBin,y=I)) + 
  geom_hline(yintercept = 0, linetype="dashed") +
  geom_line() + geom_point(size=3) +
  labs(x="Distance (m)",y="Moran's I", title = "Moran's I as function of distance")

Above, you can see that the values of lead are autocorrelated for a few hundred m and then falls off. This is conceptually similar to Ripley’s K.

Polygons

Often, and especially with areal data, we calculate the weights matrix using just the correlation between the \(k\) closest neighbors and usually set \(k=8\) (there is nothing magical about eight – it is a loose kind of rule).

w <- knn2nb(knearneigh(meuse_sf, k=8)) #knearneigh tells you the nearest neighbors for each point and then turns the object into an nb with knn2nb. 

moran.test(meuse_sf$lead, nb2listw(w)) #the nb2listw function takes in an nb object and makes it useable in the moran.test function. 
## 
##  Moran I test under randomisation
## 
## data:  meuse_sf$lead  
## weights: nb2listw(w)    
## 
## Moran I statistic standard deviate = 11.512, p-value < 2.2e-16
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##       0.406297811      -0.006134969       0.001283454

But what do we see here? If use the eight nearest neighbors to calculate spatial autocorrelation the value of Moran’s I jumps way up. Near is like near when it comes to lead. Kind of cool. If we looked at 16 neighbors we’d see Moran’s I of 0.28 which is still pretty high. It we looked at 32 neighbors? Moran’s I of 0.123. So, more neighbors, greater distances we get declining values. See where I’m going with this?

n <- 7
res <- data.frame(k=2^(1:n),I=rep(NA,n))
for(i in 1:n){
  w <- knn2nb(knearneigh(meuse_sf,k=2^i))
  res$I[i] <- moran.test(meuse_sf$lead,nb2listw(w))$estimate[1]
}
ggplot(data=res, mapping = aes(x=k,y=I)) + 
  geom_hline(yintercept = 0, linetype="dashed") +
  geom_line() + geom_point(size=3) +
  labs(x="K neighbors",y="Moran's I")

That’s a Moran’s I a correlogram by the number of neighbors. Because these are points, I prefer to look at Moran’s I in a correlogram by distance – like we did above but if we had to deal with polygonal data, we’d likely use this approach. See text for more info.

ncf

The spdep library is fantastic but doesn’t produce the correlogram in the way we often see it with Moran’s I by distance \((I=f(d))\). The ncf library does this out of the box.

There are two functions we will look at to do this. One uses distance categories and one makes a continuous function.

meuseX <- st_coordinates(meuse_sf)[,1]
meuseY <- st_coordinates(meuse_sf)[,2]

meuseLead <- meuse_sf$lead
Discrete Correlogram

First, the discrete plot of Moran’s I by distance categories.

leadI <- correlog(x=meuseX, y=meuseY, z=meuseLead,
                  increment=100, resamp=100, quiet=TRUE)
plot(leadI,xlim=c(0,1500))
abline(h=0,lty="dashed")
We still see the same result we did above – positive autocorrelation out to distances of about 500 m, very slight negative autocorrelation around 1000 m, and CSR otherwise.

We still see the same result we did above – positive autocorrelation out to distances of about 500 m, very slight negative autocorrelation around 1000 m, and CSR otherwise.

The above graph calculates \(I=f(d)\) using 100 m increments. There is also a significance test component with 100 permutations of the data to test the null hypothesis that the data above follows CSR. Colored dots above suggest significance (not CSR at those points), and hollow dots suggest insignificant. The test uses a two-sided 5% level and this code is way easier than the for-loop previously used.

Check Distances

Oh, one more thing. Remember how the point pattern functions in spatstat were very careful about not letting you run into edge effects? And variogram is that way too. The correlog function is not so careful! It’s more libertarian its approach.

Here is the whole data set. in the default plot.

plot(leadI)
abline(h=0, lty="dashed")
Here you can see that you get non-significant values with high values of I (a bunch of nonsense). We need to find ways to fix the edge effects.

Here you can see that you get non-significant values with high values of I (a bunch of nonsense). We need to find ways to fix the edge effects.

Deciding Distances

YOU WANT TO LOOK AT DISTANCES LESS THAN \(\frac{1}3\) YOUR MAX SPAN BETWEEN POINTS

Let’s look at the extent of the distances in the data. The coordinates span about 2800 m by 3900 m:

max(meuseX)-min(meuseX)
## [1] 2785
max(meuseY)-min(meuseY)
## [1] 3897
meuseD <- dist(cbind(meuseX, meuseY))
max(meuseD)
## [1] 4440.764

The furthest point to point distance is about 4400 m. So we will decide on distances using this: \(\frac{4400}3 = 1500\) \(m\). Beyond that we actually run into a pairwise comparison and get an edge effect.

Rolling your own plot

Oh, one more thing for the ggplot crowd. Note that all the data for the correlogram is stored in an easy to access fashion in the leadI object. You can roll your own plot in you want in ggplot. And unlike the default plot above, we can control the alpha level for plotting the significance from the permutation test. E.g.,

data.frame(n=leadI$n,
           I = leadI$correlation, 
           d = leadI$mean.of.class,
           p = leadI$p) %>%
  mutate(Significant = p < .01) %>%
  ggplot() +
  geom_hline(yintercept = 0,linetype="dashed") +
  geom_path(aes(x = d, y = I,group = 1, color=Significant),size=1) + 
  geom_point(aes(x = d, y = I, fill=Significant),size=5,pch=21) +
  lims(x=c(0,1500),y=c(-0.6,0.6)) +
  scale_fill_manual(values = c("steelblue","plum")) +
  scale_color_manual(values = c("steelblue","plum")) +
  labs(x="Distance (m)",y="Moran's I",
       title = "Autocorrelation of Lead",subtitle = "Crit value of p<0.01")

Continuous Correlogram

The plot above is discrete. It is showing autocorrelation in distance categories. We can also calculate a continuous function:

leadI <- spline.correlog(x=meuseX, y=meuseY, z=meuse_sf$lead, 
                         resamp=100, xmax=1500, quiet=TRUE)
plot(leadI)

This gives the same picture. You can read up on these functions to learn the subtleties. I’ll unpack this in greater detail in the lecture.

LS0tDQp0aXRsZTogIiAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNzczogc3R5bGUuY3NzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGLCBjYWNoZSA9IFQsIHNpemU9InNtYWxsIikNCmxpYnJhcnkoZ3N0YXQpDQpsaWJyYXJ5KG5jZikNCmxpYnJhcnkoc3BkZXApDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRtYXApDQpsaWJyYXJ5KGd0KQ0KbGlicmFyeShzcCkNCmxpYnJhcnkoZ2dtYXApDQpgYGANCg0KIyMgQXV0b2NvcnJlbGF0aW9uIEFuYWx5c2lzDQoNCiMjIyMgSG9tZXdvcmsgIzMNCg0KPGJyPg0KDQoqKk1hcmtkb3duIEF1dGhvcjoqKiBKZXNzaWUgQmVsbA0KDQp8IA0KDQoqKkRvd25sb2FkIHRoaXMgUm1kOioqIFRvcCByaWdodCBjb3JuZXIg4oaSIENvZGUg4oaSIERvd25sb2FkIFJtZA0KDQp8IA0KDQojIyMgKirwn6acIEJpcmQgUmljaG5lc3MgaW4gTWV4aWNvKiogey50YWJzZXR9DQoNCiMjIyMgQWJvdXQgdGhlIGJpcmRzDQoNCmBgYGJpcmRSaWNobmVzc01leGljby5yZHNgYGAgY29udGFpbnMgcG9pbnRzIG9mIGJpcmQgcmljaG5lc3MgKCMgb2Ygc3BlY2llcykgYXQgYSBidW5jaCBvZiBwb2ludHMgaW4gTWV4aWNvLiBUaGUgY29sdW1uIGBgYG5TcGVjaWVzYGBgIGlzIHRoZSBudW1iZXIgb2YgYmlyZCBzcGVjaWVzIG9jY3VycmluZyB3aXRoaW4gYSBrbSBvZiB0aGF0IGxvY2F0aW9uLg0KDQoqKlByb2plY3Rpb246KiogTm9ydGggQW1lcmljYW4gQWxiZXJzIEVxdWFsIEFyZWEgQ29uaWMgDQoNCioqQ2VudGVyOioqIE1leGljbyAoW1NSLU9SRzozOF0oaHR0cHM6Ly9naXRodWIuY29tL09TR2VvL3NwYXRpYWxyZWZlcmVuY2Uub3JnL2Jsb2IvbWFzdGVyL3NjcmlwdHMvc3Itb3JnLmpzb24pKQ0KDQoqKlVuaXRzOioqIEtpbG9tZXRlcnMgKGttKQ0KDQpTaW1wbGUgbWFwIG9mIHRoZSBkYXRhIGFyZSBzaG93biBiZWxvdzogDQpgYGB7ciBzaW1wbGUgYmlyZG1hcCwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCmJpcmRzX3NmIDwtIHJlYWRSRFMoImJpcmRSaWNobmVzc01leGljby5yZHMiKQ0KDQp0bXBfTWV4aWNvIDwtIGRhdGEuZnJhbWUoc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGJpcmRzX3NmLCBjcnM9NDMyNikpLCBuU3BlY2llcz1iaXJkc19zZiRuU3BlY2llcykgI3N0YXJ0ZWQgb24gQW5keSdzIGNvZGUgYW5kIHRoZW4gbmVlZGVkIEFQSSBrZXkgZm9yIGdvb2dsZSBhbmQgaGF2ZSBubyB0aW1lIHRvIG1lc3MuDQoNCg0KdG1hcF9tb2RlKCJ2aWV3IikNCnRtX2Jhc2VtYXAoYyhTdHJlZXRNYXAgPSAiT3BlblN0cmVldE1hcCIsDQogIFRvcG9NYXAgPSAiT3BlblRvcG9NYXAiKSkrDQp0bV9zaGFwZShiaXJkc19zZikgKw0KICB0bV9zeW1ib2xzKGNvbD0iblNwZWNpZXMiLGFscGhhID0gMC43LCBwYWxldHRlID0gcmV2KGhjbC5jb2xvcnMoNSwgIk1hZ2VudGEiKSkpDQoNCmBgYA0KDQpJbiB0aGUgbWFwIGFib3ZlLCB5b3UgY2FuIHNlZSB0aGF0IHNwZWNpZXMgcmljaG5lc3Mgc2VlbXMgdG8gYmUgaGlnaGVzdCBpbiBzb3V0aGVybiBNZXhpY28sIGluIHRoZSBub3J0aGVybiBoYWxmIC0tIHNwZWNpZXMgcmljaG5lc3MgaXMgZ3JlYXRlc3QgYWxvbmcgdGhlIG1pZHdlc3QsIGEgYml0IG9mZiBmcm9tIHRoZSBjb2FzdC4gQWZ0ZXIgYWRkaW5nIHRoZSB0b3BvIGxheWVyIGludG8gdGhlIGBgYHRtYXBgYGAsIEkgbm90aWNlZCB0aGF0IHNwZWNpZXMgcmljaG5lc3MgaW5jcmVhc2VzIGluIGFyZWFzIG9mIGxvd2VyIGVsZXZhdGlvbiBhbmQgbmVhcmVzdCBhIG1vdW50YWluIHJhbmdlLiANCg0KIyMjIyBTcGF0aWFsIEF1dG9jb3JyZWxhdGlvbg0KDQojIyMjIyBJLiBDbG91ZCBWYXJpb2dyYW0NCg0KSW4gb3JkZXIgdG8gZGV0ZXJtaW5lIHdoaWNoIHBvaW50cyBpbiBgYGBiaXJkc1JpY2huZXNzTWV4aWNvLnJkc2BgYCBhcmUgY29ycmVsYXRlZCwgSSB3aWxsIHVzZSB0aGUgYGBgc2ZgYGAgc2ltcGxlIGZlYXR1cmVzIHRvIHBsdWcgaW50byBhIGNsb3VkIHZhcmlvZ3JhbSBmaXJzdC4gDQpgYGB7cn0NCmJpcmRWYXJDbG91ZCA8LSB2YXJpb2dyYW0oblNwZWNpZXN+MSwgYmlyZHNfc2YsIGNsb3VkPVQpDQoNCnBsb3QoYmlyZFZhckNsb3VkLCBwY2g9MjAsIGNleD0xLjUsIGNvbD0iI2ZmOWU5ZSIsIGFscGhhPTAuMSwgeWxhYj1leHByZXNzaW9uKFNlbWl2YXJpYW5jZX4oZ2FtbWEpKSwgeGxhYj0iRGlzdGFuY2UgKGttKSIsIG1haW4gPSAiU3BlY2llcyBSaWNobmVzcyIpDQpgYGANCg0KDQoqKkZpZ3VyZSAxOioqIE9uIHRoZSB4LWF4aXMgb2YgdGhlIHBsb3QgaXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIGxvY2F0aW9ucywgYW5kIG9uIHRoZSB5LWF4aXMgaXMgdGhlIGRpZmZlcmVuY2Ugb2YgdGhlaXIgYGBgblNwZWNpZXNgYGAgdmFsdWVzIHNxdWFyZWQuIFRoYXQgaXMsIGVhY2ggZG90IGluIHRoZSBgYGB2YXJpb2dyYW1gYGAgcmVwcmVzZW50cyBhIHBhaXIgb2YgbG9jYXRpb25zLCBub3QgdGhlIGluZGl2aWR1YWwgbG9jYXRpb25zIG9uIHRoZSBtYXAuIFRoaXMgaXMgYSBiaXQgbWVzc3ksIHRoZSBwb2ludHMgZnJvbSBhYm91dCA0MCwwMDAgYW5kIGFib3ZlIGFyZSBub3Qgc3BhdGlhbGx5IGF1dG9jb3JyZWxhdGVkLiBUbyB2aWV3IHRoaXMgbW9yZSBjbGVhcmx5LCB3ZSBjYW4gIGdldCBhbiBhdmVyYWdlIG1vZGVsIHZhbHVlIGZvciB0aGUgcG9pbnRzIChlc3NlbnRpYWxseSBiaW5uaW5nIHRoZW0pLg0KDQojIyMjIyBJSS4gQXZlcmFnZSBNb2RlbCBWYWx1ZSBWYXJpb2dyYW0NCg0KDQpgYGB7ciBhdXRvLCBmaWcuY2FwPSIqKkZpZ3VyZSAyOioqIEF2ZXJhZ2UgbW9kZWwgdmFsdWVzIGZvciBhdXRvY29ycmVsYXRpb24gb2Ygc3BlY2llcyByaWNobmVzcy4gSGVyZSB5b3UgY2FuIHNlZSB0aGF0IGJpcmQgc3BlY2llcyByaWNobmVzcyBpbiBNZXhpY28gc2VlbXMgdG8gYmUgYXV0b2NvcnJlbGF0ZWQgb3V0IHRvIGEgZGlzdGFuY2Ugb2YgYWJvdXQgNzAwIGttLCBiZWNhdXNlIM6zIGxldmVscyBvZmYgYW5kIGlzIGxhcmdlciBpbiBudW1iZXIgYXQgdGhhdCBkaXN0YW5jZS4ifQ0KDQpiaXJkVmFyIDwtIHZhcmlvZ3JhbShuU3BlY2llc34xLCBiaXJkc19zZiwgY2xvdWQgPSBGQUxTRSkNCnBsb3QoYmlyZFZhcixwY2g9MjAsY2V4PTEuNSxjb2w9IiM4YWZmOWMiLA0KICAgICB5bGFiPWV4cHJlc3Npb24oU2VtaXZhcmlhbmNlfihnYW1tYSkpLA0KICAgICB4bGFiPSJEaXN0YW5jZSAoa20pIiwgbWFpbiA9ICJCaXJkIFNwZWNpZXMgUmljaG5lc3MiKQ0KDQpgYGANCg0KIyMjIyMgSUlJLiBNb3JhbidzIEkNCg0KRmlyc3QsIHRoZSB0ZXN0Og0KDQpgYGB7ciBtb3JhbnMgaSB0ZXN0fQ0KZCA8LSBhcy5tYXRyaXgoZGlzdChzdF9jb29yZGluYXRlcyhiaXJkc19zZikpKQ0KIyBtYWtlIGFuIGVtcHR5IHdlaWdodHMgbWF0cml4IG9mIHRoZSByaWdodCBkaW1lbnNpb25zDQp3IDwtIG1hdHJpeCgwLG5jb2w9bmNvbChkKSxucm93PW5yb3coZCkpDQojIHNldCBzb21lIHZhbHVlcyB0byAxDQp3W2Q+NTAwICYgZDw9MTAwMF0gPC0gMQ0KIyBjb252ZXJ0IGEgdGhlIHdlaWdodHMgbWF0cml4IHRvIGEgd2VpZ2h0cyBsaXN0IG9iamVjdA0KIyBzbyBzcGRlcCBpcyBoYXBweQ0Kd0xpc3QgPC0gbWF0Mmxpc3R3KHcpDQojIGNhbGN1bGF0ZSBJDQptb3Jhbi50ZXN0KGJpcmRzX3NmJG5TcGVjaWVzLHdMaXN0KQ0KDQpgYGANCg0KDQpGb3IgdGhpcywgSSBhbSBnb2luZyB0byB1c2UgdGhlIGBgYG5jZmBgYCBsaWJyYXJ5LiBJIGFtIGdvaW5nIHRvIHVzZSB0aGUgZGlzdGFuY2UgcnVsZSB0byBkZXRlcm1pbmUgaG93IGZhciBvdXQgdG8gZ28gb24gdGhpcyBncmFwaDogDQoNCmBgYHtyIG1vcmFuczJ9DQpiaXJkWCA8LSBzdF9jb29yZGluYXRlcyhiaXJkc19zZilbLDFdDQpiaXJkWSA8LSBzdF9jb29yZGluYXRlcyhiaXJkc19zZilbLDJdDQpiaXJkUmljaG5lc3MgPC0gYmlyZHNfc2YkblNwZWNpZXMNCg0KbWF4KGJpcmRYKSAtIG1pbihiaXJkWCkNCiMjIFsxXSAyODA4LjAyNA0KbWF4KGJpcmRZKSAtIG1pbihiaXJkWSkNCiMjIFsxXSAxODI0Ljk0Ng0KDQpiaXJkRCA8LSBkaXN0KGNiaW5kKGJpcmRYLGJpcmRZKSkNCm1heChiaXJkRCkvMw0KDQpiaXJkSSA8LSBjb3JyZWxvZyh4PWJpcmRYLCB5PWJpcmRZLCB6PWJpcmRSaWNobmVzcywNCiAgICAgICAgICAgICAgICAgIGluY3JlbWVudD0xMDAsIHJlc2FtcD0xMDAsIHF1aWV0PVRSVUUpDQpgYGANClRoZSBmdXJ0aGVzdCBwb2ludC10by1wb2ludCBkaXN0YW5jZSBpcyBhYm91dCAzMTAwIGttLCBhbmQgMS8zIG9mIHRoYXQgZGlzdGFuY2UgaXMgYWJvdXQgMTAwMCBrbS4gSSB3aWxsIG9ubHkgaW50ZXJwcmV0IGRpc3RhbmNlcyBvZiAxMDAwIGttIGluIHRoZSBmb2xsb3dpbmcgZ3JhcGhzLiANCg0KDQojIyMjIyBJVi4gRGlzY3JldGUgQ29ycmVsb2dyYW0NCg0KYGBge3IgZGljc3JldGUgY29yLCBmaWcuY2FwPSIqKkZpZ3VyZSAzOioqIENvcnJlbG9ncmFtIGZvciBiaXJkIHJpY2huZXNzIGluIE1leGljby4gWW91IGNhbiBzZWUgdGhhdCB0aGVyZSBzZWVtcyB0byBiZSBzaWduaWZpY2FudCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBhdCBkaXN0YW5jZXMgb2YgMTAwMCBrbS4ifQ0KDQoNCmJpcmRfZGYgPC0gZGF0YS5mcmFtZShuPWJpcmRJJG4sIA0KICAgICAgICAgICBJPWJpcmRJJGNvcnJlbGF0aW9uLCANCiAgICAgICAgICAgZCA9IGJpcmRJJG1lYW4ub2YuY2xhc3MsIA0KICAgICAgICAgICBwPWJpcmRJJHApDQoNCg0KYmlyZF9kZiAlPiUNCiAgbXV0YXRlKFNpZ25pZmljYW50ID0gcCA8IDAuMSkgJT4lDQpnZ3Bsb3QoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgZ2VvbV9wYXRoKGFlcyh4ID0gZCwgeSA9IEksIGdyb3VwID0gMSwgY29sb3I9U2lnbmlmaWNhbnQpLHNpemU9MSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeCA9IGQsIHkgPSBJLCBmaWxsPVNpZ25pZmljYW50KSxzaXplPTUscGNoPTIxKSArDQogIGxpbXMoeD1jKDAsMTAwMCkseT1jKC0wLjYsMC42KSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjZmQ0YTA5IiwiI2FlZDYwMSIpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjZmQ0YTA5IiwiI2FlZDYwMSIpKSArDQogIGxhYnMoeD0iRGlzdGFuY2UgKGttKSIseT0iTW9yYW4ncyBJIiwNCiAgICAgICB0aXRsZSA9ICJBdXRvY29ycmVsYXRpb24gb2YgQmlyZCBTcGVjaWVzIixzdWJ0aXRsZSA9ICJDcml0aWNhbCB2YWx1ZSBvZiBwPDAuMDEiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMjIyMgVi4gRGVuc2l0eSBQbG90IGZvciBmdW5zaWVzDQoNCmBgYHtyIGRlbnMsIGZpZy5jYXA9IioqRmlndXJlIDQ6KiogQXR0ZW1wdGluZyB0byBnZXQgYSBnZ3Bsb3RseSBsaWtlIEFuZHkncyBhbmQgd2lsbCByZXZpc2l0IHRoaXMgaWYgSSBoYXZlIG1vcmUgdGltZS4ifQ0KDQpnZ3Bsb3QoYmlyZF9kZiwgYWVzKGQsIEkpKSsNCiAgc3RhdF9kZW5zaXR5XzJkKGFlcyhmaWxsID0gLi5sZXZlbC4uKSwgZ2VvbSA9ICJwb2x5Z29uIikrDQogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGU9ICJTZXQyIiwgZGlyZWN0aW9uPTEpKw0KICAgIGdlb21fcG9pbnQoYmlyZF9kZiwgbWFwcGluZz1hZXMoeD1kLCB5PUkpLCBhbHBoYT0wLjcsIGNvbG9yPSIjZmQ0YTA5IikrDQogICAgICB0aGVtZV9jbGFzc2ljKCkNCg0KYGBgDQoNCg0KIyMjIyBJbnRlcnByZXQNCg0KKipRdWVzdGlvbjoqKiBXaGF0IGNhbiB5b3UgbGVhcm4gYnkgY29tcGFyaW5nIHRoZSBhcHByb2FjaGVzPyBNYXliZSBtb3JlIGltcG9ydGFudGx5IHdoYXQgYXJlIHdlIG1pc3Npbmcgd2l0aCB0aGlzIGFwcHJvYWNoPyBXaGVyZSBkb2VzIHRoaXMgdW5kZXJzdGFuZGluZyBvZiBwYXR0ZXJuIGhlbHAgdXMgd2l0aCBwcm9jZXNzIGFuZCB3aGVyZSBpcyBpdCBsYWNraW5nPw0KDQpbKipBbnN3ZXI6Kipde3N0eWxlPSJjb2xvcjogI2FlZDYwMTsifSBJIGhhZCB0byBza2lwIGFoZWFkIGFuZCBjb21lIGJhY2sgdG8gdGhpcyBiZWNhdXNlIEkgZGlkbid0IGtub3cgaG93IHRvIGFuc3dlciBpdC4gSSB0aGluayB0aGF0IHRoaXMgYXBwcm9hY2ggZG9lcyBub3QgaW5jbHVkZSBkaXJlY3Rpb25hbGl0eSBhbmQgd2UgY291bGQgYmVuZWZpdCBmcm9tIGluY2x1ZGluZyBpdCAoYXMgd2UgZGlkIGluIHRoZSBuZXh0IHBhcnQpLg0KDQoNCiMjIyMgRGlyZWN0aW9uYWwgVmFyaW9ncmFtcw0KDQoNCmBgYHtyIGRpcmVjdGlvbmFsIHZhcmlvZ3JhbXMsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCmJpcmRWYXIgPC0gdmFyaW9ncmFtKG5TcGVjaWVzfjEsIGRhdGEgPSBiaXJkc19zZiwgYWxwaGE9YygwLCA5MCkpDQpwbG90KGJpcmRWYXIpDQpgYGANCg0KUHV0IG9uIGEgYmlvZ2VvZ3JhcGh5IGhhdCBhbmQgZXhwbGFpbiB3aGF0IG1pZ2h0IGJlIGhhcHBlbmluZyBoZXJlLg0KDQpbKipBbnN3ZXI6Kipde3N0eWxlPSJjb2xvcjogI2FlZDYwMTsifSBJIHRoaW5rIHRoYXQgdGhlIGFuaXNvdHJvcHkgKHRoZSBmYWN0IHRoYXQgdGhlcmUgaXMgaGlnaGVyIHNwZWNpZXMgcmljaG5lc3Mgb24gdGhlIHNvdXRoZXJuIGhhbGYgb2YgTWV4aWNvKSBvZiB0aGUgZGF0YSBpcyBiZWNhdXNlIG9mIHRoZSByYWluIHNoYWRvdyBmYWxsaW5nIG9uIHRoZSBlYXN0ZXJuIHNpZGUgb2YgdGhlIFNpZXJyYSBNYWRyYXMuIFRoZSBpbmNyZWFzZSBpbiByYWluIGxpa2VseSBhbGxvd3MgYW4gaW5jcmVhc2UgaW4gcGxhbnQgZGl2ZXJzaXR5IGFzIHdlbGwgYXMgYW4gaW5jcmVhc2UgaW4gYmlyZCBkaXZlcnNpdHkuIFlvdSBjYW4gc2VlIGFib3ZlIHRoYXQgcG9pbnRzIGluIHNvdXRoZXJuIHBhcnQgb2YgTWV4aWNvICg5MMKwKSBoYXZlIGhpZ2hlciBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBhcyB3ZWxsLiANCg0KDQojIyMgKirwn6eBIEFuZHkncyBUdXRvcmlhbCoqIHsudGFic2V0fQ0KDQojIyMjIERpc3RhbmNlIE1hdHJpY2VzDQoNCkltYWdpbmUgd2UgaGF2ZSB0d28gcG9pbnRzIHdpdGggbG9jYXRpb25zIHNwZWNpZmllZCBieSB4IGFuZCB5LCB3ZSBjYW4gZmluZCB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGVtIHVzaW5nIHRoZSBFdWNsaWRpYW4gRGlzdGFuY2U6IA0KDQokRCA9JCAkXHNxcnQgeyh4XzIgLSB4XzEpXjIgKyAoeV8yIC0geV8xKV4yfSQNCg0KfA0KDQpOb3csIGNhbGN1bGF0ZSBwYWlyd2lzZSBkaXN0YW5jZXMgZm9yIGEgc2V0IG9mIHBvaW50czoNCg0KXHRpbnkNCmBgYHtyIGRpc3RhbmNlIG1hdHJpeH0NCg0KeCA8LSBjKC40NSwgLjM3NSwgLjQ1LCAuNjc4LCAuNTUpDQp5IDwtIGMoLjAyLCAuODcsIC40NywgLjksIC41NSkNCg0KZGF0IDwtIGRhdGEuZnJhbWUoeCx5KQ0KDQoNCmRpc3RhbmNlbWF0cml4IDwtIGRpc3QoZGF0KQ0KDQojICAgICAgICAgIDEgICAgICAgICAyDQojMiAwLjg1MzMwMjQgICAgICAgICAgDQojMyAwLjQ1MDAwMDAgMC40MDY5NzA1DQojNCAwLjkwOTA1NjcgMC4zMDQ0ODE1DQojNSAwLjUzOTM1MTUgMC4zNjQ3MjU5DQojICAgICAgICAgIDMgICAgICAgICA0DQojMiAgICAgICAgICAgICAgICAgICAgDQojMyAgICAgICAgICAgICAgICAgICAgDQojNCAwLjQ4NjcwNzMgICAgICAgICAgDQojNSAwLjEyODA2MjUgMC4zNzI2NzE0DQpgYGANCg0KDQp8DQoNCkhlcmUgeW91IGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgJFxmcmFje25cdGltZXMobi0xKX0yJCBwYWlyd2lzZSBlbGVtZW50cyBvZiBELiAkKDVcdGltZXNcZnJhY3s0fTIgPSAxMCkkIFNvIHRoZXJlIGFyZSAxMCBwYWlycyBvZiB2YWx1ZXMuIFRodXMuLi50aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgZmlyc3QgYW5kIHNlY29uZCBwb2ludHMgY2FuIGJlIGNhbGN1bGF0ZWQgYXMgZm9sbG93czogDQoNCg0KYGBge3IgZGlzdGFuY2ViZXR3ZWVuLCBlY2hvPUZ9DQoNCmRpc3RhbmNlMWFuZDIgPC0gcm91bmQoYXMubWF0cml4KGRpc3RhbmNlbWF0cml4KVsxLDJdLCAyKQ0KDQpnZ3Bsb3QoZGF0LCBhZXMoeD14LCB5PXkpKSsNCiAgZ2VvbV9wb2ludCgpKw0KICB0aGVtZV9taW5pbWFsKCkrDQogIGdlb21fcGF0aChkYXRhPWRhdFtjKDEsMiksXSwgY29sb3I9InBsdW0iKSsNCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMC40MTUsIHkgPSAwLjYsIGxhYmVsID0gIjAuODUiKQ0KDQpgYGANCg0KfA0KDQpOb3cgbGV0J3MgYWRkIHNvbWUgYXR0cmlidXRlcyB0byB0aG9zZSBwb2ludHMhIA0KDQojIyMjIE1ldXNlIFJpdmVyDQoNCmBgYHtyIG1ldXNlIHJpdmVyIGRhdGF9DQpkYXRhKG1ldXNlLmFsbCkNCg0KbWV1c2VfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZXM9Y29sbmFtZXMobWV1c2UuYWxsKSkNCg0KDQoNCmd0KGhlYWQobWV1c2VfZGYpKXw+DQogIG9wdF90YWJsZV9mb250KCl8Pg0KICBvcHRfdGFibGVfb3V0bGluZShzdHlsZT0ic29saWQiKXw+DQogIHRhYl9oZWFkZXIoDQogICAgdGl0bGUgPSAiTWV1c2UgUml2ZXIgVmFyaWFibGVzIil8Pg0KICBvcHRfc3R5bGl6ZShzdHlsZSA9IDUsIGNvbG9yID0gImN5YW4iKXw+DQogIHRhYl9vcHRpb25zKHRhYmxlLmFsaWduPSdjZW50ZXInLCB0YWJsZS5mb250LnNpemUgPSBweCgxMCkpDQpgYGANCg0KfA0KDQpUcmFuc2Zvcm0gdGhlIGRhdGEgaW50byBhbiBgYGBzZmBgYCBvYmplY3Q6DQoNCmBgYHtyIHRyYW5zZm9ybWF0aW9uLGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KbWV1c2Vfc2YgPC0gc3RfYXNfc2YobWV1c2UuYWxsLCBjb29yZHMgPSBjKCJ4IiwieSIpKSB8Pg0KICBzdF9zZXRfY3JzKHZhbHVlID0gMjg5OTIpDQpgYGANCg0KDQpBbmQgbWFrZSBhIHBsb3Qgb2YgdGhlIGxlYWQgY29uY2VudGF0aW9uIGluIHRoZSBzb2lscy4NCg0KYGBge3IgbGVhZCBjb25jIHBsb3QsIG1lc3NhZ2U9Rn0NCmdncGxvdChkYXRhPW1ldXNlX3NmKSsNCiAgZ2VvbV9zZihtYXBwaW5nPWFlcyhmaWxsPWxlYWQsIHNpemU9bGVhZCksIHNoYXBlPTIxLCBhbHBoYT0wLjYpKw0KICBzY2FsZV9maWxsX2NvbnRpbnVvdXModHlwZT0idmlyaWRpcyIsIG5hbWU9InBwbSIpDQoNCmBgYA0KDQpgYGB7ciBhZGQgdG8gbWFwLCBtZXNzYWdlPUZ9DQp0bWFwX21vZGUoJ3ZpZXcnKQ0KdG1fc2hhcGUobWV1c2Vfc2YpICsNCiAgdG1fZG90cyhjb2wgPSAibGVhZCIsIHBhbGV0dGUgPSAidmlyaWRpcyIpICN0cnkgdG1hcCB0b28gaGVyZQ0KDQoNCmBgYA0KDQojIyMjIFZhcmlvZ3JhbXMNCg0KRm9yIGRldGVybWluaW5nIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uLiBTZW1pdmFyaW9ncmFtcyBjYW4gYmUgdXNlZCBmb3IgYm90aCAqKmRlc2NyaXB0aXZlKiogc3RhdGlzdGljcyAqYW5kKiAqKnByZWRpY3RpdmUqKiBzdGF0aXN0aWNzLiANCg0KfA0KDQoqKlNlbWktdmFyaWFuY2U6KiogaG93IHN0cm9uZ2x5IGRvZXMgYW4gb2JzZXJ2ZWQgb2JzZXJ2YXRpb24gKGkuZS4gbGVhZCBjb25jZW50cmF0aW9uKSB2YXJ5IGFzIGEgZnVuY3Rpb24gb2YgZGlzdGFuY2UuIFRoZSB5IGF4ZXMgb24gdGhlc2UgYXJlIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgdmFsdWVzIGJldHdlZW4gdGhlIHBvaW50cyAoaW4gdGhpcyBjYXNlIGl0IGlzIHRoZSBsZWFkIGNvbmNlbnRyYXRpb24pLiANCg0KYGBge3IgdmFyaW9ncmFtIGNsb3VkfQ0KDQpsZWFkVmFyQ2xvdWQgPC0gdmFyaW9ncmFtKGxlYWR+MSwgbWV1c2Vfc2YsIGNsb3VkPVQpDQoNCnBsb3QobGVhZFZhckNsb3VkLCBwY2g9MjAsIGNleD0xLjUsIGNvbD0icGx1bSIsIGFscGhhPTAuMSwgeWxhYj1leHByZXNzaW9uKFNlbWl2YXJpYW5jZX4oZ2FtbWEpKSwgeGxhYj0iRGlzdGFuY2UgKG0pIiwgbWFpbiA9ICJMZWFkIGNvbmNlbnRyYXRpb25zIChwcG0pIikNCg0KYGBgDQoNCioqVGhpbmdzIHRoYXQgYXJlIG1vcmUgY29ycmVsYXRlZCBpbiBhIHNlbWktdmFyaW9ncmFtIGFyZSB0aGUgc21hbGxlciB2YWx1ZSBwb2ludHMqKg0KDQpUaGUgdmFyaW9ncmFtIGNsb3VkIGFib3ZlIChUaGlzIHNob3dzIG1ham9yaXR5IG9mIHRoZSBub2lzZSAoYWxsIHRoZSB1cHBlciB0cmFuc2x1Y2VudCBwb2ludHMpKSwgYnV0IGhhcyB0b28gbXVjaCBkYXRhIG9uIGl0IHRvIHN5bnRoZXNpemUsIHNvIHdlIGF2ZXJhZ2UgdGhlbSBpbnRvIHRoZSBuZXh0IGdyYXBoIHNob3duLg0KDQpgYGB7ciBzYWZlIHJhbmdlIGF2ZXJhZ2V9DQoNCmxlYWRWYXIgPC0gdmFyaW9ncmFtKGxlYWR+MSwgbWV1c2Vfc2YsIGNsb3VkPUYpDQoNCnBsb3QobGVhZFZhciwgcGNoPTIwLCBjZXg9MS41LCBjb2w9ImRhcmttYWdlbnRhIiwgDQogICAgIHlsYWI9ZXhwcmVzc2lvbihTZW1pdmFyaWFuY2V+KGdhbW1hKSksIHhsYWI9IkRpc3RhbmNlIChtKSIsIG1haW49IkxlYWQgY29uY2VudHJhdGlvbnMgKHBwbSkiKQ0KYGBgDQoNClRoaXMgZ3JhcGggaGFzIHdheSBsZXNzIG5vaXNlIHRoYW4gdGhlIGNsb3VkLiBZb3UgY291bGQgc2F5IHRoYXQgdGhlIGxlYWQgY29uY2VudHJhdGlvbnMgaW4gdGhpcyBkYXRhc2V0IGFyZSBhdXRvY29ybGF0ZWQgb3V0IHRvIGEgZGlzdGFuY2Ugb2YgYWJvdXQgNzUwIG0gYmVjYXVzZSAkXGdhbW1hJCBpcyBzbWFsbCBhdCBkaXN0YW5jZXMgbGVzcyB0aGFuIDc1MCBtIGJlZm9yZSB0YXBwZXJpbmcgb2ZmIGEgYml0LiBXZSB3aWxsIGJ1aWxkIG1vZGVscyBmb3IgdGhpcyBzb29uLiBUaGVzZSBtb2RlbHMgbm9ybWFsaXplIHRoZSBzZW1pdmFyaW9ncmFtIGludG8gc29tZXRoaW5nIHRoYXQgcmFuZ2VzIGZyb20gLTEgdG8gMS4gVGhpcyBpcyBjYWxsZWQsICoqTW9yYW4ncyBJKiouDQoNCiMjIyMgTW9yYW4ncyBJDQoNClRoaXMgaGFzIHJlYWxseSBuaWNlIHByb2JhYmxpdHkgcHJvcGVydGllcywgd2hpY2ggaXMgd2h5IHdlIHVzZSBpdC4gDQoNCg0KJEkgPSQgJFxmcmFje059e1xTaWdtYV9pIFxTaWdtYV9qd197aWp9fSQgJFxmcmFje1xTaWdtYV9pIFxTaWdtYV9qIHdfe2lqfShaX2ktXGJhcntafSkoWl9qIC0gXGJhcntafSl9e1xTaWdtYV9pKFpfaS1cYmFye1p9KV4yfSQNCg0KJFokIGlzIHRoZSB2YXJpYWJsZSBiZWluZyBtZWFzdXJlZCwgYW5kICR3JCBpcyBhIG1hdHJpeCBvZiB3ZWlnaHRzIChpLmUuIHRoZSBpbnZlcnNlIG9mIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHBvaW50cykuDQoNCiMjIyMjIFdlaWdodHMNCg0KVGhpcyBpcyBzaW1pbGFyIHRvIHRoZSBib29sZWFuIHllcy9ubyB3aGVuIHR1cm5pbmcgY2VydGFpbiBmdW5jdGlvbnMgb24gb3Igb2ZmLiBFbGVtZW50cyB0aGF0IGFyZSBmdXJ0aGVyIGF3YXkgZnJvbSBvbmUgYW5vdGhlciB3aWxsIGhhdmUgYSBzbWFsbGVyIHdlaWdodC4gWW91IGNhbiB0cmFuc2Zvcm0gTW9yYW4ncyBJIGludG8gYSBaIHNjb3JlIGZvciBoeXBvdGhlc2lzIHRlc3RpbmcuIFRoaXMgbWVhbnMgdGhhdCBhIFotc2NvcmUgb2YgPiAxLjk2IG9yIDwgLTEuOTYgd291bGQgbWVhbiB0aGVyZSBpcyBzaWduaWZpY2FudCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiB3aXRoICRcYWxwaGE9MC4wNSQNCg0KIyMjIyMgYGBgc3BkZXBgYGANCmBgYHtyIG1vcmFucyB3aXRoIHNwZGVwfQ0KDQojIGRpc3RhbmNlIG1hdHJpeCANCmQgPC0gZGlzdChzdF9jb29yZGluYXRlcyhtZXVzZV9zZikpDQojIGludmVyc2UgZGlzdGFuY2UgbWF0cml4DQp3IDwtIGFzLm1hdHJpeCgxL2QpDQojIGNvbnZlcnQgYSB0aGUgd2VpZ2h0cyBtYXRyaXggdG8gYSB3ZWlnaHRzIGxpc3Qgb2JqZWN0DQojIHNvIHNwZGVwIGlzIGhhcHB5DQp3TGlzdCA8LSBtYXQybGlzdHcodykNCiMgY2FsY3VsYXRlIEkNCm1vcmFuLnRlc3QobWV1c2Vfc2YkbGVhZCx3TGlzdCkNCg0KYGBgDQoNClRoZXNlIHJlc3VsdHMgaW5kaWNhdGUgdGhlIGxlYWQgZGF0YSBhcmUgcG9zaXRpdmUgKGJ1dCB2ZXJ5IHdlYWtseSkgYXV0b2NvcnJlbGF0ZWQgd2hlbiB3ZSB1c2UgJFckIGFzIGludmVyc2UgZGlzdGFuY2VzLiBBcyB3ZSBzYXcgcHJldmlvdXNseSB0aG91Z2gsIHZhbHVlcyBhYm92ZSAxMDAwIG0gZG9uJ3QgYWN0dWFsbHkgaGF2ZSBhIGxvdCBvZiBpbmZsdWVuY2Ugb24gb25lIGFub3RoZXIuIFRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBkaXN0YW5jZSBhbmQgaW52ZXJzZSBkaXN0YW5jZSBpczogDQoNCmBgYHtyIGludmVyc2UgZGlzdCByZWxhdGlvbnNoaXAsIHdhcm5pbmc9Rn0NCg0KeCA8LSBhcy52ZWN0b3IoYXMubWF0cml4KChkaXN0KHN0X2Nvb3JkaW5hdGVzKChtZXVzZV9zZikpKSkpKQ0KDQoNCnkgPC0gYXMudmVjdG9yKHcpDQoNCmdncGxvdCgpKw0KICBnZW9tX2xpbmUoYWVzKHg9eFt4PjBdLCB5PXlbeD4wXSksIGNvbG9yPSJwaW5rIikrDQogIGxhYnMoeD0iRGlzdGFuY2UgKG0pIiwgeT0iSW52ZXJzZSBkaXN0YW5jZSAoVykiKSsNCiAgbGltcyh4PWMoMCwxNTAwKSkrDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQpgYGANCg0KIyMjIyMgU2V0IERpc3RhbmNlDQoNCkxldCdzIGxvb2sgYXQgTW9yYW4ncyBJIHdoZW4gcG9pbnRzIGFyZSB3aXRoaW4gMTAwIG0gb2YgZWFjaCBvdGhlci4gDQoNCmBgYHtyIDEwMCBtLCB3YXJuaW5nPUZ9DQpkIDwtIGFzLm1hdHJpeChkaXN0KHN0X2Nvb3JkaW5hdGVzKG1ldXNlX3NmKSkpDQojIG1ha2UgYW4gZW1wdHkgd2VpZ2h0cyBtYXRyaXgNCncgPC0gbWF0cml4KDAsbmNvbD1uY29sKGQpLG5yb3c9bnJvdyhkKSkNCiMgc2V0IHNvbWUgdmFsdWVzIHRvIDEgdXNpbmcgYSBsb2dpY2FsIG1hc2sgb2YgZDwxMDANCndbZDwxMDBdIDwtIDENCiMgY29udmVydCBhIHRoZSB3ZWlnaHRzIG1hdHJpeCB0byBhIHdlaWdodHMgbGlzdCBvYmplY3QNCiMgc28gc3BkZXAgaXMgaGFwcHkNCndMaXN0IDwtIG1hdDJsaXN0dyh3KQ0KIyBjYWxjdWxhdGUgSQ0KbW9yYW4udGVzdChtZXVzZV9zZiRsZWFkLHdMaXN0KQ0KDQpgYGANCioqTm90ZToqKiBUaGUgTW9yYW4ncyBJIGF0IHNtYWxsZXIgZGlzdGFuY2VzIGlzIHF1aXRlIGhpZ2guLi5pbmRpY2F0aW5nIGhpZ2ggYXV0b2NvcnJlbGF0aW9uIGZvciBsZWFkIGNvbmNlbnRyYXRpb25zLiBMZXQncyB0cnkgbW9yZSBkaXN0YW5jZXMuIExldCdzIGxvb2sgYXQgb25seSBwb2ludHMgYmV0d2VlbiA1MDAgYW5kIDEwMDAgbSBvZiBvbmUgYW5vdGhlci4gDQoNCmBgYHtyIGRpc3RhbmNlIHJhbmdlLCB3YXJuaW5nPUZ9DQojIG1ha2UgYW4gZW1wdHkgd2VpZ2h0cyBtYXRyaXggb2YgdGhlIHJpZ2h0IGRpbWVuc2lvbnMNCncgPC0gbWF0cml4KDAsbmNvbD1uY29sKGQpLG5yb3c9bnJvdyhkKSkNCiMgc2V0IHNvbWUgdmFsdWVzIHRvIDENCndbZD41MDAgJiBkPD0xMDAwXSA8LSAxDQojIGNvbnZlcnQgYSB0aGUgd2VpZ2h0cyBtYXRyaXggdG8gYSB3ZWlnaHRzIGxpc3Qgb2JqZWN0DQojIHNvIHNwZGVwIGlzIGhhcHB5DQp3TGlzdCA8LSBtYXQybGlzdHcodykNCiMgY2FsY3VsYXRlIEkNCm1vcmFuLnRlc3QobWV1c2Vfc2YkbGVhZCx3TGlzdCkNCg0KYGBgDQoNCiMjIyMjIE1vcmFuJ3MgQ29ycmVsb2dyYW0NCg0KVGhpcyBzaG93cyBNb3JhbidzIEkgYXMgYSBjb3JyZWxvZ3JhbSBieSBkaXN0YW5jZXMgMCB0byAxNTAwIG0gaW4gMjAwIG0gYmluczoNCg0KYGBge3IgdGhlIGZvciBsb29wLCB3YXJuaW5nPUZ9DQoNCmRpc3RhbmNlSW50ZXJ2YWwgPC0gMjAwDQpkaXN0YW5jZVZlY3RvciA8LSBzZXEoMCwyMDAwLGJ5PWRpc3RhbmNlSW50ZXJ2YWwpDQpuIDwtIGxlbmd0aChkaXN0YW5jZVZlY3RvcikNCmQgPC0gYXMubWF0cml4KGRpc3Qoc3RfY29vcmRpbmF0ZXMobWV1c2Vfc2YpKSkNCiMgbWFrZSBhbiBvYmplY3QgdG8gaG9sZCByZXN1bHRzDQpyZXMgPC0gZGF0YS5mcmFtZShtaWRCaW49cmVwKE5BLG4tMSksST1yZXAoTkEsbi0xKSkNCmZvcihpIGluIDI6bil7DQogIHcgPC0gbWF0cml4KDAsbmNvbD1uY29sKGQpLG5yb3c9bnJvdyhkKSkNCiAgIyBzZXQgc29tZSB2YWx1ZXMgdG8gMQ0KICB3W2QgPj0gZGlzdGFuY2VWZWN0b3JbaS0xXSAmIGQgPCBkaXN0YW5jZVZlY3RvcltpXV0gPC0gMQ0KICAjIGNvbnZlcnQgYSB0aGUgd2VpZ2h0cyBtYXRyaXggdG8gYSB3ZWlnaHRzIGxpc3Qgb2JqZWN0DQogICMgc28gc3BkZXAgaXMgaGFwcHkNCiAgd0xpc3QgPC0gbWF0Mmxpc3R3KHcpDQogICMgY2FsY3VsYXRlIEkNCiAgcmVzJElbaS0xXSA8LSBtb3Jhbi50ZXN0KG1ldXNlX3NmJGxlYWQsd0xpc3QsemVyby5wb2xpY3k9VFJVRSkkZXN0aW1hdGVbMV0NCiAgIyBjZW50ZXJlZCBkaXN0YW5jZSBiaW4NCiAgcmVzJG1pZEJpbltpLTFdIDwtIGRpc3RhbmNlVmVjdG9yW2ldIC0gZGlzdGFuY2VJbnRlcnZhbC8yDQp9DQpnZ3Bsb3QoZGF0YT1yZXMsIG1hcHBpbmcgPSBhZXMoeD1taWRCaW4seT1JKSkgKyANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemU9MykgKw0KICBsYWJzKHg9IkRpc3RhbmNlIChtKSIseT0iTW9yYW4ncyBJIiwgdGl0bGUgPSAiTW9yYW4ncyBJIGFzIGZ1bmN0aW9uIG9mIGRpc3RhbmNlIikNCmBgYA0KDQpBYm92ZSwgeW91IGNhbiBzZWUgdGhhdCB0aGUgdmFsdWVzIG9mIGxlYWQgYXJlIGF1dG9jb3JyZWxhdGVkIGZvciBhIGZldyBodW5kcmVkIG0gYW5kIHRoZW4gZmFsbHMgb2ZmLiBUaGlzIGlzIGNvbmNlcHR1YWxseSBzaW1pbGFyIHRvIFJpcGxleSdzIEsuDQoNCiMjIyMjIFBvbHlnb25zDQoNCk9mdGVuLCBhbmQgZXNwZWNpYWxseSB3aXRoIGFyZWFsIGRhdGEsIHdlIGNhbGN1bGF0ZSB0aGUgd2VpZ2h0cyBtYXRyaXggdXNpbmcganVzdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgJGskIGNsb3Nlc3QgbmVpZ2hib3JzIGFuZCB1c3VhbGx5IHNldCAkaz04JCAodGhlcmUgaXMgbm90aGluZyBtYWdpY2FsIGFib3V0IGVpZ2h0IOKAkyBpdCBpcyBhIGxvb3NlIGtpbmQgb2YgcnVsZSkuDQoNCmBgYHtyIGFlcmlhbH0NCncgPC0ga25uMm5iKGtuZWFybmVpZ2gobWV1c2Vfc2YsIGs9OCkpICNrbmVhcm5laWdoIHRlbGxzIHlvdSB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgZm9yIGVhY2ggcG9pbnQgYW5kIHRoZW4gdHVybnMgdGhlIG9iamVjdCBpbnRvIGFuIG5iIHdpdGgga25uMm5iLiANCg0KbW9yYW4udGVzdChtZXVzZV9zZiRsZWFkLCBuYjJsaXN0dyh3KSkgI3RoZSBuYjJsaXN0dyBmdW5jdGlvbiB0YWtlcyBpbiBhbiBuYiBvYmplY3QgYW5kIG1ha2VzIGl0IHVzZWFibGUgaW4gdGhlIG1vcmFuLnRlc3QgZnVuY3Rpb24uIA0KDQpgYGANCg0KQnV0IHdoYXQgZG8gd2Ugc2VlIGhlcmU/IElmIHVzZSB0aGUgZWlnaHQgbmVhcmVzdCBuZWlnaGJvcnMgdG8gY2FsY3VsYXRlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIHRoZSB2YWx1ZSBvZiBNb3JhbuKAmXMgSSBqdW1wcyB3YXkgdXAuIE5lYXIgaXMgbGlrZSBuZWFyIHdoZW4gaXQgY29tZXMgdG8gbGVhZC4gS2luZCBvZiBjb29sLiBJZiB3ZSBsb29rZWQgYXQgMTYgbmVpZ2hib3JzIHdl4oCZZCBzZWUgTW9yYW7igJlzIEkgb2YgMC4yOCB3aGljaCBpcyBzdGlsbCBwcmV0dHkgaGlnaC4gSXQgd2UgbG9va2VkIGF0IDMyIG5laWdoYm9ycz8gTW9yYW7igJlzIEkgb2YgMC4xMjMuIFNvLCBtb3JlIG5laWdoYm9ycywgZ3JlYXRlciBkaXN0YW5jZXMgd2UgZ2V0IGRlY2xpbmluZyB2YWx1ZXMuIFNlZSB3aGVyZSBJ4oCZbSBnb2luZyB3aXRoIHRoaXM/DQoNCmBgYHtyIG1vcmFucyBlYXN5LCB3YXJuaW5nPUZ9DQpuIDwtIDcNCnJlcyA8LSBkYXRhLmZyYW1lKGs9Ml4oMTpuKSxJPXJlcChOQSxuKSkNCmZvcihpIGluIDE6bil7DQogIHcgPC0ga25uMm5iKGtuZWFybmVpZ2gobWV1c2Vfc2Ysaz0yXmkpKQ0KICByZXMkSVtpXSA8LSBtb3Jhbi50ZXN0KG1ldXNlX3NmJGxlYWQsbmIybGlzdHcodykpJGVzdGltYXRlWzFdDQp9DQpnZ3Bsb3QoZGF0YT1yZXMsIG1hcHBpbmcgPSBhZXMoeD1rLHk9SSkpICsgDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPSJkYXNoZWQiKSArDQogIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludChzaXplPTMpICsNCiAgbGFicyh4PSJLIG5laWdoYm9ycyIseT0iTW9yYW4ncyBJIikNCmBgYA0KVGhhdOKAmXMgYSBNb3JhbuKAmXMgSSBhIGNvcnJlbG9ncmFtIGJ5IHRoZSBudW1iZXIgb2YgbmVpZ2hib3JzLiBCZWNhdXNlIHRoZXNlIGFyZSBwb2ludHMsIEkgcHJlZmVyIHRvIGxvb2sgYXQgTW9yYW7igJlzIEkgaW4gYSBjb3JyZWxvZ3JhbSBieSBkaXN0YW5jZSDigJMgbGlrZSB3ZSBkaWQgYWJvdmUgYnV0IGlmIHdlIGhhZCB0byBkZWFsIHdpdGggcG9seWdvbmFsIGRhdGEsIHdl4oCZZCBsaWtlbHkgdXNlIHRoaXMgYXBwcm9hY2guIFNlZSB0ZXh0IGZvciBtb3JlIGluZm8uDQoNCiMjIyMjIGBgYG5jZmBgYA0KDQpUaGUgYGBgc3BkZXBgYGAgbGlicmFyeSBpcyBmYW50YXN0aWMgYnV0IGRvZXNu4oCZdCBwcm9kdWNlIHRoZSBjb3JyZWxvZ3JhbSBpbiB0aGUgd2F5IHdlIG9mdGVuIHNlZSBpdCB3aXRoIE1vcmFu4oCZcyBJIGJ5IGRpc3RhbmNlICQoST1mKGQpKSQuIFRoZSBgYGBuY2ZgYGAgbGlicmFyeSBkb2VzIHRoaXMgb3V0IG9mIHRoZSBib3guDQoNClRoZXJlIGFyZSB0d28gZnVuY3Rpb25zIHdlIHdpbGwgbG9vayBhdCB0byBkbyB0aGlzLiBPbmUgdXNlcyBkaXN0YW5jZSAqKmNhdGVnb3JpZXMqKiBhbmQgb25lIG1ha2VzIGEgKipjb250aW51b3VzKiogZnVuY3Rpb24uDQoNCmBgYHtyIGdyYWIgc29tZSBzdHVmZn0NCm1ldXNlWCA8LSBzdF9jb29yZGluYXRlcyhtZXVzZV9zZilbLDFdDQptZXVzZVkgPC0gc3RfY29vcmRpbmF0ZXMobWV1c2Vfc2YpWywyXQ0KDQptZXVzZUxlYWQgPC0gbWV1c2Vfc2YkbGVhZA0KDQpgYGANCg0KIyMjIyMgRGlzY3JldGUgQ29ycmVsb2dyYW0NCg0KRmlyc3QsIHRoZSBkaXNjcmV0ZSBwbG90IG9mIE1vcmFuJ3MgSSBieSBkaXN0YW5jZSBjYXRlZ29yaWVzLg0KDQpgYGB7ciBkaXNjcmV0ZSwgZmlnLmNhcD0iV2Ugc3RpbGwgc2VlIHRoZSBzYW1lIHJlc3VsdCB3ZSBkaWQgYWJvdmUg4oCTIHBvc2l0aXZlIGF1dG9jb3JyZWxhdGlvbiBvdXQgdG8gZGlzdGFuY2VzIG9mIGFib3V0IDUwMCBtLCB2ZXJ5IHNsaWdodCBuZWdhdGl2ZSBhdXRvY29ycmVsYXRpb24gYXJvdW5kIDEwMDAgbSwgYW5kIENTUiBvdGhlcndpc2UuIn0NCmxlYWRJIDwtIGNvcnJlbG9nKHg9bWV1c2VYLCB5PW1ldXNlWSwgej1tZXVzZUxlYWQsDQogICAgICAgICAgICAgICAgICBpbmNyZW1lbnQ9MTAwLCByZXNhbXA9MTAwLCBxdWlldD1UUlVFKQ0KcGxvdChsZWFkSSx4bGltPWMoMCwxNTAwKSkNCmFibGluZShoPTAsbHR5PSJkYXNoZWQiKQ0KYGBgDQoNClRoZSBhYm92ZSBncmFwaCBjYWxjdWxhdGVzICRJPWYoZCkkIHVzaW5nIDEwMCBtIGluY3JlbWVudHMuIFRoZXJlIGlzIGFsc28gYSBzaWduaWZpY2FuY2UgdGVzdCBjb21wb25lbnQgd2l0aCAxMDAgcGVybXV0YXRpb25zIG9mIHRoZSBkYXRhIHRvIHRlc3QgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZSBkYXRhIGFib3ZlIGZvbGxvd3MgQ1NSLiBDb2xvcmVkIGRvdHMgYWJvdmUgc3VnZ2VzdCBzaWduaWZpY2FuY2UgKG5vdCBDU1IgYXQgdGhvc2UgcG9pbnRzKSwgYW5kIGhvbGxvdyBkb3RzIHN1Z2dlc3QgaW5zaWduaWZpY2FudC4gVGhlIHRlc3QgdXNlcyBhIHR3by1zaWRlZCA1JSBsZXZlbCBhbmQgdGhpcyBjb2RlIGlzIHdheSBlYXNpZXIgdGhhbiB0aGUgZm9yLWxvb3AgcHJldmlvdXNseSB1c2VkLiANCg0KIyMjIyMgQ2hlY2sgRGlzdGFuY2VzDQoNCk9oLCBvbmUgbW9yZSB0aGluZy4gUmVtZW1iZXIgaG93IHRoZSBwb2ludCBwYXR0ZXJuIGZ1bmN0aW9ucyBpbiBzcGF0c3RhdCB3ZXJlIHZlcnkgY2FyZWZ1bCBhYm91dCBub3QgbGV0dGluZyB5b3UgcnVuIGludG8gZWRnZSBlZmZlY3RzPyBBbmQgdmFyaW9ncmFtIGlzIHRoYXQgd2F5IHRvby4gVGhlIGNvcnJlbG9nIGZ1bmN0aW9uIGlzIG5vdCBzbyBjYXJlZnVsISBJdOKAmXMgbW9yZSBsaWJlcnRhcmlhbiBpdHMgYXBwcm9hY2guDQoNCkhlcmUgaXMgdGhlIHdob2xlIGRhdGEgc2V0LiBpbiB0aGUgZGVmYXVsdCBwbG90Lg0KDQpgYGB7ciBhbGxkYXRhIHBsb3QsIGZpZy5jYXA9IkhlcmUgeW91IGNhbiBzZWUgdGhhdCB5b3UgZ2V0IG5vbi1zaWduaWZpY2FudCB2YWx1ZXMgd2l0aCBoaWdoIHZhbHVlcyBvZiBJIChhIGJ1bmNoIG9mIG5vbnNlbnNlKS4gV2UgbmVlZCB0byBmaW5kIHdheXMgdG8gZml4IHRoZSBlZGdlIGVmZmVjdHMuIn0NCg0KcGxvdChsZWFkSSkNCmFibGluZShoPTAsIGx0eT0iZGFzaGVkIikNCg0KYGBgDQoNCg0KIyMjIyMgRGVjaWRpbmcgRGlzdGFuY2VzDQoNCioqWU9VIFdBTlQgVE8gTE9PSyBBVCBESVNUQU5DRVMgTEVTUyBUSEFOICRcZnJhY3sxfTMkIFlPVVIgTUFYIFNQQU4gQkVUV0VFTiBQT0lOVFMqKg0KDQpMZXTigJlzIGxvb2sgYXQgdGhlIGV4dGVudCBvZiB0aGUgZGlzdGFuY2VzIGluIHRoZSBkYXRhLiBUaGUgY29vcmRpbmF0ZXMgc3BhbiBhYm91dCAyODAwIG0gYnkgMzkwMCBtOg0KDQpgYGB7ciBkYXRhc3Bhbn0NCm1heChtZXVzZVgpLW1pbihtZXVzZVgpDQoNCm1heChtZXVzZVkpLW1pbihtZXVzZVkpDQpgYGANCg0KDQpgYGB7ciBkaXN0Mn0NCm1ldXNlRCA8LSBkaXN0KGNiaW5kKG1ldXNlWCwgbWV1c2VZKSkNCm1heChtZXVzZUQpDQpgYGANCg0KVGhlIGZ1cnRoZXN0IHBvaW50IHRvIHBvaW50IGRpc3RhbmNlIGlzIGFib3V0IDQ0MDAgbS4gU28gd2Ugd2lsbCBkZWNpZGUgb24gZGlzdGFuY2VzIHVzaW5nIHRoaXM6ICRcZnJhY3s0NDAwfTMgPSAxNTAwJCAkbSQuIEJleW9uZCB0aGF0IHdlIGFjdHVhbGx5IHJ1biBpbnRvIGEgcGFpcndpc2UgY29tcGFyaXNvbiBhbmQgZ2V0IGFuIGVkZ2UgZWZmZWN0LiANCg0KIyMjIyMgUm9sbGluZyB5b3VyIG93biBwbG90DQoNCk9oLCBvbmUgbW9yZSB0aGluZyBmb3IgdGhlIGdncGxvdCBjcm93ZC4gTm90ZSB0aGF0IGFsbCB0aGUgZGF0YSBmb3IgdGhlIGNvcnJlbG9ncmFtIGlzIHN0b3JlZCBpbiBhbiBlYXN5IHRvIGFjY2VzcyBmYXNoaW9uIGluIHRoZSBgYGBsZWFkSWBgYCBvYmplY3QuIFlvdSBjYW4gcm9sbCB5b3VyIG93biBwbG90IGluIHlvdSB3YW50IGluIGdncGxvdC4gQW5kIHVubGlrZSB0aGUgZGVmYXVsdCBwbG90IGFib3ZlLCB3ZSBjYW4gY29udHJvbCB0aGUgYWxwaGEgbGV2ZWwgZm9yIHBsb3R0aW5nIHRoZSBzaWduaWZpY2FuY2UgZnJvbSB0aGUgcGVybXV0YXRpb24gdGVzdC4gRS5nLiwNCg0KYGBge3IgZ2dwbG90LCB3YXJuaW5nPUZ9DQpkYXRhLmZyYW1lKG49bGVhZEkkbiwNCiAgICAgICAgICAgSSA9IGxlYWRJJGNvcnJlbGF0aW9uLCANCiAgICAgICAgICAgZCA9IGxlYWRJJG1lYW4ub2YuY2xhc3MsDQogICAgICAgICAgIHAgPSBsZWFkSSRwKSAlPiUNCiAgbXV0YXRlKFNpZ25pZmljYW50ID0gcCA8IC4wMSkgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCxsaW5ldHlwZT0iZGFzaGVkIikgKw0KICBnZW9tX3BhdGgoYWVzKHggPSBkLCB5ID0gSSxncm91cCA9IDEsIGNvbG9yPVNpZ25pZmljYW50KSxzaXplPTEpICsgDQogIGdlb21fcG9pbnQoYWVzKHggPSBkLCB5ID0gSSwgZmlsbD1TaWduaWZpY2FudCksc2l6ZT01LHBjaD0yMSkgKw0KICBsaW1zKHg9YygwLDE1MDApLHk9YygtMC42LDAuNikpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygic3RlZWxibHVlIiwicGx1bSIpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJzdGVlbGJsdWUiLCJwbHVtIikpICsNCiAgbGFicyh4PSJEaXN0YW5jZSAobSkiLHk9Ik1vcmFuJ3MgSSIsDQogICAgICAgdGl0bGUgPSAiQXV0b2NvcnJlbGF0aW9uIG9mIExlYWQiLHN1YnRpdGxlID0gIkNyaXQgdmFsdWUgb2YgcDwwLjAxIikNCmBgYA0KDQoNCiMjIyMjIENvbnRpbnVvdXMgQ29ycmVsb2dyYW0NCg0KVGhlIHBsb3QgYWJvdmUgaXMgZGlzY3JldGUuIEl0IGlzIHNob3dpbmcgYXV0b2NvcnJlbGF0aW9uIGluIGRpc3RhbmNlIGNhdGVnb3JpZXMuIFdlIGNhbiBhbHNvIGNhbGN1bGF0ZSBhIGNvbnRpbnVvdXMgZnVuY3Rpb246DQoNCmBgYHtyIGNvbnRpbnVvdXN9DQpsZWFkSSA8LSBzcGxpbmUuY29ycmVsb2coeD1tZXVzZVgsIHk9bWV1c2VZLCB6PW1ldXNlX3NmJGxlYWQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHJlc2FtcD0xMDAsIHhtYXg9MTUwMCwgcXVpZXQ9VFJVRSkNCnBsb3QobGVhZEkpDQpgYGANCg0KVGhpcyBnaXZlcyB0aGUgc2FtZSBwaWN0dXJlLiBZb3UgY2FuIHJlYWQgdXAgb24gdGhlc2UgZnVuY3Rpb25zIHRvIGxlYXJuIHRoZSBzdWJ0bGV0aWVzLiBJ4oCZbGwgdW5wYWNrIHRoaXMgaW4gZ3JlYXRlciBkZXRhaWwgaW4gdGhlIGxlY3R1cmUuDQoNCg0KDQoNCg0K