Introduction
Customer Lifetime Value is the present value of the
future profits associated with a particular customer. In this section,
we’ll focus on contractual settings, where customers must notify the
firm when they quit. In other words, customer churn is
observed by the firm. The primitives of CLV are the
margin, the discount rate, and retention,
which often receives the most focus out of the three.
Basic Terms
Below we use some data from a subscription company. The time period
is years.
active_cust=c(12489,
7356,
5258,
4309,
3747,
3435,
3123,
2948,
2786,
2711,
2624)
data <- cbind(0:10,active_cust)
colnames(data)<-c("Period", "Active Customers")
data %>%
kbl() %>%
kable_styling()
Period
|
Active Customers
|
0
|
12489
|
1
|
7356
|
2
|
5258
|
3
|
4309
|
4
|
3747
|
5
|
3435
|
6
|
3123
|
7
|
2948
|
8
|
2786
|
9
|
2711
|
10
|
2624
|
At year \(t=0\) there is a
cohort of 12,489 customers who join in the same
year.
A year later 7,356 of them renewed, so \(S(1) = \frac{7356}{12489} = 0.589\).
Coding hint: diff
takes the difference between adjacent
terms in a vector.
lost <- -diff(active_cust)
S <- active_cust[1:11]/active_cust[1] ## Survivor Function
data <- cbind(data, S)
data %>%
kbl() %>%
kable_styling()
Period
|
Active Customers
|
S
|
0
|
12489
|
1.000
|
1
|
7356
|
0.589
|
2
|
5258
|
0.421
|
3
|
4309
|
0.345
|
4
|
3747
|
0.300
|
5
|
3435
|
0.275
|
6
|
3123
|
0.250
|
7
|
2948
|
0.236
|
8
|
2786
|
0.223
|
9
|
2711
|
0.217
|
10
|
2624
|
0.210
|
Survival Function Plot:
par(mai=c(.9,.8,.2,.2))
plot(0:10, S, type="b",ylab="Survival fucntion S(t)", xlab="Period",ylim=par("yaxp")[1:2])
The retention rate is the probability that a
customer who was active (i.e., still subscribed) in \(t-1\) will still be active at the end of
the next period \(t\). This is a
conditional probability, because we are conditioning on the
fact that the customer has survived \(t-1\) periods.
It turns out the retention rate is the ratio of survivor functions in
adjacent periods: \[
r(t) = P(T>t \mid T>t-1) = \frac{S(t)}{S(t-1)}
\] In our data at \(t=2\), the
retention rate is \(r(2) = \frac{5258}{7356} =
0.715\): of the 7356 customers who were active in \(t=1\), 5258 were active in \(t=2\).
r <- S[2:11]/S[1:10] # DEFINITION
data <- cbind(data, c(NA,r))
colnames(data)[[4]] <- "r"
data %>%
kbl() %>%
kable_styling()
Period
|
Active Customers
|
S
|
r
|
0
|
12489
|
1.000
|
NA
|
1
|
7356
|
0.589
|
0.589
|
2
|
5258
|
0.421
|
0.715
|
3
|
4309
|
0.345
|
0.820
|
4
|
3747
|
0.300
|
0.870
|
5
|
3435
|
0.275
|
0.917
|
6
|
3123
|
0.250
|
0.909
|
7
|
2948
|
0.236
|
0.944
|
8
|
2786
|
0.223
|
0.945
|
9
|
2711
|
0.217
|
0.973
|
10
|
2624
|
0.210
|
0.968
|
Retention Rate Plot:
par(mai=c(.9,.8,.2,.2))
plot(1:10, r, type="b",ylab="retention rate r(t)", xlab="Period",ylim=par("yaxp")[1:2])
Lastly the churn rate is the probability that
customer who was active in period \(t-1\) quits in period \(t\). Note this is related to the
hazard rate, which is how social scientists model
time until an event (here churn). It is the complement of the
retention rate. In other words: \[
c(t)=P(T=t \mid T>t-1) = 1-r(t)
\]
c <- 1-r
data <- cbind(data, c(NA,c))
colnames(data)[[5]] <- "c"
par(mai=c(.9,.8,.2,.2))
plot(1:10, c, type="b",ylab="churn rate c(t)", xlab="Period",ylim=par("yaxp")[1:2])
We wrote the retention rate as the fraction of adjacent Survival
functions. You can also write the Survival function as the product of
retention rates. \[
S(t) = \prod_{j=1}^{t} \; r(j)
\]
Geometric model
Assumes a constant retention rate.
Every period you flip a coin and with probability \(p\) you stay.
The probability of lasting more than \(t\) periods is then getting tails \(t\) times in a row. \[
S(t) = p^t
\]
There’s one parameter in the model, \(p\). Let’s assume, for example, that \(p = 0.8\).
p <- 0.8 # retention probability (rate)
t <- seq(0,10) # time period starting at 0
par(mfrow=c(1,1))
par(mai=c(.8,.8,.2,.2))
plot(t, p^t, type="b",ylab="Probability the customer has survived", xlab="Period", main="Survival function")
text(1.5, .78, " 0.8", cex=1, pos=3, col="black")
text(2.5, .62, parse(text= '.8^2'), cex=1, pos=3, col="black")
text(9.6, .16, parse(text='.8^10'), cex=1, pos=3, col="black")
CLV (Homogeneity Case)
CLV is the present value of the expected profits: the margin (assumed
constant), multiplied by the probability that the customer survives up
until this point.
In the geometric model, \(S(t)=p^t\). We can use the results of a
geometric series^[For a geometric series: \[
\begin{array}{ccl}
E[CLV] & = & \displaystyle \frac{m \; (1+d)}{1+d-p}
\end{array}
\] We write a formula to calculate it:
geoCLV <- function(p,m,d){
m*(1+d)/(1+d-p)
}
p<-0.8 # retention probability (rate)
m<-100 # margin (profit)
d<-0.1 # discount rate
## Expected Customer Lifetime Value of the new customer
geoCLV(p,m,d)
## [1] 367
We can compare this formula calculated over a infinite horizon to the
first 10 terms.
t<-seq(0,10) # time period starting at 0
m*(p/(1+d))^t # the first 10 terms of CLV
## [1] 100.00 72.73 52.89 38.47 27.98 20.35 14.80 10.76 7.83 5.69 4.14
sum(m*(p/(1+d))^t) # CLV using only the first 10 terms
## [1] 356
So the 10-period CLV is 356. It is relatively close
to the infinite-horizon CLV, which is 367. Each
additional term adds less because the probability of remaining
a customer diminishes over time, and discounting diminishes it as well.
We will use this fact later on.
Estimating the Geometric Model & Evaluating its Fit
How well does the geometric model describe actual retention
behavior?
lost <- -diff(active_cust)
active <- active_cust[-1]
loop.lik <- function(params) {
p <- params[1]
ll <- 0
for (i in 1:length(lost)) {
ll<-ll+lost[i]*(log(1-p)+(i-1)*log(p))
}
ll <- ll+active[i]*i*log(p)
return(-ll) #return the negative of the function to maximize likelihood
}
#find parameters for p with optim
geom <- optimize(loop.lik, c(0, 1), tol = 0.0001)
p_hat <- geom$minimum
Now we can judge the fit of the model by comparing the retention rate
and survival function implied by the geometric model to the actual
numbers.
par(mfrow=c(1,1))
par(mai=c(.8,.8,.2,.2))
plot(1:10,rep(p_hat,10),ylab="Retention Rate",xlab="Period",main="",ylim=c(.55,1),type="l")
lines(1:10, r, type="b",ylab="retention rate r(t)", xlab="Period",ylim=par("yaxp")[1:2])
text(8, .73, "predicted: geom. model", cex=1, pos=3, col="black")
text(6, .95, "actual", cex=1, pos=3, col="black")
Equivalently for the survival function:
S_geo=p_hat^(0:10)
plot(0:10,S_geo,ylab="Survivor function",xlab="Period",main="",ylim=c(.1,1),type="l")
lines(0:10, S, type="b",ylab="retention rate r(t)", xlab="Period",ylim=par("yaxp")[1:2])
text(3, .8, "predicted: geom. model", cex=1, pos=3, col="black")
text(2, .3, "actual", cex=1, pos=3, col="black")
Ruse of heterogeneity
In a given cohort of customers, the retention rate
usually increases over time. An “old” customer is more likely to stay
than a new one.
Why do retention rates increase over time (for a cohort of
customers)?
- This can arise because customers are becoming more
loyal. - But it can also be due to
heterogeneity in the population with respect to
retention rates.
Consider a population of two types of customers: good
customers have retention rate of 0.90; bad
customers have 0.50. There are twice as many
bad as good.
N<-10000
prop<-1/3
n<- data.frame(seg1=rep(NA,10), seg2=rep(NA,10))
ret<-c(.9,.5)
n$seg1[1]<-round(N*prop)
n$seg2[1]<-round((1-prop)*N)
for(k in 2:10){
n[k,]<-round(n[k-1,]*ret)
}
avgr<-(n$seg1*ret[1]+n$seg2*ret[2])/rowSums(n)
t=seq(1,10)
par(mfrow=c(1,2))
plot(t,avgr,ylab="retention rate",xlab="period",main="",ylim=c(.4,1),type="b", xaxt="none", lwd=3)
axis(1, seq(1,10,1))
abline(h= ret[1], lty=2, lwd=3, col="blue")
abline(h= ret[2], lty=2, lwd=3, col="red")
text(2.5, .78, "avg. ret.")
text(3, .52, "seg. 2", cex=1, pos=3, col="black")
text(3, .92, "seg. 1", cex=1, pos=3, col="black")
plot(t,n$seg2, xlab="period",main="",type="b", lwd=3, xaxt="none", col="red", ylab="Number of customers")
axis(1, seq(1,10,1))
lines(t,n$seg1, lwd=3, type="b", col="blue")
text(4, 3000, "seg. 1", cex=1, pos=3, col="black")
text(2.3, 5000, "seg. 2", cex=1, pos=3, col="black")
In the beginning, there are more bad than good customers. But by
period 3 that changes, as more bad customers quit, leaving more good
customers in the cohort.
Shifted Beta geometric
Above we considered two types of customers. Now we build a model
where there is a distribution of retention rates across customers.
Conditional on the retention rate, we assume that the probability a
customer survives at least \(t\)
periods is geometric, as before.
We will rewrite it in terms of the churn rate, \(\theta = 1-p\), rather than the retention
rate: \[
S(t \mid \theta) = p^t = (1-\theta)^t
\]
We then assume that there is a distribution of churn rates \(\theta\) in the population. This rate has
to be between 0 and 1, so we assume it comes from a Beta distribution
with parameters \(a\) and \(b\). \(B(a,b)\) is the beta function:
beta(1,5)
## [1] 0.2
The beta distribution is: \[ f(\theta | a,b) = \frac{\theta^{a-1}
(1-\theta)^{b-1}}{B(a,b)}, \qquad a>0, b>0\] The retention
rate implied by this, using the formula \(r(t)=S(t)/S(t-1)\) is \[
r(t|a,b)=\frac{B(a,b+t)}{B(a,b+t-1)}=\frac{b+t-1}{a+b+t-1}
\]
As you can see, this retention rate increases over time. How fast it
increases depends on the parameters \(a\) and \(b\).
- If \(a\) and \(b\) are are small, the retention rate
increases quickly, but also levels off quickly.
- At medium levels, \(a\) and \(b\) rise at a decreasing rate. - If \(a\) and \(b\) are large, there is hardly any
increase. It is essentially equivalent to the geometric model that
assumed a constant retetion rate.
t=seq(1,10)
r_sBG=function(a,b,t){
(b+t-1)/(a+b+t-1)
}
par(mfrow=c(1,1))
plot(t,r_sBG(.1,.3,t),ylab="retention rate",xlab="period",main="retention rate: shifted Beta geometric model for different parameter values",ylim=c(.65,1),type="b", xaxt="none")
points(t,r_sBG(1,3,t),type="b",col="red")
points(t,r_sBG(100,300,t),type="b",col="green")
axis(1, seq(0,10,1))
text(8, .7, "a=100,b=300", cex=1, pos=3, col="black")
text(8, .85, "a=1,b=3", cex=1, pos=3, col="black")
text(8, .94, "a=0.1,b=0.3", cex=1, pos=3, col="black")
Each period, the high \(\theta\)
customers drop out, shrinking the average \(\theta\) across customers. The rate at
which this happens depends on the heterogeneity.
set.seed(19103)
N=1000
a<-2
b<-8
par(mfrow=c(2,2))
for (t in 1:4){
cust<-rbeta(N, a,b) # draw N times from a beta distribution with parameters a and b
par(mai=c(.7,.8,.2,.2))
g<-hist(cust,breaks = 99,xlim = c(0,1),density = 10, main=paste("churn prob. in period", t), xlab = expression(paste("churn probability (", theta, ")")), ylab = "number of customers",)
text(.8,.8*par("yaxp")[2], paste("N=",round(N)),cex=1,pos=3,col="black")
abline(v=mean(cust),col = "red", lwd = 2) # draw average churn
b<-b+1 # Bayes update churn distribution
N<-N*(b+t-1)/(a+b+t-1) # churners leave
}
Estimating the model
You can estimate the model using maximum likelihood.
We get parameters \(a\) and \(b\). Then we can forecast \(r(t)\) and compare the predicted retention
to the actual retention rate.
loop.lik<-function(params) {
a<-params[1]
b<-params[2]
ll<-0
for (i in 1:length(lost)) {
ll<-ll+lost[i]*log(beta(a+1,b+i-1)/beta(a,b))
}
ll<-ll+active[i]*log(beta(a,b+i)/beta(a,b))
return(-ll) #return the negative of the function to maximize likelihood
}
#find parameters for a and b with optim
sBG<-optim(par=c(1,1),loop.lik)
a<-sBG$par[1]
b<-sBG$par[2]
#calculate retention using model parameters
t<-1:length(active)
r_pred<-r_sBG(a,b,t)
# plot actual and predicted retention rate
#rep(p_hat,10)
#t
par(mfrow=c(1,1))
plot(t,r_pred,ylab="retention rate",xlab="period",type="b", xaxt="none", ylim = c(.55,1))
lines(t,rep(p_hat,10),type="b",col="red")
lines(t,r, type="b", col="blue")
axis(1, seq(0,10,1))
legend('right',legend=c("sBG", "geo", "actual"),col=c("black","red","blue"), pch=c(1,1))
S_pred<-c(1,cumprod(r_pred)) # predicted survivor function
S_geo<-p_hat^(c(0,t))
t<-seq(0,10)
par(mfrow=c(1,1))
plot(t,S_pred,ylab="retention rate",xlab="period",type="b", xaxt="none", ylim = c(0,1))
lines(t,S_geo,type="b",col="red")
lines(t,S, type="b", col="blue")
axis(1, seq(0,10,1))
legend('right',legend=c("sBG", "geo", "actual"),col=c("black","red","blue"), pch=c(1,1))
Calculating CLV with the sBG
In the geometric model, we could use the properties of a geometric
series to derive CLV over an infinite time horizon. With the sBG, we
have no such closed form expression. Instead, we can set the time
horizon \(T\) at some large number,
like \(200\), and sum up the first
\(T\) terms of the CLV expression.
Because of discounting and the diminishing survival function, each
additional term contributes less to CLV. So after a suitably large \(T\), we can safely ignore the later
terms.
\[
E[CLV] = m + \frac{m \; S(1)}{(1+d)^1} + \frac{m \; S(2)}{(1+d)^2} +
\frac{m \; S(3)}{(1+d)^3} + \cdots + \frac{m \; S(T)}{(1+d)^T}
\]
Since we have parameters \(a\) and
\(b\), we can project \(r(t)\) and therefore \(S(t)\). The code below does this.
t<-seq(1,200) # time periods
r_pred <- r_sBG(a,b,t) # predicted retention rate
S_pred <- c(1,cumprod(r_pred)[1:199]) # predicted survivor function
dis <- 1/(1+d)^(t-1) # discount factor, first term is present so no discounting
CLV_sBG<-sum(m*S_pred*dis) # the sum of margin x survivor x discount factor
CLV_sBG
## [1] 368
# compare with the geometric CLV model
geo<-geoCLV(p_hat,m,d)
geo
## [1] 361
The CLV we calculate using the sBG model is 368, slightly higher than
what we would predict with the geometric model, 361.
While it doesn’t seem like much, the big difference will come when we
calculate RLV. Why? Because the geometric model says there is no
difference between old and new customers. The sBG model says there is a
difference, due to heterogeneity and sorting.
RLV with the sBG
We know from above that the sBG model leads to customers with higher
churn probabilities dropping out earlier, the sorting mechanism we
described earlier. Hence a customer who has remained for so many periods
will likely have a lower churn probability than a new
customer. In the geometric model, this makes no difference: everyone has
the same \(\theta\). But in the sBG
model, it does make a difference due to heterogeneity.
CLV refers to new customers, but what about those who we have
observed for some time? Residual lifetime value (RLV)
is the term for already acquired customers, as opposed to new
customers.
The conditional probability of a customer who has survived \(\tau\) periods surviving \(s>\tau\) periods is: \[
P(T>s | T > \tau) = \frac{S(s)}{S(\tau)} \quad \textrm{where} \; s
> \tau
\]
For RLV, we calculate right before the renewal decision.
Let’s try this out for someone who has renewed \(\tau = 4\) times. We are standing just
before period 5: does this person, who has survived 4 periods, survive
once more? The probability that he/she does is \(\frac{S(5)}{S(4)}\).
tau <- 4
t <- seq(1,tau+200)
r_pred <- r_sBG(a,b,t)
S_pred <- cumprod(r_pred)
S_shift <- S_pred[(tau+1):length(S_pred)] # survival function from tau + 1 until T
dis<-1/(1+d)^(t(1:200)-1) # discount rate
RLV_sBG<-sum(m*S_shift/S_pred[tau]*dis) # sum of margin x S(tau + t)/ S(tau) x discount
RLV_sBG
## [1] 650
We calculate the RLV as 650, which is substantially higher CLV 368,
because RLV is for already existing customers who are likely to
have lower churn probabilities than new customers. The
implication is that heterogeneity and sorting are important. As time
passes, the customer base shrinks and those who remain are increasingly
likely to stay longer.
The RLV formula for geometric model right before renewing is: \[
E[RLV]= m p + \frac{m \; p^2}{(1+d)} + \frac{m \; p^3}{(1+d)^2} +
\dots = \frac{mp(1+d)}{1+d-p}
\] Plugging some numbers in, we see that the RLV is substantially
lower in the geometric model.
RLV_geo<-m*p_hat*(1+d)/(1+d-p_hat)
RLV_geo
## [1] 287
The RLV using the geometric model is 287, substantially less than
that of the sBG model.
LS0tDQp0aXRsZTogIlR1dG9yaWFsIDY6IENMViAtIENvbnRyYWN0dWFsIFNldHRpbmdzIg0KYXV0aG9yOiAiRGFuaWVsIFJlZGVsIg0KZGF0ZTogIjIwMjMtMDEtMjMiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCnJtKGxpc3Q9bHMoKSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpvcHRpb25zKCJzY2lwZW4iPTEwMCwgImRpZ2l0cyI9Mywgd2lkdGggPSAxNTApDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KKipDdXN0b21lciBMaWZldGltZSBWYWx1ZSoqIGlzIHRoZSBwcmVzZW50IHZhbHVlIG9mIHRoZSBmdXR1cmUgcHJvZml0cyBhc3NvY2lhdGVkIHdpdGggYSBwYXJ0aWN1bGFyIGN1c3RvbWVyLiBJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIGZvY3VzIG9uIGNvbnRyYWN0dWFsIHNldHRpbmdzLCB3aGVyZSBjdXN0b21lcnMgbXVzdCBub3RpZnkgdGhlIGZpcm0gd2hlbiB0aGV5IHF1aXQuIEluIG90aGVyIHdvcmRzLCBjdXN0b21lciBjaHVybiBpcyAqKm9ic2VydmVkKiogYnkgdGhlIGZpcm0uIFRoZSBwcmltaXRpdmVzIG9mIENMViBhcmUgdGhlICptYXJnaW4qLCB0aGUgKmRpc2NvdW50IHJhdGUqLCBhbmQgKnJldGVudGlvbiosIHdoaWNoIG9mdGVuIHJlY2VpdmVzIHRoZSBtb3N0IGZvY3VzIG91dCBvZiB0aGUgdGhyZWUuDQoNCiMgQmFzaWMgVGVybXMNCg0KQmVsb3cgd2UgdXNlIHNvbWUgZGF0YSBmcm9tIGEgc3Vic2NyaXB0aW9uIGNvbXBhbnkuIFRoZSB0aW1lIHBlcmlvZCBpcyB5ZWFycy4NCg0KYGBge3J9DQphY3RpdmVfY3VzdD1jKDEyNDg5LA0KNzM1NiwNCjUyNTgsDQo0MzA5LA0KMzc0NywNCjM0MzUsDQozMTIzLA0KMjk0OCwNCjI3ODYsDQoyNzExLA0KMjYyNCkNCmRhdGEgPC0gY2JpbmQoMDoxMCxhY3RpdmVfY3VzdCkNCmNvbG5hbWVzKGRhdGEpPC1jKCJQZXJpb2QiLCAiQWN0aXZlIEN1c3RvbWVycyIpDQpkYXRhICU+JSANCiAga2JsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkF0IHllYXIgJHQ9MCQgdGhlcmUgaXMgYSAqKmNvaG9ydCoqIG9mIDEyLDQ4OSBjdXN0b21lcnMgd2hvIGpvaW4gaW4gdGhlIHNhbWUgeWVhci4NCg0KQSB5ZWFyIGxhdGVyIDcsMzU2IG9mIHRoZW0gcmVuZXdlZCwgc28gJFMoMSkgPSBcZnJhY3s3MzU2fXsxMjQ4OX0gPSAwLjU4OSQuDQoNCkNvZGluZyBoaW50OiBgZGlmZmAgdGFrZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBhZGphY2VudCB0ZXJtcyBpbiBhIHZlY3Rvci4NCg0KYGBge3J9DQpsb3N0IDwtIC1kaWZmKGFjdGl2ZV9jdXN0KQ0KUyA8LSBhY3RpdmVfY3VzdFsxOjExXS9hY3RpdmVfY3VzdFsxXSAjIyBTdXJ2aXZvciBGdW5jdGlvbg0KZGF0YSA8LSBjYmluZChkYXRhLCBTKQ0KDQpkYXRhICU+JSANCiAga2JsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNClN1cnZpdmFsIEZ1bmN0aW9uIFBsb3Q6DQoNCmBgYHtyfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCg0KcGxvdCgwOjEwLCBTLCB0eXBlPSJiIix5bGFiPSJTdXJ2aXZhbCBmdWNudGlvbiBTKHQpIiwgeGxhYj0iUGVyaW9kIix5bGltPXBhcigieWF4cCIpWzE6Ml0pIA0KYGBgDQoNClRoZSAqKnJldGVudGlvbiByYXRlKiogaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBjdXN0b21lciB3aG8gd2FzIGFjdGl2ZSAoaS5lLiwgc3RpbGwgc3Vic2NyaWJlZCkgaW4gJHQtMSQgd2lsbCBzdGlsbCBiZSBhY3RpdmUgYXQgdGhlIGVuZCBvZiB0aGUgbmV4dCBwZXJpb2QgJHQkLiBUaGlzIGlzIGEgKmNvbmRpdGlvbmFsKiBwcm9iYWJpbGl0eSwgYmVjYXVzZSB3ZSBhcmUgY29uZGl0aW9uaW5nIG9uIHRoZSBmYWN0IHRoYXQgdGhlIGN1c3RvbWVyIGhhcyBzdXJ2aXZlZCAkdC0xJCBwZXJpb2RzLg0KDQpJdCB0dXJucyBvdXQgdGhlIHJldGVudGlvbiByYXRlIGlzIHRoZSByYXRpbyBvZiBzdXJ2aXZvciBmdW5jdGlvbnMgaW4gYWRqYWNlbnQgcGVyaW9kczogJCQNCnIodCkgPSBQKFQ+dCBcbWlkIFQ+dC0xKSA9IFxmcmFje1ModCl9e1ModC0xKX0gDQokJCBJbiBvdXIgZGF0YSBhdCAkdD0yJCwgdGhlIHJldGVudGlvbiByYXRlIGlzICRyKDIpID0gXGZyYWN7NTI1OH17NzM1Nn0gPSAwLjcxNSQ6IG9mIHRoZSA3MzU2IGN1c3RvbWVycyB3aG8gd2VyZSBhY3RpdmUgaW4gJHQ9MSQsIDUyNTggd2VyZSBhY3RpdmUgaW4gJHQ9MiQuDQoNCmBgYHtyfQ0KciA8LSBTWzI6MTFdL1NbMToxMF0gIyBERUZJTklUSU9ODQoNCmRhdGEgPC0gY2JpbmQoZGF0YSwgYyhOQSxyKSkNCmNvbG5hbWVzKGRhdGEpW1s0XV0gPC0gInIiDQpkYXRhICU+JSANCiAga2JsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNClJldGVudGlvbiBSYXRlIFBsb3Q6DQoNCmBgYHtyfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCnBsb3QoMToxMCwgciwgdHlwZT0iYiIseWxhYj0icmV0ZW50aW9uIHJhdGUgcih0KSIsIHhsYWI9IlBlcmlvZCIseWxpbT1wYXIoInlheHAiKVsxOjJdKSANCg0KYGBgDQoNCkxhc3RseSB0aGUgKipjaHVybiByYXRlKiogaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgY3VzdG9tZXIgd2hvIHdhcyBhY3RpdmUgaW4gcGVyaW9kICR0LTEkIHF1aXRzIGluIHBlcmlvZCAkdCQuIE5vdGUgdGhpcyBpcyByZWxhdGVkIHRvIHRoZSAqKmhhemFyZCByYXRlKiosIHdoaWNoIGlzIGhvdyBzb2NpYWwgc2NpZW50aXN0cyBtb2RlbCAqdGltZSB1bnRpbCBhbiBldmVudCogKGhlcmUgY2h1cm4pLiBJdCBpcyB0aGUgY29tcGxlbWVudCBvZiB0aGUgcmV0ZW50aW9uIHJhdGUuIEluIG90aGVyIHdvcmRzOiAkJA0KYyh0KT1QKFQ9dCBcbWlkIFQ+dC0xKSA9IDEtcih0KQ0KJCQNCg0KYGBge3J9DQpjIDwtIDEtcg0KZGF0YSA8LSBjYmluZChkYXRhLCBjKE5BLGMpKQ0KY29sbmFtZXMoZGF0YSlbWzVdXSA8LSAiYyINCmBgYA0KDQpgYGB7cn0NCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpwbG90KDE6MTAsIGMsIHR5cGU9ImIiLHlsYWI9ImNodXJuIHJhdGUgYyh0KSIsIHhsYWI9IlBlcmlvZCIseWxpbT1wYXIoInlheHAiKVsxOjJdKSANCmBgYA0KDQpXZSB3cm90ZSB0aGUgcmV0ZW50aW9uIHJhdGUgYXMgdGhlIGZyYWN0aW9uIG9mIGFkamFjZW50IFN1cnZpdmFsIGZ1bmN0aW9ucy4gWW91IGNhbiBhbHNvIHdyaXRlIHRoZSBTdXJ2aXZhbCBmdW5jdGlvbiBhcyB0aGUgcHJvZHVjdCBvZiByZXRlbnRpb24gcmF0ZXMuICQkDQpTKHQpID0gXHByb2Rfe2o9MX1ee3R9IFw7IHIoaikgDQokJA0KDQojIEdlb21ldHJpYyBtb2RlbA0KDQpBc3N1bWVzIGEgY29uc3RhbnQgcmV0ZW50aW9uIHJhdGUuDQoNCkV2ZXJ5IHBlcmlvZCB5b3UgZmxpcCBhIGNvaW4gYW5kIHdpdGggcHJvYmFiaWxpdHkgJHAkIHlvdSBzdGF5Lg0KDQpUaGUgcHJvYmFiaWxpdHkgb2YgbGFzdGluZyBtb3JlIHRoYW4gJHQkIHBlcmlvZHMgaXMgdGhlbiBnZXR0aW5nIHRhaWxzICR0JCB0aW1lcyBpbiBhIHJvdy4gJCQNClModCkgPSBwXnQNCiQkDQoNClRoZXJlJ3Mgb25lIHBhcmFtZXRlciBpbiB0aGUgbW9kZWwsICRwJC4gTGV0J3MgYXNzdW1lLCBmb3IgZXhhbXBsZSwgdGhhdCAkcCA9IDAuOCQuDQoNCmBgYHtyIG91dC53aWR0aCA9ICcxMDAlJywgZmlnLmFsaWduID0gImNlbnRlciJ9DQpwIDwtIDAuOCAgICAjIHJldGVudGlvbiBwcm9iYWJpbGl0eSAocmF0ZSkgDQoNCnQgPC0gc2VxKDAsMTApICAjIHRpbWUgcGVyaW9kIHN0YXJ0aW5nIGF0IDANCg0KcGFyKG1mcm93PWMoMSwxKSkNCnBhcihtYWk9YyguOCwuOCwuMiwuMikpDQpwbG90KHQsIHBedCwgdHlwZT0iYiIseWxhYj0iUHJvYmFiaWxpdHkgdGhlIGN1c3RvbWVyIGhhcyBzdXJ2aXZlZCIsIHhsYWI9IlBlcmlvZCIsIG1haW49IlN1cnZpdmFsIGZ1bmN0aW9uIikNCnRleHQoMS41LCAuNzgsICIgMC44IiwgY2V4PTEsIHBvcz0zLCBjb2w9ImJsYWNrIikNCnRleHQoMi41LCAuNjIsIHBhcnNlKHRleHQ9ICcuOF4yJyksIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQp0ZXh0KDkuNiwgLjE2LCBwYXJzZSh0ZXh0PScuOF4xMCcpLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KDQpgYGANCg0KIyBDTFYgKEhvbW9nZW5laXR5IENhc2UpDQoNCkNMViBpcyB0aGUgcHJlc2VudCB2YWx1ZSBvZiB0aGUgZXhwZWN0ZWQgcHJvZml0czogdGhlIG1hcmdpbiAoYXNzdW1lZCBjb25zdGFudCksIG11bHRpcGxpZWQgYnkgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIGN1c3RvbWVyIHN1cnZpdmVzIHVwIHVudGlsIHRoaXMgcG9pbnQuDQoNCkluIHRoZSBnZW9tZXRyaWMgbW9kZWwsICRTKHQpPXBedCQuIFdlIGNhbiB1c2UgdGhlIHJlc3VsdHMgb2YgYSBnZW9tZXRyaWMgc2VyaWVzXF5bRm9yIGEgZ2VvbWV0cmljIHNlcmllczogJCQNClxiZWdpbnthcnJheX17Y2NsfQ0KRVtDTFZdICYgPSAmIFxkaXNwbGF5c3R5bGUgXGZyYWN7bSBcOyAoMStkKX17MStkLXB9DQpcZW5ke2FycmF5fQ0KJCQgV2Ugd3JpdGUgYSBmb3JtdWxhIHRvIGNhbGN1bGF0ZSBpdDoNCg0KYGBge3J9DQpnZW9DTFYgPC0gZnVuY3Rpb24ocCxtLGQpew0KICBtKigxK2QpLygxK2QtcCkNCn0NCg0KcDwtMC44ICAgICMgcmV0ZW50aW9uIHByb2JhYmlsaXR5IChyYXRlKSANCm08LTEwMCAgICAjIG1hcmdpbiAocHJvZml0KQ0KZDwtMC4xICAgICMgZGlzY291bnQgcmF0ZQ0KDQojIyBFeHBlY3RlZCBDdXN0b21lciBMaWZldGltZSBWYWx1ZSBvZiB0aGUgbmV3IGN1c3RvbWVyDQpnZW9DTFYocCxtLGQpDQoNCmBgYA0KDQpXZSBjYW4gY29tcGFyZSB0aGlzIGZvcm11bGEgY2FsY3VsYXRlZCBvdmVyIGEgaW5maW5pdGUgaG9yaXpvbiB0byB0aGUgZmlyc3QgMTAgdGVybXMuDQoNCmBgYHtyfQ0KdDwtc2VxKDAsMTApICAjIHRpbWUgcGVyaW9kIHN0YXJ0aW5nIGF0IDANCg0KbSoocC8oMStkKSledCAjIHRoZSBmaXJzdCAxMCB0ZXJtcyBvZiBDTFYNCg0Kc3VtKG0qKHAvKDErZCkpXnQpICAjIENMViB1c2luZyBvbmx5IHRoZSBmaXJzdCAxMCB0ZXJtcw0KYGBgDQoNClNvIHRoZSAqKjEwLXBlcmlvZCBDTFYqKiBpcyBgciByb3VuZChzdW0obSoocC8oMStkKSledCkpYC4gSXQgaXMgcmVsYXRpdmVseSBjbG9zZSB0byB0aGUgKippbmZpbml0ZS1ob3Jpem9uIENMVioqLCB3aGljaCBpcyBgciByb3VuZChnZW9DTFYocCxtLGQpKWAuICoqRWFjaCBhZGRpdGlvbmFsIHRlcm0gYWRkcyBsZXNzKiogYmVjYXVzZSB0aGUgcHJvYmFiaWxpdHkgb2YgcmVtYWluaW5nIGEgY3VzdG9tZXIgZGltaW5pc2hlcyBvdmVyIHRpbWUsIGFuZCBkaXNjb3VudGluZyBkaW1pbmlzaGVzIGl0IGFzIHdlbGwuICoqV2Ugd2lsbCB1c2UgdGhpcyBmYWN0IGxhdGVyIG9uLioqDQoNCiMjIEVzdGltYXRpbmcgdGhlIEdlb21ldHJpYyBNb2RlbCAmIEV2YWx1YXRpbmcgaXRzIEZpdA0KDQpIb3cgd2VsbCBkb2VzIHRoZSBnZW9tZXRyaWMgbW9kZWwgZGVzY3JpYmUgKmFjdHVhbCogcmV0ZW50aW9uIGJlaGF2aW9yPw0KDQpgYGB7ciBvdXQud2lkdGggPSAnMTAwJScsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQ0KbG9zdCA8LSAtZGlmZihhY3RpdmVfY3VzdCkNCmFjdGl2ZSA8LSBhY3RpdmVfY3VzdFstMV0NCg0KbG9vcC5saWsgPC0gZnVuY3Rpb24ocGFyYW1zKSB7DQpwIDwtIHBhcmFtc1sxXQ0KbGwgPC0gMA0KZm9yIChpIGluIDE6bGVuZ3RoKGxvc3QpKSB7DQogICAgbGw8LWxsK2xvc3RbaV0qKGxvZygxLXApKyhpLTEpKmxvZyhwKSkNCn0NCmxsIDwtIGxsK2FjdGl2ZVtpXSppKmxvZyhwKQ0KcmV0dXJuKC1sbCkgICAgI3JldHVybiB0aGUgbmVnYXRpdmUgb2YgdGhlIGZ1bmN0aW9uIHRvIG1heGltaXplIGxpa2VsaWhvb2QNCn0gDQoNCiNmaW5kIHBhcmFtZXRlcnMgZm9yIHAgd2l0aCBvcHRpbQ0KZ2VvbSA8LSBvcHRpbWl6ZShsb29wLmxpaywgYygwLCAxKSwgdG9sID0gMC4wMDAxKQ0KDQpwX2hhdCA8LSBnZW9tJG1pbmltdW0NCmBgYA0KDQpOb3cgd2UgY2FuIGp1ZGdlIHRoZSBmaXQgb2YgdGhlIG1vZGVsIGJ5IGNvbXBhcmluZyB0aGUgcmV0ZW50aW9uIHJhdGUgYW5kIHN1cnZpdmFsIGZ1bmN0aW9uIGltcGxpZWQgYnkgdGhlIGdlb21ldHJpYyBtb2RlbCB0byB0aGUgYWN0dWFsIG51bWJlcnMuDQoNCmBgYHtyfQ0KDQpwYXIobWZyb3c9YygxLDEpKQ0KcGFyKG1haT1jKC44LC44LC4yLC4yKSkNCnBsb3QoMToxMCxyZXAocF9oYXQsMTApLHlsYWI9IlJldGVudGlvbiBSYXRlIix4bGFiPSJQZXJpb2QiLG1haW49IiIseWxpbT1jKC41NSwxKSx0eXBlPSJsIikNCmxpbmVzKDE6MTAsIHIsIHR5cGU9ImIiLHlsYWI9InJldGVudGlvbiByYXRlIHIodCkiLCB4bGFiPSJQZXJpb2QiLHlsaW09cGFyKCJ5YXhwIilbMToyXSkgDQoNCnRleHQoOCwgLjczLCAicHJlZGljdGVkOiBnZW9tLiBtb2RlbCIsIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQp0ZXh0KDYsIC45NSwgImFjdHVhbCIsIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQoNCmBgYA0KDQpFcXVpdmFsZW50bHkgZm9yIHRoZSBzdXJ2aXZhbCBmdW5jdGlvbjoNCg0KYGBge3Igb3V0LndpZHRoID0gJzEwMCUnLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0NClNfZ2VvPXBfaGF0XigwOjEwKQ0KcGxvdCgwOjEwLFNfZ2VvLHlsYWI9IlN1cnZpdm9yIGZ1bmN0aW9uIix4bGFiPSJQZXJpb2QiLG1haW49IiIseWxpbT1jKC4xLDEpLHR5cGU9ImwiKQ0KbGluZXMoMDoxMCwgUywgdHlwZT0iYiIseWxhYj0icmV0ZW50aW9uIHJhdGUgcih0KSIsIHhsYWI9IlBlcmlvZCIseWxpbT1wYXIoInlheHAiKVsxOjJdKSANCnRleHQoMywgLjgsICJwcmVkaWN0ZWQ6IGdlb20uIG1vZGVsIiwgY2V4PTEsIHBvcz0zLCBjb2w9ImJsYWNrIikNCnRleHQoMiwgLjMsICJhY3R1YWwiLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KYGBgDQoNCiMgUnVzZSBvZiBoZXRlcm9nZW5laXR5DQoNCkluIGEgZ2l2ZW4gKipjb2hvcnQqKiBvZiBjdXN0b21lcnMsIHRoZSByZXRlbnRpb24gcmF0ZSB1c3VhbGx5IGluY3JlYXNlcyBvdmVyIHRpbWUuIEFuICJvbGQiIGN1c3RvbWVyIGlzIG1vcmUgbGlrZWx5IHRvIHN0YXkgdGhhbiBhIG5ldyBvbmUuDQoNCldoeSBkbyByZXRlbnRpb24gcmF0ZXMgaW5jcmVhc2Ugb3ZlciB0aW1lIChmb3IgYSBjb2hvcnQgb2YgY3VzdG9tZXJzKT9cDQotIFRoaXMgY2FuIGFyaXNlIGJlY2F1c2UgY3VzdG9tZXJzIGFyZSAqKmJlY29taW5nIG1vcmUgbG95YWwqKi4gLSBCdXQgaXQgY2FuIGFsc28gYmUgZHVlIHRvICoqaGV0ZXJvZ2VuZWl0eSoqIGluIHRoZSBwb3B1bGF0aW9uIHdpdGggcmVzcGVjdCB0byByZXRlbnRpb24gcmF0ZXMuDQoNCkNvbnNpZGVyIGEgcG9wdWxhdGlvbiBvZiB0d28gdHlwZXMgb2YgY3VzdG9tZXJzOiAqZ29vZCogY3VzdG9tZXJzIGhhdmUgcmV0ZW50aW9uIHJhdGUgb2YgKiowLjkwKio7ICpiYWQqIGN1c3RvbWVycyBoYXZlICoqMC41MCoqLiBUaGVyZSBhcmUgKnR3aWNlKiBhcyBtYW55ICoqYmFkKiogYXMgKipnb29kKiouDQoNCmBgYHtyIG91dC53aWR0aCA9ICcxMDAlJywgZmlnLmFsaWduID0gImNlbnRlciJ9DQpOPC0xMDAwMA0KcHJvcDwtMS8zDQpuPC0gZGF0YS5mcmFtZShzZWcxPXJlcChOQSwxMCksIHNlZzI9cmVwKE5BLDEwKSkNCnJldDwtYyguOSwuNSkNCm4kc2VnMVsxXTwtcm91bmQoTipwcm9wKQ0KbiRzZWcyWzFdPC1yb3VuZCgoMS1wcm9wKSpOKQ0KDQpmb3IoayBpbiAyOjEwKXsNCiAgbltrLF08LXJvdW5kKG5bay0xLF0qcmV0KQ0KfQ0KDQphdmdyPC0obiRzZWcxKnJldFsxXStuJHNlZzIqcmV0WzJdKS9yb3dTdW1zKG4pDQp0PXNlcSgxLDEwKQ0KDQpwYXIobWZyb3c9YygxLDIpKQ0KcGxvdCh0LGF2Z3IseWxhYj0icmV0ZW50aW9uIHJhdGUiLHhsYWI9InBlcmlvZCIsbWFpbj0iIix5bGltPWMoLjQsMSksdHlwZT0iYiIsIHhheHQ9Im5vbmUiLCBsd2Q9MykNCmF4aXMoMSwgc2VxKDEsMTAsMSkpDQphYmxpbmUoaD0gcmV0WzFdLCBsdHk9MiwgbHdkPTMsIGNvbD0iYmx1ZSIpDQphYmxpbmUoaD0gcmV0WzJdLCBsdHk9MiwgbHdkPTMsIGNvbD0icmVkIikNCnRleHQoMi41LCAuNzgsICJhdmcuIHJldC4iKQ0KdGV4dCgzLCAuNTIsICJzZWcuIDIiLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KdGV4dCgzLCAuOTIsICJzZWcuIDEiLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KDQpwbG90KHQsbiRzZWcyLCB4bGFiPSJwZXJpb2QiLG1haW49IiIsdHlwZT0iYiIsIGx3ZD0zLCB4YXh0PSJub25lIiwgY29sPSJyZWQiLCB5bGFiPSJOdW1iZXIgb2YgY3VzdG9tZXJzIikNCmF4aXMoMSwgc2VxKDEsMTAsMSkpDQpsaW5lcyh0LG4kc2VnMSwgbHdkPTMsIHR5cGU9ImIiLCBjb2w9ImJsdWUiKQ0KdGV4dCg0LCAzMDAwLCAic2VnLiAxIiwgY2V4PTEsIHBvcz0zLCBjb2w9ImJsYWNrIikNCnRleHQoMi4zLCA1MDAwLCAic2VnLiAyIiwgY2V4PTEsIHBvcz0zLCBjb2w9ImJsYWNrIikNCmBgYA0KDQpJbiB0aGUgYmVnaW5uaW5nLCB0aGVyZSBhcmUgbW9yZSBiYWQgdGhhbiBnb29kIGN1c3RvbWVycy4gQnV0IGJ5IHBlcmlvZCAzIHRoYXQgY2hhbmdlcywgYXMgbW9yZSBiYWQgY3VzdG9tZXJzIHF1aXQsIGxlYXZpbmcgbW9yZSBnb29kIGN1c3RvbWVycyBpbiB0aGUgY29ob3J0Lg0KDQojIFNoaWZ0ZWQgQmV0YSBnZW9tZXRyaWMNCg0KQWJvdmUgd2UgY29uc2lkZXJlZCB0d28gdHlwZXMgb2YgY3VzdG9tZXJzLiBOb3cgd2UgYnVpbGQgYSBtb2RlbCB3aGVyZSB0aGVyZSBpcyBhIGRpc3RyaWJ1dGlvbiBvZiByZXRlbnRpb24gcmF0ZXMgYWNyb3NzIGN1c3RvbWVycy4NCg0KQ29uZGl0aW9uYWwgb24gdGhlIHJldGVudGlvbiByYXRlLCB3ZSBhc3N1bWUgdGhhdCB0aGUgcHJvYmFiaWxpdHkgYSBjdXN0b21lciBzdXJ2aXZlcyBhdCBsZWFzdCAkdCQgcGVyaW9kcyBpcyBnZW9tZXRyaWMsIGFzIGJlZm9yZS4NCg0KV2Ugd2lsbCByZXdyaXRlIGl0IGluIHRlcm1zIG9mIHRoZSBjaHVybiByYXRlLCAkXHRoZXRhID0gMS1wJCwgcmF0aGVyIHRoYW4gdGhlIHJldGVudGlvbiByYXRlOiAkJA0KUyh0IFxtaWQgXHRoZXRhKSA9IHBedCA9ICgxLVx0aGV0YSledA0KJCQNCg0KV2UgdGhlbiBhc3N1bWUgdGhhdCB0aGVyZSBpcyBhIGRpc3RyaWJ1dGlvbiBvZiBjaHVybiByYXRlcyAkXHRoZXRhJCBpbiB0aGUgcG9wdWxhdGlvbi4gVGhpcyByYXRlIGhhcyB0byBiZSBiZXR3ZWVuIDAgYW5kIDEsIHNvIHdlIGFzc3VtZSBpdCBjb21lcyBmcm9tIGEgQmV0YSBkaXN0cmlidXRpb24gd2l0aCBwYXJhbWV0ZXJzICRhJCBhbmQgJGIkLiAkQihhLGIpJCBpcyB0aGUgKmJldGEgZnVuY3Rpb24qOg0KDQpgYGB7cn0NCmJldGEoMSw1KQ0KYGBgDQoNClRoZSAqKmJldGEgZGlzdHJpYnV0aW9uKiogaXM6ICQkIGYoXHRoZXRhIHwgYSxiKSA9IFxmcmFje1x0aGV0YV57YS0xfSAoMS1cdGhldGEpXntiLTF9fXtCKGEsYil9LCBccXF1YWQgYT4wLCBiPjAkJCBUaGUgcmV0ZW50aW9uIHJhdGUgaW1wbGllZCBieSB0aGlzLCB1c2luZyB0aGUgZm9ybXVsYSAkcih0KT1TKHQpL1ModC0xKSQgaXMgJCQNCnIodHxhLGIpPVxmcmFje0IoYSxiK3QpfXtCKGEsYit0LTEpfT1cZnJhY3tiK3QtMX17YStiK3QtMX0NCiQkDQoNCkFzIHlvdSBjYW4gc2VlLCB0aGlzIHJldGVudGlvbiByYXRlIGluY3JlYXNlcyBvdmVyIHRpbWUuIEhvdyBmYXN0IGl0IGluY3JlYXNlcyBkZXBlbmRzIG9uIHRoZSBwYXJhbWV0ZXJzICRhJCBhbmQgJGIkLlwNCi0gSWYgJGEkIGFuZCAkYiQgYXJlIGFyZSBzbWFsbCwgdGhlIHJldGVudGlvbiByYXRlIGluY3JlYXNlcyBxdWlja2x5LCBidXQgYWxzbyBsZXZlbHMgb2ZmIHF1aWNrbHkuXA0KLSBBdCBtZWRpdW0gbGV2ZWxzLCAkYSQgYW5kICRiJCByaXNlIGF0IGEgZGVjcmVhc2luZyByYXRlLiAtIElmICRhJCBhbmQgJGIkIGFyZSBsYXJnZSwgdGhlcmUgaXMgaGFyZGx5IGFueSBpbmNyZWFzZS4gSXQgaXMgZXNzZW50aWFsbHkgZXF1aXZhbGVudCB0byB0aGUgZ2VvbWV0cmljIG1vZGVsIHRoYXQgYXNzdW1lZCBhIGNvbnN0YW50IHJldGV0aW9uIHJhdGUuDQoNCmBgYHtyIG91dC53aWR0aCA9ICcxMDAlJywgZmlnLmFsaWduID0gImNlbnRlciJ9DQoNCnQ9c2VxKDEsMTApDQpyX3NCRz1mdW5jdGlvbihhLGIsdCl7DQogICAgKGIrdC0xKS8oYStiK3QtMSkNCn0NCnBhcihtZnJvdz1jKDEsMSkpDQpwbG90KHQscl9zQkcoLjEsLjMsdCkseWxhYj0icmV0ZW50aW9uIHJhdGUiLHhsYWI9InBlcmlvZCIsbWFpbj0icmV0ZW50aW9uIHJhdGU6IHNoaWZ0ZWQgQmV0YSBnZW9tZXRyaWMgbW9kZWwgZm9yIGRpZmZlcmVudCBwYXJhbWV0ZXIgdmFsdWVzIix5bGltPWMoLjY1LDEpLHR5cGU9ImIiLCB4YXh0PSJub25lIikNCnBvaW50cyh0LHJfc0JHKDEsMyx0KSx0eXBlPSJiIixjb2w9InJlZCIpDQpwb2ludHModCxyX3NCRygxMDAsMzAwLHQpLHR5cGU9ImIiLGNvbD0iZ3JlZW4iKQ0KYXhpcygxLCBzZXEoMCwxMCwxKSkNCnRleHQoOCwgLjcsICJhPTEwMCxiPTMwMCIsIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQp0ZXh0KDgsIC44NSwgImE9MSxiPTMiLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KdGV4dCg4LCAuOTQsICJhPTAuMSxiPTAuMyIsIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQpgYGANCg0KRWFjaCBwZXJpb2QsIHRoZSBoaWdoICRcdGhldGEkIGN1c3RvbWVycyBkcm9wIG91dCwgc2hyaW5raW5nIHRoZSBhdmVyYWdlICRcdGhldGEkIGFjcm9zcyBjdXN0b21lcnMuIFRoZSByYXRlIGF0IHdoaWNoIHRoaXMgaGFwcGVucyBkZXBlbmRzIG9uIHRoZSBoZXRlcm9nZW5laXR5Lg0KDQpgYGB7ciBvdXQud2lkdGggPSAnMTAwJScsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQ0Kc2V0LnNlZWQoMTkxMDMpDQpOPTEwMDAgIA0KYTwtMiAgDQpiPC04DQpwYXIobWZyb3c9YygyLDIpKQ0KZm9yICh0IGluIDE6NCl7DQogIGN1c3Q8LXJiZXRhKE4sIGEsYikgICMgZHJhdyBOIHRpbWVzIGZyb20gYSBiZXRhIGRpc3RyaWJ1dGlvbiB3aXRoIHBhcmFtZXRlcnMgYSBhbmQgYg0KICBwYXIobWFpPWMoLjcsLjgsLjIsLjIpKQ0KICBnPC1oaXN0KGN1c3QsYnJlYWtzID0gOTkseGxpbSA9IGMoMCwxKSxkZW5zaXR5ID0gMTAsIG1haW49cGFzdGUoImNodXJuIHByb2IuIGluIHBlcmlvZCIsIHQpLCB4bGFiID0gICAgIGV4cHJlc3Npb24ocGFzdGUoImNodXJuIHByb2JhYmlsaXR5ICgiLCB0aGV0YSwgIikiKSksIHlsYWIgPSAibnVtYmVyIG9mIGN1c3RvbWVycyIsKQ0KICAgIHRleHQoLjgsLjgqcGFyKCJ5YXhwIilbMl0sIHBhc3RlKCJOPSIscm91bmQoTikpLGNleD0xLHBvcz0zLGNvbD0iYmxhY2siKQ0KICAgIGFibGluZSh2PW1lYW4oY3VzdCksY29sID0gInJlZCIsIGx3ZCA9IDIpICMgZHJhdyBhdmVyYWdlIGNodXJuDQogIGI8LWIrMSAgIyBCYXllcyB1cGRhdGUgY2h1cm4gZGlzdHJpYnV0aW9uDQogIE48LU4qKGIrdC0xKS8oYStiK3QtMSkgIyBjaHVybmVycyBsZWF2ZQ0KfQ0KYGBgDQoNCiMgRXN0aW1hdGluZyB0aGUgbW9kZWwNCg0KWW91IGNhbiBlc3RpbWF0ZSB0aGUgbW9kZWwgdXNpbmcgbWF4aW11bSBsaWtlbGlob29kLg0KDQpXZSBnZXQgcGFyYW1ldGVycyAkYSQgYW5kICRiJC4gVGhlbiB3ZSBjYW4gZm9yZWNhc3QgJHIodCkkIGFuZCBjb21wYXJlIHRoZSBwcmVkaWN0ZWQgcmV0ZW50aW9uIHRvIHRoZSBhY3R1YWwgcmV0ZW50aW9uIHJhdGUuDQoNCmBgYHtyIG91dC53aWR0aCA9ICcxMDAlJywgZmlnLmFsaWduID0gImNlbnRlciJ9DQpsb29wLmxpazwtZnVuY3Rpb24ocGFyYW1zKSB7DQphPC1wYXJhbXNbMV0NCmI8LXBhcmFtc1syXQ0KbGw8LTANCmZvciAoaSBpbiAxOmxlbmd0aChsb3N0KSkgew0KICAgIGxsPC1sbCtsb3N0W2ldKmxvZyhiZXRhKGErMSxiK2ktMSkvYmV0YShhLGIpKQ0KfQ0KbGw8LWxsK2FjdGl2ZVtpXSpsb2coYmV0YShhLGIraSkvYmV0YShhLGIpKQ0KcmV0dXJuKC1sbCkgICAgI3JldHVybiB0aGUgbmVnYXRpdmUgb2YgdGhlIGZ1bmN0aW9uIHRvIG1heGltaXplIGxpa2VsaWhvb2QNCn0gDQoNCiNmaW5kIHBhcmFtZXRlcnMgZm9yIGEgYW5kIGIgd2l0aCBvcHRpbQ0Kc0JHPC1vcHRpbShwYXI9YygxLDEpLGxvb3AubGlrKQ0KDQphPC1zQkckcGFyWzFdDQpiPC1zQkckcGFyWzJdDQoNCiNjYWxjdWxhdGUgcmV0ZW50aW9uIHVzaW5nIG1vZGVsIHBhcmFtZXRlcnMNCnQ8LTE6bGVuZ3RoKGFjdGl2ZSkNCnJfcHJlZDwtcl9zQkcoYSxiLHQpDQoNCiMgcGxvdCBhY3R1YWwgYW5kIHByZWRpY3RlZCByZXRlbnRpb24gcmF0ZQ0KI3JlcChwX2hhdCwxMCkNCiN0DQpwYXIobWZyb3c9YygxLDEpKQ0KcGxvdCh0LHJfcHJlZCx5bGFiPSJyZXRlbnRpb24gcmF0ZSIseGxhYj0icGVyaW9kIix0eXBlPSJiIiwgeGF4dD0ibm9uZSIsIHlsaW0gPSBjKC41NSwxKSkNCmxpbmVzKHQscmVwKHBfaGF0LDEwKSx0eXBlPSJiIixjb2w9InJlZCIpDQpsaW5lcyh0LHIsIHR5cGU9ImIiLCBjb2w9ImJsdWUiKQ0KYXhpcygxLCBzZXEoMCwxMCwxKSkNCmxlZ2VuZCgncmlnaHQnLGxlZ2VuZD1jKCJzQkciLCAiZ2VvIiwgImFjdHVhbCIpLGNvbD1jKCJibGFjayIsInJlZCIsImJsdWUiKSwgcGNoPWMoMSwxKSkNCmBgYA0KDQpgYGB7ciBvdXQud2lkdGggPSAnMTAwJScsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQ0KU19wcmVkPC1jKDEsY3VtcHJvZChyX3ByZWQpKSAjIHByZWRpY3RlZCBzdXJ2aXZvciBmdW5jdGlvbg0KU19nZW88LXBfaGF0XihjKDAsdCkpDQoNCnQ8LXNlcSgwLDEwKQ0KcGFyKG1mcm93PWMoMSwxKSkNCnBsb3QodCxTX3ByZWQseWxhYj0icmV0ZW50aW9uIHJhdGUiLHhsYWI9InBlcmlvZCIsdHlwZT0iYiIsIHhheHQ9Im5vbmUiLCB5bGltID0gYygwLDEpKQ0KbGluZXModCxTX2dlbyx0eXBlPSJiIixjb2w9InJlZCIpDQpsaW5lcyh0LFMsIHR5cGU9ImIiLCBjb2w9ImJsdWUiKQ0KYXhpcygxLCBzZXEoMCwxMCwxKSkNCmxlZ2VuZCgncmlnaHQnLGxlZ2VuZD1jKCJzQkciLCAiZ2VvIiwgImFjdHVhbCIpLGNvbD1jKCJibGFjayIsInJlZCIsImJsdWUiKSwgcGNoPWMoMSwxKSkNCg0KDQpgYGANCg0KIyBDYWxjdWxhdGluZyBDTFYgd2l0aCB0aGUgc0JHDQoNCkluIHRoZSBnZW9tZXRyaWMgbW9kZWwsIHdlIGNvdWxkIHVzZSB0aGUgcHJvcGVydGllcyBvZiBhIGdlb21ldHJpYyBzZXJpZXMgdG8gZGVyaXZlIENMViBvdmVyIGFuIGluZmluaXRlIHRpbWUgaG9yaXpvbi4gV2l0aCB0aGUgc0JHLCB3ZSBoYXZlIG5vIHN1Y2ggY2xvc2VkIGZvcm0gZXhwcmVzc2lvbi4gSW5zdGVhZCwgd2UgY2FuIHNldCB0aGUgdGltZSBob3Jpem9uICRUJCBhdCBzb21lIGxhcmdlIG51bWJlciwgbGlrZSAkMjAwJCwgYW5kIHN1bSB1cCB0aGUgZmlyc3QgJFQkIHRlcm1zIG9mIHRoZSBDTFYgZXhwcmVzc2lvbi4gQmVjYXVzZSBvZiBkaXNjb3VudGluZyBhbmQgdGhlIGRpbWluaXNoaW5nIHN1cnZpdmFsIGZ1bmN0aW9uLCBlYWNoIGFkZGl0aW9uYWwgdGVybSBjb250cmlidXRlcyBsZXNzIHRvIENMVi4gU28gYWZ0ZXIgYSBzdWl0YWJseSBsYXJnZSAkVCQsIHdlIGNhbiBzYWZlbHkgaWdub3JlIHRoZSBsYXRlciB0ZXJtcy4NCg0KJCQNCkVbQ0xWXSA9IG0gKyBcZnJhY3ttIFw7IFMoMSl9eygxK2QpXjF9ICsgXGZyYWN7bSBcOyBTKDIpfXsoMStkKV4yfSArIFxmcmFje20gXDsgUygzKX17KDErZCleM30gKyBcY2RvdHMgKyBcZnJhY3ttIFw7IFMoVCl9eygxK2QpXlR9DQokJA0KDQpTaW5jZSB3ZSBoYXZlIHBhcmFtZXRlcnMgJGEkIGFuZCAkYiQsIHdlIGNhbiBwcm9qZWN0ICRyKHQpJCBhbmQgdGhlcmVmb3JlICRTKHQpJC4gVGhlIGNvZGUgYmVsb3cgZG9lcyB0aGlzLg0KDQpgYGB7cn0NCnQ8LXNlcSgxLDIwMCkgIyB0aW1lIHBlcmlvZHMNCnJfcHJlZCA8LSByX3NCRyhhLGIsdCkgIyBwcmVkaWN0ZWQgcmV0ZW50aW9uIHJhdGUNClNfcHJlZCA8LSBjKDEsY3VtcHJvZChyX3ByZWQpWzE6MTk5XSkgIyBwcmVkaWN0ZWQgc3Vydml2b3IgZnVuY3Rpb24NCg0KZGlzIDwtIDEvKDErZCleKHQtMSkgIyBkaXNjb3VudCBmYWN0b3IsIGZpcnN0IHRlcm0gaXMgcHJlc2VudCBzbyBubyBkaXNjb3VudGluZw0KDQpDTFZfc0JHPC1zdW0obSpTX3ByZWQqZGlzKSAjIHRoZSBzdW0gb2YgbWFyZ2luIHggc3Vydml2b3IgeCBkaXNjb3VudCBmYWN0b3INCg0KQ0xWX3NCRw0KYGBgDQoNCmBgYHtyfQ0KIyBjb21wYXJlIHdpdGggdGhlIGdlb21ldHJpYyBDTFYgbW9kZWwNCmdlbzwtZ2VvQ0xWKHBfaGF0LG0sZCkgDQpnZW8NCmBgYA0KDQpUaGUgQ0xWIHdlIGNhbGN1bGF0ZSB1c2luZyB0aGUgc0JHIG1vZGVsIGlzIDM2OCwgc2xpZ2h0bHkgaGlnaGVyIHRoYW4gd2hhdCB3ZSB3b3VsZCBwcmVkaWN0IHdpdGggdGhlIGdlb21ldHJpYyBtb2RlbCwgMzYxLg0KDQpXaGlsZSBpdCBkb2Vzbid0IHNlZW0gbGlrZSBtdWNoLCB0aGUgYmlnIGRpZmZlcmVuY2Ugd2lsbCBjb21lIHdoZW4gd2UgY2FsY3VsYXRlIFJMVi4gV2h5PyBCZWNhdXNlIHRoZSBnZW9tZXRyaWMgbW9kZWwgc2F5cyB0aGVyZSBpcyBubyBkaWZmZXJlbmNlIGJldHdlZW4gb2xkIGFuZCBuZXcgY3VzdG9tZXJzLiBUaGUgc0JHIG1vZGVsIHNheXMgdGhlcmUgaXMgYSBkaWZmZXJlbmNlLCBkdWUgdG8gaGV0ZXJvZ2VuZWl0eSBhbmQgc29ydGluZy4NCg0KIyBSTFYgd2l0aCB0aGUgc0JHDQoNCldlIGtub3cgZnJvbSBhYm92ZSB0aGF0IHRoZSBzQkcgbW9kZWwgbGVhZHMgdG8gY3VzdG9tZXJzIHdpdGggaGlnaGVyIGNodXJuIHByb2JhYmlsaXRpZXMgZHJvcHBpbmcgb3V0IGVhcmxpZXIsIHRoZSBzb3J0aW5nIG1lY2hhbmlzbSB3ZSBkZXNjcmliZWQgZWFybGllci4gSGVuY2UgYSBjdXN0b21lciB3aG8gaGFzIHJlbWFpbmVkIGZvciBzbyBtYW55IHBlcmlvZHMgd2lsbCBsaWtlbHkgaGF2ZSBhICoqbG93ZXIqKiBjaHVybiBwcm9iYWJpbGl0eSB0aGFuIGEgbmV3IGN1c3RvbWVyLiBJbiB0aGUgZ2VvbWV0cmljIG1vZGVsLCB0aGlzIG1ha2VzIG5vIGRpZmZlcmVuY2U6IGV2ZXJ5b25lIGhhcyB0aGUgc2FtZSAkXHRoZXRhJC4gQnV0IGluIHRoZSBzQkcgbW9kZWwsIGl0IGRvZXMgbWFrZSBhIGRpZmZlcmVuY2UgZHVlIHRvIGhldGVyb2dlbmVpdHkuDQoNCkNMViByZWZlcnMgdG8gbmV3IGN1c3RvbWVycywgYnV0IHdoYXQgYWJvdXQgdGhvc2Ugd2hvIHdlIGhhdmUgb2JzZXJ2ZWQgZm9yIHNvbWUgdGltZT8gKipSZXNpZHVhbCBsaWZldGltZSB2YWx1ZSAoUkxWKSoqIGlzIHRoZSB0ZXJtIGZvciBhbHJlYWR5IGFjcXVpcmVkIGN1c3RvbWVycywgYXMgb3Bwb3NlZCB0byBuZXcgY3VzdG9tZXJzLg0KDQpUaGUgY29uZGl0aW9uYWwgcHJvYmFiaWxpdHkgb2YgYSBjdXN0b21lciB3aG8gaGFzIHN1cnZpdmVkICRcdGF1JCBwZXJpb2RzIHN1cnZpdmluZyAkcz5cdGF1JCBwZXJpb2RzIGlzOiAkJA0KUChUPnMgfCBUID4gXHRhdSkgPSBcZnJhY3tTKHMpfXtTKFx0YXUpfSBccXVhZCBcdGV4dHJte3doZXJlfSBcOyBzID4gXHRhdQ0KJCQNCg0KRm9yIFJMViwgd2UgY2FsY3VsYXRlIHJpZ2h0IGJlZm9yZSB0aGUgcmVuZXdhbCBkZWNpc2lvbi4NCg0KTGV0J3MgdHJ5IHRoaXMgb3V0IGZvciBzb21lb25lIHdobyBoYXMgcmVuZXdlZCAkXHRhdSA9IDQkIHRpbWVzLiBXZSBhcmUgc3RhbmRpbmcganVzdCBiZWZvcmUgcGVyaW9kIDU6IGRvZXMgdGhpcyBwZXJzb24sIHdobyBoYXMgc3Vydml2ZWQgNCBwZXJpb2RzLCBzdXJ2aXZlIG9uY2UgbW9yZT8gVGhlIHByb2JhYmlsaXR5IHRoYXQgaGUvc2hlIGRvZXMgaXMgJFxmcmFje1MoNSl9e1MoNCl9JC4NCg0KYGBge3J9DQoNCnRhdSA8LSA0DQoNCnQgPC0gc2VxKDEsdGF1KzIwMCkNCnJfcHJlZCA8LSByX3NCRyhhLGIsdCkNClNfcHJlZCA8LSBjdW1wcm9kKHJfcHJlZCkNCg0KU19zaGlmdCA8LSBTX3ByZWRbKHRhdSsxKTpsZW5ndGgoU19wcmVkKV0gICMgc3Vydml2YWwgZnVuY3Rpb24gZnJvbSB0YXUgKyAxIHVudGlsIFQNCg0KZGlzPC0xLygxK2QpXih0KDE6MjAwKS0xKSAjIGRpc2NvdW50IHJhdGUNCg0KUkxWX3NCRzwtc3VtKG0qU19zaGlmdC9TX3ByZWRbdGF1XSpkaXMpICAjIHN1bSBvZiBtYXJnaW4geCBTKHRhdSArIHQpLyBTKHRhdSkgeCBkaXNjb3VudA0KDQpSTFZfc0JHDQpgYGANCg0KV2UgY2FsY3VsYXRlIHRoZSBSTFYgYXMgNjUwLCB3aGljaCBpcyBzdWJzdGFudGlhbGx5IGhpZ2hlciBDTFYgMzY4LCBiZWNhdXNlICoqUkxWIGlzIGZvciBhbHJlYWR5IGV4aXN0aW5nIGN1c3RvbWVycyB3aG8gYXJlIGxpa2VseSB0byBoYXZlIGxvd2VyIGNodXJuIHByb2JhYmlsaXRpZXMgdGhhbiBuZXcgY3VzdG9tZXJzKiouIFRoZSBpbXBsaWNhdGlvbiBpcyB0aGF0IGhldGVyb2dlbmVpdHkgYW5kIHNvcnRpbmcgYXJlIGltcG9ydGFudC4gQXMgdGltZSBwYXNzZXMsIHRoZSBjdXN0b21lciBiYXNlIHNocmlua3MgYW5kIHRob3NlIHdobyByZW1haW4gYXJlIGluY3JlYXNpbmdseSBsaWtlbHkgdG8gc3RheSBsb25nZXIuDQoNClRoZSBSTFYgZm9ybXVsYSBmb3IgZ2VvbWV0cmljIG1vZGVsIHJpZ2h0IGJlZm9yZSByZW5ld2luZyBpczogJCQNCkVbUkxWXT0gbSBwICArIFxmcmFje20gXDsgcF4yfXsoMStkKX0gICsgXGZyYWN7bSBcOyBwXjN9eygxK2QpXjJ9ICsgXGRvdHMgPSBcZnJhY3ttcCgxK2QpfXsxK2QtcH0NCiQkIFBsdWdnaW5nIHNvbWUgbnVtYmVycyBpbiwgd2Ugc2VlIHRoYXQgdGhlIFJMViBpcyBzdWJzdGFudGlhbGx5IGxvd2VyIGluIHRoZSBnZW9tZXRyaWMgbW9kZWwuDQoNCmBgYHtyfQ0KUkxWX2dlbzwtbSpwX2hhdCooMStkKS8oMStkLXBfaGF0KQ0KUkxWX2dlbw0KYGBgDQoNClRoZSBSTFYgdXNpbmcgdGhlIGdlb21ldHJpYyBtb2RlbCBpcyBgciByb3VuZChSTFZfZ2VvKWAsIHN1YnN0YW50aWFsbHkgbGVzcyB0aGFuIHRoYXQgb2YgdGhlIHNCRyBtb2RlbC4NCg==