In this case study, our objective is to classify movie reviews as positive or negative. This is a classic binary classification, which aims to predict one of two classes (positive vs. negative). To predict whether a review is positive or negative, we will use the text of the movie review. ℹ️

Throughout this case study you will learn a few new concepts:

Package requirements

library(keras)     # for deep learning
library(tidyverse) # for dplyr, ggplot2, etc.
library(testthat)  # unit testing
library(glue)      # easy print statements

The IMDB dataset

Our data consists of 50,000 movie reviews from IMDB. This data has been curated and supplied to us via keras; however, tomorrow we will go through the process of preprocessing the original data on our own. First, let’s grab our data and unpack them into training vs test and features vs labels.

imdb <- dataset_imdb(num_words = 10000)
c(c(reviews_train, y_train), c(reviews_test, y_test)) %<-% imdb

length(reviews_train)   # 25K reviews in our training data
[1] 25000
length(reviews_test)    # 25K reviews in our test data
[1] 25000

Understanding our data

The reviews have been preprocessed, and each review is encoded as a sequence of word indexes (integers). For convenience, words are indexed by overall frequency in the dataset. For example, the integer “14” encodes the 14th most frequent word in the data. Actually, since the numbers 1, 2, and 3 are reserved to identify:

  1. start of a sequence
  2. unknown words
  3. padding

the integer “14” represents the \(14 - 3 = 11\)th most frequent word.

reviews_train[[1]]
  [1]    1   14   22   16   43  530  973 1622 1385   65  458 4468   66 3941    4  173
 [17]   36  256    5   25  100   43  838  112   50  670    2    9   35  480  284    5
 [33]  150    4  172  112  167    2  336  385   39    4  172 4536 1111   17  546   38
 [49]   13  447    4  192   50   16    6  147 2025   19   14   22    4 1920 4613  469
 [65]    4   22   71   87   12   16   43  530   38   76   15   13 1247    4   22   17
 [81]  515   17   12   16  626   18    2    5   62  386   12    8  316    8  106    5
 [97]    4 2223 5244   16  480   66 3785   33    4  130   12   16   38  619    5   25
[113]  124   51   36  135   48   25 1415   33    6   22   12  215   28   77   52    5
[129]   14  407   16   82    2    8    4  107  117 5952   15  256    4    2    7 3766
[145]    5  723   36   71   43  530  476   26  400  317   46    7    4    2 1029   13
[161]  104   88    4  381   15  297   98   32 2071   56   26  141    6  194 7486   18
[177]    4  226   22   21  134  476   26  480    5  144   30 5535   18   51   36   28
[193]  224   92   25  104    4  226   65   16   38 1334   88   12   16  283    5   16
[209] 4472  113  103   32   15   16 5345   19  178   32

We can map the integer values back to the original word index (dataset_imdb_word_index()). The integer number corresponds to the position in the word count list and the name of the vector is the actual word.

word_index <- dataset_imdb_word_index() %>% 
  unlist() %>%                                 
  sort() %>%                                   
  names()                                      

# The indices are offset by 3 since 0, 1, and 2 are reserved for "padding", 
# "start of sequence", and "unknown"
reviews_train[[1]] %>% 
  map_chr(~ ifelse(.x >= 3, word_index[.x - 3], "<UNK>")) %>%
  cat()
<UNK> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all

Our response variable is just a vector of 1s (positive reviews) and 0s (negative reviews).

str(y_train)
 int [1:25000] 1 0 0 1 0 0 1 0 1 0 ...
# our labels are equally balanced between positive (1s) and negative (0s)
# reviews
table(y_train)
y_train
    0     1 
12500 12500 

Preparing the features

All inputs and response values in a neural network must be tensors of either floating-point or integer data. Moreover, our feature values should not be relatively large compared to the randomized initial weights and all our features should take values in roughly the same range.

Consequently, we need to vectorize our data into a format conducive to neural networks. For this data set, we’ll transform our list of article reviews to a 2D tensor of 0s and 1s representing if the word was used (aka one-hot encode). ℹ️

# number of unique words will be the number of features
n_features <- c(reviews_train, reviews_test) %>%  
  unlist() %>% 
  max()

# function to create 2D tensor (aka matrix)
vectorize_sequences <- function(sequences, dimension = n_features) {
  # Create a matrix of 0s
  results <- matrix(0, nrow = length(sequences), ncol = dimension)

  # Populate the matrix with 1s
  for (i in seq_along(sequences))
    results[i, sequences[[i]]] <- 1
  results
}

# apply to training and test data
x_train <- vectorize_sequences(reviews_train)
x_test <- vectorize_sequences(reviews_test)

# unit testing to make sure certain attributes hold
expect_equal(ncol(x_train), n_features)
expect_equal(nrow(x_train), length(reviews_train))
expect_equal(nrow(x_test), length(reviews_test))

Our transformed feature set is now just a matrix (2D tensor) with 25K rows and 10K columns (features).

dim(x_train)
[1] 25000  9999

Let’s check out the first 10 rows and columns:

x_train[1:10, 1:10]
      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
 [1,]    1    1    0    1    1    1    1    1    1     0
 [2,]    1    1    0    1    1    1    1    1    1     0
 [3,]    1    1    0    1    0    1    1    1    1     0
 [4,]    1    1    0    1    1    1    1    1    1     1
 [5,]    1    1    0    1    1    1    1    1    0     1
 [6,]    1    1    0    1    0    0    0    1    0     1
 [7,]    1    1    0    1    1    1    1    1    1     0
 [8,]    1    1    0    1    0    1    1    1    1     1
 [9,]    1    1    0    1    1    1    1    1    1     0
[10,]    1    1    0    1    1    1    1    1    1     1

Preparing the labels

In contrast to MNIST, the labels of a binary classification will just be one of two values, 0 (negative) or 1 (positive). We do not need to do any further preprocessing.

str(y_train)
 int [1:25000] 1 0 0 1 0 0 1 0 1 0 ...

Initial model

Since we are performing binary classification, our output activation function will be the sigmoid activation function ℹ️. Recall hat the sigmoid activation is used to predict the probability of the output being positive. This will constrain our output to be values ranging from 0-100%.

network <- keras_model_sequential() %>% 
  layer_dense(units = 16, activation = "relu", input_shape = n_features) %>% 
  layer_dense(units = 16, activation = "relu") %>% 
  layer_dense(units = 1, activation = "sigmoid")
summary(network)
Model: "sequential"
_________________________________________________________________________________________
Layer (type)                            Output Shape                       Param #       
=========================================================================================
dense (Dense)                           (None, 16)                         160000        
_________________________________________________________________________________________
dense_1 (Dense)                         (None, 16)                         272           
_________________________________________________________________________________________
dense_2 (Dense)                         (None, 1)                          17            
=========================================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________________________________

We’re going to use binary crossentropy since we only have two possible classes.

network %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = "accuracy"
)

Now let’s train our network for 20 epochs and we’ll use a batch size of 512 because, as you’ll find out, this model overfits very quickly (remember, large batch sizes compute more accurate gradient descents that traverse the loss more slowly).

history <- network %>% fit(
  x_train,
  y_train,
  epochs = 20,
  batch_size = 512,
  validation_split = 0.2
)

Check out our initial resuls:

best_epoch <- which.min(history$metrics$val_loss)
best_loss <- history$metrics$val_loss[best_epoch] %>% round(3)
best_acc <- history$metrics$val_accuracy[best_epoch] %>% round(3)

glue("Our optimal loss is {best_loss} with an accuracy of {best_acc*100}%")
Our optimal loss is 0.27 with an accuracy of 89.6%

In the previous module, we had the problem of underfitting; however looking at our learning curve for this model it’s obvious that we have an overfitting problem.

plot(history)

YOUR TURN (3 min)

Using what you learned in the last module, make modifications to this model such as:

  1. Increasing or decreasing number of units and layers
  2. Adjusting the learning rate
  3. Adjusting the batch size
  4. Adding callbacks (i.e. early stopping, learning rate adjuster)
network <- keras_model_sequential() %>% 
  layer_dense(units = ____, activation = "relu", input_shape = n_features) %>% 
  layer_dense(units = ____, activation = "relu") %>% 
  layer_dense(units = 1, activation = "sigmoid")

network %>% compile(
  optimizer = ____, 
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

history <- network %>% fit(
  x_train,
  y_train,
  epochs = 20,
  batch_size = ____,
  validation_split = 0.2
)

Regardless of what you tried above, you likely had results that consistently overfit. Our quest is to see if we can control this overfitting. Often, when we control the overfitting we improve model performance and generalizability. To reduce overfitting we are going to look at a few common ways to regularize our model.

Regularizing how quickly the model learns

Recall that the learning rate decides how fast we try to traverse the gradient descent of the loss. When the loss curve has a sharp U shape, this can indicate that your learning rate is too large.

The default learning rate for RMSprop is 0.001 (?optimizer_rmsprop()). Reducing the learning rate will allow us to traverse the gradient more cautiously. Although the learning rate is not traditionally considered a “regularization” hyperparameter, it should be the first hyperparameter you start assessing.

Best practice:

network <- keras_model_sequential() %>% 
  layer_dense(units = 16, activation = "relu", input_shape = n_features) %>% 
  layer_dense(units = 16, activation = "relu") %>% 
  layer_dense(units = 1, activation = "sigmoid")

network %>% compile(
  optimizer = optimizer_rmsprop(lr = 0.0001),        # regularization parameter
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

history <- network %>% fit(
  x_train,
  y_train,
  epochs = 25,
  batch_size = 128,
  validation_split = 0.2,
  callbacks = list(
    callback_reduce_lr_on_plateau(patience = 3),     # regularization parameter
    callback_early_stopping(patience = 7)
  )
)

Our results show decrease in overfitting and improvement in our loss score and (possibly) accuracy.

best_epoch <- which.min(history$metrics$val_loss)
best_loss <- history$metrics$val_loss[best_epoch] %>% round(3)
best_acc <- history$metrics$val_accuracy[best_epoch] %>% round(3)

glue("Our optimal loss is {best_loss} with an accuracy of {best_acc}")
Our optimal loss is 0.265 with an accuracy of 0.894
plot(history) + 
  scale_x_continuous(limits = c(0, length(history$metrics$val_loss)))

Regularizing model capacity

In the last module, we discussed how we could add model capacity by increasing the number of units in each hidden layer and/or the number of layers to reduce underfitting. We can also reduce these parameters to regularize model capacity.

In the last module, we changed model capacity manually. Here, we’ll use a custom function and a for loop to automate this process.

Variant 1: Larger or smaller layers?

Here, we’ll use a larger range of neurons (from \(2^2 = 4\) to \(2^8 = 256\)) in each hidden layer.

To do this, we’ll define a function dl_model that allows us to define and compile our DL network with the specified number of neurons based on \(2^n\). This function returns a data frame with the training and validation loss and accuracy for each epoch and number of neurons:

dl_model <- function(powerto = 6) {
  
  network <- keras_model_sequential() %>%
    layer_dense(units = 2^powerto, activation = "relu",     # regularizing param
                input_shape = n_features) %>% 
    layer_dense(units = 2^powerto, activation = "relu") %>% # regularizing param
    layer_dense(units = 1, activation = "sigmoid") 
  
  network %>% compile(
      optimizer = "rmsprop",
      loss = "binary_crossentropy",
      metrics = c("accuracy")
      )
  
  history <- network %>% 
    fit(
      x_train,
      y_train, 
      epochs = 20,
      batch_size = 512,
      validation_split = 0.2,
      verbose = FALSE,
      callbacks = callback_early_stopping(patience = 5)
    )
  
  output <- as.data.frame(history) %>%
    mutate(neurons = 2^powerto)
  
  return(output)
  }

Let’s also define a helper function that simply pulls out the minimum loss score from the above output (this is not necessary, just informational):

get_min_loss <- function(output) {
  output %>%
    filter(data == "validation", metric == "loss") %>%
    summarize(min_loss = min(value, na.rm = TRUE)) %>%
    pull(min_loss) %>%
    round(3)
}

Now we can iterate over \(2^2 = 4\) to \(2^8 = 256\) neurons in each layer:

# so that we can store results
results <- data.frame()
powerto_range <- 2:8

for (i in powerto_range) {
  cat("Running model with", 2^i, "neurons per hidden layer: ")
  m <- dl_model(i)
  results <- rbind(results, m)
  loss <- get_min_loss(m)
  cat(loss, "\n", append = TRUE)
}
Running model with 4 neurons per hidden layer: 0.271 
Running model with 8 neurons per hidden layer: 0.271 
Running model with 16 neurons per hidden layer: 0.282 
Running model with 32 neurons per hidden layer: 0.301 
Running model with 64 neurons per hidden layer: 0.268 
Running model with 128 neurons per hidden layer: 0.293 
Running model with 256 neurons per hidden layer: 0.277 

The above results indicate that we may actually be improving our optimal loss score as we constrain the size of our hidden layers. The below plot shows that we definitely reduce overfitting.

min_loss <- results %>%
  filter(metric == "loss" & data == "validation") %>%
  summarize(min_loss = min(value, na.rm = TRUE)) %>%
  pull()

results %>%
  filter(metric == "loss") %>%
  ggplot(aes(epoch, value, color = data)) +
  geom_line() +
  geom_hline(yintercept = min_loss, lty = "dashed") +
  facet_wrap(~ neurons) +
  theme_bw()

Variant 2: More or less layers?

We can perform a similar approach to assess the impact that the number of layers has on model performance. The following modifies our dl_model so that we can dynamically alter the number of layers and neurons.

dl_model <- function(nlayers = 2, powerto = 4) {
  
  # Create a model with a single hidden input layer
  network <- keras_model_sequential() %>%
    layer_dense(units = 2^powerto, activation = "relu", input_shape = n_features)
  
  # regularizing parameter --> Add additional hidden layers based on input
  if (nlayers > 1) {
    for (i in seq_along(nlayers - 1)) {
      network %>% layer_dense(units = 2^powerto, activation = "relu")
    }
  }
  
  # Add final output layer
  network %>% layer_dense(units = 1, activation = "sigmoid")
  
  # Add compile step
  network %>% compile(
      optimizer = "rmsprop",
      loss = "binary_crossentropy",
      metrics = c("accuracy")
      )
  
  # Train model
  history <- network %>% 
    fit(
      x_train,
      y_train, 
      epochs = 25,
      batch_size = 512,
      validation_split = 0.2,
      verbose = FALSE,
      callbacks = callback_early_stopping(patience = 5)
    )
  
  # Create formated output for downstream plotting & analysis
  output <- as.data.frame(history) %>%
    mutate(nlayers = nlayers, neurons = 2^powerto)
  
  return(output)
  }

Now we can iterate over a range of layers and neurons in each layer to assess the impact to performance. For time, we’ll use hidden layers with 64 nodes and just assess the impact of adding more layers:

# so that we can store results
results <- data.frame()
nlayers <- 1:6

for (i in nlayers) {
  cat("Running model with", i, "hidden layer(s) and 16 neurons per layer: ")
  m <- dl_model(nlayers = i, powerto = 4)
  results <- rbind(results, m)
  loss <- get_min_loss(m)
  cat(loss, "\n", append = TRUE)
}
Running model with 1 hidden layer(s) and 16 neurons per layer: 0.27 
Running model with 2 hidden layer(s) and 16 neurons per layer: 0.274 
Running model with 3 hidden layer(s) and 16 neurons per layer: 0.27 
Running model with 4 hidden layer(s) and 16 neurons per layer: 0.278 
Running model with 5 hidden layer(s) and 16 neurons per layer: 0.279 
Running model with 6 hidden layer(s) and 16 neurons per layer: 0.274 

It’s uncertain how much performance in the minimum loss score we get from the above results; however, the plot below illustrates that our 1-2 layer models have less overfitting than the deeper models.

min_loss <- results %>%
  filter(metric == "loss" & data == "validation") %>%
  summarize(min_loss = min(value, na.rm = TRUE)) %>%
  pull()

results %>%
  filter(metric == "loss") %>%
  ggplot(aes(epoch, value, color = data)) +
  geom_line() +
  geom_hline(yintercept = min_loss, lty = "dashed") +
  facet_wrap(~ nlayers, ncol = 3) +
  theme_bw()

Regularizing the size of weights

A common way to mitigate overfitting is to put constraints on the complexity of a network by forcing its weights to take on small values, which makes the distribution of weight values more regular. This is called weight regularization and its done by adding to the loss function of the network a cost associated with having large weights.

If you a familiar with regularized regression ℹ️ (lasso, ridge, elastic nets) then weight regularization is essentially the same thing. ℹ️

Best practice:

network <- keras_model_sequential() %>%
  layer_dense(
    units = 16, activation = "relu", input_shape = n_features,
    kernel_regularizer = regularizer_l2(l = 0.01)    # regularization parameter
    ) %>%
  layer_dense(
    units = 16, activation = "relu",
    kernel_regularizer = regularizer_l2(l = 0.01)    # regularization parameter
    ) %>%
  layer_dense(units = 1, activation = "sigmoid")

network %>% compile(
    optimizer = "rmsprop", 
    loss = loss_binary_crossentropy,
    metrics = c("accuracy")
)

history <- network %>% fit(
    x_train,
    y_train,
    epochs = 100,
    batch_size = 512,
    validation_split = 0.2,
    callbacks = callback_early_stopping(patience = 15)
)

Unfortunately, in this example, weight decay negatively impacts performance. The impact of weight decay is largely problem and data specific.

best_epoch <- which.min(history$metrics$val_loss)
best_loss <- history$metrics$val_loss[best_epoch] %>% round(3)
best_acc <- history$metrics$val_accuracy[best_epoch] %>% round(3)

glue("Our optimal loss is {best_loss} with an accuracy of {best_acc}")
Our optimal loss is 0.375 with an accuracy of 0.885
plot(history) + 
  scale_x_continuous(limits = c(0, length(history$metrics$val_loss)))

Regularizing happenstance patterns

Dropout is one of the most effective and commonly used regularization techniques for neural networks. Dropout applied to a layer randomly drops out (sets to zero) a certain percentage of the output features of that layer. By randomly dropping some of a layer’s outputs we minimize the chance of fitting patterns to noise in the data, a common cause of overfitting. ℹ️

Best practice:

network <- keras_model_sequential() %>%
  layer_dense(units = 16, activation = "relu", input_shape = n_features) %>%
  layer_dropout(0.6) %>%                            # regularization parameter
  layer_dense(units = 16, activation = "relu") %>%
  layer_dropout(0.6) %>%                            # regularization parameter
  layer_dense(units = 1, activation = "sigmoid")

network %>% compile(
    optimizer = "rmsprop", 
    loss = loss_binary_crossentropy,
    metrics = c("accuracy")
)

history <- network %>% fit(
    x_train,
    y_train,
    epochs = 100,
    batch_size = 512,
    validation_split = 0.2,
    callbacks = callback_early_stopping(patience = 10)
)

Similar to weight regularization, the impact of dropout is largely problem and data specific. In this example we do not see significant improvement.

best_epoch <- which.min(history$metrics$val_loss)
best_loss <- history$metrics$val_loss[best_epoch] %>% round(3)
best_acc <- history$metrics$val_accuracy[best_epoch] %>% round(3)

glue("Our optimal loss is {best_loss} with an accuracy of {best_acc}")
Our optimal loss is 0.274 with an accuracy of 0.896
plot(history) + 
  scale_x_continuous(limits = c(0, length(history$metrics$val_loss)))

So which is best?

There is no definitive best approach for minimizing overfitting. However, typically you want to focus first on finding the optimal learning rate and model capacity that optimizes the loss score. Then move on to fighting overfitting with dropout or weight decay.

Unfortunately, many of these hyperparameters interact so changing one can impact the performance of another. Performing a grid search can help you identify the optimal combination; however, as your data gets larger or as you start using more complex models such as CNNs and LSTMs, you often constrained by compute to adequately execute a sizable grid search. Here is a great paper on how to practically approach hyperparameter tuning for neural networks (https://arxiv.org/abs/1803.09820).

To see the performance of a grid search on this data set and the parameters discussed here, check out this notebook.

Key takeaways

LS0tCnRpdGxlOiAiQ2FzZSBTdHVkeSAyOiBJTURCIC0tIEJpbmFyeSBDbGFzc2lmaWNhdGlvbiBvZiBNb3ZpZSBSZXZpZXdzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSkKYGBgCgpJbiB0aGlzIGNhc2Ugc3R1ZHksIG91ciBvYmplY3RpdmUgaXMgdG8gY2xhc3NpZnkgbW92aWUgcmV2aWV3cyBhcyBwb3NpdGl2ZSBvcgpuZWdhdGl2ZS4gVGhpcyBpcyBhIGNsYXNzaWMgX2JpbmFyeSBjbGFzc2lmaWNhdGlvbl8sIHdoaWNoIGFpbXMgdG8gcHJlZGljdCBvbmUgCm9mIHR3byBjbGFzc2VzIChfcG9zaXRpdmUgdnMuIG5lZ2F0aXZlXykuIFRvIHByZWRpY3Qgd2hldGhlciBhIHJldmlldyBpcwpwb3NpdGl2ZSBvciBuZWdhdGl2ZSwgd2Ugd2lsbCB1c2UgdGhlIHRleHQgb2YgdGhlIG1vdmllIHJldmlldy4gW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMyMSkKClRocm91Z2hvdXQgdGhpcyBjYXNlIHN0dWR5IHlvdSB3aWxsIGxlYXJuIGEgZmV3IG5ldyBjb25jZXB0czoKCiogVmVjdG9yaXppbmcgdGV4dCB3aXRoIG9uZS1ob3QgZW5jb2RpbmcKKiBSZWd1bGFyaXphdGlvbiB3aXRoOgogICAtIExlYXJuaW5nIHJhdGUKICAgLSBNb2RlbCBjYXBhY2l0eQogICAtIFdlaWdodCBkZWNheQogICAtIERyb3BvdXQKCiMgUGFja2FnZSByZXF1aXJlbWVudHMKCmBgYHtyIGxvYWQtcGtnc30KbGlicmFyeShrZXJhcykgICAgICMgZm9yIGRlZXAgbGVhcm5pbmcKbGlicmFyeSh0aWR5dmVyc2UpICMgZm9yIGRwbHlyLCBnZ3Bsb3QyLCBldGMuCmxpYnJhcnkodGVzdHRoYXQpICAjIHVuaXQgdGVzdGluZwpsaWJyYXJ5KGdsdWUpICAgICAgIyBlYXN5IHByaW50IHN0YXRlbWVudHMKYGBgCgojIFRoZSBJTURCIGRhdGFzZXQKCk91ciBkYXRhIGNvbnNpc3RzIG9mIDUwLDAwMCBtb3ZpZSByZXZpZXdzIGZyb20gW0lNREJdKGh0dHBzOi8vd3d3LmltZGIuY29tLykuClRoaXMgZGF0YSBoYXMgYmVlbiBjdXJhdGVkIGFuZCBzdXBwbGllZCB0byB1cyB2aWEga2VyYXM7IGhvd2V2ZXIsIHRvbW9ycm93IHdlCndpbGwgZ28gdGhyb3VnaCB0aGUgcHJvY2VzcyBvZiBwcmVwcm9jZXNzaW5nIHRoZSBvcmlnaW5hbCBkYXRhIG9uIG91ciBvd24uIEZpcnN0LApsZXQncyBncmFiIG91ciBkYXRhIGFuZCB1bnBhY2sgdGhlbSBpbnRvIHRyYWluaW5nIHZzIHRlc3QgYW5kIGZlYXR1cmVzIHZzIGxhYmVscy4KCmBgYHtyIGdldC1kYXRhfQppbWRiIDwtIGRhdGFzZXRfaW1kYihudW1fd29yZHMgPSAxMDAwMCkKYyhjKHJldmlld3NfdHJhaW4sIHlfdHJhaW4pLCBjKHJldmlld3NfdGVzdCwgeV90ZXN0KSkgJTwtJSBpbWRiCgpsZW5ndGgocmV2aWV3c190cmFpbikgICAjIDI1SyByZXZpZXdzIGluIG91ciB0cmFpbmluZyBkYXRhCmxlbmd0aChyZXZpZXdzX3Rlc3QpICAgICMgMjVLIHJldmlld3MgaW4gb3VyIHRlc3QgZGF0YQpgYGAKCiMgVW5kZXJzdGFuZGluZyBvdXIgZGF0YQoKVGhlIHJldmlld3MgaGF2ZSBiZWVuIHByZXByb2Nlc3NlZCwgYW5kIGVhY2ggcmV2aWV3IGlzIGVuY29kZWQgYXMgYSBzZXF1ZW5jZSBvZiAKd29yZCBpbmRleGVzIChpbnRlZ2VycykuIEZvciBjb252ZW5pZW5jZSwgd29yZHMgYXJlIGluZGV4ZWQgYnkgb3ZlcmFsbCBmcmVxdWVuY3kgCmluIHRoZSBkYXRhc2V0LiBGb3IgZXhhbXBsZSwgdGhlIGludGVnZXIgIjE0IiBlbmNvZGVzIHRoZSAxNHRoIG1vc3QgZnJlcXVlbnQgCndvcmQgaW4gdGhlIGRhdGEuIEFjdHVhbGx5LCBzaW5jZSB0aGUgbnVtYmVycyAxLCAyLCBhbmQgMyBhcmUgcmVzZXJ2ZWQgdG8KaWRlbnRpZnk6CgoxLiBzdGFydCBvZiBhIHNlcXVlbmNlCjIuIHVua25vd24gd29yZHMKMy4gcGFkZGluZwoKdGhlIGludGVnZXIgIjE0IiByZXByZXNlbnRzIHRoZSAkMTQgLSAzID0gMTEkdGggbW9zdCBmcmVxdWVudCB3b3JkLgoKYGBge3IgZmlyc3QtcmV2aWV3fQpyZXZpZXdzX3RyYWluW1sxXV0KYGBgCgpXZSBjYW4gbWFwIHRoZSBpbnRlZ2VyIHZhbHVlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCB3b3JkIGluZGV4CihgZGF0YXNldF9pbWRiX3dvcmRfaW5kZXgoKWApLiBUaGUgaW50ZWdlciBudW1iZXIgY29ycmVzcG9uZHMgdG8gdGhlIHBvc2l0aW9uIGluCnRoZSB3b3JkIGNvdW50IGxpc3QgYW5kIHRoZSBuYW1lIG9mIHRoZSB2ZWN0b3IgaXMgdGhlIGFjdHVhbCB3b3JkLiAKCmBgYHtyIG1hcC1yZXZpZXctdG8td29yZHN9CndvcmRfaW5kZXggPC0gZGF0YXNldF9pbWRiX3dvcmRfaW5kZXgoKSAlPiUgCiAgdW5saXN0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgc29ydCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgbmFtZXMoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCgojIFRoZSBpbmRpY2VzIGFyZSBvZmZzZXQgYnkgMyBzaW5jZSAwLCAxLCBhbmQgMiBhcmUgcmVzZXJ2ZWQgZm9yICJwYWRkaW5nIiwgCiMgInN0YXJ0IG9mIHNlcXVlbmNlIiwgYW5kICJ1bmtub3duIgpyZXZpZXdzX3RyYWluW1sxXV0gJT4lIAogIG1hcF9jaHIofiBpZmVsc2UoLnggPj0gMywgd29yZF9pbmRleFsueCAtIDNdLCAiPFVOSz4iKSkgJT4lCiAgY2F0KCkKYGBgCgpPdXIgcmVzcG9uc2UgdmFyaWFibGUgaXMganVzdCBhIHZlY3RvciBvZiAxcyAocG9zaXRpdmUgcmV2aWV3cykgYW5kIDBzIChuZWdhdGl2ZQpyZXZpZXdzKS4KCmBgYHtyIGxhYmVsc30Kc3RyKHlfdHJhaW4pCgojIG91ciBsYWJlbHMgYXJlIGVxdWFsbHkgYmFsYW5jZWQgYmV0d2VlbiBwb3NpdGl2ZSAoMXMpIGFuZCBuZWdhdGl2ZSAoMHMpCiMgcmV2aWV3cwp0YWJsZSh5X3RyYWluKQpgYGAKCiMgUHJlcGFyaW5nIHRoZSBmZWF0dXJlcwoKQWxsIGlucHV0cyBhbmQgcmVzcG9uc2UgdmFsdWVzIGluIGEgbmV1cmFsIG5ldHdvcmsgbXVzdCBiZSB0ZW5zb3JzIG9mIGVpdGhlciAKZmxvYXRpbmctcG9pbnQgb3IgaW50ZWdlciBkYXRhLiBNb3Jlb3Zlciwgb3VyIGZlYXR1cmUgdmFsdWVzIHNob3VsZCBub3QgYmUKcmVsYXRpdmVseSBsYXJnZSBjb21wYXJlZCB0byB0aGUgcmFuZG9taXplZCBpbml0aWFsIHdlaWdodHMgX2FuZF8gYWxsIG91ciAKZmVhdHVyZXMgc2hvdWxkIHRha2UgdmFsdWVzIGluIHJvdWdobHkgdGhlIHNhbWUgcmFuZ2UuCgpDb25zZXF1ZW50bHksIHdlIG5lZWQgdG8gX3ZlY3Rvcml6ZV8gb3VyIGRhdGEgaW50byBhIGZvcm1hdCBjb25kdWNpdmUgdG8gbmV1cmFsIApuZXR3b3Jrcy4gRm9yIHRoaXMgZGF0YSBzZXQsIHdlJ2xsIHRyYW5zZm9ybSBvdXIgbGlzdCBvZiBhcnRpY2xlIHJldmlld3MgdG8gYQoyRCB0ZW5zb3Igb2YgMHMgYW5kIDFzIHJlcHJlc2VudGluZyBpZiB0aGUgd29yZCB3YXMgdXNlZCAoYWthIG9uZS1ob3QgZW5jb2RlKS4KW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMyMikKCmBgYHtyIHByZXAtZmVhdHVyZXN9CiMgbnVtYmVyIG9mIHVuaXF1ZSB3b3JkcyB3aWxsIGJlIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMKbl9mZWF0dXJlcyA8LSBjKHJldmlld3NfdHJhaW4sIHJldmlld3NfdGVzdCkgJT4lICAKICB1bmxpc3QoKSAlPiUgCiAgbWF4KCkKCiMgZnVuY3Rpb24gdG8gY3JlYXRlIDJEIHRlbnNvciAoYWthIG1hdHJpeCkKdmVjdG9yaXplX3NlcXVlbmNlcyA8LSBmdW5jdGlvbihzZXF1ZW5jZXMsIGRpbWVuc2lvbiA9IG5fZmVhdHVyZXMpIHsKICAjIENyZWF0ZSBhIG1hdHJpeCBvZiAwcwogIHJlc3VsdHMgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgoc2VxdWVuY2VzKSwgbmNvbCA9IGRpbWVuc2lvbikKCiAgIyBQb3B1bGF0ZSB0aGUgbWF0cml4IHdpdGggMXMKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHNlcXVlbmNlcykpCiAgICByZXN1bHRzW2ksIHNlcXVlbmNlc1tbaV1dXSA8LSAxCiAgcmVzdWx0cwp9CgojIGFwcGx5IHRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEKeF90cmFpbiA8LSB2ZWN0b3JpemVfc2VxdWVuY2VzKHJldmlld3NfdHJhaW4pCnhfdGVzdCA8LSB2ZWN0b3JpemVfc2VxdWVuY2VzKHJldmlld3NfdGVzdCkKCiMgdW5pdCB0ZXN0aW5nIHRvIG1ha2Ugc3VyZSBjZXJ0YWluIGF0dHJpYnV0ZXMgaG9sZApleHBlY3RfZXF1YWwobmNvbCh4X3RyYWluKSwgbl9mZWF0dXJlcykKZXhwZWN0X2VxdWFsKG5yb3coeF90cmFpbiksIGxlbmd0aChyZXZpZXdzX3RyYWluKSkKZXhwZWN0X2VxdWFsKG5yb3coeF90ZXN0KSwgbGVuZ3RoKHJldmlld3NfdGVzdCkpCmBgYAoKT3VyIHRyYW5zZm9ybWVkIGZlYXR1cmUgc2V0IGlzIG5vdyBqdXN0IGEgbWF0cml4ICgyRCB0ZW5zb3IpIHdpdGggMjVLIHJvd3MgYW5kCjEwSyBjb2x1bW5zIChmZWF0dXJlcykuCgpgYGB7cn0KZGltKHhfdHJhaW4pCmBgYAoKTGV0J3MgY2hlY2sgb3V0IHRoZSBmaXJzdCAxMCByb3dzIGFuZCBjb2x1bW5zOgoKYGBge3J9CnhfdHJhaW5bMToxMCwgMToxMF0KYGBgCgoKIyBQcmVwYXJpbmcgdGhlIGxhYmVscwoKSW4gY29udHJhc3QgdG8gTU5JU1QsIHRoZSBsYWJlbHMgb2YgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gd2lsbCBqdXN0IGJlIG9uZSBvZgp0d28gdmFsdWVzLCAwIChuZWdhdGl2ZSkgb3IgMSAocG9zaXRpdmUpLiBXZSBkbyBub3QgbmVlZCB0byBkbyBhbnkgZnVydGhlcgpwcmVwcm9jZXNzaW5nLgoKYGBge3IgcHJlcC1sYWJlbHN9CnN0cih5X3RyYWluKQpgYGAKCgojIEluaXRpYWwgbW9kZWwKClNpbmNlIHdlIGFyZSBwZXJmb3JtaW5nIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiwgb3VyIG91dHB1dCBhY3RpdmF0aW9uIGZ1bmN0aW9uIAp3aWxsIGJlIHRoZSBfc2lnbW9pZCBhY3RpdmF0aW9uIGZ1bmN0aW9uXyBb4oS577iPXShodHRwOi8vYml0Lmx5L2RsLTAxIzQ0KS4gUmVjYWxsIApoYXQgdGhlIHNpZ21vaWQgYWN0aXZhdGlvbiBpcyB1c2VkIHRvIHByZWRpY3QgdGhlIHByb2JhYmlsaXR5IG9mIHRoZSBvdXRwdXQKYmVpbmcgcG9zaXRpdmUuIFRoaXMgd2lsbCBjb25zdHJhaW4gb3VyIG91dHB1dCB0byBiZSB2YWx1ZXMgcmFuZ2luZyBmcm9tIDAtMTAwJS4KCmBgYHtyIGFyY2hpdGVjdHVyZX0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbl9mZWF0dXJlcykgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCmBgYAoKYGBge3Igc3VtbWFyeX0Kc3VtbWFyeShuZXR3b3JrKQpgYGAKCldlJ3JlIGdvaW5nIHRvIHVzZSBfYmluYXJ5IGNyb3NzZW50cm9weV8gc2luY2Ugd2Ugb25seSBoYXZlIHR3byBwb3NzaWJsZSBjbGFzc2VzLgoKYGBge3IgY29tcGlsZX0KbmV0d29yayAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgbG9zcyA9ICJiaW5hcnlfY3Jvc3NlbnRyb3B5IiwKICBtZXRyaWNzID0gImFjY3VyYWN5IgopCmBgYAoKTm93IGxldCdzIHRyYWluIG91ciBuZXR3b3JrIGZvciAyMCBlcG9jaHMgYW5kIHdlJ2xsIHVzZSBhIGJhdGNoIHNpemUgb2YgNTEyCmJlY2F1c2UsIGFzIHlvdSdsbCBmaW5kIG91dCwgdGhpcyBtb2RlbCBvdmVyZml0cyB2ZXJ5IHF1aWNrbHkgKHJlbWVtYmVyLCBsYXJnZQpiYXRjaCBzaXplcyBjb21wdXRlIG1vcmUgYWNjdXJhdGUgZ3JhZGllbnQgZGVzY2VudHMgdGhhdCB0cmF2ZXJzZSB0aGUgbG9zcyBtb3JlCnNsb3dseSkuCgpgYGB7ciB0cmFpbn0KaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGVwb2NocyA9IDIwLAogIGJhdGNoX3NpemUgPSA1MTIsCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMgopCmBgYAoKQ2hlY2sgb3V0IG91ciBpbml0aWFsIHJlc3VsczoKCmBgYHtyIGluaXRpYWwtcmVzdWx0c30KYmVzdF9lcG9jaCA8LSB3aGljaC5taW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKQpiZXN0X2xvc3MgPC0gaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzW2Jlc3RfZXBvY2hdICU+JSByb3VuZCgzKQpiZXN0X2FjYyA8LSBoaXN0b3J5JG1ldHJpY3MkdmFsX2FjY3VyYWN5W2Jlc3RfZXBvY2hdICU+JSByb3VuZCgzKQoKZ2x1ZSgiT3VyIG9wdGltYWwgbG9zcyBpcyB7YmVzdF9sb3NzfSB3aXRoIGFuIGFjY3VyYWN5IG9mIHtiZXN0X2FjYyoxMDB9JSIpCmBgYAoKSW4gdGhlIHByZXZpb3VzIG1vZHVsZSwgd2UgaGFkIHRoZSBwcm9ibGVtIG9mIHVuZGVyZml0dGluZzsgaG93ZXZlciBsb29raW5nIGF0Cm91ciBsZWFybmluZyBjdXJ2ZSBmb3IgdGhpcyBtb2RlbCBpdCdzIG9idmlvdXMgdGhhdCB3ZSBoYXZlIGFuIG92ZXJmaXR0aW5nCnByb2JsZW0uCgpgYGB7ciBpbml0aWFsLXJlc3VsdHMtcGxvdH0KcGxvdChoaXN0b3J5KQpgYGAKCiMjIFlPVVIgVFVSTiAoMyBtaW4pCgpVc2luZyB3aGF0IHlvdSBsZWFybmVkIGluIHRoZSBsYXN0IG1vZHVsZSwgbWFrZSBtb2RpZmljYXRpb25zIHRvIHRoaXMgbW9kZWwgc3VjaAphczoKCjEuIEluY3JlYXNpbmcgb3IgZGVjcmVhc2luZyBudW1iZXIgb2YgdW5pdHMgYW5kIGxheWVycwoyLiBBZGp1c3RpbmcgdGhlIGxlYXJuaW5nIHJhdGUKMy4gQWRqdXN0aW5nIHRoZSBiYXRjaCBzaXplCjQuIEFkZGluZyBjYWxsYmFja3MgKGkuZS4gZWFybHkgc3RvcHBpbmcsIGxlYXJuaW5nIHJhdGUgYWRqdXN0ZXIpCgpgYGB7ciB5b3VyLXR1cm4tMX0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXywgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuX2ZlYXR1cmVzKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSBfX19fLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLCBhY3RpdmF0aW9uID0gInNpZ21vaWQiKQoKbmV0d29yayAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSBfX19fLCAKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiLAogIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpCikKCmhpc3RvcnkgPC0gbmV0d29yayAlPiUgZml0KAogIHhfdHJhaW4sCiAgeV90cmFpbiwKICBlcG9jaHMgPSAyMCwKICBiYXRjaF9zaXplID0gX19fXywKICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4yCikKYGBgCgpSZWdhcmRsZXNzIG9mIHdoYXQgeW91IHRyaWVkIGFib3ZlLCB5b3UgbGlrZWx5IGhhZCByZXN1bHRzIHRoYXQgY29uc2lzdGVudGx5Cm92ZXJmaXQuIE91ciBxdWVzdCBpcyB0byBzZWUgaWYgd2UgY2FuIGNvbnRyb2wgdGhpcyBvdmVyZml0dGluZy4gT2Z0ZW4sIHdoZW4gd2UKY29udHJvbCB0aGUgb3ZlcmZpdHRpbmcgd2UgaW1wcm92ZSBtb2RlbCBwZXJmb3JtYW5jZSBhbmQgZ2VuZXJhbGl6YWJpbGl0eS4gVG8KcmVkdWNlIG92ZXJmaXR0aW5nIHdlIGFyZSBnb2luZyB0byBsb29rIGF0IGEgZmV3IGNvbW1vbiB3YXlzIHRvIF9fX3JlZ3VsYXJpemVfX18Kb3VyIG1vZGVsLgoKIyBSZWd1bGFyaXppbmcgaG93IHF1aWNrbHkgdGhlIG1vZGVsIGxlYXJucwoKUmVjYWxsIHRoYXQgdGhlIGxlYXJuaW5nIHJhdGUgZGVjaWRlcyBob3cgZmFzdCB3ZSB0cnkgdG8gdHJhdmVyc2UgdGhlIGdyYWRpZW50CmRlc2NlbnQgb2YgdGhlIGxvc3MuIFdoZW4gdGhlIGxvc3MgY3VydmUgaGFzIGEgc2hhcnAgVSBzaGFwZSwgdGhpcyBjYW4gaW5kaWNhdGUKdGhhdCB5b3VyIGxlYXJuaW5nIHJhdGUgaXMgdG9vIGxhcmdlLiAgCgpUaGUgZGVmYXVsdCBsZWFybmluZyByYXRlIGZvciBSTVNwcm9wIGlzIDAuMDAxIChgP29wdGltaXplcl9ybXNwcm9wKClgKS4gUmVkdWNpbmcKdGhlIGxlYXJuaW5nIHJhdGUgd2lsbCBhbGxvdyB1cyB0byB0cmF2ZXJzZSB0aGUgZ3JhZGllbnQgbW9yZSBjYXV0aW91c2x5LgpBbHRob3VnaCB0aGUgbGVhcm5pbmcgcmF0ZSBpcyBub3QgdHJhZGl0aW9uYWxseSBjb25zaWRlcmVkIGEgInJlZ3VsYXJpemF0aW9uIgpoeXBlcnBhcmFtZXRlciwgaXQgc2hvdWxkIGJlIHRoZSBmaXJzdCBoeXBlcnBhcmFtZXRlciB5b3Ugc3RhcnQgYXNzZXNzaW5nLgoKQmVzdCBwcmFjdGljZToKCi0gV2hlbiB0dW5pbmcgdGhlIGxlYXJuaW5nIHJhdGUsIHdlIG9mdGVuIHRyeSBmYWN0b3JzIG9mICQxMF57LXN9JCB3aGVyZSBzIHJhbmdlcwogIGJldHdlZW4gMS02ICgwLjEsIDAuMDEsIC4uLiwgMC4wMDAwMDEpLgotIEFkZCBgY2FsbGJhY2tfcmVkdWNlX2xyX29uX3BsYXRlYXUoKWAgdG8gYXV0b21hdGljYWxseSBhZGp1c3QgdGhlIGxlYXJuaW5nCiAgZHVyaW5nIHRyYWluaW5nLgotIEFzIHlvdSByZWR1Y2UgdGhlIGxlYXJuaW5nIHJhdGUsIHJlZHVjZSB0aGUgYmF0Y2ggc2l6ZQogICAtIEFkZHMgc3RvY2hhc3RpYyBuYXR1cmUgdG8gcmVkdWNlIGNoYW5jZSBvZiBnZXR0aW5nIHN0dWNrIGluIGxvY2FsIG1pbmltdW0KICAgLSBTcGVlZHMgdXAgdHJhaW5pbmcgKHNtYWxsIGxlYXJuaW5nIHJhdGUgKyBsYXJnZSBiYXRjaCBzaXplID0gU0xPVyEpCgpgYGB7ciByZWd1bGFyaXplLWxyfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuX2ZlYXR1cmVzKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikKCm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAwMDEpLCAgICAgICAgIyByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiLAogIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpCikKCmhpc3RvcnkgPC0gbmV0d29yayAlPiUgZml0KAogIHhfdHJhaW4sCiAgeV90cmFpbiwKICBlcG9jaHMgPSAyNSwKICBiYXRjaF9zaXplID0gMTI4LAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KHBhdGllbmNlID0gMyksICAgICAjIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlcgogICAgY2FsbGJhY2tfZWFybHlfc3RvcHBpbmcocGF0aWVuY2UgPSA3KQogICkKKQpgYGAKCk91ciByZXN1bHRzIHNob3cgZGVjcmVhc2UgaW4gb3ZlcmZpdHRpbmcgYW5kIGltcHJvdmVtZW50IGluIG91ciBsb3NzIHNjb3JlIGFuZAoocG9zc2libHkpIGFjY3VyYWN5LgoKYGBge3IgcmVndWxhcml6ZS1sci1yZXN1bHRzfQpiZXN0X2Vwb2NoIDwtIHdoaWNoLm1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpCmJlc3RfbG9zcyA8LSBoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3NbYmVzdF9lcG9jaF0gJT4lIHJvdW5kKDMpCmJlc3RfYWNjIDwtIGhpc3RvcnkkbWV0cmljcyR2YWxfYWNjdXJhY3lbYmVzdF9lcG9jaF0gJT4lIHJvdW5kKDMpCgpnbHVlKCJPdXIgb3B0aW1hbCBsb3NzIGlzIHtiZXN0X2xvc3N9IHdpdGggYW4gYWNjdXJhY3kgb2Yge2Jlc3RfYWNjfSIpCmBgYAoKYGBge3IgcmVndWxhcml6ZS1sci1yZXN1bHRzLXBsb3QsIG1lc3NhZ2U9RkFMU0V9CnBsb3QoaGlzdG9yeSkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCBsZW5ndGgoaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSkpCmBgYAoKIyBSZWd1bGFyaXppbmcgbW9kZWwgY2FwYWNpdHkKCkluIHRoZSBsYXN0IG1vZHVsZSwgd2UgZGlzY3Vzc2VkIGhvdyB3ZSBjb3VsZCBhZGQgbW9kZWwgY2FwYWNpdHkgYnkgaW5jcmVhc2luZwp0aGUgbnVtYmVyIG9mIHVuaXRzIGluIGVhY2ggaGlkZGVuIGxheWVyIGFuZC9vciB0aGUgbnVtYmVyIG9mIGxheWVycyB0byByZWR1Y2UKdW5kZXJmaXR0aW5nLiBXZSBjYW4gYWxzbyByZWR1Y2UgdGhlc2UgcGFyYW1ldGVycyB0byByZWd1bGFyaXplIG1vZGVsIGNhcGFjaXR5LgoKSW4gdGhlIGxhc3QgbW9kdWxlLCB3ZSBjaGFuZ2VkIG1vZGVsIGNhcGFjaXR5IG1hbnVhbGx5LiBIZXJlLCB3ZSdsbCB1c2UgYSAKY3VzdG9tIGZ1bmN0aW9uIGFuZCBhIGBmb3JgIGxvb3AgdG8gYXV0b21hdGUgdGhpcyBwcm9jZXNzLgoKIyMgVmFyaWFudCAxOiBMYXJnZXIgb3Igc21hbGxlciBsYXllcnM/CgpIZXJlLCB3ZSdsbCB1c2UgYSBsYXJnZXIgcmFuZ2Ugb2YgbmV1cm9ucyAoZnJvbSAkMl4yID0gNCQgdG8gJDJeOCA9IDI1NiQpIAppbiBlYWNoIGhpZGRlbiBsYXllci4KClRvIGRvIHRoaXMsIHdlJ2xsIGRlZmluZSBhIGZ1bmN0aW9uIGBkbF9tb2RlbGAgdGhhdCBhbGxvd3MgdXMgdG8gZGVmaW5lIAphbmQgY29tcGlsZSBvdXIgREwgbmV0d29yayB3aXRoIHRoZSBzcGVjaWZpZWQgbnVtYmVyIG9mIG5ldXJvbnMgYmFzZWQgb24gJDJebiQuIApUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBkYXRhIGZyYW1lIHdpdGggdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGxvc3MgYW5kIAphY2N1cmFjeSBmb3IgZWFjaCBlcG9jaCBhbmQgbnVtYmVyIG9mIG5ldXJvbnM6CgpgYGB7ciBwb3dlcnRvLWZ1bmN0aW9ufQpkbF9tb2RlbCA8LSBmdW5jdGlvbihwb3dlcnRvID0gNikgewogIAogIG5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogICAgbGF5ZXJfZGVuc2UodW5pdHMgPSAyXnBvd2VydG8sIGFjdGl2YXRpb24gPSAicmVsdSIsICAgICAjIHJlZ3VsYXJpemluZyBwYXJhbQogICAgICAgICAgICAgICAgaW5wdXRfc2hhcGUgPSBuX2ZlYXR1cmVzKSAlPiUgCiAgICBsYXllcl9kZW5zZSh1bml0cyA9IDJecG93ZXJ0bywgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lICMgcmVndWxhcml6aW5nIHBhcmFtCiAgICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpIAogIAogIG5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgICAgIG9wdGltaXplciA9ICJybXNwcm9wIiwKICAgICAgbG9zcyA9ICJiaW5hcnlfY3Jvc3NlbnRyb3B5IiwKICAgICAgbWV0cmljcyA9IGMoImFjY3VyYWN5IikKICAgICAgKQogIAogIGhpc3RvcnkgPC0gbmV0d29yayAlPiUgCiAgICBmaXQoCiAgICAgIHhfdHJhaW4sCiAgICAgIHlfdHJhaW4sIAogICAgICBlcG9jaHMgPSAyMCwKICAgICAgYmF0Y2hfc2l6ZSA9IDUxMiwKICAgICAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICAgICAgdmVyYm9zZSA9IEZBTFNFLAogICAgICBjYWxsYmFja3MgPSBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDUpCiAgICApCiAgCiAgb3V0cHV0IDwtIGFzLmRhdGEuZnJhbWUoaGlzdG9yeSkgJT4lCiAgICBtdXRhdGUobmV1cm9ucyA9IDJecG93ZXJ0bykKICAKICByZXR1cm4ob3V0cHV0KQogIH0KYGBgCgpMZXQncyBhbHNvIGRlZmluZSBhIGhlbHBlciBmdW5jdGlvbiB0aGF0IHNpbXBseSBwdWxscyBvdXQgdGhlIG1pbmltdW0gbG9zcyBzY29yZSAKZnJvbSB0aGUgYWJvdmUgb3V0cHV0ICh0aGlzIGlzIG5vdCBuZWNlc3NhcnksIGp1c3QgaW5mb3JtYXRpb25hbCk6CgpgYGB7ciBoZWxwZXItZnh9CmdldF9taW5fbG9zcyA8LSBmdW5jdGlvbihvdXRwdXQpIHsKICBvdXRwdXQgJT4lCiAgICBmaWx0ZXIoZGF0YSA9PSAidmFsaWRhdGlvbiIsIG1ldHJpYyA9PSAibG9zcyIpICU+JQogICAgc3VtbWFyaXplKG1pbl9sb3NzID0gbWluKHZhbHVlLCBuYS5ybSA9IFRSVUUpKSAlPiUKICAgIHB1bGwobWluX2xvc3MpICU+JQogICAgcm91bmQoMykKfQpgYGAKCk5vdyB3ZSBjYW4gaXRlcmF0ZSBvdmVyICQyXjIgPSA0JCB0byAkMl44ID0gMjU2JCBuZXVyb25zIGluIGVhY2ggbGF5ZXI6CgpgYGB7ciBpdGVyYXRlLW92ZXItbi1uZXVyb25zfQojIHNvIHRoYXQgd2UgY2FuIHN0b3JlIHJlc3VsdHMKcmVzdWx0cyA8LSBkYXRhLmZyYW1lKCkKcG93ZXJ0b19yYW5nZSA8LSAyOjgKCmZvciAoaSBpbiBwb3dlcnRvX3JhbmdlKSB7CiAgY2F0KCJSdW5uaW5nIG1vZGVsIHdpdGgiLCAyXmksICJuZXVyb25zIHBlciBoaWRkZW4gbGF5ZXI6ICIpCiAgbSA8LSBkbF9tb2RlbChpKQogIHJlc3VsdHMgPC0gcmJpbmQocmVzdWx0cywgbSkKICBsb3NzIDwtIGdldF9taW5fbG9zcyhtKQogIGNhdChsb3NzLCAiXG4iLCBhcHBlbmQgPSBUUlVFKQp9CmBgYAoKVGhlIGFib3ZlIHJlc3VsdHMgaW5kaWNhdGUgdGhhdCB3ZSBtYXkgYWN0dWFsbHkgYmUgaW1wcm92aW5nIG91ciBvcHRpbWFsIGxvc3MKc2NvcmUgYXMgd2UgY29uc3RyYWluIHRoZSBzaXplIG9mIG91ciBoaWRkZW4gbGF5ZXJzLiBUaGUgYmVsb3cgcGxvdCBzaG93cyB0aGF0CndlIGRlZmluaXRlbHkgcmVkdWNlIG92ZXJmaXR0aW5nLgoKYGBge3IgcGxvdC1yZXN1bHRzLCB3YXJuaW5nPUZBTFNFfQptaW5fbG9zcyA8LSByZXN1bHRzICU+JQogIGZpbHRlcihtZXRyaWMgPT0gImxvc3MiICYgZGF0YSA9PSAidmFsaWRhdGlvbiIpICU+JQogIHN1bW1hcml6ZShtaW5fbG9zcyA9IG1pbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgcHVsbCgpCgpyZXN1bHRzICU+JQogIGZpbHRlcihtZXRyaWMgPT0gImxvc3MiKSAlPiUKICBnZ3Bsb3QoYWVzKGVwb2NoLCB2YWx1ZSwgY29sb3IgPSBkYXRhKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBtaW5fbG9zcywgbHR5ID0gImRhc2hlZCIpICsKICBmYWNldF93cmFwKH4gbmV1cm9ucykgKwogIHRoZW1lX2J3KCkKYGBgCgoKIyMgVmFyaWFudCAyOiBNb3JlIG9yIGxlc3MgbGF5ZXJzPwoKV2UgY2FuIHBlcmZvcm0gYSBzaW1pbGFyIGFwcHJvYWNoIHRvIGFzc2VzcyB0aGUgaW1wYWN0IHRoYXQgdGhlIG51bWJlciBvZiBsYXllcnMgCmhhcyBvbiBtb2RlbCBwZXJmb3JtYW5jZS4gVGhlIGZvbGxvd2luZyBtb2RpZmllcyBvdXIgYGRsX21vZGVsYCBzbyB0aGF0IHdlIGNhbiAKZHluYW1pY2FsbHkgYWx0ZXIgdGhlIG51bWJlciBvZiBsYXllcnMgYW5kIG5ldXJvbnMuCgpgYGB7ciBubGF5ZXJzLWZ1bmN0aW9ufQpkbF9tb2RlbCA8LSBmdW5jdGlvbihubGF5ZXJzID0gMiwgcG93ZXJ0byA9IDQpIHsKICAKICAjIENyZWF0ZSBhIG1vZGVsIHdpdGggYSBzaW5nbGUgaGlkZGVuIGlucHV0IGxheWVyCiAgbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgICBsYXllcl9kZW5zZSh1bml0cyA9IDJecG93ZXJ0bywgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuX2ZlYXR1cmVzKQogIAogICMgcmVndWxhcml6aW5nIHBhcmFtZXRlciAtLT4gQWRkIGFkZGl0aW9uYWwgaGlkZGVuIGxheWVycyBiYXNlZCBvbiBpbnB1dAogIGlmIChubGF5ZXJzID4gMSkgewogICAgZm9yIChpIGluIHNlcV9hbG9uZyhubGF5ZXJzIC0gMSkpIHsKICAgICAgbmV0d29yayAlPiUgbGF5ZXJfZGVuc2UodW5pdHMgPSAyXnBvd2VydG8sIGFjdGl2YXRpb24gPSAicmVsdSIpCiAgICB9CiAgfQogIAogICMgQWRkIGZpbmFsIG91dHB1dCBsYXllcgogIG5ldHdvcmsgJT4lIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikKICAKICAjIEFkZCBjb21waWxlIHN0ZXAKICBuZXR3b3JrICU+JSBjb21waWxlKAogICAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgICAgIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIsCiAgICAgIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpCiAgICAgICkKICAKICAjIFRyYWluIG1vZGVsCiAgaGlzdG9yeSA8LSBuZXR3b3JrICU+JSAKICAgIGZpdCgKICAgICAgeF90cmFpbiwKICAgICAgeV90cmFpbiwgCiAgICAgIGVwb2NocyA9IDI1LAogICAgICBiYXRjaF9zaXplID0gNTEyLAogICAgICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4yLAogICAgICB2ZXJib3NlID0gRkFMU0UsCiAgICAgIGNhbGxiYWNrcyA9IGNhbGxiYWNrX2Vhcmx5X3N0b3BwaW5nKHBhdGllbmNlID0gNSkKICAgICkKICAKICAjIENyZWF0ZSBmb3JtYXRlZCBvdXRwdXQgZm9yIGRvd25zdHJlYW0gcGxvdHRpbmcgJiBhbmFseXNpcwogIG91dHB1dCA8LSBhcy5kYXRhLmZyYW1lKGhpc3RvcnkpICU+JQogICAgbXV0YXRlKG5sYXllcnMgPSBubGF5ZXJzLCBuZXVyb25zID0gMl5wb3dlcnRvKQogIAogIHJldHVybihvdXRwdXQpCiAgfQpgYGAKCk5vdyB3ZSBjYW4gaXRlcmF0ZSBvdmVyIGEgcmFuZ2Ugb2YgbGF5ZXJzIGFuZCBuZXVyb25zIGluIGVhY2ggbGF5ZXIgdG8gYXNzZXNzIAp0aGUgaW1wYWN0IHRvIHBlcmZvcm1hbmNlLiBGb3IgdGltZSwgd2UnbGwgdXNlIGhpZGRlbiBsYXllcnMgd2l0aCA2NCBub2RlcyBhbmQgCmp1c3QgYXNzZXNzIHRoZSBpbXBhY3Qgb2YgYWRkaW5nIG1vcmUgbGF5ZXJzOgoKYGBge3IgaXRlcmF0ZS1vdmVyLW4tbGF5ZXJzfQojIHNvIHRoYXQgd2UgY2FuIHN0b3JlIHJlc3VsdHMKcmVzdWx0cyA8LSBkYXRhLmZyYW1lKCkKbmxheWVycyA8LSAxOjYKCmZvciAoaSBpbiBubGF5ZXJzKSB7CiAgY2F0KCJSdW5uaW5nIG1vZGVsIHdpdGgiLCBpLCAiaGlkZGVuIGxheWVyKHMpIGFuZCAxNiBuZXVyb25zIHBlciBsYXllcjogIikKICBtIDwtIGRsX21vZGVsKG5sYXllcnMgPSBpLCBwb3dlcnRvID0gNCkKICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIG0pCiAgbG9zcyA8LSBnZXRfbWluX2xvc3MobSkKICBjYXQobG9zcywgIlxuIiwgYXBwZW5kID0gVFJVRSkKfQpgYGAKCkl0J3MgdW5jZXJ0YWluIGhvdyBtdWNoIHBlcmZvcm1hbmNlIGluIHRoZSBtaW5pbXVtIGxvc3Mgc2NvcmUgd2UgZ2V0IGZyb20gdGhlCmFib3ZlIHJlc3VsdHM7IGhvd2V2ZXIsIHRoZSBwbG90IGJlbG93IGlsbHVzdHJhdGVzIHRoYXQgb3VyIDEtMiBsYXllciBtb2RlbHMKaGF2ZSBsZXNzIG92ZXJmaXR0aW5nIHRoYW4gdGhlIGRlZXBlciBtb2RlbHMuCgpgYGB7ciBwbG90LXJlc3VsdHMyLCB3YXJuaW5nPUZBTFNFfQptaW5fbG9zcyA8LSByZXN1bHRzICU+JQogIGZpbHRlcihtZXRyaWMgPT0gImxvc3MiICYgZGF0YSA9PSAidmFsaWRhdGlvbiIpICU+JQogIHN1bW1hcml6ZShtaW5fbG9zcyA9IG1pbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgcHVsbCgpCgpyZXN1bHRzICU+JQogIGZpbHRlcihtZXRyaWMgPT0gImxvc3MiKSAlPiUKICBnZ3Bsb3QoYWVzKGVwb2NoLCB2YWx1ZSwgY29sb3IgPSBkYXRhKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBtaW5fbG9zcywgbHR5ID0gImRhc2hlZCIpICsKICBmYWNldF93cmFwKH4gbmxheWVycywgbmNvbCA9IDMpICsKICB0aGVtZV9idygpCmBgYAoKIyBSZWd1bGFyaXppbmcgdGhlIHNpemUgb2Ygd2VpZ2h0cwoKQSBjb21tb24gd2F5IHRvIG1pdGlnYXRlIG92ZXJmaXR0aW5nIGlzIHRvIHB1dCBjb25zdHJhaW50cyBvbiB0aGUgY29tcGxleGl0eSBvZgphIG5ldHdvcmsgYnkgZm9yY2luZyBpdHMgd2VpZ2h0cyB0byB0YWtlIG9uIHNtYWxsIHZhbHVlcywgd2hpY2ggbWFrZXMgdGhlCmRpc3RyaWJ1dGlvbiBvZiB3ZWlnaHQgdmFsdWVzIG1vcmUgcmVndWxhci4gVGhpcyBpcyBjYWxsZWQgX3dlaWdodCByZWd1bGFyaXphdGlvbl8KYW5kIGl0cyBkb25lIGJ5IGFkZGluZyB0byB0aGUgbG9zcyBmdW5jdGlvbiBvZiB0aGUgbmV0d29yayBhIGNvc3QgYXNzb2NpYXRlZAp3aXRoIGhhdmluZyBsYXJnZSB3ZWlnaHRzLgoKSWYgeW91IGEgZmFtaWxpYXIgd2l0aCByZWd1bGFyaXplZCByZWdyZXNzaW9uIFvihLnvuI9dKGh0dHA6Ly9iaXQubHkvaG9tbHItcmVndWxhcml6ZSkKKGxhc3NvLCByaWRnZSwgZWxhc3RpYyBuZXRzKSB0aGVuIHdlaWdodCByZWd1bGFyaXphdGlvbiBpcyBlc3NlbnRpYWxseSB0aGUgc2FtZQp0aGluZy4gW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMyMykKCkJlc3QgcHJhY3RpY2U6CgotIEFsdGhvdWdoIHlvdSBjYW4gdXNlIEwxLCBMMiBvciBhIGNvbWJpbmF0aW9uLCBMMiBpcyBieSBmYXIgdGhlIG1vc3QgY29tbW9uIGFuZAogIGlzIGtub3duIGFzIF93ZWlnaHQgZGVjYXlfIGluIHRoZSBjb250ZXh0IG9mIG5ldXJhbCBuZXRzLgotIE9wdGltYWwgdmFsdWVzIHZhcnkgYnV0IHdoZW4gdHVuaW5nIHdlIHR5cGljYWxseSBzdGFydCB3aXRoIGZhY3RvcnMgb2YgJDEwXnstc30kCiAgd2hlcmUgcyByYW5nZXMgYmV0d2VlbiAxLTQgKDAuMSwgMC4wMSwgLi4uLCAwLjAwMDEpLgotIFRoZSBsYXJnZXIgdGhlIHdlaWdodCByZWd1bGFyaXplciwgdGhlIG1vcmUgZXBvY2hzIGdlbmVyYWxseSByZXF1aXJlZCB0byByZWFjaAogIGEgbWluaW11bSBsb3NzCi0gV2VpZ2h0IGRlY2F5IGNhbiBjYXVzZSBhIG5vaXNpZXIgbGVhcm5pbmcgY3VydmUgc28gaXRzIG9mdGVuIGJlbmVmaWNpYWwgdG8KICBpbmNyZWFzZSB0aGUgYHBhdGllbmNlYCBwYXJhbWV0ZXIgZm9yIGVhcmx5IHN0b3BwaW5nCgpgYGB7cn0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UoCiAgICB1bml0cyA9IDE2LCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IG5fZmVhdHVyZXMsCiAgICBrZXJuZWxfcmVndWxhcml6ZXIgPSByZWd1bGFyaXplcl9sMihsID0gMC4wMSkgICAgIyByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIKICAgICkgJT4lCiAgbGF5ZXJfZGVuc2UoCiAgICB1bml0cyA9IDE2LCBhY3RpdmF0aW9uID0gInJlbHUiLAogICAga2VybmVsX3JlZ3VsYXJpemVyID0gcmVndWxhcml6ZXJfbDIobCA9IDAuMDEpICAgICMgcmVndWxhcml6YXRpb24gcGFyYW1ldGVyCiAgICApICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikKCm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsIAogICAgbG9zcyA9IGxvc3NfYmluYXJ5X2Nyb3NzZW50cm9weSwKICAgIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpCikKCmhpc3RvcnkgPC0gbmV0d29yayAlPiUgZml0KAogICAgeF90cmFpbiwKICAgIHlfdHJhaW4sCiAgICBlcG9jaHMgPSAxMDAsCiAgICBiYXRjaF9zaXplID0gNTEyLAogICAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICAgIGNhbGxiYWNrcyA9IGNhbGxiYWNrX2Vhcmx5X3N0b3BwaW5nKHBhdGllbmNlID0gMTUpCikKYGBgCgpVbmZvcnR1bmF0ZWx5LCBpbiB0aGlzIGV4YW1wbGUsIHdlaWdodCBkZWNheSBuZWdhdGl2ZWx5IGltcGFjdHMgcGVyZm9ybWFuY2UuIFRoZQppbXBhY3Qgb2Ygd2VpZ2h0IGRlY2F5IGlzIGxhcmdlbHkgcHJvYmxlbSBhbmQgZGF0YSBzcGVjaWZpYy4KCmBgYHtyIHJlZ3VsYXJpemUtd2VpZ2h0cy1yZXN1bHRzfQpiZXN0X2Vwb2NoIDwtIHdoaWNoLm1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpCmJlc3RfbG9zcyA8LSBoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3NbYmVzdF9lcG9jaF0gJT4lIHJvdW5kKDMpCmJlc3RfYWNjIDwtIGhpc3RvcnkkbWV0cmljcyR2YWxfYWNjdXJhY3lbYmVzdF9lcG9jaF0gJT4lIHJvdW5kKDMpCgpnbHVlKCJPdXIgb3B0aW1hbCBsb3NzIGlzIHtiZXN0X2xvc3N9IHdpdGggYW4gYWNjdXJhY3kgb2Yge2Jlc3RfYWNjfSIpCmBgYAoKYGBge3IgcmVndWxhcml6ZS13ZWlnaHRzLXJlc3VsdHMtcGxvdCwgbWVzc2FnZT1GQUxTRX0KcGxvdChoaXN0b3J5KSArIAogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGxlbmd0aChoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKSkKYGBgCgojIFJlZ3VsYXJpemluZyBoYXBwZW5zdGFuY2UgcGF0dGVybnMKCl9Ecm9wb3V0XyBpcyBvbmUgb2YgdGhlIG1vc3QgZWZmZWN0aXZlIGFuZCBjb21tb25seSB1c2VkIHJlZ3VsYXJpemF0aW9uCnRlY2huaXF1ZXMgZm9yIG5ldXJhbCBuZXR3b3Jrcy4gRHJvcG91dCBhcHBsaWVkIHRvIGEgbGF5ZXIgcmFuZG9tbHkgZHJvcHMgb3V0CihzZXRzIHRvIHplcm8pIGEgY2VydGFpbiBwZXJjZW50YWdlIG9mIHRoZSBvdXRwdXQgZmVhdHVyZXMgb2YgdGhhdCBsYXllci4gQnkKcmFuZG9tbHkgZHJvcHBpbmcgc29tZSBvZiBhIGxheWVyJ3Mgb3V0cHV0cyB3ZSBtaW5pbWl6ZSB0aGUgY2hhbmNlIG9mIGZpdHRpbmcKcGF0dGVybnMgdG8gbm9pc2UgaW4gdGhlIGRhdGEsIGEgY29tbW9uIGNhdXNlIG9mIG92ZXJmaXR0aW5nLiAKW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMyNSkKCkJlc3QgcHJhY3RpY2U6CgotIERyb3BvdXQgcmF0ZXMgdHlwaWNhbGx5IHJhbmdlcyBiZXR3ZWVuIDAuMi0wLjUuIFNvbWV0aW1lcyBoaWdoZXIgcmF0ZXMgYXJlCiAgbmVjZXNzYXJ5IGJ1dCBub3RlIHRoYXQgeW91IHdpbGwgZ2V0IGEgd2FybmluZyB3aGVuIHN1cHBseWluZyByYXRlID4gMC41LgotIFRoZSBoaWdoZXIgdGhlIGRyb3BvdXQgcmF0ZSwgdGhlIHNsb3dlciB0aGUgY29udmVyZ2VuY2Ugc28geW91IG1heSBuZWVkIHRvCiAgaW5jcmVhc2UgdGhlIG51bWJlciBvZiBlcG9jaHMuCi0gSXRzIGNvbW1vbiB0byBhcHBseSBkcm9wb3V0IGFmdGVyIGVhY2ggaGlkZGVuIGxheWVyIGFuZCB3aXRoIHRoZSBzYW1lIHJhdGU7CiAgaG93ZXZlciwgdGhpcyBpcyBub3QgbmVjZXNzYXJ5LgoKYGBge3J9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbl9mZWF0dXJlcykgJT4lCiAgbGF5ZXJfZHJvcG91dCgwLjYpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlcgogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2Ryb3BvdXQoMC42KSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCgpuZXR3b3JrICU+JSBjb21waWxlKAogICAgb3B0aW1pemVyID0gInJtc3Byb3AiLCAKICAgIGxvc3MgPSBsb3NzX2JpbmFyeV9jcm9zc2VudHJvcHksCiAgICBtZXRyaWNzID0gYygiYWNjdXJhY3kiKQopCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICAgIHhfdHJhaW4sCiAgICB5X3RyYWluLAogICAgZXBvY2hzID0gMTAwLAogICAgYmF0Y2hfc2l6ZSA9IDUxMiwKICAgIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgICBjYWxsYmFja3MgPSBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDEwKQopCmBgYAoKU2ltaWxhciB0byB3ZWlnaHQgcmVndWxhcml6YXRpb24sIHRoZSBpbXBhY3Qgb2YgZHJvcG91dCBpcyBsYXJnZWx5IHByb2JsZW0gYW5kIApkYXRhIHNwZWNpZmljLiBJbiB0aGlzIGV4YW1wbGUgd2UgZG8gbm90IHNlZSBzaWduaWZpY2FudCBpbXByb3ZlbWVudC4KCmBgYHtyIHJlZ3VsYXJpemUtZHJvcG91dC1yZXN1bHRzfQpiZXN0X2Vwb2NoIDwtIHdoaWNoLm1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpCmJlc3RfbG9zcyA8LSBoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3NbYmVzdF9lcG9jaF0gJT4lIHJvdW5kKDMpCmJlc3RfYWNjIDwtIGhpc3RvcnkkbWV0cmljcyR2YWxfYWNjdXJhY3lbYmVzdF9lcG9jaF0gJT4lIHJvdW5kKDMpCgpnbHVlKCJPdXIgb3B0aW1hbCBsb3NzIGlzIHtiZXN0X2xvc3N9IHdpdGggYW4gYWNjdXJhY3kgb2Yge2Jlc3RfYWNjfSIpCmBgYAoKYGBge3IgcmVndWxhcml6ZS1kcm9wb3V0LXJlc3VsdHMtcGxvdCwgbWVzc2FnZT1GQUxTRX0KcGxvdChoaXN0b3J5KSArIAogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGxlbmd0aChoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKSkKYGBgCgojIFNvIHdoaWNoIGlzIGJlc3Q/CgpUaGVyZSBpcyBubyBkZWZpbml0aXZlIGJlc3QgYXBwcm9hY2ggZm9yIG1pbmltaXppbmcgb3ZlcmZpdHRpbmcuIEhvd2V2ZXIsCnR5cGljYWxseSB5b3Ugd2FudCB0byBmb2N1cyBmaXJzdCBvbiBmaW5kaW5nIHRoZSBvcHRpbWFsIGxlYXJuaW5nIHJhdGUgYW5kCm1vZGVsIGNhcGFjaXR5IHRoYXQgb3B0aW1pemVzIHRoZSBsb3NzIHNjb3JlLiBUaGVuIG1vdmUgb24gdG8gZmlnaHRpbmcKb3ZlcmZpdHRpbmcgd2l0aCBkcm9wb3V0IG9yIHdlaWdodCBkZWNheS4KClVuZm9ydHVuYXRlbHksIG1hbnkgb2YgdGhlc2UgaHlwZXJwYXJhbWV0ZXJzIGludGVyYWN0IHNvIGNoYW5naW5nIG9uZSBjYW4gaW1wYWN0CnRoZSBwZXJmb3JtYW5jZSBvZiBhbm90aGVyLiBQZXJmb3JtaW5nIGEgZ3JpZCBzZWFyY2ggY2FuIGhlbHAgeW91IGlkZW50aWZ5IHRoZQpvcHRpbWFsIGNvbWJpbmF0aW9uOyBob3dldmVyLCBhcyB5b3VyIGRhdGEgZ2V0cyBsYXJnZXIgb3IgYXMgeW91IHN0YXJ0IHVzaW5nCm1vcmUgY29tcGxleCBtb2RlbHMgc3VjaCBhcyBDTk5zIGFuZCBMU1RNcywgeW91IG9mdGVuIGNvbnN0cmFpbmVkIGJ5IGNvbXB1dGUgdG8KYWRlcXVhdGVseSBleGVjdXRlIGEgc2l6YWJsZSBncmlkIHNlYXJjaC4gSGVyZSBpcyBhIGdyZWF0IHBhcGVyIG9uIGhvdyB0bwpwcmFjdGljYWxseSBhcHByb2FjaCBoeXBlcnBhcmFtZXRlciB0dW5pbmcgZm9yIG5ldXJhbCBuZXR3b3JrcwooaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE4MDMuMDk4MjApLgoKVG8gc2VlIHRoZSBwZXJmb3JtYW5jZSBvZiBhIGdyaWQgc2VhcmNoIG9uIHRoaXMgZGF0YSBzZXQgYW5kIHRoZSBwYXJhbWV0ZXJzCmRpc2N1c3NlZCBoZXJlLCBjaGVjayBvdXQgW3RoaXMgbm90ZWJvb2tdKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy9pbWRiLWdyaWQtc2VhcmNoLm5iLmh0bWwpLgoKIyBLZXkgdGFrZWF3YXlzCgoqIFByZXBhcmluZyB0ZXh0IGRhdGEKICAgLSBUZXh0IGRhdGEgaXMgdXN1YWxseSBzdG9yZWQgYXMgbnVtZXJpYyBkYXRhIHJlcHJlc2VudGluZyBhIHdvcmQgaW5kZXgKICAgLSBXZSB0eXBpY2FsbHkgYXBwbHkgYSB3b3JkIGxpbWl0IChpLmUuIHRvcCAxMEssIDIwSywgZXRjIG1vc3QgZnJlcXVlbnQgd29yZHMpCiAgIC0gSW4gdGhpcyBleGFtcGxlIHdlIG9uZS1ob3QgZW5jb2RlZCB0aGUgZmVhdHVyZXMgaW50byBhIDJEIHRlbnNvciBidXQKICAgICB0b21vcnJvdyB3ZSB3aWxsIGxvb2sgYXQgYmV0dGVyIGFwcHJvYWNoZXMKKiBXaGVuIG91ciBtb2RlbCBvdmVyZml0cyByZWd1bGFyaXppbmcgY2FuIGltcHJvdmUgbW9kZWwgcGVyZm9ybWFuY2UKKiBDb21tb24gYXBwcm9hY2hlcyB0byByZWd1bGFyaXphdGlvbgogICAtIGxlYXJuaW5nIHJhdGUKICAgLSBtb2RlbCBjYXBhY2l0eQogICAtIHdlaWdodCBkZWNheQogICAtIGRyb3BvdXQ=