Introduction

The Bayes classifier is a classification method, meaning that it predicts a categorical response for each possible value of a set of explanatory variables. Based on the way it works, one can prove mathematically (though we don’t do so in these notes) that the Bayes classifier has an error rate that serves as a lower bound for the expected test error of any other possible classifier. In other words, no matter how good a fancy classification algorithm is, one cannot get (on average) an error rate that is lower than the Bayes error rate.

In the real world, we cannot actually use the Bayes classifier or compute the Bayes error rate. This is because the Bayes classifier relies on knowledge of a “true” data-generating process that we never have. (If we knew the true data-generating process, we would know everything we wanted to know statistically about the population we’re trying to study, negating the need for applying a classification algorithm to data.)

So why study the Bayes classifier? Because it’s an important theoretical concept that tells us there are natural limits on our ability to make predictions from data. There is a certain amount of irreducible error in any classification process.

Preliminaries

library(tidyverse)

Fake data

Because the Bayes classifier requires a knowledge of the true probabilities of each response category, we have to simulate some fake data in order to explore it.

The idea here is to imagine that x is the value of some explanatory variable that takes values between 0 and 1. Assume there is some response variable that is categorical and takes two value, red and blue.

For each point along the x-axis between 0 and 1, we’ll define a function that describes the probability of being blue. Here is such a function:

\[prob(x) = \frac{11}{3}x - 8x^{2} + \frac{16}{3}x^{3}.\]

And in R:

prob <- function(x) {
    (11/3)*x - 8*(x^2) + (16/3)*(x^3)
}

Here is a plot of this function:

prob_plot <- ggplot(data.frame(x = c(0, 1)), aes(x)) +
    stat_function(fun = prob) +
    labs(y = "prob")
prob_plot

To be clear, this is not a probability density function. The area under this curve is not one. We’re not describing the probability density of selecting any given x. When you pick a value of x, the curve height tells you the probability that the chosen value will have blue as its response.

As an example, suppose x = 0.1. The value of the function at x = 0.1 is 29.2%:

prob(0.1)
[1] 0.292
prob_plot +
    geom_segment(x = 0.1, xend = 0.1,
                 y = 0, yend = prob(0.1),
                 color = "blue", size = 1) +
    geom_segment(x = 0.1, xend = 0.1,
                 y = prob(0.1), yend = 1,
                 color = "red", size = 1) +
    geom_point(x = 0.1, y = prob(0.1), size = 3)

There is a 29.2% chance of this point being blue. That means that there is also a 70.8% chance of it being red. Whether such a point actually turns out to be blue or red remains to be seen when we gather data. Or another way to think about it is that in the population of all data points that have x = 0.1, 29.2% will be blue and 70.8% will be red.

This function was also chosen for convenience so that the probabilities cross the 50% mark at x = 0.25, x = 0.5, and x = 0.75. According to this function, points on the left are more likely to be red up to x = 0.25, at which point the probability shifts to favor blue slightly. At x = 0.5, the probability shifts back to favor red slightly. Finally, to the right of x = 0.75, points are more likely to be blue again. Points near the center are not very well determined because their probabilities are very close to 50%. The following illustrates this a little more vividly:

prob_plot_color <- prob_plot +
    geom_ribbon(data = data.frame(x = seq(0, 1, 0.01)),
                aes(x = x, ymin = 0, ymax = prob(x)),
                fill = "blue", alpha = 0.25) +
    geom_ribbon(data = data.frame(x = seq(0, 1, 0.01)),
                aes(x = x, ymin = prob(x), ymax = 1),
                fill = "red", alpha = 0.25) +
    labs(y = "prob") +
    geom_hline(yintercept = 0.5, linetype = "dashed") +
    geom_vline(xintercept = c(0.25, 0.5, 0.75),
               linetype = "dotted")
prob_plot_color

Please don’t confuse this picture with the pictures of Bayes classifiers from the book! Those examples in the book show probabilities defined in a two-dimensional explanatory variable space (X1 and X2). Our example is a one-dimensional space, only along the x-axis. We only need a second dimension in this picture to show the probabilities “sitting above” each x value.

Now that we have our probability function, let’s simulate some fake data. Here are 100 random x values:

set.seed(11111)
explanatory <- runif(100)
fake_data <- tibble(explanatory)
fake_data

Now we choose a response variable. We’ll pick colors at random, but constrained by the probabilities for each x value. (The Bernoulli distribution is effectively a coin-flipping mechanism, but instead of heads and tails, we are flipping blues and reds according to the probabilities given by the prob function.)

set.seed(22222)
fake_data <- fake_data %>%
    rowwise() %>%
    mutate(response =
               ifelse(rbernoulli(1, p = prob(explanatory)),
                      "Blue", "Red"))
fake_data

Let’s plot the sample below our previous graph:

prob_plot_response <- prob_plot_color +
    geom_jitter(data = fake_data,
                aes(x = explanatory, y = -0.2,
                    color = response),
                position = position_jitter(width = 0,
                                           height = 0.2,
                                           seed = 1)) +
    scale_colour_manual(values = c("Blue", "Red")) +
    guides(color = FALSE)
prob_plot_response

There’s a lot going on in this picture because we are showing the true probability function and we’ve vertically jittered the x values so they are all visible. In the real world, this is all we see:

x_plot <- ggplot(fake_data, aes(x = explanatory, y = 0)) +
    geom_segment(x = 0, xend = 1, y = 0, yend = 0,
                 color = "black") +
    scale_colour_manual(values = c("Blue", "Red")) +
    guides(color = FALSE) +
    theme(axis.text.y = element_blank(),
          axis.ticks.y = element_blank(),
          axis.title.y = element_blank(),
          panel.grid.major.y = element_blank(),
          panel.grid.minor.y = element_blank())
x_plot_response <- x_plot + 
    geom_point(aes(color = response))
x_plot_response

The Bayes classifier

The Bayes classifier operates using a very simple rule: classify each point based on the the highest probability given the value of any explanatory variables. In symbols, we calculate the probability of the response variable taking on all possible classes given the explanatory variable:

\[Pr(Response = \text{Blue} \mid Explanatory = x)\] \[Pr(Response = \text{Red} \mid Explanatory = x)\]

In general, there may be more than two categories. Either way, just choose the category for which the probability listed above is highest.

For example, at x = 0.1, the probability of blue is 29.2%, and therefore it follows that the probability of red is 70.8%. Therefore, a point with x = 0.1 should be classified as red. Keep in mind that any actual data point located at x = 0.1 is not forced to be red. There’s still some probability of it being blue, but the Bayes classifier will predict that it is red.

This sounds easy, but keep in mind, the Bayes classifier is a theoretical construct only; in practice, we never know the true probability distribution that describes the pattern of responses.

However, our data is fake data that we simulated, so we have the true probability distributions. Therefore, we can classify points according to the Bayes classifier. Let’s create some new columns in our data frame, one for the predicted value according to the Bayes classifier, and another to indicate if the classifier made a correct prediction (comparing it with the actual response value).

fake_data <- fake_data %>%
    mutate(prediction = ifelse(prob(explanatory) >= 0.5,
                               "Blue", "Red"),
           correct = ifelse(response == prediction,
                            TRUE, FALSE))
fake_data

Here are the points as classified by the Bayes classifier:

prob_plot_pred <- prob_plot_color +
    geom_jitter(data = fake_data,
                aes(x = explanatory, y = -0.2,
                    color = prediction),
                position = position_jitter(width = 0,
                                           height = 0.2,
                                           seed = 1)) +
    scale_colour_manual(values = c("Blue", "Red")) +
    guides(color = FALSE)
prob_plot_pred

This shows only our training data, but keep in mind that any point along the x-axis from 0 to 1 can be classified into blue or red using the Bayes classifier. The dotted vertical lines mark the Bayes decision boundary. This boundary separates the regions where the classifier makes a color prediction. Across these boundary points, the classifier changes its prediction from one color to another.

Again, be careful not to confuse this one-dimensional example from the book’s two-dimensional example. In one-dimensional terms, this is what the Bayes decision boundary looks like:

x_plot_pred <- x_plot +
    geom_point(aes(color = prediction)) +
    geom_segment(x = 0.25, xend = 0.25,
                 y = -0.005, yend = 0.005,
                 color = "black", size = 1) +
    geom_segment(x = 0.5, xend = 0.5,
                 y = -0.005, yend = 0.005,
                 color = "black", size = 1) +
    geom_segment(x = 0.75, xend = 0.75,
                 y = -0.005, yend = 0.005,
                 color = "black", size = 1)
x_plot_pred

Here are the Bayes decision boundaries again, but this time with the actual response values:

x_plot_response +
    geom_segment(x = 0.25, xend = 0.25,
                 y = -0.005, yend = 0.005,
                 color = "black", size = 1) +
    geom_segment(x = 0.5, xend = 0.5,
                 y = -0.005, yend = 0.005,
                 color = "black", size = 1) +
    geom_segment(x = 0.75, xend = 0.75,
                 y = -0.005, yend = 0.005,
                 color = "black", size = 1)

If we compare the actual responses to the Bayes decision boundary, we note that quite a few points are misclassified by the Bayes classifier.

Returning to the plot with the probability function, the squares indicate correct predictions and the crosses indicate incorrect predictions:

prob_plot_all <- prob_plot_response +
    geom_jitter(data = fake_data,
                aes(x = explanatory, y = -0.2,
                    shape = correct),
                size = 4,
                position = position_jitter(width = 0,
                                           height = 0.2,
                                           seed = 1)) +
    scale_shape_manual(values=c(4, 0)) +
    guides(shape = FALSE)
prob_plot_all

The Bayes error rate is the percentage of incorrectly classified responses using the Bayes classifier.

1 - mean(fake_data$correct)
[1] 0.41

So it’s a relative large 41% here. The theory behind the Bayes classifier says that no classification strategy will give you better than a 41% test error rate. I mean, it’s possible for any given data set and any given classifier to get lucky, but overall, we can’t expect (on average) that any other classifier will do better than 59% on new test data generated from the same probability distribution.

The Bayes classifier in higher dimensions

In our one-dimensional example above, the Bayes decision boundary was a set of three points on the x-axis. The four regions thus created are regions of “constant prediction”, meaning that the Bayes classifier classifies everything within a region using a single color. The classifier only changes colors across a point of the Bayes decision boundary.

In a two-dimensional space of explanatory variables (like the book examples), the Bayes decision boundary does not consist of points anymore. It’s a curve (or multiple curves) that separates the plane into regions of constant prediction.

In a three-dimensional space of explanatory variables, the Bayes decision boundary is a surface (or multiple surfaces) separating space into regions of constant prediction.

In higher dimensions, it’s harder to visualize what’s going on. For an n-dimensional space of explanatory variables, the Bayes decision boundary is an (n-1)-dimensional hypersurface!

LS0tDQp0aXRsZTogIlRoZSBCYXllcyBjbGFzc2lmaWVyIg0KYXV0aG9yOiAiU2VhbiBSYWxlaWdoIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KVGhlICpCYXllcyBjbGFzc2lmaWVyKiBpcyBhIGNsYXNzaWZpY2F0aW9uIG1ldGhvZCwgbWVhbmluZyB0aGF0IGl0IHByZWRpY3RzIGEgY2F0ZWdvcmljYWwgcmVzcG9uc2UgZm9yIGVhY2ggcG9zc2libGUgdmFsdWUgb2YgYSBzZXQgb2YgZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBCYXNlZCBvbiB0aGUgd2F5IGl0IHdvcmtzLCBvbmUgY2FuIHByb3ZlIG1hdGhlbWF0aWNhbGx5ICh0aG91Z2ggd2UgZG9uJ3QgZG8gc28gaW4gdGhlc2Ugbm90ZXMpIHRoYXQgdGhlIEJheWVzIGNsYXNzaWZpZXIgaGFzIGFuIGVycm9yIHJhdGUgdGhhdCBzZXJ2ZXMgYXMgYSBsb3dlciBib3VuZCBmb3IgdGhlIGV4cGVjdGVkIHRlc3QgZXJyb3Igb2YgYW55IG90aGVyIHBvc3NpYmxlIGNsYXNzaWZpZXIuIEluIG90aGVyIHdvcmRzLCBubyBtYXR0ZXIgaG93IGdvb2QgYSBmYW5jeSBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0gaXMsIG9uZSBjYW5ub3QgZ2V0IChvbiBhdmVyYWdlKSBhbiBlcnJvciByYXRlIHRoYXQgaXMgbG93ZXIgdGhhbiB0aGUgKkJheWVzIGVycm9yIHJhdGUqLg0KDQpJbiB0aGUgcmVhbCB3b3JsZCwgd2UgY2Fubm90IGFjdHVhbGx5IHVzZSB0aGUgQmF5ZXMgY2xhc3NpZmllciBvciBjb21wdXRlIHRoZSBCYXllcyBlcnJvciByYXRlLiBUaGlzIGlzIGJlY2F1c2UgdGhlIEJheWVzIGNsYXNzaWZpZXIgcmVsaWVzIG9uIGtub3dsZWRnZSBvZiBhICJ0cnVlIiBkYXRhLWdlbmVyYXRpbmcgcHJvY2VzcyB0aGF0IHdlIG5ldmVyIGhhdmUuIChJZiB3ZSBrbmV3IHRoZSB0cnVlIGRhdGEtZ2VuZXJhdGluZyBwcm9jZXNzLCB3ZSB3b3VsZCBrbm93IGV2ZXJ5dGhpbmcgd2Ugd2FudGVkIHRvIGtub3cgc3RhdGlzdGljYWxseSBhYm91dCB0aGUgcG9wdWxhdGlvbiB3ZSdyZSB0cnlpbmcgdG8gc3R1ZHksIG5lZ2F0aW5nIHRoZSBuZWVkIGZvciBhcHBseWluZyBhIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSB0byBkYXRhLikNCg0KU28gd2h5IHN0dWR5IHRoZSBCYXllcyBjbGFzc2lmaWVyPyBCZWNhdXNlIGl0J3MgYW4gaW1wb3J0YW50IHRoZW9yZXRpY2FsIGNvbmNlcHQgdGhhdCB0ZWxscyB1cyB0aGVyZSBhcmUgbmF0dXJhbCBsaW1pdHMgb24gb3VyIGFiaWxpdHkgdG8gbWFrZSBwcmVkaWN0aW9ucyBmcm9tIGRhdGEuIFRoZXJlIGlzIGEgY2VydGFpbiBhbW91bnQgb2YgaXJyZWR1Y2libGUgZXJyb3IgaW4gYW55IGNsYXNzaWZpY2F0aW9uIHByb2Nlc3MuDQoNCg0KIyMgUHJlbGltaW5hcmllcw0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCiMjIEZha2UgZGF0YQ0KDQpCZWNhdXNlIHRoZSBCYXllcyBjbGFzc2lmaWVyIHJlcXVpcmVzIGEga25vd2xlZGdlIG9mIHRoZSB0cnVlIHByb2JhYmlsaXRpZXMgb2YgZWFjaCByZXNwb25zZSBjYXRlZ29yeSwgd2UgaGF2ZSB0byBzaW11bGF0ZSBzb21lIGZha2UgZGF0YSBpbiBvcmRlciB0byBleHBsb3JlIGl0Lg0KDQpUaGUgaWRlYSBoZXJlIGlzIHRvIGltYWdpbmUgdGhhdCB4IGlzIHRoZSB2YWx1ZSBvZiBzb21lIGV4cGxhbmF0b3J5IHZhcmlhYmxlIHRoYXQgdGFrZXMgdmFsdWVzIGJldHdlZW4gMCBhbmQgMS4gQXNzdW1lIHRoZXJlIGlzIHNvbWUgcmVzcG9uc2UgdmFyaWFibGUgdGhhdCBpcyBjYXRlZ29yaWNhbCBhbmQgdGFrZXMgdHdvIHZhbHVlLCByZWQgYW5kIGJsdWUuDQoNCkZvciBlYWNoIHBvaW50IGFsb25nIHRoZSB4LWF4aXMgYmV0d2VlbiAwIGFuZCAxLCB3ZSdsbCBkZWZpbmUgYSBmdW5jdGlvbiB0aGF0IGRlc2NyaWJlcyB0aGUgcHJvYmFiaWxpdHkgb2YgYmVpbmcgYmx1ZS4gSGVyZSBpcyBzdWNoIGEgZnVuY3Rpb246DQoNCiQkcHJvYih4KSA9IFxmcmFjezExfXszfXggLSA4eF57Mn0gKyBcZnJhY3sxNn17M314XnszfS4kJA0KDQpBbmQgaW4gUjoNCg0KYGBge3J9DQpwcm9iIDwtIGZ1bmN0aW9uKHgpIHsNCiAgICAoMTEvMykqeCAtIDgqKHheMikgKyAoMTYvMykqKHheMykNCn0NCmBgYA0KDQpIZXJlIGlzIGEgcGxvdCBvZiB0aGlzIGZ1bmN0aW9uOg0KDQpgYGB7cn0NCnByb2JfcGxvdCA8LSBnZ3Bsb3QoZGF0YS5mcmFtZSh4ID0gYygwLCAxKSksIGFlcyh4KSkgKw0KICAgIHN0YXRfZnVuY3Rpb24oZnVuID0gcHJvYikgKw0KICAgIGxhYnMoeSA9ICJwcm9iIikNCnByb2JfcGxvdA0KYGBgDQoNClRvIGJlIGNsZWFyLCB0aGlzIGlzIG5vdCBhIHByb2JhYmlsaXR5IGRlbnNpdHkgZnVuY3Rpb24uIFRoZSBhcmVhIHVuZGVyIHRoaXMgY3VydmUgaXMgbm90IG9uZS4gV2UncmUgbm90IGRlc2NyaWJpbmcgdGhlIHByb2JhYmlsaXR5IGRlbnNpdHkgb2Ygc2VsZWN0aW5nIGFueSBnaXZlbiB4LiBXaGVuIHlvdSBwaWNrIGEgdmFsdWUgb2YgeCwgdGhlIGN1cnZlIGhlaWdodCB0ZWxscyB5b3UgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIGNob3NlbiB2YWx1ZSB3aWxsIGhhdmUgYmx1ZSBhcyBpdHMgcmVzcG9uc2UuDQoNCkFzIGFuIGV4YW1wbGUsIHN1cHBvc2UgeCA9IDAuMS4gVGhlIHZhbHVlIG9mIHRoZSBmdW5jdGlvbiBhdCB4ID0gMC4xIGlzIDI5LjIlOg0KDQpgYGB7cn0NCnByb2IoMC4xKQ0KYGBgDQoNCg0KYGBge3J9DQpwcm9iX3Bsb3QgKw0KICAgIGdlb21fc2VnbWVudCh4ID0gMC4xLCB4ZW5kID0gMC4xLA0KICAgICAgICAgICAgICAgICB5ID0gMCwgeWVuZCA9IHByb2IoMC4xKSwNCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAxKSArDQogICAgZ2VvbV9zZWdtZW50KHggPSAwLjEsIHhlbmQgPSAwLjEsDQogICAgICAgICAgICAgICAgIHkgPSBwcm9iKDAuMSksIHllbmQgPSAxLA0KICAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBzaXplID0gMSkgKw0KICAgIGdlb21fcG9pbnQoeCA9IDAuMSwgeSA9IHByb2IoMC4xKSwgc2l6ZSA9IDMpDQpgYGANCg0KVGhlcmUgaXMgYSAyOS4yJSBjaGFuY2Ugb2YgdGhpcyBwb2ludCBiZWluZyBibHVlLiBUaGF0IG1lYW5zIHRoYXQgdGhlcmUgaXMgYWxzbyBhIDcwLjglIGNoYW5jZSBvZiBpdCBiZWluZyByZWQuIFdoZXRoZXIgc3VjaCBhIHBvaW50IGFjdHVhbGx5IHR1cm5zIG91dCB0byBiZSBibHVlIG9yIHJlZCByZW1haW5zIHRvIGJlIHNlZW4gd2hlbiB3ZSBnYXRoZXIgZGF0YS4gT3IgYW5vdGhlciB3YXkgdG8gdGhpbmsgYWJvdXQgaXQgaXMgdGhhdCBpbiB0aGUgcG9wdWxhdGlvbiBvZiBhbGwgZGF0YSBwb2ludHMgdGhhdCBoYXZlIHggPSAwLjEsIDI5LjIlIHdpbGwgYmUgYmx1ZSBhbmQgNzAuOCUgd2lsbCBiZSByZWQuDQoNClRoaXMgZnVuY3Rpb24gd2FzIGFsc28gY2hvc2VuIGZvciBjb252ZW5pZW5jZSBzbyB0aGF0IHRoZSBwcm9iYWJpbGl0aWVzIGNyb3NzIHRoZSA1MCUgbWFyayBhdCB4ID0gMC4yNSwgeCA9IDAuNSwgYW5kIHggPSAwLjc1LiBBY2NvcmRpbmcgdG8gdGhpcyBmdW5jdGlvbiwgcG9pbnRzIG9uIHRoZSBsZWZ0IGFyZSBtb3JlIGxpa2VseSB0byBiZSByZWQgdXAgdG8geCA9IDAuMjUsIGF0IHdoaWNoIHBvaW50IHRoZSBwcm9iYWJpbGl0eSBzaGlmdHMgdG8gZmF2b3IgYmx1ZSBzbGlnaHRseS4gQXQgeCA9IDAuNSwgdGhlIHByb2JhYmlsaXR5IHNoaWZ0cyBiYWNrIHRvIGZhdm9yIHJlZCBzbGlnaHRseS4gRmluYWxseSwgdG8gdGhlIHJpZ2h0IG9mIHggPSAwLjc1LCBwb2ludHMgYXJlIG1vcmUgbGlrZWx5IHRvIGJlIGJsdWUgYWdhaW4uIFBvaW50cyBuZWFyIHRoZSBjZW50ZXIgYXJlIG5vdCB2ZXJ5IHdlbGwgZGV0ZXJtaW5lZCBiZWNhdXNlIHRoZWlyIHByb2JhYmlsaXRpZXMgYXJlIHZlcnkgY2xvc2UgdG8gNTAlLiBUaGUgZm9sbG93aW5nIGlsbHVzdHJhdGVzIHRoaXMgYSBsaXR0bGUgbW9yZSB2aXZpZGx5Og0KDQpgYGB7cn0NCnByb2JfcGxvdF9jb2xvciA8LSBwcm9iX3Bsb3QgKw0KICAgIGdlb21fcmliYm9uKGRhdGEgPSBkYXRhLmZyYW1lKHggPSBzZXEoMCwgMSwgMC4wMSkpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0geCwgeW1pbiA9IDAsIHltYXggPSBwcm9iKHgpKSwNCiAgICAgICAgICAgICAgICBmaWxsID0gImJsdWUiLCBhbHBoYSA9IDAuMjUpICsNCiAgICBnZW9tX3JpYmJvbihkYXRhID0gZGF0YS5mcmFtZSh4ID0gc2VxKDAsIDEsIDAuMDEpKSwNCiAgICAgICAgICAgICAgICBhZXMoeCA9IHgsIHltaW4gPSBwcm9iKHgpLCB5bWF4ID0gMSksDQogICAgICAgICAgICAgICAgZmlsbCA9ICJyZWQiLCBhbHBoYSA9IDAuMjUpICsNCiAgICBsYWJzKHkgPSAicHJvYiIpICsNCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKDAuMjUsIDAuNSwgMC43NSksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkb3R0ZWQiKQ0KcHJvYl9wbG90X2NvbG9yDQpgYGANCg0KKipQbGVhc2UgZG9uJ3QgY29uZnVzZSB0aGlzIHBpY3R1cmUgd2l0aCB0aGUgcGljdHVyZXMgb2YgQmF5ZXMgY2xhc3NpZmllcnMgZnJvbSB0aGUgYm9vayEqKiBUaG9zZSBleGFtcGxlcyBpbiB0aGUgYm9vayBzaG93IHByb2JhYmlsaXRpZXMgZGVmaW5lZCBpbiBhIHR3by1kaW1lbnNpb25hbCBleHBsYW5hdG9yeSB2YXJpYWJsZSBzcGFjZSAoWDEgYW5kIFgyKS4gT3VyIGV4YW1wbGUgaXMgYSBvbmUtZGltZW5zaW9uYWwgc3BhY2UsIG9ubHkgYWxvbmcgdGhlIHgtYXhpcy4gV2Ugb25seSBuZWVkIGEgc2Vjb25kIGRpbWVuc2lvbiBpbiB0aGlzIHBpY3R1cmUgdG8gc2hvdyB0aGUgcHJvYmFiaWxpdGllcyAic2l0dGluZyBhYm92ZSIgZWFjaCB4IHZhbHVlLg0KDQpOb3cgdGhhdCB3ZSBoYXZlIG91ciBwcm9iYWJpbGl0eSBmdW5jdGlvbiwgbGV0J3Mgc2ltdWxhdGUgc29tZSBmYWtlIGRhdGEuIEhlcmUgYXJlIDEwMCByYW5kb20geCB2YWx1ZXM6DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTExMTEpDQpleHBsYW5hdG9yeSA8LSBydW5pZigxMDApDQpmYWtlX2RhdGEgPC0gdGliYmxlKGV4cGxhbmF0b3J5KQ0KZmFrZV9kYXRhDQpgYGANCg0KTm93IHdlIGNob29zZSBhIHJlc3BvbnNlIHZhcmlhYmxlLiBXZSdsbCBwaWNrIGNvbG9ycyBhdCByYW5kb20sIGJ1dCBjb25zdHJhaW5lZCBieSB0aGUgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCB4IHZhbHVlLiAoVGhlIEJlcm5vdWxsaSBkaXN0cmlidXRpb24gaXMgZWZmZWN0aXZlbHkgYSBjb2luLWZsaXBwaW5nIG1lY2hhbmlzbSwgYnV0IGluc3RlYWQgb2YgaGVhZHMgYW5kIHRhaWxzLCB3ZSBhcmUgZmxpcHBpbmcgYmx1ZXMgYW5kIHJlZHMgYWNjb3JkaW5nIHRvIHRoZSBwcm9iYWJpbGl0aWVzIGdpdmVuIGJ5IHRoZSBgcHJvYmAgZnVuY3Rpb24uKQ0KDQpgYGB7cn0NCnNldC5zZWVkKDIyMjIyKQ0KZmFrZV9kYXRhIDwtIGZha2VfZGF0YSAlPiUNCiAgICByb3d3aXNlKCkgJT4lDQogICAgbXV0YXRlKHJlc3BvbnNlID0NCiAgICAgICAgICAgICAgIGlmZWxzZShyYmVybm91bGxpKDEsIHAgPSBwcm9iKGV4cGxhbmF0b3J5KSksDQogICAgICAgICAgICAgICAgICAgICAgIkJsdWUiLCAiUmVkIikpDQpmYWtlX2RhdGENCmBgYA0KDQpMZXQncyBwbG90IHRoZSBzYW1wbGUgYmVsb3cgb3VyIHByZXZpb3VzIGdyYXBoOg0KDQpgYGB7cn0NCnByb2JfcGxvdF9yZXNwb25zZSA8LSBwcm9iX3Bsb3RfY29sb3IgKw0KICAgIGdlb21faml0dGVyKGRhdGEgPSBmYWtlX2RhdGEsDQogICAgICAgICAgICAgICAgYWVzKHggPSBleHBsYW5hdG9yeSwgeSA9IC0wLjIsDQogICAgICAgICAgICAgICAgICAgIGNvbG9yID0gcmVzcG9uc2UpLA0KICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwLjIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEpKSArDQogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJCbHVlIiwgIlJlZCIpKSArDQogICAgZ3VpZGVzKGNvbG9yID0gRkFMU0UpDQpwcm9iX3Bsb3RfcmVzcG9uc2UNCmBgYA0KDQpUaGVyZSdzIGEgbG90IGdvaW5nIG9uIGluIHRoaXMgcGljdHVyZSBiZWNhdXNlIHdlIGFyZSBzaG93aW5nIHRoZSB0cnVlIHByb2JhYmlsaXR5IGZ1bmN0aW9uIGFuZCB3ZSd2ZSB2ZXJ0aWNhbGx5IGppdHRlcmVkIHRoZSB4IHZhbHVlcyBzbyB0aGV5IGFyZSBhbGwgdmlzaWJsZS4gSW4gdGhlIHJlYWwgd29ybGQsIHRoaXMgaXMgYWxsIHdlIHNlZToNCg0KYGBge3J9DQp4X3Bsb3QgPC0gZ2dwbG90KGZha2VfZGF0YSwgYWVzKHggPSBleHBsYW5hdG9yeSwgeSA9IDApKSArDQogICAgZ2VvbV9zZWdtZW50KHggPSAwLCB4ZW5kID0gMSwgeSA9IDAsIHllbmQgPSAwLA0KICAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsNCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoIkJsdWUiLCAiUmVkIikpICsNCiAgICBndWlkZXMoY29sb3IgPSBGQUxTRSkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkNCnhfcGxvdF9yZXNwb25zZSA8LSB4X3Bsb3QgKyANCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHJlc3BvbnNlKSkNCnhfcGxvdF9yZXNwb25zZQ0KYGBgDQoNCg0KIyMgVGhlIEJheWVzIGNsYXNzaWZpZXINCg0KVGhlIEJheWVzIGNsYXNzaWZpZXIgb3BlcmF0ZXMgdXNpbmcgYSB2ZXJ5IHNpbXBsZSBydWxlOiBjbGFzc2lmeSBlYWNoIHBvaW50IGJhc2VkIG9uIHRoZSB0aGUgaGlnaGVzdCBwcm9iYWJpbGl0eSBnaXZlbiB0aGUgdmFsdWUgb2YgYW55IGV4cGxhbmF0b3J5IHZhcmlhYmxlcy4gSW4gc3ltYm9scywgd2UgY2FsY3VsYXRlIHRoZSBwcm9iYWJpbGl0eSBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUgdGFraW5nIG9uIGFsbCBwb3NzaWJsZSBjbGFzc2VzIGdpdmVuIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZToNCg0KJCRQcihSZXNwb25zZSA9IFx0ZXh0e0JsdWV9IFxtaWQgRXhwbGFuYXRvcnkgPSB4KSQkDQokJFByKFJlc3BvbnNlID0gXHRleHR7UmVkfSBcbWlkIEV4cGxhbmF0b3J5ID0geCkkJA0KDQpJbiBnZW5lcmFsLCB0aGVyZSBtYXkgYmUgbW9yZSB0aGFuIHR3byBjYXRlZ29yaWVzLiBFaXRoZXIgd2F5LCBqdXN0IGNob29zZSB0aGUgY2F0ZWdvcnkgZm9yIHdoaWNoIHRoZSBwcm9iYWJpbGl0eSBsaXN0ZWQgYWJvdmUgaXMgaGlnaGVzdC4NCg0KRm9yIGV4YW1wbGUsIGF0IHggPSAwLjEsIHRoZSBwcm9iYWJpbGl0eSBvZiBibHVlIGlzIDI5LjIlLCBhbmQgdGhlcmVmb3JlIGl0IGZvbGxvd3MgdGhhdCB0aGUgcHJvYmFiaWxpdHkgb2YgcmVkIGlzIDcwLjglLiBUaGVyZWZvcmUsIGEgcG9pbnQgd2l0aCB4ID0gMC4xIHNob3VsZCBiZSBjbGFzc2lmaWVkIGFzIHJlZC4gS2VlcCBpbiBtaW5kIHRoYXQgYW55IGFjdHVhbCBkYXRhIHBvaW50IGxvY2F0ZWQgYXQgeCA9IDAuMSBpcyBub3QgZm9yY2VkIHRvIGJlIHJlZC4gVGhlcmUncyBzdGlsbCBzb21lIHByb2JhYmlsaXR5IG9mIGl0IGJlaW5nIGJsdWUsIGJ1dCB0aGUgQmF5ZXMgY2xhc3NpZmllciB3aWxsIHByZWRpY3QgdGhhdCBpdCBpcyByZWQuDQoNClRoaXMgc291bmRzIGVhc3ksIGJ1dCBrZWVwIGluIG1pbmQsIHRoZSBCYXllcyBjbGFzc2lmaWVyIGlzIGEgdGhlb3JldGljYWwgY29uc3RydWN0IG9ubHk7IGluIHByYWN0aWNlLCB3ZSBuZXZlciBrbm93IHRoZSB0cnVlIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiB0aGF0IGRlc2NyaWJlcyB0aGUgcGF0dGVybiBvZiByZXNwb25zZXMuDQoNCkhvd2V2ZXIsIG91ciBkYXRhIGlzIGZha2UgZGF0YSB0aGF0IHdlIHNpbXVsYXRlZCwgc28gd2UgaGF2ZSB0aGUgdHJ1ZSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb25zLiBUaGVyZWZvcmUsIHdlIGNhbiBjbGFzc2lmeSBwb2ludHMgYWNjb3JkaW5nIHRvIHRoZSBCYXllcyBjbGFzc2lmaWVyLiBMZXQncyBjcmVhdGUgc29tZSBuZXcgY29sdW1ucyBpbiBvdXIgZGF0YSBmcmFtZSwgb25lIGZvciB0aGUgcHJlZGljdGVkIHZhbHVlIGFjY29yZGluZyB0byB0aGUgQmF5ZXMgY2xhc3NpZmllciwgYW5kIGFub3RoZXIgdG8gaW5kaWNhdGUgaWYgdGhlIGNsYXNzaWZpZXIgbWFkZSBhIGNvcnJlY3QgcHJlZGljdGlvbiAoY29tcGFyaW5nIGl0IHdpdGggdGhlIGFjdHVhbCByZXNwb25zZSB2YWx1ZSkuDQoNCmBgYHtyfQ0KZmFrZV9kYXRhIDwtIGZha2VfZGF0YSAlPiUNCiAgICBtdXRhdGUocHJlZGljdGlvbiA9IGlmZWxzZShwcm9iKGV4cGxhbmF0b3J5KSA+PSAwLjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJsdWUiLCAiUmVkIiksDQogICAgICAgICAgIGNvcnJlY3QgPSBpZmVsc2UocmVzcG9uc2UgPT0gcHJlZGljdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFLCBGQUxTRSkpDQpmYWtlX2RhdGENCmBgYA0KDQpIZXJlIGFyZSB0aGUgcG9pbnRzIGFzIGNsYXNzaWZpZWQgYnkgdGhlIEJheWVzIGNsYXNzaWZpZXI6DQoNCmBgYHtyfQ0KcHJvYl9wbG90X3ByZWQgPC0gcHJvYl9wbG90X2NvbG9yICsNCiAgICBnZW9tX2ppdHRlcihkYXRhID0gZmFrZV9kYXRhLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gZXhwbGFuYXRvcnksIHkgPSAtMC4yLA0KICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHByZWRpY3Rpb24pLA0KICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwLjIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEpKSArDQogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJCbHVlIiwgIlJlZCIpKSArDQogICAgZ3VpZGVzKGNvbG9yID0gRkFMU0UpDQpwcm9iX3Bsb3RfcHJlZA0KYGBgDQoNClRoaXMgc2hvd3Mgb25seSBvdXIgdHJhaW5pbmcgZGF0YSwgYnV0IGtlZXAgaW4gbWluZCB0aGF0IGFueSBwb2ludCBhbG9uZyB0aGUgeC1heGlzIGZyb20gMCB0byAxIGNhbiBiZSBjbGFzc2lmaWVkIGludG8gYmx1ZSBvciByZWQgdXNpbmcgdGhlIEJheWVzIGNsYXNzaWZpZXIuIFRoZSBkb3R0ZWQgdmVydGljYWwgbGluZXMgbWFyayB0aGUgKkJheWVzIGRlY2lzaW9uIGJvdW5kYXJ5Ki4gVGhpcyBib3VuZGFyeSBzZXBhcmF0ZXMgdGhlIHJlZ2lvbnMgd2hlcmUgdGhlIGNsYXNzaWZpZXIgbWFrZXMgYSBjb2xvciBwcmVkaWN0aW9uLiBBY3Jvc3MgdGhlc2UgYm91bmRhcnkgcG9pbnRzLCB0aGUgY2xhc3NpZmllciBjaGFuZ2VzIGl0cyBwcmVkaWN0aW9uIGZyb20gb25lIGNvbG9yIHRvIGFub3RoZXIuDQoNCioqQWdhaW4sIGJlIGNhcmVmdWwgbm90IHRvIGNvbmZ1c2UgdGhpcyBvbmUtZGltZW5zaW9uYWwgZXhhbXBsZSBmcm9tIHRoZSBib29rJ3MgdHdvLWRpbWVuc2lvbmFsIGV4YW1wbGUuKiogSW4gb25lLWRpbWVuc2lvbmFsIHRlcm1zLCB0aGlzIGlzIHdoYXQgdGhlIEJheWVzIGRlY2lzaW9uIGJvdW5kYXJ5IGxvb2tzIGxpa2U6DQoNCmBgYHtyfQ0KeF9wbG90X3ByZWQgPC0geF9wbG90ICsNCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHByZWRpY3Rpb24pKSArDQogICAgZ2VvbV9zZWdtZW50KHggPSAwLjI1LCB4ZW5kID0gMC4yNSwNCiAgICAgICAgICAgICAgICAgeSA9IC0wLjAwNSwgeWVuZCA9IDAuMDA1LA0KICAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsIHNpemUgPSAxKSArDQogICAgZ2VvbV9zZWdtZW50KHggPSAwLjUsIHhlbmQgPSAwLjUsDQogICAgICAgICAgICAgICAgIHkgPSAtMC4wMDUsIHllbmQgPSAwLjAwNSwNCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMSkgKw0KICAgIGdlb21fc2VnbWVudCh4ID0gMC43NSwgeGVuZCA9IDAuNzUsDQogICAgICAgICAgICAgICAgIHkgPSAtMC4wMDUsIHllbmQgPSAwLjAwNSwNCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMSkNCnhfcGxvdF9wcmVkDQpgYGANCg0KSGVyZSBhcmUgdGhlIEJheWVzIGRlY2lzaW9uIGJvdW5kYXJpZXMgYWdhaW4sIGJ1dCB0aGlzIHRpbWUgd2l0aCB0aGUgYWN0dWFsIHJlc3BvbnNlIHZhbHVlczoNCg0KYGBge3J9DQp4X3Bsb3RfcmVzcG9uc2UgKw0KICAgIGdlb21fc2VnbWVudCh4ID0gMC4yNSwgeGVuZCA9IDAuMjUsDQogICAgICAgICAgICAgICAgIHkgPSAtMC4wMDUsIHllbmQgPSAwLjAwNSwNCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMSkgKw0KICAgIGdlb21fc2VnbWVudCh4ID0gMC41LCB4ZW5kID0gMC41LA0KICAgICAgICAgICAgICAgICB5ID0gLTAuMDA1LCB5ZW5kID0gMC4wMDUsDQogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDEpICsNCiAgICBnZW9tX3NlZ21lbnQoeCA9IDAuNzUsIHhlbmQgPSAwLjc1LA0KICAgICAgICAgICAgICAgICB5ID0gLTAuMDA1LCB5ZW5kID0gMC4wMDUsDQogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDEpDQpgYGANCg0KSWYgd2UgY29tcGFyZSB0aGUgYWN0dWFsIHJlc3BvbnNlcyB0byB0aGUgQmF5ZXMgZGVjaXNpb24gYm91bmRhcnksIHdlIG5vdGUgdGhhdCBxdWl0ZSBhIGZldyBwb2ludHMgYXJlIG1pc2NsYXNzaWZpZWQgYnkgdGhlIEJheWVzIGNsYXNzaWZpZXIuDQoNClJldHVybmluZyB0byB0aGUgcGxvdCB3aXRoIHRoZSBwcm9iYWJpbGl0eSBmdW5jdGlvbiwgdGhlIHNxdWFyZXMgaW5kaWNhdGUgY29ycmVjdCBwcmVkaWN0aW9ucyBhbmQgdGhlIGNyb3NzZXMgaW5kaWNhdGUgaW5jb3JyZWN0IHByZWRpY3Rpb25zOg0KDQpgYGB7cn0NCnByb2JfcGxvdF9hbGwgPC0gcHJvYl9wbG90X3Jlc3BvbnNlICsNCiAgICBnZW9tX2ppdHRlcihkYXRhID0gZmFrZV9kYXRhLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gZXhwbGFuYXRvcnksIHkgPSAtMC4yLA0KICAgICAgICAgICAgICAgICAgICBzaGFwZSA9IGNvcnJlY3QpLA0KICAgICAgICAgICAgICAgIHNpemUgPSA0LA0KICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwLjIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEpKSArDQogICAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKDQsIDApKSArDQogICAgZ3VpZGVzKHNoYXBlID0gRkFMU0UpDQpwcm9iX3Bsb3RfYWxsDQpgYGANCg0KVGhlIEJheWVzIGVycm9yIHJhdGUgaXMgdGhlIHBlcmNlbnRhZ2Ugb2YgaW5jb3JyZWN0bHkgY2xhc3NpZmllZCByZXNwb25zZXMgdXNpbmcgdGhlIEJheWVzIGNsYXNzaWZpZXIuDQoNCmBgYHtyfQ0KMSAtIG1lYW4oZmFrZV9kYXRhJGNvcnJlY3QpDQpgYGANCg0KU28gaXQncyBhIHJlbGF0aXZlIGxhcmdlIDQxJSBoZXJlLiBUaGUgdGhlb3J5IGJlaGluZCB0aGUgQmF5ZXMgY2xhc3NpZmllciBzYXlzIHRoYXQgbm8gY2xhc3NpZmljYXRpb24gc3RyYXRlZ3kgd2lsbCBnaXZlIHlvdSBiZXR0ZXIgdGhhbiBhIDQxJSB0ZXN0IGVycm9yIHJhdGUuIEkgbWVhbiwgaXQncyBwb3NzaWJsZSBmb3IgYW55IGdpdmVuIGRhdGEgc2V0IGFuZCBhbnkgZ2l2ZW4gY2xhc3NpZmllciB0byBnZXQgbHVja3ksIGJ1dCBvdmVyYWxsLCB3ZSBjYW4ndCBleHBlY3QgKG9uIGF2ZXJhZ2UpIHRoYXQgYW55IG90aGVyIGNsYXNzaWZpZXIgd2lsbCBkbyBiZXR0ZXIgdGhhbiA1OSUgb24gbmV3IHRlc3QgZGF0YSBnZW5lcmF0ZWQgZnJvbSB0aGUgc2FtZSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24uDQoNCg0KIyMgVGhlIEJheWVzIGNsYXNzaWZpZXIgaW4gaGlnaGVyIGRpbWVuc2lvbnMNCg0KSW4gb3VyIG9uZS1kaW1lbnNpb25hbCBleGFtcGxlIGFib3ZlLCB0aGUgQmF5ZXMgZGVjaXNpb24gYm91bmRhcnkgd2FzIGEgc2V0IG9mIHRocmVlIHBvaW50cyBvbiB0aGUgeC1heGlzLiBUaGUgZm91ciByZWdpb25zIHRodXMgY3JlYXRlZCBhcmUgcmVnaW9ucyBvZiAiY29uc3RhbnQgcHJlZGljdGlvbiIsIG1lYW5pbmcgdGhhdCB0aGUgQmF5ZXMgY2xhc3NpZmllciBjbGFzc2lmaWVzIGV2ZXJ5dGhpbmcgd2l0aGluIGEgcmVnaW9uIHVzaW5nIGEgc2luZ2xlIGNvbG9yLiBUaGUgY2xhc3NpZmllciBvbmx5IGNoYW5nZXMgY29sb3JzIGFjcm9zcyBhIHBvaW50IG9mIHRoZSBCYXllcyBkZWNpc2lvbiBib3VuZGFyeS4NCg0KSW4gYSB0d28tZGltZW5zaW9uYWwgc3BhY2Ugb2YgZXhwbGFuYXRvcnkgdmFyaWFibGVzIChsaWtlIHRoZSBib29rIGV4YW1wbGVzKSwgdGhlIEJheWVzIGRlY2lzaW9uIGJvdW5kYXJ5IGRvZXMgbm90IGNvbnNpc3Qgb2YgcG9pbnRzIGFueW1vcmUuIEl0J3MgYSBjdXJ2ZSAob3IgbXVsdGlwbGUgY3VydmVzKSB0aGF0IHNlcGFyYXRlcyB0aGUgcGxhbmUgaW50byByZWdpb25zIG9mIGNvbnN0YW50IHByZWRpY3Rpb24uDQoNCkluIGEgdGhyZWUtZGltZW5zaW9uYWwgc3BhY2Ugb2YgZXhwbGFuYXRvcnkgdmFyaWFibGVzLCB0aGUgQmF5ZXMgZGVjaXNpb24gYm91bmRhcnkgaXMgYSBzdXJmYWNlIChvciBtdWx0aXBsZSBzdXJmYWNlcykgc2VwYXJhdGluZyBzcGFjZSBpbnRvIHJlZ2lvbnMgb2YgY29uc3RhbnQgcHJlZGljdGlvbi4NCg0KSW4gaGlnaGVyIGRpbWVuc2lvbnMsIGl0J3MgaGFyZGVyIHRvIHZpc3VhbGl6ZSB3aGF0J3MgZ29pbmcgb24uIEZvciBhbiBuLWRpbWVuc2lvbmFsIHNwYWNlIG9mIGV4cGxhbmF0b3J5IHZhcmlhYmxlcywgdGhlIEJheWVzIGRlY2lzaW9uIGJvdW5kYXJ5IGlzIGFuIChuLTEpLWRpbWVuc2lvbmFsIGh5cGVyc3VyZmFjZSENCg==