Data Import:

ebeer <- read_csv("ebeer.csv")

Introduction

RFM, recency (R), frequency (F) and monetary value (M) are the most often used database marketing metrics used to quantify customer transaction history. RFM analysis segments customer into groups according to these measures and relates these segments to the likelihood of responding to a marketing offer. This notebook discusses the measures, segmentation, usefulness for guiding marketing decisions, and extensions to the basic model.

Inspecting the data

Each row is a customer. acctnum is their id. We have gender, Recency (the number of months since the last purchase), Frequency (number of purchases), M (average amount spent per purchase), first purchase (number of months since first purchase), age, single, student, whether they received a mailing, did they respond.

head(ebeer)
## # A tibble: 6 × 11
##   acctnum gender     R     F     M firstpur age_class single student mailing respmail
##     <dbl>  <dbl> <dbl> <dbl> <dbl>    <dbl>     <dbl>  <dbl>   <dbl>   <dbl>    <dbl>
## 1   10001      1    30    10  35.7       50         3      1       1       1        0
## 2   10005      0    16     1 149         16         0      0       1       1        0
## 3   10010      0    12     1 123         12         0      1       0       1        0
## 4   10011      0     6     2 147          8         0      1       1       1        0
## 5   10014      1     6     3  96         18         0      0       1       1        1
## 6   10020      0    12     2 150.        14         0      1       1       1        0

Let’s look at the marketing variable: who gets a mailing in this dataset?

table(ebeer$mailing)
## 
##    0    1 
## 5012 4952

How many people of those mailed respond?

ebeer %>% 
  group_by(mailing) %>% 
  summarise(mean = mean(respmail), n = n())
## # A tibble: 2 × 3
##   mailing   mean     n
##     <dbl>  <dbl> <int>
## 1       0 NA      5012
## 2       1  0.124  4952

Binomial model for responses

The probability of observing \(s\) people respond out of \(n\) people mailed is described by a binomial distribution: \[P(s|n, p) = {n \choose s} p^{s} (1-p)^{n-s}\]

The overall response rate (probability of response) is \(\hat{p}=\) 0.124, and the standard error is 0.005.

ci_low <- qnorm(0.025, mean=p_hat, sd=p_hat_se) 
ci_high <- qnorm(0.975, mean=p_hat, sd=p_hat_se) 
## The Response Rate is Between 0.115 and 0.134

The confidence interval is [0.115; 0.134].

Creating R, F and M segments separately.

Let’s look at the distribution of raw RFM variables.

ebeer$F <- as.numeric(ebeer$F)
par(mai=c(.9,.8,.2,.2))
hist(ebeer$R, main="Recency", xlab="Recency: # months since last purchase")

hist(ebeer$F, main="Frequency", xlab="Frequency: # purchases")

hist(ebeer$M, main="Monetary", xlab="Monetary: avg amount spent per purchase")

Let’s first create segments from each variable separately; we create separate segments for R, F, and M. We sort them from largest to smallest. Then we create \(n\) bins, where \(n=5\)

We do this by creating quantiles, divide into 20% most recent, 20% next most recent. We use the quantcut function in package rtools.

ebeer$Rgroup <- quantcut(x = ebeer$R, q=5)

ebeer %>% 
  group_by(Rgroup) %>% 
  summarise(n=n(), mean_R=mean(R), sd_R=sd(R))
## # A tibble: 5 × 4
##   Rgroup      n mean_R  sd_R
##   <fct>   <int>  <dbl> <dbl>
## 1 [2,6]    2229   3.97 1.62 
## 2 (6,10]   2069   9.22 0.975
## 3 (10,14]  2509  13.0  1.00 
## 4 (14,16]  1231  16    0    
## 5 (16,36]  1926  26.8  5.46

Now, let’s examine how response rate vary with the recency groups we just created. We only want to look at the subset of customer who were mailed, so we filter the dataset first filter(mailing==1). We group by our just created 5 R segments. And we calculate the mean of response, mean(respmail).

respR <- ebeer %>% 
  filter(mailing==1) %>% 
  group_by(Rgroup) %>% 
  summarise(avg_respmail=mean(respmail), sd_respmail=sd(respmail), n=n())
respR
## # A tibble: 5 × 4
##   Rgroup  avg_respmail sd_respmail     n
##   <fct>          <dbl>       <dbl> <int>
## 1 [2,6]          0.126       0.332  1098
## 2 (6,10]         0.119       0.324  1041
## 3 (10,14]        0.122       0.328  1211
## 4 (14,16]        0.136       0.343   632
## 5 (16,36]        0.124       0.329   970
barplot(respR$avg_respmail~respR$Rgroup, main="response by Recency group", xlab="Recency Group", ylab="average response")

Full RFM analysis

Now do the full RFM analysis. Remember, the idea is that

  1. We first sort by R, create segments. (we already did this.)
  2. Within each R segment, we sort F and create RF segments.
  3. Within each RF segment, we sort M and create RFM segments.

The way to do this is slightly complicated; I would give you this script in an exam or assignment. You would not have to code this up yourselves. First, we change ebeer into data.table Within each R group, we create F groups -> RF groups. Within each RF group, we create M groups -> RFM groups

ntiles <- function(x, bins) {
  quantiles = seq(from=0, to = 1, length.out=bins+1)
  cut(ecdf(x)(x),breaks=quantiles, labels=F)
}

ebeer$Rgroup <- ntiles(ebeer$R, bins=5)  


dt = data.table(ebeer)
nbins = 5
dt[, RFgroup := paste0(as.character(Rgroup), as.character(ntiles(F, bins = nbins))), by = c('Rgroup')]
dt[, RFMgroup := paste0(as.character(RFgroup), as.character(ntiles(M, bins = nbins))), by = c('RFgroup')]

# put it back to data.frame
ebeer = data.frame(dt)

# change it to a factor variable
ebeer$RFMgroup <- as.factor(ebeer$RFMgroup)

How many RFM groups do we get with this procedure?

length(unique(ebeer$RFMgroup))
## [1] 90

We have 90 RFM segments.

barplot(table(ebeer$RFMgroup), xlab = "RFM segments", ylab="frequency")

Response rate by RFM segment

Let’s make the response rate by segment.

# p = s_z/n_z
respRFM <- ebeer %>% 
  group_by(RFMgroup) %>% 
  summarise(n_resp= sum(respmail, na.rm = TRUE), 
            n_mail= sum(mailing, na.rm = TRUE)) %>% 
  mutate(resp_rate = n_resp/n_mail) %>% 
  arrange(desc(resp_rate)) #re-order from most to least, make picture better
respRFM
## # A tibble: 90 × 4
##    RFMgroup n_resp n_mail resp_rate
##    <fct>     <dbl>  <dbl>     <dbl>
##  1 454          14     28     0.5  
##  2 453           9     20     0.45 
##  3 441           8     18     0.444
##  4 152          12     29     0.414
##  5 151          13     33     0.394
##  6 252          13     33     0.394
##  7 154          11     28     0.393
##  8 155          11     28     0.393
##  9 451          11     28     0.393
## 10 555          30     77     0.390
## # … with 80 more rows

Targeting using RFM analysis

Now let’s figure out which segments we should target. We want to target segments that have a response rate above the breakeven point. Remember the breakeven probability: \[ \bar{p}_{BE} = \frac{c}{m} = \frac{\textrm{cost}}{\textrm{margin}} \]

c = 1.5
m = 50
brk = c/m

Our breakeven point is 0.03.

respRFM <- as.data.frame(respRFM)

bp <- barplot(respRFM[,4], 
              main="response by RFM group", 
              xlab="RFM Group", ylab="average response", xaxt="n")
axis(1, at = bp[,1], labels=respRFM[,1], cex.axis=0.7, las=2)

abline(h=brk)
text(85, brk, "breakeven", cex=1, pos=3, col="black")

How many segments are above the breakeven, and therefore targeted? What segments are they? As a percentage of the total segments?

# how many segments above breakeven? which segments?
n_segments <- sum(respRFM$resp_rate >= brk)
# as a percentage of all segments
p_segments <- sum(respRFM$resp_rate >= brk) / length(unique(ebeer$RFMgroup))
## Optimal Number of Segments to target is 60 which is 66.7 % of total segments

Prediction of Rollout Customers

OK, now let’s apply this model to those customers who have not been mailed, sometimes called the “rollout” sample. We use binary linear regressions to predict. The \(\hat{\beta}_z\) will be the mean response rate of each segment.

RFM_model <- lm(respmail ~ RFMgroup, data = ebeer)

Applying predictions to rollout data: ROI

We separate the rollout data (where there is no mailing) from everything else. Then, we’ll score the new data, i.e., apply the predictions of the model to the new data.

ebeer.rollout <- ebeer[is.na(ebeer$respmail), ]

##Prediction
ebeer.rollout$RFMpred <- predict(RFM_model, ebeer.rollout)

summary(ebeer.rollout$RFMpred)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   0.000   0.085   0.125   0.185   0.500

The average prediction is the average response rate we found earlier. So makes sense in terms of face validity.

We now have a probability of response to everyone in the rollout data. How many customers in the rollout would get mailed? as a fraction of the total, what would the profits and return on investment (ROI) be?

# Total number of rollout customers with predicted response rates above breakeven
n_customers <- sum(ebeer.rollout$RFMpred >= brk)
# as a proportion of all rollout customers
p_customers <- sum(ebeer.rollout$RFMpred >= brk) / length(ebeer.rollout$RFMpred)
## Optimal Number of Segments to target is 3222 which is 64.3 % of total segments
# profit per customer
# if p > p_BE, expected profit = p*m - c  ||  if p < p_BE, = 0
ebeer.rollout <- ebeer.rollout %>% 
  mutate(RFMprofit = case_when(RFMpred >= brk ~ RFMpred*m-c, 
    TRUE ~ 0))
# or pmax takes columnwise maximum (same as in L2)
#ebeer.rollout$RFMprofit <- pmax(ebeer.rollout$RFMpred *m - c, 0)

# sum over customers
sum_profit = sum(ebeer.rollout$RFMprofit)

# sum costs of targeting customers
ebeer.rollout$RFMcost <- ifelse(ebeer.rollout$RFMpred >= brk, c, 0)

sum_cost = sum(ebeer.rollout$RFMcost)
## ROI: 540 %

If we targeted everyone in the rollout group:

ebeer.rollout$all <-ebeer.rollout$RFMpred *m - c
sum_profit_all = sum(ebeer.rollout$all)
sum_cost_all = c*length(ebeer.rollout$RFMpred)
## ROI: 315 %
respRFM <- respRFM %>% mutate(n_nonresp = n_mail-n_resp) %>% relocate(n_nonresp, .after=n_resp)

Using a Bayesian approach

Right now we assume that these segments response rates are entirely independent of each other. But if we make an assumption about the distribution of response rates across segments, we could use that common distribution to “borrow” information from the other segments.

par(mai=c(.9,.8,.2,.2))
hist(respRFM$resp_rate, density=10, breaks=20, main="Distribution of response rates across segments", xlab="segment-specific probability of response")
curve(dbeta(x, .3, 3), add = TRUE,  type="l", col="gray")

Empirical Bayes

\[ \begin{array}{ccl} P(s_z|n_z, a, b)& = & \displaystyle {n_z \choose s_z} \frac{B(a+s_z,b+n_z-s_z)}{B(a,b)} \end{array} \] #### Prior We start by estimating the priors borrowing information form other segments. We use MLE for this:

Coef(fit)
## shape1 shape2 
##  0.493  3.113
# make them a and b
a <- Coef(fit)[[1]]
b <- Coef(fit)[[2]]

Let’s plot this prior estimate against the data

par(mai=c(.9,.8,.2,.2))
hist(respRFM$resp_rate, density=10, breaks=20, main="Distribution of response rates across segments", xlab="segment-specific probability of response")
curve(dbeta(x, a, b), add = TRUE,  type="l", col="red")
curve(dbeta(x, .3, 3), add = TRUE,  type="l", col="blue")

Posterior Mean Response

# posterior mean response rate
post_mean_resp <- (a+respRFM$n_resp)/(a+b+respRFM$n_mail)
             
# add this as column to respRFM
respRFM <- cbind(respRFM, post_mean_resp)

#order from lowest to greatest
respRFM <- respRFM %>% arrange((resp_rate))

head(respRFM)
##   RFMgroup n_resp n_nonresp n_mail resp_rate post_mean_resp
## 1      121      0        39     39         0        0.01157
## 2      122      0        48     48         0        0.00955
## 3      123      0        46     46         0        0.00994
## 4      124      0        45     45         0        0.01014
## 5      125      0        38     38         0        0.01185
## 6      131      0        43     43         0        0.01058

We plot this:

plot(respRFM$resp_rate, xaxt="n",col="red",xlab="RFM segments",ylab="response rate (x/n) and posterior mean response rate")
points(respRFM$post_mean_resp, col='blue')
legend('topleft',legend=c("estimate response rate", "posterior expected response rate"),col=c("red","blue"), pch=1)
axis(1, at = 1:90, labels=respRFM$RFMgroup, cex.axis=0.7, las=2)
abline(h=brk)
text(85, brk, "breakeven", cex=1, pos=3, col="black")

Are there any switches we would make using the posterior mean rather than the actual mean to target segments?

## Using the posterior mean to target segments leads to 62 segments, whereas using the actual mean leads to 60
LS0tDQp0aXRsZTogIlR1dG9yaWFsIDI6IFJGTSBBbmFseXNpcyINCmF1dGhvcjogIkRhbmllbCBSZWRlbCINCmRhdGU6ICIyMDIyLTExLTAxIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpybShsaXN0ID0gbHMoKSkNCg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGd0b29scykNCmxpYnJhcnkoVkdBTSkNCmxpYnJhcnkocmVhZHIpDQoNCm9wdGlvbnMoInNjaXBlbiI9MTAwLCAiZGlnaXRzIj0zKQ0KYGBgDQoNCkRhdGEgSW1wb3J0Og0KDQpgYGB7ciBpbXBvcnQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQplYmVlciA8LSByZWFkX2NzdigiZWJlZXIuY3N2IikNCmBgYA0KDQojIyMgSW50cm9kdWN0aW9uDQoNCioqUkZNKiosIHJlY2VuY3kgKCoqUioqKSwgZnJlcXVlbmN5ICgqKkYqKikgYW5kIG1vbmV0YXJ5IHZhbHVlICgqKk0qKikgYXJlIHRoZSBtb3N0IG9mdGVuIHVzZWQgZGF0YWJhc2UgbWFya2V0aW5nIG1ldHJpY3MgdXNlZCB0byBxdWFudGlmeSBjdXN0b21lciB0cmFuc2FjdGlvbiBoaXN0b3J5LiBSRk0gYW5hbHlzaXMgc2VnbWVudHMgY3VzdG9tZXIgaW50byBncm91cHMgYWNjb3JkaW5nIHRvIHRoZXNlIG1lYXN1cmVzIGFuZCByZWxhdGVzIHRoZXNlIHNlZ21lbnRzIHRvIHRoZSBsaWtlbGlob29kIG9mIHJlc3BvbmRpbmcgdG8gYSBtYXJrZXRpbmcgb2ZmZXIuIFRoaXMgbm90ZWJvb2sgZGlzY3Vzc2VzIHRoZSBtZWFzdXJlcywgc2VnbWVudGF0aW9uLCB1c2VmdWxuZXNzIGZvciBndWlkaW5nIG1hcmtldGluZyBkZWNpc2lvbnMsIGFuZCBleHRlbnNpb25zIHRvIHRoZSBiYXNpYyBtb2RlbC4NCg0KIyMjIEluc3BlY3RpbmcgdGhlIGRhdGENCg0KRWFjaCByb3cgaXMgYSBjdXN0b21lci4gYGFjY3RudW1gIGlzIHRoZWlyIGlkLiBXZSBoYXZlIGdlbmRlciwgKipSZWNlbmN5KiogKHRoZSBudW1iZXIgb2YgbW9udGhzIHNpbmNlIHRoZSBsYXN0IHB1cmNoYXNlKSwgKipGcmVxdWVuY3kqKiAobnVtYmVyIG9mIHB1cmNoYXNlcyksICoqTSoqIChhdmVyYWdlIGFtb3VudCBzcGVudCBwZXIgcHVyY2hhc2UpLCBmaXJzdCBwdXJjaGFzZSAobnVtYmVyIG9mIG1vbnRocyBzaW5jZSBmaXJzdCBwdXJjaGFzZSksIGFnZSwgc2luZ2xlLCBzdHVkZW50LCB3aGV0aGVyIHRoZXkgcmVjZWl2ZWQgYSBtYWlsaW5nLCBkaWQgdGhleSByZXNwb25kLg0KDQpgYGB7cn0NCmhlYWQoZWJlZXIpDQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgKiptYXJrZXRpbmcgdmFyaWFibGUqKjogd2hvIGdldHMgYSBtYWlsaW5nIGluIHRoaXMgZGF0YXNldD8NCg0KYGBge3J9DQp0YWJsZShlYmVlciRtYWlsaW5nKQ0KYGBgDQoNCkhvdyBtYW55IHBlb3BsZSBvZiB0aG9zZSBtYWlsZWQgcmVzcG9uZD8NCg0KYGBge3J9DQplYmVlciAlPiUgDQogIGdyb3VwX2J5KG1haWxpbmcpICU+JSANCiAgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKHJlc3BtYWlsKSwgbiA9IG4oKSkNCmBgYA0KDQojIyMgQmlub21pYWwgbW9kZWwgZm9yIHJlc3BvbnNlcw0KDQpUaGUgcHJvYmFiaWxpdHkgb2Ygb2JzZXJ2aW5nICRzJCBwZW9wbGUgcmVzcG9uZCBvdXQgb2YgJG4kIHBlb3BsZSBtYWlsZWQgaXMgZGVzY3JpYmVkIGJ5IGEgYmlub21pYWwgZGlzdHJpYnV0aW9uOiAkJFAoc3xuLCBwKSA9IHtuIFxjaG9vc2Ugc30gcF57c30gKDEtcClee24tc30kJA0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCnBfaGF0IDwtIG1lYW4oZWJlZXIkcmVzcG1haWxbZWJlZXIkbWFpbGluZz09MV0pDQpgYGANCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQpuID0gc3VtKGViZWVyJG1haWxpbmc9PTEpIA0KcF9oYXRfc2UgPSBzcXJ0KHBfaGF0KigxLXBfaGF0KS9uKSAjc3RhbmRhcmQgZXJyb3Igb2YgZXN0aW1hdGUgcA0KYGBgDQoNClRoZSBvdmVyYWxsIHJlc3BvbnNlIHJhdGUgKHByb2JhYmlsaXR5IG9mIHJlc3BvbnNlKSBpcyAkXGhhdHtwfT0kIGByIHBfaGF0YCwgYW5kIHRoZSBzdGFuZGFyZCBlcnJvciBpcyBgciBwX2hhdF9zZWAuDQoNCmBgYHtyIGNpfQ0KY2lfbG93IDwtIHFub3JtKDAuMDI1LCBtZWFuPXBfaGF0LCBzZD1wX2hhdF9zZSkgDQpjaV9oaWdoIDwtIHFub3JtKDAuOTc1LCBtZWFuPXBfaGF0LCBzZD1wX2hhdF9zZSkgDQoNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmNhdCgiVGhlIFJlc3BvbnNlIFJhdGUgaXMgQmV0d2VlbiIsIGNpX2xvdywgImFuZCIsIGNpX2hpZ2gpDQpgYGANCg0KVGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgaXMgW2ByIGNpX2xvd2A7IGByIGNpX2hpZ2hgXS4NCg0KIyMjIENyZWF0aW5nIFIsIEYgYW5kIE0gc2VnbWVudHMgc2VwYXJhdGVseS4NCg0KTGV0J3MgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHJhdyBSRk0gdmFyaWFibGVzLg0KDQpgYGB7cn0NCmViZWVyJEYgPC0gYXMubnVtZXJpYyhlYmVlciRGKQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCmhpc3QoZWJlZXIkUiwgbWFpbj0iUmVjZW5jeSIsIHhsYWI9IlJlY2VuY3k6ICMgbW9udGhzIHNpbmNlIGxhc3QgcHVyY2hhc2UiKQ0KaGlzdChlYmVlciRGLCBtYWluPSJGcmVxdWVuY3kiLCB4bGFiPSJGcmVxdWVuY3k6ICMgcHVyY2hhc2VzIikNCmhpc3QoZWJlZXIkTSwgbWFpbj0iTW9uZXRhcnkiLCB4bGFiPSJNb25ldGFyeTogYXZnIGFtb3VudCBzcGVudCBwZXIgcHVyY2hhc2UiKQ0KYGBgDQoNCkxldCdzIGZpcnN0IGNyZWF0ZSBzZWdtZW50cyBmcm9tIGVhY2ggdmFyaWFibGUgc2VwYXJhdGVseTsgd2UgY3JlYXRlIHNlcGFyYXRlIHNlZ21lbnRzIGZvciBSLCBGLCBhbmQgTS4gV2Ugc29ydCB0aGVtIGZyb20gbGFyZ2VzdCB0byBzbWFsbGVzdC4gVGhlbiB3ZSBjcmVhdGUgJG4kIGJpbnMsIHdoZXJlICRuPTUkDQoNCldlIGRvIHRoaXMgYnkgY3JlYXRpbmcgcXVhbnRpbGVzLCBkaXZpZGUgaW50byAyMCUgbW9zdCByZWNlbnQsIDIwJSBuZXh0IG1vc3QgcmVjZW50LiBXZSB1c2UgdGhlIGBxdWFudGN1dGAgZnVuY3Rpb24gaW4gcGFja2FnZSBgcnRvb2xzYC4NCg0KYGBge3J9DQplYmVlciRSZ3JvdXAgPC0gcXVhbnRjdXQoeCA9IGViZWVyJFIsIHE9NSkNCg0KZWJlZXIgJT4lIA0KICBncm91cF9ieShSZ3JvdXApICU+JSANCiAgc3VtbWFyaXNlKG49bigpLCBtZWFuX1I9bWVhbihSKSwgc2RfUj1zZChSKSkNCg0KYGBgDQoNCk5vdywgbGV0J3MgZXhhbWluZSBob3cgKipyZXNwb25zZSByYXRlKiogdmFyeSB3aXRoIHRoZSByZWNlbmN5IGdyb3VwcyB3ZSBqdXN0IGNyZWF0ZWQuIFdlIG9ubHkgd2FudCB0byBsb29rIGF0IHRoZSBzdWJzZXQgb2YgY3VzdG9tZXIgd2hvIHdlcmUgbWFpbGVkLCBzbyB3ZSBmaWx0ZXIgdGhlIGRhdGFzZXQgZmlyc3QgYGZpbHRlcihtYWlsaW5nPT0xKWAuIFdlIGdyb3VwIGJ5IG91ciBqdXN0IGNyZWF0ZWQgNSBSIHNlZ21lbnRzLiBBbmQgd2UgY2FsY3VsYXRlIHRoZSBtZWFuIG9mIHJlc3BvbnNlLCBgbWVhbihyZXNwbWFpbClgLg0KDQpgYGB7cn0NCnJlc3BSIDwtIGViZWVyICU+JSANCiAgZmlsdGVyKG1haWxpbmc9PTEpICU+JSANCiAgZ3JvdXBfYnkoUmdyb3VwKSAlPiUgDQogIHN1bW1hcmlzZShhdmdfcmVzcG1haWw9bWVhbihyZXNwbWFpbCksIHNkX3Jlc3BtYWlsPXNkKHJlc3BtYWlsKSwgbj1uKCkpDQpyZXNwUg0KYGBgDQoNCmBgYHtyfQ0KYmFycGxvdChyZXNwUiRhdmdfcmVzcG1haWx+cmVzcFIkUmdyb3VwLCBtYWluPSJyZXNwb25zZSBieSBSZWNlbmN5IGdyb3VwIiwgeGxhYj0iUmVjZW5jeSBHcm91cCIsIHlsYWI9ImF2ZXJhZ2UgcmVzcG9uc2UiKQ0KYGBgDQoNCiMjIyBGdWxsIFJGTSBhbmFseXNpcw0KDQpOb3cgZG8gdGhlIGZ1bGwgUkZNIGFuYWx5c2lzLiBSZW1lbWJlciwgdGhlIGlkZWEgaXMgdGhhdA0KDQoxLiAgV2UgZmlyc3Qgc29ydCBieSBSLCBjcmVhdGUgc2VnbWVudHMuICh3ZSBhbHJlYWR5IGRpZCB0aGlzLikNCjIuICBXaXRoaW4gZWFjaCBSIHNlZ21lbnQsIHdlIHNvcnQgRiBhbmQgY3JlYXRlIFJGIHNlZ21lbnRzLg0KMy4gIFdpdGhpbiBlYWNoIFJGIHNlZ21lbnQsIHdlIHNvcnQgTSBhbmQgY3JlYXRlIFJGTSBzZWdtZW50cy4NCg0KVGhlIHdheSB0byBkbyB0aGlzIGlzIHNsaWdodGx5IGNvbXBsaWNhdGVkOyBJIHdvdWxkIGdpdmUgeW91IHRoaXMgc2NyaXB0IGluIGFuIGV4YW0gb3IgYXNzaWdubWVudC4gWW91IHdvdWxkIG5vdCBoYXZlIHRvIGNvZGUgdGhpcyB1cCB5b3Vyc2VsdmVzLiBGaXJzdCwgd2UgY2hhbmdlIGViZWVyIGludG8gZGF0YS50YWJsZSBXaXRoaW4gZWFjaCBSIGdyb3VwLCB3ZSBjcmVhdGUgRiBncm91cHMgLVw+IFJGIGdyb3Vwcy4gV2l0aGluIGVhY2ggUkYgZ3JvdXAsIHdlIGNyZWF0ZSBNIGdyb3VwcyAtXD4gUkZNIGdyb3Vwcw0KDQpgYGB7ciwgY2FjaGU9VFJVRX0NCg0KbnRpbGVzIDwtIGZ1bmN0aW9uKHgsIGJpbnMpIHsNCiAgcXVhbnRpbGVzID0gc2VxKGZyb209MCwgdG8gPSAxLCBsZW5ndGgub3V0PWJpbnMrMSkNCiAgY3V0KGVjZGYoeCkoeCksYnJlYWtzPXF1YW50aWxlcywgbGFiZWxzPUYpDQp9DQoNCmViZWVyJFJncm91cCA8LSBudGlsZXMoZWJlZXIkUiwgYmlucz01KSAgDQoNCg0KZHQgPSBkYXRhLnRhYmxlKGViZWVyKQ0KbmJpbnMgPSA1DQpkdFssIFJGZ3JvdXAgOj0gcGFzdGUwKGFzLmNoYXJhY3RlcihSZ3JvdXApLCBhcy5jaGFyYWN0ZXIobnRpbGVzKEYsIGJpbnMgPSBuYmlucykpKSwgYnkgPSBjKCdSZ3JvdXAnKV0NCmR0WywgUkZNZ3JvdXAgOj0gcGFzdGUwKGFzLmNoYXJhY3RlcihSRmdyb3VwKSwgYXMuY2hhcmFjdGVyKG50aWxlcyhNLCBiaW5zID0gbmJpbnMpKSksIGJ5ID0gYygnUkZncm91cCcpXQ0KDQojIHB1dCBpdCBiYWNrIHRvIGRhdGEuZnJhbWUNCmViZWVyID0gZGF0YS5mcmFtZShkdCkNCg0KIyBjaGFuZ2UgaXQgdG8gYSBmYWN0b3IgdmFyaWFibGUNCmViZWVyJFJGTWdyb3VwIDwtIGFzLmZhY3RvcihlYmVlciRSRk1ncm91cCkNCg0KYGBgDQoNCkhvdyBtYW55IFJGTSBncm91cHMgZG8gd2UgZ2V0IHdpdGggdGhpcyBwcm9jZWR1cmU/DQoNCmBgYHtyfQ0KbGVuZ3RoKHVuaXF1ZShlYmVlciRSRk1ncm91cCkpDQpgYGANCg0KV2UgaGF2ZSBgciBsZW5ndGgodW5pcXVlKGViZWVyJFJGTWdyb3VwKSlgIFJGTSBzZWdtZW50cy4NCg0KYGBge3J9DQpiYXJwbG90KHRhYmxlKGViZWVyJFJGTWdyb3VwKSwgeGxhYiA9ICJSRk0gc2VnbWVudHMiLCB5bGFiPSJmcmVxdWVuY3kiKQ0KYGBgDQoNCiMjIyBSZXNwb25zZSByYXRlIGJ5IFJGTSBzZWdtZW50DQoNCkxldCdzIG1ha2UgdGhlIHJlc3BvbnNlIHJhdGUgYnkgc2VnbWVudC4NCg0KYGBge3J9DQojIHAgPSBzX3ovbl96DQpyZXNwUkZNIDwtIGViZWVyICU+JSANCiAgZ3JvdXBfYnkoUkZNZ3JvdXApICU+JSANCiAgc3VtbWFyaXNlKG5fcmVzcD0gc3VtKHJlc3BtYWlsLCBuYS5ybSA9IFRSVUUpLCANCiAgICAgICAgICAgIG5fbWFpbD0gc3VtKG1haWxpbmcsIG5hLnJtID0gVFJVRSkpICU+JSANCiAgbXV0YXRlKHJlc3BfcmF0ZSA9IG5fcmVzcC9uX21haWwpICU+JSANCiAgYXJyYW5nZShkZXNjKHJlc3BfcmF0ZSkpICNyZS1vcmRlciBmcm9tIG1vc3QgdG8gbGVhc3QsIG1ha2UgcGljdHVyZSBiZXR0ZXINCnJlc3BSRk0NCmBgYA0KDQojIyMgVGFyZ2V0aW5nIHVzaW5nIFJGTSBhbmFseXNpcw0KDQpOb3cgbGV0J3MgZmlndXJlIG91dCB3aGljaCBzZWdtZW50cyB3ZSBzaG91bGQgdGFyZ2V0LiBXZSB3YW50IHRvIHRhcmdldCBzZWdtZW50cyB0aGF0IGhhdmUgYSByZXNwb25zZSByYXRlIGFib3ZlIHRoZSBicmVha2V2ZW4gcG9pbnQuIFJlbWVtYmVyIHRoZSBicmVha2V2ZW4gcHJvYmFiaWxpdHk6ICQkDQpcYmFye3B9X3tCRX0gPSBcZnJhY3tjfXttfSA9IFxmcmFje1x0ZXh0cm17Y29zdH19e1x0ZXh0cm17bWFyZ2lufX0gDQokJA0KDQpgYGB7ciBicmVha2V2ZW59DQpjID0gMS41DQptID0gNTANCmJyayA9IGMvbQ0KYGBgDQoNCk91ciBicmVha2V2ZW4gcG9pbnQgaXMgYHIgYnJrYC4NCg0KYGBge3J9DQpyZXNwUkZNIDwtIGFzLmRhdGEuZnJhbWUocmVzcFJGTSkNCg0KYnAgPC0gYmFycGxvdChyZXNwUkZNWyw0XSwgDQogICAgICAgICAgICAgIG1haW49InJlc3BvbnNlIGJ5IFJGTSBncm91cCIsIA0KICAgICAgICAgICAgICB4bGFiPSJSRk0gR3JvdXAiLCB5bGFiPSJhdmVyYWdlIHJlc3BvbnNlIiwgeGF4dD0ibiIpDQpheGlzKDEsIGF0ID0gYnBbLDFdLCBsYWJlbHM9cmVzcFJGTVssMV0sIGNleC5heGlzPTAuNywgbGFzPTIpDQoNCmFibGluZShoPWJyaykNCnRleHQoODUsIGJyaywgImJyZWFrZXZlbiIsIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQoNCmBgYA0KDQpIb3cgbWFueSBzZWdtZW50cyBhcmUgYWJvdmUgdGhlIGJyZWFrZXZlbiwgYW5kIHRoZXJlZm9yZSB0YXJnZXRlZD8gV2hhdCBzZWdtZW50cyBhcmUgdGhleT8gQXMgYSBwZXJjZW50YWdlIG9mIHRoZSB0b3RhbCBzZWdtZW50cz8NCg0KYGBge3J9DQojIGhvdyBtYW55IHNlZ21lbnRzIGFib3ZlIGJyZWFrZXZlbj8gd2hpY2ggc2VnbWVudHM/DQpuX3NlZ21lbnRzIDwtIHN1bShyZXNwUkZNJHJlc3BfcmF0ZSA+PSBicmspDQojIGFzIGEgcGVyY2VudGFnZSBvZiBhbGwgc2VnbWVudHMNCnBfc2VnbWVudHMgPC0gc3VtKHJlc3BSRk0kcmVzcF9yYXRlID49IGJyaykgLyBsZW5ndGgodW5pcXVlKGViZWVyJFJGTWdyb3VwKSkNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmNhdCgiT3B0aW1hbCBOdW1iZXIgb2YgU2VnbWVudHMgdG8gdGFyZ2V0IGlzIiwgbl9zZWdtZW50cywgIndoaWNoIGlzIiwgMTAwKnBfc2VnbWVudHMsICIlIG9mIHRvdGFsIHNlZ21lbnRzIikNCmBgYA0KDQojIyMjIFByZWRpY3Rpb24gb2YgUm9sbG91dCBDdXN0b21lcnMNCg0KT0ssIG5vdyBsZXQncyBhcHBseSB0aGlzIG1vZGVsIHRvIHRob3NlIGN1c3RvbWVycyB3aG8gaGF2ZSBub3QgYmVlbiBtYWlsZWQsIHNvbWV0aW1lcyBjYWxsZWQgdGhlICJyb2xsb3V0IiBzYW1wbGUuIFdlIHVzZSBiaW5hcnkgbGluZWFyIHJlZ3Jlc3Npb25zIHRvIHByZWRpY3QuIFRoZSAkXGhhdHtcYmV0YX1feiQgd2lsbCBiZSB0aGUgbWVhbiByZXNwb25zZSByYXRlIG9mIGVhY2ggc2VnbWVudC4NCg0KYGBge3J9DQpSRk1fbW9kZWwgPC0gbG0ocmVzcG1haWwgfiBSRk1ncm91cCwgZGF0YSA9IGViZWVyKQ0KYGBgDQoNCiMjIyBBcHBseWluZyBwcmVkaWN0aW9ucyB0byByb2xsb3V0IGRhdGE6IFJPSQ0KDQpXZSBzZXBhcmF0ZSB0aGUgcm9sbG91dCBkYXRhICh3aGVyZSB0aGVyZSBpcyBubyBtYWlsaW5nKSBmcm9tIGV2ZXJ5dGhpbmcgZWxzZS4gVGhlbiwgd2UnbGwgc2NvcmUgdGhlIG5ldyBkYXRhLCBpLmUuLCBhcHBseSB0aGUgcHJlZGljdGlvbnMgb2YgdGhlIG1vZGVsIHRvIHRoZSBuZXcgZGF0YS4NCg0KYGBge3J9DQplYmVlci5yb2xsb3V0IDwtIGViZWVyW2lzLm5hKGViZWVyJHJlc3BtYWlsKSwgXQ0KDQojI1ByZWRpY3Rpb24NCmViZWVyLnJvbGxvdXQkUkZNcHJlZCA8LSBwcmVkaWN0KFJGTV9tb2RlbCwgZWJlZXIucm9sbG91dCkNCg0Kc3VtbWFyeShlYmVlci5yb2xsb3V0JFJGTXByZWQpDQoNCg0KYGBgDQoNClRoZSBhdmVyYWdlIHByZWRpY3Rpb24gaXMgdGhlIGF2ZXJhZ2UgcmVzcG9uc2UgcmF0ZSB3ZSBmb3VuZCBlYXJsaWVyLiBTbyBtYWtlcyBzZW5zZSBpbiB0ZXJtcyBvZiBmYWNlIHZhbGlkaXR5Lg0KDQpXZSBub3cgaGF2ZSBhIHByb2JhYmlsaXR5IG9mIHJlc3BvbnNlIHRvIGV2ZXJ5b25lIGluIHRoZSByb2xsb3V0IGRhdGEuIEhvdyBtYW55IGN1c3RvbWVycyBpbiB0aGUgcm9sbG91dCB3b3VsZCBnZXQgbWFpbGVkPyBhcyBhIGZyYWN0aW9uIG9mIHRoZSB0b3RhbCwgd2hhdCB3b3VsZCB0aGUgcHJvZml0cyBhbmQgcmV0dXJuIG9uIGludmVzdG1lbnQgKFJPSSkgYmU/DQoNCmBgYHtyfQ0KIyBUb3RhbCBudW1iZXIgb2Ygcm9sbG91dCBjdXN0b21lcnMgd2l0aCBwcmVkaWN0ZWQgcmVzcG9uc2UgcmF0ZXMgYWJvdmUgYnJlYWtldmVuDQpuX2N1c3RvbWVycyA8LSBzdW0oZWJlZXIucm9sbG91dCRSRk1wcmVkID49IGJyaykNCiMgYXMgYSBwcm9wb3J0aW9uIG9mIGFsbCByb2xsb3V0IGN1c3RvbWVycw0KcF9jdXN0b21lcnMgPC0gc3VtKGViZWVyLnJvbGxvdXQkUkZNcHJlZCA+PSBicmspIC8gbGVuZ3RoKGViZWVyLnJvbGxvdXQkUkZNcHJlZCkNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCg0KY2F0KCJPcHRpbWFsIE51bWJlciBvZiBTZWdtZW50cyB0byB0YXJnZXQgaXMiLCBuX2N1c3RvbWVycywgIndoaWNoIGlzIiwgMTAwKnBfY3VzdG9tZXJzLCAiJSBvZiB0b3RhbCBzZWdtZW50cyIpDQpgYGANCg0KYGBge3J9DQojIHByb2ZpdCBwZXIgY3VzdG9tZXINCiMgaWYgcCA+IHBfQkUsIGV4cGVjdGVkIHByb2ZpdCA9IHAqbSAtIGMgIHx8ICBpZiBwIDwgcF9CRSwgPSAwDQplYmVlci5yb2xsb3V0IDwtIGViZWVyLnJvbGxvdXQgJT4lIA0KICBtdXRhdGUoUkZNcHJvZml0ID0gY2FzZV93aGVuKFJGTXByZWQgPj0gYnJrIH4gUkZNcHJlZCptLWMsIA0KICAgIFRSVUUgfiAwKSkNCiMgb3IgcG1heCB0YWtlcyBjb2x1bW53aXNlIG1heGltdW0gKHNhbWUgYXMgaW4gTDIpDQojZWJlZXIucm9sbG91dCRSRk1wcm9maXQgPC0gcG1heChlYmVlci5yb2xsb3V0JFJGTXByZWQgKm0gLSBjLCAwKQ0KDQojIHN1bSBvdmVyIGN1c3RvbWVycw0Kc3VtX3Byb2ZpdCA9IHN1bShlYmVlci5yb2xsb3V0JFJGTXByb2ZpdCkNCg0KIyBzdW0gY29zdHMgb2YgdGFyZ2V0aW5nIGN1c3RvbWVycw0KZWJlZXIucm9sbG91dCRSRk1jb3N0IDwtIGlmZWxzZShlYmVlci5yb2xsb3V0JFJGTXByZWQgPj0gYnJrLCBjLCAwKQ0KDQpzdW1fY29zdCA9IHN1bShlYmVlci5yb2xsb3V0JFJGTWNvc3QpDQoNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCiMgd2hhdCBhYm91dCB0aGUgcmV0dXJuIG9uIGludmVzdG1lbnQgUk9JPw0KY2F0KCJST0k6Iiwgc3VtX3Byb2ZpdCAvIHN1bV9jb3N0KjEwMCwgIiUiKQ0KYGBgDQoNCklmIHdlIHRhcmdldGVkIGV2ZXJ5b25lIGluIHRoZSByb2xsb3V0IGdyb3VwOg0KDQpgYGB7cn0NCmViZWVyLnJvbGxvdXQkYWxsIDwtZWJlZXIucm9sbG91dCRSRk1wcmVkICptIC0gYw0Kc3VtX3Byb2ZpdF9hbGwgPSBzdW0oZWJlZXIucm9sbG91dCRhbGwpDQpzdW1fY29zdF9hbGwgPSBjKmxlbmd0aChlYmVlci5yb2xsb3V0JFJGTXByZWQpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlJPSToiLCBzdW1fcHJvZml0X2FsbCAvIHN1bV9jb3N0X2FsbCoxMDAsICIlIikNCmBgYA0KDQpgYGB7cn0NCnJlc3BSRk0gPC0gcmVzcFJGTSAlPiUgbXV0YXRlKG5fbm9ucmVzcCA9IG5fbWFpbC1uX3Jlc3ApICU+JSByZWxvY2F0ZShuX25vbnJlc3AsIC5hZnRlcj1uX3Jlc3ApDQpgYGANCg0KIyMjIFVzaW5nIGEgQmF5ZXNpYW4gYXBwcm9hY2gNCg0KUmlnaHQgbm93IHdlIGFzc3VtZSB0aGF0IHRoZXNlIHNlZ21lbnRzIHJlc3BvbnNlIHJhdGVzIGFyZSBlbnRpcmVseSBpbmRlcGVuZGVudCBvZiBlYWNoIG90aGVyLiBCdXQgaWYgd2UgbWFrZSBhbiBhc3N1bXB0aW9uIGFib3V0IHRoZSBkaXN0cmlidXRpb24gb2YgcmVzcG9uc2UgcmF0ZXMgYWNyb3NzIHNlZ21lbnRzLCB3ZSBjb3VsZCB1c2UgdGhhdCBjb21tb24gZGlzdHJpYnV0aW9uIHRvICJib3Jyb3ciIGluZm9ybWF0aW9uIGZyb20gdGhlIG90aGVyIHNlZ21lbnRzLg0KDQpgYGB7cn0NCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpoaXN0KHJlc3BSRk0kcmVzcF9yYXRlLCBkZW5zaXR5PTEwLCBicmVha3M9MjAsIG1haW49IkRpc3RyaWJ1dGlvbiBvZiByZXNwb25zZSByYXRlcyBhY3Jvc3Mgc2VnbWVudHMiLCB4bGFiPSJzZWdtZW50LXNwZWNpZmljIHByb2JhYmlsaXR5IG9mIHJlc3BvbnNlIikNCmN1cnZlKGRiZXRhKHgsIC4zLCAzKSwgYWRkID0gVFJVRSwgIHR5cGU9ImwiLCBjb2w9ImdyYXkiKQ0KYGBgDQoNCiMjIyBFbXBpcmljYWwgQmF5ZXMNCg0KJCQgDQpcYmVnaW57YXJyYXl9e2NjbH0NClAoc196fG5feiwgYSwgYikmICA9ICYgXGRpc3BsYXlzdHlsZSB7bl96IFxjaG9vc2Ugc196fSBcZnJhY3tCKGErc196LGIrbl96LXNfeil9e0IoYSxiKX0gDQpcZW5ke2FycmF5fQ0KJCQgXCMjIyMgUHJpb3IgV2Ugc3RhcnQgYnkgZXN0aW1hdGluZyB0aGUgcHJpb3JzIGJvcnJvd2luZyBpbmZvcm1hdGlvbiBmb3JtIG90aGVyIHNlZ21lbnRzLiBXZSB1c2UgKipNTEUqKiBmb3IgdGhpczoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQojaW5zdGFsbC5wYWNrYWdlcygiVkdBTSIpDQojIGEgPSBhICsgc196DQojIGIgPSBiICsgKG5fei1zX3opDQpmaXQgPC0gdmdsbShjYmluZChyZXNwUkZNJG5fcmVzcCxyZXNwUkZNJG5fbm9ucmVzcCkgfiAxLCBiZXRhYmlub21pYWxmZiwgdHJhY2U9VFJVRSkNCg0KYGBgDQoNCmBgYHtyfQ0KQ29lZihmaXQpDQoNCiMgbWFrZSB0aGVtIGEgYW5kIGINCmEgPC0gQ29lZihmaXQpW1sxXV0NCmIgPC0gQ29lZihmaXQpW1syXV0NCmBgYA0KDQpMZXQncyBwbG90IHRoaXMgcHJpb3IgZXN0aW1hdGUgYWdhaW5zdCB0aGUgZGF0YQ0KDQpgYGB7cn0NCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpoaXN0KHJlc3BSRk0kcmVzcF9yYXRlLCBkZW5zaXR5PTEwLCBicmVha3M9MjAsIG1haW49IkRpc3RyaWJ1dGlvbiBvZiByZXNwb25zZSByYXRlcyBhY3Jvc3Mgc2VnbWVudHMiLCB4bGFiPSJzZWdtZW50LXNwZWNpZmljIHByb2JhYmlsaXR5IG9mIHJlc3BvbnNlIikNCmN1cnZlKGRiZXRhKHgsIGEsIGIpLCBhZGQgPSBUUlVFLCAgdHlwZT0ibCIsIGNvbD0icmVkIikNCmN1cnZlKGRiZXRhKHgsIC4zLCAzKSwgYWRkID0gVFJVRSwgIHR5cGU9ImwiLCBjb2w9ImJsdWUiKQ0KYGBgDQoNCiMjIyMgUG9zdGVyaW9yIE1lYW4gUmVzcG9uc2UNCg0KYGBge3J9DQojIHBvc3RlcmlvciBtZWFuIHJlc3BvbnNlIHJhdGUNCnBvc3RfbWVhbl9yZXNwIDwtIChhK3Jlc3BSRk0kbl9yZXNwKS8oYStiK3Jlc3BSRk0kbl9tYWlsKQ0KICAgICAgICAgICAgIA0KIyBhZGQgdGhpcyBhcyBjb2x1bW4gdG8gcmVzcFJGTQ0KcmVzcFJGTSA8LSBjYmluZChyZXNwUkZNLCBwb3N0X21lYW5fcmVzcCkNCg0KI29yZGVyIGZyb20gbG93ZXN0IHRvIGdyZWF0ZXN0DQpyZXNwUkZNIDwtIHJlc3BSRk0gJT4lIGFycmFuZ2UoKHJlc3BfcmF0ZSkpDQoNCmhlYWQocmVzcFJGTSkNCmBgYA0KDQpXZSBwbG90IHRoaXM6DQoNCmBgYHtyfQ0KcGxvdChyZXNwUkZNJHJlc3BfcmF0ZSwgeGF4dD0ibiIsY29sPSJyZWQiLHhsYWI9IlJGTSBzZWdtZW50cyIseWxhYj0icmVzcG9uc2UgcmF0ZSAoeC9uKSBhbmQgcG9zdGVyaW9yIG1lYW4gcmVzcG9uc2UgcmF0ZSIpDQpwb2ludHMocmVzcFJGTSRwb3N0X21lYW5fcmVzcCwgY29sPSdibHVlJykNCmxlZ2VuZCgndG9wbGVmdCcsbGVnZW5kPWMoImVzdGltYXRlIHJlc3BvbnNlIHJhdGUiLCAicG9zdGVyaW9yIGV4cGVjdGVkIHJlc3BvbnNlIHJhdGUiKSxjb2w9YygicmVkIiwiYmx1ZSIpLCBwY2g9MSkNCmF4aXMoMSwgYXQgPSAxOjkwLCBsYWJlbHM9cmVzcFJGTSRSRk1ncm91cCwgY2V4LmF4aXM9MC43LCBsYXM9MikNCmFibGluZShoPWJyaykNCnRleHQoODUsIGJyaywgImJyZWFrZXZlbiIsIGNleD0xLCBwb3M9MywgY29sPSJibGFjayIpDQoNCmBgYA0KDQpBcmUgdGhlcmUgYW55IHN3aXRjaGVzIHdlIHdvdWxkIG1ha2UgdXNpbmcgdGhlIHBvc3RlcmlvciBtZWFuIHJhdGhlciB0aGFuIHRoZSBhY3R1YWwgbWVhbiB0byB0YXJnZXQgc2VnbWVudHM/DQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KY2F0KCJVc2luZyB0aGUgcG9zdGVyaW9yIG1lYW4gdG8gdGFyZ2V0IHNlZ21lbnRzIGxlYWRzIHRvIiwgc3VtKHJlc3BSRk0kcG9zdF9tZWFuX3Jlc3A+PWJyayksICJzZWdtZW50cywgd2hlcmVhcyB1c2luZyB0aGUgYWN0dWFsIG1lYW4gbGVhZHMgdG8iLCBzdW0ocmVzcFJGTSRyZXNwX3JhdGU+PWJyaykgKQ0KYGBgDQo=