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==