Improving Computer Vision Accuracy using Convolutions: An R Version
Hello! Welcome to the fourth (includes the 3rd) 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 learning models using TensorFlow.
In this episode, Episode 4, Laurence Moroney takes us through yet another exciting application of Machine Learning. Here, we put convolutions and pooling to use and create our own convolutional neural networks! This makes the Fashion MNIST neural network more efficient – because it will classify based on features, and not on raw pixels. Convolutional layers learn the features and pass these to the dense layers which map the learned features to the given labels.
Like the previous R Notebooks, this Notebook tries to replicate the Python Notebook used for this episode.
Before we begin, I highly recommend that you follow Episode 3 and Episode 4 where Laurence Moroney demystifies the concepts of convolution and pooling in computer vision. 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.
A high level overview of Convolution and Pooling
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. At its heart, convolution is really simple. It involves scanning every pixel in the image, looking at it’s neighboring pixels, multiplying these pixels by their corresponding weight in a filter and then summing this all up to obtain a new pixel value. This can be shown as below:
## [1] Image source: https://colab.research.google.com/github/lmoroney/mlday-tokyo/blob/master/Lab3-What-Are-Convolutions.ipynb#scrollTo=xF0FPplsgHNh
The previous Dense Neural Network
that we created simply learned from the raw pixels what made up a sweater or what made up a boot. This in itself is quite a limitation.
Ultimately the goal of trying to understand what an item is, isn’t just matching the raw pixels to labels like we did in the previous exercises.
What if we could extract features from the image instead and when an image has some specific features, it belongs to a particular class. This is the heart of Convolution Neural Networks do.
This key characteristic gives convnets two interesting properties:
The patterns they learn are translation-invariant: This means that after learning a certain pattern, a covnet can recognize it anywhere else in the image as opposed to a DNN which would have to learn the pattern a new if it appeared at a new location. For this reason, covnets require few training samples.
They 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. For this reason, covnets can learn increasingly complex and abstract features.
If you’ve ever done image processing using a filter (like this: https://en.wikipedia.org/wiki/Kernel_(image_processing)) then convolutions will look very familiar.
In short, you take an array (usually 3x3 or 5x5) and pass it over the image. By changing the underlying pixels based on the formula within that matrix, you can do things like edge detection. So, for example, if you look at the above link, you’ll see a 3x3 that is defined for edge detection where the middle cell is 8, and all of its neighbors are -1. In this case, for each pixel, you would multiply its value by 8, then subtract the value of each neighbor. Do this for every pixel, and you’ll end up with a new image that has the edges enhanced.
That’s the concept of Convolutional Neural Networks. Add some layers to do convolution before you have the dense layers, and then the information going to the dense layers is more focussed, and possibly more accurate.
Pooling
reduces the amount of irrelevant information in an image while maintaining the features that are detected. It does so by looking at a pixel and its immediate neighbours to the right, beneath and right-beneath, takes the largest hence the name Max pooling, and loads it into a new image. It thus reduces the amount of information that a model has to process while still maintaining the prominent features.
## [1] Image source: https://colab.research.google.com/github/lmoroney/mlday-tokyo/blob/master/Lab3-What-Are-Convolutions.ipynb#scrollTo=xF0FPplsgHNh
Building Convolution Neural Networks.
Gathering the Data
Let’s start by loading the libraries required for this session.
We’ll be requiring some packages in the Tidyverse and Keras(a framework for defining a neural network as a set of Sequential layers). You can have them installed as follows
suppressMessages(install.packages("tidyverse"))
suppressMessages(install.packages("keras"))
suppressMessages(install_keras())
Ps: it could take a while
Once installed, let’s get rolling:
Reshaping our image arrays and normalizing them
training_images <- array_reshape(training_images, c(60000, 28, 28, 1))
training_images <- training_images/255
test_images <- array_reshape(test_images, c(10000, 28, 28, 1))
test_images <- test_images/255
# the values 60,000 and 10,000 are not arbitrary, we obtained them using
dim(training_images)
## [1] 60000 28 28 1
Why are we adding one more dimension? That’s an important question. Convolutions operate over 3D tensors, called feature maps, with two spatial axes (height width) as well as a depth axis. For an RGB depth channels image, the dimension of the depth axis is 3, because the image has 3 color channels: red, green, and blue. For a black-and-white picture, like the Fashion MNIST dataset, the depth is 1.
Instantiating a Convolution
Next is to define your model. Now instead of the input layer at the top, we’re going to add a Convolution. The parameters are:
The number of convolutions you want to generate. Purely arbitrary, but good to start with something in the order of 32
The size of the Convolution, in this case a 3x3 grid
The activation function to use – in this case we’ll use relu, which you might recall is the equivalent of returning x when x>0, else returning 0
We’ll follow the Convolution with a MaxPooling layer which is then designed to compress the image, while maintaining the content of the features that were highlighted by the convlution. By specifying (2,2) for the MaxPooling, the effect is to quarter the size of the image. Without going into too much detail here, the idea is that it creates a 2x2 array of pixels, and picks the biggest one, thus turning 4 pixels into 1. It repeats this across the image, and in so doing halves the number of horizontal, and halves the number of vertical pixels, effectively reducing the image by 25%. These concepts are clearly explained in Episode 3.
model <- keras_model_sequential() %>%
# adding the first convolution layer with 64 3by3 filter
# we add a color depth of 1 since convolutions operate over 3D tensors
layer_conv_2d(input_shape = c(28, 28, 1), filters = 64, 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 which filters the results
# from the previous layer
layer_conv_2d(filters = 64, kernel_size = c(3,3), activation = 'relu') %>% # adding a pooling layer
layer_max_pooling_2d(pool_size = c(2,2))
Adding a classifier to the covnet
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, whereas 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.
Compile: Configuring a Keras model for training
## Model: "sequential"
## ___________________________________________________________________________
## Layer (type) Output Shape Param #
## ===========================================================================
## conv2d (Conv2D) (None, 26, 26, 64) 640
## ___________________________________________________________________________
## max_pooling2d (MaxPooling2D) (None, 13, 13, 64) 0
## ___________________________________________________________________________
## conv2d_1 (Conv2D) (None, 11, 11, 64) 36928
## ___________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D) (None, 5, 5, 64) 0
## ___________________________________________________________________________
## flatten (Flatten) (None, 1600) 0
## ___________________________________________________________________________
## dense (Dense) (None, 128) 204928
## ___________________________________________________________________________
## dense_1 (Dense) (None, 10) 1290
## ===========================================================================
## Total params: 243,786
## Trainable params: 243,786
## Non-trainable params: 0
## ___________________________________________________________________________
From the summary, we expect output of the first convolution to be a 28x28 but we obtain a 26x26. If you have watched the episode, you probably know why. A 3 by 3 filter requiring a neighbour on all sides can’t work on the pixels around the edges of the picture. You effectively have to remove one pixel from the top,bottom left and rignt and this reduces your dimension by 2 on each axis. So a 28 by 28 becomes a 26 by 26. Also, the pooling layer clearly halves the dimensions of each axis.
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.
To start training, call the fit method — the model is “fit” to the training data for a fixed number of epochs.
## Trained on 60,000 samples (batch_size=32, epochs=5)
## Final epoch (plot to see history):
## loss: 0.1971
## acc: 0.9253
Evaluating the model
This is the step where we evaluate how accurately the network learnt to classify the images using the test_set
## test_loss: 0.2746516
## test_accuracy 0.9
It’s likely gone up to about 93% on the training data and 91% on the validation data.
That’s significant, and a step in the right direction!
Try running it for more epochs – say about 20, and explore the results! But while the results might seem really good, the validation results may actually go down, due to something called ‘overfitting’ which will be discussed later. Overfitting occurs when a model is trained so intensely on training data that it becomes so familiar to the particular dataset to an extent that it fails to predict new data correctly.
Visualizing every filter output in each convolution and pooling layer
For this step, I took a little detour from what was done in the Python Notebook but it illustrates the same concept: how the convolutions apply different filters to extract features from our input images.
let’s extract the first 4 convolution and pooling layers
## [1] 1 26 26 64
Next we define a function that will help us visualise the result of each filter in each of the layer activations above.
Visualizing the convolutions and pooling on our first test image
The output of each filter in each layer is as shown row-wise.
Great! Now there are a few things to note:
The first layer acts as a collection of various filters. At that stage, the output of the layer seems to retain almost all of the information present in the initial picture.
As you go higher, the outputs of the layers become increasingly abstract and less visually interpretable. They begin to encode higher-level concepts such as the “heel” and “vamp” of the shoe. Higher presentations carry increasingly less information about the visual contents of the image, and increasingly more information related to the class of the image.
Exercises
The following exercises are from the Python Notebook used for this session.
Try editing the convolutions. Change the 32s to either 16 or 64. What impact will this have on accuracy and/or training time.
Remove the final Convolution. What impact will this have on accuracy or training time?
How about adding more Convolutions? What impact do you think this will have? Experiment with it.
Remove all Convolutions but the first. What impact do you think this will have? Experiment with it.
In the previous lesson you implemented a callback to check on the loss function and to cancel training once it hit a certain amount. See if you can implement that here!
I will attempt the last question on using callbacks to stop the training process.
Keras includes a number of built-in callbacks. For this exercise, we will build our own callback which stops the model from training once a desired accuracy is attained say 90%. Training should stop after the end of the 3rd epoch when accuracy goes above 90%.
Custom Callbacks
You can create a custom callback by creating a new R6 class that inherits from the KerasCallback class.
That done, we just have to create an instance of the callback and Attach the callback to model training as below:
# creates an instance of the callback
callback <- train_stop$new()
# loading the required packages
library(keras)
# importing the datasets directly from Keras
mnist <- dataset_fashion_mnist()
c(training_images, training_labels) %<-% mnist$train
c(test_images, test_labels) %<-% mnist$test
# Reshaping our image arrays and normalizing them
training_images <- array_reshape(training_images, c(60000, 28, 28, 1))
training_images <- training_images/255
test_images <- array_reshape(test_images, c(10000, 28, 28, 1))
test_images <- test_images/255
# Instantiating a Convolution to extract features
model <- keras_model_sequential() %>%
layer_conv_2d(input_shape = c(28, 28, 1), filters = 64, kernel_size = c(3,3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
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 covnet which maps the learned features to the given labels
model <- model %>%
layer_flatten() %>%
layer_dense(units = 128, activation = 'relu') %>%
layer_dense(units = 10, activation = 'softmax')
# Configuring a Keras model for training
model %>% compile(
loss = 'sparse_categorical_crossentropy',
metrics = c('accuracy'),
optimizer = optimizer_adam()
)
# Training the NN and attaching a callback
history <- model %>%
fit(x = training_images,
y = training_labels,
epochs = 5,
# Attach the callback to model training
callbacks = list(callback)
)
history
# Evaluating the model
metrics <- model %>% evaluate(
x = test_images,
y = test_labels
)
cat("Test loss", metrics$loss, "\n")
cat("Test accuracy", metrics$acc, "\n")
By now, you know the drill. Laurence leaves quite a neat exercise at the end of each episode which helps one implement the concept learnt here to a new dataset. The exercise for this episode can be found here: Exercise 3. Its solution will be given in the next episode but I bet it will be something close to this.
# my solution
# improve MNIST to 99.8% accuracy
# using only a single convolutional layer and a single MaxPooling 2D.
# You should stop training once the accuracy goes above this amount.
# It should happen in less than 20 epochs.
# When 99.8% accuracy has been hit print out the string "Reached 99.8% accuracy so cancelling training!"
# ======================================================================
library(R6)
# define custom callback class
train_stop <- R6::R6Class("train_stop",
inherit = KerasCallback,
public = list(
on_epoch_end = function(epoch, logs = list()){
if(logs$'acc'>0.998){
self$model$stop_training = TRUE
paste0("Reached 99.8% accuracy so cancelling training!")
}
}
))
# ======================================================================
# loading the required packages
library(keras)
# creates an instance of the callback
callback <- train_stop$new()
# importing the datasets directly from Keras
mnist <- dataset_mnist()
c(training_images, training_labels) %<-% mnist$train
c(test_images, test_labels) %<-% mnist$test
# Reshaping our image arrays and normalizing them
training_images <- array_reshape(training_images, c(60000, 28, 28, 1))
training_images <- training_images/255
test_images <- array_reshape(test_images, c(10000, 28, 28, 1))
test_images <- test_images/255
# Instantiating a Convolution to extract features
model <- keras_model_sequential() %>%
layer_conv_2d(input_shape = c(28, 28, 1), filters = 128, kernel_size = c(3,3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2,2))
# Adding a classifier to the covnet which maps the learned features to the given labels
model <- model %>%
layer_flatten() %>%
layer_dense(units = 128, activation = 'relu') %>%
layer_dense(units = 10, activation = 'softmax')
# Configuring a Keras model for training
model %>% compile(
loss = 'sparse_categorical_crossentropy',
metrics = c('accuracy'),
optimizer = optimizer_adam()
)
# Training the NN and attaching a callback
history <- model %>%
fit(x = training_images,
y = training_labels,
epochs = 10,
# Attach the callback to model training
callbacks = list(callback)
)
history
# Evaluating the model
metrics <- model %>% evaluate(
x = test_images,
y = test_labels
)
cat("Test loss", metrics$loss, "\n")
cat("Test accuracy", metrics$acc, "\n")
Yes! We dit it!! 🤩 We successfully built a model which implements convolutions and pooling to improve the accuracy of computer vision using R. Indeed R, at its core is a beautiful and elegant language, well designed for Data Science 💖.
LS0tDQojIHRpdGxlOiAiSW1wcm92aW5nIENvbXB1dGVyIFZpc2lvbiBBY2N1cmFjeSB1c2luZyBDb252b2x1dGlvbnMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KLS0tDQoNCg0KIyAqKkltcHJvdmluZyBDb21wdXRlciBWaXNpb24gQWNjdXJhY3kgdXNpbmcgQ29udm9sdXRpb25zOioqIEFuIFIgVmVyc2lvbg0KDQpIZWxsbyEgV2VsY29tZSB0byB0aGUgZm91cnRoIChpbmNsdWRlcyB0aGUgM3JkKSAqKlIqKiBjb2RlIHdhbGt0aHJvdWdoIG9mIHRoZSBzZXNzaW9uICoqKk1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnMqKiogd2hlcmUgdGhlIGF3ZXNvbWUgW0xhdXJlbmNlIE1vcm9uZXldKGh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9pbi9sYXVyZW5jZS1tb3JvbmV5KSxhIERldmVsb3BlciBBZHZvY2F0ZSBhdCBHb29nbGUgd29ya2luZyBvbiBBcnRpZmljaWFsIEludGVsbGlnZW5jZSwgdGFrZXMgdXMgdGhyb3VnaCB0aGUgZnVuZGFtZW50YWxzIG9mIGJ1aWxkaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHVzaW5nIFRlbnNvckZsb3cuDQoNCg0KSW4gdGhpcyBlcGlzb2RlLCBbRXBpc29kZSA0XShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWRkOEg0ZmlMOVljJnQ9MXMpLCBMYXVyZW5jZSBNb3JvbmV5IHRha2VzIHVzIHRocm91Z2ggeWV0IGFub3RoZXIgZXhjaXRpbmcgYXBwbGljYXRpb24gb2YgTWFjaGluZSBMZWFybmluZy4NCkhlcmUsIHdlIHB1dCBjb252b2x1dGlvbnMgYW5kIHBvb2xpbmcgdG8gdXNlIGFuZCBjcmVhdGUgb3VyIG93biBjb252b2x1dGlvbmFsIG5ldXJhbCBuZXR3b3JrcyEgVGhpcyBtYWtlcyB0aGUgRmFzaGlvbiBNTklTVCBuZXVyYWwgbmV0d29yayBtb3JlIGVmZmljaWVudCAtLSBiZWNhdXNlIGl0IHdpbGwgY2xhc3NpZnkgYmFzZWQgb24gZmVhdHVyZXMsIGFuZCBub3Qgb24gcmF3IHBpeGVscy4gQ29udm9sdXRpb25hbCBsYXllcnMgbGVhcm4gdGhlIGZlYXR1cmVzIGFuZCBwYXNzIHRoZXNlIHRvIHRoZSBkZW5zZSBsYXllcnMgd2hpY2ggbWFwIHRoZSBsZWFybmVkIGZlYXR1cmVzIHRvIHRoZSBnaXZlbiBsYWJlbHMuDQoNCkxpa2UgdGhlIHByZXZpb3VzIFtSIE5vdGVib29rc10ocnB1YnMuZVJfaWMpLCB0aGlzIE5vdGVib29rIHRyaWVzIHRvIHJlcGxpY2F0ZSB0aGUgW1B5dGhvbiBOb3RlYm9va10oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWI0LVVzaW5nLUNvbnZvbHV0aW9ucy5pcHluYikgdXNlZCBmb3IgdGhpcyBlcGlzb2RlLg0KDQoNCkJlZm9yZSB3ZSBiZWdpbiwgSSBoaWdobHkgcmVjb21tZW5kIHRoYXQgeW91IGZvbGxvdyBbRXBpc29kZSAzXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PVBDZ0xtemtSTTM4JnQ9MSkgYW5kIFtFcGlzb2RlIDRdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZGQ4SDRmaUw5WWMmdD0xcykgd2hlcmUgTGF1cmVuY2UgTW9yb25leSBkZW15c3RpZmllcyB0aGUgY29uY2VwdHMgb2YgKipjb252b2x1dGlvbioqIGFuZCAqKnBvb2xpbmcqKiBpbiBjb21wdXRlciB2aXNpb24uIEkgd2lsbCB0cnkgYW5kIGhpZ2hsaWdodCBzb21lIG9mIHRoZSBzdHVmZiBMYXVyZW5jZSBNb3JvbmV5IHNhaWQgYW5kIGFkZCBzb21lIG9mIG15IG93biBmb3IgdGhlIHNha2Ugb2YgY29tcGxldGVuZXNzIG9mIHRoaXMgcG9zdCBidXQgSSBoaWdobHkgcmVjb21tZW5kIHlvdSBsaXN0ZW4gZnJvbSBoaW0gZmlyc3QuDQoNCg0KIyAqKkEgaGlnaCBsZXZlbCBvdmVydmlldyBvZiBDb252b2x1dGlvbiBhbmQgUG9vbGluZyoqDQoNCkEgYGNvbnZvbHV0aW9uYCBpcyBhIGZpbHRlciB0aGF0IHBhc3NlcyBvdmVyIGFuIGltYWdlLCBwcm9jZXNzaW5nIGl0LCBhbmQgZXh0cmFjdGluZyBmZWF0dXJlcyB0aGF0IHNob3cgYSBjb21tb25vbGF0aXR5IGluIHRoZSBpbWFnZSBzdWNoIHRoYXQgaWYgYW4gaW1hZ2UgaGFzIGNlcnRhaW4gZmVhdHVyZXMsIGl0IGJlbG9uZ3MgdG8gYSBwYXJ0aWN1bGFyIGNsYXNzLiBBdCBpdHMgaGVhcnQsIGNvbnZvbHV0aW9uIGlzIHJlYWxseSBzaW1wbGUuIEl0IGludm9sdmVzIHNjYW5uaW5nIGV2ZXJ5IHBpeGVsIGluIHRoZSBpbWFnZSwgbG9va2luZyBhdCBpdCdzIG5laWdoYm9yaW5nIHBpeGVscywgbXVsdGlwbHlpbmcgdGhlc2UgcGl4ZWxzIGJ5IHRoZWlyIGNvcnJlc3BvbmRpbmcgd2VpZ2h0IGluIGEgZmlsdGVyIGFuZCB0aGVuIHN1bW1pbmcgdGhpcyBhbGwgdXAgdG8gb2J0YWluIGEgbmV3IHBpeGVsIHZhbHVlLiBUaGlzIGNhbiBiZSBzaG93biBhcyBiZWxvdzoNCg0KYGBge3IsZWNobz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9BRE1JTi9EZXNrdG9wL0ludG9kdWN0aW9uIHRvIFB5dGhvbiBmb3IgZGF0YSBzY2llbmNlL1IgZm9yIGRhdGEgc2NpZW5jZS9hUmR1aW5vL3I0Zy9jb252b2x1dGlvbi5KUEciKQ0KcHJpbnQoIkltYWdlIHNvdXJjZTogaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWIzLVdoYXQtQXJlLUNvbnZvbHV0aW9ucy5pcHluYiNzY3JvbGxUbz14RjBGUHBsc2dITmgiLCBxdW90ZSA9IEYpDQoNCmBgYA0KDQoNCg0KVGhlIHByZXZpb3VzIGBEZW5zZSBOZXVyYWwgTmV0d29ya2AgdGhhdCB3ZSBjcmVhdGVkIHNpbXBseSBsZWFybmVkIGZyb20gdGhlIHJhdyBwaXhlbHMgd2hhdCBtYWRlIHVwIGEgc3dlYXRlciBvciB3aGF0IG1hZGUgdXAgYSBib290LiBUaGlzIGluIGl0c2VsZiBpcyBxdWl0ZSBhIGxpbWl0YXRpb24uIA0KDQpVbHRpbWF0ZWx5IHRoZSBnb2FsIG9mIHRyeWluZyB0byB1bmRlcnN0YW5kIHdoYXQgYW4gaXRlbSBpcywgaXNuJ3QganVzdCBtYXRjaGluZyB0aGUgcmF3IHBpeGVscyB0byBsYWJlbHMgbGlrZSB3ZSBkaWQgaW4gdGhlIHByZXZpb3VzIGV4ZXJjaXNlcy4NCg0KV2hhdCBpZiB3ZSBjb3VsZCBleHRyYWN0IGZlYXR1cmVzIGZyb20gdGhlIGltYWdlIGluc3RlYWQgYW5kIHdoZW4gYW4gaW1hZ2UgaGFzIHNvbWUgc3BlY2lmaWMgZmVhdHVyZXMsIGl0IGJlbG9uZ3MgdG8gYSBwYXJ0aWN1bGFyIGNsYXNzLiBUaGlzIGlzIHRoZSBoZWFydCBvZiBDb252b2x1dGlvbiBOZXVyYWwgTmV0d29ya3MgZG8uDQoNClRoaXMga2V5IGNoYXJhY3RlcmlzdGljIGdpdmVzIGNvbnZuZXRzIHR3byBpbnRlcmVzdGluZyBwcm9wZXJ0aWVzOg0KDQoqICpUaGUgcGF0dGVybnMgdGhleSBsZWFybiBhcmUgdHJhbnNsYXRpb24taW52YXJpYW50OioNClRoaXMgbWVhbnMgdGhhdCBhZnRlciBsZWFybmluZyBhIGNlcnRhaW4gcGF0dGVybiwgYSBjb3ZuZXQgY2FuIHJlY29nbml6ZSBpdCBhbnl3aGVyZSBlbHNlIGluIHRoZSBpbWFnZSBhcyBvcHBvc2VkIHRvIGEgRE5OIHdoaWNoIHdvdWxkIGhhdmUgdG8gbGVhcm4gdGhlIHBhdHRlcm4gYSBuZXcgaWYgaXQgYXBwZWFyZWQgYXQgYSBuZXcgbG9jYXRpb24uIEZvciB0aGlzIHJlYXNvbiwgY292bmV0cyByZXF1aXJlIGZldyB0cmFpbmluZyBzYW1wbGVzLg0KDQoqICpUaGV5IGNhbiBsZWFybiBzcGF0aWFsIGhpZXJhcmNoaWVzIG9mIHBhdHRlcm5zOioNCkEgZmlyc3QgY29udm9sdXRpb24gbGF5ZXIgd2lsbCBsZWFybiBzbWFsbCBsb2NhbCBwYXR0ZXJucyBzdWNoIGFzIGVkZ2VzLCBhIHNlY29uZCBjb252b2x1dGlvbiBsYXllciB3aWxsIGxlYXJuIGxhcmdlciBwYXR0ZXJucyBtYWRlIG9mIHRoZSBmZWF0dXJlcyBvZiB0aGUgZmlyc3QgbGF5ZXJzLCBhbmQgc28gb24uIEZvciB0aGlzIHJlYXNvbiwgY292bmV0cyBjYW4gbGVhcm4gaW5jcmVhc2luZ2x5IGNvbXBsZXggYW5kIGFic3RyYWN0IGZlYXR1cmVzLg0KDQoNCklmIHlvdSd2ZSBldmVyIGRvbmUgaW1hZ2UgcHJvY2Vzc2luZyB1c2luZyBhIGZpbHRlciAobGlrZSB0aGlzOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9LZXJuZWxfKGltYWdlX3Byb2Nlc3NpbmcpKSB0aGVuIGNvbnZvbHV0aW9ucyB3aWxsIGxvb2sgdmVyeSBmYW1pbGlhci4NCg0KSW4gc2hvcnQsIHlvdSB0YWtlIGFuIGFycmF5ICh1c3VhbGx5IDN4MyBvciA1eDUpIGFuZCBwYXNzIGl0IG92ZXIgdGhlIGltYWdlLiBCeSBjaGFuZ2luZyB0aGUgdW5kZXJseWluZyBwaXhlbHMgYmFzZWQgb24gdGhlIGZvcm11bGEgd2l0aGluIHRoYXQgbWF0cml4LCB5b3UgY2FuIGRvIHRoaW5ncyBsaWtlIGVkZ2UgZGV0ZWN0aW9uLiBTbywgZm9yIGV4YW1wbGUsIGlmIHlvdSBsb29rIGF0IHRoZSBhYm92ZSBsaW5rLCB5b3UnbGwgc2VlIGEgM3gzIHRoYXQgaXMgZGVmaW5lZCBmb3IgZWRnZSBkZXRlY3Rpb24gd2hlcmUgdGhlIG1pZGRsZSBjZWxsIGlzIDgsIGFuZCBhbGwgb2YgaXRzIG5laWdoYm9ycyBhcmUgLTEuIEluIHRoaXMgY2FzZSwgZm9yIGVhY2ggcGl4ZWwsIHlvdSB3b3VsZCBtdWx0aXBseSBpdHMgdmFsdWUgYnkgOCwgdGhlbiBzdWJ0cmFjdCB0aGUgdmFsdWUgb2YgZWFjaCBuZWlnaGJvci4gRG8gdGhpcyBmb3IgZXZlcnkgcGl4ZWwsIGFuZCB5b3UnbGwgZW5kIHVwIHdpdGggYSBuZXcgaW1hZ2UgdGhhdCBoYXMgdGhlIGVkZ2VzIGVuaGFuY2VkLg0KDQoNCg0KKlRoYXQncyB0aGUgY29uY2VwdCBvZiBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3Jrcy4gQWRkIHNvbWUgbGF5ZXJzIHRvIGRvIGNvbnZvbHV0aW9uIGJlZm9yZSB5b3UgaGF2ZSB0aGUgZGVuc2UgbGF5ZXJzLCBhbmQgdGhlbiB0aGUgaW5mb3JtYXRpb24gZ29pbmcgdG8gdGhlIGRlbnNlIGxheWVycyBpcyBtb3JlIGZvY3Vzc2VkLCBhbmQgcG9zc2libHkgbW9yZSBhY2N1cmF0ZS4qDQoNCg0KYFBvb2xpbmdgIHJlZHVjZXMgdGhlIGFtb3VudCBvZiBpcnJlbGV2YW50IGluZm9ybWF0aW9uIGluIGFuIGltYWdlIHdoaWxlIG1haW50YWluaW5nIHRoZSBmZWF0dXJlcyB0aGF0IGFyZSBkZXRlY3RlZC4gSXQgZG9lcyBzbyBieSBsb29raW5nIGF0IGEgcGl4ZWwgYW5kIGl0cyBpbW1lZGlhdGUgbmVpZ2hib3VycyB0byB0aGUgcmlnaHQsIGJlbmVhdGggYW5kIHJpZ2h0LWJlbmVhdGgsIHRha2VzIHRoZSBsYXJnZXN0IGhlbmNlIHRoZSBuYW1lIE1heCBwb29saW5nLCBhbmQgbG9hZHMgaXQgaW50byBhIG5ldyBpbWFnZS4gSXQgdGh1cyByZWR1Y2VzIHRoZSBhbW91bnQgb2YgaW5mb3JtYXRpb24gdGhhdCBhIG1vZGVsIGhhcyB0byBwcm9jZXNzIHdoaWxlIHN0aWxsIG1haW50YWluaW5nIHRoZSBwcm9taW5lbnQgZmVhdHVyZXMuDQoNCmBgYHtyLGVjaG89RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0KaW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMvQURNSU4vRGVza3RvcC9JbnRvZHVjdGlvbiB0byBQeXRob24gZm9yIGRhdGEgc2NpZW5jZS9SIGZvciBkYXRhIHNjaWVuY2UvYVJkdWluby9yNGcvcG9vbGluZy5KUEciKQ0KcHJpbnQoIkltYWdlIHNvdXJjZTogaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWIzLVdoYXQtQXJlLUNvbnZvbHV0aW9ucy5pcHluYiNzY3JvbGxUbz14RjBGUHBsc2dITmgiLCBxdW90ZSA9IEYpDQoNCmBgYA0KDQoNCg0KIyMjICoqQnVpbGRpbmcgQ29udm9sdXRpb24gTmV1cmFsIE5ldHdvcmtzLioqDQoNCg0KIyAqKkdhdGhlcmluZyB0aGUgRGF0YSoqDQoNCkxldCdzIHN0YXJ0IGJ5IGxvYWRpbmcgdGhlIGxpYnJhcmllcyByZXF1aXJlZCBmb3IgdGhpcyBzZXNzaW9uLg0KDQpXZSdsbCBiZSByZXF1aXJpbmcgc29tZSBwYWNrYWdlcyBpbiB0aGUgVGlkeXZlcnNlIGFuZCBLZXJhcyhhIGZyYW1ld29yayBmb3IgZGVmaW5pbmcgYSBuZXVyYWwgbmV0d29yayBhcyBhIHNldCBvZiBTZXF1ZW50aWFsIGxheWVycykuIFlvdSBjYW4gaGF2ZSB0aGVtIGluc3RhbGxlZCBhcyBmb2xsb3dzDQoNCmBgYA0Kc3VwcHJlc3NNZXNzYWdlcyhpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKSkNCnN1cHByZXNzTWVzc2FnZXMoaW5zdGFsbC5wYWNrYWdlcygia2VyYXMiKSkNCnN1cHByZXNzTWVzc2FnZXMoaW5zdGFsbF9rZXJhcygpKQ0KDQpQczogaXQgY291bGQgdGFrZSBhIHdoaWxlDQpgYGANCk9uY2UgaW5zdGFsbGVkLCBsZXQncyBnZXQgcm9sbGluZzoNCg0KYGBge3J9DQpsaWJyYXJ5KGtlcmFzKQ0KDQptbmlzdCA8LSBkYXRhc2V0X2Zhc2hpb25fbW5pc3QoKQ0KDQpjKHRyYWluaW5nX2ltYWdlcywgdHJhaW5pbmdfbGFiZWxzKSAlPC0lIG1uaXN0JHRyYWluDQpjKHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscykgJTwtJSBtbmlzdCR0ZXN0DQoNCiMgdGhlIHRyYWluX2ltYWdlcyBhbmQgdHJhaW5fbGFiZWxzIGFycmF5cyBhcmUgdGhlIHRyYWluaW5nIHNldA0KIyAodGhlIGRhdGEgdGhlIG1vZGVsIHVzZXMgdG8gbGVhcm4pLiANCg0KDQojIFRoZSBtb2RlbCBpcyB0ZXN0ZWQgYWdhaW5zdCB0aGUgdGVzdCBzZXQ6IA0KIyB0aGUgdGVzdF9pbWFnZXMsIGFuZCB0ZXN0X2xhYmVscyBhcnJheXMuDQpgYGANCg0KDQoqKlJlc2hhcGluZyBvdXIgaW1hZ2UgYXJyYXlzIGFuZCBub3JtYWxpemluZyB0aGVtKioNCg0KDQpgYGB7cn0NCnRyYWluaW5nX2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRyYWluaW5nX2ltYWdlcywgYyg2MDAwMCwgMjgsIDI4LCAxKSkNCg0KdHJhaW5pbmdfaW1hZ2VzIDwtIHRyYWluaW5nX2ltYWdlcy8yNTUNCg0KdGVzdF9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0ZXN0X2ltYWdlcywgYygxMDAwMCwgMjgsIDI4LCAxKSkNCnRlc3RfaW1hZ2VzIDwtIHRlc3RfaW1hZ2VzLzI1NQ0KDQojIHRoZSB2YWx1ZXMgNjAsMDAwIGFuZCAxMCwwMDAgYXJlIG5vdCBhcmJpdHJhcnksIHdlIG9idGFpbmVkIHRoZW0gdXNpbmcNCmRpbSh0cmFpbmluZ19pbWFnZXMpDQoNCiMgNjAsMDAwIGltYWdlcyBpbiB0aGUgdHJhaW5pbmcgc2V0LA0KIyB3aXRoIGVhY2ggaW1hZ2UgcmVwcmVzZW50ZWQgYXMgMjggeCAyOCBwaXhlbHMgYW5kIDEgZGVwdGggb2YgMShiaW5hcnkgaW1hZ2UpDQpgYGANCg0KV2h5IGFyZSB3ZSBhZGRpbmcgb25lIG1vcmUgZGltZW5zaW9uPyBUaGF0J3MgYW4gaW1wb3J0YW50IHF1ZXN0aW9uLg0KQ29udm9sdXRpb25zIG9wZXJhdGUgb3ZlciAzRCB0ZW5zb3JzLCBjYWxsZWQgZmVhdHVyZSBtYXBzLCB3aXRoIHR3byBzcGF0aWFsIGF4ZXMgKGhlaWdodCB3aWR0aCkgYXMgd2VsbCBhcyBhIGRlcHRoIGF4aXMuIEZvciBhbiBSR0IgZGVwdGggY2hhbm5lbHMgaW1hZ2UsIHRoZSBkaW1lbnNpb24gb2YgdGhlIGRlcHRoIGF4aXMgaXMgMywgYmVjYXVzZSB0aGUgaW1hZ2UgaGFzIDMgY29sb3IgY2hhbm5lbHM6IHJlZCwgZ3JlZW4sIGFuZCBibHVlLiBGb3IgYSBibGFjay1hbmQtd2hpdGUgcGljdHVyZSwgbGlrZSB0aGUgRmFzaGlvbiBNTklTVCBkYXRhc2V0LCB0aGUgZGVwdGggaXMgMS4NCg0KDQoNCg0KIyAqKkluc3RhbnRpYXRpbmcgYSBDb252b2x1dGlvbioqDQoNCk5leHQgaXMgdG8gZGVmaW5lIHlvdXIgbW9kZWwuIE5vdyBpbnN0ZWFkIG9mIHRoZSBpbnB1dCBsYXllciBhdCB0aGUgdG9wLCB3ZSdyZSBnb2luZyB0byBhZGQgYSBDb252b2x1dGlvbi4gVGhlIHBhcmFtZXRlcnMgYXJlOg0KDQoxLiBUaGUgbnVtYmVyIG9mIGNvbnZvbHV0aW9ucyB5b3Ugd2FudCB0byBnZW5lcmF0ZS4gUHVyZWx5IGFyYml0cmFyeSwgYnV0IGdvb2QgdG8gc3RhcnQgd2l0aCBzb21ldGhpbmcgaW4gdGhlIG9yZGVyIG9mIDMyDQoNCjIuIFRoZSBzaXplIG9mIHRoZSBDb252b2x1dGlvbiwgaW4gdGhpcyBjYXNlIGEgM3gzIGdyaWQNCg0KMy4gVGhlIGFjdGl2YXRpb24gZnVuY3Rpb24gdG8gdXNlIC0tIGluIHRoaXMgY2FzZSB3ZSdsbCB1c2UgcmVsdSwgd2hpY2ggeW91IG1pZ2h0IHJlY2FsbCBpcyB0aGUgZXF1aXZhbGVudCBvZiByZXR1cm5pbmcgeCB3aGVuIHg+MCwgZWxzZSByZXR1cm5pbmcgMA0KDQpXZSdsbCBmb2xsb3cgdGhlIENvbnZvbHV0aW9uIHdpdGggYSBNYXhQb29saW5nIGxheWVyIHdoaWNoIGlzIHRoZW4gZGVzaWduZWQgdG8gY29tcHJlc3MgdGhlIGltYWdlLCB3aGlsZSBtYWludGFpbmluZyB0aGUgY29udGVudCBvZiB0aGUgZmVhdHVyZXMgdGhhdCB3ZXJlIGhpZ2hsaWdodGVkIGJ5IHRoZSBjb252bHV0aW9uLiBCeSBzcGVjaWZ5aW5nICgyLDIpIGZvciB0aGUgTWF4UG9vbGluZywgdGhlIGVmZmVjdCBpcyB0byBxdWFydGVyIHRoZSBzaXplIG9mIHRoZSBpbWFnZS4gV2l0aG91dCBnb2luZyBpbnRvIHRvbyBtdWNoIGRldGFpbCBoZXJlLCB0aGUgaWRlYSBpcyB0aGF0IGl0IGNyZWF0ZXMgYSAyeDIgYXJyYXkgb2YgcGl4ZWxzLCBhbmQgcGlja3MgdGhlIGJpZ2dlc3Qgb25lLCB0aHVzIHR1cm5pbmcgNCBwaXhlbHMgaW50byAxLiBJdCByZXBlYXRzIHRoaXMgYWNyb3NzIHRoZSBpbWFnZSwgYW5kIGluIHNvIGRvaW5nIGhhbHZlcyB0aGUgbnVtYmVyIG9mIGhvcml6b250YWwsIGFuZCBoYWx2ZXMgdGhlIG51bWJlciBvZiB2ZXJ0aWNhbCBwaXhlbHMsIGVmZmVjdGl2ZWx5IHJlZHVjaW5nIHRoZSBpbWFnZSBieSAyNSUuIFRoZXNlIGNvbmNlcHRzIGFyZSBjbGVhcmx5IGV4cGxhaW5lZCBpbiBbRXBpc29kZSAzXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PVBDZ0xtemtSTTM4JnQ9MSkuDQoNCg0KYGBge3J9DQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIA0KICAjIGFkZGluZyB0aGUgZmlyc3QgY29udm9sdXRpb24gbGF5ZXIgd2l0aCA2NCAzYnkzIGZpbHRlcg0KICAjIHdlIGFkZCBhIGNvbG9yIGRlcHRoIG9mIDEgc2luY2UgY29udm9sdXRpb25zIG9wZXJhdGUgb3ZlciAzRCB0ZW5zb3JzDQogIGxheWVyX2NvbnZfMmQoaW5wdXRfc2hhcGUgPSBjKDI4LCAyOCwgMSksIGZpbHRlcnMgPSA2NCwga2VybmVsX3NpemUgPSBjKDMsMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JSANCiAgIyBhZGRpbmcgYSBtYXggcG9vbGluZyBsYXllciB3aGljaCBoYWx2ZXMgdGhlIGRpbWVuc2lvbnMNCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKSAlPiUgDQogICMgYWRkaW5nIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdoaWNoIGZpbHRlcnMgdGhlIHJlc3VsdHMNCiAgIyBmcm9tIHRoZSBwcmV2aW91cyBsYXllcg0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSA2NCwga2VybmVsX3NpemUgPSBjKDMsMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JSAjIGFkZGluZyBhIHBvb2xpbmcgbGF5ZXINCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKQ0KICANCmBgYA0KDQoNCg0KDQojICoqQWRkaW5nIGEgY2xhc3NpZmllciB0byB0aGUgY292bmV0KioNCkNvbnZvbHV0aW9uYWwgbGF5ZXJzIGxlYXJuIHRoZSBmZWF0dXJlcyBhbmQgcGFzcyB0aGVzZSB0byB0aGUgZGVuc2UgbGF5ZXJzIHdoaWNoIG1hcCB0aGUgbGVhcm5lZCBmZWF0dXJlcyB0byB0aGUgZ2l2ZW4gbGFiZWxzLiBUaGVyZWZvcmUsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gZmVlZCB0aGUgbGFzdCBvdXRwdXQgdGVuc29yIGludG8gYSBkZW5zZWx5DQpjb25uZWN0ZWQgY2xhc3NpZmllciBuZXR3b3JrIGxpa2UgdGhvc2Ugd2XigJlyZSBhbHJlYWR5IGZhbWlsaWFyIHdpdGg6IGEgc3RhY2sgb2YgZGVuc2UgbGF5ZXJzLg0KVGhlc2UgY2xhc3NpZmllcnMgcHJvY2VzcyB2ZWN0b3JzLCB3aGljaCBhcmUgMUQsIHdoZXJlYXMgdGhlIGN1cnJlbnQgb3V0cHV0IGlzIGEgM0QgdGVuc29yLiBGaXJzdCB3ZSBoYXZlIHRvIGZsYXR0ZW4gdGhlIDNEIG91dHB1dHMgdG8gMUQsIGFuZCB0aGVuIGFkZCBhIGZldyBkZW5zZSBsYXllcnMgb24gdG9wLg0KDQpgYGB7cn0NCm1vZGVsIDwtIG1vZGVsICU+JSANCiAgIyBOb3cgZmxhdHRlbiB0aGUgb3V0cHV0Lg0KICBsYXllcl9mbGF0dGVuKCkgJT4lIA0KICAjIEFmdGVyIHRoaXMgeW91J2xsIGp1c3QgaGF2ZSB0aGUgc2FtZSBETk4gc3RydWN0dXJlIGFzIHRoZSBub24gY29udm9sdXRpb25hbCB2ZXJzaW9uDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUgDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAnc29mdG1heCcpDQoNCmBgYA0KDQoqKkNvbXBpbGU6KiogQ29uZmlndXJpbmcgYSBLZXJhcyBtb2RlbCBmb3IgdHJhaW5pbmcNCg0KYGBge3J9DQptb2RlbCAlPiUgY29tcGlsZSgNCiAgbG9zcyA9ICdzcGFyc2VfY2F0ZWdvcmljYWxfY3Jvc3NlbnRyb3B5JywNCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX2FkYW0oKSwNCiAgbWV0cmljcyA9IGMoJ2FjY3VyYWN5JykNCg0KKQ0KbW9kZWwgJT4lIHN1bW1hcnkoKQ0KYGBgDQpGcm9tIHRoZSBzdW1tYXJ5LCB3ZSBleHBlY3Qgb3V0cHV0IG9mIHRoZSBmaXJzdCBjb252b2x1dGlvbiB0byBiZSBhIDI4eDI4IGJ1dCB3ZSBvYnRhaW4gYSAyNngyNi4gSWYgeW91IGhhdmUgd2F0Y2hlZCB0aGUgZXBpc29kZSwgeW91IHByb2JhYmx5IGtub3cgd2h5LiBBIDMgYnkgMyBmaWx0ZXIgcmVxdWlyaW5nIGEgbmVpZ2hib3VyIG9uIGFsbCBzaWRlcyBjYW4ndCB3b3JrIG9uIHRoZSBwaXhlbHMgYXJvdW5kIHRoZSBlZGdlcyBvZiB0aGUgcGljdHVyZS4gWW91IGVmZmVjdGl2ZWx5IGhhdmUgdG8gcmVtb3ZlIG9uZSBwaXhlbCBmcm9tIHRoZSB0b3AsYm90dG9tIGxlZnQgYW5kIHJpZ250IGFuZCB0aGlzIHJlZHVjZXMgeW91ciBkaW1lbnNpb24gYnkgMiBvbiBlYWNoIGF4aXMuIFNvIGEgMjggYnkgMjggYmVjb21lcyBhIDI2IGJ5IDI2Lg0KQWxzbywgdGhlIHBvb2xpbmcgbGF5ZXIgY2xlYXJseSBoYWx2ZXMgdGhlIGRpbWVuc2lvbnMgb2YgZWFjaCBheGlzLg0KDQoNCg0KIyAqKlRyYWluaW5nIHRoZSBOZXVyYWwgTmV0d29yayoqDQoNClRoaXMgaXMgdGhlIHByb2Nlc3Mgb2YgdHJhaW5pbmcgdGhlIG5ldXJhbCBuZXR3b3JrLCB3aGVyZSBpdCAnbGVhcm5zJyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHRyYWluX2ltYWdlcyBhbmQgdHJhaW5fbGFiZWxzIGFycmF5cy4NCg0KVG8gc3RhcnQgdHJhaW5pbmcsIGNhbGwgdGhlIGZpdCBtZXRob2Qg4oCUIHRoZSBtb2RlbCBpcyDigJxmaXTigJ0gdG8gdGhlIHRyYWluaW5nIGRhdGEgZm9yIGEgZml4ZWQgbnVtYmVyIG9mIGVwb2Nocy4NCg0KYGBge3J9DQpoaXN0b3J5IDwtIG1vZGVsICU+JSANCiAgZml0KA0KICAgIHggPSB0cmFpbmluZ19pbWFnZXMsDQogICAgeSA9IHRyYWluaW5nX2xhYmVscywNCiAgICBlcG9jaHMgPSA1DQogICkNCg0KaGlzdG9yeQ0KYGBgDQoNCg0KDQoNCiMgKipFdmFsdWF0aW5nIHRoZSBtb2RlbCoqDQoNClRoaXMgaXMgdGhlIHN0ZXAgd2hlcmUgd2UgZXZhbHVhdGUgaG93IGFjY3VyYXRlbHkgdGhlIG5ldHdvcmsgbGVhcm50IHRvIGNsYXNzaWZ5IHRoZSBpbWFnZXMgdXNpbmcgdGhlIHRlc3Rfc2V0DQoNCmBgYHtyfQ0KbWV0cmljcyA8LSBtb2RlbCAlPiUgZXZhbHVhdGUoeCA9IHRlc3RfaW1hZ2VzLCB5ID0gdGVzdF9sYWJlbHMpDQpjYXQoInRlc3RfbG9zczoiLCBtZXRyaWNzJGxvc3MsICJcbiIpDQpjYXQoInRlc3RfYWNjdXJhY3kiLCBtZXRyaWNzJGFjYywgIlxuIikNCmBgYA0KDQoNCkl0J3MgbGlrZWx5IGdvbmUgdXAgdG8gYWJvdXQgOTMlIG9uIHRoZSB0cmFpbmluZyBkYXRhIGFuZCA5MSUgb24gdGhlIHZhbGlkYXRpb24gZGF0YS4NCg0KVGhhdCdzIHNpZ25pZmljYW50LCBhbmQgYSBzdGVwIGluIHRoZSByaWdodCBkaXJlY3Rpb24hDQoNClRyeSBydW5uaW5nIGl0IGZvciBtb3JlIGVwb2NocyAtLSBzYXkgYWJvdXQgMjAsIGFuZCBleHBsb3JlIHRoZSByZXN1bHRzISBCdXQgd2hpbGUgdGhlIHJlc3VsdHMgbWlnaHQgc2VlbSByZWFsbHkgZ29vZCwgdGhlIHZhbGlkYXRpb24gcmVzdWx0cyBtYXkgYWN0dWFsbHkgZ28gZG93biwgZHVlIHRvIHNvbWV0aGluZyBjYWxsZWQgJ292ZXJmaXR0aW5nJyB3aGljaCB3aWxsIGJlIGRpc2N1c3NlZCBsYXRlci4NCk92ZXJmaXR0aW5nIG9jY3VycyB3aGVuIGEgbW9kZWwgaXMgdHJhaW5lZCBzbyBpbnRlbnNlbHkgb24gdHJhaW5pbmcgZGF0YSB0aGF0IGl0IGJlY29tZXMgc28gZmFtaWxpYXIgdG8gdGhlIHBhcnRpY3VsYXIgZGF0YXNldCB0byBhbiBleHRlbnQgdGhhdCBpdCBmYWlscyB0byBwcmVkaWN0IG5ldyBkYXRhIGNvcnJlY3RseS4NCg0KDQoNCg0KIyAqKlZpc3VhbGl6aW5nIGV2ZXJ5IGZpbHRlciBvdXRwdXQgaW4gZWFjaCBjb252b2x1dGlvbiBhbmQgcG9vbGluZyBsYXllcioqDQoNCkZvciB0aGlzIHN0ZXAsIEkgdG9vayBhIGxpdHRsZSBkZXRvdXIgZnJvbSB3aGF0IHdhcyBkb25lIGluIHRoZSBQeXRob24gTm90ZWJvb2sgYnV0IGl0IGlsbHVzdHJhdGVzIHRoZSBzYW1lIGNvbmNlcHQ6IGhvdyB0aGUgY29udm9sdXRpb25zIGFwcGx5IGRpZmZlcmVudCBmaWx0ZXJzIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSBvdXIgaW5wdXQgaW1hZ2VzLg0KDQpgYGB7cn0NCiMgbGV0J3MgdmlzdWFsaXplIHRoZSBmaWx0ZXJzIGFwcGxpZWQgYnkgdGhlIGNvbnZvbHV0aW9ucyB1c2luZyBvdXINCiMgZmlyc3QgaW1hZ2UNCg0KaW1nX3RlbnNvciA8LSB0ZXN0X2ltYWdlc1sxLCAsICxdDQoNCiMgbGV0J3MgdmlldyBvdXIgZmlyc3QgaW1hZ2UNCnBsb3QoYXMucmFzdGVyKGltZ190ZW5zb3IpKQ0KDQojIGV4dHJhY3RpbmcgdGhlIG91dHB1dHMgb2YgdGhlIHRvcCA0IGxheWVycw0KbGF5ZXJfb3V0cHV0IDwtIGxhcHBseShtb2RlbCRsYXllcnNbMTo0XSwgZnVuY3Rpb24obGF5ZXIpIGxheWVyJG91dHB1dCkNCg0KDQojIENyZWF0ZXMgYSBtb2RlbCB0aGF0IHdpbGwgcmV0dXJuIHRoZXNlIG91dHB1dHMsIGdpdmVuIHRoZSBtb2RlbCBpbnB1dA0KIyBUaGlzIG1vZGVsIGhhcyBvbmUgaW5wdXQgYW5kIGZvdXIgb3V0cHV0czoNCiMgb25lIG91dHB1dCBwZXIgbGF5ZXIgYWN0aXZhdGlvbi4NCmFjdGl2YXRpb25fbW9kZWwgPC0ga2VyYXNfbW9kZWwoaW5wdXRzID0gbW9kZWwkaW5wdXQsIG91dHB1dHMgPSBsYXllcl9vdXRwdXQpDQoNCg0KIyB1c2luZyB0aGUgbW9kZWwgdG8gcHJlZGljdCB0aGUgb3V0cHV0IG9mIHRoZSBmaWx0ZXJzIGluIHRoZSByZXBlY3RpdmUgbGF5ZXJzDQojIHdlIHN0aWxsIGhhdmUgdG8gcmVzaGFwZSBvdXIgaW1nX3RlbnNvciBzaW5jZSBjb252b2x1dGlvbnMgd29yayB3aXRoDQojIDNEIGlucHV0cw0KDQphY3RpdmF0aW9ucyA8LSBhY3RpdmF0aW9uX21vZGVsICU+JSBwcmVkaWN0KGFycmF5X3Jlc2hhcGUoaW1nX3RlbnNvciwgYygxLCAyOCwgMjgsIDEpKSkNCg0KYGBgDQoNCioqbGV0J3MgZXh0cmFjdCB0aGUgZmlyc3QgNCBjb252b2x1dGlvbiBhbmQgcG9vbGluZyBsYXllcnMqKg0KDQpgYGB7cn0NCmZpcnN0X2xheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbWzFdXQ0Kc2Vjb25kX2xheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbWzJdXQ0KdGhpcmRfbGF5ZXJfYWN0aXZhdGlvbiA8LSBhY3RpdmF0aW9uc1tbM11dDQpmb3VydGhfbGF5ZXJfYWN0aXZhdGlvbiA8LSBhY3RpdmF0aW9uc1tbNF1dDQoNCg0KZGltKGZpcnN0X2xheWVyX2FjdGl2YXRpb24pDQojIHRoZSBmaXJzdCBsYXllciBoYXMgNjQgZmlsdGVycyBhcyB3ZSBkZWZpbmVkIGVhcmxpZXINCmBgYA0KDQoNCk5leHQgd2UgZGVmaW5lIGEgZnVuY3Rpb24gdGhhdCB3aWxsIGhlbHAgdXMgdmlzdWFsaXNlIHRoZSByZXN1bHQgb2YgZWFjaCBmaWx0ZXIgaW4gZWFjaCBvZiB0aGUgbGF5ZXIgYWN0aXZhdGlvbnMgYWJvdmUuDQoNCmBgYHtyfQ0KcGxvdF9jaGFubmVsIDwtIGZ1bmN0aW9uKGNoYW5uZWwpIHsNCiAgIyByb3RhdGluZyB0aGUgaW1hZ2VzDQogIGltZyA8LSB0KGFwcGx5KGNoYW5uZWwsIDIsIHJldikpDQogIGltYWdlKGltZywgYXhlcyA9IEZBTFNFLCBhc3A9MSwgY29sID0gaGNsLmNvbG9ycygzMykpDQp9DQpgYGANCg0KDQoqKlZpc3VhbGl6aW5nIHRoZSBjb252b2x1dGlvbnMgYW5kIHBvb2xpbmcgb24gb3VyIGZpcnN0IHRlc3QgaW1hZ2UqKg0KDQpUaGUgb3V0cHV0IG9mIGVhY2ggZmlsdGVyIGluIGVhY2ggbGF5ZXIgaXMgYXMgc2hvd24gcm93LXdpc2UuDQpgYGB7cn0NCm9wIDwtIHBhcihtZnJvdyA9IGMoOCw4KSwgbWFpID0gYygwLjA1LCAwLCAwLCAwKSkNCmZvciAoaSBpbiAxOmRpbShmaXJzdF9sYXllcl9hY3RpdmF0aW9uKVs0XSkgew0KIHBsb3RfY2hhbm5lbChmaXJzdF9sYXllcl9hY3RpdmF0aW9uWzEsLCxpXSkNCiBwYXN0ZTAoImZpcnN0IGxheWVyIikNCn0NCmBgYA0KDQoNCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYyg4LDgpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKHNlY29uZF9sYXllcl9hY3RpdmF0aW9uKVs0XSkgew0KIHBsb3RfY2hhbm5lbChzZWNvbmRfbGF5ZXJfYWN0aXZhdGlvblsxLCwsaV0pDQogcGFzdGUwKCJzZWNvbmQgbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KYGBge3J9DQpvcCA8LSBwYXIobWZyb3cgPSBjKDgsOCksIG1haSA9IGMoMC4wNSwgMCwgMCwgMCkpDQpmb3IgKGkgaW4gMTpkaW0odGhpcmRfbGF5ZXJfYWN0aXZhdGlvbilbNF0pIHsNCiBwbG90X2NoYW5uZWwodGhpcmRfbGF5ZXJfYWN0aXZhdGlvblsxLCwsaV0pDQogcGFzdGUwKCJ0aGlyZCBsYXllciIpDQp9DQpgYGANCg0KYGBge3J9DQpvcCA8LSBwYXIobWZyb3cgPSBjKDgsOCksIG1haSA9IGMoMC4wNSwgMCwgMCwgMCkpDQpmb3IgKGkgaW4gMTpkaW0oZm91cnRoX2xheWVyX2FjdGl2YXRpb24pWzRdKSB7DQogcGxvdF9jaGFubmVsKGZvdXJ0aF9sYXllcl9hY3RpdmF0aW9uWzEsLCxpXSkNCiBwYXN0ZTAoImZvdXJ0aCBsYXllciIpDQp9DQpgYGANCg0KDQpHcmVhdCEgTm93IHRoZXJlIGFyZSBhIGZldyB0aGluZ3MgdG8gbm90ZToNCg0KKiBUaGUgZmlyc3QgbGF5ZXIgYWN0cyBhcyBhIGNvbGxlY3Rpb24gb2YgdmFyaW91cyBmaWx0ZXJzLiBBdCB0aGF0IHN0YWdlLCB0aGUgb3V0cHV0IG9mIHRoZSBsYXllciBzZWVtcyB0byByZXRhaW4gYWxtb3N0IGFsbCBvZiB0aGUgaW5mb3JtYXRpb24gcHJlc2VudCBpbiB0aGUgaW5pdGlhbCBwaWN0dXJlLg0KDQoqIEFzIHlvdSBnbyBoaWdoZXIsIHRoZSBvdXRwdXRzIG9mIHRoZSBsYXllcnMgYmVjb21lIGluY3JlYXNpbmdseSBhYnN0cmFjdCBhbmQgbGVzcyB2aXN1YWxseSBpbnRlcnByZXRhYmxlLiBUaGV5IGJlZ2luIHRvIGVuY29kZSBoaWdoZXItbGV2ZWwgY29uY2VwdHMgc3VjaCBhcyB0aGUgImhlZWwiIGFuZCAidmFtcCIgb2YgdGhlIHNob2UuDQpIaWdoZXIgcHJlc2VudGF0aW9ucyBjYXJyeSBpbmNyZWFzaW5nbHkgbGVzcyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgdmlzdWFsIGNvbnRlbnRzIG9mIHRoZSBpbWFnZSwgYW5kIGluY3JlYXNpbmdseSBtb3JlIGluZm9ybWF0aW9uIHJlbGF0ZWQgdG8gdGhlIGNsYXNzIG9mIHRoZSBpbWFnZS4NCg0KDQoNCg0KDQojICoqRXhlcmNpc2VzKioNCg0KVGhlIGZvbGxvd2luZyBleGVyY2lzZXMgYXJlIGZyb20gdGhlIFB5dGhvbiBOb3RlYm9vayB1c2VkIGZvciB0aGlzIHNlc3Npb24uDQoNCjEuIFRyeSBlZGl0aW5nIHRoZSBjb252b2x1dGlvbnMuIENoYW5nZSB0aGUgMzJzIHRvIGVpdGhlciAxNiBvciA2NC4gV2hhdCBpbXBhY3Qgd2lsbCB0aGlzIGhhdmUgb24gYWNjdXJhY3kgYW5kL29yIHRyYWluaW5nIHRpbWUuDQoNCjIuIFJlbW92ZSB0aGUgZmluYWwgQ29udm9sdXRpb24uIFdoYXQgaW1wYWN0IHdpbGwgdGhpcyBoYXZlIG9uIGFjY3VyYWN5IG9yIHRyYWluaW5nIHRpbWU/DQoNCjMuIEhvdyBhYm91dCBhZGRpbmcgbW9yZSBDb252b2x1dGlvbnM/IFdoYXQgaW1wYWN0IGRvIHlvdSB0aGluayB0aGlzIHdpbGwgaGF2ZT8gRXhwZXJpbWVudCB3aXRoIGl0Lg0KDQo0LiBSZW1vdmUgYWxsIENvbnZvbHV0aW9ucyBidXQgdGhlIGZpcnN0LiBXaGF0IGltcGFjdCBkbyB5b3UgdGhpbmsgdGhpcyB3aWxsIGhhdmU/IEV4cGVyaW1lbnQgd2l0aCBpdC4NCg0KNS4gSW4gdGhlIHByZXZpb3VzIGxlc3NvbiB5b3UgaW1wbGVtZW50ZWQgYSBjYWxsYmFjayB0byBjaGVjayBvbiB0aGUgbG9zcyBmdW5jdGlvbiBhbmQgdG8gY2FuY2VsIHRyYWluaW5nIG9uY2UgaXQgaGl0IGEgY2VydGFpbiBhbW91bnQuIFNlZSBpZiB5b3UgY2FuIGltcGxlbWVudCB0aGF0IGhlcmUhDQoNCkkgd2lsbCBhdHRlbXB0IHRoZSBsYXN0IHF1ZXN0aW9uIG9uIHVzaW5nIGNhbGxiYWNrcyB0byBzdG9wIHRoZSB0cmFpbmluZyBwcm9jZXNzLg0KDQpLZXJhcyBpbmNsdWRlcyBhIG51bWJlciBvZiBbYnVpbHQtaW4gY2FsbGJhY2tzXShodHRwczovL2tlcmFzLnJzdHVkaW8uY29tL2FydGljbGVzL3RyYWluaW5nX2NhbGxiYWNrcy5odG1sI2J1aWx0LWluLWNhbGxiYWNrcykuIA0KRm9yIHRoaXMgZXhlcmNpc2UsIHdlIHdpbGwgW2J1aWxkIG91ciBvd24gY2FsbGJhY2tdKGh0dHBzOi8va2VyYXMucnN0dWRpby5jb20vYXJ0aWNsZXMvdHJhaW5pbmdfY2FsbGJhY2tzLmh0bWwjY3VzdG9tLWNhbGxiYWNrcykgd2hpY2ggc3RvcHMgdGhlIG1vZGVsIGZyb20gdHJhaW5pbmcgb25jZSBhIGRlc2lyZWQgYWNjdXJhY3kgaXMgYXR0YWluZWQgc2F5IDkwJS4gVHJhaW5pbmcgc2hvdWxkIHN0b3AgYWZ0ZXIgdGhlIGVuZCBvZiB0aGUgM3JkIGVwb2NoIHdoZW4gYWNjdXJhY3kgZ29lcyBhYm92ZSA5MCUuDQoNCioqQ3VzdG9tIENhbGxiYWNrcyoqDQoNCllvdSBjYW4gY3JlYXRlIGEgY3VzdG9tIGNhbGxiYWNrIGJ5IGNyZWF0aW5nIGEgbmV3IFI2IGNsYXNzIHRoYXQgaW5oZXJpdHMgZnJvbSB0aGUgS2VyYXNDYWxsYmFjayBjbGFzcy4NCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT0gVFJVRX0NCmxpYnJhcnkoUjYpDQoNCiMgZGVmaW5lIGN1c3RvbSBjYWxsYmFjayBjbGFzcw0KdHJhaW5fc3RvcCA8LSBSNjo6UjZDbGFzcygidHJhaW5fc3RvcCIsDQogaW5oZXJpdCA9IEtlcmFzQ2FsbGJhY2ssDQogcHVibGljID0gbGlzdCgNCiAgIG9uX2Vwb2NoX2VuZCA9IGZ1bmN0aW9uKGVwb2NoLCBsb2dzID0gbGlzdCgpKXsNCiAgICAgaWYobG9ncyQnYWNjJz4wLjkpew0KICAgICAgIHNlbGYkbW9kZWwkc3RvcF90cmFpbmluZyA9IFRSVUUNCiAgICAgICBwYXN0ZTAoIlJlYWNoZWQgOTAlIGFjY3VyYWN5IHNvIGNhbmNlbGxpbmcgdHJhaW5pbmchIikNCiAgICAgfQ0KICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICApKQ0KYGBgDQoNCg0KVGhhdCBkb25lLCB3ZSBqdXN0IGhhdmUgdG8gY3JlYXRlIGFuIGluc3RhbmNlIG9mIHRoZSBjYWxsYmFjayBhbmQgQXR0YWNoIHRoZSBjYWxsYmFjayB0byBtb2RlbCB0cmFpbmluZyBhcyBiZWxvdzoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT0gVFJVRX0NCiMgY3JlYXRlcyBhbiBpbnN0YW5jZSBvZiB0aGUgY2FsbGJhY2sNCmNhbGxiYWNrIDwtIHRyYWluX3N0b3AkbmV3KCkNCg0KIyBsb2FkaW5nIHRoZSByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeShrZXJhcykNCg0KIyBpbXBvcnRpbmcgdGhlIGRhdGFzZXRzIGRpcmVjdGx5IGZyb20gS2VyYXMNCm1uaXN0IDwtIGRhdGFzZXRfZmFzaGlvbl9tbmlzdCgpDQpjKHRyYWluaW5nX2ltYWdlcywgdHJhaW5pbmdfbGFiZWxzKSAlPC0lIG1uaXN0JHRyYWluDQpjKHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscykgJTwtJSBtbmlzdCR0ZXN0DQoNCiMgUmVzaGFwaW5nIG91ciBpbWFnZSBhcnJheXMgYW5kIG5vcm1hbGl6aW5nIHRoZW0NCnRyYWluaW5nX2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRyYWluaW5nX2ltYWdlcywgYyg2MDAwMCwgMjgsIDI4LCAxKSkNCnRyYWluaW5nX2ltYWdlcyA8LSB0cmFpbmluZ19pbWFnZXMvMjU1DQp0ZXN0X2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRlc3RfaW1hZ2VzLCBjKDEwMDAwLCAyOCwgMjgsIDEpKQ0KdGVzdF9pbWFnZXMgPC0gdGVzdF9pbWFnZXMvMjU1DQoNCiMgSW5zdGFudGlhdGluZyBhIENvbnZvbHV0aW9uIHRvIGV4dHJhY3QgZmVhdHVyZXMNCm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUNCiAgbGF5ZXJfY29udl8yZChpbnB1dF9zaGFwZSA9IGMoMjgsIDI4LCAxKSwgZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywzKSwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lIA0KICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsMikpICU+JSANCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gNjQsIGtlcm5lbF9zaXplID0gYygzLDMpLCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKQ0KDQojIEFkZGluZyBhIGNsYXNzaWZpZXIgdG8gdGhlIGNvdm5ldCB3aGljaCBtYXBzIHRoZSBsZWFybmVkIGZlYXR1cmVzIHRvIHRoZSBnaXZlbiBsYWJlbHMNCm1vZGVsIDwtIG1vZGVsICU+JQ0KICBsYXllcl9mbGF0dGVuKCkgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUNCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMCwgYWN0aXZhdGlvbiA9ICdzb2Z0bWF4JykNCg0KIyAgQ29uZmlndXJpbmcgYSBLZXJhcyBtb2RlbCBmb3IgdHJhaW5pbmcNCm1vZGVsICU+JSBjb21waWxlKA0KICBsb3NzID0gJ3NwYXJzZV9jYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknLA0KICBtZXRyaWNzID0gYygnYWNjdXJhY3knKSwNCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX2FkYW0oKQ0KKQ0KDQojIFRyYWluaW5nIHRoZSBOTiBhbmQgYXR0YWNoaW5nIGEgY2FsbGJhY2sNCmhpc3RvcnkgPC0gbW9kZWwgJT4lIA0KICBmaXQoeCA9IHRyYWluaW5nX2ltYWdlcywNCiAgICAgIHkgPSB0cmFpbmluZ19sYWJlbHMsDQogICAgICBlcG9jaHMgPSA1LA0KICAgICAgICMgQXR0YWNoIHRoZSBjYWxsYmFjayB0byBtb2RlbCB0cmFpbmluZw0KICAgICAgY2FsbGJhY2tzID0gbGlzdChjYWxsYmFjaykNCiAgDQopDQpoaXN0b3J5DQoNCiMgRXZhbHVhdGluZyB0aGUgbW9kZWwNCm1ldHJpY3MgPC0gbW9kZWwgJT4lIGV2YWx1YXRlKA0KICB4ID0gdGVzdF9pbWFnZXMsDQogIHkgPSB0ZXN0X2xhYmVscw0KKQ0KY2F0KCJUZXN0IGxvc3MiLCBtZXRyaWNzJGxvc3MsICJcbiIpDQpjYXQoIlRlc3QgYWNjdXJhY3kiLCBtZXRyaWNzJGFjYywgIlxuIikNCmBgYA0KDQoNCkJ5IG5vdywgeW91IGtub3cgdGhlIGRyaWxsLiBMYXVyZW5jZSBsZWF2ZXMgcXVpdGUgYSBuZWF0IGV4ZXJjaXNlIGF0IHRoZSBlbmQgb2YgZWFjaCBlcGlzb2RlIHdoaWNoIGhlbHBzIG9uZSBpbXBsZW1lbnQgdGhlIGNvbmNlcHQgbGVhcm50IGhlcmUgdG8gYSBuZXcgZGF0YXNldC4gVGhlIGV4ZXJjaXNlIGZvciB0aGlzIGVwaXNvZGUgY2FuIGJlIGZvdW5kIGhlcmU6IFtFeGVyY2lzZSAzXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L2RsYWljb3Vyc2UvYmxvYi9tYXN0ZXIvRXhlcmNpc2VzL0V4ZXJjaXNlJTIwMyUyMC0lMjBDb252b2x1dGlvbnMvRXhlcmNpc2UlMjAzJTIwLSUyMFF1ZXN0aW9uLmlweW5iI3Njcm9sbFRvPXNmUVJ5YUpXQUlkZykuIEl0cyBzb2x1dGlvbiB3aWxsIGJlIGdpdmVuIGluIHRoZSBuZXh0IGVwaXNvZGUgYnV0IEkgYmV0IGl0IHdpbGwgYmUgc29tZXRoaW5nIGNsb3NlIHRvIHRoaXMuIA0KDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0NCiMgbXkgc29sdXRpb24NCiMgaW1wcm92ZSBNTklTVCB0byA5OS44JSBhY2N1cmFjeQ0KIyB1c2luZyBvbmx5IGEgc2luZ2xlIGNvbnZvbHV0aW9uYWwgbGF5ZXIgYW5kIGEgc2luZ2xlIE1heFBvb2xpbmcgMkQuIA0KIyBZb3Ugc2hvdWxkIHN0b3AgdHJhaW5pbmcgb25jZSB0aGUgYWNjdXJhY3kgZ29lcyBhYm92ZSB0aGlzIGFtb3VudC4NCiMgSXQgc2hvdWxkIGhhcHBlbiBpbiBsZXNzIHRoYW4gMjAgZXBvY2hzLg0KIyBXaGVuIDk5LjglIGFjY3VyYWN5IGhhcyBiZWVuIGhpdCBwcmludCBvdXQgdGhlIHN0cmluZyAiUmVhY2hlZCA5OS44JSBhY2N1cmFjeSBzbyBjYW5jZWxsaW5nIHRyYWluaW5nISINCg0KDQoNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQpsaWJyYXJ5KFI2KQ0KDQojIGRlZmluZSBjdXN0b20gY2FsbGJhY2sgY2xhc3MNCnRyYWluX3N0b3AgPC0gUjY6OlI2Q2xhc3MoInRyYWluX3N0b3AiLA0KIGluaGVyaXQgPSBLZXJhc0NhbGxiYWNrLA0KIHB1YmxpYyA9IGxpc3QoDQogICBvbl9lcG9jaF9lbmQgPSBmdW5jdGlvbihlcG9jaCwgbG9ncyA9IGxpc3QoKSl7DQogICAgIGlmKGxvZ3MkJ2FjYyc+MC45OTgpew0KICAgICAgIHNlbGYkbW9kZWwkc3RvcF90cmFpbmluZyA9IFRSVUUNCiAgICAgICBwYXN0ZTAoIlJlYWNoZWQgOTkuOCUgYWNjdXJhY3kgc28gY2FuY2VsbGluZyB0cmFpbmluZyEiKQ0KICAgICB9DQogICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICkpDQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQoNCiAgDQogIA0KIyBsb2FkaW5nIHRoZSByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeShrZXJhcykgDQogIA0KIyBjcmVhdGVzIGFuIGluc3RhbmNlIG9mIHRoZSBjYWxsYmFjaw0KY2FsbGJhY2sgPC0gdHJhaW5fc3RvcCRuZXcoKQ0KDQoNCg0KIyBpbXBvcnRpbmcgdGhlIGRhdGFzZXRzIGRpcmVjdGx5IGZyb20gS2VyYXMNCm1uaXN0IDwtIGRhdGFzZXRfbW5pc3QoKQ0KYyh0cmFpbmluZ19pbWFnZXMsIHRyYWluaW5nX2xhYmVscykgJTwtJSBtbmlzdCR0cmFpbg0KYyh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpICU8LSUgbW5pc3QkdGVzdA0KDQojIFJlc2hhcGluZyBvdXIgaW1hZ2UgYXJyYXlzIGFuZCBub3JtYWxpemluZyB0aGVtDQp0cmFpbmluZ19pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0cmFpbmluZ19pbWFnZXMsIGMoNjAwMDAsIDI4LCAyOCwgMSkpDQp0cmFpbmluZ19pbWFnZXMgPC0gdHJhaW5pbmdfaW1hZ2VzLzI1NQ0KdGVzdF9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0ZXN0X2ltYWdlcywgYygxMDAwMCwgMjgsIDI4LCAxKSkNCnRlc3RfaW1hZ2VzIDwtIHRlc3RfaW1hZ2VzLzI1NQ0KDQojIEluc3RhbnRpYXRpbmcgYSBDb252b2x1dGlvbiB0byBleHRyYWN0IGZlYXR1cmVzDQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lDQogIGxheWVyX2NvbnZfMmQoaW5wdXRfc2hhcGUgPSBjKDI4LCAyOCwgMSksIGZpbHRlcnMgPSAxMjgsIGtlcm5lbF9zaXplID0gYygzLDMpLCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUgDQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwyKSkgIA0KICANCg0KIyBBZGRpbmcgYSBjbGFzc2lmaWVyIHRvIHRoZSBjb3ZuZXQgd2hpY2ggbWFwcyB0aGUgbGVhcm5lZCBmZWF0dXJlcyB0byB0aGUgZ2l2ZW4gbGFiZWxzDQptb2RlbCA8LSBtb2RlbCAlPiUNCiAgbGF5ZXJfZmxhdHRlbigpICU+JQ0KICBsYXllcl9kZW5zZSh1bml0cyA9IDEyOCwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAnc29mdG1heCcpDQoNCiMgIENvbmZpZ3VyaW5nIGEgS2VyYXMgbW9kZWwgZm9yIHRyYWluaW5nDQptb2RlbCAlPiUgY29tcGlsZSgNCiAgbG9zcyA9ICdzcGFyc2VfY2F0ZWdvcmljYWxfY3Jvc3NlbnRyb3B5JywNCiAgbWV0cmljcyA9IGMoJ2FjY3VyYWN5JyksDQogIG9wdGltaXplciA9IG9wdGltaXplcl9hZGFtKCkNCikNCg0KIyBUcmFpbmluZyB0aGUgTk4gYW5kIGF0dGFjaGluZyBhIGNhbGxiYWNrDQpoaXN0b3J5IDwtIG1vZGVsICU+JSANCiAgZml0KHggPSB0cmFpbmluZ19pbWFnZXMsDQogICAgICB5ID0gdHJhaW5pbmdfbGFiZWxzLA0KICAgICAgZXBvY2hzID0gMTAsDQogICAgICAgIyBBdHRhY2ggdGhlIGNhbGxiYWNrIHRvIG1vZGVsIHRyYWluaW5nDQogICAgICBjYWxsYmFja3MgPSBsaXN0KGNhbGxiYWNrKQ0KICANCikNCmhpc3RvcnkNCg0KIyBFdmFsdWF0aW5nIHRoZSBtb2RlbA0KbWV0cmljcyA8LSBtb2RlbCAlPiUgZXZhbHVhdGUoDQogIHggPSB0ZXN0X2ltYWdlcywNCiAgeSA9IHRlc3RfbGFiZWxzDQopDQpjYXQoIlRlc3QgbG9zcyIsIG1ldHJpY3MkbG9zcywgIlxuIikNCmNhdCgiVGVzdCBhY2N1cmFjeSIsIG1ldHJpY3MkYWNjLCAiXG4iKQ0KDQpgYGANCg0KWWVzISBXZSBkaXQgaXQhISDwn6SpIFdlIHN1Y2Nlc3NmdWxseSBidWlsdCBhIG1vZGVsIHdoaWNoIGltcGxlbWVudHMgY29udm9sdXRpb25zIGFuZCBwb29saW5nIHRvIGltcHJvdmUgdGhlIGFjY3VyYWN5IG9mIGNvbXB1dGVyIHZpc2lvbiB1c2luZyBSLiBJbmRlZWQgUiwgYXQgaXRzIGNvcmUgaXMgYSBiZWF1dGlmdWwgYW5kIGVsZWdhbnQgbGFuZ3VhZ2UsIHdlbGwgZGVzaWduZWQgZm9yIERhdGEgU2NpZW5jZSDwn5KWLg0KDQoNCiMgKipSZWZlcmVuY2UgTWF0ZXJpYWwqKg0KDQoqIE1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnM6IEVwICM0IC0gW0NvZGluZyB3aXRoIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWRkOEg0ZmlMOVljJnQ9MXMpDQoNCiogTWFjaGluZSBMZWFybmluZyBGb3VuZGF0aW9uczogRXAgIzMgLSBbQ29udm9sdXRpb25zIGFuZCBwb29saW5nXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PVBDZ0xtemtSTTM4JnQ9MSkNCg0KKiBEZWVwIExlYXJuaW5nIHdpdGggUiBieSBGcmFuY29pcyBDaG9sbGV0IGFuZCBKLkouQWxsYWlyZQ0KDQoqIFRoZSBbUiBpbnRlcmZhY2UgdG8gS2VyYXNdKGh0dHBzOi8va2VyYXMucnN0dWRpby5jb20pIHdlYnNpdGUNCg0KKiBMYWIgNC0gW1VzaW5nIENvbnZvbHV0aW9uc10oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWI0LVVzaW5nLUNvbnZvbHV0aW9ucy5pcHluYiNzY3JvbGxUbz16bGRFWFNzRjhOb3opIA0KDQoqIExhYiAzLSBbV2hhdCBhcmUgQ29udm9sdXRpb25zXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L21sZGF5LXRva3lvL2Jsb2IvbWFzdGVyL0xhYjQtVXNpbmctQ29udm9sdXRpb25zLmlweW5iI3Njcm9sbFRvPXpsZEVYU3NGOE5veikgDQoNCiogRXhlcmNpc2UgZm9yIHRoaXMgZXBpc29kZTogW0V4ZXJjaXNlIDNdKGh0dHBzOi8vY29sYWIucmVzZWFyY2guZ29vZ2xlLmNvbS9naXRodWIvbG1vcm9uZXkvZGxhaWNvdXJzZS9ibG9iL21hc3Rlci9FeGVyY2lzZXMvRXhlcmNpc2UlMjAzJTIwLSUyMENvbnZvbHV0aW9ucy9FeGVyY2lzZSUyMDMlMjAtJTIwUXVlc3Rpb24uaXB5bmIjc2Nyb2xsVG89c2ZRUnlhSldBSWRnKQ0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBUaGlzIHNhdmVzIHRoZSBvdXRwdXQgb2YgdGhlIGNvbnZvbHV0aW9ucyBhbmQgcG9vbGluZyBpbiBhIGZvbGRlcg0KIyB3aXRoaW4gdGhlIGN1cnJlbnQgZGlyZWN0b3J5DQpkaXIuY3JlYXRlKCJib290X2FjdGl2YXRpb25zIikNCmltYWdlX3NpemUgPC0gNTgNCmltYWdlc19wZXJfcm93IDwtIDE2DQpmb3IgKGkgaW4gMTo0KSB7DQogIA0KICBsYXllcl9hY3RpdmF0aW9uIDwtIGFjdGl2YXRpb25zW1tpXV0NCiAgbGF5ZXJfbmFtZSA8LSBtb2RlbCRsYXllcnNbW2ldXSRuYW1lDQogDQogIG5fZmVhdHVyZXMgPC0gZGltKGxheWVyX2FjdGl2YXRpb24pW1s0XV0NCiAgbl9jb2xzIDwtIG5fZmVhdHVyZXMgJS8lIGltYWdlc19wZXJfcm93DQogDQogIHBuZyhwYXN0ZTAoImJvb3RfYWN0aXZhdGlvbnMvIiwgaSwgIl8iLCBsYXllcl9uYW1lLCAiLnBuZyIpLCANCiAgICAgIHdpZHRoID0gaW1hZ2Vfc2l6ZSAqIGltYWdlc19wZXJfcm93LCANCiAgICAgIGhlaWdodCA9IGltYWdlX3NpemUgKiBuX2NvbHMpDQogIG9wIDwtIHBhcihtZnJvdyA9IGMobl9jb2xzLCBpbWFnZXNfcGVyX3JvdyksIG1haSA9IHJlcF9sZW4oMC4wMiwgNCksIG5ldyA9IFRSVUUpDQogIA0KICBmb3IgKGNvbCBpbiAwOihuX2NvbHMtMSkpIHsNCiAgICBmb3IgKHJvdyBpbiAwOihpbWFnZXNfcGVyX3Jvdy0xKSkgew0KICAgICAgY2hhbm5lbF9pbWFnZSA8LSBsYXllcl9hY3RpdmF0aW9uWzEsLCwoY29sKmltYWdlc19wZXJfcm93KSArIHJvdyArIDFdDQogICAgICBwbG90X2NoYW5uZWwoY2hhbm5lbF9pbWFnZSkNCiAgICB9DQogIH0NCiAgDQogIHBhcihvcCkNCiAgZGV2Lm9mZigpDQp9DQpgYGANCg0K