The goal of this document is to show how data science can generate real value for business. Here we present different analytic’s solutions that help managers to understand and segment their customers based on the purchase history.


1 Data science problem

Our analytic’s solution depends on our understanding of the business problem. The better we grasp how the business works and what challenges it face, the better we can answer its need. In this first part, we present our analytic’s solution:


1.1 Business understanding

Our business is a medium sized store based in a large city. Within it 20 years of existence, our business never have done any kind of analytics. But since few years, the shop is not growing as wanted, so the new manager decided to study the customer’ behaviours.

The manager is aware that his company is sitting on a treasure trove of data and simply lack the skills and people to analyse and exploit them efficiently. He ask us to look into them.




1.2 Problem

The sales are not growing as planned. Since few years the store has proposed new offers and get a new parking garage but the sales continued to grow too slowly for the store to achieve critical economy of scale.

On the analytic side, the store has nothing. They don’t know who their customers are and where they should spend their marketing budget.




1.3 Solution

Digging insight from the store’s data will enable us to visualise the customer’s behaviour in a new way, and create targeted marketing segmentations and campaigns, based on a data-driven strategy.

After having seen the available data, we propose three marketing analysis’ methods: segmentation, scoring models and customer lifetime value. Segmentation is about understanding your customers, scoring models are about targeting the right ones, and customer lifetime value is about anticipating their future value.




2 Data exploration

Our first goal is to understand the data that will be used to build each step of our analysis, and our second goal is to assess where the analysis might lack data or where the quality of it might suffer.

2.1 The data quality report

Our database present the orders processed from 2006 to 2016. There are three variables:

##   Customer_id       Date Total
## 1        5540 2006-01-02   130
## 2        8150 2006-01-02    26
## 3       10210 2006-01-02    26
## 4       14140 2006-01-04    26
## 5        5640 2006-01-04    39
## 6       14150 2006-01-06   273

Here we describe the characteristics of our three variables. In 10 years we have had 264 200 customers that spent on average $80. We can see two issues in the data that we have to handle. One is the 428 missing values and the second is the outliers. As the outliers drive the mean up, this metric become irrelevant to measure the central tendency because our dataset is to much skewed by the few outliers. Here, the median is better to understand our distributions parameters.

##   Customer_id          Date                Total         
##  Min.   :    10   Min.   :2006-01-02   Min.   :-4500.00  
##  1st Qu.: 57720   1st Qu.:2010-01-17   1st Qu.:   32.50  
##  Median :102440   Median :2012-11-22   Median :   39.00  
##  Mean   :108935   Mean   :2012-07-13   Mean   :   82.37  
##  3rd Qu.:160525   3rd Qu.:2014-12-29   3rd Qu.:   78.00  
##  Max.   :264200   Max.   :2016-12-30   Max.   :24530.00  
##                                        NA's   :474



2.2 First visualisations

# Load the SQL library 
library(sqldf)

# Select only the year as a numeric
Orders$YOrder = as.numeric(format(Orders$Date, "%Y"))
 
# Number of orders per year
Number_of_Orders = sqldf("SELECT YOrder, COUNT(YOrder) AS 'counter' FROM Orders GROUP BY 1 ORDER BY 1")

# Average "order total"" per year
AvgTotal = sqldf("SELECT YOrder, AVG(Total) AS 'Total' FROM Orders GROUP BY 1 ORDER BY 1")

par(mfrow=c(1,2))
barplot(Number_of_Orders$counter, names.arg = Number_of_Orders$YOrder, main="Number of Orders", col="#2C3E50")
barplot(AvgTotal$Total, names.arg = AvgTotal$YOrder, main = "Average per Orders ($)", col="#2C3E50")




2.3 Handling data issues


2.3.1 Missing Values

Let’s count the missing values per columns and see how they are distributed within the dataset:

## Customer_id        Date       Total      YOrder 
##           0           0         474           0
##       Customer_id Date YOrder Total    
## 50769           1    1      1     1   0
##   474           1    1      1     0   1
##                 0    0      0   474 474
aggr(Orders, prop = F, numbers = T)

Our missing values are only found in the total column, not within the date or the customer ID column. After discussing with the manager we learn that is due to a change of their ERP system. To handle theses missing values we decide to replace them with the median as the mean was too much influenced by the outliers. Now we have no more missing values:

## Customer_id        Date       Total      YOrder 
##           0           0           0           0



2.3.2 Outliers

boxplot(Total ~ Date, data=Orders, main="Outliers")  # clear pattern is noticeable.

After presenting this to the manager he told us we could remove the outliers and the negative value. So, here are now the data we will work with:

##   Customer_id          Date                Total             YOrder    
##  Min.   :    10   Min.   :2006-01-02   Min.   :   6.50   Min.   :2006  
##  1st Qu.: 57735   1st Qu.:2010-01-17   1st Qu.:  32.50   1st Qu.:2010  
##  Median :102440   Median :2012-11-22   Median :  39.00   Median :2012  
##  Mean   :108939   Mean   :2012-07-13   Mean   :  80.83   Mean   :2012  
##  3rd Qu.:160525   3rd Qu.:2014-12-29   3rd Qu.:  71.50   3rd Qu.:2014  
##  Max.   :264200   Max.   :2016-12-30   Max.   :5850.00   Max.   :2016



3 Segementation

We can’t treat all our customers in the same way, offer them the same product or charge the same price, because this leave too much value on the table. We need to build relevant segment of customers and use them to improve our relationship, offers and campaigns. Indeed, segmentation summarise efficiently mountains of data and make it usable.

A good segmentation gather similar entities together and separate different one. It enable decision maker to treat the customer’ segments differently without going down to the individual level, which will be to hard to manage.

Once a segment is done, we need to describe it in simple terms. This enable managers to see the customer’ differences of needs, desires and habits. Then, they can customise offerings, adapt customer’ messages and optimise marketing campaigns.

3.1 Statistical segmentation

Customers have to be similar, but similar on which variables? Well, it depends of the business and the managerial question we are asking. Here our segmentation variables will be the recency, frequency, and monetary value, and our managerial goal is to understand the customer’ behaviours and values. Also we are limited by the amount of data available. For example we don’t have any information about each customers age or sex.

3.1.1 RFM segmentation

These three variables are good predictors of the future customer’ profitability and are easy to compute from the database presented above:

# Compute the number of days from the last order (2017-01-01) and name this variable lastorders
Orders$lastorders= as.numeric(difftime(time1 = "2017-01-01", time2 = Orders$Date,units = "days"))

# Compute recency, frequency, and average order amount
rfmOrders = sqldf("SELECT Customer_id,
                          MIN(lastorders) AS 'recency',
                          COUNT(*) AS 'frequency',
                          AVG(Total) AS 'avgorder'
                   FROM Orders GROUP BY 1")

# Arrange per frequency
rfmOrders <- rfmOrders %>% arrange(desc(frequency))

# Display the first lines
head(rfmOrders)
##   Customer_id    recency frequency avgorder
## 1        9720  30.208333        45 57.34444
## 2      109370   2.208333        41 22.03659
## 3      119430 302.208333        38 19.18868
## 4       10720  35.208333        37 34.08108
## 5        1420  25.208333        34 65.57353
## 6       10640  37.208333        34 21.53412


Each of these variable answer a key question:

  • Recency – How recent is the customer’s latest purchase?
  • Frequency – How often did they purchase?
  • Monetary Value – How much do they spend on average?

Now that we have created the three variables, we can visualise our new distributions of the total and average order per customer:

par(mfrow=c(1,2))
# Plot the distribution of the frequency ~ Total
hist(Orders$Total, breaks = 80, main = "Orders", xlab = "Total", col="#2C3E50")
# Plot the distribution of the frequency ~ Avg order
hist(rfmOrders$avgorder, breaks = 80, main = "Average Orders (per customers)", xlab = "Average Orders (per customers)", col="#2C3E50")


As our data aren’t at the same scale, we risk to find irrelevant patterns and build wrong segmentation. That’s why we need to de-skew our distribution and make it more normal. Indeed, transforming data is crucial as it enable us to meet the assumptions our analysis require:

# Use the logarithm for all the variables:
RerfmOrders <- rfmOrders
RerfmOrders$frequency <- log(rfmOrders$frequency)
RerfmOrders$recency <- log(rfmOrders$recency)
RerfmOrders$avgorder <- log(rfmOrders$avgorder)
# Print the first lines
head(RerfmOrders)
##   Customer_id   recency frequency avgorder
## 1        9720 3.4081178  3.806662 4.049076
## 2      109370 0.7922381  3.713572 3.092704
## 3      119430 5.7111166  3.637586 2.954321
## 4       10720 3.5612828  3.610918 3.528742
## 5        1420 3.2271746  3.526361 4.183172
## 6       10640 3.6165328  3.526361 3.069639
# Have a look on the summary
summary(RerfmOrders)
##   Customer_id        recency         frequency         avgorder    
##  Min.   :    10   Min.   :0.7922   Min.   :0.0000   Min.   :1.872  
##  1st Qu.: 81990   1st Qu.:5.5021   1st Qu.:0.0000   1st Qu.:3.376  
##  Median :136430   Median :6.9765   Median :0.6931   Median :3.664  
##  Mean   :137574   Mean   :6.2743   Mean   :0.6656   Mean   :3.844  
##  3rd Qu.:195100   3rd Qu.:7.6644   3rd Qu.:1.0986   3rd Qu.:4.174  
##  Max.   :264200   Max.   :8.2978   Max.   :3.8067   Max.   :8.674
# Plot row
par(mfrow=c(1,1))
# Plot the distribution of the log
hist(RerfmOrders$avgorde, breaks = 25, main = "Log (Average Orders (per customers)) ", xlab = "Log (Average Orders (per customers))", col="#2C3E50")

There are 3 other methods to transform your data:

  • Normalizing (or standardizing): subtract your data by the mean and divide it by the standard deviation. By doing that, you will obtain the “standard score”. Your data will be adjusted to a common scale, and it will help you to compare your data in a meaningful way.
  • Scaling (Min-Max scaling): alternatively, you could use the Min-Max scaling. It come in R with a very simple function scale(). Your data will be scaled in a range from 0 to 1 (or -1 to 1).
  • Create bucket: also, we could create a new variable that reinterpret the three variables by creating buckets. Of course this technic let a lot of information behind.



3.1.2 Customer clustering

Now we can create five clusters of customers using our 3 rescaled variables. It’s important to notice that without the rescaling process we might find very different clusters than the one we display below.

# 1. Run K-means (nstart = 20) and 5 different groups
RerfmOrders <- RerfmOrders %>% filter(frequency>0)
RerfmOrders_ <- RerfmOrders %>% select(recency:avgorder)
RerfmOrders_km <- kmeans(RerfmOrders_, centers = 5, nstart = 20)

# Plot using plotly 
library(plotly)
#p <- plot_ly(RerfmOrders, x = RerfmOrders$recency, y = RerfmOrders$avgorder, z = frequency, type = "scatter3d", mode = "markers", color=RerfmOrders_km$cluster)%>% layout(showlegend = FALSE)
#p %>% layout(showlegend = FALSE)
#print(p)
#p <- plot

If there is a good separation on the recency variables, the separation is more nuanced for the frequency and the average ordered varibles.

RerfmOrders_km$centers
##    recency frequency avgorder
## 1 1.331202 1.5977582 4.206890
## 2 6.925577 1.2462405 4.979758
## 3 3.669905 1.6521907 4.105000
## 4 5.691762 1.4611416 3.843151
## 5 7.427947 0.9981392 3.561406

Conclusion: these clustering methods enable us to create targeted marketing segments. Now we need to think on how to exploit them.




3.2 Managerial segmentation

From the segmentation obtained above we want to build segments of customers that are easily manageable. We are working with the same database and adding a variable that take the first purchase of every customers to assess their loyalty:

##   Customer_id   recency first_purchase frequency    amount
## 1          10 3830.2083       3830.208         1  39.00000
## 2          80  344.2083       3752.208         7  92.85714
## 3          90  759.2083       3784.208        10 150.54000
## 4         120 1402.2083       1402.208         1  26.00000
## 5         130 2971.2083       3711.208         2  65.00000
## 6         160 2964.2083       3578.208         2  39.00000

To limit managerial complexity we restraint the number of segments created, but to enhance the value of our segmentation we create at least 4 segments that are usable to managers. Of course as the optimal segmentation solution will vary over time we will have to re-run our code to update our segmentation.

We now define as active a customer who purchased something within the last 12 months, as warm a customer whose last purchase happens a year before, that is between 13 and 24 months. We qualify as cold, a customer whose last purchase was between two and three years ago. For those who haven’t purchased anything for more than three years, we qualify them as inactive.

customers_2016$segment = "NA"
customers_2016$segment[which(customers_2016$recency > 365*3)] = "inactive"
customers_2016$segment[which(customers_2016$recency <= 365*3 & customers_2016$recency > 365*2)] = "cold"
customers_2016$segment[which(customers_2016$recency <= 365*2 & customers_2016$recency > 365*1)] = "warm"
customers_2016$segment[which(customers_2016$recency <= 365)] = "active"
Cust_2015 <-aggregate(x = customers_2016[, 2:5], by = list(customers_2016$segment), mean)
names(Cust_2015)[names(Cust_2015)=="Group.1"] <- "Segments"
head(Cust_2015)
##   Segments   recency first_purchase frequency   amount
## 1   active  100.9490       1467.051  4.560393 93.60942
## 2     cold  859.0564       1433.468  2.303365 67.24480
## 3 inactive 2179.2029       2547.223  1.814390 62.33179
## 4     warm  491.1481       1320.799  2.871808 90.39982

Now, we create segments called low or high value, and underline our new customers by calling them new warm or new active customers. Here are all our final segments and the number of customers within each segments:

# Complete segment solution using which, and exploiting previous test as input
customers_2016$segment = "NA"
customers_2016$segment[which(customers_2016$recency > 365*3)] = "inactive"
customers_2016$segment[which(customers_2016$recency <= 365*3 & customers_2016$recency > 365*2)] = "cold"
customers_2016$segment[which(customers_2016$recency <= 365*2 & customers_2016$recency > 365*1)] = "warm"
customers_2016$segment[which(customers_2016$recency <= 365)] = "active"
customers_2016$segment[which(customers_2016$segment == "warm" & customers_2016$first_purchase <= 365*2)] = "new warm"
customers_2016$segment[which(customers_2016$segment == "warm" & customers_2016$amount < 100)] = "warm low value"
customers_2016$segment[which(customers_2016$segment == "warm" & customers_2016$amount >= 100)] = "warm high value"
customers_2016$segment[which(customers_2016$segment == "active" & customers_2016$first_purchase <= 365)] = "new active"
customers_2016$segment[which(customers_2016$segment == "active" & customers_2016$amount < 100)] = "active low value"
customers_2016$segment[which(customers_2016$segment == "active" & customers_2016$amount >= 100)] = "active high value"

# Re-order factor in a way that makes sense
customers_2016$segment = factor(x = customers_2016$segment, levels = c("inactive", "cold",
                                                             "warm high value", "warm low value", "new warm",
                                                             "active high value", "active low value", "new active"))
table(customers_2016$segment)
## 
##          inactive              cold   warm high value    warm low value 
##              9159              1902               171               849 
##          new warm active high value  active low value        new active 
##               938               795              3091              1512
pie(table(customers_2016$segment), col = topo.colors(24))

Our final segmentation is composed of 8 segments. Such insights can improve managerial decisions on every levels. We see how much customers are different and how they should be treated differently.

Cust_full_2015 <-aggregate(x = customers_2016[, 2:5], by = list(customers_2016$segment), mean)
names(Cust_full_2015)[names(Cust_full_2015)=="Group.1"] <- "Segments"
print(Cust_full_2015)
##            Segments    recency first_purchase frequency    amount
## 1          inactive 2179.20287     2547.22340  1.814390  62.33179
## 2              cold  859.05639     1433.46806  2.303365  67.24480
## 3   warm high value  459.01535     2045.02705  4.959064 329.42552
## 4    warm low value  476.22482     2062.07170  4.469965  46.47849
## 5          new warm  510.51324      517.83093  1.044776  86.57873
## 6 active high value   91.91022     2051.92531  6.075472 255.67906
## 7  active low value  110.48883     1989.62567  5.890003  48.66532
## 8        new active   86.19907       91.22222  1.045635 100.27400

Here is the revenue from each segment since two years. We can repeat this process and see which customers is changing segment and when. For example when a customer went from “active high value” to “low value” we should see if we can do anything about that.

par(mfrow=c(1,1))
since2years <- customers_2016 %>% filter(recency<720)
since2years <- since2years %>% group_by(segment) %>% summarise(sum=sum(amount,na.rm=TRUE))
barplot(since2years$sum, names.arg = since2years$segment, col="#2C3E50")

Conclusion: with these segmentation methods, we can treat different customer, differently. For example we can send special offers to recently acquired customers, or meet them in person to push them to become loyal. Also we now see which customers are high value and which one aren’t. That crucial for developing valuable relationship.

We know how many customers there are within each segments and can visualise when a customer is changing segment. We could even lay out a narrative for each segment like, “I’m John, I’m 52 years old and I made my first purchase three months ago for a total of $20, and I wonder whether I’d make a new purchase in the future.”

A great segmentation find a good balance between usability and completeness, between simplifying enough so it remains usable and not simplifying too much, so it’s still valuable. As much as we can, segment have to be similar, measurable, and accessible. To say it in another way segments have to be statistically relevant and managerially relevant.




4 Targeting and scoring

Here we build a model to predict how much money our customers are going to spend over the next 12 months. We use the same database than above and we compute three new variables: maximal amount spend, revenue from 2016, and a binary variable that answer if a customer bought anything in 2016 (1) or nothing (0).

# Extract the predictors: (from 2015)
customers_2015 = sqldf("SELECT customer_id,
                               MIN(days_since) - 365 AS 'recency',
                               MAX(days_since) - 365 AS 'first_purchase',
                               COUNT(*) AS 'frequency',
                               AVG(Total) AS 'avg_amount',
                               MAX(Total) AS 'max_amount'
                        FROM Orders
                        WHERE days_since > 365
                        GROUP BY 1")

# Compute revenues generated by customers in 2016
revenue_2016 = sqldf("SELECT customer_id, SUM(Total) AS 'revenue_2016'
                      FROM Orders
                      WHERE year_of_purchase = 2016
                      GROUP BY 1")

# Merge 2015 customers and 2016 revenue
in_sample = merge(customers_2015, revenue_2016, all.x = TRUE)
in_sample$revenue_2016[is.na(in_sample$revenue_2016)] = 0
in_sample$active_2015 = as.numeric(in_sample$revenue_2016 > 0)

# Display calibration (in-sample) data
head(in_sample)
##   Customer_id   recency first_purchase frequency avg_amount max_amount
## 1          10 3465.2083       3465.208         1      39.00       39.0
## 2          80  303.2083       3387.208         6      91.00      104.0
## 3          90  394.2083       3419.208        10     150.54      198.9
## 4         120 1037.2083       1037.208         1      26.00       26.0
## 5         130 2606.2083       3346.208         2      65.00       78.0
## 6         160 2599.2083       3213.208         2      39.00       39.0
##   revenue_2016 active_2015
## 1            0           0
## 2          104           1
## 3            0           0
## 4            0           0
## 5            0           0
## 6            0           0
summary(in_sample)
##   Customer_id        recency         first_purchase       frequency     
##  Min.   :    10   Min.   :   2.208   Min.   :   2.208   Min.   : 1.000  
##  1st Qu.: 77710   1st Qu.: 259.208   1st Qu.: 797.208   1st Qu.: 1.000  
##  Median :127140   Median : 891.208   Median :1892.208   Median : 2.000  
##  Mean   :127315   Mean   :1123.817   Mean   :1789.618   Mean   : 2.664  
##  3rd Qu.:181800   3rd Qu.:1869.208   3rd Qu.:2696.208   3rd Qu.: 3.000  
##  Max.   :245840   Max.   :3650.208   Max.   :3652.208   Max.   :40.000  
##    avg_amount        max_amount       revenue_2016      active_2015    
##  Min.   :   6.50   Min.   :   6.50   Min.   :   0.00   Min.   :0.0000  
##  1st Qu.:  28.17   1st Qu.:  32.50   1st Qu.:   0.00   1st Qu.:0.0000  
##  Median :  39.00   Median :  39.00   Median :   0.00   Median :0.0000  
##  Mean   :  72.03   Mean   :  85.35   Mean   :  27.58   Mean   :0.2299  
##  3rd Qu.:  65.00   3rd Qu.:  78.00   3rd Qu.:   0.00   3rd Qu.:0.0000  
##  Max.   :5850.00   Max.   :5850.00   Max.   :5850.00   Max.   :1.0000

We now have 7 variables that by themselves are not saying much, but when combined together tell an interesting story. And to graps this story we are going to create a calibration model.

4.1 Calibrate the probability model

library(nnet)
prob.model = multinom(formula = active_2015 ~ recency + first_purchase + frequency + avg_amount + max_amount,
                      data = in_sample)
## # weights:  7 (6 variable)
## initial  value 11717.653087 
## iter  10 value 6232.735194
## final  value 6184.162328 
## converged

The model predict customer’ probabilities. Here the importance of each predictor is shown by what we call weights and their statistical significance. If the weights are large it means they are good predictors. If not, it means they contribute very little to the predictions.

coef = summary(prob.model)$coefficients
std  = summary(prob.model)$standard.errors

# Ratio 
print(coef / std)
##    (Intercept)        recency first_purchase      frequency     avg_amount 
##    -12.0208933    -32.7428558     -0.3052296     14.8451174      1.1274078 
##     max_amount 
##     -0.5583795

Our results show to which extent each parameters are significant. We see that recency and frequency are the most meaningful predictor in our model.

4.2 Calibrate the monetary model

Now for our monetary model, we need to select only those who made a purchase. Here we try to predict how much -active customers- are going to spend next year. Note that we are using the logarithmic function to reduce the influence power from the few outliers.

# Select only active customer: 
z = which(in_sample$active_2015 == 1)

# Calibrate the monetary model, using a log-transform
amount.model = lm(formula = log(revenue_2016) ~ log(avg_amount) + log(max_amount), data = in_sample[z, ])
summary(amount.model)
## 
## Call:
## lm(formula = log(revenue_2016) ~ log(avg_amount) + log(max_amount), 
##     data = in_sample[z, ])
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.7891 -0.1802 -0.0742  0.1851  3.5658 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      0.38312    0.04282   8.946   <2e-16 ***
## log(avg_amount)  0.54477    0.04161  13.091   <2e-16 ***
## log(max_amount)  0.39295    0.03787  10.375   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.4782 on 3883 degrees of freedom
## Multiple R-squared:  0.6925, Adjusted R-squared:  0.6924 
## F-statistic:  4373 on 2 and 3883 DF,  p-value: < 2.2e-16
# Plot the results of this new monetary model
## The fitted values are the value predicted by the model
plot(x = log(in_sample[z, ]$revenue_2016), y = amount.model$fitted.values, col="#2C3E50", xlab = "revenue_2016", ylab = "fitted.values")

4.3 Apply the models

We are predicting two things. The first is the probability that an active customer will buy and the second is the amount they will spent.

First we compute the RFM variables of today -note that we now work from the full database and not just a sample-, then we predict the target variables based on today’s data. Here is our predicted probabilities.

customers_2016 = sqldf("SELECT customer_id,
                               MIN(days_since) AS 'recency',
                               MAX(days_since) AS 'first_purchase',
                               COUNT(*) AS 'frequency',
                               AVG(Total) AS 'avg_amount',
                               MAX(Total) AS 'max_amount'
                        FROM Orders GROUP BY 1")

customers_2016$prob_predicted = predict(object = prob.model, newdata = customers_2016, type = "probs")
# To get the real value from the logarithm we have to use the exponant:
customers_2016$revenue_predicted = exp(predict(object = amount.model, newdata = customers_2016))
customers_2016$score_predicted = customers_2016$prob_predicted * customers_2016$revenue_predicted

# Predicted probabilities:
summary(customers_2016$prob_predicted)
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 0.0002629 0.0126336 0.1061096 0.2249887 0.3978301 0.9999048

Now we see the revenue predicted by our model. On average our customers will spend $ 45. We also have more information about the distribution:

##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##    8.485   38.138   45.536   85.305   73.664 4999.308

The third things we predict is called score predicted and its the product of the two first values predicted. It’s the average for every customer next year. The distribution of this spending goes from 0 to extreme values.

##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##    0.004    0.591    5.919   24.508   23.341 3715.838

This last figure is important as it tells us how many customers have an expected revenue of more than $50. We can even see who these customers are, and lay out a special relationship with them. Now we see that there are 2006 customers that have an expected revenue of more than $50, and we print the first customer_id of this new group:

z = which(customers_2016$score_predicted > 50)
print(length(z))
## [1] 2006
## [1]  2  3 13 18 32 39



5 Customer lifetime value

The point of a customer lifetime value models -or CLV- is to see what is on average the value of each customer from the first purchase to the last. The goal of such methods is to analyse what is happening today and what has happened in the recent past in order to predict the revenues customers will generate in the future.

Customer lifetime value models have many other applications in practice. For instance, you could compare one acquisition campaign to another, or the full life time value of different customer segments.

5.1 Compute transition matrix

This transition matrix show how many customers switch from one segment to another. The rows display 2015 and the column display 2016. So, for instance, we can say that 49 inactive customers in 2015, became active high value in 2016. The next step is to understand why.

new_data = merge(x = customers_2015, y = customers_2016, by = "Customer_id", all.x = TRUE)
transition = table(new_data$segment.x, new_data$segment.y)
print(transition)
##                    
##                     inactive cold warm high value warm low value new warm
##   inactive              7227    0               0              0        0
##   cold                  1932    0               0              0        0
##   warm high value          0  108               0              0        0
##   warm low value           0  655               0              0        0
##   new warm                 0 1139               0              0        0
##   active high value        0    0             171              0        0
##   active low value         0    0               0            849        0
##   new active               0    0               0              0      938
##                    
##                     active high value active low value new active
##   inactive                         48              237          0
##   cold                             30              192          0
##   warm high value                  44                0          0
##   warm low value                    5              254          0
##   new warm                         21               90          0
##   active high value               495               13          0
##   active low value                 49             1909          0
##   new active                      103              396          0

The last line display the new active customers. It’s interesting to see that most of them after their first purchase become new warm and not active high value. This line is relevant if we want to assess the results of an acquisition campaign.

Now, to see the percentage behind this transition matrix we need to divide each row by its sum. And we obtain the matrix below. We can say for example, that if you were an inactive customer in 2015, then you had a 96 % change of remaining inactive. This matrix will be useful to make predictions.

transition = transition / rowSums(transition)
print(transition)
##                    
##                        inactive        cold warm high value warm low value
##   inactive          0.962060703 0.000000000     0.000000000    0.000000000
##   cold              0.896935933 0.000000000     0.000000000    0.000000000
##   warm high value   0.000000000 0.710526316     0.000000000    0.000000000
##   warm low value    0.000000000 0.716630197     0.000000000    0.000000000
##   new warm          0.000000000 0.911200000     0.000000000    0.000000000
##   active high value 0.000000000 0.000000000     0.251840943    0.000000000
##   active low value  0.000000000 0.000000000     0.000000000    0.302458140
##   new active        0.000000000 0.000000000     0.000000000    0.000000000
##                    
##                        new warm active high value active low value
##   inactive          0.000000000       0.006389776      0.031549521
##   cold              0.000000000       0.013927577      0.089136490
##   warm high value   0.000000000       0.289473684      0.000000000
##   warm low value    0.000000000       0.005470460      0.277899344
##   new warm          0.000000000       0.016800000      0.072000000
##   active high value 0.000000000       0.729013255      0.019145803
##   active low value  0.000000000       0.017456359      0.680085501
##   new active        0.652748782       0.071677105      0.275574113
##                    
##                      new active
##   inactive          0.000000000
##   cold              0.000000000
##   warm high value   0.000000000
##   warm low value    0.000000000
##   new warm          0.000000000
##   active high value 0.000000000
##   active low value  0.000000000
##   new active        0.000000000

5.2 Make predictions

We can see which customers will go from one segment to the next in the coming years. Our model compute the 3 next years -note that we didn’t take into account the new customers in 2016 and 2017-. Below we present the bar plot of this evolution for 4 segments:

# Initialize a matrix with the number of customers in each segment today and after 3 periods
segments = matrix(nrow = 8, ncol = 4)
segments[, 1] = table(customers_2016$segment)
colnames(segments) = 2017:2020
row.names(segments) = levels(customers_2016$segment)

# Compute for each an every period
for (i in 2:4) {
   segments[, i] = segments[, i-1] %*% transition
}

# Display how segments will evolve over time
print(round(segments))
##                   2017  2018  2019  2020
## inactive          9159 10517 11540 12637
## cold              1902  1585  1712   875
## warm high value    171   200   226   222
## warm low value     849   935   997   926
## new warm           938   987     0     0
## active high value  795   897   880   864
## active low value  3091  3296  3063  2893
## new active        1512     0     0     0

We now compute the revenue per segment for the three coming years. To do that we use our transition matrix and the average order per segment. (Note that we still don’t take into account the new customer that we will start buying in 2017 and after.)

yearly_revenue = c(0, 2, 60, 3, 10, 370, 60, 90)
revenue_per_segment = yearly_revenue * segments
print(revenue_per_segment)
##                     2017       2018       2019       2020
## inactive               0      0.000      0.000      0.000
## cold                3804   3169.249   3423.095   1749.774
## warm high value    10260  12012.813  13551.299  13301.026
## warm low value      2547   2804.694   2990.711   2778.992
## new warm            9380   9869.562      0.000      0.000
## active high value 294150 331821.905 325693.640 319526.868
## active low value  185460 197760.328 183760.424 173604.449
## new active        136080      0.000      0.000      0.000



6 Conclusion

This analysis is a first step toward a better customer’s oriented business. Now, the managers can customise their offering, adapt their messages and optimise their marketing campaigns much better than few months ago. But they can also measure their results in a more accurate manner.

So… What’s next? More analytics will require more data about the products sold and about the customers. We could run an association rule algorithm to see what products tends to be bought together and therefore reorganised the store relevantly. On the customer side, it will be useful to know the location, sex and age of customers. For example woman and man might have very different buying behaviour and that is important to notice.

Finally, we propose to the store a plan to gather more data about their customer and we present the next possible step toward a more data-driven business. We show how applied machine learning with bigger dataset will improve the predictability and segmentation for the store.

LS0tCnRpdGxlOiA8Y2VudGVyPiBDdXN0b21lciBBbmFseXNpcyA8L2NlbnRlcj4Kb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBqb3VybmFsCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgo8YnIvPgoKPGNlbnRlcj4gPGg1PlRoZSBnb2FsIG9mIHRoaXMgZG9jdW1lbnQgaXMgdG8gc2hvdyAqKmhvdyBkYXRhIHNjaWVuY2UgY2FuIGdlbmVyYXRlIHJlYWwgdmFsdWUgZm9yIGJ1c2luZXNzLioqIEhlcmUgd2UgcHJlc2VudCBkaWZmZXJlbnQgYW5hbHl0aWMncyBzb2x1dGlvbnMgdGhhdCBoZWxwIG1hbmFnZXJzIHRvIHVuZGVyc3RhbmQgYW5kIHNlZ21lbnQgdGhlaXIgY3VzdG9tZXJzIGJhc2VkIG9uIHRoZSBwdXJjaGFzZSBoaXN0b3J5LiA8L2g1PiA8L2NlbnRlcj4KCjxoci8+Cjxici8+CgojIERhdGEgc2NpZW5jZSBwcm9ibGVtIHsudGFic2V0fQoKT3VyIGFuYWx5dGljJ3Mgc29sdXRpb24gZGVwZW5kcyBvbiBvdXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgYnVzaW5lc3MgcHJvYmxlbS4gVGhlIGJldHRlciB3ZSBncmFzcCBob3cgdGhlIGJ1c2luZXNzIHdvcmtzIGFuZCB3aGF0IGNoYWxsZW5nZXMgaXQgZmFjZSwgdGhlIGJldHRlciB3ZSBjYW4gYW5zd2VyIGl0cyBuZWVkLiBJbiB0aGlzIGZpcnN0IHBhcnQsIHdlIHByZXNlbnQgb3VyICoqYW5hbHl0aWPigJlzIHNvbHV0aW9uKio6Cgo8YnIvPgoKIyMgQnVzaW5lc3MgdW5kZXJzdGFuZGluZwoKT3VyIGJ1c2luZXNzIGlzIGEgbWVkaXVtIHNpemVkIHN0b3JlIGJhc2VkIGluIGEgbGFyZ2UgY2l0eS4gV2l0aGluIGl0IDIwIHllYXJzIG9mIGV4aXN0ZW5jZSwgb3VyIGJ1c2luZXNzIG5ldmVyIGhhdmUgZG9uZSBhbnkga2luZCBvZiBhbmFseXRpY3MuIEJ1dCBzaW5jZSBmZXcgeWVhcnMsIHRoZSBzaG9wIGlzIG5vdCBncm93aW5nIGFzIHdhbnRlZCwgc28gdGhlIG5ldyBtYW5hZ2VyIGRlY2lkZWQgdG8gc3R1ZHkgdGhlIGN1c3RvbWVy4oCZIGJlaGF2aW91cnMuCgpUaGUgbWFuYWdlciBpcyBhd2FyZSB0aGF0IGhpcyBjb21wYW55IGlzIHNpdHRpbmcgb24gYSB0cmVhc3VyZSB0cm92ZSBvZiBkYXRhIGFuZCBzaW1wbHkgKipsYWNrIHRoZSBza2lsbHMgYW5kIHBlb3BsZSB0byBhbmFseXNlIGFuZCBleHBsb2l0IHRoZW0gZWZmaWNpZW50bHkuKiogSGUgYXNrIHVzIHRvIGxvb2sgaW50byB0aGVtLgoKPGJyLz4KPGhyLz4KPGJyLz4KCiMjIFByb2JsZW0gCgpUaGUgc2FsZXMgYXJlIG5vdCBncm93aW5nIGFzIHBsYW5uZWQuIFNpbmNlIGZldyB5ZWFycyB0aGUgc3RvcmUgaGFzIHByb3Bvc2VkIG5ldyBvZmZlcnMgYW5kIGdldCBhIG5ldyBwYXJraW5nIGdhcmFnZSBidXQgdGhlIHNhbGVzIGNvbnRpbnVlZCB0byBncm93IHRvbyBzbG93bHkgZm9yIHRoZSBzdG9yZSB0byBhY2hpZXZlIGNyaXRpY2FsIGVjb25vbXkgb2Ygc2NhbGUuIAoKT24gdGhlIGFuYWx5dGljIHNpZGUsIHRoZSBzdG9yZSBoYXMgbm90aGluZy4gKipUaGV5IGRvbid0IGtub3cgd2hvIHRoZWlyIGN1c3RvbWVycyBhcmUgYW5kIHdoZXJlIHRoZXkgc2hvdWxkIHNwZW5kIHRoZWlyIG1hcmtldGluZyBidWRnZXQuKiogCgoKPGJyLz4gCjxoci8+Cjxici8+CgojIyBTb2x1dGlvbgoKRGlnZ2luZyBpbnNpZ2h0IGZyb20gdGhlIHN0b3JlJ3MgZGF0YSB3aWxsIGVuYWJsZSB1cyB0byAqKnZpc3VhbGlzZSB0aGUgY3VzdG9tZXIncyBiZWhhdmlvdXIgaW4gYSBuZXcgd2F5LCoqIGFuZCBjcmVhdGUgKip0YXJnZXRlZCBtYXJrZXRpbmcgc2VnbWVudGF0aW9ucyBhbmQgY2FtcGFpZ25zLCBiYXNlZCBvbiBhIGRhdGEtZHJpdmVuIHN0cmF0ZWd5LioqCgpBZnRlciBoYXZpbmcgc2VlbiB0aGUgYXZhaWxhYmxlIGRhdGEsIHdlIHByb3Bvc2UgdGhyZWUgbWFya2V0aW5nIGFuYWx5c2lzJyBtZXRob2RzOiBzZWdtZW50YXRpb24sIHNjb3JpbmcgbW9kZWxzIGFuZCBjdXN0b21lciBsaWZldGltZSB2YWx1ZS4gU2VnbWVudGF0aW9uIGlzIGFib3V0IHVuZGVyc3RhbmRpbmcgeW91ciBjdXN0b21lcnMsIHNjb3JpbmcgbW9kZWxzIGFyZSBhYm91dCB0YXJnZXRpbmcgdGhlIHJpZ2h0IG9uZXMsIGFuZCBjdXN0b21lciBsaWZldGltZSB2YWx1ZSBpcyBhYm91dCBhbnRpY2lwYXRpbmcgdGhlaXIgZnV0dXJlIHZhbHVlLiAKCjxici8+Cjxoci8+Cjxici8+CgoKIyBEYXRhIGV4cGxvcmF0aW9uCgpPdXIgZmlyc3QgZ29hbCBpcyB0byB1bmRlcnN0YW5kIHRoZSBkYXRhIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIGVhY2ggc3RlcCBvZiBvdXIgYW5hbHlzaXMsIGFuZCBvdXIgc2Vjb25kIGdvYWwgaXMgdG8gYXNzZXNzIHdoZXJlIHRoZSBhbmFseXNpcyBtaWdodCBsYWNrIGRhdGEgb3Igd2hlcmUgdGhlIHF1YWxpdHkgb2YgaXQgbWlnaHQgc3VmZmVyLgoKIyMgVGhlIGRhdGEgcXVhbGl0eSByZXBvcnQKCk91ciBkYXRhYmFzZSBwcmVzZW50IHRoZSAqKm9yZGVycyBwcm9jZXNzZWQqKiBmcm9tIDIwMDYgdG8gMjAxNi4gVGhlcmUgYXJlIHRocmVlIHZhcmlhYmxlczoKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KIyBMaWJyYXJ5IG5lZWRlZDoKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdndmlzKQojIERvd25sb2FkIGRhdGFiYXNlCk9yZGVycyA8LSByZWFkLmNzdigiRGF0YUJhc2UvT3JkZXJzdC5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiOyIpCgojIFNldCBEYXRlIGFzIERhdGUKT3JkZXJzJERhdGUgPSBhcy5EYXRlKE9yZGVycyREYXRlLCAiJWQvJW0vJXkiKQoKIyBDaGVjayB0aGUgZmlyc3QgbGluZXMKaGVhZChPcmRlcnMpCmBgYAoKSGVyZSB3ZSBkZXNjcmliZSB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIG91ciB0aHJlZSB2YXJpYWJsZXMuIEluIDEwIHllYXJzIHdlIGhhdmUgaGFkIDI2NCAyMDAgY3VzdG9tZXJzIHRoYXQgc3BlbnQgb24gYXZlcmFnZSAkODAuIFdlIGNhbiBzZWUgdHdvIGlzc3VlcyBpbiB0aGUgZGF0YSB0aGF0IHdlIGhhdmUgdG8gaGFuZGxlLiBPbmUgaXMgdGhlIDQyOCBtaXNzaW5nIHZhbHVlcyBhbmQgdGhlIHNlY29uZCBpcyB0aGUgb3V0bGllcnMuCkFzIHRoZSBvdXRsaWVycyBkcml2ZSB0aGUgbWVhbiB1cCwgdGhpcyBtZXRyaWMgYmVjb21lIGlycmVsZXZhbnQgdG8gbWVhc3VyZSB0aGUgY2VudHJhbCB0ZW5kZW5jeSBiZWNhdXNlIG91ciBkYXRhc2V0IGlzIHRvIG11Y2ggc2tld2VkIGJ5IHRoZSBmZXcgb3V0bGllcnMuIEhlcmUsIHRoZSBtZWRpYW4gaXMgYmV0dGVyIHRvIHVuZGVyc3RhbmQgb3VyIGRpc3RyaWJ1dGlvbnMgcGFyYW1ldGVycy4KCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0Kc3VtbWFyeShPcmRlcnMpCmBgYAoKPGJyLz4KPGhyLz4KPGJyLz4KCiMjIEZpcnN0IHZpc3VhbGlzYXRpb25zCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KCiMgTG9hZCB0aGUgU1FMIGxpYnJhcnkgCmxpYnJhcnkoc3FsZGYpCgojIFNlbGVjdCBvbmx5IHRoZSB5ZWFyIGFzIGEgbnVtZXJpYwpPcmRlcnMkWU9yZGVyID0gYXMubnVtZXJpYyhmb3JtYXQoT3JkZXJzJERhdGUsICIlWSIpKQogCiMgTnVtYmVyIG9mIG9yZGVycyBwZXIgeWVhcgpOdW1iZXJfb2ZfT3JkZXJzID0gc3FsZGYoIlNFTEVDVCBZT3JkZXIsIENPVU5UKFlPcmRlcikgQVMgJ2NvdW50ZXInIEZST00gT3JkZXJzIEdST1VQIEJZIDEgT1JERVIgQlkgMSIpCgojIEF2ZXJhZ2UgIm9yZGVyIHRvdGFsIiIgcGVyIHllYXIKQXZnVG90YWwgPSBzcWxkZigiU0VMRUNUIFlPcmRlciwgQVZHKFRvdGFsKSBBUyAnVG90YWwnIEZST00gT3JkZXJzIEdST1VQIEJZIDEgT1JERVIgQlkgMSIpCgpwYXIobWZyb3c9YygxLDIpKQpiYXJwbG90KE51bWJlcl9vZl9PcmRlcnMkY291bnRlciwgbmFtZXMuYXJnID0gTnVtYmVyX29mX09yZGVycyRZT3JkZXIsIG1haW49Ik51bWJlciBvZiBPcmRlcnMiLCBjb2w9IiMyQzNFNTAiKQpiYXJwbG90KEF2Z1RvdGFsJFRvdGFsLCBuYW1lcy5hcmcgPSBBdmdUb3RhbCRZT3JkZXIsIG1haW4gPSAiQXZlcmFnZSBwZXIgT3JkZXJzICgkKSIsIGNvbD0iIzJDM0U1MCIpCgpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLndpZHRoPTEwfQpPcmRlcnNwbG90IDwtIE9yZGVycyAlPiUgZ3JvdXBfYnkoWU9yZGVyKSAlPiUgc3VtbWFyaXNlKHN1bT1zdW0oVG90YWwsbmEucm09VFJVRSkpCmJhcnBsb3QoT3JkZXJzcGxvdCRzdW0sIG5hbWVzLmFyZyA9IE9yZGVyc3Bsb3QkWU9yZGVyLCBtYWluID0gIlRvdGFsIG9yZGVycyBwZXIgeWVhcnMgKCQpIiwgY29sPSIjMkMzRTUwIikKYGBgCgo8YnIvPgo8aHIvPgo8YnIvPgoKIyMgSGFuZGxpbmcgZGF0YSBpc3N1ZXN7LnRhYnNldH0KCjxici8+CgojIyMgTWlzc2luZyBWYWx1ZXMKCkxldCdzIGNvdW50IHRoZSBtaXNzaW5nIHZhbHVlcyBwZXIgY29sdW1ucyBhbmQgc2VlIGhvdyB0aGV5IGFyZSBkaXN0cmlidXRlZCB3aXRoaW4gdGhlIGRhdGFzZXQ6CgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9CmFwcGx5KGlzLm5hKE9yZGVycyksMixzdW0pCiMgVmlzdWFsaXNhdGlvbgpsaWJyYXJ5KFZJTSkKbGlicmFyeShtaWNlKQptZC5wYXR0ZXJuKE9yZGVycykKYGBgCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KYWdncihPcmRlcnMsIHByb3AgPSBGLCBudW1iZXJzID0gVCkKYGBgCgpPdXIgbWlzc2luZyB2YWx1ZXMgYXJlIG9ubHkgZm91bmQgaW4gdGhlIHRvdGFsIGNvbHVtbiwgbm90IHdpdGhpbiB0aGUgZGF0ZSBvciB0aGUgY3VzdG9tZXIgSUQgY29sdW1uLiBBZnRlciBkaXNjdXNzaW5nIHdpdGggdGhlIG1hbmFnZXIgd2UgbGVhcm4gdGhhdCBpcyBkdWUgdG8gYSBjaGFuZ2Ugb2YgdGhlaXIgRVJQIHN5c3RlbS4gVG8gaGFuZGxlIHRoZXNlcyBtaXNzaW5nIHZhbHVlcyB3ZSBkZWNpZGUgdG8gcmVwbGFjZSB0aGVtIHdpdGggdGhlIG1lZGlhbiBhcyB0aGUgbWVhbiB3YXMgdG9vIG11Y2ggaW5mbHVlbmNlZCBieSB0aGUgb3V0bGllcnMuIE5vdyB3ZSBoYXZlIG5vIG1vcmUgbWlzc2luZyB2YWx1ZXM6CgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9Ck9yZGVyc1tpcy5uYShPcmRlcnMpXSA8LSAzOQphcHBseShpcy5uYShPcmRlcnMpLDIsc3VtKQpgYGAKCjxici8+Cjxoci8+Cjxici8+CgojIyMgT3V0bGllcnMKCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KYm94cGxvdChUb3RhbCB+IERhdGUsIGRhdGE9T3JkZXJzLCBtYWluPSJPdXRsaWVycyIpICAjIGNsZWFyIHBhdHRlcm4gaXMgbm90aWNlYWJsZS4KYGBgCgpBZnRlciBwcmVzZW50aW5nIHRoaXMgdG8gdGhlIG1hbmFnZXIgaGUgdG9sZCB1cyB3ZSBjb3VsZCByZW1vdmUgdGhlIG91dGxpZXJzIGFuZCB0aGUgbmVnYXRpdmUgdmFsdWUuIFNvLCBoZXJlIGFyZSBub3cgdGhlIGRhdGEgd2Ugd2lsbCB3b3JrIHdpdGg6CgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9Ck9yZGVycyA8LSBPcmRlcnMgJT4lIGZpbHRlcihUb3RhbD49MCkKT3JkZXJzIDwtIE9yZGVycyAlPiUgZmlsdGVyKFRvdGFsPDEwMDAwKQpzdW1tYXJ5KE9yZGVycykKYGBgCgo8YnIvPgo8aHIvPgo8YnIvPgoKCiMgU2VnZW1lbnRhdGlvbgoKV2UgY2FuJ3QgdHJlYXQgYWxsIG91ciBjdXN0b21lcnMgaW4gdGhlIHNhbWUgd2F5LCBvZmZlciB0aGVtIHRoZSBzYW1lIHByb2R1Y3Qgb3IgY2hhcmdlIHRoZSBzYW1lIHByaWNlLCBiZWNhdXNlIHRoaXMgbGVhdmUgdG9vIG11Y2ggdmFsdWUgb24gdGhlIHRhYmxlLiBXZSBuZWVkIHRvIGJ1aWxkIHJlbGV2YW50IHNlZ21lbnQgb2YgY3VzdG9tZXJzIGFuZCB1c2UgdGhlbSB0byBpbXByb3ZlIG91ciByZWxhdGlvbnNoaXAsIG9mZmVycyBhbmQgY2FtcGFpZ25zLiBJbmRlZWQsIHNlZ21lbnRhdGlvbiBzdW1tYXJpc2UgZWZmaWNpZW50bHkgbW91bnRhaW5zIG9mIGRhdGEgYW5kIG1ha2UgaXQgdXNhYmxlLgoKQSBnb29kIHNlZ21lbnRhdGlvbiBnYXRoZXIgc2ltaWxhciBlbnRpdGllcyB0b2dldGhlciBhbmQgc2VwYXJhdGUgZGlmZmVyZW50IG9uZS4gSXQgZW5hYmxlIGRlY2lzaW9uIG1ha2VyIHRvIHRyZWF0IHRoZSBjdXN0b21lcuKAmSBzZWdtZW50cyBkaWZmZXJlbnRseSB3aXRob3V0IGdvaW5nIGRvd24gdG8gdGhlIGluZGl2aWR1YWwgbGV2ZWwsIHdoaWNoIHdpbGwgYmUgdG8gaGFyZCB0byBtYW5hZ2UuIAoKT25jZSBhIHNlZ21lbnQgaXMgZG9uZSwgd2UgbmVlZCB0byBkZXNjcmliZSBpdCBpbiBzaW1wbGUgdGVybXMuIFRoaXMgZW5hYmxlIG1hbmFnZXJzIHRvIHNlZSB0aGUgY3VzdG9tZXInIGRpZmZlcmVuY2VzIG9mIG5lZWRzLCBkZXNpcmVzIGFuZCBoYWJpdHMuIFRoZW4sIHRoZXkgY2FuIGN1c3RvbWlzZSBvZmZlcmluZ3MsIGFkYXB0IGN1c3RvbWVyJyBtZXNzYWdlcyBhbmQgb3B0aW1pc2UgbWFya2V0aW5nIGNhbXBhaWducy4KCiMjIFN0YXRpc3RpY2FsIHNlZ21lbnRhdGlvbiAKCkN1c3RvbWVycyBoYXZlIHRvIGJlIHNpbWlsYXIsIGJ1dCBzaW1pbGFyIG9uIHdoaWNoIHZhcmlhYmxlcz8gV2VsbCwgaXQgZGVwZW5kcyBvZiB0aGUgYnVzaW5lc3MgYW5kIHRoZSBtYW5hZ2VyaWFsIHF1ZXN0aW9uIHdlIGFyZSBhc2tpbmcuIEhlcmUgb3VyIHNlZ21lbnRhdGlvbiB2YXJpYWJsZXMgd2lsbCBiZSB0aGUgcmVjZW5jeSwgZnJlcXVlbmN5LCBhbmQgbW9uZXRhcnkgdmFsdWUsIGFuZCBvdXIgbWFuYWdlcmlhbCBnb2FsIGlzIHRvIHVuZGVyc3RhbmQgdGhlIGN1c3RvbWVy4oCZIGJlaGF2aW91cnMgYW5kIHZhbHVlcy4gQWxzbyB3ZSBhcmUgbGltaXRlZCBieSB0aGUgYW1vdW50IG9mIGRhdGEgYXZhaWxhYmxlLiBGb3IgZXhhbXBsZSB3ZSBkb24ndCBoYXZlIGFueSBpbmZvcm1hdGlvbiBhYm91dCBlYWNoIGN1c3RvbWVycyBhZ2Ugb3Igc2V4LgoKIyMjIFJGTSBzZWdtZW50YXRpb24KClRoZXNlIHRocmVlIHZhcmlhYmxlcyBhcmUgZ29vZCBwcmVkaWN0b3JzIG9mIHRoZSBmdXR1cmUgY3VzdG9tZXLigJkgcHJvZml0YWJpbGl0eSBhbmQgYXJlIGVhc3kgdG8gY29tcHV0ZSBmcm9tIHRoZSBkYXRhYmFzZSBwcmVzZW50ZWQgYWJvdmU6CgpgYGB7ciwgbWVzc2FnZT1ULCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KIyBDb21wdXRlIHRoZSBudW1iZXIgb2YgZGF5cyBmcm9tIHRoZSBsYXN0IG9yZGVyICgyMDE3LTAxLTAxKSBhbmQgbmFtZSB0aGlzIHZhcmlhYmxlIGxhc3RvcmRlcnMKT3JkZXJzJGxhc3RvcmRlcnM9IGFzLm51bWVyaWMoZGlmZnRpbWUodGltZTEgPSAiMjAxNy0wMS0wMSIsIHRpbWUyID0gT3JkZXJzJERhdGUsdW5pdHMgPSAiZGF5cyIpKQoKIyBDb21wdXRlIHJlY2VuY3ksIGZyZXF1ZW5jeSwgYW5kIGF2ZXJhZ2Ugb3JkZXIgYW1vdW50CnJmbU9yZGVycyA9IHNxbGRmKCJTRUxFQ1QgQ3VzdG9tZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgTUlOKGxhc3RvcmRlcnMpIEFTICdyZWNlbmN5JywKICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAnZnJlcXVlbmN5JywKICAgICAgICAgICAgICAgICAgICAgICAgICBBVkcoVG90YWwpIEFTICdhdmdvcmRlcicKICAgICAgICAgICAgICAgICAgIEZST00gT3JkZXJzIEdST1VQIEJZIDEiKQoKIyBBcnJhbmdlIHBlciBmcmVxdWVuY3kKcmZtT3JkZXJzIDwtIHJmbU9yZGVycyAlPiUgYXJyYW5nZShkZXNjKGZyZXF1ZW5jeSkpCgojIERpc3BsYXkgdGhlIGZpcnN0IGxpbmVzCmhlYWQocmZtT3JkZXJzKQpgYGAKCjxici8+CgpFYWNoIG9mIHRoZXNlIHZhcmlhYmxlIGFuc3dlciBhIGtleSBxdWVzdGlvbjoKCiAgKyBSZWNlbmN5IOKAkyBIb3cgcmVjZW50IGlzIHRoZSBjdXN0b21lcidzIGxhdGVzdCBwdXJjaGFzZT8KICArIEZyZXF1ZW5jeSDigJMgSG93IG9mdGVuIGRpZCB0aGV5IHB1cmNoYXNlPwogICsgTW9uZXRhcnkgVmFsdWUg4oCTIEhvdyBtdWNoIGRvIHRoZXkgc3BlbmQgb24gYXZlcmFnZT8KCk5vdyB0aGF0IHdlIGhhdmUgY3JlYXRlZCB0aGUgdGhyZWUgdmFyaWFibGVzLCB3ZSBjYW4gdmlzdWFsaXNlIG91ciBuZXcgZGlzdHJpYnV0aW9ucyBvZiB0aGUgdG90YWwgYW5kIGF2ZXJhZ2Ugb3JkZXIgcGVyIGN1c3RvbWVyOgoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CnBhcihtZnJvdz1jKDEsMikpCiMgUGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBmcmVxdWVuY3kgfiBUb3RhbApoaXN0KE9yZGVycyRUb3RhbCwgYnJlYWtzID0gODAsIG1haW4gPSAiT3JkZXJzIiwgeGxhYiA9ICJUb3RhbCIsIGNvbD0iIzJDM0U1MCIpCiMgUGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBmcmVxdWVuY3kgfiBBdmcgb3JkZXIKaGlzdChyZm1PcmRlcnMkYXZnb3JkZXIsIGJyZWFrcyA9IDgwLCBtYWluID0gIkF2ZXJhZ2UgT3JkZXJzIChwZXIgY3VzdG9tZXJzKSIsIHhsYWIgPSAiQXZlcmFnZSBPcmRlcnMgKHBlciBjdXN0b21lcnMpIiwgY29sPSIjMkMzRTUwIikKYGBgCjxici8+CgpBcyBvdXIgZGF0YSBhcmVu4oCZdCBhdCB0aGUgc2FtZSBzY2FsZSwgd2UgcmlzayB0byBmaW5kIGlycmVsZXZhbnQgcGF0dGVybnMgYW5kIGJ1aWxkIHdyb25nIHNlZ21lbnRhdGlvbi4gVGhhdOKAmXMgd2h5IHdlIG5lZWQgdG8gZGUtc2tldyBvdXIgZGlzdHJpYnV0aW9uIGFuZCBtYWtlIGl0IG1vcmUgbm9ybWFsLiBJbmRlZWQsIHRyYW5zZm9ybWluZyBkYXRhIGlzIGNydWNpYWwgYXMgaXQgZW5hYmxlIHVzIHRvIG1lZXQgdGhlIGFzc3VtcHRpb25zIG91ciBhbmFseXNpcyByZXF1aXJlOgoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CiMgVXNlIHRoZSBsb2dhcml0aG0gZm9yIGFsbCB0aGUgdmFyaWFibGVzOgpSZXJmbU9yZGVycyA8LSByZm1PcmRlcnMKUmVyZm1PcmRlcnMkZnJlcXVlbmN5IDwtIGxvZyhyZm1PcmRlcnMkZnJlcXVlbmN5KQpSZXJmbU9yZGVycyRyZWNlbmN5IDwtIGxvZyhyZm1PcmRlcnMkcmVjZW5jeSkKUmVyZm1PcmRlcnMkYXZnb3JkZXIgPC0gbG9nKHJmbU9yZGVycyRhdmdvcmRlcikKIyBQcmludCB0aGUgZmlyc3QgbGluZXMKaGVhZChSZXJmbU9yZGVycykKIyBIYXZlIGEgbG9vayBvbiB0aGUgc3VtbWFyeQpzdW1tYXJ5KFJlcmZtT3JkZXJzKQojIFBsb3Qgcm93CnBhcihtZnJvdz1jKDEsMSkpCiMgUGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBsb2cKaGlzdChSZXJmbU9yZGVycyRhdmdvcmRlLCBicmVha3MgPSAyNSwgbWFpbiA9ICJMb2cgKEF2ZXJhZ2UgT3JkZXJzIChwZXIgY3VzdG9tZXJzKSkgIiwgeGxhYiA9ICJMb2cgKEF2ZXJhZ2UgT3JkZXJzIChwZXIgY3VzdG9tZXJzKSkiLCBjb2w9IiMyQzNFNTAiKQpgYGAKClRoZXJlIGFyZSAzIG90aGVyIG1ldGhvZHMgdG8gdHJhbnNmb3JtIHlvdXIgZGF0YToKCisgTm9ybWFsaXppbmcgKG9yIHN0YW5kYXJkaXppbmcpOiBzdWJ0cmFjdCB5b3VyIGRhdGEgYnkgdGhlIG1lYW4gYW5kIGRpdmlkZSBpdCBieSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLiBCeSBkb2luZyB0aGF0LCB5b3Ugd2lsbCBvYnRhaW4gdGhlIOKAnHN0YW5kYXJkIHNjb3Jl4oCdLiBZb3VyIGRhdGEgd2lsbCBiZSBhZGp1c3RlZCB0byBhIGNvbW1vbiBzY2FsZSwgYW5kIGl0IHdpbGwgaGVscCB5b3UgdG8gY29tcGFyZSB5b3VyIGRhdGEgaW4gYSBtZWFuaW5nZnVsIHdheS4KKyBTY2FsaW5nIChNaW4tTWF4IHNjYWxpbmcpOiBhbHRlcm5hdGl2ZWx5LCB5b3UgY291bGQgdXNlIHRoZSBNaW4tTWF4IHNjYWxpbmcuIEl0IGNvbWUgaW4gUiB3aXRoIGEgdmVyeSBzaW1wbGUgZnVuY3Rpb24gc2NhbGUoKS4gWW91ciBkYXRhIHdpbGwgYmUgc2NhbGVkIGluIGEgcmFuZ2UgZnJvbSAwIHRvIDEgKG9yIC0xIHRvIDEpLgorIENyZWF0ZSBidWNrZXQ6IGFsc28sIHdlIGNvdWxkIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSB0aGF0IHJlaW50ZXJwcmV0IHRoZSB0aHJlZSB2YXJpYWJsZXMgYnkgY3JlYXRpbmcgYnVja2V0cy4gT2YgY291cnNlIHRoaXMgdGVjaG5pYyBsZXQgYSBsb3Qgb2YgaW5mb3JtYXRpb24gYmVoaW5kLgoKPGJyLz4KPGhyLz4KPGJyLz4KCiMjIyBDdXN0b21lciBjbHVzdGVyaW5nCgpOb3cgd2UgY2FuIGNyZWF0ZSBmaXZlIGNsdXN0ZXJzIG9mIGN1c3RvbWVycyB1c2luZyBvdXIgMyByZXNjYWxlZCB2YXJpYWJsZXMuIEl0J3MgaW1wb3J0YW50IHRvIG5vdGljZSB0aGF0IHdpdGhvdXQgdGhlIHJlc2NhbGluZyBwcm9jZXNzIHdlIG1pZ2h0IGZpbmQgdmVyeSBkaWZmZXJlbnQgY2x1c3RlcnMgdGhhbiB0aGUgb25lIHdlIGRpc3BsYXkgYmVsb3cuCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KIyAxLiBSdW4gSy1tZWFucyAobnN0YXJ0ID0gMjApIGFuZCA1IGRpZmZlcmVudCBncm91cHMKUmVyZm1PcmRlcnMgPC0gUmVyZm1PcmRlcnMgJT4lIGZpbHRlcihmcmVxdWVuY3k+MCkKUmVyZm1PcmRlcnNfIDwtIFJlcmZtT3JkZXJzICU+JSBzZWxlY3QocmVjZW5jeTphdmdvcmRlcikKUmVyZm1PcmRlcnNfa20gPC0ga21lYW5zKFJlcmZtT3JkZXJzXywgY2VudGVycyA9IDUsIG5zdGFydCA9IDIwKQoKIyBQbG90IHVzaW5nIHBsb3RseSAKbGlicmFyeShwbG90bHkpCiNwIDwtIHBsb3RfbHkoUmVyZm1PcmRlcnMsIHggPSBSZXJmbU9yZGVycyRyZWNlbmN5LCB5ID0gUmVyZm1PcmRlcnMkYXZnb3JkZXIsIHogPSBmcmVxdWVuY3ksIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJtYXJrZXJzIiwgY29sb3I9UmVyZm1PcmRlcnNfa20kY2x1c3RlciklPiUgbGF5b3V0KHNob3dsZWdlbmQgPSBGQUxTRSkKI3AgJT4lIGxheW91dChzaG93bGVnZW5kID0gRkFMU0UpCiNwcmludChwKQojcCA8LSBwbG90CmBgYAoKSWYgdGhlcmUgaXMgYSBnb29kIHNlcGFyYXRpb24gb24gdGhlIHJlY2VuY3kgdmFyaWFibGVzLCB0aGUgc2VwYXJhdGlvbiBpcyBtb3JlIG51YW5jZWQgZm9yIHRoZSBmcmVxdWVuY3kgYW5kIHRoZSBhdmVyYWdlIG9yZGVyZWQgdmFyaWJsZXMuCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KUmVyZm1PcmRlcnNfa20kY2VudGVycwpgYGAKCgoqKkNvbmNsdXNpb246KiogdGhlc2UgY2x1c3RlcmluZyBtZXRob2RzIGVuYWJsZSB1cyB0byBjcmVhdGUgdGFyZ2V0ZWQgbWFya2V0aW5nIHNlZ21lbnRzLiBOb3cgd2UgbmVlZCB0byB0aGluayBvbiBob3cgdG8gZXhwbG9pdCB0aGVtLgoKPGJyLz4KPGhyLz4KPGJyLz4KCiMjIE1hbmFnZXJpYWwgc2VnbWVudGF0aW9uCgpGcm9tIHRoZSBzZWdtZW50YXRpb24gb2J0YWluZWQgYWJvdmUgd2Ugd2FudCB0byBidWlsZCBzZWdtZW50cyBvZiBjdXN0b21lcnMgdGhhdCBhcmUgZWFzaWx5IG1hbmFnZWFibGUuIFdlIGFyZSB3b3JraW5nIHdpdGggdGhlIHNhbWUgZGF0YWJhc2UgYW5kIGFkZGluZyBhIHZhcmlhYmxlIHRoYXQgdGFrZSB0aGUgZmlyc3QgcHVyY2hhc2Ugb2YgZXZlcnkgY3VzdG9tZXJzIHRvIGFzc2VzcyB0aGVpciBsb3lhbHR5OgoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9Ck9yZGVycyR5ZWFyX29mX3B1cmNoYXNlID0gYXMubnVtZXJpYyhmb3JtYXQoT3JkZXJzJERhdGUsICIlWSIpKQpPcmRlcnMkZGF5c19zaW5jZSA9IGFzLm51bWVyaWMoZGlmZnRpbWUodGltZTEgPSAiMjAxNy0wMS0wMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZTIgPSBPcmRlcnMkRGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bml0cyA9ICJkYXlzIikpCgpjdXN0b21lcnNfMjAxNiA9IHNxbGRmKCJTRUxFQ1QgY3VzdG9tZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNSU4oZGF5c19zaW5jZSkgQVMgJ3JlY2VuY3knLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKGRheXNfc2luY2UpIEFTICdmaXJzdF9wdXJjaGFzZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAnZnJlcXVlbmN5JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFWRyhUb3RhbCkgQVMgJ2Ftb3VudCcKICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBPcmRlcnMgR1JPVVAgQlkgMSIpCgpoZWFkKGN1c3RvbWVyc18yMDE2KQpgYGAKClRvIGxpbWl0IG1hbmFnZXJpYWwgY29tcGxleGl0eSB3ZSByZXN0cmFpbnQgdGhlIG51bWJlciBvZiBzZWdtZW50cyBjcmVhdGVkLCBidXQgdG8gZW5oYW5jZSB0aGUgdmFsdWUgb2Ygb3VyIHNlZ21lbnRhdGlvbiB3ZSBjcmVhdGUgYXQgbGVhc3QgNCBzZWdtZW50cyB0aGF0IGFyZSB1c2FibGUgdG8gbWFuYWdlcnMuIE9mIGNvdXJzZSBhcyB0aGUgb3B0aW1hbCBzZWdtZW50YXRpb24gc29sdXRpb24gd2lsbCB2YXJ5IG92ZXIgdGltZSB3ZSB3aWxsIGhhdmUgdG8gcmUtcnVuIG91ciBjb2RlIHRvIHVwZGF0ZSBvdXIgc2VnbWVudGF0aW9uLiAKCldlIG5vdyBkZWZpbmUgYXMgYWN0aXZlIGEgY3VzdG9tZXIgd2hvIHB1cmNoYXNlZCBzb21ldGhpbmcgd2l0aGluIHRoZSBsYXN0IDEyIG1vbnRocywgYXMgd2FybSBhIGN1c3RvbWVyIHdob3NlIGxhc3QgcHVyY2hhc2UgaGFwcGVucyBhIHllYXIgYmVmb3JlLCB0aGF0IGlzIGJldHdlZW4gMTMgYW5kIDI0IG1vbnRocy4gV2UgcXVhbGlmeSBhcyBjb2xkLCBhIGN1c3RvbWVyIHdob3NlIGxhc3QgcHVyY2hhc2Ugd2FzIGJldHdlZW4gdHdvIGFuZCB0aHJlZSB5ZWFycyBhZ28uIEZvciB0aG9zZSB3aG8gaGF2ZW4ndCBwdXJjaGFzZWQgYW55dGhpbmcgZm9yIG1vcmUgdGhhbiB0aHJlZSB5ZWFycywgd2UgcXVhbGlmeSB0aGVtIGFzIGluYWN0aXZlLgoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQpjdXN0b21lcnNfMjAxNiRzZWdtZW50ID0gIk5BIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHJlY2VuY3kgPiAzNjUqMyldID0gImluYWN0aXZlIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHJlY2VuY3kgPD0gMzY1KjMgJiBjdXN0b21lcnNfMjAxNiRyZWNlbmN5ID4gMzY1KjIpXSA9ICJjb2xkIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHJlY2VuY3kgPD0gMzY1KjIgJiBjdXN0b21lcnNfMjAxNiRyZWNlbmN5ID4gMzY1KjEpXSA9ICJ3YXJtIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHJlY2VuY3kgPD0gMzY1KV0gPSAiYWN0aXZlIgpDdXN0XzIwMTUgPC1hZ2dyZWdhdGUoeCA9IGN1c3RvbWVyc18yMDE2WywgMjo1XSwgYnkgPSBsaXN0KGN1c3RvbWVyc18yMDE2JHNlZ21lbnQpLCBtZWFuKQpuYW1lcyhDdXN0XzIwMTUpW25hbWVzKEN1c3RfMjAxNSk9PSJHcm91cC4xIl0gPC0gIlNlZ21lbnRzIgpoZWFkKEN1c3RfMjAxNSkKYGBgCgpOb3csIHdlIGNyZWF0ZSBzZWdtZW50cyBjYWxsZWQgbG93IG9yIGhpZ2ggdmFsdWUsIGFuZCB1bmRlcmxpbmUgb3VyIG5ldyBjdXN0b21lcnMgYnkgY2FsbGluZyB0aGVtIG5ldyB3YXJtIG9yIG5ldyBhY3RpdmUgY3VzdG9tZXJzLiBIZXJlIGFyZSBhbGwgb3VyIGZpbmFsIHNlZ21lbnRzIGFuZCB0aGUgbnVtYmVyIG9mIGN1c3RvbWVycyB3aXRoaW4gZWFjaCBzZWdtZW50czoKCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KIyBDb21wbGV0ZSBzZWdtZW50IHNvbHV0aW9uIHVzaW5nIHdoaWNoLCBhbmQgZXhwbG9pdGluZyBwcmV2aW91cyB0ZXN0IGFzIGlucHV0CmN1c3RvbWVyc18yMDE2JHNlZ21lbnQgPSAiTkEiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkcmVjZW5jeSA+IDM2NSozKV0gPSAiaW5hY3RpdmUiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkcmVjZW5jeSA8PSAzNjUqMyAmIGN1c3RvbWVyc18yMDE2JHJlY2VuY3kgPiAzNjUqMildID0gImNvbGQiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkcmVjZW5jeSA8PSAzNjUqMiAmIGN1c3RvbWVyc18yMDE2JHJlY2VuY3kgPiAzNjUqMSldID0gIndhcm0iCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkcmVjZW5jeSA8PSAzNjUpXSA9ICJhY3RpdmUiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9PSAid2FybSIgJiBjdXN0b21lcnNfMjAxNiRmaXJzdF9wdXJjaGFzZSA8PSAzNjUqMildID0gIm5ldyB3YXJtIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQgPT0gIndhcm0iICYgY3VzdG9tZXJzXzIwMTYkYW1vdW50IDwgMTAwKV0gPSAid2FybSBsb3cgdmFsdWUiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9PSAid2FybSIgJiBjdXN0b21lcnNfMjAxNiRhbW91bnQgPj0gMTAwKV0gPSAid2FybSBoaWdoIHZhbHVlIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQgPT0gImFjdGl2ZSIgJiBjdXN0b21lcnNfMjAxNiRmaXJzdF9wdXJjaGFzZSA8PSAzNjUpXSA9ICJuZXcgYWN0aXZlIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQgPT0gImFjdGl2ZSIgJiBjdXN0b21lcnNfMjAxNiRhbW91bnQgPCAxMDApXSA9ICJhY3RpdmUgbG93IHZhbHVlIgpjdXN0b21lcnNfMjAxNiRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQgPT0gImFjdGl2ZSIgJiBjdXN0b21lcnNfMjAxNiRhbW91bnQgPj0gMTAwKV0gPSAiYWN0aXZlIGhpZ2ggdmFsdWUiCgojIFJlLW9yZGVyIGZhY3RvciBpbiBhIHdheSB0aGF0IG1ha2VzIHNlbnNlCmN1c3RvbWVyc18yMDE2JHNlZ21lbnQgPSBmYWN0b3IoeCA9IGN1c3RvbWVyc18yMDE2JHNlZ21lbnQsIGxldmVscyA9IGMoImluYWN0aXZlIiwgImNvbGQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIndhcm0gaGlnaCB2YWx1ZSIsICJ3YXJtIGxvdyB2YWx1ZSIsICJuZXcgd2FybSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYWN0aXZlIGhpZ2ggdmFsdWUiLCAiYWN0aXZlIGxvdyB2YWx1ZSIsICJuZXcgYWN0aXZlIikpCnRhYmxlKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQpCnBpZSh0YWJsZShjdXN0b21lcnNfMjAxNiRzZWdtZW50KSwgY29sID0gdG9wby5jb2xvcnMoMjQpKQpgYGAKCk91ciBmaW5hbCBzZWdtZW50YXRpb24gaXMgY29tcG9zZWQgb2YgOCBzZWdtZW50cy4gU3VjaCBpbnNpZ2h0cyBjYW4gaW1wcm92ZSBtYW5hZ2VyaWFsIGRlY2lzaW9ucyBvbiBldmVyeSBsZXZlbHMuIFdlIHNlZSBob3cgbXVjaCBjdXN0b21lcnMgYXJlIGRpZmZlcmVudCBhbmQgaG93IHRoZXkgc2hvdWxkIGJlIHRyZWF0ZWQgZGlmZmVyZW50bHkuCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9CkN1c3RfZnVsbF8yMDE1IDwtYWdncmVnYXRlKHggPSBjdXN0b21lcnNfMjAxNlssIDI6NV0sIGJ5ID0gbGlzdChjdXN0b21lcnNfMjAxNiRzZWdtZW50KSwgbWVhbikKbmFtZXMoQ3VzdF9mdWxsXzIwMTUpW25hbWVzKEN1c3RfZnVsbF8yMDE1KT09Ikdyb3VwLjEiXSA8LSAiU2VnbWVudHMiCnByaW50KEN1c3RfZnVsbF8yMDE1KQpgYGAKCkhlcmUgaXMgdGhlIHJldmVudWUgZnJvbSBlYWNoIHNlZ21lbnQgc2luY2UgdHdvIHllYXJzLiBXZSBjYW4gcmVwZWF0IHRoaXMgcHJvY2VzcyBhbmQgc2VlIHdoaWNoIGN1c3RvbWVycyBpcyBjaGFuZ2luZyBzZWdtZW50IGFuZCB3aGVuLiBGb3IgZXhhbXBsZSB3aGVuIGEgY3VzdG9tZXIgd2VudCBmcm9tICJhY3RpdmUgaGlnaCB2YWx1ZSIgdG8gImxvdyB2YWx1ZSIgd2Ugc2hvdWxkIHNlZSBpZiB3ZSBjYW4gZG8gYW55dGhpbmcgYWJvdXQgdGhhdC4gCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KcGFyKG1mcm93PWMoMSwxKSkKc2luY2UyeWVhcnMgPC0gY3VzdG9tZXJzXzIwMTYgJT4lIGZpbHRlcihyZWNlbmN5PDcyMCkKc2luY2UyeWVhcnMgPC0gc2luY2UyeWVhcnMgJT4lIGdyb3VwX2J5KHNlZ21lbnQpICU+JSBzdW1tYXJpc2Uoc3VtPXN1bShhbW91bnQsbmEucm09VFJVRSkpCmJhcnBsb3Qoc2luY2UyeWVhcnMkc3VtLCBuYW1lcy5hcmcgPSBzaW5jZTJ5ZWFycyRzZWdtZW50LCBjb2w9IiMyQzNFNTAiKQpgYGAKCioqQ29uY2x1c2lvbjoqKiB3aXRoIHRoZXNlIHNlZ21lbnRhdGlvbiBtZXRob2RzLCB3ZSBjYW4gdHJlYXQgZGlmZmVyZW50IGN1c3RvbWVyLCBkaWZmZXJlbnRseS4gRm9yIGV4YW1wbGUgd2UgY2FuIHNlbmQgc3BlY2lhbCBvZmZlcnMgdG8gcmVjZW50bHkgYWNxdWlyZWQgY3VzdG9tZXJzLCBvciBtZWV0IHRoZW0gaW4gcGVyc29uIHRvIHB1c2ggdGhlbSB0byBiZWNvbWUgbG95YWwuIEFsc28gd2Ugbm93IHNlZSB3aGljaCBjdXN0b21lcnMgYXJlIGhpZ2ggdmFsdWUgYW5kIHdoaWNoIG9uZSBhcmVuJ3QuIFRoYXQgY3J1Y2lhbCBmb3IgZGV2ZWxvcGluZyB2YWx1YWJsZSByZWxhdGlvbnNoaXAuCgpXZSBrbm93IGhvdyBtYW55IGN1c3RvbWVycyB0aGVyZSBhcmUgd2l0aGluIGVhY2ggc2VnbWVudHMgYW5kIGNhbiB2aXN1YWxpc2Ugd2hlbiBhIGN1c3RvbWVyIGlzIGNoYW5naW5nIHNlZ21lbnQuIFdlIGNvdWxkIGV2ZW4gbGF5IG91dCBhIG5hcnJhdGl2ZSBmb3IgZWFjaCBzZWdtZW50IGxpa2UsICJJJ20gSm9obiwgSSdtIDUyIHllYXJzIG9sZCBhbmQgSSBtYWRlIG15IGZpcnN0IHB1cmNoYXNlIHRocmVlIG1vbnRocyBhZ28gZm9yIGEgdG90YWwgb2YgJDIwLCBhbmQgSSB3b25kZXIgd2hldGhlciBJJ2QgbWFrZSBhIG5ldyBwdXJjaGFzZSBpbiB0aGUgZnV0dXJlLiIgCgpBIGdyZWF0IHNlZ21lbnRhdGlvbiBmaW5kIGEgZ29vZCBiYWxhbmNlIGJldHdlZW4gdXNhYmlsaXR5IGFuZCBjb21wbGV0ZW5lc3MsIGJldHdlZW4gc2ltcGxpZnlpbmcgZW5vdWdoIHNvIGl0IHJlbWFpbnMgdXNhYmxlIGFuZCBub3Qgc2ltcGxpZnlpbmcgdG9vIG11Y2gsIHNvIGl0J3Mgc3RpbGwgdmFsdWFibGUuIEFzIG11Y2ggYXMgd2UgY2FuLCBzZWdtZW50IGhhdmUgdG8gYmUgc2ltaWxhciwgbWVhc3VyYWJsZSwgYW5kIGFjY2Vzc2libGUuIFRvIHNheSBpdCBpbiBhbm90aGVyIHdheSBzZWdtZW50cyBoYXZlIHRvIGJlIHN0YXRpc3RpY2FsbHkgcmVsZXZhbnQgYW5kIG1hbmFnZXJpYWxseSByZWxldmFudC4KCjxici8+Cjxoci8+Cjxici8+CgoKIyBUYXJnZXRpbmcgYW5kIHNjb3JpbmcgCgpIZXJlIHdlIGJ1aWxkIGEgbW9kZWwgdG8gcHJlZGljdCBob3cgbXVjaCBtb25leSBvdXIgY3VzdG9tZXJzIGFyZSBnb2luZyB0byBzcGVuZCBvdmVyIHRoZSBuZXh0IDEyIG1vbnRocy4gV2UgdXNlIHRoZSBzYW1lIGRhdGFiYXNlIHRoYW4gYWJvdmUgYW5kIHdlIGNvbXB1dGUgdGhyZWUgbmV3IHZhcmlhYmxlczogbWF4aW1hbCBhbW91bnQgc3BlbmQsIHJldmVudWUgZnJvbSAyMDE2LCBhbmQgYSBiaW5hcnkgdmFyaWFibGUgdGhhdCBhbnN3ZXIgaWYgYSBjdXN0b21lciBib3VnaHQgYW55dGhpbmcgaW4gMjAxNiAoMSkgb3Igbm90aGluZyAoMCkuCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KIyBFeHRyYWN0IHRoZSBwcmVkaWN0b3JzOiAoZnJvbSAyMDE1KQpjdXN0b21lcnNfMjAxNSA9IHNxbGRmKCJTRUxFQ1QgY3VzdG9tZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNSU4oZGF5c19zaW5jZSkgLSAzNjUgQVMgJ3JlY2VuY3knLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKGRheXNfc2luY2UpIC0gMzY1IEFTICdmaXJzdF9wdXJjaGFzZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAnZnJlcXVlbmN5JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFWRyhUb3RhbCkgQVMgJ2F2Z19hbW91bnQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKFRvdGFsKSBBUyAnbWF4X2Ftb3VudCcKICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBPcmRlcnMKICAgICAgICAgICAgICAgICAgICAgICAgV0hFUkUgZGF5c19zaW5jZSA+IDM2NQogICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSAxIikKCiMgQ29tcHV0ZSByZXZlbnVlcyBnZW5lcmF0ZWQgYnkgY3VzdG9tZXJzIGluIDIwMTYKcmV2ZW51ZV8yMDE2ID0gc3FsZGYoIlNFTEVDVCBjdXN0b21lcl9pZCwgU1VNKFRvdGFsKSBBUyAncmV2ZW51ZV8yMDE2JwogICAgICAgICAgICAgICAgICAgICAgRlJPTSBPcmRlcnMKICAgICAgICAgICAgICAgICAgICAgIFdIRVJFIHllYXJfb2ZfcHVyY2hhc2UgPSAyMDE2CiAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSAxIikKCiMgTWVyZ2UgMjAxNSBjdXN0b21lcnMgYW5kIDIwMTYgcmV2ZW51ZQppbl9zYW1wbGUgPSBtZXJnZShjdXN0b21lcnNfMjAxNSwgcmV2ZW51ZV8yMDE2LCBhbGwueCA9IFRSVUUpCmluX3NhbXBsZSRyZXZlbnVlXzIwMTZbaXMubmEoaW5fc2FtcGxlJHJldmVudWVfMjAxNildID0gMAppbl9zYW1wbGUkYWN0aXZlXzIwMTUgPSBhcy5udW1lcmljKGluX3NhbXBsZSRyZXZlbnVlXzIwMTYgPiAwKQoKIyBEaXNwbGF5IGNhbGlicmF0aW9uIChpbi1zYW1wbGUpIGRhdGEKaGVhZChpbl9zYW1wbGUpCnN1bW1hcnkoaW5fc2FtcGxlKQpgYGAKCldlIG5vdyBoYXZlIDcgdmFyaWFibGVzIHRoYXQgYnkgdGhlbXNlbHZlcyBhcmUgbm90IHNheWluZyBtdWNoLCBidXQgd2hlbiBjb21iaW5lZCB0b2dldGhlciB0ZWxsIGFuIGludGVyZXN0aW5nIHN0b3J5LiBBbmQgdG8gZ3JhcHMgdGhpcyBzdG9yeSB3ZSBhcmUgZ29pbmcgdG8gY3JlYXRlIGEgY2FsaWJyYXRpb24gbW9kZWwuCgojIyBDYWxpYnJhdGUgdGhlIHByb2JhYmlsaXR5IG1vZGVsCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KbGlicmFyeShubmV0KQpwcm9iLm1vZGVsID0gbXVsdGlub20oZm9ybXVsYSA9IGFjdGl2ZV8yMDE1IH4gcmVjZW5jeSArIGZpcnN0X3B1cmNoYXNlICsgZnJlcXVlbmN5ICsgYXZnX2Ftb3VudCArIG1heF9hbW91bnQsCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gaW5fc2FtcGxlKQpgYGAKClRoZSBtb2RlbCBwcmVkaWN0IGN1c3RvbWVy4oCZIHByb2JhYmlsaXRpZXMuIEhlcmUgdGhlIGltcG9ydGFuY2Ugb2YgZWFjaCBwcmVkaWN0b3IgaXMgc2hvd24gYnkgd2hhdCB3ZSBjYWxsIHdlaWdodHMgYW5kIHRoZWlyIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZS4gSWYgdGhlIHdlaWdodHMgYXJlIGxhcmdlIGl0IG1lYW5zIHRoZXkgYXJlIGdvb2QgcHJlZGljdG9ycy4gSWYgbm90LCBpdCBtZWFucyB0aGV5IGNvbnRyaWJ1dGUgdmVyeSBsaXR0bGUgdG8gdGhlIHByZWRpY3Rpb25zLgoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CmNvZWYgPSBzdW1tYXJ5KHByb2IubW9kZWwpJGNvZWZmaWNpZW50cwpzdGQgID0gc3VtbWFyeShwcm9iLm1vZGVsKSRzdGFuZGFyZC5lcnJvcnMKCiMgUmF0aW8gCnByaW50KGNvZWYgLyBzdGQpCmBgYAoKT3VyIHJlc3VsdHMgc2hvdyB0byB3aGljaCBleHRlbnQgZWFjaCBwYXJhbWV0ZXJzIGFyZSBzaWduaWZpY2FudC4gV2Ugc2VlIHRoYXQgcmVjZW5jeSBhbmQgZnJlcXVlbmN5IGFyZSB0aGUgbW9zdCBtZWFuaW5nZnVsIHByZWRpY3RvciBpbiBvdXIgbW9kZWwuCgojIyBDYWxpYnJhdGUgdGhlIG1vbmV0YXJ5IG1vZGVsCgpOb3cgZm9yIG91ciBtb25ldGFyeSBtb2RlbCwgd2UgbmVlZCB0byBzZWxlY3Qgb25seSB0aG9zZSB3aG8gbWFkZSBhIHB1cmNoYXNlLiBIZXJlIHdlIHRyeSB0byBwcmVkaWN0IGhvdyBtdWNoIC1hY3RpdmUgY3VzdG9tZXJzLSBhcmUgZ29pbmcgdG8gc3BlbmQgbmV4dCB5ZWFyLiBOb3RlIHRoYXQgd2UgYXJlIHVzaW5nIHRoZSBsb2dhcml0aG1pYyBmdW5jdGlvbiB0byByZWR1Y2UgdGhlIGluZmx1ZW5jZSBwb3dlciBmcm9tIHRoZSBmZXcgb3V0bGllcnMuIAoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CiMgU2VsZWN0IG9ubHkgYWN0aXZlIGN1c3RvbWVyOiAKeiA9IHdoaWNoKGluX3NhbXBsZSRhY3RpdmVfMjAxNSA9PSAxKQoKIyBDYWxpYnJhdGUgdGhlIG1vbmV0YXJ5IG1vZGVsLCB1c2luZyBhIGxvZy10cmFuc2Zvcm0KYW1vdW50Lm1vZGVsID0gbG0oZm9ybXVsYSA9IGxvZyhyZXZlbnVlXzIwMTYpIH4gbG9nKGF2Z19hbW91bnQpICsgbG9nKG1heF9hbW91bnQpLCBkYXRhID0gaW5fc2FtcGxlW3osIF0pCnN1bW1hcnkoYW1vdW50Lm1vZGVsKQoKIyBQbG90IHRoZSByZXN1bHRzIG9mIHRoaXMgbmV3IG1vbmV0YXJ5IG1vZGVsCiMjIFRoZSBmaXR0ZWQgdmFsdWVzIGFyZSB0aGUgdmFsdWUgcHJlZGljdGVkIGJ5IHRoZSBtb2RlbApwbG90KHggPSBsb2coaW5fc2FtcGxlW3osIF0kcmV2ZW51ZV8yMDE2KSwgeSA9IGFtb3VudC5tb2RlbCRmaXR0ZWQudmFsdWVzLCBjb2w9IiMyQzNFNTAiLCB4bGFiID0gInJldmVudWVfMjAxNiIsIHlsYWIgPSAiZml0dGVkLnZhbHVlcyIpCmBgYAoKCiMjIEFwcGx5IHRoZSBtb2RlbHMKCldlIGFyZSBwcmVkaWN0aW5nIHR3byB0aGluZ3MuIFRoZSBmaXJzdCBpcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCBhbiBhY3RpdmUgY3VzdG9tZXIgd2lsbCBidXkgYW5kIHRoZSBzZWNvbmQgaXMgdGhlIGFtb3VudCB0aGV5IHdpbGwgc3BlbnQuCgpGaXJzdCB3ZSBjb21wdXRlIHRoZSBSRk0gdmFyaWFibGVzIG9mIHRvZGF5IC1ub3RlIHRoYXQgd2Ugbm93IHdvcmsgZnJvbSB0aGUgZnVsbCBkYXRhYmFzZSBhbmQgbm90IGp1c3QgYSBzYW1wbGUtLCB0aGVuIHdlIHByZWRpY3QgdGhlIHRhcmdldCB2YXJpYWJsZXMgYmFzZWQgb24gdG9kYXkncyBkYXRhLiBIZXJlIGlzIG91ciBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcy4KCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLndpZHRoPTEwfQpjdXN0b21lcnNfMjAxNiA9IHNxbGRmKCJTRUxFQ1QgY3VzdG9tZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNSU4oZGF5c19zaW5jZSkgQVMgJ3JlY2VuY3knLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKGRheXNfc2luY2UpIEFTICdmaXJzdF9wdXJjaGFzZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAnZnJlcXVlbmN5JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFWRyhUb3RhbCkgQVMgJ2F2Z19hbW91bnQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKFRvdGFsKSBBUyAnbWF4X2Ftb3VudCcKICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBPcmRlcnMgR1JPVVAgQlkgMSIpCgpjdXN0b21lcnNfMjAxNiRwcm9iX3ByZWRpY3RlZCA9IHByZWRpY3Qob2JqZWN0ID0gcHJvYi5tb2RlbCwgbmV3ZGF0YSA9IGN1c3RvbWVyc18yMDE2LCB0eXBlID0gInByb2JzIikKIyBUbyBnZXQgdGhlIHJlYWwgdmFsdWUgZnJvbSB0aGUgbG9nYXJpdGhtIHdlIGhhdmUgdG8gdXNlIHRoZSBleHBvbmFudDoKY3VzdG9tZXJzXzIwMTYkcmV2ZW51ZV9wcmVkaWN0ZWQgPSBleHAocHJlZGljdChvYmplY3QgPSBhbW91bnQubW9kZWwsIG5ld2RhdGEgPSBjdXN0b21lcnNfMjAxNikpCmN1c3RvbWVyc18yMDE2JHNjb3JlX3ByZWRpY3RlZCA9IGN1c3RvbWVyc18yMDE2JHByb2JfcHJlZGljdGVkICogY3VzdG9tZXJzXzIwMTYkcmV2ZW51ZV9wcmVkaWN0ZWQKCiMgUHJlZGljdGVkIHByb2JhYmlsaXRpZXM6CnN1bW1hcnkoY3VzdG9tZXJzXzIwMTYkcHJvYl9wcmVkaWN0ZWQpCmBgYAoKTm93IHdlIHNlZSB0aGUgcmV2ZW51ZSBwcmVkaWN0ZWQgYnkgb3VyIG1vZGVsLiBPbiBhdmVyYWdlIG91ciBjdXN0b21lcnMgd2lsbCBzcGVuZCAkIDQ1LiBXZSBhbHNvIGhhdmUgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZGlzdHJpYnV0aW9uOgoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CnN1bW1hcnkoY3VzdG9tZXJzXzIwMTYkcmV2ZW51ZV9wcmVkaWN0ZWQpCmBgYAoKVGhlIHRoaXJkIHRoaW5ncyB3ZSBwcmVkaWN0IGlzIGNhbGxlZCAqKnNjb3JlIHByZWRpY3RlZCoqIGFuZCBpdHMgdGhlIHByb2R1Y3Qgb2YgdGhlIHR3byBmaXJzdCB2YWx1ZXMgcHJlZGljdGVkLiBJdCdzIHRoZSBhdmVyYWdlIGZvciBldmVyeSBjdXN0b21lciBuZXh0IHllYXIuIFRoZSBkaXN0cmlidXRpb24gb2YgdGhpcyBzcGVuZGluZyBnb2VzIGZyb20gMCB0byBleHRyZW1lIHZhbHVlcy4KCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLndpZHRoPTEwfQpzdW1tYXJ5KGN1c3RvbWVyc18yMDE2JHNjb3JlX3ByZWRpY3RlZCkKYGBgCgpUaGlzIGxhc3QgZmlndXJlIGlzIGltcG9ydGFudCBhcyBpdCB0ZWxscyB1cyBob3cgbWFueSBjdXN0b21lcnMgaGF2ZSBhbiBleHBlY3RlZCByZXZlbnVlIG9mIG1vcmUgdGhhbiAkNTAuIFdlIGNhbiBldmVuIHNlZSB3aG8gdGhlc2UgY3VzdG9tZXJzIGFyZSwgYW5kIGxheSBvdXQgYSBzcGVjaWFsIHJlbGF0aW9uc2hpcCB3aXRoIHRoZW0uIE5vdyB3ZSBzZWUgdGhhdCB0aGVyZSBhcmUgMjAwNiBjdXN0b21lcnMgdGhhdCBoYXZlIGFuIGV4cGVjdGVkIHJldmVudWUgb2YgbW9yZSB0aGFuICQ1MCwgYW5kIHdlIHByaW50IHRoZSBmaXJzdCBjdXN0b21lcl9pZCBvZiB0aGlzIG5ldyBncm91cDoKCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLndpZHRoPTEwfQp6ID0gd2hpY2goY3VzdG9tZXJzXzIwMTYkc2NvcmVfcHJlZGljdGVkID4gNTApCnByaW50KGxlbmd0aCh6KSkKYGBgCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLndpZHRoPTEwfQpoZWFkKHopCmBgYAoKCjxici8+Cjxoci8+Cjxici8+CgoKIyBDdXN0b21lciBsaWZldGltZSB2YWx1ZQoKVGhlIHBvaW50IG9mIGEgY3VzdG9tZXIgbGlmZXRpbWUgdmFsdWUgbW9kZWxzIC1vciBDTFYtIGlzIHRvIHNlZSB3aGF0IGlzIG9uIGF2ZXJhZ2UgdGhlIHZhbHVlIG9mIGVhY2ggY3VzdG9tZXIgZnJvbSB0aGUgZmlyc3QgcHVyY2hhc2UgdG8gdGhlIGxhc3QuIFRoZSBnb2FsIG9mIHN1Y2ggbWV0aG9kcyBpcyB0byBhbmFseXNlIHdoYXQgaXMgaGFwcGVuaW5nIHRvZGF5IGFuZCB3aGF0IGhhcyBoYXBwZW5lZCBpbiB0aGUgcmVjZW50IHBhc3QgaW4gb3JkZXIgdG8gcHJlZGljdCB0aGUgcmV2ZW51ZXMgY3VzdG9tZXJzIHdpbGwgZ2VuZXJhdGUgaW4gdGhlIGZ1dHVyZS4KCkN1c3RvbWVyIGxpZmV0aW1lIHZhbHVlIG1vZGVscyBoYXZlIG1hbnkgb3RoZXIgYXBwbGljYXRpb25zIGluIHByYWN0aWNlLiBGb3IgaW5zdGFuY2UsIHlvdSBjb3VsZCBjb21wYXJlIG9uZSBhY3F1aXNpdGlvbiBjYW1wYWlnbiB0byBhbm90aGVyLCBvciB0aGUgZnVsbCBsaWZlIHRpbWUgdmFsdWUgb2YgZGlmZmVyZW50IGN1c3RvbWVyIHNlZ21lbnRzLgoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CiMgQ0hBTkdFIEFMTCBUSEUgREFURSEhCmN1c3RvbWVyc18yMDE2ID0gc3FsZGYoIlNFTEVDVCBjdXN0b21lcl9pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1JTihkYXlzX3NpbmNlKSBBUyAncmVjZW5jeScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNQVgoZGF5c19zaW5jZSkgQVMgJ2ZpcnN0X3B1cmNoYXNlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICdmcmVxdWVuY3knLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVZHKFRvdGFsKSBBUyAnYW1vdW50JwogICAgICAgICAgICAgICAgICAgICAgICBGUk9NIE9yZGVycyBHUk9VUCBCWSAxIikKY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9ICJOQSIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNiRyZWNlbmN5ID4gMzY1KjMpXSA9ICJpbmFjdGl2ZSIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNiRyZWNlbmN5IDw9IDM2NSozICYgY3VzdG9tZXJzXzIwMTYkcmVjZW5jeSA+IDM2NSoyKV0gPSAiY29sZCIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNiRyZWNlbmN5IDw9IDM2NSoyICYgY3VzdG9tZXJzXzIwMTYkcmVjZW5jeSA+IDM2NSoxKV0gPSAid2FybSIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNiRyZWNlbmN5IDw9IDM2NSldID0gImFjdGl2ZSIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNiRzZWdtZW50ID09ICJ3YXJtIiAmIGN1c3RvbWVyc18yMDE2JGZpcnN0X3B1cmNoYXNlIDw9IDM2NSoyKV0gPSAibmV3IHdhcm0iCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9PSAid2FybSIgJiBjdXN0b21lcnNfMjAxNiRhbW91bnQgPCAxMDApXSA9ICJ3YXJtIGxvdyB2YWx1ZSIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNiRzZWdtZW50ID09ICJ3YXJtIiAmIGN1c3RvbWVyc18yMDE2JGFtb3VudCA+PSAxMDApXSA9ICJ3YXJtIGhpZ2ggdmFsdWUiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9PSAiYWN0aXZlIiAmIGN1c3RvbWVyc18yMDE2JGZpcnN0X3B1cmNoYXNlIDw9IDM2NSldID0gIm5ldyBhY3RpdmUiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9PSAiYWN0aXZlIiAmIGN1c3RvbWVyc18yMDE2JGFtb3VudCA8IDEwMCldID0gImFjdGl2ZSBsb3cgdmFsdWUiCmN1c3RvbWVyc18yMDE2JHNlZ21lbnRbd2hpY2goY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9PSAiYWN0aXZlIiAmIGN1c3RvbWVyc18yMDE2JGFtb3VudCA+PSAxMDApXSA9ICJhY3RpdmUgaGlnaCB2YWx1ZSIKY3VzdG9tZXJzXzIwMTYkc2VnbWVudCA9IGZhY3Rvcih4ID0gY3VzdG9tZXJzXzIwMTYkc2VnbWVudCwgbGV2ZWxzID0gYygiaW5hY3RpdmUiLCAiY29sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIndhcm0gaGlnaCB2YWx1ZSIsICJ3YXJtIGxvdyB2YWx1ZSIsICJuZXcgd2FybSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImFjdGl2ZSBoaWdoIHZhbHVlIiwgImFjdGl2ZSBsb3cgdmFsdWUiLCAibmV3IGFjdGl2ZSIpKQoKIyBTZWdtZW50IGN1c3RvbWVycyBpbiAyMDE1CmN1c3RvbWVyc18yMDE1ID0gc3FsZGYoIlNFTEVDVCBjdXN0b21lcl9pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1JTihkYXlzX3NpbmNlKSAtIDM2NSBBUyAncmVjZW5jeScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNQVgoZGF5c19zaW5jZSkgLSAzNjUgQVMgJ2ZpcnN0X3B1cmNoYXNlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICdmcmVxdWVuY3knLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVZHKFRvdGFsKSBBUyAnYW1vdW50JwogICAgICAgICAgICAgICAgICAgICAgICBGUk9NIE9yZGVycwogICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSBkYXlzX3NpbmNlID4gMzY1CiAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIDEiKQpjdXN0b21lcnNfMjAxNSRzZWdtZW50ID0gIk5BIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE1JHJlY2VuY3kgPiAzNjUqMyldID0gImluYWN0aXZlIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE1JHJlY2VuY3kgPD0gMzY1KjMgJiBjdXN0b21lcnNfMjAxNSRyZWNlbmN5ID4gMzY1KjIpXSA9ICJjb2xkIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE1JHJlY2VuY3kgPD0gMzY1KjIgJiBjdXN0b21lcnNfMjAxNSRyZWNlbmN5ID4gMzY1KjEpXSA9ICJ3YXJtIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE1JHJlY2VuY3kgPD0gMzY1KV0gPSAiYWN0aXZlIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE1JHNlZ21lbnQgPT0gIndhcm0iICYgY3VzdG9tZXJzXzIwMTUkZmlyc3RfcHVyY2hhc2UgPD0gMzY1KjIpXSA9ICJuZXcgd2FybSIKY3VzdG9tZXJzXzIwMTUkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNSRzZWdtZW50ID09ICJ3YXJtIiAmIGN1c3RvbWVyc18yMDE1JGFtb3VudCA8IDEwMCldID0gIndhcm0gbG93IHZhbHVlIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50W3doaWNoKGN1c3RvbWVyc18yMDE1JHNlZ21lbnQgPT0gIndhcm0iICYgY3VzdG9tZXJzXzIwMTUkYW1vdW50ID49IDEwMCldID0gIndhcm0gaGlnaCB2YWx1ZSIKY3VzdG9tZXJzXzIwMTUkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNSRzZWdtZW50ID09ICJhY3RpdmUiICYgY3VzdG9tZXJzXzIwMTUkZmlyc3RfcHVyY2hhc2UgPD0gMzY1KV0gPSAibmV3IGFjdGl2ZSIKY3VzdG9tZXJzXzIwMTUkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNSRzZWdtZW50ID09ICJhY3RpdmUiICYgY3VzdG9tZXJzXzIwMTUkYW1vdW50IDwgMTAwKV0gPSAiYWN0aXZlIGxvdyB2YWx1ZSIKY3VzdG9tZXJzXzIwMTUkc2VnbWVudFt3aGljaChjdXN0b21lcnNfMjAxNSRzZWdtZW50ID09ICJhY3RpdmUiICYgY3VzdG9tZXJzXzIwMTUkYW1vdW50ID49IDEwMCldID0gImFjdGl2ZSBoaWdoIHZhbHVlIgpjdXN0b21lcnNfMjAxNSRzZWdtZW50ID0gZmFjdG9yKHggPSBjdXN0b21lcnNfMjAxNSRzZWdtZW50LCBsZXZlbHMgPSBjKCJpbmFjdGl2ZSIsICJjb2xkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAid2FybSBoaWdoIHZhbHVlIiwgIndhcm0gbG93IHZhbHVlIiwgIm5ldyB3YXJtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYWN0aXZlIGhpZ2ggdmFsdWUiLCAiYWN0aXZlIGxvdyB2YWx1ZSIsICJuZXcgYWN0aXZlIikpCmBgYAoKIyMgQ29tcHV0ZSB0cmFuc2l0aW9uIG1hdHJpeAoKVGhpcyB0cmFuc2l0aW9uIG1hdHJpeCBzaG93IGhvdyBtYW55IGN1c3RvbWVycyBzd2l0Y2ggZnJvbSBvbmUgc2VnbWVudCB0byBhbm90aGVyLiBUaGUgcm93cyBkaXNwbGF5IDIwMTUgYW5kIHRoZSBjb2x1bW4gZGlzcGxheSAyMDE2LiBTbywgZm9yIGluc3RhbmNlLCB3ZSBjYW4gc2F5IHRoYXQgNDkgaW5hY3RpdmUgY3VzdG9tZXJzIGluIDIwMTUsIGJlY2FtZSBhY3RpdmUgaGlnaCB2YWx1ZSBpbiAyMDE2LiBUaGUgbmV4dCBzdGVwIGlzIHRvIHVuZGVyc3RhbmQgd2h5LiAKCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLndpZHRoPTEwfQpuZXdfZGF0YSA9IG1lcmdlKHggPSBjdXN0b21lcnNfMjAxNSwgeSA9IGN1c3RvbWVyc18yMDE2LCBieSA9ICJDdXN0b21lcl9pZCIsIGFsbC54ID0gVFJVRSkKdHJhbnNpdGlvbiA9IHRhYmxlKG5ld19kYXRhJHNlZ21lbnQueCwgbmV3X2RhdGEkc2VnbWVudC55KQpwcmludCh0cmFuc2l0aW9uKQpgYGAKClRoZSBsYXN0IGxpbmUgZGlzcGxheSB0aGUgbmV3IGFjdGl2ZSBjdXN0b21lcnMuIEl0J3MgaW50ZXJlc3RpbmcgdG8gc2VlIHRoYXQgbW9zdCBvZiB0aGVtIGFmdGVyIHRoZWlyIGZpcnN0IHB1cmNoYXNlIGJlY29tZSBuZXcgd2FybSBhbmQgbm90IGFjdGl2ZSBoaWdoIHZhbHVlLiBUaGlzIGxpbmUgaXMgcmVsZXZhbnQgaWYgd2Ugd2FudCB0byBhc3Nlc3MgdGhlIHJlc3VsdHMgb2YgYW4gYWNxdWlzaXRpb24gY2FtcGFpZ24uIAoKTm93LCB0byBzZWUgdGhlIHBlcmNlbnRhZ2UgYmVoaW5kIHRoaXMgdHJhbnNpdGlvbiBtYXRyaXggd2UgbmVlZCB0byBkaXZpZGUgZWFjaCByb3cgYnkgaXRzIHN1bS4gQW5kIHdlIG9idGFpbiB0aGUgbWF0cml4IGJlbG93LiBXZSBjYW4gc2F5IGZvciBleGFtcGxlLCB0aGF0IGlmIHlvdSB3ZXJlIGFuIGluYWN0aXZlIGN1c3RvbWVyIGluIDIwMTUsIHRoZW4geW91IGhhZCBhIDk2ICUgY2hhbmdlIG9mIHJlbWFpbmluZyBpbmFjdGl2ZS4gVGhpcyBtYXRyaXggd2lsbCBiZSB1c2VmdWwgdG8gbWFrZSBwcmVkaWN0aW9ucy4gCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KdHJhbnNpdGlvbiA9IHRyYW5zaXRpb24gLyByb3dTdW1zKHRyYW5zaXRpb24pCnByaW50KHRyYW5zaXRpb24pCmBgYAoKIyMgTWFrZSBwcmVkaWN0aW9ucwoKV2UgY2FuIHNlZSB3aGljaCBjdXN0b21lcnMgd2lsbCBnbyBmcm9tIG9uZSBzZWdtZW50IHRvIHRoZSBuZXh0IGluIHRoZSBjb21pbmcgeWVhcnMuIE91ciBtb2RlbCBjb21wdXRlIHRoZSAzIG5leHQgeWVhcnMgLW5vdGUgdGhhdCB3ZSBkaWRuJ3QgdGFrZSBpbnRvIGFjY291bnQgdGhlIG5ldyBjdXN0b21lcnMgaW4gMjAxNiBhbmQgMjAxNy0uIEJlbG93IHdlIHByZXNlbnQgdGhlIGJhciBwbG90IG9mIHRoaXMgZXZvbHV0aW9uIGZvciA0IHNlZ21lbnRzOgoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9MTB9CiMgSW5pdGlhbGl6ZSBhIG1hdHJpeCB3aXRoIHRoZSBudW1iZXIgb2YgY3VzdG9tZXJzIGluIGVhY2ggc2VnbWVudCB0b2RheSBhbmQgYWZ0ZXIgMyBwZXJpb2RzCnNlZ21lbnRzID0gbWF0cml4KG5yb3cgPSA4LCBuY29sID0gNCkKc2VnbWVudHNbLCAxXSA9IHRhYmxlKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQpCmNvbG5hbWVzKHNlZ21lbnRzKSA9IDIwMTc6MjAyMApyb3cubmFtZXMoc2VnbWVudHMpID0gbGV2ZWxzKGN1c3RvbWVyc18yMDE2JHNlZ21lbnQpCgojIENvbXB1dGUgZm9yIGVhY2ggYW4gZXZlcnkgcGVyaW9kCmZvciAoaSBpbiAyOjQpIHsKICAgc2VnbWVudHNbLCBpXSA9IHNlZ21lbnRzWywgaS0xXSAlKiUgdHJhbnNpdGlvbgp9CgojIERpc3BsYXkgaG93IHNlZ21lbnRzIHdpbGwgZXZvbHZlIG92ZXIgdGltZQpwcmludChyb3VuZChzZWdtZW50cykpCmBgYApgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KIyBQbG90IGluYWN0aXZlLCBhY3RpdmUgaGlnaCB2YWx1ZSBjdXN0b21lcnMgb3ZlciB0aW1lCnBhcihtZnJvdz1jKDIsMikpCmJhcnBsb3Qoc2VnbWVudHNbMywgXSwgY29sPSIjMkMzRTUwIiwgIG1haW49Indhcm0gaGlnaCB2YWx1ZSIpCmJhcnBsb3Qoc2VnbWVudHNbNCwgXSwgY29sPSIjMkMzRTUwIiwgIG1haW49Indhcm0gbG93IHZhbHVlIikKYmFycGxvdChzZWdtZW50c1s3LCBdLCBjb2w9IiMyQzNFNTAiLCAgbWFpbj0iYWN0aXZlIGhpZ2ggdmFsdWUiKQpiYXJwbG90KHNlZ21lbnRzWzYsIF0sIGNvbD0iIzJDM0U1MCIsICBtYWluPSJhY3RpdmUgd2FybSBsb3cgdmFsdWUiKQpgYGAKCldlIG5vdyBjb21wdXRlIHRoZSByZXZlbnVlIHBlciBzZWdtZW50IGZvciB0aGUgdGhyZWUgY29taW5nIHllYXJzLiBUbyBkbyB0aGF0IHdlIHVzZSBvdXIgdHJhbnNpdGlvbiBtYXRyaXggYW5kIHRoZSBhdmVyYWdlIG9yZGVyIHBlciBzZWdtZW50LiAoTm90ZSB0aGF0IHdlIHN0aWxsIGRvbid0IHRha2UgaW50byBhY2NvdW50IHRoZSBuZXcgY3VzdG9tZXIgdGhhdCB3ZSB3aWxsIHN0YXJ0IGJ1eWluZyBpbiAyMDE3IGFuZCBhZnRlci4pCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy53aWR0aD0xMH0KeWVhcmx5X3JldmVudWUgPSBjKDAsIDIsIDYwLCAzLCAxMCwgMzcwLCA2MCwgOTApCnJldmVudWVfcGVyX3NlZ21lbnQgPSB5ZWFybHlfcmV2ZW51ZSAqIHNlZ21lbnRzCnByaW50KHJldmVudWVfcGVyX3NlZ21lbnQpCmBgYAoKPGJyLz4KPGhyLz4KPGJyLz4KCiMgQ29uY2x1c2lvbgoKVGhpcyBhbmFseXNpcyBpcyBhIGZpcnN0IHN0ZXAgdG93YXJkIGEgKipiZXR0ZXIgY3VzdG9tZXIncyBvcmllbnRlZCBidXNpbmVzcyoqLiBOb3csIHRoZSBtYW5hZ2VycyBjYW4gKipjdXN0b21pc2UgdGhlaXIgb2ZmZXJpbmcsIGFkYXB0IHRoZWlyIG1lc3NhZ2VzIGFuZCBvcHRpbWlzZSB0aGVpciBtYXJrZXRpbmcgY2FtcGFpZ25zKiogbXVjaCBiZXR0ZXIgdGhhbiBmZXcgbW9udGhzIGFnby4gQnV0IHRoZXkgY2FuIGFsc28gbWVhc3VyZSB0aGVpciByZXN1bHRzIGluIGEgbW9yZSBhY2N1cmF0ZSBtYW5uZXIuCgoqKlNvLi4uIFdoYXQncyBuZXh0PyoqIApNb3JlIGFuYWx5dGljcyB3aWxsIHJlcXVpcmUgbW9yZSBkYXRhIGFib3V0IHRoZSBwcm9kdWN0cyBzb2xkIGFuZCBhYm91dCB0aGUgY3VzdG9tZXJzLiBXZSBjb3VsZCBydW4gYW4gYXNzb2NpYXRpb24gcnVsZSBhbGdvcml0aG0gdG8gc2VlIHdoYXQgcHJvZHVjdHMgdGVuZHMgdG8gYmUgYm91Z2h0IHRvZ2V0aGVyIGFuZCB0aGVyZWZvcmUgcmVvcmdhbmlzZWQgdGhlIHN0b3JlIHJlbGV2YW50bHkuCk9uIHRoZSBjdXN0b21lciBzaWRlLCBpdCB3aWxsIGJlIHVzZWZ1bCB0byBrbm93IHRoZSBsb2NhdGlvbiwgc2V4IGFuZCBhZ2Ugb2YgY3VzdG9tZXJzLiBGb3IgZXhhbXBsZSB3b21hbiBhbmQgbWFuIG1pZ2h0IGhhdmUgdmVyeSBkaWZmZXJlbnQgYnV5aW5nIGJlaGF2aW91ciBhbmQgdGhhdCBpcyBpbXBvcnRhbnQgdG8gbm90aWNlLiAKCkZpbmFsbHksIHdlIHByb3Bvc2UgdG8gdGhlIHN0b3JlIGEgcGxhbiB0byBnYXRoZXIgbW9yZSBkYXRhIGFib3V0IHRoZWlyIGN1c3RvbWVyIGFuZCB3ZSBwcmVzZW50IHRoZSBuZXh0IHBvc3NpYmxlIHN0ZXAgdG93YXJkIGEgbW9yZSBkYXRhLWRyaXZlbiBidXNpbmVzcy4gV2Ugc2hvdyBob3cgYXBwbGllZCBtYWNoaW5lIGxlYXJuaW5nIHdpdGggYmlnZ2VyIGRhdGFzZXQgd2lsbCBpbXByb3ZlIHRoZSBwcmVkaWN0YWJpbGl0eSBhbmQgc2VnbWVudGF0aW9uIGZvciB0aGUgc3RvcmUuCgoKCg==