Why rule-based attribution is not good enough?

Continuing our discussion about attribution modeling, We’ve already convered basic rule-based attribution, which can by easily produced in excel. One major limitation of rule-based attribution is that it ignores customers that don’t convert. This means that we throw out the majority of data given that the ratio of non-converting paths tends to be higher than that of converting paths, which in return can produce biased result.

In order to take advantage of non-converting paths, Two advanced attribution methodologies are introduced. I will cover Markov Chain in this post, and the data-mining approach in the next post.

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"

Shortcut: R packages

I built this calculation pretty much from scratch with minimum dependencies on existing R packages. The intention of this is solely pedagogical, as I believe only by doing the calculation step-by-step, taking no shortcut could we truely grasp the mechanism of how things work. But it is a loss of efficiency if you do not leverage existing packages that can acomplish the same task faster and better. As far as I know there are two packages: markovchain(https://cran.r-project.org/web/packages/markovchain/index.html) and ChannelAttribution(https://cran.r-project.org/web/packages/ChannelAttribution/ChannelAttribution.pdf). I will briefly show you want to use markovchain. And I will use ChannelAttribution in a seperate post for comparasion purposes.

#Import library 
library(markovchain)
states <- c("start","a","b","convert","churn")
example.mc <- new("markovchain", states = states,transitionMatrix = example_matrix,name = "attribution")
plot(example.mc)

initialState <- c(1,0,0,0,0)
after2Iter <- initialState * (example.mc * example.mc)
print(after2Iter)
     start    a    b convert churn
[1,]     0 0.24 0.08    0.26  0.42
after7Iter <- initialState * (example.mc ^ 7)
print(after7Iter)
     start         a         b   convert     churn
[1,]     0 0.0003456 0.0013824 0.0013248 0.9969472

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

Other use cases on Markov Chain

As you might have already found out, often time one statistical methods can be applied to multiple disciplines.Another use case of Markov Chain in marketing is in CRM analytics(Which I will be talking about in a different post on migration model). It is also possible to apply markov chain into other fields as long as transitions of states exist. Can you think of any other scenarios where Markov Chain can be applied?

LS0tDQp0aXRsZTogIkFkdmFuY2VkIEF0dHJpYnV0aW9uOiBNYXJrb3YgQ2hhaW4iDQphdXRob3I6ICJEYXRhIE5pbmphIC0gUmluYSBMaW4iDQpkYXRlOiAiSmFudWFyeSAxNCwgMjAxOCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiNXaHkgcnVsZS1iYXNlZCBhdHRyaWJ1dGlvbiBpcyBub3QgZ29vZCBlbm91Z2g/DQoNCg0KQ29udGludWluZyBvdXIgZGlzY3Vzc2lvbiBhYm91dCBhdHRyaWJ1dGlvbiBtb2RlbGluZywgV2UndmUgYWxyZWFkeSBjb252ZXJlZCBiYXNpYyBydWxlLWJhc2VkIGF0dHJpYnV0aW9uLCB3aGljaCBjYW4gYnkgZWFzaWx5IHByb2R1Y2VkIGluIGV4Y2VsLiBPbmUgbWFqb3IgbGltaXRhdGlvbiBvZiBydWxlLWJhc2VkIGF0dHJpYnV0aW9uIGlzIHRoYXQgaXQgaWdub3JlcyBjdXN0b21lcnMgdGhhdCBkb24ndCBjb252ZXJ0LiBUaGlzIG1lYW5zIHRoYXQgd2UgdGhyb3cgb3V0IHRoZSBtYWpvcml0eSBvZiBkYXRhIGdpdmVuIHRoYXQgdGhlIHJhdGlvIG9mIG5vbi1jb252ZXJ0aW5nIHBhdGhzIHRlbmRzIHRvIGJlIGhpZ2hlciB0aGFuIHRoYXQgb2YgY29udmVydGluZyBwYXRocywgd2hpY2ggaW4gcmV0dXJuIGNhbiBwcm9kdWNlIGJpYXNlZCByZXN1bHQuDQoNCkluIG9yZGVyIHRvIHRha2UgYWR2YW50YWdlIG9mIG5vbi1jb252ZXJ0aW5nIHBhdGhzLCBUd28gYWR2YW5jZWQgYXR0cmlidXRpb24gbWV0aG9kb2xvZ2llcyBhcmUgaW50cm9kdWNlZC4gSSB3aWxsIGNvdmVyIE1hcmtvdiBDaGFpbiBpbiB0aGlzIHBvc3QsIGFuZCB0aGUgZGF0YS1taW5pbmcgYXBwcm9hY2ggaW4gdGhlIG5leHQgcG9zdC4gDQoNCiNXaGF0IGlzIG1hcmtvdiBjaGFpbj8gDQoNCg0KVGhlIG1hdGhtYXRpY2FsIGRlZmluaXRpb24gb2YgTWFya292IENoYWluIGlzICJhIHN0b2NoYXN0aWMgbW9kZWwgZGVzY3JpYmluZyBhIHNlcXVlbmNlIG9mIHBvc3NpYmxlIGV2ZW50cyBpbiB3aGljaCB0aGUgcHJvYmFiaWxpdHkgb2YgZWFjaCBldmVudCBkZXBlbmRzIG9ubHkgb24gdGhlIHN0YXRlIGF0dGFpbmVkIGluIHRoZSBwcmV2aW91cyBldmVudC4iDQoNCk1hcmtvdiBjaGFpbiBkZXNjcmliZXMgYSBwcm9iYWJpbGlzdGljIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHN0YXRlcy4gSGVyZSBpcyBhIHNhbXBsZSBleGFtcGxlOg0KDQpHaXZlbiB3ZSBoYXZlIHR3byBjaGFubmVscywgPGk+YTwvaT4gYW5kIDxpPmI8L2k+OyBhbmQgdHdvIHBvc3NpYmxlIG91dGNvbWVzLCA8aT5jb252ZXJ0PC9pPiBvciA8aT5jaHVybjwvaT4uIA0KQSBzZXJpZXMgb2YgcHJvYmFiaWxpdGllcyBhcmUgZ2l2ZW4gYXMgZm9sbG93czogDQoNCg0KUChDdXN0b21lciBzdGFydChmaXJzdCBpbnRlcmFjdCkgd2l0aCBBIGNoYW5uZWwpOiAwLjINCg0KUChDdXN0b21lciBzdGFydChmaXJzdCBpbnRlcmFjdCkgd2l0aCBCIGNoYW5uZWwpOiAwLjgNCg0KUChDdXN0b21lciBpbnRlcmFjdCB3aXRoIEF8R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEIpOiAwLjMNCg0KUChDdXN0b21lciBpbnRlcmFjdCB3aXRoIEJ8R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEEpOiAwLjQNCg0KUChDdXN0b21lciBDb252ZXJ0fEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBBKTogMC4zDQoNClAoQ3VzdG9tZXIgQ2h1cm58R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEEpOiAwLjMNCg0KUChDdXN0b21lciBDb252ZXJ0fEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBCKTogMC4yNQ0KDQpQKEN1c3RvbWVyIENodXJufEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBCKTogMC40NQ0KDQoNCg0KV2UgY2FuIGNvbnN0cmFjdCBhIG1hdHJpeCBvdXQgZnJvbSB0aGVzZSByZWxhdGlvbnNoaXAuDQoNCg0KYGBge3IgVHJhbnNpdGlvbiBNYXRyaXggRXhhbXBsZX0NCmV4YW1wbGVfbWF0cml4ID0gbWF0cml4KGMoMCwwLDAsMCwwLDAuMiwwLDAuMywwLDAsMC44LDAuNCwwLDAsMCwwLDAuMywwLjI1LDAsMCwwLDAuMywwLjQ1LDEsMSksbnJvdyA9IDUsbmNvbCA9IDUpDQpjb2xuYW1lcyhleGFtcGxlX21hdHJpeCkgPC0gYygic3RhcnQiLCJhIiwiYiIsImNvbnZlcnQiLCJjaHVybiIpDQpyb3duYW1lcyhleGFtcGxlX21hdHJpeCkgPC0gYygic3RhcnQiLCJhIiwiYiIsImNvbnZlcnQiLCJjaHVybiIpDQpwcmludChleGFtcGxlX21hdHJpeCkNCmBgYA0KDQoNCkhvdyBkbyB3ZSBjb25zdHJ1Y3QgYSBtYXRyaXggbGlrZSB0aGlzPyBJZiB5b3UgdGFrZSBhIGNsb3NlIGxvb2ssIHlvdSB3aWxsIGZpbmQgb3V0IHRoYXQgdGhlIG1hdHJpeCBlbnRyeSBbeCx5XSBhbm5vdGF0ZXMgdGhlIHByb2JhYmlsaXR5IG9mIHRyYW5zaXRpbmcgZnJvbSBzdGF0ZSB4IHRvIHN0YXRlIHksIG5hbWluZ2x5LCB0aGUgcHJvYmFiaWxpdHkgb2YgeCBvY2N1cnMgZ2l2ZW4geSBvY2N1cnMuIA0KDQpUaGlzIG1hdHJpeCBpcyBjYWxsZWQgPGI+dHJhbnNpdGlvbiBtYXRyaXg8L2I+LiANCg0KDQpXaGF0IHByb2JsZW0gZG9lcyB0cmFuc2l0aW9uIG1hdHJpeCBzb2x2ZT8gDQoNClNheSBpZiB3ZSBhcmUgZ2l2ZW4gdGhlIHRyYW5zaXRpb24gbWF0cml4LCB3aGF0IGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGdpdmVuIG9uZSBpbnRlcmFjdGlvbiwgZ2l2ZW4gMiBpbnRlcmFjdGlvbnMsIGdpdmVuIG4gaW50ZXJhY3Rpb25zPw0KDQpMZXQncyBmb3JnZXQgYWJvdXQgdGhlIHRyYW5zaXRpb24gbWF0cml4IGFuZCBzb2x2ZSBpdCB1c2luZyBjb25kaXRpb25hbCBwcm9iYWJpbGl0eS4NCg0KUTogV2hhdCBpcyB0aGUgcHJvYmFiaWxpdHkgb2YgY29udmVyc2lvbiBnaXZlbiBvbmUgaW50ZXJhY3Rpb24/DQpBOnNpbmNlIHRoZXJlIGlzIG5vIGRpcmVjdCBjb25uZWN0aW9uIGJldHdlZW4gc3RhcnQgdG8gY29udmVydCwgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gYWZ0ZXIgb25lIGludGVyYWN0aW9uIHdpbGwgYmUgMC4NCg0KUTpXaGF0IGlzIHRoZSBwcm9iYWJsaXR5IG9mIGNvbnZlcnNpb24gZ2l2ZW4gdHdvIGludGVyYWN0aW9uPw0KQTpQKEN1c3RvbWVyIHN0YXJ0KGZpcnN0IGludGVyYWN0KSB3aXRoIEEgY2hhbm5lbCkgeCBQKEN1c3RvbWVyIENvbnZlcnR8R2l2ZW4gcHJldmlvdXNseSBpbnRlcmFjdCB3aXRoIEEpICsgUChDdXN0b21lciBzdGFydChmaXJzdCBpbnRlcmFjdCkgd2l0aCBCIGNoYW5uZWwpIHggUChDdXN0b21lciBDb252ZXJ0fEdpdmVuIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBCKTogMC43IA0KDQowLjIgeCAwLjMgKyAwLjggeCAwLjI1ID0gMC4xNSArIDAuMTQgPSAwLjI2DQoNCkFzIHlvdSBjYW4gc2VlLCBpdCBpcyBzdGlsbCBwcmV0dHkgbWFuYWdpYmxlIHRvIGNvbXB1dGUgdGhlIGZpcnN0IGNvdXBsZSBpbnRlcmFjdGlvbnMsIGJ1dCBpdCB3aWxsIGdldCBtb3JlIGN1bWJlcnNvbWUgYXMgbnVtYmVyIG9mIGludGVyYWN0aW9uIGluY3JlYXNlcy4gQWxzbyBpZiB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGluY3JlYXNlcywgdGhlIGNvbXBsZXhpdHkgb2YgY29tcHV0YXRpb24gd2lsbCBhbHNvIGluY3JlYXNlLiBJbiBzaG9ydCwgdGhpcyBzb2x1dGlvbiBvZiBub3Qgc2NhbGFibGUuDQoNCkxldCdzIGxvb2sgYXQgdGhlIGFsdGhlcm5hdGl2ZXMuIChGb3IgeW91IHRvIHVuZGVyc3RhbmQgdGhpcywgYSBsaXR0bGUgYml0IG9mIGJydXNoaW5nIG9mZiBvbiBzaW1wbGUgbGluZWFyIGFsZ2VicmEgY2FsY3VsYXRpb24gbWF5IGhlbHAgeW91IGEgbG9uZyB3YXk6aHR0cHM6Ly93d3cua2hhbmFjYWRlbXkub3JnL21hdGgvbGluZWFyLWFsZ2VicmEpDQoNCg0KYGBge3IgZmlyc3QgaW50ZXJhY3Rpb259DQppbml0YWxfc3RhdGUgPC0gYygxLDAsMCwwLDApDQppbml0YWxfc3RhdGUlKiVleGFtcGxlX21hdHJpeA0KYGBgDQoNCkxvb2sgYXQgdGhlIGNvbnZlcnQgY29sdW1uLCBXZSBnZXQgdGhlIHNhbWUgcmVzdWx0IQ0KDQoNCmBgYHtyIHNlY29uZCBpbnRlcmFjdGlvbn0NCmluaXRpYWxfc3RhdGUgPC0gYygxLDAsMCwwLDApDQppbml0aWFsX3N0YXRlJSolZXhhbXBsZV9tYXRyaXglKiVleGFtcGxlX21hdHJpeA0KYGBgDQoNCkFnYWluLCB0aGUgc2FtZSByZXN1bHQgaXMgcHJvZHVjZWQuDQoNCkFuZCB0aGVyZSBjb21lcyB0aGUgdHJpY2sgd2hlbiB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbiBzY2FsZXMgdXAuDQoNCmBgYHtyIG4gaW50ZXJhY3Rpb259DQpsb2dpYyA8LSBUUlVFDQppbml0aWFsX3N0YXRlIDwtIGMoMSwwLDAsMCwwKQ0KbmV3X3N0YXRlIDwtIGluaXRpYWxfc3RhdGUNCml0ZXIgPSAwDQpjb252LnByb2JhIDwtIG5ld19zdGF0ZVs0XQ0KcHJlY2lzaW9uIDwtIDAuMDAwMQ0KDQoNCndoaWxlKGxvZ2ljKSB7DQogIG9sZF9zdGF0ZSA8LSBuZXdfc3RhdGUNCiAgbmV3X3N0YXRlIDwtIG5ld19zdGF0ZSUqJWV4YW1wbGVfbWF0cml4DQogIGNvbnYucHJvYmEgPC0gY29udi5wcm9iYStuZXdfc3RhdGVbNF0NCiAgI0FzIGl0ZXJhY3Rpb24gYXBwcm9hY2ggaW5maW5pdHksIGFsbCBjdXN0b21lcnMgd2lsbCBldmVudHVhbGx5IG1pZ3JhdGUgdG8gIkNodXJuIi4gdGhlcmVmb3JlIGl0IGlzIHNhZmUgdG8gc2F5IHRoYXQgb3VyIHByb2dyYW0gYXBwcm9hY2hlcyBlcXVpbGlicml1bSB3aGVuIHByb2JhYmlsaXR5IGZvciBjaHVybiBhcHByb2FjaGVzIDEgYW5kIG90aGVyIGNoYW5uZWxzIGFwcHJvYWNocyAwLg0KICBsb2dpYyA8LSBjKG5ld19zdGF0ZVsxOjRdIDwgcHJlY2lzaW9uLChhYnMobmV3X3N0YXRlWzVdIC0gMSkgPCBwcmVjaXNpb24pKQ0KICBsb2dpYyA8LSBzdW0obG9naWM9PUZBTFNFKSA+IDANCiAgaXRlciA8LSBpdGVyICsgMQ0KfQ0KcHJpbnQocGFzdGUoImFmdGVyIixpdGVyLCJpdGVyYXRpb25zIiwidGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gaXMiLHJvdW5kKGNvbnYucHJvYmEsMikpKQ0KDQoNCmBgYA0KDQpTY2FsaW5nIHRoaXMgcHJvZ3JhbSB1cCB3aWxsIGdpdmUgdXMgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gZm9yIGFsbCBudW1iZXJzIG9mIGludGVyYWN0aW9ucywgd2hpY2ggaXMgdGhlIGdsb2JhbCBjb252ZXJzaW9uIHJhdGUgb2YgdGhpcyBwcm9ncmFtLiBGcm9tIHRoZSBwcm9ncmFtIHdlIGp1c3QgcmFuLCB3ZSBrbm93IHRoYXQgdGhlIGdsb2JhbCBjb252ZXJzaW9uIHJhdGUgaXMgMC40NSwgZ2l2ZW4gdGhlcmUgYXJlIHR3byBjaGFubmVscyBhLGIsIGFuZCB0aGUgdHJhbnNpdGlvbiBtYXRyaXguDQoNCg0KTm93IHNheSwgd2UgdGhpbmsgdGhhdCBhIGlzIG5vdCBwZXJmb3JtaW5nIGFuZCB3ZSBkZWNpZGUgdG8gc3RvcCBpbnZlc3RtZW50IGluIGNoYW5uZWwgYS4gYnV0IHdlJ2QgbGlrZSB0byBnYXVnZSB0aGUgaW1wYWN0IG9mIHRoaXMgZGVjaXNpb24gYmVmb3JlIHdlIGltcGxlbWVudCBpdC4gV2hhdCBjYW4gd2UgZG8/IA0KDQoNCldlIGNhbiBzaW1wbGVseSByZW1vdmUgY2hhbm5lbCBhIGZyb20gdGhlIHRyYW5zaXRpb24gbWF0cml4Og0KDQooSGVyZSBJIG5lZWQgdG8gcG9pbnQgb3V0IHRoYXQgdGhpcyBvcGVyYXRpb24gaXMgYmFzZWQgb24gcHJlbWlzZSB0aGF0IHRoZSByZW1haW5pbmcgcHJvYmFiaWxpdGllcyB3aWxsIGJlIGluZGVwZW5kZW50IG9mIGNoYW5uZWwgYS4gd2hpY2ggbWF5IG5vdCBiZSB0aGUgY2FzZSBpbiByZWFsIGxpZmUgc2NlbmFyaW9zLiBJdCBpcyBwb3NzaWJsZSB0aGF0IHdpdGhvdXQgY2hhbm5lbCBhLCBhIHN1YnNldCBvZiBjdXN0b21lcnMgd2hvIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBjaGFubmVsIGEgZ2V0cyB0cmFuc21pdHRlZCB0byBjaGFubmVsIGIgLSBZb3Ugd2lsbCBzZWUgbGF0ZXIgaW4gYW4gZXhhbXBsZSBpbiB3aGljaCBJIHVzZWQgYSBkaWZmZXJlbnQgYXNzdW1wdGlvbikuDQoNCg0KYGBge3IgRXhhbXBsZSBNYXRyaXg6IFdpdGhvdXQgYX0NCmV4YW1wbGVfbWF0cml4X2IgPC0gZXhhbXBsZV9tYXRyaXhbLTIsLTJdDQpwcmludChleGFtcGxlX21hdHJpeF9iKQ0KDQoNCiNwcmVtaXNlczphbGwgdXNlcnMgd2hvIHByZXZpb3VzbHkgaW50ZXJhY3Qgd2l0aCBhIGFsbCBjaHVybg0KZXhhbXBsZV9tYXRyaXhfYlsxLDRdIDwtIDEgLSBzdW0oZXhhbXBsZV9tYXRyaXhfYlsxLF0pICsgZXhhbXBsZV9tYXRyaXhfYlsxLDRdDQpleGFtcGxlX21hdHJpeF9iWzIsNF0gPC0gMSAtIHN1bShleGFtcGxlX21hdHJpeF9iWzIsXSkgKyBleGFtcGxlX21hdHJpeF9iWzIsNF0NCg0KDQpleGFtcGxlX21hdHJpeF9iDQpgYGANCg0KTGV0J3MgY2FsY3VsYXRlIHRoZSBjb252ZXJzaW9uIHJhdGUgd2hlbiB0aGVyZSBpcyBvbmx5IGNoYW5uZWwgYi4NCg0KDQpgYGB7ciBDb252ZXJzaW9uIFJhdGUgd2l0aG91dCBhfQ0KbG9naWMgPC0gVFJVRQ0KaW5pdGlhbF9zdGF0ZSA8LSBjKDEsMCwwLDApDQpuZXdfc3RhdGUgPC0gaW5pdGlhbF9zdGF0ZQ0KaXRlciA9IDANCmNvbnYucHJvYmEubm8uYSA8LSBuZXdfc3RhdGVbM10NCg0KDQp3aGlsZShsb2dpYykgew0KICBvbGRfc3RhdGUgPC0gbmV3X3N0YXRlDQogIG5ld19zdGF0ZSA8LSBuZXdfc3RhdGUlKiVleGFtcGxlX21hdHJpeF9iDQoNCiAgY29udi5wcm9iYS5uby5hIDwtIGNvbnYucHJvYmEubm8uYStuZXdfc3RhdGVbM10NCiAgbG9naWMgPC0gYyhuZXdfc3RhdGVbMTozXSA8IHByZWNpc2lvbiwoYWJzKG5ld19zdGF0ZVs0XSAtIDEpIDwgcHJlY2lzaW9uKSkNCiAgbG9naWMgPC0gc3VtKGxvZ2ljPT1GQUxTRSkgPiAwDQogIGl0ZXIgPC0gaXRlciArIDENCn0NCg0KcHJpbnQocGFzdGUoImFmdGVyIixpdGVyLCJpdGVyYXRpb25zIiwidGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gaXMiLHJvdW5kKGNvbnYucHJvYmEubm8uYSwyKSkpDQoNCmBgYA0KDQpXZSB0aGluayB0aGF0IGNoYW5uZWwgYSBpcyBub3QgZWZmaWNpZW50IGJ1dCB3aXRob3V0IGNoYW5uZWwgYSB0aGUgY29udmVyc2lvbiByYXRlIGlzIHJlZHVjZWQgZnJvbSAwLjQ1IHRvIDAuMiEgdGhpcyBtZWFucyB0aGF0IGNoYW5uZWwgYSBpbmNyZWFzZXMgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gYnkgMjAwJSEgaXQgd2lsbCBiZSBhIGJhZCBkZWNpc2lvbiB0byBzY2FsZSBkb3duIGludmVzdG1lbnQgaW4gY2hhbm5lbCBhLiANCg0KTGV0J3MgZG8gdGhlIHNhbWUgdGhpbmcgdG8gY2hhbm5lbCBiLg0KDQpgYGB7ciBFeGFtcGxlIE1hdHJpeDogV2l0aG91dCBifQ0KZXhhbXBsZV9tYXRyaXhfYSA8LSBleGFtcGxlX21hdHJpeFstMywtM10NCg0KZXhhbXBsZV9tYXRyaXhfYVsxLDRdIDwtIDEgLSBzdW0oZXhhbXBsZV9tYXRyaXhfYVsxLF0pICsgZXhhbXBsZV9tYXRyaXhfYVsxLDRdDQpleGFtcGxlX21hdHJpeF9hWzIsNF0gPC0gMSAtIHN1bShleGFtcGxlX21hdHJpeF9hWzIsXSkgKyBleGFtcGxlX21hdHJpeF9hWzIsNF0NCg0KYGBgDQoNCg0KYGBge3IgQ29udmVyc2lvbiBSYXRlIHdpdGhvdXQgYn0NCmxvZ2ljIDwtIFRSVUUNCmluaXRpYWxfc3RhdGUgPC0gYygxLDAsMCwwKQ0KbmV3X3N0YXRlIDwtIGluaXRpYWxfc3RhdGUNCml0ZXIgPSAwDQpjb252LnByb2JhLm5vLmIgPC0gbmV3X3N0YXRlWzNdDQoNCg0Kd2hpbGUoaXRlcjwxMDAwKSB7DQogIG9sZF9zdGF0ZSA8LSBuZXdfc3RhdGUNCiAgbmV3X3N0YXRlIDwtIG5ld19zdGF0ZSUqJWV4YW1wbGVfbWF0cml4X2ENCg0KICBjb252LnByb2JhLm5vLmIgPC0gY29udi5wcm9iYS5uby5iK25ld19zdGF0ZVszXQ0KICAjbG9naWMgPC0gMS1uZXdfc3RhdGVbNF0gPiAwLjAwMDAwMDEgI0FzIG51bWJlciBvZiBpdGVyYXRpb24gYXBwcm9hY2ggaW5maW5pdHksIHRoZSBwcm9iYWJsaXRpZXMgaW4gZWFjaCBzdGF0ZSB3aWxsIGJlIGFwcHJvYWNoaW5nIDAuIEhlcmUgd2Ugc2V0IHRoZSBjdXR0aW5nIHBvaW50IGF0IDAuMDAwMDAwMSBpbnN0ZWFkIG9mIDAgdG8gYXZvaWQgcHJvZ3JhbSBydW5uaW5nIHRvbyBsb25nIG9yIGNyYXNoaW5nIHRoZSBjb21wdXRlci4gRXNzZW50aWFsbHkgSSdtIHNheWluZyBJIHdpbGwgbm90IGNhcmUgdGhlIHByb2JhYmlsaXR5IGxvc3MgYXMgbG9uZyBhcyBpdCBpcyBsZXNzIDAuMDAwMDAxLg0KICBpdGVyIDwtIGl0ZXIgKyAxDQp9DQoNCnByaW50KHBhc3RlKCJhZnRlciIsaXRlciwiaXRlcmF0aW9ucyIsInRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGlzIixyb3VuZChjb252LnByb2JhLm5vLmIsMikpKQ0KYGBgDQoNCkFmdGVyIHJlbW92aW5nIGNoYW5uZWwgYiwgdGhlIGNvbnZlcnNpb24gcmF0ZSBpcyByZWR1Y2VkIGZyb20gMC40IHRvIDAuMDYuIENoYW5uZWwgYiBpbmNyZWFzZXMgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gYnkgNjY3JS4gDQoNCg0KRnJvbSBoZXJlIHdlIGNhbiBlYXNpbHkgaW5kdWNlIGF0dHJpYnV0aW9uIG1vZGVsIGZyb20gdGhlIHJlc3VsdHMgd2UgaGF2ZSBnb3QuDQoNCmBgYHtyIGF0dHJpYnV0aW9ufQ0KYS5saWZ0IDwtIGNvbnYucHJvYmEvY29udi5wcm9iYS5uby5hDQpiLmxpZnQgPC0gY29udi5wcm9iYS9jb252LnByb2JhLm5vLmINCmEuYXR0ciA8LSBhLmxpZnQvKGEubGlmdCtiLmxpZnQpDQpiLmF0dHIgPC0gYi5saWZ0LyhhLmxpZnQrYi5saWZ0KQ0KcHJpbnQocGFzdGUoImF0dHJpYnV0ZWQgdmFsdWUgb2YgY2hhbm5lbCBhOiIscm91bmQoYS5hdHRyLDIpKSkNCnByaW50KHBhc3RlKCJhdHRyaWJ1dGVkIHZhbHVlIG9mIGNoYW5uZWwgYjoiLHJvdW5kKGIuYXR0ciwyKSkpDQpgYGANCg0KDQoNCiNTaG9ydGN1dDogUiBwYWNrYWdlcw0KDQpJIGJ1aWx0IHRoaXMgY2FsY3VsYXRpb24gcHJldHR5IG11Y2ggZnJvbSBzY3JhdGNoIHdpdGggbWluaW11bSBkZXBlbmRlbmNpZXMgb24gZXhpc3RpbmcgUiBwYWNrYWdlcy4gVGhlIGludGVudGlvbiBvZiB0aGlzIGlzIHNvbGVseSBwZWRhZ29naWNhbCwgYXMgSSBiZWxpZXZlIG9ubHkgYnkgZG9pbmcgdGhlIGNhbGN1bGF0aW9uIHN0ZXAtYnktc3RlcCwgdGFraW5nIG5vIHNob3J0Y3V0IGNvdWxkIHdlIHRydWVseSBncmFzcCB0aGUgbWVjaGFuaXNtIG9mIGhvdyB0aGluZ3Mgd29yay4gQnV0IGl0IGlzIGEgbG9zcyBvZiBlZmZpY2llbmN5IGlmIHlvdSBkbyBub3QgbGV2ZXJhZ2UgZXhpc3RpbmcgcGFja2FnZXMgdGhhdCBjYW4gYWNvbXBsaXNoIHRoZSBzYW1lIHRhc2sgZmFzdGVyIGFuZCBiZXR0ZXIuIEFzIGZhciBhcyBJIGtub3cgdGhlcmUgYXJlIHR3byBwYWNrYWdlczogbWFya292Y2hhaW4oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL21hcmtvdmNoYWluL2luZGV4Lmh0bWwpIGFuZCBDaGFubmVsQXR0cmlidXRpb24oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0NoYW5uZWxBdHRyaWJ1dGlvbi9DaGFubmVsQXR0cmlidXRpb24ucGRmKS4gSSB3aWxsIGJyaWVmbHkgc2hvdyB5b3Ugd2FudCB0byB1c2UgbWFya292Y2hhaW4uIEFuZCBJIHdpbGwgdXNlIENoYW5uZWxBdHRyaWJ1dGlvbiBpbiBhIHNlcGVyYXRlIHBvc3QgZm9yIGNvbXBhcmFzaW9uIHB1cnBvc2VzLg0KDQpgYGB7ciBVc2luZyBtYXJrb3ZjaGFpbiBwYWNrYWdlfQ0KI0ltcG9ydCBsaWJyYXJ5IA0KbGlicmFyeShtYXJrb3ZjaGFpbikNCnN0YXRlcyA8LSBjKCJzdGFydCIsImEiLCJiIiwiY29udmVydCIsImNodXJuIikNCmV4YW1wbGUubWMgPC0gbmV3KCJtYXJrb3ZjaGFpbiIsIHN0YXRlcyA9IHN0YXRlcyx0cmFuc2l0aW9uTWF0cml4ID0gZXhhbXBsZV9tYXRyaXgsbmFtZSA9ICJhdHRyaWJ1dGlvbiIpDQpgYGANCg0KYGBge3IgcGxvdCBtYXJrb3YgY2hhaW59DQpwbG90KGV4YW1wbGUubWMpDQpgYGANCg0KYGBge3IgTWFya292IENoYWlufQ0KaW5pdGlhbFN0YXRlIDwtIGMoMSwwLDAsMCwwKQ0KYWZ0ZXIySXRlciA8LSBpbml0aWFsU3RhdGUgKiAoZXhhbXBsZS5tYyAqIGV4YW1wbGUubWMpDQpwcmludChhZnRlcjJJdGVyKQ0KYWZ0ZXI3SXRlciA8LSBpbml0aWFsU3RhdGUgKiAoZXhhbXBsZS5tYyBeIDcpDQpwcmludChhZnRlcjdJdGVyKQ0KYGBgDQoNCg0KI1Byb2R1Y3Rpb24gQ29kZSBvbiBTdGltdWxhdGVkIFJlc3VsdHMNCg0KTm93IGhhdmluZyBncmFzcGVkIGhvdyBNYXJrb3YgQ2hhaW4gd29ya3MsIGxldCdzIGFwcGx5IHRoaXMgbWV0aG9kIG9uIGEgc3RpbXVhdGVkIGRhdGFzZXQuIEFzbG8gSSdtIGdvaW5nIHRvIG1vZHVsYXJpemUgdGhlIGNvZGUgYnkgY3JlYXRpbmcgdG8gZnVuY3Rpb25zLCA8Yj5Db252UmF0ZUVzdDwvYj4gPGI+YW5kIG5ld1RyYW5zaXRpb25NYXRyaXg8L2I+IHRvIGFpZCByZXByb2R1Y3Rpb24uDQoNCg0KDQpgYGB7ciBSZWFkIGluIHN0aW11bGF0ZWQgZGF0YX0NCmRhdGEgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3Jpbmxpbi9EZXNrdG9wL0F0dHJpYnV0aW9uL0Jsb2dwb3N0L2F0dHJpYnV0aW9uX3Jhdy5jc3YiLHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCmBgYA0KDQoNCmBgYHtyIFVzZXIgRGVmaW5lZCBGdW5jdGlvbn0NCkNvbnZSYXRlRXN0IDwtIGZ1bmN0aW9uKFRyYW5zaXRpb25NYXRyaXgpIHsNCiAgaXRlciA8LSAwDQogIGxvZ2ljIDwtIFRSVUUNCiAgcHJlY2lzaW9uIDwtIDAuMDAwMQ0KICANCiAgbmV3X3N0YXRlIDwtIG1hdHJpeChyZXAoMCxsZW5ndGgoc3RhdGVzKFRyYW5zaXRpb25NYXRyaXgpKSksIG5yb3cgPSAxKQ0KICBjb2xuYW1lcyhuZXdfc3RhdGUpIDwtIHN0YXRlcyhUcmFuc2l0aW9uTWF0cml4KQ0KICBuZXdfc3RhdGVbLCJzdGFydCJdIDwtIDENCiAgDQogIENvbnZSYXRlIDwtIG5ld19zdGF0ZVssImNvbnZlcnRlZCJdDQogIA0KICANCiAgDQogIHdoaWxlKGxvZ2ljKSB7DQogICAgDQogICAgbmV3X3N0YXRlIDwtIG5ld19zdGF0ZSpUcmFuc2l0aW9uTWF0cml4DQogICAgQ29udlJhdGUgPC0gQ29udlJhdGUgKyBuZXdfc3RhdGVbLCJjb252ZXJ0ZWQiXQ0KICAgIGxvZ2ljIDwtICgoYWJzKG5ld19zdGF0ZVssImNodXJuZWQiXSAtIDEpIDwgcHJlY2lzaW9uKSkNCiAgICBsb2dpYyA8LSBzdW0obG9naWM9PUZBTFNFKSA+IDANCiAgICBpdGVyIDwtIGl0ZXIgKyAxDQogIH0NCiAgDQogIHByaW50KHBhc3RlKCJhZnRlciIsaXRlciwiaXRlcmF0aW9ucyIsInRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGlzIixyb3VuZChDb252UmF0ZSwyKSkpDQogIHJldHVybihDb252UmF0ZSkNCiAgDQp9DQoNCg0KbmV3VHJhbnNpdGlvbk1hdHJpeCA8LSBmdW5jdGlvbihtY01hdHJpeCxyZW1vdmVDaGFubmVsKSB7DQogIG5ld01hdHJpeCA8LSBtY01hdHJpeFstd2hpY2goc3RhdGVzKG1jTWF0cml4KSA9PSByZW1vdmVDaGFubmVsKSwtd2hpY2goc3RhdGVzKG1jTWF0cml4KSA9PSByZW1vdmVDaGFubmVsKV0NCiAgZm9yKHN0YXRlIGluIHJvd25hbWVzKG5ld01hdHJpeCkpIHsNCiAgICB0b3RhbCA8LSBzdW0obmV3TWF0cml4W3N0YXRlLF0pDQogICAgZm9yKHN0YXRlLjIgaW4gcm93bmFtZXMobmV3TWF0cml4KSkgew0KICAgICAgbmV3TWF0cml4W3N0YXRlLHN0YXRlLjJdIDwtIG5ld01hdHJpeFtzdGF0ZSxzdGF0ZS4yXS90b3RhbCANCiAgICB9DQogICAgI25ld01hdHJpeFtzdGF0ZSwiY2h1cm5lZCJdIDwtIDEtIHN1bShuZXdNYXRyaXhbc3RhdGUsXSkgKyBuZXdNYXRyaXhbc3RhdGUsImNodXJuZWQiXQ0KICB9DQogIG5ld01hdHJpeCA8LSBuZXcoIm1hcmtvdmNoYWluIixzdGF0ZXMgPSByb3duYW1lcyhuZXdNYXRyaXgpLHRyYW5zaXRpb25NYXRyaXggPSBuZXdNYXRyaXgsbmFtZSA9ICJhdHRyaWJ1dGlvbiIpDQogIHJldHVybihuZXdNYXRyaXgpDQp9DQoNCg0KYGBgDQoNCg0KDQpgYGB7ciBEYXRhIFByZXByb2Nlc3Npbmd9DQpkYXRhLjEgPC0gZGF0YQ0KZGF0YS4xJHN0YXR1cyA8LSAic3RhcnQiDQoNCmRhdGEuMiA8LSBkYXRhDQpkYXRhLjIkc3RhdHVzIDwtICJlbmQiDQoNCg0KZGYgPC0gcmJpbmQoZGF0YS4xLGRhdGEuMikNCg0KdGVtcCA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoUGF0aC5JRCxDb252ZXJzaW9uKSAlPiUNCiAgc3VtbWFyaXNlX2F0KCJUaW1lLlN0YW1wIixmdW5zKG1heCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KZW5kIDwtIHRlbXAgDQplbmQkVGltZS5TdGFtcCA8LSBlbmQkVGltZS5TdGFtcCArIDENCmVuZCRDaGFubmVsIDwtIGlmZWxzZShlbmQkQ29udmVyc2lvbiA9PSAxLCJjb252ZXJ0ZWQiLCJjaHVybmVkIikNCmVuZCRzdGF0dXMgPC0gImVuZCINCg0Kc3RhcnQgPC0gdGVtcA0Kc3RhcnQkVGltZS5TdGFtcCA8LSAwDQpzdGFydCRDaGFubmVsIDwtICJzdGFydCINCnN0YXJ0JHN0YXR1cyA8LSAic3RhcnQiDQoNCmRmIDwtIHJiaW5kKGRmLHN0YXJ0LGVuZCkNCg0KZGYgPC0gZGYgJT4lDQogIGFycmFuZ2UoUGF0aC5JRCxUaW1lLlN0YW1wLHN0YXR1cykgJT4lDQogIGdyb3VwX2J5KFBhdGguSUQpICU+JQ0KICBtdXRhdGUobmV3LmlkID0gbGFnKFRpbWUuU3RhbXApKSAlPiUNCiAgdW5ncm91cCgpIA0KDQpkZltpcy5uYShkZiRuZXcuaWQpLF0kbmV3LmlkIDwtIDANCg0KDQpvdXRwdXQgPC0gZGYgJT4lDQogIG11dGF0ZShpZCA9IHBhc3RlMChQYXRoLklELG5ldy5pZCkpICU+JQ0KICBzZWxlY3QoaWQsc3RhdHVzLENoYW5uZWwpICU+JQ0KICB0aWR5cjo6c3ByZWFkKHN0YXR1cyxDaGFubmVsKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnQsZW5kKSAlPiUNCiAgc3VtbWFyaXNlX2F0KCJpZCIsZnVucyhsZW5ndGgpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUoY291bnQgPSBpZCkgJT4lDQogIHNlbGVjdCgtaWQpIA0KDQoNCm91dHB1dCA8LSBvdXRwdXQgJT4lDQogIGxlZnRfam9pbihvdXRwdXQgJT4lDQogICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0KSAlPiUNCiAgICAgICAgICAgICAgc3VtbWFyaXNlX2F0KCJjb3VudCIsZnVucyhzdW0pKSAlPiUNCiAgICAgICAgICAgICAgdW5ncm91cCgpICU+JQ0KICAgICAgICAgICAgICBtdXRhdGUodG90YWwuY291bnQgPSBjb3VudCkgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtY291bnQpLGJ5ID0gInN0YXJ0IikgJT4lDQogIG11dGF0ZShwcm9iYSA9IGNvdW50L3RvdGFsLmNvdW50KSAjJT4lDQogICNzZWxlY3Qoc3RhcnQsZW5kLHByb2JhKQ0KDQpybShkYXRhLjEpDQpybShkYXRhLjIpDQpybShkZikNCnJtKGVuZCkNCnJtKHN0YXJ0KQ0Kcm0odGVtcCkNCg0KDQoNCnN0YXRlcyA8LSBzb3J0KHVuaXF1ZShjKG91dHB1dCRzdGFydCxvdXRwdXQkZW5kKSkpDQpvdXRwdXQgPC0gZGF0YS5mcmFtZShzdGFydCA9IHNvcnQocmVwKHN0YXRlcyxsZW5ndGgoc3RhdGVzKSkpLCBlbmQgPSByZXAoc3RhdGVzLGxlbmd0aChzdGF0ZXMpKSxzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICU+JQ0KICBsZWZ0X2pvaW4ob3V0cHV0LGJ5ID0gYygic3RhcnQiLCJlbmQiKSkNCg0Kb3V0cHV0W2lzLm5hKG91dHB1dCRwcm9iYSksXSRwcm9iYSA8LSAwDQoNCg0KbWF0cml4IDwtIG1hdHJpeChvdXRwdXQkcHJvYmEsYnlyb3cgPSBUUlVFLG5yb3cgPSA2KQ0KbWF0cml4WzEsMV0gPC0gMQ0KbWF0cml4WzIsMV0gPC0gMQ0KbWMuYXR0cjwtbmV3KCJtYXJrb3ZjaGFpbiIsIHN0YXRlcyA9IHN0YXRlcyx0cmFuc2l0aW9uTWF0cml4ID0gbWF0cml4LG5hbWUgPSAiYXR0cmlidXRpb24iKQ0KcHJpbnQobWMuYXR0cikNCg0KYGBgDQoNCg0KYGBge3IgQ29udmVyc2lvbiBSYXRlfQ0KDQpDb252UmF0ZSA8LSBDb252UmF0ZUVzdChtYy5hdHRyKQ0KQ29uUmF0ZU5vU2VhcmNoIDwtIENvbnZSYXRlRXN0KG5ld1RyYW5zaXRpb25NYXRyaXgobWMuYXR0ciwiU2VhcmNoIikpDQpDb25SYXRlTm9EaXNwbGF5IDwtIENvbnZSYXRlRXN0KG5ld1RyYW5zaXRpb25NYXRyaXgobWMuYXR0ciwiRGlzcGxheSIpKQ0KQ29uUmF0ZU5vRW1haWwgPC0gQ29udlJhdGVFc3QobmV3VHJhbnNpdGlvbk1hdHJpeChtYy5hdHRyLCJFbWFpbCIpKQ0KDQoNCg0KDQpgYGANCg0KYGBge3IgQXR0cmlidXRpb259DQpTZWFyY2hMaWZ0IDwtIENvbnZSYXRlL0NvblJhdGVOb1NlYXJjaA0KRGlzcGxheUxpZnQgPC0gQ29udlJhdGUvQ29uUmF0ZU5vRGlzcGxheQ0KRW1haWxMaWZ0IDwtIENvbnZSYXRlL0NvblJhdGVOb0VtYWlsDQoNCmRhdGEuZnJhbWUoY2hhbm5lbCA9IGMoIlNlYXJjaCIsIkRpc3BsYXkiLCJFbWFpbCIpLGF0dHJpYnV0aW9uID0gYyhyb3VuZChTZWFyY2hMaWZ0LyhTZWFyY2hMaWZ0ICsgRGlzcGxheUxpZnQgKyBFbWFpbExpZnQpLDIpLHJvdW5kKERpc3BsYXlMaWZ0LyhTZWFyY2hMaWZ0ICsgRGlzcGxheUxpZnQgKyBFbWFpbExpZnQpLDIpLHJvdW5kKEVtYWlsTGlmdC8oU2VhcmNoTGlmdCArIERpc3BsYXlMaWZ0ICsgRW1haWxMaWZ0KSwyKSkpDQpgYGANCg0KDQojT3RoZXIgdXNlIGNhc2VzIG9uIE1hcmtvdiBDaGFpbg0KDQpBcyB5b3UgbWlnaHQgaGF2ZSBhbHJlYWR5IGZvdW5kIG91dCwgb2Z0ZW4gdGltZSBvbmUgc3RhdGlzdGljYWwgbWV0aG9kcyBjYW4gYmUgYXBwbGllZCB0byBtdWx0aXBsZSBkaXNjaXBsaW5lcy5Bbm90aGVyIHVzZSBjYXNlIG9mIE1hcmtvdiBDaGFpbiBpbiBtYXJrZXRpbmcgaXMgaW4gQ1JNIGFuYWx5dGljcyhXaGljaCBJIHdpbGwgYmUgdGFsa2luZyBhYm91dCBpbiBhIGRpZmZlcmVudCBwb3N0IG9uIG1pZ3JhdGlvbiBtb2RlbCkuIEl0IGlzIGFsc28gcG9zc2libGUgdG8gYXBwbHkgbWFya292IGNoYWluIGludG8gb3RoZXIgZmllbGRzIGFzIGxvbmcgYXMgdHJhbnNpdGlvbnMgb2Ygc3RhdGVzIGV4aXN0LiBDYW4geW91IHRoaW5rIG9mIGFueSBvdGhlciBzY2VuYXJpb3Mgd2hlcmUgTWFya292IENoYWluIGNhbiBiZSBhcHBsaWVkPyANCg0KIA==