Convolutional cats and dogs: An R version
Hello! Welcome to the sixth R code walkthrough of the session Machine Learning Foundations where the awesome Laurence Moroney,a Developer Advocate at Google working on Artificial Intelligence, takes us through the fundamentals of building machine learned models using TensorFlow.
In this episode, Episode 6, Laurence Moroney takes us through yet another exciting application of Machine Learning. Here, we take what we learned about Convolutional Neural Networks in the previous few episodes and apply them to a computer vision scenario that was a Kaggle challenge not long ago–building a classifier for cats and dogs!
Like the previous R Notebooks, this Notebook tries to replicate the Python Notebook used for this episode. The Python Notebook for this session is particularly exciting since it show you how to create and manipulate directories within google colab itself, so you definitely should check it out.
Before we begin, I highly recommend that you go through Episode 6 first then you can come back and implement these concepts using R. I will try and highlight some of the stuff Laurence Moroney said and add some of my own for the sake of completeness of this post but I highly recommend you listen from him first.
Let’s start by loading the libraries required for this session.
We’ll be requiring some packages in the EBImage, Tidyverse and Keras(a framework for defining a neural network as a set of Sequential layers). You can have them installed as follows:
For the Tidyverse, install the complete tidyverse with:
suppressMessages(install.packages("tidyverse"))
EBImage is an R package distributed as part of the Bioconductor project. To install the package, start R and enter:
install.packages("BiocManager")
BiocManager::install("EBImage")
The Keras R interface uses the TensorFlow backend engine by default. An elegant doucumentation for the installation of both the core Keras library as well as the TensorFlow backend can be found on the R interface to Keras website.
The Data set can be downloaded from here,Dogs vs. Cats dataset. It’s quite a large dataset and could take some time.
Copying images to training and test directories
Before we get into all things convolutions
, pooling
and image generators
, let’s first do some processing on the data. Unlike the previous horses-or-humans
dataset, you will notice that this one isn’t split into training and validation directories. It only has a PetImages
folder which contains Cat
and Dog
folders within it. This can be seen as below:
list.dirs(path = "C:/Users/keras/Documents/cats-and-dogs",
full.names = TRUE, recursive = TRUE)
## [1] "C:/Users/keras/Documents/cats-and-dogs"
## [2] "C:/Users/keras/Documents/cats-and-dogs/PetImages"
## [3] "C:/Users/keras/Documents/cats-and-dogs/PetImages/Cat"
## [4] "C:/Users/keras/Documents/cats-and-dogs/PetImages/Dog"
Let’s list how many files are in there:
PetImages <- list.dirs(path = "C:/Users/keras/Documents/cats-and-dogs/PetImages",
full.names = TRUE, recursive = FALSE)
sapply(PetImages, function(dir){length(list.files(dir))})
## C:/Users/keras/Documents/cats-and-dogs/PetImages/Cat
## 12501
## C:/Users/keras/Documents/cats-and-dogs/PetImages/Dog
## 12501
Perfect. Let’s now create our own directories:
A cats and dogs master directory which contains training and testing sub-directories, each of which will host the cats and dogs folders.
base_dir <- "C:/Users/keras/Documents/cats-v-dogs"
dir.create(base_dir)
train_dir <- file.path(base_dir, "training")
dir.create(train_dir)
test_dir <- file.path(base_dir, "testing")
dir.create(test_dir)
train_cats_dir <- file.path(train_dir, "cats")
dir.create(train_cats_dir)
train_dogs_dir <- file.path(train_dir, "dogs")
dir.create(train_dogs_dir)
test_cats_dir <- file.path(test_dir, "cats")
dir.create(test_cats_dir)
test_dogs_dir <- file.path(test_dir, "dogs")
dir.create(test_dogs_dir)
# defining our source directories i.e the folders that contains the cats and dogs images
cat_source_dir <- "C:/Users/keras/Documents/cats-and-dogs/PetImages/Cat"
dog_source_dir <- "C:/Users/keras/Documents/cats-and-dogs/PetImages/Dog"
Awesome. We have just split the data into Training
and Validation
directories, and each of these contain the cats
and dogs
sub-directories. We will then train a classifier on cats and dogs images using a generator which pulls images from the training sub-directories
. The classifier will then validate its accuracy using a generator which pulls images from the validation-subdirectories
. This can be shown briefly as:
It wouldn’t be that bad if we took a peep at some of these furry creatures(I couldn’t help it 🐾).
suppressPackageStartupMessages({
library(tidyverse)
library(EBImage)
library(knitr)
})
# listing the files in the cat_source_dir and dog_source_dir
cats_disp <- list.files(path = cat_source_dir,
pattern = ".jpg", full.names = T) %>%
sample(size = 4, replace = F)
dogs_disp <- list.files(path = dog_source_dir,
pattern = ".jpg", full.names = T) %>%
sample(size = 4, replace = F)
img_disp <- sample(c(cats_disp,dogs_disp))
# resizing the images since readImage {EBImage} requires all images
# to have same dimension and color mode
for (i in seq_along(img_disp)) {
readImage(img_disp[i]) %>%
resize(w = 300, h = 300) %>%
writeImage(img_disp[i])
}
EBImage::display(
readImage(img_disp),
method = 'raster',
all = T,
nx = 4,
spacing = c(0,0)
)

# maybe you are wondering if this will affect the performance of our NN
# worry not, later, we will use the image generator
# to resize all images to a common dimension.
Now, let’s write a function that drops corrupt images, randomizes the images a little bit, splits the images into training
and testing
sets and copies them into new folders that will be used by the image generators. Let’s get right into it, shall we?
split_data <- function(source_dir, training_dest, testing_dest, split_size){
# obtaining a character vector containing the image paths
files <- list.files(path = source_dir, full.names = T)
size <- file.size(files)
# removing any corrupt images based on the image size
shuffled_set <- cbind (files, size) %>%
subset(size > 0, select = c(files)) %>%
as.character() %>%
# randomizing our data a little
sample(replace = F)
# splitting our data such that _% goes to training and _% goes to testing
training_length <- length(shuffled_set) * split_size
testing_length <- length(shuffled_set) * (1 - split_size)
training_set <- shuffled_set[1:training_length]
testing_set <- shuffled_set[(training_length+1):length(shuffled_set)]
# copying the training and testing sets into their appropriate destination folders
# wrapping the function in: invisible {base} to prevent printing of 11,250 TRUE/FALSE :)
invisible (file.copy(from = training_set, to = training_dest))
invisible (file.copy(from = testing_set, to = testing_dest))
}
# splitting cat images
split_data(source_dir = cat_source_dir,
training_dest = train_cats_dir,
testing_dest = test_cats_dir,
split_size = 0.9)
# splitting dog images
split_data(source_dir = dog_source_dir,
training_dest = train_dogs_dir,
testing_dest = test_dogs_dir,
split_size = 0.9)
# sanity check
cat("Total training cat images:", length(list.files(train_cats_dir)), '\n')
## Total training cat images: 11250
cat("Total testing cat images:", length(list.files(test_cats_dir)), '\n')
## Total testing cat images: 1250
cat("Total training dog images:", length(list.files(train_dogs_dir)), '\n')
## Total training dog images: 11250
cat("Total testing dog images:", length(list.files(test_dogs_dir)), '\n')
## Total testing dog images: 1250
Building your network
Very quickly, from the previous sessions: A convolution
is a filter that passes over an image, processing it, and extracting features that show a commonolatity in the image such that if an image has certain features, it belongs to a particular class. Convolutional layers learn the features and pass these to the dense layers which map the learned features to the given labels.
Pooling
reduces the amount of irrelevant information in an image while maintaining the features that are detected.
Instantiating a Convolution
We’ll reuse the same general structure: the convnet will be a stack of alternated layer_conv_2d (with relu activation) and layer_max_pooling_2d stages. You will notice that as we go deeper, we increase the number of filters. This is because convolutions can learn spatial hierarchies of patterns. A first convolution layer will learn small local patterns such as edges, a second convolution layer will learn larger patterns made of the features of the first layers, and so on. This allows convnets to efficiently learn increasingly complex and abstract visual concepts
##
## Attaching package: 'keras'
## The following object is masked from 'package:EBImage':
##
## normalize
model <- keras_model_sequential() %>%
# adding the first convolution layer with 16 3by3 filters
# we add an additional dimension in the input shape since convolutions operate over 3D tensors
# the input shape tells the network that the first layer should expect
# images of 150 by 150 pixels with a color depth of 3 ie RGB images
layer_conv_2d(input_shape = c(150, 150, 3), filters = 16, kernel_size = c(3, 3), activation = 'relu') %>%
# adding a max pooling layer which halves the dimensions
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
# adding a second convolution layer with 32 filters
layer_conv_2d(filters = 32, kernel_size = c(3, 3), activation = 'relu') %>%
# adding a pooling layer
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
# increasing number of filters as image size decreases
layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2, 2))
Adding a classifier to the convnet
Convolutional layers learn the features and pass these to the dense layers which map the learned features to the given labels. Therefore, the next step is to feed the last output tensor into a densely connected classifier network like those we’re already familiar with: a stack of dense layers. These classifiers process vectors, which are 1D, however, the current output is a 3D tensor. First we have to flatten the 3D outputs to 1D, and then add a few dense layers on top.
Note that because we are facing a two-class classification problem, i.e. a binary classification problem, we will end our network with a sigmoid activation, so that the output of our network will be a single scalar between 0 and 1, encoding the probability that the current image is class 1 (as opposed to class 0). For more information about Keras activation functions, kindly visit the Keras website.
model <- model %>%
layer_flatten() %>%
layer_dense(units = 512, activation = 'relu') %>%
layer_dense(units = 1, activation ='sigmoid')
# Let’s look at how the dimensions of the feature maps change with every successive layer:
model %>% summary()
## Model: "sequential"
## ________________________________________________________________________________
## Layer (type) Output Shape Param #
## ================================================================================
## conv2d (Conv2D) (None, 148, 148, 16) 448
## ________________________________________________________________________________
## max_pooling2d (MaxPooling2D) (None, 74, 74, 16) 0
## ________________________________________________________________________________
## conv2d_1 (Conv2D) (None, 72, 72, 32) 4640
## ________________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D) (None, 36, 36, 32) 0
## ________________________________________________________________________________
## conv2d_2 (Conv2D) (None, 34, 34, 64) 18496
## ________________________________________________________________________________
## max_pooling2d_2 (MaxPooling2D) (None, 17, 17, 64) 0
## ________________________________________________________________________________
## flatten (Flatten) (None, 18496) 0
## ________________________________________________________________________________
## dense (Dense) (None, 512) 9470464
## ________________________________________________________________________________
## dense_1 (Dense) (None, 1) 513
## ================================================================================
## Total params: 9,494,561
## Trainable params: 9,494,561
## Non-trainable params: 0
## ________________________________________________________________________________
Sigmoid is equivalent to a 2-element Softmax, therefore, with a binary classification problem like this, you can get away with only 1 neuron and a sigmoid activation function which pushes values between 0 for one class and 1 for the other class.
Compile: Configuring a Keras model for training
model %>%
compile(
loss = 'binary_crossentropy',
optimizer = optimizer_rmsprop(lr = 0.001),
metrics = 'accuracy'
)
Binary_ Crossentropy loss Computes the cross-entropy loss between true labels and predicted labels. Typically used when there are only two label classes.(For a refresher on loss metrics, see the Machine Learning Crash Course and the Keras documentation)
Data preprocessing
Now that we have the data, we should format it into appropriately preprocessed floating-point tensors before being fed into the network. So the steps for getting it into the network are roughly as follows:
- Read the picture files.
- Decode the JPG content to RGB grids of pixels.
- Convert these into floating-point tensors.
- Normalize the pixel values to the [0, 1] interval (It is uncommon to feed raw pixels into a convnet).
- Autolabel the images of cats and dogs automatically based on the subdirectory name: - ImageGenerator will label the images appropriately for you, reducing a coding step. Sounds neat, right?
It may seem a bit daunting, but thankfully Keras has utilities to turn image files on disk into batches of pre-processed tensors. Such image processing tools include the function image_data_generator
.
# normalizing the data by multipling by a rescaling factor
train_datagen <- image_data_generator(rescale = 1/255)
# Flow training images in batches of 250 using train_datagen generator
train_generator <- flow_images_from_directory(
# target directory
directory = train_dir,
# training data generator
generator = train_datagen,
# resizing the images to the same dimensions expected by our NN
target_size = c(150, 150),
# 250 images at a time to be fed into the NN
batch_size = 250,
# Since we use binary_crossentropy loss, we need binary label arrays
class_mode = 'binary'
)
Seems we had a data base file in each of the cats and dogs training folders. Luckily that will not affect anything since they haven’t been recognized as images.
Maybe some few things to point out that could result into bugs:
- The
directory
is the parent directory folder that contains the labels sub-directories.
- For
class_mode
if you only have two classes keep it as binary
, if you have more than two classes, keep it categorical
.
Let’s do the same for the validation dataset
validation_datagen <- image_data_generator(rescale = 1/255)
validation_generator <- flow_images_from_directory(
# target directory
directory = test_dir,
# testing data generator
generator = validation_datagen,
# resizing the images to the same dimensions expected by our NN
target_size = c(150, 150),
# 250 images at a time to be fed into the NN
batch_size = 250,
# Since we use binary_crossentropy loss, we need binary label arrays
class_mode = 'binary'
)
Training the Neural Network
This is the process of training the neural network, where it ‘learns’ the relationship between the train_images and train_labels arrays.
Let’s fit the model to the data using the generator. You do so using the fit_generator {keras}
function, the equivalent for fit
for data generators like this one. It expects as its first argument a generator that will yield batches of inputs and targets indefinitely. Because the data is being generated endlessly, the model needs to know how many samples to draw from the generator before declaring an epoch over. This is the role of the steps_per_epoch
argument. It defines the total number of steps (batches of samples) to yield from generator before declaring one epoch finished and starting the next epoch. It should typically be equal to the number of samples in your dataset divided by the batch size.
validation_steps
describes the total number of steps (batches of samples) to yield from generator before stopping at the end of every epoch. It tells the network how many batches to draw from the validation generator for evaluation.
An epoch finishes when steps_per_epoch batches have been seen by the model.
Fitting the model using a batch generator
Let’s train for 15 epochs – this may take a few minutes to run.
The Loss and Accuracy are a great indication of progress of training. It’s making a guess as to the classification of the training data, and then measuring it against the known label, calculating the result. Accuracy is the portion of correct guesses.
history <- model %>% fit_generator(
generator = train_generator,
# Total number of steps (batches of samples) to yield
#before declaring one epoch finished and starting the next epoch.
steps_per_epoch = 90,
# An epoch is an iteration over the entire data provided
epochs = 15,
validation_data = validation_generator,
validation_steps = 5
)
# It’s good practice to always save your models after training.
model %>% save_model_hdf5("cats_and_dogs.h5")
# plotting the loss and accuracy over the training and validation data
# during training
plot(history)
## `geom_smooth()` using formula 'y ~ x'

# A summary of how the model performed
history
##
## Final epoch (plot to see history):
## loss: 0.1031
## accuracy: 0.97
## val_loss: 0.5494
## val_accuracy: 0.8048
These plots show a characteristic of overfitting: the fact that machine learning models tend to perform worse on new data than on their training data. It occurs when the network ends up learning representations that are specific to the training data and doesn’t generalize to data outside of the training set. This can be improved using image augmentation
right up in the next episode.
Generating predictions on new data/our own data
What’s the fun of building this NN if we can’t try it on our own pets, right?😸
We’ll download some images of cats and dogs and see how well our model classifies these images it has never seen before. From this exercise’s Python Notebook the image_load {keras}
and image_to_array {keras}
were used. These can easily be implemented in R too. For this post, I have opted for predict_generator {keras}
.
Implementing a data generator for the test images
fun_dir <- file.path(base_dir, "my_test_images")
dir.create(fun_dir)
# sub-folder in `my_test_images` that contains the images
my_cats_dogs_dir <- file.path(fun_dir, "my_images")
dir.create(my_cats_dogs_dir)
# copying downloaded images to `my_cats_dogs` directory
download_imgs <- list.files(path = "C:/Users/keras/Downloads",
pattern = ".jpg",
full.names = T )
invisible(file.copy(from = download_imgs, to = my_cats_dogs_dir))
test_datagen <- image_data_generator(rescale = 1/255)
test_generator <- flow_images_from_directory(
directory = fun_dir,
generator = test_datagen,
target_size = c(150, 150),
batch_size = 10,
class_mode = 'binary',
shuffle = F
)
Generating predictions for the test samples from a data generator.
predictions <- model %>% predict_generator(
steps = 1,
generator = test_generator,
verbose = 0
)
image_labels <- list.files(path = my_cats_dogs_dir)
pred_results <- as.data.frame(cbind(image_labels, predictions)) %>%
rename("prediction" = 2) %>%
mutate("predicted_class" = if_else(prediction>0.5,print("dog"),print("cat")),
prediction = as.double(prediction))
## [1] "dog"
## [1] "cat"
Below are the images I used:
my_images <- list.files(my_cats_dogs_dir, full.names = T)
# resizing the images to a common dimension as required by readImage{EBImage}
for (i in seq_along(my_images)) {
readImage(my_images[i]) %>%
resize(w = 2500, h = 2500) %>%
writeImage(my_images[i])
}
EBImage::display(
readImage(my_images),
method = 'raster',
all = T,
nx = 3,
spacing = c(0,0)
)
Predictions made by the model that trained for 15 epochs
Not bad! With some few tweaks here and there, the model can be optimized to perform better. Anyhow, we’ll leave it at that, for now.😊
Convnets work by learning a hierarchy of modular patterns and concepts to represent the visual world. As you go higher, the activations become increasingly abstract and less visually interpretable. They begin to encode higherlevel concepts such as “cat ear” and “cat eye.” Higher presentations carry increasingly less information about the visual contents of the image, and increasingly more information related to the class of the image.
Again, we have made it this far 🏆! Pretty, awesome. Convnets aren’t so ‘black-boxes’. You can go ahead and visualize Intermediate Representations to see how an input gets transformed as it goes through a convnet’s filters as we did in the previous episode.
That’s all for now. Happy Learning! 👩🏽💻 👨💻 👨🏾💻 👩💻
LS0tDQp0aXRsZTogJyAnDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBzdHlsZV8yLmNzcw0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGhpZ2hsaWdodDogYnJlZXplZGFyaw0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgaW5jbHVkZXM6DQogICAgICBhZnRlcl9ib2R5OiBmb290ZXIuaHRtbA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQotLS0NCg0KIyAqKkNvbnZvbHV0aW9uYWwgY2F0cyBhbmQgZG9nczoqKiBBbiBSIHZlcnNpb24NCg0KSGVsbG8hIFdlbGNvbWUgdG8gdGhlIHNpeHRoICoqUioqIGNvZGUgd2Fsa3Rocm91Z2ggb2YgdGhlIHNlc3Npb24gKioqTWFjaGluZSBMZWFybmluZyBGb3VuZGF0aW9ucyoqKiB3aGVyZSB0aGUgYXdlc29tZSBbTGF1cmVuY2UgTW9yb25leV0oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL2xhdXJlbmNlLW1vcm9uZXkpLGEgRGV2ZWxvcGVyIEFkdm9jYXRlIGF0IEdvb2dsZSB3b3JraW5nIG9uIEFydGlmaWNpYWwgSW50ZWxsaWdlbmNlLCB0YWtlcyB1cyB0aHJvdWdoIHRoZSBmdW5kYW1lbnRhbHMgb2YgYnVpbGRpbmcgbWFjaGluZSBsZWFybmVkIG1vZGVscyB1c2luZyBUZW5zb3JGbG93Lg0KDQpJbiB0aGlzIGVwaXNvZGUsIFtFcGlzb2RlIDZdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9bnE3X1pZSlBXZjApLCBMYXVyZW5jZSBNb3JvbmV5IHRha2VzIHVzIHRocm91Z2ggeWV0IGFub3RoZXIgZXhjaXRpbmcgYXBwbGljYXRpb24gb2YgTWFjaGluZSBMZWFybmluZy4NCkhlcmUsIHdlIHRha2Ugd2hhdCB3ZSBsZWFybmVkIGFib3V0IENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzIGluIHRoZSBwcmV2aW91cyBmZXcgZXBpc29kZXMgYW5kIGFwcGx5IHRoZW0gdG8gYSBjb21wdXRlciB2aXNpb24gc2NlbmFyaW8gdGhhdCB3YXMgYSBLYWdnbGUgY2hhbGxlbmdlIG5vdCBsb25nIGFnby0tYnVpbGRpbmcgYSBjbGFzc2lmaWVyIGZvciBjYXRzIGFuZCBkb2dzIQ0KDQpMaWtlIHRoZSBwcmV2aW91cyBbUiBOb3RlYm9va3NdKHJwdWJzLmVSX2ljKSwgdGhpcyBOb3RlYm9vayB0cmllcyB0byByZXBsaWNhdGUgdGhlIFtQeXRob24gTm90ZWJvb2tdKGh0dHBzOi8vY29sYWIucmVzZWFyY2guZ29vZ2xlLmNvbS9naXRodWIvbG1vcm9uZXkvbWxkYXktdG9reW8vYmxvYi9tYXN0ZXIvTGFiNi1DYXRzLXYtRG9ncy5pcHluYikgdXNlZCBmb3IgdGhpcyBlcGlzb2RlLiBUaGUgUHl0aG9uIE5vdGVib29rIGZvciB0aGlzIHNlc3Npb24gaXMgcGFydGljdWxhcmx5IGV4Y2l0aW5nIHNpbmNlIGl0IHNob3cgeW91IGhvdyB0byBjcmVhdGUgYW5kIG1hbmlwdWxhdGUgZGlyZWN0b3JpZXMgd2l0aGluIGdvb2dsZSBjb2xhYiBpdHNlbGYsIHNvIHlvdSBkZWZpbml0ZWx5IHNob3VsZCBjaGVjayBpdCBvdXQuDQoNCkJlZm9yZSB3ZSBiZWdpbiwgSSBoaWdobHkgcmVjb21tZW5kIHRoYXQgeW91IGdvIHRocm91Z2ggW0VwaXNvZGUgNl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1ucTdfWllKUFdmMCkgZmlyc3QgdGhlbiB5b3UgY2FuIGNvbWUgYmFjayBhbmQgaW1wbGVtZW50IHRoZXNlIGNvbmNlcHRzIHVzaW5nIFIuIEkgd2lsbCB0cnkgYW5kIGhpZ2hsaWdodCBzb21lIG9mIHRoZSBzdHVmZiBMYXVyZW5jZSBNb3JvbmV5IHNhaWQgYW5kIGFkZCBzb21lIG9mIG15IG93biBmb3IgdGhlIHNha2Ugb2YgY29tcGxldGVuZXNzIG9mIHRoaXMgcG9zdCBidXQgSSBoaWdobHkgcmVjb21tZW5kIHlvdSBsaXN0ZW4gZnJvbSBoaW0gZmlyc3QuDQoNCjxicj4NCg0KTGV0J3Mgc3RhcnQgYnkgbG9hZGluZyB0aGUgbGlicmFyaWVzIHJlcXVpcmVkIGZvciB0aGlzIHNlc3Npb24uDQoNCldlJ2xsIGJlIHJlcXVpcmluZyBzb21lIHBhY2thZ2VzIGluIHRoZSBFQkltYWdlLCBUaWR5dmVyc2UgYW5kIEtlcmFzKGEgZnJhbWV3b3JrIGZvciBkZWZpbmluZyBhIG5ldXJhbCBuZXR3b3JrIGFzIGEgc2V0IG9mIFNlcXVlbnRpYWwgbGF5ZXJzKS4gWW91IGNhbiBoYXZlIHRoZW0gaW5zdGFsbGVkIGFzIGZvbGxvd3M6DQoNCkZvciB0aGUgW1RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLCBpbnN0YWxsIHRoZSBjb21wbGV0ZSB0aWR5dmVyc2Ugd2l0aDoNCmBgYA0Kc3VwcHJlc3NNZXNzYWdlcyhpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKSkNCmBgYA0KDQo8YnI+DQoNCltFQkltYWdlXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvMy4xMS9iaW9jL2h0bWwvRUJJbWFnZS5odG1sKSBpcyBhbiBSIHBhY2thZ2UgZGlzdHJpYnV0ZWQgYXMgcGFydCBvZiB0aGUgW0Jpb2NvbmR1Y3Rvcl0oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvKSBwcm9qZWN0LiBUbyBpbnN0YWxsIHRoZSBwYWNrYWdlLCBzdGFydCBSIGFuZCBlbnRlcjoNCmBgYA0KaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KQmlvY01hbmFnZXI6Omluc3RhbGwoIkVCSW1hZ2UiKQ0KYGBgDQo8YnI+DQpUaGUgS2VyYXMgUiBpbnRlcmZhY2UgdXNlcyB0aGUgVGVuc29yRmxvdyBiYWNrZW5kIGVuZ2luZSBieSBkZWZhdWx0LiBBbiBlbGVnYW50IGRvdWN1bWVudGF0aW9uIGZvciB0aGUgaW5zdGFsbGF0aW9uIG9mIGJvdGggdGhlIGNvcmUgS2VyYXMgbGlicmFyeSBhcyB3ZWxsIGFzIHRoZSBUZW5zb3JGbG93IGJhY2tlbmQgY2FuIGJlIGZvdW5kIG9uIHRoZSBbUiBpbnRlcmZhY2UgdG8gS2VyYXNdKGh0dHBzOi8va2VyYXMucnN0dWRpby5jb20vcmVmZXJlbmNlL2luc3RhbGxfa2VyYXMuaHRtbCkgd2Vic2l0ZS4NCg0KDQoNCg0KVGhlIERhdGEgc2V0IGNhbiBiZSBkb3dubG9hZGVkIGZyb20gaGVyZSxbRG9ncyB2cy4gQ2F0cyBkYXRhc2V0XShodHRwczovL3d3dy5taWNyb3NvZnQuY29tL2VuLXVzL2Rvd25sb2FkL2NvbmZpcm1hdGlvbi5hc3B4P2lkPTU0NzY1KS4gSXQncyBxdWl0ZSBhIGxhcmdlIGRhdGFzZXQgYW5kIGNvdWxkIHRha2Ugc29tZSB0aW1lLg0KDQo8YnI+DQoNCiMgKipDb3B5aW5nIGltYWdlcyB0byB0cmFpbmluZyBhbmQgdGVzdCBkaXJlY3RvcmllcyoqDQoNCkJlZm9yZSB3ZSBnZXQgaW50byBhbGwgdGhpbmdzIGBjb252b2x1dGlvbnNgLCBgcG9vbGluZ2AgYW5kIGBpbWFnZSBnZW5lcmF0b3JzYCwgbGV0J3MgZmlyc3QgZG8gc29tZSBwcm9jZXNzaW5nIG9uIHRoZSBkYXRhLg0KVW5saWtlIHRoZSBwcmV2aW91cyBgaG9yc2VzLW9yLWh1bWFuc2AgZGF0YXNldCwgeW91IHdpbGwgbm90aWNlIHRoYXQgdGhpcyBvbmUgaXNuJ3Qgc3BsaXQgaW50byB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkaXJlY3Rvcmllcy4gSXQgb25seSBoYXMgYSBgUGV0SW1hZ2VzYCBmb2xkZXIgd2hpY2ggY29udGFpbnMgYENhdGAgYW5kIGBEb2dgIGZvbGRlcnMgd2l0aGluIGl0Lg0KVGhpcyBjYW4gYmUgc2VlbiBhcyBiZWxvdzoNCg0KYGBge3J9DQpsaXN0LmRpcnMocGF0aCA9ICJDOi9Vc2Vycy9rZXJhcy9Eb2N1bWVudHMvY2F0cy1hbmQtZG9ncyIsDQogICAgICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IFRSVUUpDQoNCmBgYA0KDQpMZXQncyBsaXN0IGhvdyBtYW55IGZpbGVzIGFyZSBpbiB0aGVyZToNCg0KYGBge3J9DQpQZXRJbWFnZXMgPC0gbGlzdC5kaXJzKHBhdGggPSAiQzovVXNlcnMva2VyYXMvRG9jdW1lbnRzL2NhdHMtYW5kLWRvZ3MvUGV0SW1hZ2VzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IEZBTFNFKQ0KDQpzYXBwbHkoUGV0SW1hZ2VzLCBmdW5jdGlvbihkaXIpe2xlbmd0aChsaXN0LmZpbGVzKGRpcikpfSkNCg0KYGBgDQpQZXJmZWN0LiBMZXQncyBub3cgY3JlYXRlIG91ciBvd24gZGlyZWN0b3JpZXM6DQoNCkEgY2F0cyBhbmQgZG9ncyBtYXN0ZXIgZGlyZWN0b3J5IHdoaWNoIGNvbnRhaW5zIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHN1Yi1kaXJlY3RvcmllcywgZWFjaCBvZiB3aGljaCB3aWxsIGhvc3QgdGhlIGNhdHMgYW5kIGRvZ3MgZm9sZGVycy4NCg0KYGBge3J9DQpiYXNlX2RpciA8LSAiQzovVXNlcnMva2VyYXMvRG9jdW1lbnRzL2NhdHMtdi1kb2dzIg0KZGlyLmNyZWF0ZShiYXNlX2RpcikNCnRyYWluX2RpciA8LSBmaWxlLnBhdGgoYmFzZV9kaXIsICJ0cmFpbmluZyIpDQpkaXIuY3JlYXRlKHRyYWluX2RpcikNCnRlc3RfZGlyIDwtIGZpbGUucGF0aChiYXNlX2RpciwgInRlc3RpbmciKQ0KZGlyLmNyZWF0ZSh0ZXN0X2RpcikNCnRyYWluX2NhdHNfZGlyIDwtIGZpbGUucGF0aCh0cmFpbl9kaXIsICJjYXRzIikNCmRpci5jcmVhdGUodHJhaW5fY2F0c19kaXIpDQp0cmFpbl9kb2dzX2RpciA8LSBmaWxlLnBhdGgodHJhaW5fZGlyLCAiZG9ncyIpDQpkaXIuY3JlYXRlKHRyYWluX2RvZ3NfZGlyKQ0KdGVzdF9jYXRzX2RpciA8LSBmaWxlLnBhdGgodGVzdF9kaXIsICJjYXRzIikNCmRpci5jcmVhdGUodGVzdF9jYXRzX2RpcikNCnRlc3RfZG9nc19kaXIgPC0gZmlsZS5wYXRoKHRlc3RfZGlyLCAiZG9ncyIpDQpkaXIuY3JlYXRlKHRlc3RfZG9nc19kaXIpDQoNCiMgZGVmaW5pbmcgb3VyIHNvdXJjZSBkaXJlY3RvcmllcyBpLmUgdGhlIGZvbGRlcnMgdGhhdCBjb250YWlucyB0aGUgY2F0cyBhbmQgZG9ncyBpbWFnZXMNCg0KY2F0X3NvdXJjZV9kaXIgPC0gIkM6L1VzZXJzL2tlcmFzL0RvY3VtZW50cy9jYXRzLWFuZC1kb2dzL1BldEltYWdlcy9DYXQiDQpkb2dfc291cmNlX2RpciA8LSAiQzovVXNlcnMva2VyYXMvRG9jdW1lbnRzL2NhdHMtYW5kLWRvZ3MvUGV0SW1hZ2VzL0RvZyINCg0KDQpgYGANCkF3ZXNvbWUuIFdlIGhhdmUganVzdCBzcGxpdCB0aGUgZGF0YSBpbnRvIGBUcmFpbmluZ2AgYW5kIGBWYWxpZGF0aW9uYCBkaXJlY3RvcmllcywgYW5kIGVhY2ggb2YgdGhlc2UgY29udGFpbiB0aGUgYGNhdHNgIGFuZCBgZG9nc2Agc3ViLWRpcmVjdG9yaWVzLg0KV2Ugd2lsbCB0aGVuIHRyYWluIGEgY2xhc3NpZmllciBvbiBjYXRzIGFuZCBkb2dzIGltYWdlcyB1c2luZyBhIGdlbmVyYXRvciB3aGljaCBwdWxscyBpbWFnZXMgZnJvbSB0aGUgYHRyYWluaW5nIHN1Yi1kaXJlY3Rvcmllc2AuIFRoZSBjbGFzc2lmaWVyIHdpbGwgdGhlbiB2YWxpZGF0ZSBpdHMgYWNjdXJhY3kgdXNpbmcgYSBnZW5lcmF0b3Igd2hpY2ggcHVsbHMgaW1hZ2VzIGZyb20gdGhlIGB2YWxpZGF0aW9uLXN1YmRpcmVjdG9yaWVzYC4gVGhpcyBjYW4gYmUgc2hvd24gYnJpZWZseSBhczoNCg0KYGBge3IsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0gIkltYWdlIHNvdXJjZTogTWFjaGluZSBMZWFybmluZyBGb3VuZGF0aW9uczogRXAgIzYiLCBmaWcud2lkdGg9MTF9DQpsaWJyYXJ5KGtuaXRyKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL2tlcmFzL0Rvd25sb2Fkcy90Zi5KUEciKQ0KDQpgYGANCg0KPGJyPg0KSXQgd291bGRuJ3QgYmUgdGhhdCBiYWQgaWYgd2UgdG9vayBhIHBlZXAgYXQgc29tZSBvZiB0aGVzZSBmdXJyeSBjcmVhdHVyZXMoSSBjb3VsZG4ndCBoZWxwIGl0IPCfkL4pLg0KDQpgYGB7ciwgZmlnLndpZHRoPTExfQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsNCiAgbGlicmFyeSh0aWR5dmVyc2UpDQogIGxpYnJhcnkoRUJJbWFnZSkNCiAgbGlicmFyeShrbml0cikNCn0pDQoNCg0KIyBsaXN0aW5nIHRoZSBmaWxlcyBpbiB0aGUgY2F0X3NvdXJjZV9kaXIgYW5kIGRvZ19zb3VyY2VfZGlyDQpjYXRzX2Rpc3AgPC0gbGlzdC5maWxlcyhwYXRoID0gY2F0X3NvdXJjZV9kaXIsDQogICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIi5qcGciLCBmdWxsLm5hbWVzID0gVCkgJT4lDQogICAgICAgICAgICAgIHNhbXBsZShzaXplID0gNCwgcmVwbGFjZSA9IEYpDQoNCmRvZ3NfZGlzcCA8LSBsaXN0LmZpbGVzKHBhdGggPSBkb2dfc291cmNlX2RpciwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiLmpwZyIsIGZ1bGwubmFtZXMgPSBUKSAlPiUNCiAgICAgICAgICAgICAgc2FtcGxlKHNpemUgPSA0LCByZXBsYWNlID0gRikNCmltZ19kaXNwIDwtIHNhbXBsZShjKGNhdHNfZGlzcCxkb2dzX2Rpc3ApKQ0KDQojIHJlc2l6aW5nIHRoZSBpbWFnZXMgc2luY2UgcmVhZEltYWdlIHtFQkltYWdlfSByZXF1aXJlcyBhbGwgaW1hZ2VzDQojIHRvIGhhdmUgc2FtZSBkaW1lbnNpb24gYW5kIGNvbG9yIG1vZGUNCg0KZm9yIChpIGluIHNlcV9hbG9uZyhpbWdfZGlzcCkpIHsNCiAgcmVhZEltYWdlKGltZ19kaXNwW2ldKSAlPiUNCiAgICByZXNpemUodyA9IDMwMCwgaCA9IDMwMCkgJT4lDQogICAgd3JpdGVJbWFnZShpbWdfZGlzcFtpXSkNCiAgDQp9DQoNCkVCSW1hZ2U6OmRpc3BsYXkoDQogIHJlYWRJbWFnZShpbWdfZGlzcCksDQogIG1ldGhvZCA9ICdyYXN0ZXInLA0KICBhbGwgPSBULA0KICBueCA9IDQsDQogIHNwYWNpbmcgPSBjKDAsMCkNCikNCg0KIyBtYXliZSB5b3UgYXJlIHdvbmRlcmluZyBpZiB0aGlzIHdpbGwgYWZmZWN0IHRoZSBwZXJmb3JtYW5jZSBvZiBvdXIgTk4NCiMgd29ycnkgbm90LCBsYXRlciwgd2Ugd2lsbCB1c2UgdGhlIGltYWdlIGdlbmVyYXRvcg0KIyB0byByZXNpemUgYWxsIGltYWdlcyB0byBhIGNvbW1vbiBkaW1lbnNpb24uDQpgYGANCg0KDQoNCg0KDQpOb3csIGxldCdzIHdyaXRlIGEgZnVuY3Rpb24gdGhhdCBkcm9wcyBjb3JydXB0IGltYWdlcywgcmFuZG9taXplcyB0aGUgaW1hZ2VzIGEgbGl0dGxlIGJpdCwgc3BsaXRzIHRoZSBpbWFnZXMgaW50byBgdHJhaW5pbmdgIGFuZCBgdGVzdGluZ2Agc2V0cyBhbmQgY29waWVzIHRoZW0gaW50byBuZXcgZm9sZGVycyB0aGF0IHdpbGwgYmUgdXNlZCBieSB0aGUgaW1hZ2UgZ2VuZXJhdG9ycy4NCkxldCdzIGdldCByaWdodCBpbnRvIGl0LCBzaGFsbCB3ZT8NCg0KDQoNCmBgYHtyfQ0KDQpzcGxpdF9kYXRhIDwtIGZ1bmN0aW9uKHNvdXJjZV9kaXIsIHRyYWluaW5nX2Rlc3QsIHRlc3RpbmdfZGVzdCwgc3BsaXRfc2l6ZSl7DQogIA0KIyBvYnRhaW5pbmcgYSBjaGFyYWN0ZXIgdmVjdG9yIGNvbnRhaW5pbmcgdGhlIGltYWdlIHBhdGhzDQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSBzb3VyY2VfZGlyLCBmdWxsLm5hbWVzID0gVCkNCnNpemUgPC0gZmlsZS5zaXplKGZpbGVzKQ0KDQojIHJlbW92aW5nIGFueSBjb3JydXB0IGltYWdlcyBiYXNlZCBvbiB0aGUgaW1hZ2Ugc2l6ZQ0Kc2h1ZmZsZWRfc2V0IDwtIGNiaW5kIChmaWxlcywgc2l6ZSkgJT4lDQogIHN1YnNldChzaXplID4gMCwgc2VsZWN0ID0gYyhmaWxlcykpICU+JQ0KICBhcy5jaGFyYWN0ZXIoKSAlPiUgDQogICMgcmFuZG9taXppbmcgb3VyIGRhdGEgYSBsaXR0bGUNCiAgc2FtcGxlKHJlcGxhY2UgPSBGKQ0KDQojIHNwbGl0dGluZyBvdXIgZGF0YSBzdWNoIHRoYXQgXyUgZ29lcyB0byB0cmFpbmluZyBhbmQgXyUgZ29lcyB0byB0ZXN0aW5nDQp0cmFpbmluZ19sZW5ndGggPC0gbGVuZ3RoKHNodWZmbGVkX3NldCkgKiBzcGxpdF9zaXplDQp0ZXN0aW5nX2xlbmd0aCA8LSBsZW5ndGgoc2h1ZmZsZWRfc2V0KSAqICgxIC0gc3BsaXRfc2l6ZSkNCg0KdHJhaW5pbmdfc2V0IDwtIHNodWZmbGVkX3NldFsxOnRyYWluaW5nX2xlbmd0aF0NCnRlc3Rpbmdfc2V0IDwtIHNodWZmbGVkX3NldFsodHJhaW5pbmdfbGVuZ3RoKzEpOmxlbmd0aChzaHVmZmxlZF9zZXQpXQ0KDQojIGNvcHlpbmcgdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMgaW50byB0aGVpciBhcHByb3ByaWF0ZSBkZXN0aW5hdGlvbiBmb2xkZXJzDQojIHdyYXBwaW5nIHRoZSBmdW5jdGlvbiBpbjogaW52aXNpYmxlIHtiYXNlfSB0byBwcmV2ZW50IHByaW50aW5nIG9mIDExLDI1MCBUUlVFL0ZBTFNFIDopDQoNCmludmlzaWJsZSAoZmlsZS5jb3B5KGZyb20gPSB0cmFpbmluZ19zZXQsIHRvID0gdHJhaW5pbmdfZGVzdCkpDQppbnZpc2libGUgKGZpbGUuY29weShmcm9tID0gdGVzdGluZ19zZXQsIHRvID0gdGVzdGluZ19kZXN0KSkNCg0KICANCn0NCg0KDQojIHNwbGl0dGluZyBjYXQgaW1hZ2VzDQpzcGxpdF9kYXRhKHNvdXJjZV9kaXIgPSBjYXRfc291cmNlX2RpciwNCiAgICAgICAgICAgdHJhaW5pbmdfZGVzdCA9IHRyYWluX2NhdHNfZGlyLA0KICAgICAgICAgICB0ZXN0aW5nX2Rlc3QgPSB0ZXN0X2NhdHNfZGlyLA0KICAgICAgICAgICBzcGxpdF9zaXplID0gMC45KQ0KDQoNCiMgc3BsaXR0aW5nIGRvZyBpbWFnZXMNCnNwbGl0X2RhdGEoc291cmNlX2RpciA9IGRvZ19zb3VyY2VfZGlyLA0KICAgICAgICAgICB0cmFpbmluZ19kZXN0ID0gdHJhaW5fZG9nc19kaXIsDQogICAgICAgICAgIHRlc3RpbmdfZGVzdCA9IHRlc3RfZG9nc19kaXIsDQogICAgICAgICAgIHNwbGl0X3NpemUgPSAwLjkpDQoNCg0KIyBzYW5pdHkgY2hlY2sNCmNhdCgiVG90YWwgdHJhaW5pbmcgY2F0IGltYWdlczoiLCBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9jYXRzX2RpcikpLCAnXG4nKQ0KY2F0KCJUb3RhbCB0ZXN0aW5nIGNhdCBpbWFnZXM6IiwgbGVuZ3RoKGxpc3QuZmlsZXModGVzdF9jYXRzX2RpcikpLCAnXG4nKQ0KDQpjYXQoIlRvdGFsIHRyYWluaW5nIGRvZyBpbWFnZXM6IiwgbGVuZ3RoKGxpc3QuZmlsZXModHJhaW5fZG9nc19kaXIpKSwgJ1xuJykNCmNhdCgiVG90YWwgdGVzdGluZyBkb2cgaW1hZ2VzOiIsIGxlbmd0aChsaXN0LmZpbGVzKHRlc3RfZG9nc19kaXIpKSwgJ1xuJykNCg0KIyBWb2lsYSENCmBgYA0KPGJyPg0KDQojICoqQnVpbGRpbmcgeW91ciBuZXR3b3JrKioNCg0KVmVyeSBxdWlja2x5LCBmcm9tIHRoZSBwcmV2aW91cyBzZXNzaW9uczoNCkEgYGNvbnZvbHV0aW9uYCBpcyBhIGZpbHRlciB0aGF0IHBhc3NlcyBvdmVyIGFuIGltYWdlLCBwcm9jZXNzaW5nIGl0LCBhbmQgZXh0cmFjdGluZyBmZWF0dXJlcyB0aGF0IHNob3cgYSBjb21tb25vbGF0aXR5IGluIHRoZSBpbWFnZSBzdWNoIHRoYXQgaWYgYW4gaW1hZ2UgaGFzIGNlcnRhaW4gZmVhdHVyZXMsIGl0IGJlbG9uZ3MgdG8gYSBwYXJ0aWN1bGFyIGNsYXNzLiBDb252b2x1dGlvbmFsIGxheWVycyBsZWFybiB0aGUgZmVhdHVyZXMgYW5kIHBhc3MgdGhlc2UgdG8gdGhlIGRlbnNlIGxheWVycyB3aGljaCBtYXAgdGhlIGxlYXJuZWQgZmVhdHVyZXMgdG8gdGhlIGdpdmVuIGxhYmVscy4NCg0KPGJyPg0KYFBvb2xpbmdgIHJlZHVjZXMgdGhlIGFtb3VudCBvZiBpcnJlbGV2YW50IGluZm9ybWF0aW9uIGluIGFuIGltYWdlIHdoaWxlIG1haW50YWluaW5nIHRoZSBmZWF0dXJlcyB0aGF0IGFyZSBkZXRlY3RlZC4NCg0KDQoNCg0KIyMjICoqSW5zdGFudGlhdGluZyBhIENvbnZvbHV0aW9uKioNCg0KV2XigJlsbCByZXVzZSB0aGUgc2FtZSBnZW5lcmFsIHN0cnVjdHVyZTogdGhlIGNvbnZuZXQgd2lsbCBiZSBhIHN0YWNrDQpvZiBhbHRlcm5hdGVkIGxheWVyX2NvbnZfMmQgKHdpdGggcmVsdSBhY3RpdmF0aW9uKSBhbmQgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQNCnN0YWdlcy4NCllvdSB3aWxsIG5vdGljZSB0aGF0IGFzIHdlIGdvIGRlZXBlciwgd2UgaW5jcmVhc2UgdGhlIG51bWJlciBvZiBmaWx0ZXJzLiBUaGlzIGlzIGJlY2F1c2UgY29udm9sdXRpb25zIGNhbiBsZWFybiBzcGF0aWFsIGhpZXJhcmNoaWVzIG9mIHBhdHRlcm5zLiBBIGZpcnN0IGNvbnZvbHV0aW9uIGxheWVyIHdpbGwgbGVhcm4gc21hbGwgbG9jYWwgcGF0dGVybnMgc3VjaCBhcyBlZGdlcywgYSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXIgd2lsbCBsZWFybiBsYXJnZXIgcGF0dGVybnMgbWFkZSBvZiB0aGUgZmVhdHVyZXMgb2YgdGhlIGZpcnN0IGxheWVycywgYW5kIHNvIG9uLiBUaGlzIGFsbG93cyBjb252bmV0cyB0byBlZmZpY2llbnRseSBsZWFybiBpbmNyZWFzaW5nbHkgY29tcGxleCBhbmQgYWJzdHJhY3QgdmlzdWFsIGNvbmNlcHRzDQoNCmBgYHtyfQ0KbGlicmFyeShrZXJhcykNCg0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQ0KICAjIGFkZGluZyB0aGUgZmlyc3QgY29udm9sdXRpb24gbGF5ZXIgd2l0aCAxNiAzYnkzIGZpbHRlcnMNCiAgIyB3ZSBhZGQgYW4gYWRkaXRpb25hbCBkaW1lbnNpb24gaW4gdGhlIGlucHV0IHNoYXBlIHNpbmNlIGNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycw0KICAjIHRoZSBpbnB1dCBzaGFwZSB0ZWxscyB0aGUgbmV0d29yayB0aGF0IHRoZSBmaXJzdCBsYXllciBzaG91bGQgZXhwZWN0DQogICMgaW1hZ2VzIG9mIDE1MCBieSAxNTAgcGl4ZWxzIHdpdGggYSBjb2xvciBkZXB0aCBvZiAzIGllIFJHQiBpbWFnZXMNCiAgbGF5ZXJfY29udl8yZChpbnB1dF9zaGFwZSA9IGMoMTUwLCAxNTAsIDMpLCBmaWx0ZXJzID0gMTYsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogICMgYWRkaW5nIGEgbWF4IHBvb2xpbmcgbGF5ZXIgd2hpY2ggaGFsdmVzIHRoZSBkaW1lbnNpb25zDQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JQ0KICAgIyBhZGRpbmcgYSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXIgd2l0aCAzMiBmaWx0ZXJzDQogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDMyLCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JQ0KICAjIGFkZGluZyBhIHBvb2xpbmcgbGF5ZXINCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lDQogICMgaW5jcmVhc2luZyBudW1iZXIgb2YgZmlsdGVycyBhcyBpbWFnZSBzaXplIGRlY3JlYXNlcw0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSA2NCwga2VybmVsX3NpemUgPSBjKDMsIDMpLCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUNCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkNCiAgDQpgYGANCg0KPGJyPg0KDQojIyMgKipBZGRpbmcgYSBjbGFzc2lmaWVyIHRvIHRoZSBjb252bmV0KioNCg0KQ29udm9sdXRpb25hbCBsYXllcnMgbGVhcm4gdGhlIGZlYXR1cmVzIGFuZCBwYXNzIHRoZXNlIHRvIHRoZSBkZW5zZSBsYXllcnMgd2hpY2ggbWFwIHRoZSBsZWFybmVkIGZlYXR1cmVzIHRvIHRoZSBnaXZlbiBsYWJlbHMuIFRoZXJlZm9yZSwgdGhlIG5leHQgc3RlcCBpcyB0byBmZWVkIHRoZSBsYXN0IG91dHB1dCB0ZW5zb3IgaW50byBhIGRlbnNlbHkgY29ubmVjdGVkIGNsYXNzaWZpZXIgbmV0d29yayBsaWtlIHRob3NlIHdl4oCZcmUgYWxyZWFkeSBmYW1pbGlhciB3aXRoOiBhIHN0YWNrIG9mIGRlbnNlIGxheWVycy4NClRoZXNlIGNsYXNzaWZpZXJzIHByb2Nlc3MgdmVjdG9ycywgd2hpY2ggYXJlIDFELCBob3dldmVyLCB0aGUgY3VycmVudCBvdXRwdXQgaXMgYSAzRCB0ZW5zb3IuIEZpcnN0IHdlIGhhdmUgdG8gZmxhdHRlbiB0aGUgM0Qgb3V0cHV0cyB0byAxRCwgYW5kIHRoZW4gYWRkIGEgZmV3IGRlbnNlIGxheWVycyBvbiB0b3AuDQoNCk5vdGUgdGhhdCBiZWNhdXNlIHdlIGFyZSBmYWNpbmcgYSB0d28tY2xhc3MgY2xhc3NpZmljYXRpb24gcHJvYmxlbSwgaS5lLiBhIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtLCB3ZSB3aWxsIGVuZCBvdXIgbmV0d29yayB3aXRoIGEgW3NpZ21vaWQgYWN0aXZhdGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU2lnbW9pZF9mdW5jdGlvbiksIHNvIHRoYXQgdGhlIG91dHB1dCBvZiBvdXIgbmV0d29yayB3aWxsIGJlIGEgc2luZ2xlIHNjYWxhciBiZXR3ZWVuIDAgYW5kIDEsIGVuY29kaW5nIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjdXJyZW50IGltYWdlIGlzIGNsYXNzIDEgKGFzIG9wcG9zZWQgdG8gY2xhc3MgMCkuIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IEtlcmFzIGFjdGl2YXRpb24gZnVuY3Rpb25zLCBraW5kbHkgdmlzaXQgdGhlIFtLZXJhcyB3ZWJzaXRlXShodHRwczovL2tlcmFzLmlvL2FwaS9sYXllcnMvYWN0aXZhdGlvbnMvKS4NCg0KYGBge3J9DQptb2RlbCA8LSBtb2RlbCAlPiUNCiAgbGF5ZXJfZmxhdHRlbigpICU+JQ0KICBsYXllcl9kZW5zZSh1bml0cyA9IDUxMiwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9J3NpZ21vaWQnKQ0KDQoNCiMgTGV04oCZcyBsb29rIGF0IGhvdyB0aGUgZGltZW5zaW9ucyBvZiB0aGUgZmVhdHVyZSBtYXBzIGNoYW5nZSB3aXRoIGV2ZXJ5IHN1Y2Nlc3NpdmUgbGF5ZXI6DQoNCm1vZGVsICU+JSBzdW1tYXJ5KCkNCmBgYA0KDQoNClNpZ21vaWQgaXMgZXF1aXZhbGVudCB0byBhIDItZWxlbWVudCBTb2Z0bWF4LCB0aGVyZWZvcmUsIHdpdGggYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbSBsaWtlIHRoaXMsIHlvdSBjYW4gZ2V0IGF3YXkgd2l0aCBvbmx5IDEgbmV1cm9uIGFuZCBhIHNpZ21vaWQgYWN0aXZhdGlvbiBmdW5jdGlvbiB3aGljaCBwdXNoZXMgdmFsdWVzIGJldHdlZW4gMCBmb3Igb25lIGNsYXNzIGFuZCAxIGZvciB0aGUgb3RoZXIgY2xhc3MuDQoNCjxicj4NCg0KKipDb21waWxlOioqIENvbmZpZ3VyaW5nIGEgS2VyYXMgbW9kZWwgZm9yIHRyYWluaW5nDQoNCmBgYHtyfQ0KbW9kZWwgJT4lDQogIGNvbXBpbGUoDQogICAgbG9zcyA9ICdiaW5hcnlfY3Jvc3NlbnRyb3B5JywNCiAgICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfcm1zcHJvcChsciA9IDAuMDAxKSwNCiAgICBtZXRyaWNzID0gJ2FjY3VyYWN5Jw0KICApDQpgYGANCg0KQmluYXJ5XyBDcm9zc2VudHJvcHkgbG9zcyBDb21wdXRlcyB0aGUgY3Jvc3MtZW50cm9weSBsb3NzIGJldHdlZW4gdHJ1ZSBsYWJlbHMgYW5kIHByZWRpY3RlZCBsYWJlbHMuIFR5cGljYWxseSB1c2VkIHdoZW4gdGhlcmUgYXJlIG9ubHkgdHdvIGxhYmVsIGNsYXNzZXMuKEZvciBhIHJlZnJlc2hlciBvbiBsb3NzIG1ldHJpY3MsIHNlZSB0aGUgW01hY2hpbmUgTGVhcm5pbmcgQ3Jhc2ggQ291cnNlXShodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9tYWNoaW5lLWxlYXJuaW5nL2NyYXNoLWNvdXJzZS9kZXNjZW5kaW5nLWludG8tbWwvdmlkZW8tbGVjdHVyZSkgYW5kIHRoZSBbS2VyYXMgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9rZXJhcy5pby9hcGkvbG9zc2VzL3Byb2JhYmlsaXN0aWNfbG9zc2VzLyNiaW5hcnlfY3Jvc3NlbnRyb3B5LWZ1bmN0aW9uKSkgDQoNCjxicj4NCg0KIyAqKkRhdGEgcHJlcHJvY2Vzc2luZyoqDQoNCk5vdyB0aGF0IHdlIGhhdmUgdGhlIGRhdGEsIHdlIHNob3VsZCBmb3JtYXQgaXQgaW50byBhcHByb3ByaWF0ZWx5IHByZXByb2Nlc3NlZCBmbG9hdGluZy1wb2ludCB0ZW5zb3JzIGJlZm9yZSBiZWluZyBmZWQgaW50byB0aGUgbmV0d29yay4NClNvIHRoZSBzdGVwcyBmb3IgZ2V0dGluZyBpdCBpbnRvIHRoZSBuZXR3b3JrIGFyZSByb3VnaGx5IGFzIGZvbGxvd3M6DQoNCjEuIFJlYWQgdGhlIHBpY3R1cmUgZmlsZXMuDQoyLiBEZWNvZGUgdGhlIEpQRyBjb250ZW50IHRvIFJHQiBncmlkcyBvZiBwaXhlbHMuDQozLiBDb252ZXJ0IHRoZXNlIGludG8gZmxvYXRpbmctcG9pbnQgdGVuc29ycy4NCjQuIE5vcm1hbGl6ZSB0aGUgcGl4ZWwgdmFsdWVzIHRvIHRoZSBbMCwgMV0gaW50ZXJ2YWwgKEl0IGlzIHVuY29tbW9uIHRvIGZlZWQgcmF3IHBpeGVscyBpbnRvIGEgY29udm5ldCkuDQo1LiBBdXRvbGFiZWwgdGhlIGltYWdlcyBvZiBjYXRzIGFuZCBkb2dzIGF1dG9tYXRpY2FsbHkgYmFzZWQgb24gdGhlIHN1YmRpcmVjdG9yeSBuYW1lOiAtIEltYWdlR2VuZXJhdG9yIHdpbGwgbGFiZWwgdGhlIGltYWdlcyBhcHByb3ByaWF0ZWx5IGZvciB5b3UsIHJlZHVjaW5nIGEgY29kaW5nIHN0ZXAuIFNvdW5kcyBuZWF0LCByaWdodD8NCg0KSXQgbWF5IHNlZW0gYSBiaXQgZGF1bnRpbmcsIGJ1dCB0aGFua2Z1bGx5IEtlcmFzIGhhcyB1dGlsaXRpZXMgdG8gdHVybiBpbWFnZSBmaWxlcyBvbiBkaXNrIGludG8gYmF0Y2hlcyBvZiBwcmUtcHJvY2Vzc2VkIHRlbnNvcnMuIFN1Y2ggaW1hZ2UgcHJvY2Vzc2luZyB0b29scyBpbmNsdWRlIHRoZSBmdW5jdGlvbiBgaW1hZ2VfZGF0YV9nZW5lcmF0b3JgLg0KPGJyPjxicj4NCg0KYGBge3J9DQojIG5vcm1hbGl6aW5nIHRoZSBkYXRhIGJ5IG11bHRpcGxpbmcgYnkgYSByZXNjYWxpbmcgZmFjdG9yDQp0cmFpbl9kYXRhZ2VuIDwtIGltYWdlX2RhdGFfZ2VuZXJhdG9yKHJlc2NhbGUgPSAxLzI1NSkNCg0KIyBGbG93IHRyYWluaW5nIGltYWdlcyBpbiBiYXRjaGVzIG9mIDI1MCB1c2luZyB0cmFpbl9kYXRhZ2VuIGdlbmVyYXRvcg0KDQp0cmFpbl9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnkoDQogICMgdGFyZ2V0IGRpcmVjdG9yeQ0KICBkaXJlY3RvcnkgPSB0cmFpbl9kaXIsDQogICMgdHJhaW5pbmcgZGF0YSBnZW5lcmF0b3INCiAgZ2VuZXJhdG9yID0gdHJhaW5fZGF0YWdlbiwNCiAgIyByZXNpemluZyB0aGUgaW1hZ2VzIHRvIHRoZSBzYW1lIGRpbWVuc2lvbnMgZXhwZWN0ZWQgYnkgb3VyIE5ODQogIHRhcmdldF9zaXplID0gYygxNTAsIDE1MCksDQogICMgMjUwIGltYWdlcyBhdCBhIHRpbWUgdG8gYmUgZmVkIGludG8gdGhlIE5ODQogIGJhdGNoX3NpemUgPSAyNTAsDQogICMgU2luY2Ugd2UgdXNlIGJpbmFyeV9jcm9zc2VudHJvcHkgbG9zcywgd2UgbmVlZCBiaW5hcnkgbGFiZWwgYXJyYXlzDQogIGNsYXNzX21vZGUgPSAnYmluYXJ5Jw0KKQ0KYGBgDQpTZWVtcyB3ZSBoYWQgYSBkYXRhIGJhc2UgZmlsZSBpbiBlYWNoIG9mIHRoZSBjYXRzIGFuZCBkb2dzIHRyYWluaW5nIGZvbGRlcnMuIEx1Y2tpbHkgdGhhdCB3aWxsIG5vdCBhZmZlY3QgYW55dGhpbmcgc2luY2UgdGhleSBoYXZlbid0IGJlZW4gcmVjb2duaXplZCBhcyBpbWFnZXMuDQo8YnI+PGJyPg0KTWF5YmUgc29tZSBmZXcgdGhpbmdzIHRvIHBvaW50IG91dCB0aGF0IGNvdWxkIHJlc3VsdCBpbnRvIGJ1Z3M6DQoNCjEuIFRoZSBgZGlyZWN0b3J5YCBpcyB0aGUgcGFyZW50IGRpcmVjdG9yeSBmb2xkZXIgdGhhdCBjb250YWlucyB0aGUgbGFiZWxzIHN1Yi1kaXJlY3Rvcmllcy4NCjIuIEZvciBgY2xhc3NfbW9kZWAgaWYgeW91IG9ubHkgaGF2ZSB0d28gY2xhc3NlcyBrZWVwIGl0IGFzIGBiaW5hcnlgLCBpZiB5b3UgaGF2ZSBtb3JlIHRoYW4gdHdvIGNsYXNzZXMsIGtlZXAgaXQgYGNhdGVnb3JpY2FsYC4NCg0KTGV0J3MgZG8gdGhlIHNhbWUgZm9yIHRoZSB2YWxpZGF0aW9uIGRhdGFzZXQNCjxicj48YnI+DQpgYGB7cn0NCnZhbGlkYXRpb25fZGF0YWdlbiA8LSBpbWFnZV9kYXRhX2dlbmVyYXRvcihyZXNjYWxlID0gMS8yNTUpDQoNCnZhbGlkYXRpb25fZ2VuZXJhdG9yIDwtIGZsb3dfaW1hZ2VzX2Zyb21fZGlyZWN0b3J5KA0KICAjIHRhcmdldCBkaXJlY3RvcnkNCiAgZGlyZWN0b3J5ID0gdGVzdF9kaXIsDQogICMgdGVzdGluZyBkYXRhIGdlbmVyYXRvcg0KICBnZW5lcmF0b3IgPSB2YWxpZGF0aW9uX2RhdGFnZW4sDQogICMgcmVzaXppbmcgdGhlIGltYWdlcyB0byB0aGUgc2FtZSBkaW1lbnNpb25zIGV4cGVjdGVkIGJ5IG91ciBOTg0KICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLA0KICAjIDI1MCBpbWFnZXMgYXQgYSB0aW1lIHRvIGJlIGZlZCBpbnRvIHRoZSBOTg0KICBiYXRjaF9zaXplID0gMjUwLA0KICAjIFNpbmNlIHdlIHVzZSBiaW5hcnlfY3Jvc3NlbnRyb3B5IGxvc3MsIHdlIG5lZWQgYmluYXJ5IGxhYmVsIGFycmF5cw0KICBjbGFzc19tb2RlID0gJ2JpbmFyeScNCikNCmBgYA0KDQo8YnI+DQoNCiMgKipUcmFpbmluZyB0aGUgTmV1cmFsIE5ldHdvcmsqKg0KDQpUaGlzIGlzIHRoZSBwcm9jZXNzIG9mIHRyYWluaW5nIHRoZSBuZXVyYWwgbmV0d29yaywgd2hlcmUgaXQgJ2xlYXJucycgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB0cmFpbl9pbWFnZXMgYW5kIHRyYWluX2xhYmVscyBhcnJheXMuDQoNCkxldOKAmXMgZml0IHRoZSBtb2RlbCB0byB0aGUgZGF0YSB1c2luZyB0aGUgZ2VuZXJhdG9yLiBZb3UgZG8gc28gdXNpbmcgdGhlDQpgZml0X2dlbmVyYXRvciB7a2VyYXN9YCBmdW5jdGlvbiwgdGhlIGVxdWl2YWxlbnQgZm9yIGBmaXRgIGZvciBkYXRhIGdlbmVyYXRvcnMgbGlrZSB0aGlzIG9uZS4gSXQgZXhwZWN0cyBhcyBpdHMgZmlyc3QgYXJndW1lbnQgYSBnZW5lcmF0b3IgdGhhdCB3aWxsIHlpZWxkIGJhdGNoZXMgb2YgaW5wdXRzIGFuZCB0YXJnZXRzIGluZGVmaW5pdGVseS4gQmVjYXVzZSB0aGUgZGF0YSBpcyBiZWluZyBnZW5lcmF0ZWQgZW5kbGVzc2x5LCB0aGUgbW9kZWwgbmVlZHMgdG8ga25vdyBob3cgbWFueSBzYW1wbGVzIHRvIGRyYXcgZnJvbSB0aGUgZ2VuZXJhdG9yIGJlZm9yZSBkZWNsYXJpbmcgYW4gZXBvY2ggb3Zlci4gVGhpcyBpcyB0aGUgcm9sZSBvZiB0aGUgYHN0ZXBzX3Blcl9lcG9jaGAgYXJndW1lbnQuIEl0IGRlZmluZXMgdGhlIHRvdGFsIG51bWJlciBvZiBzdGVwcyAoYmF0Y2hlcyBvZiBzYW1wbGVzKSB0byB5aWVsZCBmcm9tIGdlbmVyYXRvciBiZWZvcmUgZGVjbGFyaW5nIG9uZSBlcG9jaCBmaW5pc2hlZCBhbmQgc3RhcnRpbmcgdGhlIG5leHQgZXBvY2guIEl0IHNob3VsZCAqdHlwaWNhbGx5KiBiZSBlcXVhbCB0byB0aGUgKm51bWJlciBvZiBzYW1wbGVzIGluIHlvdXIgZGF0YXNldCBkaXZpZGVkIGJ5IHRoZSBiYXRjaCBzaXplKi4NCg0KYHZhbGlkYXRpb25fc3RlcHNgIGRlc2NyaWJlcyB0aGUgdG90YWwgbnVtYmVyIG9mIHN0ZXBzIChiYXRjaGVzIG9mIHNhbXBsZXMpIHRvIHlpZWxkIGZyb20gZ2VuZXJhdG9yIGJlZm9yZSBzdG9wcGluZyBhdCB0aGUgZW5kIG9mIGV2ZXJ5IGVwb2NoLiBJdCB0ZWxscyB0aGUgbmV0d29yayBob3cgbWFueSBiYXRjaGVzIHRvIGRyYXcgZnJvbSB0aGUgdmFsaWRhdGlvbiBnZW5lcmF0b3IgZm9yIGV2YWx1YXRpb24uDQoNCmBBbiBlcG9jaCBmaW5pc2hlcyB3aGVuIHN0ZXBzX3Blcl9lcG9jaCBiYXRjaGVzIGhhdmUgYmVlbiBzZWVuIGJ5IHRoZSBtb2RlbC5gDQoNCjxicj4NCg0KICoqRml0dGluZyB0aGUgbW9kZWwgdXNpbmcgYSBiYXRjaCBnZW5lcmF0b3IqKg0KIA0KTGV0J3MgdHJhaW4gZm9yIDE1IGVwb2NocyAtLSB0aGlzIG1heSB0YWtlIGEgZmV3IG1pbnV0ZXMgdG8gcnVuLg0KDQpUaGUgTG9zcyBhbmQgQWNjdXJhY3kgYXJlIGEgZ3JlYXQgaW5kaWNhdGlvbiBvZiBwcm9ncmVzcyBvZiB0cmFpbmluZy4gSXQncyBtYWtpbmcgYSBndWVzcyBhcyB0byB0aGUgY2xhc3NpZmljYXRpb24gb2YgdGhlIHRyYWluaW5nIGRhdGEsIGFuZCB0aGVuIG1lYXN1cmluZyBpdCBhZ2FpbnN0IHRoZSBrbm93biBsYWJlbCwgY2FsY3VsYXRpbmcgdGhlIHJlc3VsdC4gQWNjdXJhY3kgaXMgdGhlIHBvcnRpb24gb2YgY29ycmVjdCBndWVzc2VzLg0KPGJyPjxicj4NCg0KYGBge3J9DQpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXRfZ2VuZXJhdG9yKA0KICBnZW5lcmF0b3IgPSB0cmFpbl9nZW5lcmF0b3IsDQogICMgVG90YWwgbnVtYmVyIG9mIHN0ZXBzIChiYXRjaGVzIG9mIHNhbXBsZXMpIHRvIHlpZWxkDQogICNiZWZvcmUgZGVjbGFyaW5nIG9uZSBlcG9jaCBmaW5pc2hlZCBhbmQgc3RhcnRpbmcgdGhlIG5leHQgZXBvY2guDQogIHN0ZXBzX3Blcl9lcG9jaCA9IDkwLA0KICAjIEFuIGVwb2NoIGlzIGFuIGl0ZXJhdGlvbiBvdmVyIHRoZSBlbnRpcmUgZGF0YSBwcm92aWRlZA0KICBlcG9jaHMgPSAxNSwNCiAgdmFsaWRhdGlvbl9kYXRhID0gdmFsaWRhdGlvbl9nZW5lcmF0b3IsDQogIHZhbGlkYXRpb25fc3RlcHMgPSA1DQogIA0KICANCikNCg0KIyBJdOKAmXMgZ29vZCBwcmFjdGljZSB0byBhbHdheXMgc2F2ZSB5b3VyIG1vZGVscyBhZnRlciB0cmFpbmluZy4NCg0KbW9kZWwgJT4lIHNhdmVfbW9kZWxfaGRmNSgiY2F0c19hbmRfZG9ncy5oNSIpIA0KDQojIHBsb3R0aW5nIHRoZSBsb3NzIGFuZCBhY2N1cmFjeSBvdmVyIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhDQojIGR1cmluZyB0cmFpbmluZw0KcGxvdChoaXN0b3J5KQ0KDQojIEEgc3VtbWFyeSBvZiBob3cgdGhlIG1vZGVsIHBlcmZvcm1lZA0KaGlzdG9yeQ0KDQpgYGANCg0KVGhlc2UgcGxvdHMgc2hvdyBhIGNoYXJhY3RlcmlzdGljIG9mIG92ZXJmaXR0aW5nOiB0aGUgZmFjdCB0aGF0IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRlbmQgdG8gcGVyZm9ybSB3b3JzZSBvbiBuZXcgZGF0YSB0aGFuIG9uIHRoZWlyIHRyYWluaW5nIGRhdGEuIEl0IG9jY3VycyB3aGVuIHRoZSBuZXR3b3JrIGVuZHMgdXAgbGVhcm5pbmcgcmVwcmVzZW50YXRpb25zIHRoYXQgYXJlIHNwZWNpZmljIHRvIHRoZSB0cmFpbmluZyBkYXRhIGFuZCBkb2Vzbid0IGdlbmVyYWxpemUgdG8gZGF0YSBvdXRzaWRlIG9mIHRoZSB0cmFpbmluZyBzZXQuIFRoaXMgY2FuIGJlIGltcHJvdmVkIHVzaW5nIGBpbWFnZSBhdWdtZW50YXRpb25gIHJpZ2h0IHVwIGluIHRoZSBuZXh0IFtlcGlzb2RlXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PVFXZFlXd1c2T0FFKS4NCg0KDQojICoqR2VuZXJhdGluZyBwcmVkaWN0aW9ucyBvbiBuZXcgZGF0YS9vdXIgb3duIGRhdGEqKg0KDQpXaGF0J3MgdGhlIGZ1biBvZiBidWlsZGluZyB0aGlzIE5OIGlmIHdlIGNhbid0IHRyeSBpdCBvbiBvdXIgb3duIHBldHMsIHJpZ2h0P/CfmLgNCg0KV2UnbGwgZG93bmxvYWQgc29tZSBpbWFnZXMgb2YgY2F0cyBhbmQgZG9ncyBhbmQgc2VlIGhvdyB3ZWxsIG91ciBtb2RlbCBjbGFzc2lmaWVzIHRoZXNlIGltYWdlcyBpdCBoYXMgbmV2ZXIgc2VlbiBiZWZvcmUuDQpGcm9tIHRoaXMgZXhlcmNpc2UncyBQeXRob24gTm90ZWJvb2sgdGhlIGBpbWFnZV9sb2FkIHtrZXJhc31gIGFuZCBgaW1hZ2VfdG9fYXJyYXkge2tlcmFzfWAgd2VyZSB1c2VkLiBUaGVzZSBjYW4gZWFzaWx5IGJlIGltcGxlbWVudGVkIGluIFIgdG9vLg0KRm9yIHRoaXMgcG9zdCwgSSBoYXZlIG9wdGVkIGZvciBgcHJlZGljdF9nZW5lcmF0b3Ige2tlcmFzfWAuIA0KDQo8YnI+DQoNCioqSW1wbGVtZW50aW5nIGEgZGF0YSBnZW5lcmF0b3IgZm9yIHRoZSB0ZXN0IGltYWdlcyoqDQoNCmBgYHtyfQ0KZnVuX2RpciA8LSBmaWxlLnBhdGgoYmFzZV9kaXIsICJteV90ZXN0X2ltYWdlcyIpDQpkaXIuY3JlYXRlKGZ1bl9kaXIpDQoNCiMgc3ViLWZvbGRlciBpbiBgbXlfdGVzdF9pbWFnZXNgIHRoYXQgY29udGFpbnMgdGhlIGltYWdlcw0KbXlfY2F0c19kb2dzX2RpciA8LSBmaWxlLnBhdGgoZnVuX2RpciwgIm15X2ltYWdlcyIpDQpkaXIuY3JlYXRlKG15X2NhdHNfZG9nc19kaXIpDQoNCiMgY29weWluZyBkb3dubG9hZGVkIGltYWdlcyB0byBgbXlfY2F0c19kb2dzYCBkaXJlY3RvcnkNCmRvd25sb2FkX2ltZ3MgPC0gbGlzdC5maWxlcyhwYXRoID0gIkM6L1VzZXJzL2tlcmFzL0Rvd25sb2FkcyIsDQogICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIi5qcGciLA0KICAgICAgICAgICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFQgKQ0KDQppbnZpc2libGUoZmlsZS5jb3B5KGZyb20gPSBkb3dubG9hZF9pbWdzLCB0byA9IG15X2NhdHNfZG9nc19kaXIpKQ0KDQpgYGANCg0KDQo8YnI+DQoNCmBgYHtyfQ0KdGVzdF9kYXRhZ2VuIDwtIGltYWdlX2RhdGFfZ2VuZXJhdG9yKHJlc2NhbGUgPSAxLzI1NSkNCg0KdGVzdF9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnkoDQogIGRpcmVjdG9yeSA9IGZ1bl9kaXIsDQogIGdlbmVyYXRvciA9IHRlc3RfZGF0YWdlbiwNCiAgdGFyZ2V0X3NpemUgPSBjKDE1MCwgMTUwKSwNCiAgYmF0Y2hfc2l6ZSA9IDEwLA0KICBjbGFzc19tb2RlID0gJ2JpbmFyeScsDQogIHNodWZmbGUgPSBGDQopDQpgYGANCg0KDQoNCioqR2VuZXJhdGluZyBwcmVkaWN0aW9ucyBmb3IgdGhlIHRlc3Qgc2FtcGxlcyBmcm9tIGEgZGF0YSBnZW5lcmF0b3IuKioNCg0KYGBge3J9DQpwcmVkaWN0aW9ucyA8LSBtb2RlbCAlPiUgcHJlZGljdF9nZW5lcmF0b3IoDQogIHN0ZXBzID0gMSwNCiAgZ2VuZXJhdG9yID0gdGVzdF9nZW5lcmF0b3IsDQogIHZlcmJvc2UgPSAwDQopDQoNCmltYWdlX2xhYmVscyA8LSBsaXN0LmZpbGVzKHBhdGggPSBteV9jYXRzX2RvZ3NfZGlyKQ0KDQpwcmVkX3Jlc3VsdHMgPC0gYXMuZGF0YS5mcmFtZShjYmluZChpbWFnZV9sYWJlbHMsIHByZWRpY3Rpb25zKSkgJT4lDQogIHJlbmFtZSgicHJlZGljdGlvbiIgPSAyKSAlPiUNCiAgbXV0YXRlKCJwcmVkaWN0ZWRfY2xhc3MiID0gaWZfZWxzZShwcmVkaWN0aW9uPjAuNSxwcmludCgiZG9nIikscHJpbnQoImNhdCIpKSwNCiAgICAgICAgIHByZWRpY3Rpb24gPSBhcy5kb3VibGUocHJlZGljdGlvbikpDQoNCg0KYGBgDQoNCg0KQmVsb3cgYXJlIHRoZSBpbWFnZXMgSSB1c2VkOg0KDQpgYGB7cixmaWcuY2FwPSAiSW1hZ2Ugc291cmNlOiBwZXhlbHMuY29tIn0NCm15X2ltYWdlcyA8LSBsaXN0LmZpbGVzKG15X2NhdHNfZG9nc19kaXIsIGZ1bGwubmFtZXMgPSBUKQ0KDQojIHJlc2l6aW5nIHRoZSBpbWFnZXMgdG8gYSBjb21tb24gZGltZW5zaW9uIGFzIHJlcXVpcmVkIGJ5IHJlYWRJbWFnZXtFQkltYWdlfQ0KZm9yIChpIGluIHNlcV9hbG9uZyhteV9pbWFnZXMpKSB7DQogIHJlYWRJbWFnZShteV9pbWFnZXNbaV0pICU+JQ0KICAgIHJlc2l6ZSh3ID0gMjUwMCwgaCA9IDI1MDApICU+JQ0KICAgIHdyaXRlSW1hZ2UobXlfaW1hZ2VzW2ldKQ0KDQogICAgDQp9DQoNCkVCSW1hZ2U6OmRpc3BsYXkoDQogIHJlYWRJbWFnZShteV9pbWFnZXMpLA0KICBtZXRob2QgPSAncmFzdGVyJywNCiAgYWxsID0gVCwNCiAgbnggPSAzLA0KICBzcGFjaW5nID0gYygwLDApDQopDQoNCg0KYGBgDQoNClByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsIHRoYXQgdHJhaW5lZCBmb3IgMTUgZXBvY2hzDQoNCmBgYHtyfQ0KDQpwcmVkX3Jlc3VsdHMNCmBgYA0KDQoNCk5vdCBiYWQhIFdpdGggc29tZSBmZXcgdHdlYWtzIGhlcmUgYW5kIHRoZXJlLCB0aGUgbW9kZWwgY2FuIGJlIG9wdGltaXplZCB0byBwZXJmb3JtIGJldHRlci4gQW55aG93LCB3ZSdsbCBsZWF2ZSBpdCBhdCB0aGF0LCBmb3Igbm93LvCfmIoNCg0KDQo8YnI+DQoNCkNvbnZuZXRzIHdvcmsgYnkgbGVhcm5pbmcgYSBoaWVyYXJjaHkgb2YgbW9kdWxhciBwYXR0ZXJucyBhbmQgY29uY2VwdHMgdG8NCnJlcHJlc2VudCB0aGUgdmlzdWFsIHdvcmxkLiBBcyB5b3UgZ28gaGlnaGVyLCB0aGUgYWN0aXZhdGlvbnMgYmVjb21lIGluY3JlYXNpbmdseSBhYnN0cmFjdCBhbmQgbGVzcyB2aXN1YWxseSBpbnRlcnByZXRhYmxlLiBUaGV5IGJlZ2luIHRvIGVuY29kZSBoaWdoZXLCrWxldmVsIGNvbmNlcHRzIHN1Y2ggYXMg4oCcY2F0IGVhcuKAnSBhbmQg4oCcY2F0IGV5ZS7igJ0gSGlnaGVyIHByZXNlbnRhdGlvbnMgY2FycnkgaW5jcmVhc2luZ2x5IGxlc3MgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHZpc3VhbCBjb250ZW50cyBvZiB0aGUgaW1hZ2UsIGFuZCBpbmNyZWFzaW5nbHkgbW9yZSBpbmZvcm1hdGlvbiByZWxhdGVkIHRvIHRoZSBjbGFzcyBvZiB0aGUgaW1hZ2UuDQoNCkFnYWluLCB3ZSBoYXZlIG1hZGUgaXQgdGhpcyBmYXIg8J+PhiEgUHJldHR5LCBhd2Vzb21lLiBDb252bmV0cyBhcmVuJ3Qgc28gJ2JsYWNrLWJveGVzJy4gWW91IGNhbiBnbyBhaGVhZCBhbmQgdmlzdWFsaXplIEludGVybWVkaWF0ZSBSZXByZXNlbnRhdGlvbnMgdG8gc2VlIGhvdyBhbiBpbnB1dCBnZXRzIHRyYW5zZm9ybWVkIGFzIGl0IGdvZXMgdGhyb3VnaCBhIGNvbnZuZXQncyBmaWx0ZXJzIGFzIHdlIGRpZCBpbiB0aGUgW3ByZXZpb3VzIGVwaXNvZGVdKGh0dHBzOi8vcnB1YnMuY29tL2VSX2ljL21scl81KS4NCg0KVGhhdCdzIGFsbCBmb3Igbm93Lg0KSGFwcHkgTGVhcm5pbmchICDwn5Gp8J+PveKAjfCfkrsg8J+RqOKAjfCfkrsg8J+RqPCfj77igI3wn5K7IPCfkanigI3wn5K7DQoNCiMgKipSZWZlcmVuY2UgTWF0ZXJpYWwqKg0KDQoqIE1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnM6IEVwICM2IC0gW0NvbnZvbHV0aW9uYWwgY2F0cyBhbmQgZG9nc10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1ucTdfWllKUFdmMCkNCg0KKiBEZWVwIExlYXJuaW5nIHdpdGggUiBieSBGcmFuY29pcyBDaG9sbGV0IGFuZCBKLkouQWxsYWlyZQ0KDQoqIFRoZSBbUiBpbnRlcmZhY2UgdG8gS2VyYXNdKGh0dHBzOi8va2VyYXMucnN0dWRpby5jb20vYXJ0aWNsZXMvdHV0b3JpYWxfYmFzaWNfY2xhc3NpZmljYXRpb24uaHRtbCkgd2Vic2l0ZS4NCg0KKiBUaGUgW0tlcmFzIEFQSSBSZWZlcmVuY2VdKGh0dHBzOi8va2VyYXMuaW8vYXBpLykgd2Vic2l0ZQ0KDQoqIExhYiA2OiBbTGFiNi1DYXRzLXYtRG9ncy5pcHluYl0oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWI2LUNhdHMtdi1Eb2dzLmlweW5iI3Njcm9sbFRvPTd2NTVyV2xRZWh6TCkgDQoNCg0KDQoNCg0KDQoNCg==