I ran some preliminary analyses as part of my current research studying mussel larval connectivity (see https://www.intertidalbuffet.com/population-connectivity for details and other analysis). They were interesting enough, and fun enough to code, that I decided to post my process online. Before we get into the analysis though, here’s a bit of back-story.

Mussel Dispersal

Mussels are basically sedentary as adults, and don’t move from the rock/dock/whatever they’re attached to. That means that the mussel you order at a restaurant has always lived exactly where the fisherman caught it (before it wound up on your plate of course). Even though that mussel hasn’t moved as an adult though, the mussel was born somewhere very different, and as a baby (aka “larvae”), floated through the water with ocean currents until settling down somewhere new to grow up and eventually be caught by the fisherman. This larval dispersal phase is important to connecting mussel populations separated across large spatial scales, and can be very important to managing mussel populations for fisheries, or predicting how mussel populations might react to things like climate change (again, see see the blog section of https://www.intertidalbuffet.com/population-connectivity for details).

Research Goal

Some colleagues and I are trying to understand how mussel populations in Maine are connected to each other - so, if you see an adult mussel on a rock, be able to predict where that mussel was born and traveled from as a larvae. One way we are doing this is using the chemical composition of mussel shells.

How Mussel Shell Chemistry Helps Us

When a mussel is born, it starts growing a shell within 24-hours. The mussel needs calcium to grow its shell, and so extracts calcium from the water around it. By accident, when taking this calcium from the water, the mussel also takes all the other chemicals/elements in the water around it and binds them up in its shell. Because the chemistry of the water changes as you go from one place to another along the coast, that mussel is unintentionally tagging itself with a chemical signal of where it was born.

As scientists, we raised mussel larvae up and down the coast, letting them grow in these different chemistry waters. We then took those mussel larvae, blasted their shells with a laser, and sucked the aerosolized shell into a “mass spectrometer” to measure the chemistry. Based on this data, we can build a map of what mussel shell chemistry should look like if a mussel was born at different places up and down the coast. Then, we can take a wild mussel that just settled down to grow up into an adult, look at the chemistry of that mussel’s larval shell, and compare it to the map to see where the wild mussel was born.

How This Analysis Should Work

The intent of this analysis will be to train a model so we can feed it mussel shell chemistry data, and it predicts where that chemistry matches on our map. We will train the model with the chemistry data from larvae we raised in different bays up and down the Maine coastline. We will then predict the larval sources (birthplace) for wild mussel settlers that we gathered in mussel beds.

The Analysis

Data cleaning and prep

So first, we load a bunch of packages we’ll need, and import the data set. You can see that the data, where each line is an individual mussel shell, have a bunch of data on the where that mussel shell was collected, and then the chemistry data of that shell. In this case, the “Site” corresponds to the bay name, and all of the 2 letter codes (e.g., “Mg”, “Mn”, etc) represent different chemical elements.

# Load the necessary packages
library(plyr)
library(reshape2)
library(MASS)
library(stats)
library(ggplot2)
library(ggmap)
# Read in the ICPMS geochemistry data from 2015
ICPMS.all.2015.dat<-read.csv("/Users/scottmorello/Dropbox/Archives/Jobs/Data Science Search/Insight/Insight_Interview_Code/2015.All.Data.csv")
head(ICPMS.all.2015.dat)

Now we subset out just the chemistry data for the larvae, and divide it up between the site level data (.env) and chemistry data (.val). This larval data corresponds to the mussel larvae we raised in each of the bays up and down the coast, and are used to build out map. Thus, we’ll use this data to eventually train our model to classify chemistry signals to the correct bay/site.

# Subset out the data from larvae raised in each bay to train the model
# I'll need to turn Site into a factor, separate out the actual values (".val")
# and data about the sites (".env")
larvae.dat<-subset(ICPMS.all.2015.dat,Type=="Larvae")
larvae.dat$Site<-factor(larvae.dat$Site)
larvae.dat.env<-larvae.dat[,c(1:4)]
larvae.dat.val<-larvae.dat[,-c(1:4)]
head(larvae.dat.env)
head(larvae.dat.val)

I then want to test whether the chemistry data is normally distributed for each group (site). There are certain normality assumptions for the tests I’ll use later, so we should deal with the issue now. I wind up writing a function that will cycle through each unique site and chemical element, and test for normality using a Shapiro-Wilk test. The function will return a list where one table are the p-values associated with Shapiro-Wilk test, and a table of True/False values for whether the test result was significant based on an alpha > 0.05 (True = Normal, False = Not Normal).

I find that the raw chemistry data is pretty non-normal.

# I want test whether data are normally disrtributed, so I make a function to itteratively
# go through each group (site), and test normality of each varaible (geochemical element)
# using a shapiro wilk test
all.norm.test<-function(unique.sites,norm.test.data,norm.test.env){
  sites.normtest<-data.frame(site=unique.sites,Mg=NA,Mn=NA,Co=NA,Cu=NA,Sr=NA,Ba=NA,La=NA,Pb=NA)
  for (i in 1:length(unique.sites)){
    sites.normtest[i,"Mg"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Mg"])[2]
    sites.normtest[i,"Mn"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Mn"])[2]
    sites.normtest[i,"Co"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Co"])[2]
    sites.normtest[i,"Cu"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Cu"])[2]
    sites.normtest[i,"Sr"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Sr"])[2]
    sites.normtest[i,"Ba"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Ba"])[2]
    sites.normtest[i,"La"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"La"])[2]
    sites.normtest[i,"Pb"]<-shapiro.test(norm.test.data[which(norm.test.env$Site==unique.sites[i]),"Pb"])[2]
  }
  sites.normtest.list<-list(P.Values=cbind(data.frame(Site=sites.normtest$site),round(sites.normtest[,-1],4)),
                            Significance=cbind(data.frame(Site=sites.normtest$site),sites.normtest[,-1]>0.05))
  return(sites.normtest.list)
}
# Now I test for normality, and most variables are not normally distributed
unique.sites.larv<-unique(larvae.dat.env$Site)
all.norm.test(unique.sites.larv,larvae.dat.val,larvae.dat.env)
$P.Values

$Significance
NA

I log transform the chemistry data, and that mostly solves the problem.

# I log transform the data to normalize it, which mostly solves the problem
larvae.dat.val.trans<-log(larvae.dat.val,10)
all.norm.test(unique.sites.larv,larvae.dat.val.trans,larvae.dat.env)
$P.Values

$Significance
NA

The remaining groups/elements that do not pass the Shapiro-Wilk test, I look at their Q-Q Plots to assess normality more subjectively. Generally, they look pretty good, aside from issues such as single outliers and some long tails. I decide that the log transformation is doing all I need it to, and that it’s more important for me to retain as much data as possible to train the model - considering I’ll be dealing with low sample sizes (yes, that becomes an issue later).

# I write a script just to look at the data that failed the Shapiro-Wilk test,
# and plot the Q-Q plot so I can look at them. Generaly, they look ok, except
# for a few which either have a single outlier, or some long tails. Either way, I
# choose to stick with the log transformation
sig.dat<-all.norm.test(unique.sites.larv,larvae.dat.val.trans,larvae.dat.env)$Significance
rownames(sig.dat)<-sig.dat$Site
sig.dat<-sig.dat[,-1]
false.vec <- arrayInd(which(sig.dat==FALSE), dim(sig.dat))
par(mfrow=c(ceiling(sqrt(nrow(false.vec))),floor(sqrt(nrow(false.vec)))))
for (i in 1:nrow(false.vec)){
  qqnorm(larvae.dat.val.trans[which(larvae.dat.env$Site==dimnames(sig.dat)[[1]][false.vec[i,1]]),
                              dimnames(sig.dat)[[2]][false.vec[i,2]]],
         main=paste(dimnames(sig.dat)[[1]][false.vec[i,1]],dimnames(sig.dat)[[2]][false.vec[i,2]]))
}
par(mfrow=c(1,1))

So now that I have the training data set, I subset out the test data - chemistry data from the larval shells of wild mussel settlers. I need to correct a few of the site labels since the “TFP” site has multiple names in the data set, but is really only one site. I log transform this data too, which seems to do a decent job of normalizing the data again.

# Now I do the same, subsetting and transformation, but with data from settled mussels
# which we would like to assign to a bay, based on the larval geochemical data I
# imported before. I need to correct one set of names though (2 TFP sites into 1)
settler.dat<-subset(ICPMS.all.2015.dat,Type=="Settler")
settler.dat$Site[which(settler.dat$Site=="TFP_1")]<-"TFP"
settler.dat$Site[which(settler.dat$Site=="TFP_2")]<-"TFP"
settler.dat$Site<-factor(settler.dat$Site)
settler.dat.env<-settler.dat[,c(1:4)]
settler.dat.val<-settler.dat[,-c(1:4)]
settler.dat.val.trans<-log(settler.dat.val,10)
unique.sites.settler<-unique(settler.dat.env$Site)
all.norm.test(unique.sites.settler,settler.dat.val.trans,settler.dat.env)
$P.Values

$Significance
NA

For those data that don’t seem normal, I take a look at the Q-Q plots again, which I’m OK with for the same reasons I stated before.

sig.dat<-all.norm.test(unique.sites.settler,settler.dat.val.trans,settler.dat.env)$Significance
rownames(sig.dat)<-sig.dat$Site
sig.dat<-sig.dat[,-1]
false.vec <- arrayInd(which(sig.dat==FALSE), dim(sig.dat))
par(mfrow=c(ceiling(sqrt(nrow(false.vec))),floor(sqrt(nrow(false.vec)))+1))
for (i in 1:nrow(false.vec)){
  qqnorm(settler.dat.val.trans[which(settler.dat.env$Site==dimnames(sig.dat)[[1]][false.vec[i,1]]),
                               dimnames(sig.dat)[[2]][false.vec[i,2]]],
         main=paste(dimnames(sig.dat)[[1]][false.vec[i,1]],dimnames(sig.dat)[[2]][false.vec[i,2]]))
}
par(mfrow=c(1,1))

The model

I choose to use a Quadratic Discriminant Function Analysis (QDA) for a variety of reasons. For one, it’s the most common method for classifying chemical signatures in ecology. That’s not always a good reason to use an analysis though, and so QDA is also relatively interpretable and easy to understand. That, and because it’s been around for a while, are probably why it is so commonly used in ecology. A QDA predicts group membership based on pre-trained groupings by calculating discriminant axes with quadratic combinations of chemical elements. The analysis attempts to minimize the variation within groups and maximize the variation between groups. To be candid, we are working on developing other classification models with our research (e.g., Random Forrest, Infinite Mixture Models), but for today’s preliminary analysis, I thought it best to use something pretty common in the field for doing what we’d like to.

Training the model

So first, we use train the QDA with the chemistry data from larvae we raised - since we know which bays those chemistry signals correspond to. We also use leave-one-out Cross-Validation to test how accurate well the model is at predicting a site based on chemistry data. Looking at the confusion matrix, the model actually doesn’t do so well. It only classifies ~30% of data correctly.

# So now I setup a Quadratic Discriminant Function analysis.
# I chose quadratic becasue the covariance matrices are more than likely
# unequal among groups (sites), based on previous research.
geochem.qda <- qda(larvae.dat.val.trans, larvae.dat.env$Site)
# I'll evaluate the model using Cross Validation
geochem.qda.CV <- qda(larvae.dat.val.trans, larvae.dat.env$Site,CV=TRUE)
# and now look at the confusion matrix, the overall correct assignments (total), and the
# group (site) specific correct assignments
confusion.table<-table(data.frame(Predicted=geochem.qda.CV$class,Actual=larvae.dat.env$Site))
confusion.table.total<-sum(confusion.table[row(confusion.table)==col(confusion.table)])/sum(confusion.table)
confusion.table.total
[1] 0.3007812

When we look at the breakdown by site, there’s a pretty broad range.

confusion.table.diag<-diag(confusion.table/rowSums(confusion.table))
confusion.table.diag
       CHR        FBE        FBW         GB         MB        MBR        PHB        TFP 
0.14814815 0.31034483 0.27777778 0.34883721 0.00000000 0.25000000 0.43373494 0.10000000 
       WRL 
0.05263158 

This begs the question: “why are our classifications so bad?”. If we look at how the percent correctly classified to a site relates to how many individual larvae were used to train that site’s signal in the QDA model, there is a pretty direct relationship (i.e., more individuals used to train a site = better classification). This is depicted below with the statistical output from a linear model (and a Q-Q plot to check the fit), and a graphical the output from the linear model showing the fit (black solid line) and 95% CIs (red dotted lines).

# The correct assignments by site seem to be pretty well related to the training sample size
# explaining about 60% of the observed variaiton
train.sizes<-summary(geochem.qda.CV$class)
size.confusion.lm<-lm(confusion.table.diag~train.sizes)
summary(size.confusion.lm)

Call:
lm(formula = confusion.table.diag ~ train.sizes)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.11712 -0.05866 -0.02807  0.06792  0.14972 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)  
(Intercept) 0.081749   0.051575   1.585   0.1570  
train.sizes 0.004632   0.001406   3.294   0.0132 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.09767 on 7 degrees of freedom
Multiple R-squared:  0.6078,    Adjusted R-squared:  0.5518 
F-statistic: 10.85 on 1 and 7 DF,  p-value: 0.01323
plot(size.confusion.lm,which=2)

size.confusion.predicted<-predict(size.confusion.lm,data.frame(train.sizes=c(0:100)),interval = "confidence")
plot(confusion.table.diag~train.sizes,ylab="Percent Correctly Assigned",xlab="Training Set Sample Size")
lines(c(0:100), size.confusion.predicted[ ,1], lty = "solid", col = "black")
lines(c(0:100), size.confusion.predicted[ ,2], lty = "dashed", col = "red")
lines(c(0:100), size.confusion.predicted[ ,3], lty = "dashed", col = "red")

Unfortunately, there isn’t much we can do with this preliminary analysis. As our lab processes more samples, and builds a larger training data set, the accuracy of our model will hopefully improve. In the meantime, if we want to understand larval connectivity in mussels, this is all we have to work with, so let’s continue.

Predicting The Birthplace of Settled Mussels

I take the data set of wild mussel settler shell chemistry (which the mussels laid down when they were born), and use the QDA to predict the site where that chemistry came from. I implement a flat prior, based on the total number of sites, since we have no underlying assumptions as to which source site produced the most settlers. Below is a plot of the posterior distribution of site assignments, where the x axis are the sites a settler was assigned to based on its shell chemistry, the y axis is the frequency it was assigned to that site, and the panels separate out The site that mussel was found/settled in (i.e., where it traveled to and will grow to be an adult).

# Now I'll try to assign settled mussels to sources based on the trained QDFA of geochemical
# signitures, and use a flat prior
n.sources<-length(unique.sites.larv)
settler.prediction<-predict(geochem.qda,settler.dat.val.trans,prior=(rep(1,n.sources)/n.sources))
# Now let me plot the posterior assignments to see how they're looking. The
# model seems to indicate that the settlers are all coming from one source
settler.prediction.melt<-as.data.frame(settler.prediction$posterior)
settler.prediction.melt$Settled<-settler.dat.env$Site
settler.prediction.melt$Individual<-factor(c(1:nrow(settler.prediction.melt)))
settler.prediction.melt<-melt(settler.prediction.melt)
colnames(settler.prediction.melt)<-c("Location_Settled","Individual","Source","Posterior_Assignment")
settler.posterior.graph<-ggplot(settler.prediction.melt,aes(x=Source,y=Posterior_Assignment,group=Individual))+
  geom_line()+
  facet_wrap(~Location_Settled,ncol=1)+
  theme_bw()
settler.posterior.graph

We can already see that most wild mussel settlers were predicted to have come from the sites CHR or PHB (x axis), no matter where they ended up settling (panels). But, for some settlers, even the site where it was predicted most likely to have come from (i.e., was most frequently assigned - the y axis), the assignment level was pretty low (<50% probability of assignment).

To account for this, we run some Quality Control. For an individual settler, not only do we only keep the best predicted source based on the model, but we also only keep that source assignment if it’s confidence in >50%.

# I summarize the posteriors for each group of settlers, only keeping the predicted source
# with the greatest posterior probability of assignment. Then, I remove any individuals
# where the maximum posterior probability was still <50%. Then I resummarize the data
# by group (settlement site)
settler.prediction.summary<-ddply(settler.prediction.melt,.(Individual,Location_Settled),
                                  summarize,Max.Source=Source[which.max(Posterior_Assignment)],
                                  Max.Val=Posterior_Assignment[which.max(Posterior_Assignment)])
settler.prediction.summary.table<-table(subset(settler.prediction.summary,Max.Val>0.5)[,c(2,3)])/
  rowSums(table(subset(settler.prediction.summary,Max.Val>0.5)[,c(2,3)]))
# Now I collapse the data, and attach site coordinates to each settlement point and
# source (I use the match function and another imported dataset)
connect.table<-melt(settler.prediction.summary.table)
colnames(connect.table)<-c("Settled","predicted_source","Connectivity")
site.locs<-read.csv("/Users/scottmorello/Dropbox/Archives/Jobs/Data Science Search/Insight/Insight_Interview_Code/MuLTI-2_Station Information.csv")
connect.table$to.lon<-site.locs$Longitude[match(as.character(connect.table$Settled),
                                                as.character(site.locs$Station.Code))]
connect.table$to.lat<-site.locs$Latitude[match(as.character(connect.table$Settled),
                                               as.character(site.locs$Station.Code))]
connect.table$from.lon<-site.locs$Longitude[match(as.character(connect.table$predicted_source),
                                                  as.character(site.locs$Station.Code))]
connect.table$from.lat<-site.locs$Latitude[match(as.character(connect.table$predicted_source),
                                                 as.character(site.locs$Station.Code))]

Now I re plot the data as a bar graph, so that it shows for each settlement site along the coast (panels), which birth-site (x axis) settlers were most frequently came from (# of settlers predicted to come from a source/total # of settlers at a site - y axis).

# Now I plot the percent of settlers coming from each larval source by settlement site
# We can see that sites have disproportionately high numbers of larvae coming
# from CHR and PHB
source.graph<-ggplot(connect.table,aes(x=predicted_source,y=Connectivity))+
  geom_bar(stat="identity")+
  facet_wrap(~Settled,ncol=1)+
  xlab("Source")+
  theme_bw()
source.graph

Final Answer

This sort of graphical display is pretty difficult to interpret and visualize without some help… so, “ggmap” package to the rescue! Here is a map showing how each site in northern Maine is connected to each other by larval dispersal. Each settlement site (white point) is connected to each birth place by an arrow (from birth place to settlement site). How dense that arrow is corresponds to the percent of settlers that came from that birth place (the darker the arrow, the more settlers were born there). To represent settlers that were born at a site and returned to their birthplace to settle, we use a black circle in the middle of the site (the larger the black circle, the more settlers returned to their birth place).

# Now, I can put all of this info onto a map to better visualize the connections among
# sites (connectivity) and retention within sites.
downeast.map <- get_map(location="Milbridge, Maine",maptype="terrain",zoom=9,crop=FALSE)
connect.map <- ggmap(downeast.map) + 
  geom_text(data=site.locs[,c(4,5,6)],aes(x=Longitude,y=Latitude,label=Station.Code),
            size=4,color="black", vjust=2.75,fontface="bold")+
  geom_point(data=site.locs[,c(5,6)],aes(x=Longitude,y=Latitude),size=10,color="black")+
  geom_point(data=site.locs[,c(5,6)],aes(x=Longitude,y=Latitude),size=9,color="white")+
  geom_point(data=connect.table[-which(as.character(connect.table$Settled)!=as.character(connect.table$predicted_source)),],
             aes(x=to.lon,y=to.lat,size=Connectivity),color="black")+
  geom_curve(data=connect.table[which(as.character(connect.table$Settled)!=as.character(connect.table$predicted_source)),], 
             aes(x=to.lon, y=to.lat, xend=from.lon, yend=from.lat,colour=Connectivity,alpha=Connectivity,frame=predicted_source),
             arrow=arrow(length=unit(0.4,"cm"), ends="first",angle=15),
             color="black",curvature = 0.15,position = "jitter",inherit.aes=TRUE,size=1.5) +
  coord_cartesian()+
  labs(x="Longitude",y="Latitude")+
  scale_x_continuous(limits = c(-68.4, -67.3), expand = c(0, 0)) +
  scale_y_continuous(limits = c(44.15, 44.85), expand = c(0, 0))+
  scale_size_continuous(name="Self-Seeding")
connect.map

The map does a good job of showing how settlers in the mid-coast area (e.g., GB, PHB, HBR, PLR) mostly come from a site to the northeast (CHR), or from within the mid-coast area (PHB). Settlers in the more southern area (TFP) generally come from the mid-coast (PHB), and occasionally from the area to the far northeast (CHR). This pattern is pretty consistent with other data and analyses we’ve conducted (see https://www.intertidalbuffet.com/population-connectivity for details and other analysis), and tells us that even though our QDA model accuracy needs to be improved (with more training data), the model is probably predicting where settlers come from pretty well!

OK, that was a lot, and much more science-y than the rest of the stuff I include in this section of the webpage. If you have any questions or comments, feel free to email me!

Hope you enjoyed the analysis!

LS0tCnRpdGxlOiAiUXVpY2sgYW5kIFByZWxpbWluYXJ5IEFuYWx5c2lzIG9mIE11c3NlbCBMYXJ2YWwgRGlzcGVyc2FsIFBhdGh3YXlzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpJIHJhbiBzb21lIHByZWxpbWluYXJ5IGFuYWx5c2VzIGFzIHBhcnQgb2YgbXkgY3VycmVudCByZXNlYXJjaCBzdHVkeWluZyBtdXNzZWwgbGFydmFsIGNvbm5lY3Rpdml0eSAoc2VlIGh0dHBzOi8vd3d3LmludGVydGlkYWxidWZmZXQuY29tL3BvcHVsYXRpb24tY29ubmVjdGl2aXR5IGZvciBkZXRhaWxzIGFuZCBvdGhlciBhbmFseXNpcykuIFRoZXkgd2VyZSBpbnRlcmVzdGluZyBlbm91Z2gsIGFuZCBmdW4gZW5vdWdoIHRvIGNvZGUsIHRoYXQgSSBkZWNpZGVkIHRvIHBvc3QgbXkgcHJvY2VzcyBvbmxpbmUuIEJlZm9yZSB3ZSBnZXQgaW50byB0aGUgYW5hbHlzaXMgdGhvdWdoLCBoZXJlJ3MgYSBiaXQgb2YgYmFjay1zdG9yeS4KCiMjIE11c3NlbCBEaXNwZXJzYWwKTXVzc2VscyBhcmUgYmFzaWNhbGx5IHNlZGVudGFyeSBhcyBhZHVsdHMsIGFuZCBkb24ndCBtb3ZlIGZyb20gdGhlIHJvY2svZG9jay93aGF0ZXZlciB0aGV5J3JlIGF0dGFjaGVkIHRvLiBUaGF0IG1lYW5zIHRoYXQgdGhlIG11c3NlbCB5b3Ugb3JkZXIgYXQgYSByZXN0YXVyYW50IGhhcyBhbHdheXMgbGl2ZWQgZXhhY3RseSB3aGVyZSB0aGUgZmlzaGVybWFuIGNhdWdodCBpdCAoYmVmb3JlIGl0IHdvdW5kIHVwIG9uIHlvdXIgcGxhdGUgb2YgY291cnNlKS4gRXZlbiB0aG91Z2ggdGhhdCBtdXNzZWwgaGFzbid0IG1vdmVkIGFzIGFuIGFkdWx0IHRob3VnaCwgdGhlIG11c3NlbCB3YXMgYm9ybiBzb21ld2hlcmUgdmVyeSBkaWZmZXJlbnQsIGFuZCBhcyBhIGJhYnkgKGFrYSAibGFydmFlIiksIGZsb2F0ZWQgdGhyb3VnaCB0aGUgd2F0ZXIgd2l0aCBvY2VhbiBjdXJyZW50cyB1bnRpbCBzZXR0bGluZyBkb3duIHNvbWV3aGVyZSBuZXcgdG8gZ3JvdyB1cCBhbmQgZXZlbnR1YWxseSBiZSBjYXVnaHQgYnkgdGhlIGZpc2hlcm1hbi4gVGhpcyBsYXJ2YWwgZGlzcGVyc2FsIHBoYXNlIGlzIGltcG9ydGFudCB0byBjb25uZWN0aW5nIG11c3NlbCBwb3B1bGF0aW9ucyBzZXBhcmF0ZWQgYWNyb3NzIGxhcmdlIHNwYXRpYWwgc2NhbGVzLCBhbmQgY2FuIGJlIHZlcnkgaW1wb3J0YW50IHRvIG1hbmFnaW5nIG11c3NlbCBwb3B1bGF0aW9ucyBmb3IgZmlzaGVyaWVzLCBvciBwcmVkaWN0aW5nIGhvdyBtdXNzZWwgcG9wdWxhdGlvbnMgbWlnaHQgcmVhY3QgdG8gdGhpbmdzIGxpa2UgY2xpbWF0ZSBjaGFuZ2UgKGFnYWluLCBzZWUgc2VlIHRoZSBibG9nIHNlY3Rpb24gb2YgaHR0cHM6Ly93d3cuaW50ZXJ0aWRhbGJ1ZmZldC5jb20vcG9wdWxhdGlvbi1jb25uZWN0aXZpdHkgZm9yIGRldGFpbHMpLgoKIyMgUmVzZWFyY2ggR29hbApTb21lIGNvbGxlYWd1ZXMgYW5kIEkgYXJlIHRyeWluZyB0byB1bmRlcnN0YW5kIGhvdyBtdXNzZWwgcG9wdWxhdGlvbnMgaW4gTWFpbmUgYXJlIGNvbm5lY3RlZCB0byBlYWNoIG90aGVyIC0gc28sIGlmIHlvdSBzZWUgYW4gYWR1bHQgbXVzc2VsIG9uIGEgcm9jaywgYmUgYWJsZSB0byBwcmVkaWN0IHdoZXJlIHRoYXQgbXVzc2VsIHdhcyBib3JuIGFuZCB0cmF2ZWxlZCBmcm9tIGFzIGEgbGFydmFlLiBPbmUgd2F5IHdlIGFyZSBkb2luZyB0aGlzIGlzIHVzaW5nIHRoZSBjaGVtaWNhbCBjb21wb3NpdGlvbiBvZiBtdXNzZWwgc2hlbGxzLgoKIyMjIyBIb3cgTXVzc2VsIFNoZWxsIENoZW1pc3RyeSBIZWxwcyBVcwpXaGVuIGEgbXVzc2VsIGlzIGJvcm4sIGl0IHN0YXJ0cyBncm93aW5nIGEgc2hlbGwgd2l0aGluIDI0LWhvdXJzLiBUaGUgbXVzc2VsIG5lZWRzIGNhbGNpdW0gdG8gZ3JvdyBpdHMgc2hlbGwsIGFuZCBzbyBleHRyYWN0cyBjYWxjaXVtIGZyb20gdGhlIHdhdGVyIGFyb3VuZCBpdC4gQnkgYWNjaWRlbnQsIHdoZW4gdGFraW5nIHRoaXMgY2FsY2l1bSBmcm9tIHRoZSB3YXRlciwgdGhlIG11c3NlbCBhbHNvIHRha2VzIGFsbCB0aGUgb3RoZXIgY2hlbWljYWxzL2VsZW1lbnRzIGluIHRoZSB3YXRlciBhcm91bmQgaXQgYW5kIGJpbmRzIHRoZW0gdXAgaW4gaXRzIHNoZWxsLiBCZWNhdXNlIHRoZSBjaGVtaXN0cnkgb2YgdGhlIHdhdGVyIGNoYW5nZXMgYXMgeW91IGdvIGZyb20gb25lIHBsYWNlIHRvIGFub3RoZXIgYWxvbmcgdGhlIGNvYXN0LCB0aGF0IG11c3NlbCBpcyB1bmludGVudGlvbmFsbHkgdGFnZ2luZyBpdHNlbGYgd2l0aCBhIGNoZW1pY2FsIHNpZ25hbCBvZiB3aGVyZSBpdCB3YXMgYm9ybi4KCkFzIHNjaWVudGlzdHMsIHdlIHJhaXNlZCBtdXNzZWwgbGFydmFlIHVwIGFuZCBkb3duIHRoZSBjb2FzdCwgbGV0dGluZyB0aGVtIGdyb3cgaW4gdGhlc2UgZGlmZmVyZW50IGNoZW1pc3RyeSB3YXRlcnMuIFdlIHRoZW4gdG9vayB0aG9zZSBtdXNzZWwgbGFydmFlLCBibGFzdGVkIHRoZWlyIHNoZWxscyB3aXRoIGEgbGFzZXIsIGFuZCBzdWNrZWQgdGhlIGFlcm9zb2xpemVkIHNoZWxsIGludG8gYSAibWFzcyBzcGVjdHJvbWV0ZXIiIHRvIG1lYXN1cmUgdGhlIGNoZW1pc3RyeS4gQmFzZWQgb24gdGhpcyBkYXRhLCB3ZSBjYW4gYnVpbGQgYSBtYXAgb2Ygd2hhdCBtdXNzZWwgc2hlbGwgY2hlbWlzdHJ5IHNob3VsZCBsb29rIGxpa2UgaWYgYSBtdXNzZWwgd2FzIGJvcm4gYXQgZGlmZmVyZW50IHBsYWNlcyB1cCBhbmQgZG93biB0aGUgY29hc3QuIFRoZW4sIHdlIGNhbiB0YWtlIGEgd2lsZCBtdXNzZWwgdGhhdCBqdXN0IHNldHRsZWQgZG93biB0byBncm93IHVwIGludG8gYW4gYWR1bHQsIGxvb2sgYXQgdGhlIGNoZW1pc3RyeSBvZiB0aGF0IG11c3NlbCdzIGxhcnZhbCBzaGVsbCwgYW5kIGNvbXBhcmUgaXQgdG8gdGhlIG1hcCB0byBzZWUgd2hlcmUgdGhlIHdpbGQgbXVzc2VsIHdhcyBib3JuLgoKIyMjIyBIb3cgVGhpcyBBbmFseXNpcyBTaG91bGQgV29yawpUaGUgaW50ZW50IG9mIHRoaXMgYW5hbHlzaXMgd2lsbCBiZSB0byB0cmFpbiBhIG1vZGVsIHNvIHdlIGNhbiBmZWVkIGl0IG11c3NlbCBzaGVsbCBjaGVtaXN0cnkgZGF0YSwgYW5kIGl0IHByZWRpY3RzIHdoZXJlIHRoYXQgY2hlbWlzdHJ5IG1hdGNoZXMgb24gb3VyIG1hcC4gV2Ugd2lsbCB0cmFpbiB0aGUgbW9kZWwgd2l0aCB0aGUgY2hlbWlzdHJ5IGRhdGEgZnJvbSBsYXJ2YWUgd2UgcmFpc2VkIGluIGRpZmZlcmVudCBiYXlzIHVwIGFuZCBkb3duIHRoZSBNYWluZSBjb2FzdGxpbmUuIFdlIHdpbGwgdGhlbiBwcmVkaWN0IHRoZSBsYXJ2YWwgc291cmNlcyAoYmlydGhwbGFjZSkgZm9yIHdpbGQgbXVzc2VsIHNldHRsZXJzIHRoYXQgd2UgZ2F0aGVyZWQgaW4gbXVzc2VsIGJlZHMuCgojIyBUaGUgQW5hbHlzaXMKCiMjIyMgRGF0YSBjbGVhbmluZyBhbmQgcHJlcApTbyBmaXJzdCwgd2UgbG9hZCBhIGJ1bmNoIG9mIHBhY2thZ2VzIHdlJ2xsIG5lZWQsIGFuZCBpbXBvcnQgdGhlIGRhdGEgc2V0LiBZb3UgY2FuIHNlZSB0aGF0IHRoZSBkYXRhLCB3aGVyZSBlYWNoIGxpbmUgaXMgYW4gaW5kaXZpZHVhbCBtdXNzZWwgc2hlbGwsIGhhdmUgYSBidW5jaCBvZiBkYXRhIG9uIHRoZSB3aGVyZSB0aGF0IG11c3NlbCBzaGVsbCB3YXMgY29sbGVjdGVkLCBhbmQgdGhlbiB0aGUgY2hlbWlzdHJ5IGRhdGEgb2YgdGhhdCBzaGVsbC4gSW4gdGhpcyBjYXNlLCB0aGUgIlNpdGUiIGNvcnJlc3BvbmRzIHRvIHRoZSBiYXkgbmFtZSwgYW5kIGFsbCBvZiB0aGUgMiBsZXR0ZXIgY29kZXMgKGUuZy4sICJNZyIsICJNbiIsIGV0YykgcmVwcmVzZW50IGRpZmZlcmVudCBjaGVtaWNhbCBlbGVtZW50cy4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgTG9hZCB0aGUgbmVjZXNzYXJ5IHBhY2thZ2VzCmxpYnJhcnkocGx5cikKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeShNQVNTKQpsaWJyYXJ5KHN0YXRzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dtYXApCgoKIyBSZWFkIGluIHRoZSBJQ1BNUyBnZW9jaGVtaXN0cnkgZGF0YSBmcm9tIDIwMTUKCklDUE1TLmFsbC4yMDE1LmRhdDwtcmVhZC5jc3YoIi9Vc2Vycy9zY290dG1vcmVsbG8vRHJvcGJveC9BcmNoaXZlcy9Kb2JzL0RhdGEgU2NpZW5jZSBTZWFyY2gvSW5zaWdodC9JbnNpZ2h0X0ludGVydmlld19Db2RlLzIwMTUuQWxsLkRhdGEuY3N2IikKaGVhZChJQ1BNUy5hbGwuMjAxNS5kYXQpCmBgYAoKTm93IHdlIHN1YnNldCBvdXQganVzdCB0aGUgY2hlbWlzdHJ5IGRhdGEgZm9yIHRoZSBsYXJ2YWUsIGFuZCBkaXZpZGUgaXQgdXAgYmV0d2VlbiB0aGUgc2l0ZSBsZXZlbCBkYXRhICguZW52KSBhbmQgY2hlbWlzdHJ5IGRhdGEgKC52YWwpLiBUaGlzIGxhcnZhbCBkYXRhIGNvcnJlc3BvbmRzIHRvIHRoZSBtdXNzZWwgbGFydmFlIHdlIHJhaXNlZCBpbiBlYWNoIG9mIHRoZSBiYXlzIHVwIGFuZCBkb3duIHRoZSBjb2FzdCwgYW5kIGFyZSB1c2VkIHRvIGJ1aWxkIG91dCBtYXAuIFRodXMsIHdlJ2xsIHVzZSB0aGlzIGRhdGEgdG8gZXZlbnR1YWxseSB0cmFpbiBvdXIgbW9kZWwgdG8gY2xhc3NpZnkgY2hlbWlzdHJ5IHNpZ25hbHMgdG8gdGhlIGNvcnJlY3QgYmF5L3NpdGUuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIFN1YnNldCBvdXQgdGhlIGRhdGEgZnJvbSBsYXJ2YWUgcmFpc2VkIGluIGVhY2ggYmF5IHRvIHRyYWluIHRoZSBtb2RlbAojIEknbGwgbmVlZCB0byB0dXJuIFNpdGUgaW50byBhIGZhY3Rvciwgc2VwYXJhdGUgb3V0IHRoZSBhY3R1YWwgdmFsdWVzICgiLnZhbCIpCiMgYW5kIGRhdGEgYWJvdXQgdGhlIHNpdGVzICgiLmVudiIpCmxhcnZhZS5kYXQ8LXN1YnNldChJQ1BNUy5hbGwuMjAxNS5kYXQsVHlwZT09IkxhcnZhZSIpCmxhcnZhZS5kYXQkU2l0ZTwtZmFjdG9yKGxhcnZhZS5kYXQkU2l0ZSkKbGFydmFlLmRhdC5lbnY8LWxhcnZhZS5kYXRbLGMoMTo0KV0KbGFydmFlLmRhdC52YWw8LWxhcnZhZS5kYXRbLC1jKDE6NCldCmhlYWQobGFydmFlLmRhdC5lbnYpCmhlYWQobGFydmFlLmRhdC52YWwpCmBgYAoKSSB0aGVuIHdhbnQgdG8gdGVzdCB3aGV0aGVyIHRoZSBjaGVtaXN0cnkgZGF0YSBpcyBub3JtYWxseSBkaXN0cmlidXRlZCBmb3IgZWFjaCBncm91cCAoc2l0ZSkuIFRoZXJlIGFyZSBjZXJ0YWluIG5vcm1hbGl0eSBhc3N1bXB0aW9ucyBmb3IgdGhlIHRlc3RzIEknbGwgdXNlIGxhdGVyLCBzbyB3ZSBzaG91bGQgZGVhbCB3aXRoIHRoZSBpc3N1ZSBub3cuIEkgd2luZCB1cCB3cml0aW5nIGEgZnVuY3Rpb24gdGhhdCB3aWxsIGN5Y2xlIHRocm91Z2ggZWFjaCB1bmlxdWUgc2l0ZSBhbmQgY2hlbWljYWwgZWxlbWVudCwgYW5kIHRlc3QgZm9yIG5vcm1hbGl0eSB1c2luZyBhIFNoYXBpcm8tV2lsayB0ZXN0LiBUaGUgZnVuY3Rpb24gd2lsbCByZXR1cm4gYSBsaXN0IHdoZXJlIG9uZSB0YWJsZSBhcmUgdGhlIHAtdmFsdWVzIGFzc29jaWF0ZWQgd2l0aCBTaGFwaXJvLVdpbGsgdGVzdCwgYW5kIGEgdGFibGUgb2YgVHJ1ZS9GYWxzZSB2YWx1ZXMgZm9yIHdoZXRoZXIgdGhlIHRlc3QgcmVzdWx0IHdhcyBzaWduaWZpY2FudCBiYXNlZCBvbiBhbiBhbHBoYSA+IDAuMDUgKFRydWUgPSBOb3JtYWwsIEZhbHNlID0gTm90IE5vcm1hbCkuCgpJIGZpbmQgdGhhdCB0aGUgcmF3IGNoZW1pc3RyeSBkYXRhIGlzIHByZXR0eSBub24tbm9ybWFsLgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBJIHdhbnQgdGVzdCB3aGV0aGVyIGRhdGEgYXJlIG5vcm1hbGx5IGRpc3J0cmlidXRlZCwgc28gSSBtYWtlIGEgZnVuY3Rpb24gdG8gaXR0ZXJhdGl2ZWx5CiMgZ28gdGhyb3VnaCBlYWNoIGdyb3VwIChzaXRlKSwgYW5kIHRlc3Qgbm9ybWFsaXR5IG9mIGVhY2ggdmFyYWlibGUgKGdlb2NoZW1pY2FsIGVsZW1lbnQpCiMgdXNpbmcgYSBzaGFwaXJvIHdpbGsgdGVzdAoKYWxsLm5vcm0udGVzdDwtZnVuY3Rpb24odW5pcXVlLnNpdGVzLG5vcm0udGVzdC5kYXRhLG5vcm0udGVzdC5lbnYpewogIHNpdGVzLm5vcm10ZXN0PC1kYXRhLmZyYW1lKHNpdGU9dW5pcXVlLnNpdGVzLE1nPU5BLE1uPU5BLENvPU5BLEN1PU5BLFNyPU5BLEJhPU5BLExhPU5BLFBiPU5BKQogIGZvciAoaSBpbiAxOmxlbmd0aCh1bmlxdWUuc2l0ZXMpKXsKICAgIHNpdGVzLm5vcm10ZXN0W2ksIk1nIl08LXNoYXBpcm8udGVzdChub3JtLnRlc3QuZGF0YVt3aGljaChub3JtLnRlc3QuZW52JFNpdGU9PXVuaXF1ZS5zaXRlc1tpXSksIk1nIl0pWzJdCiAgICBzaXRlcy5ub3JtdGVzdFtpLCJNbiJdPC1zaGFwaXJvLnRlc3Qobm9ybS50ZXN0LmRhdGFbd2hpY2gobm9ybS50ZXN0LmVudiRTaXRlPT11bmlxdWUuc2l0ZXNbaV0pLCJNbiJdKVsyXQogICAgc2l0ZXMubm9ybXRlc3RbaSwiQ28iXTwtc2hhcGlyby50ZXN0KG5vcm0udGVzdC5kYXRhW3doaWNoKG5vcm0udGVzdC5lbnYkU2l0ZT09dW5pcXVlLnNpdGVzW2ldKSwiQ28iXSlbMl0KICAgIHNpdGVzLm5vcm10ZXN0W2ksIkN1Il08LXNoYXBpcm8udGVzdChub3JtLnRlc3QuZGF0YVt3aGljaChub3JtLnRlc3QuZW52JFNpdGU9PXVuaXF1ZS5zaXRlc1tpXSksIkN1Il0pWzJdCiAgICBzaXRlcy5ub3JtdGVzdFtpLCJTciJdPC1zaGFwaXJvLnRlc3Qobm9ybS50ZXN0LmRhdGFbd2hpY2gobm9ybS50ZXN0LmVudiRTaXRlPT11bmlxdWUuc2l0ZXNbaV0pLCJTciJdKVsyXQogICAgc2l0ZXMubm9ybXRlc3RbaSwiQmEiXTwtc2hhcGlyby50ZXN0KG5vcm0udGVzdC5kYXRhW3doaWNoKG5vcm0udGVzdC5lbnYkU2l0ZT09dW5pcXVlLnNpdGVzW2ldKSwiQmEiXSlbMl0KICAgIHNpdGVzLm5vcm10ZXN0W2ksIkxhIl08LXNoYXBpcm8udGVzdChub3JtLnRlc3QuZGF0YVt3aGljaChub3JtLnRlc3QuZW52JFNpdGU9PXVuaXF1ZS5zaXRlc1tpXSksIkxhIl0pWzJdCiAgICBzaXRlcy5ub3JtdGVzdFtpLCJQYiJdPC1zaGFwaXJvLnRlc3Qobm9ybS50ZXN0LmRhdGFbd2hpY2gobm9ybS50ZXN0LmVudiRTaXRlPT11bmlxdWUuc2l0ZXNbaV0pLCJQYiJdKVsyXQogIH0KICBzaXRlcy5ub3JtdGVzdC5saXN0PC1saXN0KFAuVmFsdWVzPWNiaW5kKGRhdGEuZnJhbWUoU2l0ZT1zaXRlcy5ub3JtdGVzdCRzaXRlKSxyb3VuZChzaXRlcy5ub3JtdGVzdFssLTFdLDQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNpZ25pZmljYW5jZT1jYmluZChkYXRhLmZyYW1lKFNpdGU9c2l0ZXMubm9ybXRlc3Qkc2l0ZSksc2l0ZXMubm9ybXRlc3RbLC0xXT4wLjA1KSkKICByZXR1cm4oc2l0ZXMubm9ybXRlc3QubGlzdCkKfQoKIyBOb3cgSSB0ZXN0IGZvciBub3JtYWxpdHksIGFuZCBtb3N0IHZhcmlhYmxlcyBhcmUgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkCgp1bmlxdWUuc2l0ZXMubGFydjwtdW5pcXVlKGxhcnZhZS5kYXQuZW52JFNpdGUpCmFsbC5ub3JtLnRlc3QodW5pcXVlLnNpdGVzLmxhcnYsbGFydmFlLmRhdC52YWwsbGFydmFlLmRhdC5lbnYpCmBgYAoKSSBsb2cgdHJhbnNmb3JtIHRoZSBjaGVtaXN0cnkgZGF0YSwgYW5kIHRoYXQgbW9zdGx5IHNvbHZlcyB0aGUgcHJvYmxlbS4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgSSBsb2cgdHJhbnNmb3JtIHRoZSBkYXRhIHRvIG5vcm1hbGl6ZSBpdCwgd2hpY2ggbW9zdGx5IHNvbHZlcyB0aGUgcHJvYmxlbQoKbGFydmFlLmRhdC52YWwudHJhbnM8LWxvZyhsYXJ2YWUuZGF0LnZhbCwxMCkKYWxsLm5vcm0udGVzdCh1bmlxdWUuc2l0ZXMubGFydixsYXJ2YWUuZGF0LnZhbC50cmFucyxsYXJ2YWUuZGF0LmVudikKYGBgCgpUaGUgcmVtYWluaW5nIGdyb3Vwcy9lbGVtZW50cyB0aGF0IGRvIG5vdCBwYXNzIHRoZSBTaGFwaXJvLVdpbGsgdGVzdCwgSSBsb29rIGF0IHRoZWlyIFEtUSBQbG90cyB0byBhc3Nlc3Mgbm9ybWFsaXR5IG1vcmUgc3ViamVjdGl2ZWx5LiBHZW5lcmFsbHksIHRoZXkgbG9vayBwcmV0dHkgZ29vZCwgYXNpZGUgZnJvbSBpc3N1ZXMgc3VjaCBhcyBzaW5nbGUgb3V0bGllcnMgYW5kIHNvbWUgbG9uZyB0YWlscy4gSSBkZWNpZGUgdGhhdCB0aGUgbG9nIHRyYW5zZm9ybWF0aW9uIGlzIGRvaW5nIGFsbCBJIG5lZWQgaXQgdG8sIGFuZCB0aGF0IGl0J3MgbW9yZSBpbXBvcnRhbnQgZm9yIG1lIHRvIHJldGFpbiBhcyBtdWNoIGRhdGEgYXMgcG9zc2libGUgdG8gdHJhaW4gdGhlIG1vZGVsIC0gY29uc2lkZXJpbmcgSSdsbCBiZSBkZWFsaW5nIHdpdGggbG93IHNhbXBsZSBzaXplcyAoeWVzLCB0aGF0IGJlY29tZXMgYW4gaXNzdWUgbGF0ZXIpLgoKYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgojIEkgd3JpdGUgYSBzY3JpcHQganVzdCB0byBsb29rIGF0IHRoZSBkYXRhIHRoYXQgZmFpbGVkIHRoZSBTaGFwaXJvLVdpbGsgdGVzdCwKIyBhbmQgcGxvdCB0aGUgUS1RIHBsb3Qgc28gSSBjYW4gbG9vayBhdCB0aGVtLiBHZW5lcmFseSwgdGhleSBsb29rIG9rLCBleGNlcHQKIyBmb3IgYSBmZXcgd2hpY2ggZWl0aGVyIGhhdmUgYSBzaW5nbGUgb3V0bGllciwgb3Igc29tZSBsb25nIHRhaWxzLiBFaXRoZXIgd2F5LCBJCiMgY2hvb3NlIHRvIHN0aWNrIHdpdGggdGhlIGxvZyB0cmFuc2Zvcm1hdGlvbgoKc2lnLmRhdDwtYWxsLm5vcm0udGVzdCh1bmlxdWUuc2l0ZXMubGFydixsYXJ2YWUuZGF0LnZhbC50cmFucyxsYXJ2YWUuZGF0LmVudikkU2lnbmlmaWNhbmNlCnJvd25hbWVzKHNpZy5kYXQpPC1zaWcuZGF0JFNpdGUKc2lnLmRhdDwtc2lnLmRhdFssLTFdCmZhbHNlLnZlYyA8LSBhcnJheUluZCh3aGljaChzaWcuZGF0PT1GQUxTRSksIGRpbShzaWcuZGF0KSkKCnBhcihtZnJvdz1jKGNlaWxpbmcoc3FydChucm93KGZhbHNlLnZlYykpKSxmbG9vcihzcXJ0KG5yb3coZmFsc2UudmVjKSkpKSkKZm9yIChpIGluIDE6bnJvdyhmYWxzZS52ZWMpKXsKICBxcW5vcm0obGFydmFlLmRhdC52YWwudHJhbnNbd2hpY2gobGFydmFlLmRhdC5lbnYkU2l0ZT09ZGltbmFtZXMoc2lnLmRhdClbWzFdXVtmYWxzZS52ZWNbaSwxXV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyhzaWcuZGF0KVtbMl1dW2ZhbHNlLnZlY1tpLDJdXV0sCiAgICAgICAgIG1haW49cGFzdGUoZGltbmFtZXMoc2lnLmRhdClbWzFdXVtmYWxzZS52ZWNbaSwxXV0sZGltbmFtZXMoc2lnLmRhdClbWzJdXVtmYWxzZS52ZWNbaSwyXV0pKQp9CgpwYXIobWZyb3c9YygxLDEpKQpgYGAKClNvIG5vdyB0aGF0IEkgaGF2ZSB0aGUgdHJhaW5pbmcgZGF0YSBzZXQsIEkgc3Vic2V0IG91dCB0aGUgdGVzdCBkYXRhIC0gY2hlbWlzdHJ5IGRhdGEgZnJvbSB0aGUgbGFydmFsIHNoZWxscyBvZiB3aWxkIG11c3NlbCBzZXR0bGVycy4gSSBuZWVkIHRvIGNvcnJlY3QgYSBmZXcgb2YgdGhlIHNpdGUgbGFiZWxzIHNpbmNlIHRoZSAiVEZQIiBzaXRlIGhhcyBtdWx0aXBsZSBuYW1lcyBpbiB0aGUgZGF0YSBzZXQsIGJ1dCBpcyByZWFsbHkgb25seSBvbmUgc2l0ZS4gSSBsb2cgdHJhbnNmb3JtIHRoaXMgZGF0YSB0b28sIHdoaWNoIHNlZW1zIHRvIGRvIGEgZGVjZW50IGpvYiBvZiBub3JtYWxpemluZyB0aGUgZGF0YSBhZ2Fpbi4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIE5vdyBJIGRvIHRoZSBzYW1lLCBzdWJzZXR0aW5nIGFuZCB0cmFuc2Zvcm1hdGlvbiwgYnV0IHdpdGggZGF0YSBmcm9tIHNldHRsZWQgbXVzc2VscwojIHdoaWNoIHdlIHdvdWxkIGxpa2UgdG8gYXNzaWduIHRvIGEgYmF5LCBiYXNlZCBvbiB0aGUgbGFydmFsIGdlb2NoZW1pY2FsIGRhdGEgSQojIGltcG9ydGVkIGJlZm9yZS4gSSBuZWVkIHRvIGNvcnJlY3Qgb25lIHNldCBvZiBuYW1lcyB0aG91Z2ggKDIgVEZQIHNpdGVzIGludG8gMSkKc2V0dGxlci5kYXQ8LXN1YnNldChJQ1BNUy5hbGwuMjAxNS5kYXQsVHlwZT09IlNldHRsZXIiKQpzZXR0bGVyLmRhdCRTaXRlW3doaWNoKHNldHRsZXIuZGF0JFNpdGU9PSJURlBfMSIpXTwtIlRGUCIKc2V0dGxlci5kYXQkU2l0ZVt3aGljaChzZXR0bGVyLmRhdCRTaXRlPT0iVEZQXzIiKV08LSJURlAiCnNldHRsZXIuZGF0JFNpdGU8LWZhY3RvcihzZXR0bGVyLmRhdCRTaXRlKQpzZXR0bGVyLmRhdC5lbnY8LXNldHRsZXIuZGF0WyxjKDE6NCldCnNldHRsZXIuZGF0LnZhbDwtc2V0dGxlci5kYXRbLC1jKDE6NCldCnNldHRsZXIuZGF0LnZhbC50cmFuczwtbG9nKHNldHRsZXIuZGF0LnZhbCwxMCkKCnVuaXF1ZS5zaXRlcy5zZXR0bGVyPC11bmlxdWUoc2V0dGxlci5kYXQuZW52JFNpdGUpCmFsbC5ub3JtLnRlc3QodW5pcXVlLnNpdGVzLnNldHRsZXIsc2V0dGxlci5kYXQudmFsLnRyYW5zLHNldHRsZXIuZGF0LmVudikKYGBgCgpGb3IgdGhvc2UgZGF0YSB0aGF0IGRvbid0IHNlZW0gbm9ybWFsLCBJIHRha2UgYSBsb29rIGF0IHRoZSBRLVEgcGxvdHMgYWdhaW4sIHdoaWNoIEknbSBPSyB3aXRoIGZvciB0aGUgc2FtZSByZWFzb25zIEkgc3RhdGVkIGJlZm9yZS4KCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0zLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKc2lnLmRhdDwtYWxsLm5vcm0udGVzdCh1bmlxdWUuc2l0ZXMuc2V0dGxlcixzZXR0bGVyLmRhdC52YWwudHJhbnMsc2V0dGxlci5kYXQuZW52KSRTaWduaWZpY2FuY2UKcm93bmFtZXMoc2lnLmRhdCk8LXNpZy5kYXQkU2l0ZQpzaWcuZGF0PC1zaWcuZGF0WywtMV0KZmFsc2UudmVjIDwtIGFycmF5SW5kKHdoaWNoKHNpZy5kYXQ9PUZBTFNFKSwgZGltKHNpZy5kYXQpKQoKcGFyKG1mcm93PWMoY2VpbGluZyhzcXJ0KG5yb3coZmFsc2UudmVjKSkpLGZsb29yKHNxcnQobnJvdyhmYWxzZS52ZWMpKSkrMSkpCmZvciAoaSBpbiAxOm5yb3coZmFsc2UudmVjKSl7CiAgcXFub3JtKHNldHRsZXIuZGF0LnZhbC50cmFuc1t3aGljaChzZXR0bGVyLmRhdC5lbnYkU2l0ZT09ZGltbmFtZXMoc2lnLmRhdClbWzFdXVtmYWxzZS52ZWNbaSwxXV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMoc2lnLmRhdClbWzJdXVtmYWxzZS52ZWNbaSwyXV1dLAogICAgICAgICBtYWluPXBhc3RlKGRpbW5hbWVzKHNpZy5kYXQpW1sxXV1bZmFsc2UudmVjW2ksMV1dLGRpbW5hbWVzKHNpZy5kYXQpW1syXV1bZmFsc2UudmVjW2ksMl1dKSkKfQoKcGFyKG1mcm93PWMoMSwxKSkKYGBgCgojIyMjIFRoZSBtb2RlbAoKSSBjaG9vc2UgdG8gdXNlIGEgUXVhZHJhdGljIERpc2NyaW1pbmFudCBGdW5jdGlvbiBBbmFseXNpcyAoUURBKSBmb3IgYSB2YXJpZXR5IG9mIHJlYXNvbnMuIEZvciBvbmUsIGl0J3MgdGhlIG1vc3QgY29tbW9uIG1ldGhvZCBmb3IgY2xhc3NpZnlpbmcgY2hlbWljYWwgc2lnbmF0dXJlcyBpbiBlY29sb2d5LiBUaGF0J3Mgbm90IGFsd2F5cyBhIGdvb2QgcmVhc29uIHRvIHVzZSBhbiBhbmFseXNpcyB0aG91Z2gsIGFuZCBzbyBRREEgaXMgYWxzbyByZWxhdGl2ZWx5IGludGVycHJldGFibGUgYW5kIGVhc3kgdG8gdW5kZXJzdGFuZC4gVGhhdCwgYW5kIGJlY2F1c2UgaXQncyBiZWVuIGFyb3VuZCBmb3IgYSB3aGlsZSwgYXJlIHByb2JhYmx5IHdoeSBpdCBpcyBzbyBjb21tb25seSB1c2VkIGluIGVjb2xvZ3kuIEEgUURBIHByZWRpY3RzIGdyb3VwIG1lbWJlcnNoaXAgYmFzZWQgb24gcHJlLXRyYWluZWQgZ3JvdXBpbmdzIGJ5IGNhbGN1bGF0aW5nIGRpc2NyaW1pbmFudCBheGVzIHdpdGggcXVhZHJhdGljIGNvbWJpbmF0aW9ucyBvZiBjaGVtaWNhbCBlbGVtZW50cy4gVGhlIGFuYWx5c2lzIGF0dGVtcHRzIHRvIG1pbmltaXplIHRoZSB2YXJpYXRpb24gd2l0aGluIGdyb3VwcyBhbmQgbWF4aW1pemUgdGhlIHZhcmlhdGlvbiBiZXR3ZWVuIGdyb3Vwcy4gVG8gYmUgY2FuZGlkLCB3ZSBhcmUgd29ya2luZyBvbiBkZXZlbG9waW5nIG90aGVyIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB3aXRoIG91ciByZXNlYXJjaCAoZS5nLiwgUmFuZG9tIEZvcnJlc3QsIEluZmluaXRlIE1peHR1cmUgTW9kZWxzKSwgYnV0IGZvciB0b2RheSdzIHByZWxpbWluYXJ5IGFuYWx5c2lzLCBJIHRob3VnaHQgaXQgYmVzdCB0byB1c2Ugc29tZXRoaW5nIHByZXR0eSBjb21tb24gaW4gdGhlIGZpZWxkIGZvciBkb2luZyB3aGF0IHdlJ2QgbGlrZSB0by4KCiMjIyMgVHJhaW5pbmcgdGhlIG1vZGVsCgpTbyBmaXJzdCwgd2UgdXNlIHRyYWluIHRoZSBRREEgd2l0aCB0aGUgY2hlbWlzdHJ5IGRhdGEgZnJvbSBsYXJ2YWUgd2UgcmFpc2VkIC0gc2luY2Ugd2Uga25vdyB3aGljaCBiYXlzIHRob3NlIGNoZW1pc3RyeSBzaWduYWxzIGNvcnJlc3BvbmQgdG8uIFdlIGFsc28gdXNlIGxlYXZlLW9uZS1vdXQgQ3Jvc3MtVmFsaWRhdGlvbiB0byB0ZXN0IGhvdyBhY2N1cmF0ZSB3ZWxsIHRoZSBtb2RlbCBpcyBhdCBwcmVkaWN0aW5nIGEgc2l0ZSBiYXNlZCBvbiBjaGVtaXN0cnkgZGF0YS4gTG9va2luZyBhdCB0aGUgY29uZnVzaW9uIG1hdHJpeCwgdGhlIG1vZGVsIGFjdHVhbGx5IGRvZXNuJ3QgZG8gc28gd2VsbC4gSXQgb25seSBjbGFzc2lmaWVzIH4zMCUgb2YgZGF0YSBjb3JyZWN0bHkuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTbyBub3cgSSBzZXR1cCBhIFF1YWRyYXRpYyBEaXNjcmltaW5hbnQgRnVuY3Rpb24gYW5hbHlzaXMuCiMgSSBjaG9zZSBxdWFkcmF0aWMgYmVjYXN1ZSB0aGUgY292YXJpYW5jZSBtYXRyaWNlcyBhcmUgbW9yZSB0aGFuIGxpa2VseQojIHVuZXF1YWwgYW1vbmcgZ3JvdXBzIChzaXRlcyksIGJhc2VkIG9uIHByZXZpb3VzIHJlc2VhcmNoLgoKZ2VvY2hlbS5xZGEgPC0gcWRhKGxhcnZhZS5kYXQudmFsLnRyYW5zLCBsYXJ2YWUuZGF0LmVudiRTaXRlKQoKIyBJJ2xsIGV2YWx1YXRlIHRoZSBtb2RlbCB1c2luZyBDcm9zcyBWYWxpZGF0aW9uCmdlb2NoZW0ucWRhLkNWIDwtIHFkYShsYXJ2YWUuZGF0LnZhbC50cmFucywgbGFydmFlLmRhdC5lbnYkU2l0ZSxDVj1UUlVFKQoKIyBhbmQgbm93IGxvb2sgYXQgdGhlIGNvbmZ1c2lvbiBtYXRyaXgsIHRoZSBvdmVyYWxsIGNvcnJlY3QgYXNzaWdubWVudHMgKHRvdGFsKSwgYW5kIHRoZQojIGdyb3VwIChzaXRlKSBzcGVjaWZpYyBjb3JyZWN0IGFzc2lnbm1lbnRzCmNvbmZ1c2lvbi50YWJsZTwtdGFibGUoZGF0YS5mcmFtZShQcmVkaWN0ZWQ9Z2VvY2hlbS5xZGEuQ1YkY2xhc3MsQWN0dWFsPWxhcnZhZS5kYXQuZW52JFNpdGUpKQpjb25mdXNpb24udGFibGUudG90YWw8LXN1bShjb25mdXNpb24udGFibGVbcm93KGNvbmZ1c2lvbi50YWJsZSk9PWNvbChjb25mdXNpb24udGFibGUpXSkvc3VtKGNvbmZ1c2lvbi50YWJsZSkKY29uZnVzaW9uLnRhYmxlLnRvdGFsCmBgYAoKV2hlbiB3ZSBsb29rIGF0IHRoZSBicmVha2Rvd24gYnkgc2l0ZSwgdGhlcmUncyBhIHByZXR0eSBicm9hZCByYW5nZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjb25mdXNpb24udGFibGUuZGlhZzwtZGlhZyhjb25mdXNpb24udGFibGUvcm93U3Vtcyhjb25mdXNpb24udGFibGUpKQpjb25mdXNpb24udGFibGUuZGlhZwpgYGAKClRoaXMgYmVncyB0aGUgcXVlc3Rpb246ICJ3aHkgYXJlIG91ciBjbGFzc2lmaWNhdGlvbnMgc28gYmFkPyIuIElmIHdlIGxvb2sgYXQgaG93IHRoZSBwZXJjZW50IGNvcnJlY3RseSBjbGFzc2lmaWVkIHRvIGEgc2l0ZSByZWxhdGVzIHRvIGhvdyBtYW55IGluZGl2aWR1YWwgbGFydmFlIHdlcmUgdXNlZCB0byB0cmFpbiB0aGF0IHNpdGUncyBzaWduYWwgaW4gdGhlIFFEQSBtb2RlbCwgdGhlcmUgaXMgYSBwcmV0dHkgZGlyZWN0IHJlbGF0aW9uc2hpcCAoaS5lLiwgbW9yZSBpbmRpdmlkdWFscyB1c2VkIHRvIHRyYWluIGEgc2l0ZSA9IGJldHRlciBjbGFzc2lmaWNhdGlvbikuIFRoaXMgaXMgZGVwaWN0ZWQgYmVsb3cgd2l0aCB0aGUgc3RhdGlzdGljYWwgb3V0cHV0IGZyb20gYSBsaW5lYXIgbW9kZWwgKGFuZCBhIFEtUSBwbG90IHRvIGNoZWNrIHRoZSBmaXQpLCBhbmQgYSBncmFwaGljYWwgdGhlIG91dHB1dCBmcm9tIHRoZSBsaW5lYXIgbW9kZWwgc2hvd2luZyB0aGUgZml0IChibGFjayBzb2xpZCBsaW5lKSBhbmQgOTUlIENJcyAocmVkIGRvdHRlZCBsaW5lcykuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIFRoZSBjb3JyZWN0IGFzc2lnbm1lbnRzIGJ5IHNpdGUgc2VlbSB0byBiZSBwcmV0dHkgd2VsbCByZWxhdGVkIHRvIHRoZSB0cmFpbmluZyBzYW1wbGUgc2l6ZQojIGV4cGxhaW5pbmcgYWJvdXQgNjAlIG9mIHRoZSBvYnNlcnZlZCB2YXJpYWl0b24KdHJhaW4uc2l6ZXM8LXN1bW1hcnkoZ2VvY2hlbS5xZGEuQ1YkY2xhc3MpCnNpemUuY29uZnVzaW9uLmxtPC1sbShjb25mdXNpb24udGFibGUuZGlhZ350cmFpbi5zaXplcykKc3VtbWFyeShzaXplLmNvbmZ1c2lvbi5sbSkKcGxvdChzaXplLmNvbmZ1c2lvbi5sbSx3aGljaD0yKQpzaXplLmNvbmZ1c2lvbi5wcmVkaWN0ZWQ8LXByZWRpY3Qoc2l6ZS5jb25mdXNpb24ubG0sZGF0YS5mcmFtZSh0cmFpbi5zaXplcz1jKDA6MTAwKSksaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIpCnBsb3QoY29uZnVzaW9uLnRhYmxlLmRpYWd+dHJhaW4uc2l6ZXMseWxhYj0iUGVyY2VudCBDb3JyZWN0bHkgQXNzaWduZWQiLHhsYWI9IlRyYWluaW5nIFNldCBTYW1wbGUgU2l6ZSIpCmxpbmVzKGMoMDoxMDApLCBzaXplLmNvbmZ1c2lvbi5wcmVkaWN0ZWRbICwxXSwgbHR5ID0gInNvbGlkIiwgY29sID0gImJsYWNrIikKbGluZXMoYygwOjEwMCksIHNpemUuY29uZnVzaW9uLnByZWRpY3RlZFsgLDJdLCBsdHkgPSAiZGFzaGVkIiwgY29sID0gInJlZCIpCmxpbmVzKGMoMDoxMDApLCBzaXplLmNvbmZ1c2lvbi5wcmVkaWN0ZWRbICwzXSwgbHR5ID0gImRhc2hlZCIsIGNvbCA9ICJyZWQiKQpgYGAKClVuZm9ydHVuYXRlbHksIHRoZXJlIGlzbid0IG11Y2ggd2UgY2FuIGRvIHdpdGggdGhpcyBwcmVsaW1pbmFyeSBhbmFseXNpcy4gQXMgb3VyIGxhYiBwcm9jZXNzZXMgbW9yZSBzYW1wbGVzLCBhbmQgYnVpbGRzIGEgbGFyZ2VyIHRyYWluaW5nIGRhdGEgc2V0LCB0aGUgYWNjdXJhY3kgb2Ygb3VyIG1vZGVsIHdpbGwgaG9wZWZ1bGx5IGltcHJvdmUuIEluIHRoZSBtZWFudGltZSwgaWYgd2Ugd2FudCB0byB1bmRlcnN0YW5kIGxhcnZhbCBjb25uZWN0aXZpdHkgaW4gbXVzc2VscywgdGhpcyBpcyBhbGwgd2UgaGF2ZSB0byB3b3JrIHdpdGgsIHNvIGxldCdzIGNvbnRpbnVlLgoKCiMjIyMgUHJlZGljdGluZyBUaGUgQmlydGhwbGFjZSBvZiBTZXR0bGVkIE11c3NlbHMKCkkgdGFrZSB0aGUgZGF0YSBzZXQgb2Ygd2lsZCBtdXNzZWwgc2V0dGxlciBzaGVsbCBjaGVtaXN0cnkgKHdoaWNoIHRoZSBtdXNzZWxzIGxhaWQgZG93biB3aGVuIHRoZXkgd2VyZSBib3JuKSwgYW5kIHVzZSB0aGUgUURBIHRvIHByZWRpY3QgdGhlIHNpdGUgd2hlcmUgdGhhdCBjaGVtaXN0cnkgY2FtZSBmcm9tLiBJIGltcGxlbWVudCBhIGZsYXQgcHJpb3IsIGJhc2VkIG9uIHRoZSB0b3RhbCBudW1iZXIgb2Ygc2l0ZXMsIHNpbmNlIHdlIGhhdmUgbm8gdW5kZXJseWluZyBhc3N1bXB0aW9ucyBhcyB0byB3aGljaCBzb3VyY2Ugc2l0ZSBwcm9kdWNlZCB0aGUgbW9zdCBzZXR0bGVycy4gQmVsb3cgaXMgYSBwbG90IG9mIHRoZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIHNpdGUgYXNzaWdubWVudHMsIHdoZXJlIHRoZSB4IGF4aXMgYXJlIHRoZSBzaXRlcyBhIHNldHRsZXIgd2FzIGFzc2lnbmVkIHRvIGJhc2VkIG9uIGl0cyBzaGVsbCBjaGVtaXN0cnksIHRoZSB5IGF4aXMgaXMgdGhlIGZyZXF1ZW5jeSBpdCB3YXMgYXNzaWduZWQgdG8gdGhhdCBzaXRlLCBhbmQgdGhlIHBhbmVscyBzZXBhcmF0ZSBvdXQgVGhlIHNpdGUgdGhhdCBtdXNzZWwgd2FzIGZvdW5kL3NldHRsZWQgaW4gKGkuZS4sIHdoZXJlIGl0IHRyYXZlbGVkIHRvIGFuZCB3aWxsIGdyb3cgdG8gYmUgYW4gYWR1bHQpLgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBOb3cgSSdsbCB0cnkgdG8gYXNzaWduIHNldHRsZWQgbXVzc2VscyB0byBzb3VyY2VzIGJhc2VkIG9uIHRoZSB0cmFpbmVkIFFERkEgb2YgZ2VvY2hlbWljYWwKIyBzaWduaXR1cmVzLCBhbmQgdXNlIGEgZmxhdCBwcmlvcgoKbi5zb3VyY2VzPC1sZW5ndGgodW5pcXVlLnNpdGVzLmxhcnYpCnNldHRsZXIucHJlZGljdGlvbjwtcHJlZGljdChnZW9jaGVtLnFkYSxzZXR0bGVyLmRhdC52YWwudHJhbnMscHJpb3I9KHJlcCgxLG4uc291cmNlcykvbi5zb3VyY2VzKSkKCiMgTm93IGxldCBtZSBwbG90IHRoZSBwb3N0ZXJpb3IgYXNzaWdubWVudHMgdG8gc2VlIGhvdyB0aGV5J3JlIGxvb2tpbmcuIFRoZQojIG1vZGVsIHNlZW1zIHRvIGluZGljYXRlIHRoYXQgdGhlIHNldHRsZXJzIGFyZSBhbGwgY29taW5nIGZyb20gb25lIHNvdXJjZQpzZXR0bGVyLnByZWRpY3Rpb24ubWVsdDwtYXMuZGF0YS5mcmFtZShzZXR0bGVyLnByZWRpY3Rpb24kcG9zdGVyaW9yKQpzZXR0bGVyLnByZWRpY3Rpb24ubWVsdCRTZXR0bGVkPC1zZXR0bGVyLmRhdC5lbnYkU2l0ZQpzZXR0bGVyLnByZWRpY3Rpb24ubWVsdCRJbmRpdmlkdWFsPC1mYWN0b3IoYygxOm5yb3coc2V0dGxlci5wcmVkaWN0aW9uLm1lbHQpKSkKc2V0dGxlci5wcmVkaWN0aW9uLm1lbHQ8LW1lbHQoc2V0dGxlci5wcmVkaWN0aW9uLm1lbHQpCgpjb2xuYW1lcyhzZXR0bGVyLnByZWRpY3Rpb24ubWVsdCk8LWMoIkxvY2F0aW9uX1NldHRsZWQiLCJJbmRpdmlkdWFsIiwiU291cmNlIiwiUG9zdGVyaW9yX0Fzc2lnbm1lbnQiKQoKc2V0dGxlci5wb3N0ZXJpb3IuZ3JhcGg8LWdncGxvdChzZXR0bGVyLnByZWRpY3Rpb24ubWVsdCxhZXMoeD1Tb3VyY2UseT1Qb3N0ZXJpb3JfQXNzaWdubWVudCxncm91cD1JbmRpdmlkdWFsKSkrCiAgZ2VvbV9saW5lKCkrCiAgZmFjZXRfd3JhcCh+TG9jYXRpb25fU2V0dGxlZCxuY29sPTEpKwogIHRoZW1lX2J3KCkKCnNldHRsZXIucG9zdGVyaW9yLmdyYXBoCmBgYAoKV2UgY2FuIGFscmVhZHkgc2VlIHRoYXQgbW9zdCB3aWxkIG11c3NlbCBzZXR0bGVycyB3ZXJlIHByZWRpY3RlZCB0byBoYXZlIGNvbWUgZnJvbSB0aGUgc2l0ZXMgQ0hSCm9yIFBIQiAoeCBheGlzKSwgbm8gbWF0dGVyIHdoZXJlIHRoZXkgZW5kZWQgdXAgc2V0dGxpbmcgKHBhbmVscykuIEJ1dCwgZm9yIHNvbWUgc2V0dGxlcnMsIGV2ZW4gdGhlIHNpdGUgd2hlcmUgaXQgd2FzIHByZWRpY3RlZCBtb3N0IGxpa2VseSB0byBoYXZlIGNvbWUgZnJvbSAoaS5lLiwgd2FzIG1vc3QgZnJlcXVlbnRseSBhc3NpZ25lZCAtIHRoZSB5IGF4aXMpLCB0aGUgYXNzaWdubWVudCBsZXZlbCB3YXMgcHJldHR5IGxvdyAoPDUwJSBwcm9iYWJpbGl0eSBvZiBhc3NpZ25tZW50KS4KClRvIGFjY291bnQgZm9yIHRoaXMsIHdlIHJ1biBzb21lIFF1YWxpdHkgQ29udHJvbC4gRm9yIGFuIGluZGl2aWR1YWwgc2V0dGxlciwgbm90IG9ubHkgZG8gd2Ugb25seSBrZWVwIHRoZSBiZXN0IHByZWRpY3RlZCBzb3VyY2UgYmFzZWQgb24gdGhlIG1vZGVsLCBidXQgd2UgYWxzbyBvbmx5IGtlZXAgdGhhdCBzb3VyY2UgYXNzaWdubWVudCBpZiBpdCdzIGNvbmZpZGVuY2UgaW4gPjUwJS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIEkgc3VtbWFyaXplIHRoZSBwb3N0ZXJpb3JzIGZvciBlYWNoIGdyb3VwIG9mIHNldHRsZXJzLCBvbmx5IGtlZXBpbmcgdGhlIHByZWRpY3RlZCBzb3VyY2UKIyB3aXRoIHRoZSBncmVhdGVzdCBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgb2YgYXNzaWdubWVudC4gVGhlbiwgSSByZW1vdmUgYW55IGluZGl2aWR1YWxzCiMgd2hlcmUgdGhlIG1heGltdW0gcG9zdGVyaW9yIHByb2JhYmlsaXR5IHdhcyBzdGlsbCA8NTAlLiBUaGVuIEkgcmVzdW1tYXJpemUgdGhlIGRhdGEKIyBieSBncm91cCAoc2V0dGxlbWVudCBzaXRlKQpzZXR0bGVyLnByZWRpY3Rpb24uc3VtbWFyeTwtZGRwbHkoc2V0dGxlci5wcmVkaWN0aW9uLm1lbHQsLihJbmRpdmlkdWFsLExvY2F0aW9uX1NldHRsZWQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplLE1heC5Tb3VyY2U9U291cmNlW3doaWNoLm1heChQb3N0ZXJpb3JfQXNzaWdubWVudCldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTWF4LlZhbD1Qb3N0ZXJpb3JfQXNzaWdubWVudFt3aGljaC5tYXgoUG9zdGVyaW9yX0Fzc2lnbm1lbnQpXSkKc2V0dGxlci5wcmVkaWN0aW9uLnN1bW1hcnkudGFibGU8LXRhYmxlKHN1YnNldChzZXR0bGVyLnByZWRpY3Rpb24uc3VtbWFyeSxNYXguVmFsPjAuNSlbLGMoMiwzKV0pLwogIHJvd1N1bXModGFibGUoc3Vic2V0KHNldHRsZXIucHJlZGljdGlvbi5zdW1tYXJ5LE1heC5WYWw+MC41KVssYygyLDMpXSkpCgojIE5vdyBJIGNvbGxhcHNlIHRoZSBkYXRhLCBhbmQgYXR0YWNoIHNpdGUgY29vcmRpbmF0ZXMgdG8gZWFjaCBzZXR0bGVtZW50IHBvaW50IGFuZAojIHNvdXJjZSAoSSB1c2UgdGhlIG1hdGNoIGZ1bmN0aW9uIGFuZCBhbm90aGVyIGltcG9ydGVkIGRhdGFzZXQpCgpjb25uZWN0LnRhYmxlPC1tZWx0KHNldHRsZXIucHJlZGljdGlvbi5zdW1tYXJ5LnRhYmxlKQpjb2xuYW1lcyhjb25uZWN0LnRhYmxlKTwtYygiU2V0dGxlZCIsInByZWRpY3RlZF9zb3VyY2UiLCJDb25uZWN0aXZpdHkiKQoKc2l0ZS5sb2NzPC1yZWFkLmNzdigiL1VzZXJzL3Njb3R0bW9yZWxsby9Ecm9wYm94L0FyY2hpdmVzL0pvYnMvRGF0YSBTY2llbmNlIFNlYXJjaC9JbnNpZ2h0L0luc2lnaHRfSW50ZXJ2aWV3X0NvZGUvTXVMVEktMl9TdGF0aW9uIEluZm9ybWF0aW9uLmNzdiIpCmNvbm5lY3QudGFibGUkdG8ubG9uPC1zaXRlLmxvY3MkTG9uZ2l0dWRlW21hdGNoKGFzLmNoYXJhY3Rlcihjb25uZWN0LnRhYmxlJFNldHRsZWQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoc2l0ZS5sb2NzJFN0YXRpb24uQ29kZSkpXQpjb25uZWN0LnRhYmxlJHRvLmxhdDwtc2l0ZS5sb2NzJExhdGl0dWRlW21hdGNoKGFzLmNoYXJhY3Rlcihjb25uZWN0LnRhYmxlJFNldHRsZWQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihzaXRlLmxvY3MkU3RhdGlvbi5Db2RlKSldCgpjb25uZWN0LnRhYmxlJGZyb20ubG9uPC1zaXRlLmxvY3MkTG9uZ2l0dWRlW21hdGNoKGFzLmNoYXJhY3Rlcihjb25uZWN0LnRhYmxlJHByZWRpY3RlZF9zb3VyY2UpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihzaXRlLmxvY3MkU3RhdGlvbi5Db2RlKSldCmNvbm5lY3QudGFibGUkZnJvbS5sYXQ8LXNpdGUubG9jcyRMYXRpdHVkZVttYXRjaChhcy5jaGFyYWN0ZXIoY29ubmVjdC50YWJsZSRwcmVkaWN0ZWRfc291cmNlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihzaXRlLmxvY3MkU3RhdGlvbi5Db2RlKSldCmBgYAoKTm93IEkgcmUgcGxvdCB0aGUgZGF0YSBhcyBhIGJhciBncmFwaCwgc28gdGhhdCBpdCBzaG93cyBmb3IgZWFjaCBzZXR0bGVtZW50IHNpdGUgYWxvbmcgdGhlIGNvYXN0IChwYW5lbHMpLCB3aGljaCBiaXJ0aC1zaXRlICh4IGF4aXMpIHNldHRsZXJzIHdlcmUgbW9zdCBmcmVxdWVudGx5IGNhbWUgZnJvbSAoIyBvZiBzZXR0bGVycyBwcmVkaWN0ZWQgdG8gY29tZSBmcm9tIGEgc291cmNlL3RvdGFsICMgb2Ygc2V0dGxlcnMgYXQgYSBzaXRlIC0geSBheGlzKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIE5vdyBJIHBsb3QgdGhlIHBlcmNlbnQgb2Ygc2V0dGxlcnMgY29taW5nIGZyb20gZWFjaCBsYXJ2YWwgc291cmNlIGJ5IHNldHRsZW1lbnQgc2l0ZQojIFdlIGNhbiBzZWUgdGhhdCBzaXRlcyBoYXZlIGRpc3Byb3BvcnRpb25hdGVseSBoaWdoIG51bWJlcnMgb2YgbGFydmFlIGNvbWluZwojIGZyb20gQ0hSIGFuZCBQSEIKCgpzb3VyY2UuZ3JhcGg8LWdncGxvdChjb25uZWN0LnRhYmxlLGFlcyh4PXByZWRpY3RlZF9zb3VyY2UseT1Db25uZWN0aXZpdHkpKSsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKwogIGZhY2V0X3dyYXAoflNldHRsZWQsbmNvbD0xKSsKICB4bGFiKCJTb3VyY2UiKSsKICB0aGVtZV9idygpCgpzb3VyY2UuZ3JhcGgKYGBgCgojIyMjIEZpbmFsIEFuc3dlcgoKVGhpcyBzb3J0IG9mIGdyYXBoaWNhbCBkaXNwbGF5IGlzIHByZXR0eSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0IGFuZCB2aXN1YWxpemUgd2l0aG91dCBzb21lIGhlbHAuLi4gc28sICJnZ21hcCIgcGFja2FnZSB0byB0aGUgcmVzY3VlISBIZXJlIGlzIGEgbWFwIHNob3dpbmcgaG93IGVhY2ggc2l0ZSBpbiBub3J0aGVybiBNYWluZSBpcyBjb25uZWN0ZWQgdG8gZWFjaCBvdGhlciBieSBsYXJ2YWwgZGlzcGVyc2FsLiBFYWNoIHNldHRsZW1lbnQgc2l0ZSAod2hpdGUgcG9pbnQpIGlzIGNvbm5lY3RlZCB0byBlYWNoIGJpcnRoIHBsYWNlIGJ5IGFuIGFycm93IChmcm9tIGJpcnRoIHBsYWNlIHRvIHNldHRsZW1lbnQgc2l0ZSkuIEhvdyBkZW5zZSB0aGF0IGFycm93IGlzIGNvcnJlc3BvbmRzIHRvIHRoZSBwZXJjZW50IG9mIHNldHRsZXJzIHRoYXQgY2FtZSBmcm9tIHRoYXQgYmlydGggcGxhY2UgKHRoZSBkYXJrZXIgdGhlIGFycm93LCB0aGUgbW9yZSBzZXR0bGVycyB3ZXJlIGJvcm4gdGhlcmUpLiBUbyByZXByZXNlbnQgc2V0dGxlcnMgdGhhdCB3ZXJlIGJvcm4gYXQgYSBzaXRlIGFuZCByZXR1cm5lZCB0byB0aGVpciBiaXJ0aHBsYWNlIHRvIHNldHRsZSwgd2UgdXNlIGEgYmxhY2sgY2lyY2xlIGluIHRoZSBtaWRkbGUgb2YgdGhlIHNpdGUgKHRoZSBsYXJnZXIgdGhlIGJsYWNrIGNpcmNsZSwgdGhlIG1vcmUgc2V0dGxlcnMgcmV0dXJuZWQgdG8gdGhlaXIgYmlydGggcGxhY2UpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgTm93LCBJIGNhbiBwdXQgYWxsIG9mIHRoaXMgaW5mbyBvbnRvIGEgbWFwIHRvIGJldHRlciB2aXN1YWxpemUgdGhlIGNvbm5lY3Rpb25zIGFtb25nCiMgc2l0ZXMgKGNvbm5lY3Rpdml0eSkgYW5kIHJldGVudGlvbiB3aXRoaW4gc2l0ZXMuCgpkb3duZWFzdC5tYXAgPC0gZ2V0X21hcChsb2NhdGlvbj0iTWlsYnJpZGdlLCBNYWluZSIsbWFwdHlwZT0idGVycmFpbiIsem9vbT05LGNyb3A9RkFMU0UpCgpjb25uZWN0Lm1hcCA8LSBnZ21hcChkb3duZWFzdC5tYXApICsgCiAgZ2VvbV90ZXh0KGRhdGE9c2l0ZS5sb2NzWyxjKDQsNSw2KV0sYWVzKHg9TG9uZ2l0dWRlLHk9TGF0aXR1ZGUsbGFiZWw9U3RhdGlvbi5Db2RlKSwKICAgICAgICAgICAgc2l6ZT00LGNvbG9yPSJibGFjayIsIHZqdXN0PTIuNzUsZm9udGZhY2U9ImJvbGQiKSsKICBnZW9tX3BvaW50KGRhdGE9c2l0ZS5sb2NzWyxjKDUsNildLGFlcyh4PUxvbmdpdHVkZSx5PUxhdGl0dWRlKSxzaXplPTEwLGNvbG9yPSJibGFjayIpKwogIGdlb21fcG9pbnQoZGF0YT1zaXRlLmxvY3NbLGMoNSw2KV0sYWVzKHg9TG9uZ2l0dWRlLHk9TGF0aXR1ZGUpLHNpemU9OSxjb2xvcj0id2hpdGUiKSsKICBnZW9tX3BvaW50KGRhdGE9Y29ubmVjdC50YWJsZVstd2hpY2goYXMuY2hhcmFjdGVyKGNvbm5lY3QudGFibGUkU2V0dGxlZCkhPWFzLmNoYXJhY3Rlcihjb25uZWN0LnRhYmxlJHByZWRpY3RlZF9zb3VyY2UpKSxdLAogICAgICAgICAgICAgYWVzKHg9dG8ubG9uLHk9dG8ubGF0LHNpemU9Q29ubmVjdGl2aXR5KSxjb2xvcj0iYmxhY2siKSsKICBnZW9tX2N1cnZlKGRhdGE9Y29ubmVjdC50YWJsZVt3aGljaChhcy5jaGFyYWN0ZXIoY29ubmVjdC50YWJsZSRTZXR0bGVkKSE9YXMuY2hhcmFjdGVyKGNvbm5lY3QudGFibGUkcHJlZGljdGVkX3NvdXJjZSkpLF0sIAogICAgICAgICAgICAgYWVzKHg9dG8ubG9uLCB5PXRvLmxhdCwgeGVuZD1mcm9tLmxvbiwgeWVuZD1mcm9tLmxhdCxjb2xvdXI9Q29ubmVjdGl2aXR5LGFscGhhPUNvbm5lY3Rpdml0eSxmcmFtZT1wcmVkaWN0ZWRfc291cmNlKSwKICAgICAgICAgICAgIGFycm93PWFycm93KGxlbmd0aD11bml0KDAuNCwiY20iKSwgZW5kcz0iZmlyc3QiLGFuZ2xlPTE1KSwKICAgICAgICAgICAgIGNvbG9yPSJibGFjayIsY3VydmF0dXJlID0gMC4xNSxwb3NpdGlvbiA9ICJqaXR0ZXIiLGluaGVyaXQuYWVzPVRSVUUsc2l6ZT0xLjUpICsKICBjb29yZF9jYXJ0ZXNpYW4oKSsKICBsYWJzKHg9IkxvbmdpdHVkZSIseT0iTGF0aXR1ZGUiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygtNjguNCwgLTY3LjMpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoNDQuMTUsIDQ0Ljg1KSwgZXhwYW5kID0gYygwLCAwKSkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKG5hbWU9IlNlbGYtU2VlZGluZyIpCgpjb25uZWN0Lm1hcApgYGAKClRoZSBtYXAgZG9lcyBhIGdvb2Qgam9iIG9mIHNob3dpbmcgaG93IHNldHRsZXJzIGluIHRoZSBtaWQtY29hc3QgYXJlYSAoZS5nLiwgR0IsIFBIQiwgSEJSLCBQTFIpIG1vc3RseSBjb21lIGZyb20gYSBzaXRlIHRvIHRoZSBub3J0aGVhc3QgKENIUiksIG9yIGZyb20gd2l0aGluIHRoZSBtaWQtY29hc3QgYXJlYSAoUEhCKS4gU2V0dGxlcnMgaW4gdGhlIG1vcmUgc291dGhlcm4gYXJlYSAoVEZQKSBnZW5lcmFsbHkgY29tZSBmcm9tIHRoZSBtaWQtY29hc3QgKFBIQiksIGFuZCBvY2Nhc2lvbmFsbHkgZnJvbSB0aGUgYXJlYSB0byB0aGUgZmFyIG5vcnRoZWFzdCAoQ0hSKS4gVGhpcyBwYXR0ZXJuIGlzIHByZXR0eSBjb25zaXN0ZW50IHdpdGggb3RoZXIgZGF0YSBhbmQgYW5hbHlzZXMgd2UndmUgY29uZHVjdGVkIChzZWUgaHR0cHM6Ly93d3cuaW50ZXJ0aWRhbGJ1ZmZldC5jb20vcG9wdWxhdGlvbi1jb25uZWN0aXZpdHkgZm9yIGRldGFpbHMgYW5kIG90aGVyIGFuYWx5c2lzKSwgYW5kIHRlbGxzIHVzIHRoYXQgZXZlbiB0aG91Z2ggb3VyIFFEQSBtb2RlbCBhY2N1cmFjeSBuZWVkcyB0byBiZSBpbXByb3ZlZCAod2l0aCBtb3JlIHRyYWluaW5nIGRhdGEpLCB0aGUgbW9kZWwgaXMgcHJvYmFibHkgcHJlZGljdGluZyB3aGVyZSBzZXR0bGVycyBjb21lIGZyb20gcHJldHR5IHdlbGwhCgpPSywgdGhhdCB3YXMgYSBsb3QsIGFuZCBtdWNoIG1vcmUgc2NpZW5jZS15IHRoYW4gdGhlIHJlc3Qgb2YgdGhlIHN0dWZmIEkgaW5jbHVkZSBpbiB0aGlzIHNlY3Rpb24gb2YgdGhlIHdlYnBhZ2UuIElmIHlvdSBoYXZlIGFueSBxdWVzdGlvbnMgb3IgY29tbWVudHMsIGZlZWwgZnJlZSB0byBlbWFpbCBtZSEKCkhvcGUgeW91IGVuam95ZWQgdGhlIGFuYWx5c2lzIQoK