What is markov chain?
The mathmatical definition of Markov Chain is “a stochastic model describing a sequence of possible events in which the probability of each event depends only on the state attained in the previous event.”
Markov chain describes a probabilistic relationship between states. Here is a sample example:
Given we have two channels, a and b; and two possible outcomes, convert or churn. A series of probabilities are given as follows:
P(Customer start(first interact) with A channel): 0.2
P(Customer start(first interact) with B channel): 0.8
P(Customer interact with A|Given previously interact with B): 0.3
P(Customer interact with B|Given previously interact with A): 0.4
P(Customer Convert|Given previously interact with A): 0.3
P(Customer Churn|Given previously interact with A): 0.3
P(Customer Convert|Given previously interact with B): 0.25
P(Customer Churn|Given previously interact with B): 0.45
We can constract a matrix out from these relationship.
example_matrix = matrix(c(0,0,0,0,0,0.2,0,0.3,0,0,0.8,0.4,0,0,0,0,0.3,0.25,0,0,0,0.3,0.45,1,1),nrow = 5,ncol = 5)
colnames(example_matrix) <- c("start","a","b","convert","churn")
rownames(example_matrix) <- c("start","a","b","convert","churn")
print(example_matrix)
start a b convert churn
start 0 0.2 0.8 0.00 0.00
a 0 0.0 0.4 0.30 0.30
b 0 0.3 0.0 0.25 0.45
convert 0 0.0 0.0 0.00 1.00
churn 0 0.0 0.0 0.00 1.00
How do we construct a matrix like this? If you take a close look, you will find out that the matrix entry [x,y] annotates the probability of transiting from state x to state y, namingly, the probability of x occurs given y occurs.
This matrix is called transition matrix.
What problem does transition matrix solve?
Say if we are given the transition matrix, what is the probability of conversion given one interaction, given 2 interactions, given n interactions?
Let’s forget about the transition matrix and solve it using conditional probability.
Q: What is the probability of conversion given one interaction? A:since there is no direct connection between start to convert, the probability of conversion after one interaction will be 0.
Q:What is the probablity of conversion given two interaction? A:P(Customer start(first interact) with A channel) x P(Customer Convert|Given previously interact with A) + P(Customer start(first interact) with B channel) x P(Customer Convert|Given previously interact with B): 0.7
0.2 x 0.3 + 0.8 x 0.25 = 0.15 + 0.14 = 0.26
As you can see, it is still pretty managible to compute the first couple interactions, but it will get more cumbersome as number of interaction increases. Also if the number of channels increases, the complexity of computation will also increase. In short, this solution of not scalable.
Let’s look at the althernatives. (For you to understand this, a little bit of brushing off on simple linear algebra calculation may help you a long way:https://www.khanacademy.org/math/linear-algebra)
inital_state <- c(1,0,0,0,0)
inital_state%*%example_matrix
start a b convert churn
[1,] 0 0.2 0.8 0 0
Look at the convert column, We get the same result!
initial_state <- c(1,0,0,0,0)
initial_state%*%example_matrix%*%example_matrix
start a b convert churn
[1,] 0 0.24 0.08 0.26 0.42
Again, the same result is produced.
And there comes the trick when the number of iteration scales up.
logic <- TRUE
initial_state <- c(1,0,0,0,0)
new_state <- initial_state
iter = 0
conv.proba <- new_state[4]
precision <- 0.0001
while(logic) {
old_state <- new_state
new_state <- new_state%*%example_matrix
conv.proba <- conv.proba+new_state[4]
#As iteraction approach infinity, all customers will eventually migrate to "Churn". therefore it is safe to say that our program approaches equilibrium when probability for churn approaches 1 and other channels approachs 0.
logic <- c(new_state[1:4] < precision,(abs(new_state[5] - 1) < precision))
logic <- sum(logic==FALSE) > 0
iter <- iter + 1
}
print(paste("after",iter,"iterations","the probability of conversion is",round(conv.proba,2)))
[1] "after 11 iterations the probability of conversion is 0.4"
Scaling this program up will give us the probability of conversion for all numbers of interactions, which is the global conversion rate of this program. From the program we just ran, we know that the global conversion rate is 0.45, given there are two channels a,b, and the transition matrix.
Now say, we think that a is not performing and we decide to stop investment in channel a. but we’d like to gauge the impact of this decision before we implement it. What can we do?
We can simplely remove channel a from the transition matrix:
(Here I need to point out that this operation is based on premise that the remaining probabilities will be independent of channel a. which may not be the case in real life scenarios. It is possible that without channel a, a subset of customers who previously interact with channel a gets transmitted to channel b - You will see later in an example in which I used a different assumption).
example_matrix_b <- example_matrix[-2,-2]
Error: object 'example_matrix' not found
Let’s calculate the conversion rate when there is only channel b.
logic <- TRUE
initial_state <- c(1,0,0,0)
new_state <- initial_state
iter = 0
conv.proba.no.a <- new_state[3]
while(logic) {
old_state <- new_state
new_state <- new_state%*%example_matrix_b
conv.proba.no.a <- conv.proba.no.a+new_state[3]
logic <- c(new_state[1:3] < precision,(abs(new_state[4] - 1) < precision))
logic <- sum(logic==FALSE) > 0
iter <- iter + 1
}
print(paste("after",iter,"iterations","the probability of conversion is",round(conv.proba.no.a,2)))
[1] "after 3 iterations the probability of conversion is 0.2"
We think that channel a is not efficient but without channel a the conversion rate is reduced from 0.45 to 0.2! this means that channel a increases the probability of conversion by 200%! it will be a bad decision to scale down investment in channel a.
Let’s do the same thing to channel b.
example_matrix_a <- example_matrix[-3,-3]
print(example_matrix_a)
start a convert churn
start 0 0.2 0.0 0.0
a 0 0.0 0.3 0.3
convert 0 0.0 0.0 1.0
churn 0 0.0 0.0 1.0
example_matrix_a[1,4] <- 1 - sum(example_matrix_a[1,]) + example_matrix_a[1,4]
example_matrix_a[2,4] <- 1 - sum(example_matrix_a[2,]) + example_matrix_a[2,4]
example_matrix_a
start a convert churn
start 0 0.2 0.0 0.8
a 0 0.0 0.3 0.7
convert 0 0.0 0.0 1.0
churn 0 0.0 0.0 1.0
logic <- TRUE
initial_state <- c(1,0,0,0)
new_state <- initial_state
iter = 0
conv.proba.no.b <- new_state[3]
while(iter<1000) {
old_state <- new_state
new_state <- new_state%*%example_matrix_a
conv.proba.no.b <- conv.proba.no.b+new_state[3]
#logic <- 1-new_state[4] > 0.0000001 #As number of iteration approach infinity, the probablities in each state will be approaching 0. Here we set the cutting point at 0.0000001 instead of 0 to avoid program running too long or crashing the computer. Essentially I'm saying I will not care the probability loss as long as it is less 0.000001.
iter <- iter + 1
}
print(paste("after",iter,"iterations","the probability of conversion is",round(conv.proba.no.b,2)))
[1] "after 1000 iterations the probability of conversion is 0.06"
After removing channel b, the conversion rate is reduced from 0.4 to 0.06. Channel b increases the probability of conversion by 667%.
From here we can easily induce attribution model from the results we have got.
a.lift <- conv.proba/conv.proba.no.a
b.lift <- conv.proba/conv.proba.no.b
a.attr <- a.lift/(a.lift+b.lift)
b.attr <- b.lift/(a.lift+b.lift)
print(paste("attributed value of channel a:",round(a.attr,2)))
[1] "attributed value of channel a: 0.23"
print(paste("attributed value of channel b:",round(b.attr,2)))
[1] "attributed value of channel b: 0.77"
Production Code on Stimulated Results
Now having grasped how Markov Chain works, let’s apply this method on a stimuated dataset. Aslo I’m going to modularize the code by creating to functions, ConvRateEst and newTransitionMatrix to aid reproduction.
data <- read.csv("C:/Users/rinlin/Desktop/Attribution/Blogpost/attribution_raw.csv",stringsAsFactors = FALSE)
ConvRateEst <- function(TransitionMatrix) {
iter <- 0
logic <- TRUE
precision <- 0.0001
new_state <- matrix(rep(0,length(states(TransitionMatrix))), nrow = 1)
colnames(new_state) <- states(TransitionMatrix)
new_state[,"start"] <- 1
ConvRate <- new_state[,"converted"]
while(logic) {
new_state <- new_state*TransitionMatrix
ConvRate <- ConvRate + new_state[,"converted"]
logic <- ((abs(new_state[,"churned"] - 1) < precision))
logic <- sum(logic==FALSE) > 0
iter <- iter + 1
}
print(paste("after",iter,"iterations","the probability of conversion is",round(ConvRate,2)))
return(ConvRate)
}
newTransitionMatrix <- function(mcMatrix,removeChannel) {
newMatrix <- mcMatrix[-which(states(mcMatrix) == removeChannel),-which(states(mcMatrix) == removeChannel)]
for(state in rownames(newMatrix)) {
total <- sum(newMatrix[state,])
for(state.2 in rownames(newMatrix)) {
newMatrix[state,state.2] <- newMatrix[state,state.2]/total
}
#newMatrix[state,"churned"] <- 1- sum(newMatrix[state,]) + newMatrix[state,"churned"]
}
newMatrix <- new("markovchain",states = rownames(newMatrix),transitionMatrix = newMatrix,name = "attribution")
return(newMatrix)
}
data.1 <- data
data.1$status <- "start"
data.2 <- data
data.2$status <- "end"
df <- rbind(data.1,data.2)
temp <- df %>%
group_by(Path.ID,Conversion) %>%
summarise_at("Time.Stamp",funs(max)) %>%
ungroup()
end <- temp
end$Time.Stamp <- end$Time.Stamp + 1
end$Channel <- ifelse(end$Conversion == 1,"converted","churned")
end$status <- "end"
start <- temp
start$Time.Stamp <- 0
start$Channel <- "start"
start$status <- "start"
df <- rbind(df,start,end)
df <- df %>%
arrange(Path.ID,Time.Stamp,status) %>%
group_by(Path.ID) %>%
mutate(new.id = lag(Time.Stamp)) %>%
ungroup()
df[is.na(df$new.id),]$new.id <- 0
output <- df %>%
mutate(id = paste0(Path.ID,new.id)) %>%
select(id,status,Channel) %>%
tidyr::spread(status,Channel) %>%
group_by(start,end) %>%
summarise_at("id",funs(length)) %>%
ungroup() %>%
mutate(count = id) %>%
select(-id)
stop()
Error:
ConvRate <- ConvRateEst(mc.attr)
[1] "after 20 iterations the probability of conversion is 0.37"
ConRateNoSearch <- ConvRateEst(newTransitionMatrix(mc.attr,"Search"))
[1] "after 15 iterations the probability of conversion is 0.19"
ConRateNoDisplay <- ConvRateEst(newTransitionMatrix(mc.attr,"Display"))
[1] "after 13 iterations the probability of conversion is 0.45"
ConRateNoEmail <- ConvRateEst(newTransitionMatrix(mc.attr,"Email"))
[1] "after 17 iterations the probability of conversion is 0.37"
SearchLift <- ConvRate/ConRateNoSearch
DisplayLift <- ConvRate/ConRateNoDisplay
EmailLift <- ConvRate/ConRateNoEmail
data.frame(channel = c("Search","Display","Email"),attribution = c(round(SearchLift/(SearchLift + DisplayLift + EmailLift),2),round(DisplayLift/(SearchLift + DisplayLift + EmailLift),2),round(EmailLift/(SearchLift + DisplayLift + EmailLift),2)))
LS0tDQp0aXRsZTogIkFkdmFuY2VkIEF0dHJpYnV0aW9uOiBNYXJrb3YgQ2hhaW4iDQphdXRob3I6ICJEYXRhIE5pbmphIC0gUmluYSBMaW4iDQpkYXRlOiAiSmFudWFyeSAxNCwgMjAxOCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiNXaHkgcnVsZS1iYXNlZCBhdHRyaWJ1dGlvbiBpcyBub3QgZ29vZCBlbm91Z2g/DQoNCg0KQ29udGludWluZyBvdXIgZGlzY3Vzc2lvbiBhYm91dCBhdHRyaWJ1dGlvbiBtb2RlbGluZywgV2UndmUgYWxyZWFkeSBjb252ZXJlZCBiYXNpYyBydWxlLWJhc2VkIGF0dHJpYnV0aW9uLCB3aGljaCBjYW4gYnkgZWFzaWx5IHByb2R1Y2VkIGluIGV4Y2VsLiBPbmUgbWFqb3IgbGltaXRhdGlvbiBvZiBydWxlLWJhc2VkIGF0dHJpYnV0aW9uIGlzIHRoYXQgaXQgaWdub3JlcyBjdXN0b21lcnMgdGhhdCBkb24ndCBjb252ZXJ0LiBUaGlzIG1lYW5zIHRoYXQgd2UgdGhyb3cgb3V0IHRoZSBtYWpvcml0eSBvZiBkYXRhIGdpdmVuIHRoYXQgdGhlIHJhdGlvIG9mIG5vbi1jb252ZXJ0aW5nIHBhdGhzIHRlbmRzIHRvIGJlIGhpZ2hlciB0aGFuIHRoYXQgb2YgY29udmVydGluZyBwYXRocywgd2hpY2ggaW4gcmV0dXJuIGNhbiBwcm9kdWNlIGJpYXNlZCByZXN1bHQuDQoNCkluIG9yZGVyIHRvIHRha2UgYWR2YW50YWdlIG9mIG5vbi1jb252ZXJ0aW5nIHBhdGhzLCBUd28gYWR2YW5jZWQgYXR0cmlidXRpb24gbWV0aG9kb2xvZ2llcyBhcmUgaW50cm9kdWNlZC4gSSB3aWxsIGNvdmVyIE1hcmtvdiBDaGFpbiBpbiB0aGlzIHBvc3QsIGFuZCB0aGUgZGF0YS1taW5pbmcgYXBwcm9hY2ggaW4gdGhlIG5leHQgcG9zdC4gDQoNCiNXaGF0IGlzIG1hcmtvdiBjaGFpbj8gDQoNCg0KVGhlIG1hdGhtYXRpY2FsIGRlZmluaXRpb24gb2YgTWFya292IENoYWluIGlzICJhIHN0b2NoYXN0aWMgbW9kZWwgZGVzY3JpYmluZyBhIHNlcXVlbmNlIG9mIHBvc3NpYmxlIGV2ZW50cyBpbiB3aGljaCB0aGUgcHJvYmFiaWxpdHkgb2YgZWFjaCBldmVudCBkZXBlbmRzIG9ubHkgb24gdGhlIHN0YXRlIGF0dGFpbmVkIGluIHRoZSBwcmV2aW91cyBldmVudC4iDQoNCk1hcmtvdiBjaGFpbiBkZXNjcmliZXMgYSBwcm9iYWJpbGlzdGljIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHN0YXRlcy4gSGVyZSBpcyBhIHNhbXBsZSBleGFtcGxlOg0KDQpHaXZlbiB3ZSBoYXZlIHR3byBjaGFubmVscywgPGk+YTwvaT4gYW5kIDxpPmI8L2k+OyBhbmQgdHdvIHBvc3NpYmxlIG91dGNvbWVzLCA8aT5jb252ZXJ0PC9pPiBvciA8aT5jaHVybjwvaT4uIA0KQSBzZXJpZXMgb2YgcHJvYmFiaWxpdGllcyBhcmUgZ2l2ZW4gYXMgZm9sbG93czogDQoNCg0KUChDdXN0b21lciBzdGFydChmaXJzdCBpbnRlcmFjdCkgd2l0aCBBIGNoYW5uZWwpOiAwLjINCg0KUChDdXN0b21lciBzdGFydChmaXJzdCBpbnRlcmFjdCkgd2l0aCBCIGNoYW5uZWwpOiAwLjgNCg0KUChDdXN0b21lciBpbnRlcmFjdCB3aXRoIEF8R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEIpOiAwLjMNCg0KUChDdXN0b21lciBpbnRlcmFjdCB3aXRoIEJ8R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEEpOiAwLjQNCg0KUChDdXN0b21lciBDb252ZXJ0fEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBBKTogMC4zDQoNClAoQ3VzdG9tZXIgQ2h1cm58R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEEpOiAwLjMNCg0KUChDdXN0b21lciBDb252ZXJ0fEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBCKTogMC4yNQ0KDQpQKEN1c3RvbWVyIENodXJufEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBCKTogMC40NQ0KDQoNCg0KV2UgY2FuIGNvbnN0cmFjdCBhIG1hdHJpeCBvdXQgZnJvbSB0aGVzZSByZWxhdGlvbnNoaXAuDQoNCg0KYGBge3IgVHJhbnNpdGlvbiBNYXRyaXggRXhhbXBsZX0NCmV4YW1wbGVfbWF0cml4ID0gbWF0cml4KGMoMCwwLDAsMCwwLDAuMiwwLDAuMywwLDAsMC44LDAuNCwwLDAsMCwwLDAuMywwLjI1LDAsMCwwLDAuMywwLjQ1LDEsMSksbnJvdyA9IDUsbmNvbCA9IDUpDQpjb2xuYW1lcyhleGFtcGxlX21hdHJpeCkgPC0gYygic3RhcnQiLCJhIiwiYiIsImNvbnZlcnQiLCJjaHVybiIpDQpyb3duYW1lcyhleGFtcGxlX21hdHJpeCkgPC0gYygic3RhcnQiLCJhIiwiYiIsImNvbnZlcnQiLCJjaHVybiIpDQpwcmludChleGFtcGxlX21hdHJpeCkNCmBgYA0KDQoNCkhvdyBkbyB3ZSBjb25zdHJ1Y3QgYSBtYXRyaXggbGlrZSB0aGlzPyBJZiB5b3UgdGFrZSBhIGNsb3NlIGxvb2ssIHlvdSB3aWxsIGZpbmQgb3V0IHRoYXQgdGhlIG1hdHJpeCBlbnRyeSBbeCx5XSBhbm5vdGF0ZXMgdGhlIHByb2JhYmlsaXR5IG9mIHRyYW5zaXRpbmcgZnJvbSBzdGF0ZSB4IHRvIHN0YXRlIHksIG5hbWluZ2x5LCB0aGUgcHJvYmFiaWxpdHkgb2YgeCBvY2N1cnMgZ2l2ZW4geSBvY2N1cnMuIA0KDQpUaGlzIG1hdHJpeCBpcyBjYWxsZWQgPGI+dHJhbnNpdGlvbiBtYXRyaXg8L2I+LiANCg0KDQpXaGF0IHByb2JsZW0gZG9lcyB0cmFuc2l0aW9uIG1hdHJpeCBzb2x2ZT8gDQoNClNheSBpZiB3ZSBhcmUgZ2l2ZW4gdGhlIHRyYW5zaXRpb24gbWF0cml4LCB3aGF0IGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGdpdmVuIG9uZSBpbnRlcmFjdGlvbiwgZ2l2ZW4gMiBpbnRlcmFjdGlvbnMsIGdpdmVuIG4gaW50ZXJhY3Rpb25zPw0KDQpMZXQncyBmb3JnZXQgYWJvdXQgdGhlIHRyYW5zaXRpb24gbWF0cml4IGFuZCBzb2x2ZSBpdCB1c2luZyBjb25kaXRpb25hbCBwcm9iYWJpbGl0eS4NCg0KUTogV2hhdCBpcyB0aGUgcHJvYmFiaWxpdHkgb2YgY29udmVyc2lvbiBnaXZlbiBvbmUgaW50ZXJhY3Rpb24/DQpBOnNpbmNlIHRoZXJlIGlzIG5vIGRpcmVjdCBjb25uZWN0aW9uIGJldHdlZW4gc3RhcnQgdG8gY29udmVydCwgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gYWZ0ZXIgb25lIGludGVyYWN0aW9uIHdpbGwgYmUgMC4NCg0KUTpXaGF0IGlzIHRoZSBwcm9iYWJsaXR5IG9mIGNvbnZlcnNpb24gZ2l2ZW4gdHdvIGludGVyYWN0aW9uPw0KQTpQKEN1c3RvbWVyIHN0YXJ0KGZpcnN0IGludGVyYWN0KSB3aXRoIEEgY2hhbm5lbCkgeCBQKEN1c3RvbWVyIENvbnZlcnR8R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEEpICsgUChDdXN0b21lciBzdGFydChmaXJzdCBpbnRlcmFjdCkgd2l0aCBCIGNoYW5uZWwpIHggUChDdXN0b21lciBDb252ZXJ0fEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBCKTogMC43IA0KDQowLjIgeCAwLjMgKyAwLjggeCAwLjI1ID0gMC4xNSArIDAuMTQgPSAwLjI2DQoNCkFzIHlvdSBjYW4gc2VlLCBpdCBpcyBzdGlsbCBwcmV0dHkgbWFuYWdpYmxlIHRvIGNvbXB1dGUgdGhlIGZpcnN0IGNvdXBsZSBpbnRlcmFjdGlvbnMsIGJ1dCBpdCB3aWxsIGdldCBtb3JlIGN1bWJlcnNvbWUgYXMgbnVtYmVyIG9mIGludGVyYWN0aW9uIGluY3JlYXNlcy4gQWxzbyBpZiB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGluY3JlYXNlcywgdGhlIGNvbXBsZXhpdHkgb2YgY29tcHV0YXRpb24gd2lsbCBhbHNvIGluY3JlYXNlLiBJbiBzaG9ydCwgdGhpcyBzb2x1dGlvbiBvZiBub3Qgc2NhbGFibGUuDQoNCkxldCdzIGxvb2sgYXQgdGhlIGFsdGhlcm5hdGl2ZXMuIChGb3IgeW91IHRvIHVuZGVyc3RhbmQgdGhpcywgYSBsaXR0bGUgYml0IG9mIGJydXNoaW5nIG9mZiBvbiBzaW1wbGUgbGluZWFyIGFsZ2VicmEgY2FsY3VsYXRpb24gbWF5IGhlbHAgeW91IGEgbG9uZyB3YXk6aHR0cHM6Ly93d3cua2hhbmFjYWRlbXkub3JnL21hdGgvbGluZWFyLWFsZ2VicmEpDQoNCg0KYGBge3IgZmlyc3QgaW50ZXJhY3Rpb259DQppbml0YWxfc3RhdGUgPC0gYygxLDAsMCwwLDApDQppbml0YWxfc3RhdGUlKiVleGFtcGxlX21hdHJpeA0KYGBgDQoNCkxvb2sgYXQgdGhlIGNvbnZlcnQgY29sdW1uLCBXZSBnZXQgdGhlIHNhbWUgcmVzdWx0IQ0KDQoNCmBgYHtyIHNlY29uZCBpbnRlcmFjdGlvbn0NCmluaXRpYWxfc3RhdGUgPC0gYygxLDAsMCwwLDApDQppbml0aWFsX3N0YXRlJSolZXhhbXBsZV9tYXRyaXglKiVleGFtcGxlX21hdHJpeA0KYGBgDQoNCkFnYWluLCB0aGUgc2FtZSByZXN1bHQgaXMgcHJvZHVjZWQuDQoNCkFuZCB0aGVyZSBjb21lcyB0aGUgdHJpY2sgd2hlbiB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbiBzY2FsZXMgdXAuDQoNCmBgYHtyIG4gaW50ZXJhY3Rpb259DQpsb2dpYyA8LSBUUlVFDQppbml0aWFsX3N0YXRlIDwtIGMoMSwwLDAsMCwwKQ0KbmV3X3N0YXRlIDwtIGluaXRpYWxfc3RhdGUNCml0ZXIgPSAwDQpjb252LnByb2JhIDwtIG5ld19zdGF0ZVs0XQ0KcHJlY2lzaW9uIDwtIDAuMDAwMQ0KDQoNCndoaWxlKGxvZ2ljKSB7DQogIG9sZF9zdGF0ZSA8LSBuZXdfc3RhdGUNCiAgbmV3X3N0YXRlIDwtIG5ld19zdGF0ZSUqJWV4YW1wbGVfbWF0cml4DQogIGNvbnYucHJvYmEgPC0gY29udi5wcm9iYStuZXdfc3RhdGVbNF0NCiAgI0FzIGl0ZXJhY3Rpb24gYXBwcm9hY2ggaW5maW5pdHksIGFsbCBjdXN0b21lcnMgd2lsbCBldmVudHVhbGx5IG1pZ3JhdGUgdG8gIkNodXJuIi4gdGhlcmVmb3JlIGl0IGlzIHNhZmUgdG8gc2F5IHRoYXQgb3VyIHByb2dyYW0gYXBwcm9hY2hlcyBlcXVpbGlicml1bSB3aGVuIHByb2JhYmlsaXR5IGZvciBjaHVybiBhcHByb2FjaGVzIDEgYW5kIG90aGVyIGNoYW5uZWxzIGFwcHJvYWNocyAwLg0KICBsb2dpYyA8LSBjKG5ld19zdGF0ZVsxOjRdIDwgcHJlY2lzaW9uLChhYnMobmV3X3N0YXRlWzVdIC0gMSkgPCBwcmVjaXNpb24pKQ0KICBsb2dpYyA8LSBzdW0obG9naWM9PUZBTFNFKSA+IDANCiAgaXRlciA8LSBpdGVyICsgMQ0KfQ0KcHJpbnQocGFzdGUoImFmdGVyIixpdGVyLCJpdGVyYXRpb25zIiwidGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gaXMiLHJvdW5kKGNvbnYucHJvYmEsMikpKQ0KDQoNCmBgYA0KDQpTY2FsaW5nIHRoaXMgcHJvZ3JhbSB1cCB3aWxsIGdpdmUgdXMgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gZm9yIGFsbCBudW1iZXJzIG9mIGludGVyYWN0aW9ucywgd2hpY2ggaXMgdGhlIGdsb2JhbCBjb252ZXJzaW9uIHJhdGUgb2YgdGhpcyBwcm9ncmFtLiBGcm9tIHRoZSBwcm9ncmFtIHdlIGp1c3QgcmFuLCB3ZSBrbm93IHRoYXQgdGhlIGdsb2JhbCBjb252ZXJzaW9uIHJhdGUgaXMgMC40NSwgZ2l2ZW4gdGhlcmUgYXJlIHR3byBjaGFubmVscyBhLGIsIGFuZCB0aGUgdHJhbnNpdGlvbiBtYXRyaXguDQoNCg0KTm93IHNheSwgd2UgdGhpbmsgdGhhdCBhIGlzIG5vdCBwZXJmb3JtaW5nIGFuZCB3ZSBkZWNpZGUgdG8gc3RvcCBpbnZlc3RtZW50IGluIGNoYW5uZWwgYS4gYnV0IHdlJ2QgbGlrZSB0byBnYXVnZSB0aGUgaW1wYWN0IG9mIHRoaXMgZGVjaXNpb24gYmVmb3JlIHdlIGltcGxlbWVudCBpdC4gV2hhdCBjYW4gd2UgZG8/IA0KDQoNCldlIGNhbiBzaW1wbGVseSByZW1vdmUgY2hhbm5lbCBhIGZyb20gdGhlIHRyYW5zaXRpb24gbWF0cml4Og0KDQooSGVyZSBJIG5lZWQgdG8gcG9pbnQgb3V0IHRoYXQgdGhpcyBvcGVyYXRpb24gaXMgYmFzZWQgb24gcHJlbWlzZSB0aGF0IHRoZSByZW1haW5pbmcgcHJvYmFiaWxpdGllcyB3aWxsIGJlIGluZGVwZW5kZW50IG9mIGNoYW5uZWwgYS4gd2hpY2ggbWF5IG5vdCBiZSB0aGUgY2FzZSBpbiByZWFsIGxpZmUgc2NlbmFyaW9zLiBJdCBpcyBwb3NzaWJsZSB0aGF0IHdpdGhvdXQgY2hhbm5lbCBhLCBhIHN1YnNldCBvZiBjdXN0b21lcnMgd2hvIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBjaGFubmVsIGEgZ2V0cyB0cmFuc21pdHRlZCB0byBjaGFubmVsIGIgLSBZb3Ugd2lsbCBzZWUgbGF0ZXIgaW4gYW4gZXhhbXBsZSBpbiB3aGljaCBJIHVzZWQgYSBkaWZmZXJlbnQgYXNzdW1wdGlvbikuDQoNCg0KYGBge3IgRXhhbXBsZSBNYXRyaXg6IFdpdGhvdXQgYX0NCmV4YW1wbGVfbWF0cml4X2IgPC0gZXhhbXBsZV9tYXRyaXhbLTIsLTJdDQpwcmludChleGFtcGxlX21hdHJpeF9iKQ0KDQoNCiNwcmVtaXNlczphbGwgdXNlcnMgd2hvIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBhIGFsbCBjaHVybg0KZXhhbXBsZV9tYXRyaXhfYlsxLDRdIDwtIDEgLSBzdW0oZXhhbXBsZV9tYXRyaXhfYlsxLF0pICsgZXhhbXBsZV9tYXRyaXhfYlsxLDRdDQpleGFtcGxlX21hdHJpeF9iWzIsNF0gPC0gMSAtIHN1bShleGFtcGxlX21hdHJpeF9iWzIsXSkgKyBleGFtcGxlX21hdHJpeF9iWzIsNF0NCg0KDQpleGFtcGxlX21hdHJpeF9iDQpgYGANCg0KTGV0J3MgY2FsY3VsYXRlIHRoZSBjb252ZXJzaW9uIHJhdGUgd2hlbiB0aGVyZSBpcyBvbmx5IGNoYW5uZWwgYi4NCg0KDQpgYGB7ciBDb252ZXJzaW9uIFJhdGUgd2l0aG91dCBhfQ0KbG9naWMgPC0gVFJVRQ0KaW5pdGlhbF9zdGF0ZSA8LSBjKDEsMCwwLDApDQpuZXdfc3RhdGUgPC0gaW5pdGlhbF9zdGF0ZQ0KaXRlciA9IDANCmNvbnYucHJvYmEubm8uYSA8LSBuZXdfc3RhdGVbM10NCg0KDQp3aGlsZShsb2dpYykgew0KICBvbGRfc3RhdGUgPC0gbmV3X3N0YXRlDQogIG5ld19zdGF0ZSA8LSBuZXdfc3RhdGUlKiVleGFtcGxlX21hdHJpeF9iDQoNCiAgY29udi5wcm9iYS5uby5hIDwtIGNvbnYucHJvYmEubm8uYStuZXdfc3RhdGVbM10NCiAgbG9naWMgPC0gYyhuZXdfc3RhdGVbMTozXSA8IHByZWNpc2lvbiwoYWJzKG5ld19zdGF0ZVs0XSAtIDEpIDwgcHJlY2lzaW9uKSkNCiAgbG9naWMgPC0gc3VtKGxvZ2ljPT1GQUxTRSkgPiAwDQogIGl0ZXIgPC0gaXRlciArIDENCn0NCg0KcHJpbnQocGFzdGUoImFmdGVyIixpdGVyLCJpdGVyYXRpb25zIiwidGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gaXMiLHJvdW5kKGNvbnYucHJvYmEubm8uYSwyKSkpDQoNCmBgYA0KDQpXZSB0aGluayB0aGF0IGNoYW5uZWwgYSBpcyBub3QgZWZmaWNpZW50IGJ1dCB3aXRob3V0IGNoYW5uZWwgYSB0aGUgY29udmVyc2lvbiByYXRlIGlzIHJlZHVjZWQgZnJvbSAwLjQ1IHRvIDAuMiEgdGhpcyBtZWFucyB0aGF0IGNoYW5uZWwgYSBpbmNyZWFzZXMgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gYnkgMjAwJSEgaXQgd2lsbCBiZSBhIGJhZCBkZWNpc2lvbiB0byBzY2FsZSBkb3duIGludmVzdG1lbnQgaW4gY2hhbm5lbCBhLiANCg0KTGV0J3MgZG8gdGhlIHNhbWUgdGhpbmcgdG8gY2hhbm5lbCBiLg0KDQpgYGB7ciBFeGFtcGxlIE1hdHJpeDogV2l0aG91dCBifQ0KZXhhbXBsZV9tYXRyaXhfYSA8LSBleGFtcGxlX21hdHJpeFstMywtM10NCg0KZXhhbXBsZV9tYXRyaXhfYVsxLDRdIDwtIDEgLSBzdW0oZXhhbXBsZV9tYXRyaXhfYVsxLF0pICsgZXhhbXBsZV9tYXRyaXhfYVsxLDRdDQpleGFtcGxlX21hdHJpeF9hWzIsNF0gPC0gMSAtIHN1bShleGFtcGxlX21hdHJpeF9hWzIsXSkgKyBleGFtcGxlX21hdHJpeF9hWzIsNF0NCg0KYGBgDQoNCg0KYGBge3IgQ29udmVyc2lvbiBSYXRlIHdpdGhvdXQgYn0NCmxvZ2ljIDwtIFRSVUUNCmluaXRpYWxfc3RhdGUgPC0gYygxLDAsMCwwKQ0KbmV3X3N0YXRlIDwtIGluaXRpYWxfc3RhdGUNCml0ZXIgPSAwDQpjb252LnByb2JhLm5vLmIgPC0gbmV3X3N0YXRlWzNdDQoNCg0Kd2hpbGUoaXRlcjwxMDAwKSB7DQogIG9sZF9zdGF0ZSA8LSBuZXdfc3RhdGUNCiAgbmV3X3N0YXRlIDwtIG5ld19zdGF0ZSUqJWV4YW1wbGVfbWF0cml4X2ENCg0KICBjb252LnByb2JhLm5vLmIgPC0gY29udi5wcm9iYS5uby5iK25ld19zdGF0ZVszXQ0KICAjbG9naWMgPC0gMS1uZXdfc3RhdGVbNF0gPiAwLjAwMDAwMDEgI0FzIG51bWJlciBvZiBpdGVyYXRpb24gYXBwcm9hY2ggaW5maW5pdHksIHRoZSBwcm9iYWJsaXRpZXMgaW4gZWFjaCBzdGF0ZSB3aWxsIGJlIGFwcHJvYWNoaW5nIDAuIEhlcmUgd2Ugc2V0IHRoZSBjdXR0aW5nIHBvaW50IGF0IDAuMDAwMDAwMSBpbnN0ZWFkIG9mIDAgdG8gYXZvaWQgcHJvZ3JhbSBydW5uaW5nIHRvbyBsb25nIG9yIGNyYXNoaW5nIHRoZSBjb21wdXRlci4gRXNzZW50aWFsbHkgSSdtIHNheWluZyBJIHdpbGwgbm90IGNhcmUgdGhlIHByb2JhYmlsaXR5IGxvc3MgYXMgbG9uZyBhcyBpdCBpcyBsZXNzIDAuMDAwMDAxLg0KICBpdGVyIDwtIGl0ZXIgKyAxDQp9DQoNCnByaW50KHBhc3RlKCJhZnRlciIsaXRlciwiaXRlcmF0aW9ucyIsInRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGlzIixyb3VuZChjb252LnByb2JhLm5vLmIsMikpKQ0KYGBgDQoNCkFmdGVyIHJlbW92aW5nIGNoYW5uZWwgYiwgdGhlIGNvbnZlcnNpb24gcmF0ZSBpcyByZWR1Y2VkIGZyb20gMC40IHRvIDAuMDYuIENoYW5uZWwgYiBpbmNyZWFzZXMgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gYnkgNjY3JS4gDQoNCg0KRnJvbSBoZXJlIHdlIGNhbiBlYXNpbHkgaW5kdWNlIGF0dHJpYnV0aW9uIG1vZGVsIGZyb20gdGhlIHJlc3VsdHMgd2UgaGF2ZSBnb3QuDQoNCmBgYHtyIGF0dHJpYnV0aW9ufQ0KYS5saWZ0IDwtIGNvbnYucHJvYmEvY29udi5wcm9iYS5uby5hDQpiLmxpZnQgPC0gY29udi5wcm9iYS9jb252LnByb2JhLm5vLmINCmEuYXR0ciA8LSBhLmxpZnQvKGEubGlmdCtiLmxpZnQpDQpiLmF0dHIgPC0gYi5saWZ0LyhhLmxpZnQrYi5saWZ0KQ0KcHJpbnQocGFzdGUoImF0dHJpYnV0ZWQgdmFsdWUgb2YgY2hhbm5lbCBhOiIscm91bmQoYS5hdHRyLDIpKSkNCnByaW50KHBhc3RlKCJhdHRyaWJ1dGVkIHZhbHVlIG9mIGNoYW5uZWwgYjoiLHJvdW5kKGIuYXR0ciwyKSkpDQpgYGANCg0KDQoNCiNTaG9ydGN1dDogUiBwYWNrYWdlcw0KDQpJIGJ1aWx0IHRoaXMgY2FsY3VsYXRpb24gcHJldHR5IG11Y2ggZnJvbSBzY3JhdGNoIHdpdGggbWluaW11bSBkZXBlbmRlbmNpZXMgb24gZXhpc3RpbmcgUiBwYWNrYWdlcy4gVGhlIGludGVudGlvbiBvZiB0aGlzIGlzIHNvbGVseSBwZWRhZ29naWNhbCwgYXMgSSBiZWxpZXZlIG9ubHkgYnkgZG9pbmcgdGhlIGNhbGN1bGF0aW9uIHN0ZXAtYnktc3RlcCwgdGFraW5nIG5vIHNob3J0Y3V0IGNvdWxkIHdlIHRydWVseSBncmFzcCB0aGUgbWVjaGFuaXNtIG9mIGhvdyB0aGluZ3Mgd29yay4gQnV0IGl0IGlzIGEgbG9zcyBvZiBlZmZpY2llbmN5IGlmIHlvdSBkbyBub3QgbGV2ZXJhZ2UgZXhpc3RpbmcgcGFja2FnZXMgdGhhdCBjYW4gYWNvbXBsaXNoIHRoZSBzYW1lIHRhc2sgZmFzdGVyIGFuZCBiZXR0ZXIuIEFzIGZhciBhcyBJIGtub3cgdGhlcmUgYXJlIHR3byBwYWNrYWdlczogbWFya292Y2hhaW4oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL21hcmtvdmNoYWluL2luZGV4Lmh0bWwpIGFuZCBDaGFubmVsQXR0cmlidXRpb24oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0NoYW5uZWxBdHRyaWJ1dGlvbi9DaGFubmVsQXR0cmlidXRpb24ucGRmKS4gSSB3aWxsIGJyaWVmbHkgc2hvdyB5b3Ugd2FudCB0byB1c2UgbWFya292Y2hhaW4uIEFuZCBJIHdpbGwgdXNlIENoYW5uZWxBdHRyaWJ1dGlvbiBpbiBhIHNlcGVyYXRlIHBvc3QgZm9yIGNvbXBhcmFzaW9uIHB1cnBvc2VzLg0KDQpgYGB7ciBVc2luZyBtYXJrb3ZjaGFpbiBwYWNrYWdlfQ0KI0ltcG9ydCBsaWJyYXJ5IA0KbGlicmFyeShtYXJrb3ZjaGFpbikNCnN0YXRlcyA8LSBjKCJzdGFydCIsImEiLCJiIiwiY29udmVydCIsImNodXJuIikNCmV4YW1wbGUubWMgPC0gbmV3KCJtYXJrb3ZjaGFpbiIsIHN0YXRlcyA9IHN0YXRlcyx0cmFuc2l0aW9uTWF0cml4ID0gZXhhbXBsZV9tYXRyaXgsbmFtZSA9ICJhdHRyaWJ1dGlvbiIpDQpgYGANCg0KYGBge3IgcGxvdCBtYXJrb3YgY2hhaW59DQpwbG90KGV4YW1wbGUubWMpDQpgYGANCg0KYGBge3IgTWFya292IENoYWlufQ0KaW5pdGlhbFN0YXRlIDwtIGMoMSwwLDAsMCwwKQ0KYWZ0ZXIySXRlciA8LSBpbml0aWFsU3RhdGUgKiAoZXhhbXBsZS5tYyAqIGV4YW1wbGUubWMpDQpwcmludChhZnRlcjJJdGVyKQ0KYWZ0ZXI3SXRlciA8LSBpbml0aWFsU3RhdGUgKiAoZXhhbXBsZS5tYyBeIDcpDQpwcmludChhZnRlcjdJdGVyKQ0KYGBgDQoNCg0KI1Byb2R1Y3Rpb24gQ29kZSBvbiBTdGltdWxhdGVkIFJlc3VsdHMNCg0KTm93IGhhdmluZyBncmFzcGVkIGhvdyBNYXJrb3YgQ2hhaW4gd29ya3MsIGxldCdzIGFwcGx5IHRoaXMgbWV0aG9kIG9uIGEgc3RpbXVhdGVkIGRhdGFzZXQuIEFzbG8gSSdtIGdvaW5nIHRvIG1vZHVsYXJpemUgdGhlIGNvZGUgYnkgY3JlYXRpbmcgdG8gZnVuY3Rpb25zLCA8Yj5Db252UmF0ZUVzdDwvYj4gPGI+YW5kIG5ld1RyYW5zaXRpb25NYXRyaXg8L2I+IHRvIGFpZCByZXByb2R1Y3Rpb24uDQoNCg0KDQpgYGB7ciBSZWFkIGluIHN0aW11bGF0ZWQgZGF0YX0NCmRhdGEgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3Jpbmxpbi9EZXNrdG9wL0F0dHJpYnV0aW9uL0Jsb2dwb3N0L2F0dHJpYnV0aW9uX3Jhdy5jc3YiLHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCmBgYA0KDQoNCmBgYHtyIFVzZXIgRGVmaW5lZCBGdW5jdGlvbn0NCkNvbnZSYXRlRXN0IDwtIGZ1bmN0aW9uKFRyYW5zaXRpb25NYXRyaXgpIHsNCiAgaXRlciA8LSAwDQogIGxvZ2ljIDwtIFRSVUUNCiAgcHJlY2lzaW9uIDwtIDAuMDAwMQ0KICANCiAgbmV3X3N0YXRlIDwtIG1hdHJpeChyZXAoMCxsZW5ndGgoc3RhdGVzKFRyYW5zaXRpb25NYXRyaXgpKSksIG5yb3cgPSAxKQ0KICBjb2xuYW1lcyhuZXdfc3RhdGUpIDwtIHN0YXRlcyhUcmFuc2l0aW9uTWF0cml4KQ0KICBuZXdfc3RhdGVbLCJzdGFydCJdIDwtIDENCiAgDQogIENvbnZSYXRlIDwtIG5ld19zdGF0ZVssImNvbnZlcnRlZCJdDQogIA0KICANCiAgDQogIHdoaWxlKGxvZ2ljKSB7DQogICAgDQogICAgbmV3X3N0YXRlIDwtIG5ld19zdGF0ZSpUcmFuc2l0aW9uTWF0cml4DQogICAgQ29udlJhdGUgPC0gQ29udlJhdGUgKyBuZXdfc3RhdGVbLCJjb252ZXJ0ZWQiXQ0KICAgIGxvZ2ljIDwtICgoYWJzKG5ld19zdGF0ZVssImNodXJuZWQiXSAtIDEpIDwgcHJlY2lzaW9uKSkNCiAgICBsb2dpYyA8LSBzdW0obG9naWM9PUZBTFNFKSA+IDANCiAgICBpdGVyIDwtIGl0ZXIgKyAxDQogIH0NCiAgDQogIHByaW50KHBhc3RlKCJhZnRlciIsaXRlciwiaXRlcmF0aW9ucyIsInRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGlzIixyb3VuZChDb252UmF0ZSwyKSkpDQogIHJldHVybihDb252UmF0ZSkNCiAgDQp9DQoNCg0KbmV3VHJhbnNpdGlvbk1hdHJpeCA8LSBmdW5jdGlvbihtY01hdHJpeCxyZW1vdmVDaGFubmVsKSB7DQogIG5ld01hdHJpeCA8LSBtY01hdHJpeFstd2hpY2goc3RhdGVzKG1jTWF0cml4KSA9PSByZW1vdmVDaGFubmVsKSwtd2hpY2goc3RhdGVzKG1jTWF0cml4KSA9PSByZW1vdmVDaGFubmVsKV0NCiAgZm9yKHN0YXRlIGluIHJvd25hbWVzKG5ld01hdHJpeCkpIHsNCiAgICB0b3RhbCA8LSBzdW0obmV3TWF0cml4W3N0YXRlLF0pDQogICAgZm9yKHN0YXRlLjIgaW4gcm93bmFtZXMobmV3TWF0cml4KSkgew0KICAgICAgbmV3TWF0cml4W3N0YXRlLHN0YXRlLjJdIDwtIG5ld01hdHJpeFtzdGF0ZSxzdGF0ZS4yXS90b3RhbCANCiAgICB9DQogICAgI25ld01hdHJpeFtzdGF0ZSwiY2h1cm5lZCJdIDwtIDEtIHN1bShuZXdNYXRyaXhbc3RhdGUsXSkgKyBuZXdNYXRyaXhbc3RhdGUsImNodXJuZWQiXQ0KICB9DQogIG5ld01hdHJpeCA8LSBuZXcoIm1hcmtvdmNoYWluIixzdGF0ZXMgPSByb3duYW1lcyhuZXdNYXRyaXgpLHRyYW5zaXRpb25NYXRyaXggPSBuZXdNYXRyaXgsbmFtZSA9ICJhdHRyaWJ1dGlvbiIpDQogIHJldHVybihuZXdNYXRyaXgpDQp9DQoNCg0KYGBgDQoNCg0KDQpgYGB7ciBEYXRhIFByZXByb2Nlc3Npbmd9DQpkYXRhLjEgPC0gZGF0YQ0KZGF0YS4xJHN0YXR1cyA8LSAic3RhcnQiDQoNCmRhdGEuMiA8LSBkYXRhDQpkYXRhLjIkc3RhdHVzIDwtICJlbmQiDQoNCg0KZGYgPC0gcmJpbmQoZGF0YS4xLGRhdGEuMikNCg0KdGVtcCA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoUGF0aC5JRCxDb252ZXJzaW9uKSAlPiUNCiAgc3VtbWFyaXNlX2F0KCJUaW1lLlN0YW1wIixmdW5zKG1heCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KZW5kIDwtIHRlbXAgDQplbmQkVGltZS5TdGFtcCA8LSBlbmQkVGltZS5TdGFtcCArIDENCmVuZCRDaGFubmVsIDwtIGlmZWxzZShlbmQkQ29udmVyc2lvbiA9PSAxLCJjb252ZXJ0ZWQiLCJjaHVybmVkIikNCmVuZCRzdGF0dXMgPC0gImVuZCINCg0Kc3RhcnQgPC0gdGVtcA0Kc3RhcnQkVGltZS5TdGFtcCA8LSAwDQpzdGFydCRDaGFubmVsIDwtICJzdGFydCINCnN0YXJ0JHN0YXR1cyA8LSAic3RhcnQiDQoNCmRmIDwtIHJiaW5kKGRmLHN0YXJ0LGVuZCkNCg0KZGYgPC0gZGYgJT4lDQogIGFycmFuZ2UoUGF0aC5JRCxUaW1lLlN0YW1wLHN0YXR1cykgJT4lDQogIGdyb3VwX2J5KFBhdGguSUQpICU+JQ0KICBtdXRhdGUobmV3LmlkID0gbGFnKFRpbWUuU3RhbXApKSAlPiUNCiAgdW5ncm91cCgpIA0KDQpkZltpcy5uYShkZiRuZXcuaWQpLF0kbmV3LmlkIDwtIDANCg0KDQpvdXRwdXQgPC0gZGYgJT4lDQogIG11dGF0ZShpZCA9IHBhc3RlMChQYXRoLklELG5ldy5pZCkpICU+JQ0KICBzZWxlY3QoaWQsc3RhdHVzLENoYW5uZWwpICU+JQ0KICB0aWR5cjo6c3ByZWFkKHN0YXR1cyxDaGFubmVsKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnQsZW5kKSAlPiUNCiAgc3VtbWFyaXNlX2F0KCJpZCIsZnVucyhsZW5ndGgpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUoY291bnQgPSBpZCkgJT4lDQogIHNlbGVjdCgtaWQpIA0KDQoNCm91dHB1dCA8LSBvdXRwdXQgJT4lDQogIGxlZnRfam9pbihvdXRwdXQgJT4lDQogICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0KSAlPiUNCiAgICAgICAgICAgICAgc3VtbWFyaXNlX2F0KCJjb3VudCIsZnVucyhzdW0pKSAlPiUNCiAgICAgICAgICAgICAgdW5ncm91cCgpICU+JQ0KICAgICAgICAgICAgICBtdXRhdGUodG90YWwuY291bnQgPSBjb3VudCkgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtY291bnQpLGJ5ID0gInN0YXJ0IikgJT4lDQogIG11dGF0ZShwcm9iYSA9IGNvdW50L3RvdGFsLmNvdW50KSAjJT4lDQogICNzZWxlY3Qoc3RhcnQsZW5kLHByb2JhKQ0KDQpybShkYXRhLjEpDQpybShkYXRhLjIpDQpybShkZikNCnJtKGVuZCkNCnJtKHN0YXJ0KQ0Kcm0odGVtcCkNCg0KDQoNCnN0YXRlcyA8LSBzb3J0KHVuaXF1ZShjKG91dHB1dCRzdGFydCxvdXRwdXQkZW5kKSkpDQpvdXRwdXQgPC0gZGF0YS5mcmFtZShzdGFydCA9IHNvcnQocmVwKHN0YXRlcyxsZW5ndGgoc3RhdGVzKSkpLCBlbmQgPSByZXAoc3RhdGVzLGxlbmd0aChzdGF0ZXMpKSxzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICU+JQ0KICBsZWZ0X2pvaW4ob3V0cHV0LGJ5ID0gYygic3RhcnQiLCJlbmQiKSkNCg0Kb3V0cHV0W2lzLm5hKG91dHB1dCRwcm9iYSksXSRwcm9iYSA8LSAwDQoNCg0KbWF0cml4IDwtIG1hdHJpeChvdXRwdXQkcHJvYmEsYnlyb3cgPSBUUlVFLG5yb3cgPSA2KQ0KbWF0cml4WzEsMV0gPC0gMQ0KbWF0cml4WzIsMV0gPC0gMQ0KbWMuYXR0cjwtbmV3KCJtYXJrb3ZjaGFpbiIsIHN0YXRlcyA9IHN0YXRlcyx0cmFuc2l0aW9uTWF0cml4ID0gbWF0cml4LG5hbWUgPSAiYXR0cmlidXRpb24iKQ0KcHJpbnQobWMuYXR0cikNCg0KYGBgDQoNCg0KYGBge3IgQ29udmVyc2lvbiBSYXRlfQ0KDQpDb252UmF0ZSA8LSBDb252UmF0ZUVzdChtYy5hdHRyKQ0KQ29uUmF0ZU5vU2VhcmNoIDwtIENvbnZSYXRlRXN0KG5ld1RyYW5zaXRpb25NYXRyaXgobWMuYXR0ciwiU2VhcmNoIikpDQpDb25SYXRlTm9EaXNwbGF5IDwtIENvbnZSYXRlRXN0KG5ld1RyYW5zaXRpb25NYXRyaXgobWMuYXR0ciwiRGlzcGxheSIpKQ0KQ29uUmF0ZU5vRW1haWwgPC0gQ29udlJhdGVFc3QobmV3VHJhbnNpdGlvbk1hdHJpeChtYy5hdHRyLCJFbWFpbCIpKQ0KDQoNCg0KDQpgYGANCg0KYGBge3IgQXR0cmlidXRpb259DQpTZWFyY2hMaWZ0IDwtIENvbnZSYXRlL0NvblJhdGVOb1NlYXJjaA0KRGlzcGxheUxpZnQgPC0gQ29udlJhdGUvQ29uUmF0ZU5vRGlzcGxheQ0KRW1haWxMaWZ0IDwtIENvbnZSYXRlL0NvblJhdGVOb0VtYWlsDQoNCmRhdGEuZnJhbWUoY2hhbm5lbCA9IGMoIlNlYXJjaCIsIkRpc3BsYXkiLCJFbWFpbCIpLGF0dHJpYnV0aW9uID0gYyhyb3VuZChTZWFyY2hMaWZ0LyhTZWFyY2hMaWZ0ICsgRGlzcGxheUxpZnQgKyBFbWFpbExpZnQpLDIpLHJvdW5kKERpc3BsYXlMaWZ0LyhTZWFyY2hMaWZ0ICsgRGlzcGxheUxpZnQgKyBFbWFpbExpZnQpLDIpLHJvdW5kKEVtYWlsTGlmdC8oU2VhcmNoTGlmdCArIERpc3BsYXlMaWZ0ICsgRW1haWxMaWZ0KSwyKSkpDQpgYGANCg0KDQojT3RoZXIgdXNlIGNhc2VzIG9uIE1hcmtvdiBDaGFpbg0KDQpBcyB5b3UgbWlnaHQgaGF2ZSBhbHJlYWR5IGZvdW5kIG91dCwgb2Z0ZW4gdGltZSBvbmUgc3RhdGlzdGljYWwgbWV0aG9kcyBjYW4gYmUgYXBwbGllZCB0byBtdWx0aXBsZSBkaXNjaXBsaW5lcy5Bbm90aGVyIHVzZSBjYXNlIG9mIE1hcmtvdiBDaGFpbiBpbiBtYXJrZXRpbmcgaXMgaW4gQ1JNIGFuYWx5dGljcyhXaGljaCBJIHdpbGwgYmUgdGFsa2luZyBhYm91dCBpbiBhIGRpZmZlcmVudCBwb3N0IG9uIG1pZ3JhdGlvbiBtb2RlbCkuIEl0IGlzIGFsc28gcG9zc2libGUgdG8gYXBwbHkgbWFya292IGNoYWluIGludG8gb3RoZXIgZmllbGRzIGFzIGxvbmcgYXMgdHJhbnNpdGlvbnMgb2Ygc3RhdGVzIGV4aXN0LiBDYW4geW91IHRoaW5rIG9mIGFueSBvdGhlciBzY2VuYXJpb3Mgd2hlcmUgTWFya292IENoYWluIGNhbiBiZSBhcHBsaWVkPyANCg0KIA==