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:
- Vectorizing text with one-hot encoding
- Regularization with:
- Learning rate
- Model capacity
- Weight decay
- Dropout
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:
- start of a sequence
- unknown words
- 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:
- Increasing or decreasing number of units and layers
- Adjusting the learning rate
- Adjusting the batch size
- 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:
- When tuning the learning rate, we often try factors of \(10^{-s}\) where s ranges between 1-6 (0.1, 0.01, …, 0.000001).
- Add
callback_reduce_lr_on_plateau()
to automatically adjust the learning during training.
- As you reduce the learning rate, reduce the batch size
- Adds stochastic nature to reduce chance of getting stuck in local minimum
- Speeds up training (small learning rate + large batch size = SLOW!)
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:
- Although you can use L1, L2 or a combination, L2 is by far the most common and is known as weight decay in the context of neural nets.
- Optimal values vary but when tuning we typically start with factors of \(10^{-s}\) where s ranges between 1-4 (0.1, 0.01, …, 0.0001).
- The larger the weight regularizer, the more epochs generally required to reach a minimum loss
- Weight decay can cause a noisier learning curve so its often beneficial to increase the
patience
parameter for early stopping
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:
- Dropout rates typically ranges between 0.2-0.5. Sometimes higher rates are necessary but note that you will get a warning when supplying rate > 0.5.
- The higher the dropout rate, the slower the convergence so you may need to increase the number of epochs.
- Its common to apply dropout after each hidden layer and with the same rate; however, this is not necessary.
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
- Preparing text data
- Text data is usually stored as numeric data representing a word index
- We typically apply a word limit (i.e. top 10K, 20K, etc most frequent words)
- In this example we one-hot encoded the features into a 2D tensor but tomorrow we will look at better approaches
- When our model overfits regularizing can improve model performance
- Common approaches to regularization
- learning rate
- model capacity
- weight decay
- dropout
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=