Improving Computer Vision Accuracy using Convolutions: An R Version (co-authors: Ian, Sam, Eric)
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:
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
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 = 6, 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 = 16, 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, 6) 60
## ___________________________________________________________________________
## max_pooling2d (MaxPooling2D) (None, 13, 13, 6) 0
## ___________________________________________________________________________
## conv2d_1 (Conv2D) (None, 11, 11, 16) 880
## ___________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D) (None, 5, 5, 16) 0
## ___________________________________________________________________________
## flatten (Flatten) (None, 400) 0
## ___________________________________________________________________________
## dense (Dense) (None, 128) 51328
## ___________________________________________________________________________
## dense_1 (Dense) (None, 10) 1290
## ===========================================================================
## Total params: 53,558
## Trainable params: 53,558
## 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.
Displaying the first 25 images from the testing set:
par(mfrow = c(5,5))
par(mar=c(0, 0, 1.5, 0), xaxs='i', yaxs='i')
for(i in 1:50){
img = test_images[i, , ,1]
img = t(apply(img, 2, rev))
image(1:28, 1:28, img, col = gray((0:255)/255), xaxt = 'n',
yaxt = 'n')
}


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.
## [1] 1 18 27 35 37 42 61 65 71 76

let’s extract the first 4 convolution and pooling layers
## [1] 1 26 26 6
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 code below saves the images in your current directory.
images_per_row <- 3
for (i in 1:2) {
layer_activation <- activations[[i]]
layer_name <- model$layers[[i]]$name
n_features <- dim(layer_activation)[[4]]
n_cols <- n_features %/% images_per_row
png(paste0("mnist_seven_activations_", i, "_", layer_name, ".png"),
width = 500,
height = 500)
op <- par(mfrow = c(n_cols, images_per_row), mai = rep_len(0.02, 4))
for (col in 0:(n_cols-1)) {
for (row in 0:(images_per_row-1)) {
channel_image <- layer_activation[1,,,(col*images_per_row) + row + 1]
plot_channel(channel_image)
}
}
par(op)
dev.off()
}
images_per_row <- 4
for (i in 3:4) {
layer_activation <- activations[[i]]
layer_name <- model$layers[[i]]$name
n_features <- dim(layer_activation)[[4]]
n_cols <- n_features %/% images_per_row
png(paste0("mnist_seven_activations_", i, "_", layer_name, ".png"),
width = 500,
height = 500)
op <- par(mfrow = c(n_cols, images_per_row), mai = rep_len(0.02, 4))
for (col in 0:(n_cols-1)) {
for (row in 0:(images_per_row-1)) {
channel_image <- layer_activation[1,,,(col*images_per_row) + row + 1]
plot_channel(channel_image)
}
}
par(op)
dev.off()
}
The output of each filter in each layer is as shown row-wise.
How to make a raster plot of the pixel values we just saved before

Truly, Eric, Ian, Sam
LS0tDQojdGl0bGU6ICJJbXByb3ZpbmcgQ29tcHV0ZXIgVmlzaW9uIEFjY3VyYWN5IHVzaW5nIENvbnZvbHV0aW9ucyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogY2VydWxlYW4NCiAgICBoaWdobGlnaHQ6IGJyZWV6ZWRhcmsNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCg0KDQojICoqSW1wcm92aW5nIENvbXB1dGVyIFZpc2lvbiBBY2N1cmFjeSB1c2luZyBDb252b2x1dGlvbnM6KiogQW4gUiBWZXJzaW9uIChjby1hdXRob3JzOiBJYW4sIFNhbSwgRXJpYykNCg0KDQojICoqQSBoaWdoIGxldmVsIG92ZXJ2aWV3IG9mIENvbnZvbHV0aW9uIGFuZCBQb29saW5nKioNCg0KQSBgY29udm9sdXRpb25gIGlzIGEgZmlsdGVyIHRoYXQgcGFzc2VzIG92ZXIgYW4gaW1hZ2UsIHByb2Nlc3NpbmcgaXQsIGFuZCBleHRyYWN0aW5nIGZlYXR1cmVzIHRoYXQgc2hvdyBhIGNvbW1vbm9sYXRpdHkgaW4gdGhlIGltYWdlIHN1Y2ggdGhhdCBpZiBhbiBpbWFnZSBoYXMgY2VydGFpbiBmZWF0dXJlcywgaXQgYmVsb25ncyB0byBhIHBhcnRpY3VsYXIgY2xhc3MuIEF0IGl0cyBoZWFydCwgY29udm9sdXRpb24gaXMgcmVhbGx5IHNpbXBsZS4gSXQgaW52b2x2ZXMgc2Nhbm5pbmcgZXZlcnkgcGl4ZWwgaW4gdGhlIGltYWdlLCBsb29raW5nIGF0IGl0J3MgbmVpZ2hib3JpbmcgcGl4ZWxzLCBtdWx0aXBseWluZyB0aGVzZSBwaXhlbHMgYnkgdGhlaXIgY29ycmVzcG9uZGluZyB3ZWlnaHQgaW4gYSBmaWx0ZXIgYW5kIHRoZW4gc3VtbWluZyB0aGlzIGFsbCB1cCB0byBvYnRhaW4gYSBuZXcgcGl4ZWwgdmFsdWUuIFRoaXMgY2FuIGJlIHNob3duIGFzIGJlbG93Og0KDQpgYGB7cixlY2hvPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL0FETUlOL0Rlc2t0b3AvSW50b2R1Y3Rpb24gdG8gUHl0aG9uIGZvciBkYXRhIHNjaWVuY2UvUiBmb3IgZGF0YSBzY2llbmNlL2FSZHVpbm8vcjRnL2NvbnZvbHV0aW9uLkpQRyIpDQpwcmludCgiSW1hZ2Ugc291cmNlOiBodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L21sZGF5LXRva3lvL2Jsb2IvbWFzdGVyL0xhYjMtV2hhdC1BcmUtQ29udm9sdXRpb25zLmlweW5iI3Njcm9sbFRvPXhGMEZQcGxzZ0hOaCIsIHF1b3RlID0gRikNCg0KYGBgDQoNCg0KDQpUaGUgcHJldmlvdXMgYERlbnNlIE5ldXJhbCBOZXR3b3JrYCB0aGF0IHdlIGNyZWF0ZWQgc2ltcGx5IGxlYXJuZWQgZnJvbSB0aGUgcmF3IHBpeGVscyB3aGF0IG1hZGUgdXAgYSBzd2VhdGVyIG9yIHdoYXQgbWFkZSB1cCBhIGJvb3QuIFRoaXMgaW4gaXRzZWxmIGlzIHF1aXRlIGEgbGltaXRhdGlvbi4gDQoNClVsdGltYXRlbHkgdGhlIGdvYWwgb2YgdHJ5aW5nIHRvIHVuZGVyc3RhbmQgd2hhdCBhbiBpdGVtIGlzLCBpc24ndCBqdXN0IG1hdGNoaW5nIHRoZSByYXcgcGl4ZWxzIHRvIGxhYmVscyBsaWtlIHdlIGRpZCBpbiB0aGUgcHJldmlvdXMgZXhlcmNpc2VzLg0KDQpXaGF0IGlmIHdlIGNvdWxkIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSB0aGUgaW1hZ2UgaW5zdGVhZCBhbmQgd2hlbiBhbiBpbWFnZSBoYXMgc29tZSBzcGVjaWZpYyBmZWF0dXJlcywgaXQgYmVsb25ncyB0byBhIHBhcnRpY3VsYXIgY2xhc3MuIFRoaXMgaXMgdGhlIGhlYXJ0IG9mIENvbnZvbHV0aW9uIE5ldXJhbCBOZXR3b3JrcyBkby4NCg0KVGhpcyBrZXkgY2hhcmFjdGVyaXN0aWMgZ2l2ZXMgY29udm5ldHMgdHdvIGludGVyZXN0aW5nIHByb3BlcnRpZXM6DQoNCiogKlRoZSBwYXR0ZXJucyB0aGV5IGxlYXJuIGFyZSB0cmFuc2xhdGlvbi1pbnZhcmlhbnQ6Kg0KVGhpcyBtZWFucyB0aGF0IGFmdGVyIGxlYXJuaW5nIGEgY2VydGFpbiBwYXR0ZXJuLCBhIGNvdm5ldCBjYW4gcmVjb2duaXplIGl0IGFueXdoZXJlIGVsc2UgaW4gdGhlIGltYWdlIGFzIG9wcG9zZWQgdG8gYSBETk4gd2hpY2ggd291bGQgaGF2ZSB0byBsZWFybiB0aGUgcGF0dGVybiBhIG5ldyBpZiBpdCBhcHBlYXJlZCBhdCBhIG5ldyBsb2NhdGlvbi4gRm9yIHRoaXMgcmVhc29uLCBjb3ZuZXRzIHJlcXVpcmUgZmV3IHRyYWluaW5nIHNhbXBsZXMuDQoNCiogKlRoZXkgY2FuIGxlYXJuIHNwYXRpYWwgaGllcmFyY2hpZXMgb2YgcGF0dGVybnM6Kg0KQSBmaXJzdCBjb252b2x1dGlvbiBsYXllciB3aWxsIGxlYXJuIHNtYWxsIGxvY2FsIHBhdHRlcm5zIHN1Y2ggYXMgZWRnZXMsIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdpbGwgbGVhcm4gbGFyZ2VyIHBhdHRlcm5zIG1hZGUgb2YgdGhlIGZlYXR1cmVzIG9mIHRoZSBmaXJzdCBsYXllcnMsIGFuZCBzbyBvbi4gRm9yIHRoaXMgcmVhc29uLCBjb3ZuZXRzIGNhbiBsZWFybiBpbmNyZWFzaW5nbHkgY29tcGxleCBhbmQgYWJzdHJhY3QgZmVhdHVyZXMuDQoNCg0KSWYgeW91J3ZlIGV2ZXIgZG9uZSBpbWFnZSBwcm9jZXNzaW5nIHVzaW5nIGEgZmlsdGVyIChsaWtlIHRoaXM6IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0tlcm5lbF8oaW1hZ2VfcHJvY2Vzc2luZykpIHRoZW4gY29udm9sdXRpb25zIHdpbGwgbG9vayB2ZXJ5IGZhbWlsaWFyLg0KDQpJbiBzaG9ydCwgeW91IHRha2UgYW4gYXJyYXkgKHVzdWFsbHkgM3gzIG9yIDV4NSkgYW5kIHBhc3MgaXQgb3ZlciB0aGUgaW1hZ2UuIEJ5IGNoYW5naW5nIHRoZSB1bmRlcmx5aW5nIHBpeGVscyBiYXNlZCBvbiB0aGUgZm9ybXVsYSB3aXRoaW4gdGhhdCBtYXRyaXgsIHlvdSBjYW4gZG8gdGhpbmdzIGxpa2UgZWRnZSBkZXRlY3Rpb24uIFNvLCBmb3IgZXhhbXBsZSwgaWYgeW91IGxvb2sgYXQgdGhlIGFib3ZlIGxpbmssIHlvdSdsbCBzZWUgYSAzeDMgdGhhdCBpcyBkZWZpbmVkIGZvciBlZGdlIGRldGVjdGlvbiB3aGVyZSB0aGUgbWlkZGxlIGNlbGwgaXMgOCwgYW5kIGFsbCBvZiBpdHMgbmVpZ2hib3JzIGFyZSAtMS4gSW4gdGhpcyBjYXNlLCBmb3IgZWFjaCBwaXhlbCwgeW91IHdvdWxkIG11bHRpcGx5IGl0cyB2YWx1ZSBieSA4LCB0aGVuIHN1YnRyYWN0IHRoZSB2YWx1ZSBvZiBlYWNoIG5laWdoYm9yLiBEbyB0aGlzIGZvciBldmVyeSBwaXhlbCwgYW5kIHlvdSdsbCBlbmQgdXAgd2l0aCBhIG5ldyBpbWFnZSB0aGF0IGhhcyB0aGUgZWRnZXMgZW5oYW5jZWQuDQoNCg0KDQoqVGhhdCdzIHRoZSBjb25jZXB0IG9mIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzLiBBZGQgc29tZSBsYXllcnMgdG8gZG8gY29udm9sdXRpb24gYmVmb3JlIHlvdSBoYXZlIHRoZSBkZW5zZSBsYXllcnMsIGFuZCB0aGVuIHRoZSBpbmZvcm1hdGlvbiBnb2luZyB0byB0aGUgZGVuc2UgbGF5ZXJzIGlzIG1vcmUgZm9jdXNzZWQsIGFuZCBwb3NzaWJseSBtb3JlIGFjY3VyYXRlLioNCg0KDQpgUG9vbGluZ2AgcmVkdWNlcyB0aGUgYW1vdW50IG9mIGlycmVsZXZhbnQgaW5mb3JtYXRpb24gaW4gYW4gaW1hZ2Ugd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGZlYXR1cmVzIHRoYXQgYXJlIGRldGVjdGVkLiBJdCBkb2VzIHNvIGJ5IGxvb2tpbmcgYXQgYSBwaXhlbCBhbmQgaXRzIGltbWVkaWF0ZSBuZWlnaGJvdXJzIHRvIHRoZSByaWdodCwgYmVuZWF0aCBhbmQgcmlnaHQtYmVuZWF0aCwgdGFrZXMgdGhlIGxhcmdlc3QgaGVuY2UgdGhlIG5hbWUgTWF4IHBvb2xpbmcsIGFuZCBsb2FkcyBpdCBpbnRvIGEgbmV3IGltYWdlLiBJdCB0aHVzIHJlZHVjZXMgdGhlIGFtb3VudCBvZiBpbmZvcm1hdGlvbiB0aGF0IGEgbW9kZWwgaGFzIHRvIHByb2Nlc3Mgd2hpbGUgc3RpbGwgbWFpbnRhaW5pbmcgdGhlIHByb21pbmVudCBmZWF0dXJlcy4NCg0KYGBge3IsZWNobz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9BRE1JTi9EZXNrdG9wL0ludG9kdWN0aW9uIHRvIFB5dGhvbiBmb3IgZGF0YSBzY2llbmNlL1IgZm9yIGRhdGEgc2NpZW5jZS9hUmR1aW5vL3I0Zy9wb29saW5nLkpQRyIpDQpwcmludCgiSW1hZ2Ugc291cmNlOiBodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L21sZGF5LXRva3lvL2Jsb2IvbWFzdGVyL0xhYjMtV2hhdC1BcmUtQ29udm9sdXRpb25zLmlweW5iI3Njcm9sbFRvPXhGMEZQcGxzZ0hOaCIsIHF1b3RlID0gRikNCg0KYGBgDQoNCg0KDQojIyMgKipCdWlsZGluZyBDb252b2x1dGlvbiBOZXVyYWwgTmV0d29ya3MuKioNCg0KDQojICoqR2F0aGVyaW5nIHRoZSBEYXRhKioNCg0KTGV0J3Mgc3RhcnQgYnkgbG9hZGluZyB0aGUgbGlicmFyaWVzIHJlcXVpcmVkIGZvciB0aGlzIHNlc3Npb24uDQoNCldlJ2xsIGJlIHJlcXVpcmluZyBzb21lIHBhY2thZ2VzIGluIHRoZSBUaWR5dmVyc2UgYW5kIEtlcmFzKGEgZnJhbWV3b3JrIGZvciBkZWZpbmluZyBhIG5ldXJhbCBuZXR3b3JrIGFzIGEgc2V0IG9mIFNlcXVlbnRpYWwgbGF5ZXJzKS4gWW91IGNhbiBoYXZlIHRoZW0gaW5zdGFsbGVkIGFzIGZvbGxvd3MNCg0KYGBgDQpzdXBwcmVzc01lc3NhZ2VzKGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpKQ0Kc3VwcHJlc3NNZXNzYWdlcyhpbnN0YWxsLnBhY2thZ2VzKCJrZXJhcyIpKQ0Kc3VwcHJlc3NNZXNzYWdlcyhpbnN0YWxsX2tlcmFzKCkpDQoNClBzOiBpdCBjb3VsZCB0YWtlIGEgd2hpbGUNCmBgYA0KT25jZSBpbnN0YWxsZWQsIGxldCdzIGdldCByb2xsaW5nOg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGtlcmFzKQ0KDQptbmlzdCA8LSBkYXRhc2V0X21uaXN0KCkNCg0KYyh0cmFpbmluZ19pbWFnZXMsIHRyYWluaW5nX2xhYmVscykgJTwtJSBtbmlzdCR0cmFpbg0KYyh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpICU8LSUgbW5pc3QkdGVzdA0KDQojIHRoZSB0cmFpbl9pbWFnZXMgYW5kIHRyYWluX2xhYmVscyBhcnJheXMgYXJlIHRoZSB0cmFpbmluZyBzZXQNCiMgKHRoZSBkYXRhIHRoZSBtb2RlbCB1c2VzIHRvIGxlYXJuKS4gDQoNCg0KIyBUaGUgbW9kZWwgaXMgdGVzdGVkIGFnYWluc3QgdGhlIHRlc3Qgc2V0OiANCiMgdGhlIHRlc3RfaW1hZ2VzLCBhbmQgdGVzdF9sYWJlbHMgYXJyYXlzLg0KYGBgDQoNCg0KKipSZXNoYXBpbmcgb3VyIGltYWdlIGFycmF5cyBhbmQgbm9ybWFsaXppbmcgdGhlbSoqDQoNCg0KYGBge3J9DQp0cmFpbmluZ19pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0cmFpbmluZ19pbWFnZXMsIGMoNjAwMDAsIDI4LCAyOCwgMSkpDQoNCnRyYWluaW5nX2ltYWdlcyA8LSB0cmFpbmluZ19pbWFnZXMvMjU1DQoNCnRlc3RfaW1hZ2VzIDwtIGFycmF5X3Jlc2hhcGUodGVzdF9pbWFnZXMsIGMoMTAwMDAsIDI4LCAyOCwgMSkpDQp0ZXN0X2ltYWdlcyA8LSB0ZXN0X2ltYWdlcy8yNTUNCg0KIyB0aGUgdmFsdWVzIDYwLDAwMCBhbmQgMTAsMDAwIGFyZSBub3QgYXJiaXRyYXJ5LCB3ZSBvYnRhaW5lZCB0aGVtIHVzaW5nDQpkaW0odHJhaW5pbmdfaW1hZ2VzKQ0KDQojIDYwLDAwMCBpbWFnZXMgaW4gdGhlIHRyYWluaW5nIHNldCwNCiMgd2l0aCBlYWNoIGltYWdlIHJlcHJlc2VudGVkIGFzIDI4IHggMjggcGl4ZWxzIGFuZCAxIGRlcHRoIG9mIDEoYmluYXJ5IGltYWdlKQ0KYGBgDQoNCldoeSBhcmUgd2UgYWRkaW5nIG9uZSBtb3JlIGRpbWVuc2lvbj8gVGhhdCdzIGFuIGltcG9ydGFudCBxdWVzdGlvbi4NCkNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycywgY2FsbGVkIGZlYXR1cmUgbWFwcywgd2l0aCB0d28gc3BhdGlhbCBheGVzIChoZWlnaHQgd2lkdGgpIGFzIHdlbGwgYXMgYSBkZXB0aCBheGlzLiBGb3IgYW4gUkdCIGRlcHRoIGNoYW5uZWxzIGltYWdlLCB0aGUgZGltZW5zaW9uIG9mIHRoZSBkZXB0aCBheGlzIGlzIDMsIGJlY2F1c2UgdGhlIGltYWdlIGhhcyAzIGNvbG9yIGNoYW5uZWxzOiByZWQsIGdyZWVuLCBhbmQgYmx1ZS4gRm9yIGEgYmxhY2stYW5kLXdoaXRlIHBpY3R1cmUsIGxpa2UgdGhlIEZhc2hpb24gTU5JU1QgZGF0YXNldCwgdGhlIGRlcHRoIGlzIDEuDQoNCg0KDQoNCiMgKipJbnN0YW50aWF0aW5nIGEgQ29udm9sdXRpb24qKg0KDQpOZXh0IGlzIHRvIGRlZmluZSB5b3VyIG1vZGVsLiBOb3cgaW5zdGVhZCBvZiB0aGUgaW5wdXQgbGF5ZXIgYXQgdGhlIHRvcCwgd2UncmUgZ29pbmcgdG8gYWRkIGEgQ29udm9sdXRpb24uIFRoZSBwYXJhbWV0ZXJzIGFyZToNCg0KMS4gVGhlIG51bWJlciBvZiBjb252b2x1dGlvbnMgeW91IHdhbnQgdG8gZ2VuZXJhdGUuIFB1cmVseSBhcmJpdHJhcnksIGJ1dCBnb29kIHRvIHN0YXJ0IHdpdGggc29tZXRoaW5nIGluIHRoZSBvcmRlciBvZiAzMg0KDQoyLiBUaGUgc2l6ZSBvZiB0aGUgQ29udm9sdXRpb24sIGluIHRoaXMgY2FzZSBhIDN4MyBncmlkDQoNCjMuIFRoZSBhY3RpdmF0aW9uIGZ1bmN0aW9uIHRvIHVzZSAtLSBpbiB0aGlzIGNhc2Ugd2UnbGwgdXNlIHJlbHUsIHdoaWNoIHlvdSBtaWdodCByZWNhbGwgaXMgdGhlIGVxdWl2YWxlbnQgb2YgcmV0dXJuaW5nIHggd2hlbiB4PjAsIGVsc2UgcmV0dXJuaW5nIDANCg0KV2UnbGwgZm9sbG93IHRoZSBDb252b2x1dGlvbiB3aXRoIGEgTWF4UG9vbGluZyBsYXllciB3aGljaCBpcyB0aGVuIGRlc2lnbmVkIHRvIGNvbXByZXNzIHRoZSBpbWFnZSwgd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGNvbnRlbnQgb2YgdGhlIGZlYXR1cmVzIHRoYXQgd2VyZSBoaWdobGlnaHRlZCBieSB0aGUgY29udmx1dGlvbi4gQnkgc3BlY2lmeWluZyAoMiwyKSBmb3IgdGhlIE1heFBvb2xpbmcsIHRoZSBlZmZlY3QgaXMgdG8gcXVhcnRlciB0aGUgc2l6ZSBvZiB0aGUgaW1hZ2UuIFdpdGhvdXQgZ29pbmcgaW50byB0b28gbXVjaCBkZXRhaWwgaGVyZSwgdGhlIGlkZWEgaXMgdGhhdCBpdCBjcmVhdGVzIGEgMngyIGFycmF5IG9mIHBpeGVscywgYW5kIHBpY2tzIHRoZSBiaWdnZXN0IG9uZSwgdGh1cyB0dXJuaW5nIDQgcGl4ZWxzIGludG8gMS4gSXQgcmVwZWF0cyB0aGlzIGFjcm9zcyB0aGUgaW1hZ2UsIGFuZCBpbiBzbyBkb2luZyBoYWx2ZXMgdGhlIG51bWJlciBvZiBob3Jpem9udGFsLCBhbmQgaGFsdmVzIHRoZSBudW1iZXIgb2YgdmVydGljYWwgcGl4ZWxzLCBlZmZlY3RpdmVseSByZWR1Y2luZyB0aGUgaW1hZ2UgYnkgMjUlLiBUaGVzZSBjb25jZXB0cyBhcmUgY2xlYXJseSBleHBsYWluZWQgaW4gW0VwaXNvZGUgM10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1QQ2dMbXprUk0zOCZ0PTEpLg0KDQoNCmBgYHtyfQ0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSANCiAgIyBhZGRpbmcgdGhlIGZpcnN0IGNvbnZvbHV0aW9uIGxheWVyIHdpdGggNjQgM2J5MyBmaWx0ZXINCiAgIyB3ZSBhZGQgYSBjb2xvciBkZXB0aCBvZiAxIHNpbmNlIGNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycw0KICBsYXllcl9jb252XzJkKGlucHV0X3NoYXBlID0gYygyOCwgMjgsIDEpLCBmaWx0ZXJzID0gNiwga2VybmVsX3NpemUgPSBjKDMsMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JSANCiAgIyBhZGRpbmcgYSBtYXggcG9vbGluZyBsYXllciB3aGljaCBoYWx2ZXMgdGhlIGRpbWVuc2lvbnMNCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKSAlPiUgDQogICMgYWRkaW5nIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdoaWNoIGZpbHRlcnMgdGhlIHJlc3VsdHMNCiAgIyBmcm9tIHRoZSBwcmV2aW91cyBsYXllcg0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxNiwga2VybmVsX3NpemUgPSBjKDMsMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JSAjIGFkZGluZyBhIHBvb2xpbmcgbGF5ZXINCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKQ0KICANCmBgYA0KDQoNCg0KDQojICoqQWRkaW5nIGEgY2xhc3NpZmllciB0byB0aGUgY292bmV0KioNCkNvbnZvbHV0aW9uYWwgbGF5ZXJzIGxlYXJuIHRoZSBmZWF0dXJlcyBhbmQgcGFzcyB0aGVzZSB0byB0aGUgZGVuc2UgbGF5ZXJzIHdoaWNoIG1hcCB0aGUgbGVhcm5lZCBmZWF0dXJlcyB0byB0aGUgZ2l2ZW4gbGFiZWxzLiBUaGVyZWZvcmUsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gZmVlZCB0aGUgbGFzdCBvdXRwdXQgdGVuc29yIGludG8gYSBkZW5zZWx5DQpjb25uZWN0ZWQgY2xhc3NpZmllciBuZXR3b3JrIGxpa2UgdGhvc2Ugd2XigJlyZSBhbHJlYWR5IGZhbWlsaWFyIHdpdGg6IGEgc3RhY2sgb2YgZGVuc2UgbGF5ZXJzLg0KVGhlc2UgY2xhc3NpZmllcnMgcHJvY2VzcyB2ZWN0b3JzLCB3aGljaCBhcmUgMUQsIHdoZXJlYXMgdGhlIGN1cnJlbnQgb3V0cHV0IGlzIGEgM0QgdGVuc29yLiBGaXJzdCB3ZSBoYXZlIHRvIGZsYXR0ZW4gdGhlIDNEIG91dHB1dHMgdG8gMUQsIGFuZCB0aGVuIGFkZCBhIGZldyBkZW5zZSBsYXllcnMgb24gdG9wLg0KDQpgYGB7cn0NCm1vZGVsIDwtIG1vZGVsICU+JSANCiAgIyBOb3cgZmxhdHRlbiB0aGUgb3V0cHV0Lg0KICBsYXllcl9mbGF0dGVuKCkgJT4lIA0KICAjIEFmdGVyIHRoaXMgeW91J2xsIGp1c3QgaGF2ZSB0aGUgc2FtZSBETk4gc3RydWN0dXJlIGFzIHRoZSBub24gY29udm9sdXRpb25hbCB2ZXJzaW9uDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUgDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAnc29mdG1heCcpDQoNCg0KYGBgDQoNCioqQ29tcGlsZToqKiBDb25maWd1cmluZyBhIEtlcmFzIG1vZGVsIGZvciB0cmFpbmluZw0KDQpgYGB7cn0NCm1vZGVsICU+JSBjb21waWxlKA0KICBsb3NzID0gJ3NwYXJzZV9jYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknLA0KICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfYWRhbSgpLA0KICBtZXRyaWNzID0gYygnYWNjdXJhY3knKQ0KDQopDQptb2RlbCAlPiUgc3VtbWFyeSgpDQpgYGANCkZyb20gdGhlIHN1bW1hcnksIHdlIGV4cGVjdCBvdXRwdXQgb2YgdGhlIGZpcnN0IGNvbnZvbHV0aW9uIHRvIGJlIGEgMjh4MjggYnV0IHdlIG9idGFpbiBhIDI2eDI2LiBJZiB5b3UgaGF2ZSB3YXRjaGVkIHRoZSBlcGlzb2RlLCB5b3UgcHJvYmFibHkga25vdyB3aHkuIEEgMyBieSAzIGZpbHRlciByZXF1aXJpbmcgYSBuZWlnaGJvdXIgb24gYWxsIHNpZGVzIGNhbid0IHdvcmsgb24gdGhlIHBpeGVscyBhcm91bmQgdGhlIGVkZ2VzIG9mIHRoZSBwaWN0dXJlLiBZb3UgZWZmZWN0aXZlbHkgaGF2ZSB0byByZW1vdmUgb25lIHBpeGVsIGZyb20gdGhlIHRvcCxib3R0b20gbGVmdCBhbmQgcmlnbnQgYW5kIHRoaXMgcmVkdWNlcyB5b3VyIGRpbWVuc2lvbiBieSAyIG9uIGVhY2ggYXhpcy4gU28gYSAyOCBieSAyOCBiZWNvbWVzIGEgMjYgYnkgMjYuDQpBbHNvLCB0aGUgcG9vbGluZyBsYXllciBjbGVhcmx5IGhhbHZlcyB0aGUgZGltZW5zaW9ucyBvZiBlYWNoIGF4aXMuDQoNCkRpc3BsYXlpbmcgdGhlIGZpcnN0IDI1IGltYWdlcyBmcm9tIHRoZSB0ZXN0aW5nIHNldDoNCg0KDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoNSw1KSkNCnBhcihtYXI9YygwLCAwLCAxLjUsIDApLCB4YXhzPSdpJywgeWF4cz0naScpDQoNCmZvcihpIGluIDE6NTApew0KICBpbWcgPSB0ZXN0X2ltYWdlc1tpLCAsICwxXQ0KICBpbWcgPSB0KGFwcGx5KGltZywgMiwgcmV2KSkNCiAgaW1hZ2UoMToyOCwgMToyOCwgaW1nLCBjb2wgPSBncmF5KCgwOjI1NSkvMjU1KSwgeGF4dCA9ICduJywNCiAgICAgICAgeWF4dCA9ICduJykNCn0NCmBgYA0KDQoNCg0KIyAqKlZpc3VhbGl6aW5nIGV2ZXJ5IGZpbHRlciBvdXRwdXQgaW4gZWFjaCBjb252b2x1dGlvbiBhbmQgcG9vbGluZyBsYXllcioqDQoNCkZvciB0aGlzIHN0ZXAsIEkgdG9vayBhIGxpdHRsZSBkZXRvdXIgZnJvbSB3aGF0IHdhcyBkb25lIGluIHRoZSBQeXRob24gTm90ZWJvb2sgYnV0IGl0IGlsbHVzdHJhdGVzIHRoZSBzYW1lIGNvbmNlcHQ6IGhvdyB0aGUgY29udm9sdXRpb25zIGFwcGx5IGRpZmZlcmVudCBmaWx0ZXJzIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSBvdXIgaW5wdXQgaW1hZ2VzLg0KDQpgYGB7cn0NCiMgbGV0J3MgdmlzdWFsaXplIHRoZSBmaWx0ZXJzIGFwcGxpZWQgYnkgdGhlIGNvbnZvbHV0aW9ucyB1c2luZyBvdXINCiMgZmlyc3QgaW1hZ2UNCg0KIyB0aGVyZSBhcmUgMTAyOCBpbWFnZXMgb2YgbnVtYmVyIDcgaW4gdGhlIHRlc3RfaW1hZ2VzLg0KIyBJIGhhdmUgdXNlZCB0aGUgZmlyc3QgNyB3aGljaCBoYXBwZW5zIHRvIGJlIHRoZSBmaXJzdCBpbmRleC4NCiMgSSB5b3Ugd2FudCB0byBmaW5kIHRoZSBvdGhlciBpbmRpY2VzL3Jvd25hbWVzIHdpdGggbnVtYmVyIDcsIGRvIHRoaXM6DQoNCm1uaXN0X3NldmVuID0gd2hpY2godGVzdF9sYWJlbHMgPT0gNykNCg0KbW5pc3Rfc2V2ZW5bMToxMF0NCg0KIyByb3cgMSwgMTgsIDI3LCAzNSwgMzcuLi4gOTk5MSBhbGwgY29udGFpbiBkaWdpdCA3DQoNCg0KaW1nX3RlbnNvciA8LSB0ZXN0X2ltYWdlc1syNywgLCAsXQ0KDQojIHNhdmluZyB0aGUgbWF0cml4IHRoYXQgY29ycmVzcG9uZHMgdG8gaW1hZ2UgNw0Kd3JpdGUuY3N2KGltZ190ZW5zb3IsICJzZXZlbl9tbmlzdC5jc3YiKQ0KDQojIGxldCdzIHZpZXcgb3VyIGZpcnN0IGltYWdlDQpwbG90KGFzLnJhc3RlcihpbWdfdGVuc29yKSkNCg0KIyBleHRyYWN0aW5nIHRoZSBvdXRwdXRzIG9mIHRoZSB0b3AgNCBsYXllcnMNCmxheWVyX291dHB1dCA8LSBsYXBwbHkobW9kZWwkbGF5ZXJzWzE6NF0sIGZ1bmN0aW9uKGxheWVyKSBsYXllciRvdXRwdXQpDQoNCg0KIyBDcmVhdGVzIGEgbW9kZWwgdGhhdCB3aWxsIHJldHVybiB0aGVzZSBvdXRwdXRzLCBnaXZlbiB0aGUgbW9kZWwgaW5wdXQNCiMgVGhpcyBtb2RlbCBoYXMgb25lIGlucHV0IGFuZCBmb3VyIG91dHB1dHM6DQojIG9uZSBvdXRwdXQgcGVyIGxheWVyIGFjdGl2YXRpb24uDQphY3RpdmF0aW9uX21vZGVsIDwtIGtlcmFzX21vZGVsKGlucHV0cyA9IG1vZGVsJGlucHV0LCBvdXRwdXRzID0gbGF5ZXJfb3V0cHV0KQ0KDQoNCiMgdXNpbmcgdGhlIG1vZGVsIHRvIHByZWRpY3QgdGhlIG91dHB1dCBvZiB0aGUgZmlsdGVycyBpbiB0aGUgcmVwZWN0aXZlIGxheWVycw0KIyB3ZSBzdGlsbCBoYXZlIHRvIHJlc2hhcGUgb3VyIGltZ190ZW5zb3Igc2luY2UgY29udm9sdXRpb25zIHdvcmsgd2l0aA0KIyAzRCBpbnB1dHMNCg0KYWN0aXZhdGlvbnMgPC0gYWN0aXZhdGlvbl9tb2RlbCAlPiUgcHJlZGljdChhcnJheV9yZXNoYXBlKGltZ190ZW5zb3IsIGMoMSwgMjgsIDI4LCAxKSkpDQoNCmBgYA0KDQoqKmxldCdzIGV4dHJhY3QgdGhlIGZpcnN0IDQgY29udm9sdXRpb24gYW5kIHBvb2xpbmcgbGF5ZXJzKioNCg0KYGBge3J9DQpmaXJzdF9sYXllcl9hY3RpdmF0aW9uIDwtIGFjdGl2YXRpb25zW1sxXV0NCnNlY29uZF9sYXllcl9hY3RpdmF0aW9uIDwtIGFjdGl2YXRpb25zW1syXV0NCnRoaXJkX2xheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbWzNdXQ0KZm91cnRoX2xheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbWzRdXQ0KDQoNCmRpbShmaXJzdF9sYXllcl9hY3RpdmF0aW9uKQ0KIyB0aGUgZmlyc3QgbGF5ZXIgaGFzIDY0IGZpbHRlcnMgYXMgd2UgZGVmaW5lZCBlYXJsaWVyDQpgYGANCg0KDQpOZXh0IHdlIGRlZmluZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBoZWxwIHVzIHZpc3VhbGlzZSB0aGUgcmVzdWx0IG9mIGVhY2ggZmlsdGVyIGluIGVhY2ggb2YgdGhlIGxheWVyIGFjdGl2YXRpb25zIGFib3ZlLg0KDQpgYGB7cn0NCnBsb3RfY2hhbm5lbCA8LSBmdW5jdGlvbihjaGFubmVsKSB7DQogICMgcm90YXRpbmcgdGhlIGltYWdlcw0KICBpbWcgPC0gdChhcHBseShjaGFubmVsLCAyLCByZXYpKQ0KICBpbWFnZShpbWcsIGF4ZXMgPSBGQUxTRSwgYXNwPTEsY29sID0gZ3JheSgoMDoyNTUpLzI1NSkpDQp9DQpgYGANCg0KDQoqKlZpc3VhbGl6aW5nIHRoZSBjb252b2x1dGlvbnMgYW5kIHBvb2xpbmcgb24gb3VyIGZpcnN0IHRlc3QgaW1hZ2UqKg0KDQpUaGUgY29kZSBiZWxvdyBzYXZlcyB0aGUgaW1hZ2VzIGluIHlvdXIgY3VycmVudCBkaXJlY3RvcnkuDQoNCmBgYHtyfQ0KaW1hZ2VzX3Blcl9yb3cgPC0gMw0KZm9yIChpIGluIDE6Mikgew0KIGxheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbW2ldXQ0KIGxheWVyX25hbWUgPC0gbW9kZWwkbGF5ZXJzW1tpXV0kbmFtZQ0KIG5fZmVhdHVyZXMgPC0gZGltKGxheWVyX2FjdGl2YXRpb24pW1s0XV0NCiBuX2NvbHMgPC0gbl9mZWF0dXJlcyAlLyUgaW1hZ2VzX3Blcl9yb3cNCiBwbmcocGFzdGUwKCJtbmlzdF9zZXZlbl9hY3RpdmF0aW9uc18iLCBpLCAiXyIsIGxheWVyX25hbWUsICIucG5nIiksDQogd2lkdGggPSA1MDAsDQogaGVpZ2h0ID0gNTAwKQ0KIG9wIDwtIHBhcihtZnJvdyA9IGMobl9jb2xzLCBpbWFnZXNfcGVyX3JvdyksIG1haSA9IHJlcF9sZW4oMC4wMiwgNCkpDQogZm9yIChjb2wgaW4gMDoobl9jb2xzLTEpKSB7DQogZm9yIChyb3cgaW4gMDooaW1hZ2VzX3Blcl9yb3ctMSkpIHsNCiBjaGFubmVsX2ltYWdlIDwtIGxheWVyX2FjdGl2YXRpb25bMSwsLChjb2wqaW1hZ2VzX3Blcl9yb3cpICsgcm93ICsgMV0NCiBwbG90X2NoYW5uZWwoY2hhbm5lbF9pbWFnZSkNCiB9DQogfQ0KIHBhcihvcCkNCiBkZXYub2ZmKCkNCn0NCg0KaW1hZ2VzX3Blcl9yb3cgPC0gNA0KZm9yIChpIGluIDM6NCkgew0KIGxheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbW2ldXQ0KIGxheWVyX25hbWUgPC0gbW9kZWwkbGF5ZXJzW1tpXV0kbmFtZQ0KIG5fZmVhdHVyZXMgPC0gZGltKGxheWVyX2FjdGl2YXRpb24pW1s0XV0NCiBuX2NvbHMgPC0gbl9mZWF0dXJlcyAlLyUgaW1hZ2VzX3Blcl9yb3cNCiBwbmcocGFzdGUwKCJtbmlzdF9zZXZlbl9hY3RpdmF0aW9uc18iLCBpLCAiXyIsIGxheWVyX25hbWUsICIucG5nIiksDQogd2lkdGggPSA1MDAsDQogaGVpZ2h0ID0gNTAwKQ0KIG9wIDwtIHBhcihtZnJvdyA9IGMobl9jb2xzLCBpbWFnZXNfcGVyX3JvdyksIG1haSA9IHJlcF9sZW4oMC4wMiwgNCkpDQogZm9yIChjb2wgaW4gMDoobl9jb2xzLTEpKSB7DQogZm9yIChyb3cgaW4gMDooaW1hZ2VzX3Blcl9yb3ctMSkpIHsNCiBjaGFubmVsX2ltYWdlIDwtIGxheWVyX2FjdGl2YXRpb25bMSwsLChjb2wqaW1hZ2VzX3Blcl9yb3cpICsgcm93ICsgMV0NCiBwbG90X2NoYW5uZWwoY2hhbm5lbF9pbWFnZSkNCiB9DQogfQ0KIHBhcihvcCkNCiBkZXYub2ZmKCkNCn0NCg0KYGBgDQoNCg0KVGhlIG91dHB1dCBvZiBlYWNoIGZpbHRlciBpbiBlYWNoIGxheWVyIGlzIGFzIHNob3duIHJvdy13aXNlLg0KDQojIyMgVHJhbnNmb3JtYXRpb24gYnkgZmlyc3QgY29udm9sdXRpb24gbGF5ZXINCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYygzLDMpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKGZpcnN0X2xheWVyX2FjdGl2YXRpb24pWzRdKSB7DQogcGxvdF9jaGFubmVsKGZpcnN0X2xheWVyX2FjdGl2YXRpb25bMSwsLGldKQ0KIHBhc3RlMCgiZmlyc3QgbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KDQoNCg0KIyMjIFRyYW5zZm9ybWF0aW9uIGJ5IGZpcnN0IHBvb2xpbmcgbGF5ZXINCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYygzLDMpLCBtYWkgPSByZXBfbGVuKDAuMDIsIDQpKQ0KZm9yIChpIGluIDE6ZGltKHNlY29uZF9sYXllcl9hY3RpdmF0aW9uKVs0XSkgew0KIHBsb3RfY2hhbm5lbChzZWNvbmRfbGF5ZXJfYWN0aXZhdGlvblsxLCwsaV0pDQogcGFzdGUwKCJzZWNvbmQgbGF5ZXIiKQ0KfQ0KI3BuZygic2Vjb25kX2xheWVyLnBuZyIsIHdpZHRoID0gMTUwLCBoZWlnaHQgPSAxNTApDQpgYGANCg0KDQoNCiMjIyBUcmFuc2Zvcm1hdGlvbiBieSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXINCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYyg0LDQpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKHRoaXJkX2xheWVyX2FjdGl2YXRpb24pWzRdKSB7DQogcGxvdF9jaGFubmVsKHRoaXJkX2xheWVyX2FjdGl2YXRpb25bMSwsLGldKQ0KIHBhc3RlMCgidGhpcmQgbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KIyMjIFRyYW5zZm9ybWF0aW9uIGJ5IHNlY29uZCBwb29saW5nIGxheWVyDQoNCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYyg0LDQpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKGZvdXJ0aF9sYXllcl9hY3RpdmF0aW9uKVs0XSkgew0KIHBsb3RfY2hhbm5lbChmb3VydGhfbGF5ZXJfYWN0aXZhdGlvblsxLCwsaV0pDQogcGFzdGUwKCJmb3VydGggbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KR3JlYXQhIE5vdyB0aGVyZSBhcmUgYSBmZXcgdGhpbmdzIHRvIG5vdGU6DQoNCiogVGhlIGZpcnN0IGxheWVyIGFjdHMgYXMgYSBjb2xsZWN0aW9uIG9mIHZhcmlvdXMgZmlsdGVycy4gQXQgdGhhdCBzdGFnZSwgdGhlIG91dHB1dCBvZiB0aGUgbGF5ZXIgc2VlbXMgdG8gcmV0YWluIGFsbW9zdCBhbGwgb2YgdGhlIGluZm9ybWF0aW9uIHByZXNlbnQgaW4gdGhlIGluaXRpYWwgcGljdHVyZS4NCg0KKiBBcyB5b3UgZ28gaGlnaGVyLCB0aGUgb3V0cHV0cyBvZiB0aGUgbGF5ZXJzIGJlY29tZSBpbmNyZWFzaW5nbHkgYWJzdHJhY3QgYW5kIGxlc3MgdmlzdWFsbHkgaW50ZXJwcmV0YWJsZS4gVGhleSBiZWdpbiB0byBlbmNvZGUgaGlnaGVyLWxldmVsIGNvbmNlcHRzIHN1Y2ggYXMgdGhlICJoZWVsIiBhbmQgInZhbXAiIG9mIHRoZSBzaG9lLg0KSGlnaGVyIHByZXNlbnRhdGlvbnMgY2FycnkgaW5jcmVhc2luZ2x5IGxlc3MgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHZpc3VhbCBjb250ZW50cyBvZiB0aGUgaW1hZ2UsIGFuZCBpbmNyZWFzaW5nbHkgbW9yZSBpbmZvcm1hdGlvbiByZWxhdGVkIHRvIHRoZSBjbGFzcyBvZiB0aGUgaW1hZ2UuDQoNCg0KDQoNCg0KDQpZZXMhIFdlIGRpdCBpdCEhIPCfpKkgV2Ugc3VjY2Vzc2Z1bGx5IGltcGxlbWVudGVkIGNvbnZvbHV0aW9ucyBhbmQgcG9vbGluZyB0byBpbXByb3ZlIHRoZSBhY2N1cmFjeSBvZiBjb21wdXRlciB2aXNpb24gdXNpbmcgUi4gSW5kZWVkIFIsIGF0IGl0cyBjb3JlIGlzIGEgYmVhdXRpZnVsIGFuZCBlbGVnYW50IGxhbmd1YWdlLCB3ZWxsIGRlc2lnbmVkIGZvciBEYXRhIFNjaWVuY2Ug8J+Sli4NCg0KDQoNCkZvciB0aGUgZ3V5cyBhdCB0aGUgYmFjazoNCg0KIyAqSG93IHRvIG1ha2UgYSByYXN0ZXIgcGxvdCBvZiB0aGUgcGl4ZWwgdmFsdWVzIHdlIGp1c3Qgc2F2ZWQgYmVmb3JlKg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHhsKQ0Kc2V2ZW5fbW5pc3QgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL0FETUlOL0Rlc2t0b3AvRGVrdXQvcHBzL1NlbmlvciBZZWFyL0FydGlmaWNpYWwgSW50ZWxsaWdlbmNlL3NldmVuX21uaXN0LmNzdiIpDQoNCiMgdGhlIGRhdGFzZXQgd2UganVzdCBsb2FkZWQgY29udGFpbnMgdGhlIHBpeGVsIHZhbHVlcyB0aGF0IG1ha2UgdXAgYSB2YXJpYXRpb24gb2YgZGlnaXQgNw0KDQojIGNvbnZlcnRpbmcgdG8gYSBtYXRyaXggYW5kIHJlbW92aW5nIHRoZSBmaXJzdCBydWRlbHkgYWRkZWQgY29sdW1uDQoNCnNldmVuX21uaXN0IDwtIGFzLm1hdHJpeChzZXZlbl9tbmlzdCkNCnNldmVuX21uaXN0IDwtIHNldmVuX21uaXN0WywtMV0NCg0KIyBwbG90dGluZyBhIHJhc3RlciBvYmplY3Qgb2YgdGhlIG1hdHJpeA0KDQpwbG90KGFzLnJhc3RlcihzZXZlbl9tbmlzdCkpDQpgYGANCg0KDQpUcnVseSwNCmBFcmljLCBJYW4sIFNhbWANCg0KDQojICoqUmVmZXJlbmNlIE1hdGVyaWFsKioNCg0KKiBNYWNoaW5lIExlYXJuaW5nIEZvdW5kYXRpb25zOiBFcCAjNCAtIFtDb2Rpbmcgd2l0aCBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3Jrc10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kZDhINGZpTDlZYyZ0PTFzKQ0KDQoqIE1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnM6IEVwICMzIC0gW0NvbnZvbHV0aW9ucyBhbmQgcG9vbGluZ10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1QQ2dMbXprUk0zOCZ0PTEpDQoNCiogRGVlcCBMZWFybmluZyB3aXRoIFIgYnkgRnJhbmNvaXMgQ2hvbGxldCBhbmQgSi5KLkFsbGFpcmUNCg0KKiBUaGUgW1IgaW50ZXJmYWNlIHRvIEtlcmFzXShodHRwczovL2tlcmFzLnJzdHVkaW8uY29tKSB3ZWJzaXRlDQoNCiogTGFiIDQtIFtVc2luZyBDb252b2x1dGlvbnNdKGh0dHBzOi8vY29sYWIucmVzZWFyY2guZ29vZ2xlLmNvbS9naXRodWIvbG1vcm9uZXkvbWxkYXktdG9reW8vYmxvYi9tYXN0ZXIvTGFiNC1Vc2luZy1Db252b2x1dGlvbnMuaXB5bmIjc2Nyb2xsVG89emxkRVhTc0Y4Tm96KSANCg0KKiBMYWIgMy0gW1doYXQgYXJlIENvbnZvbHV0aW9uc10oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWI0LVVzaW5nLUNvbnZvbHV0aW9ucy5pcHluYiNzY3JvbGxUbz16bGRFWFNzRjhOb3opIA0KDQoqIEV4ZXJjaXNlIGZvciB0aGlzIGVwaXNvZGU6IFtFeGVyY2lzZSAzXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L2RsYWljb3Vyc2UvYmxvYi9tYXN0ZXIvRXhlcmNpc2VzL0V4ZXJjaXNlJTIwMyUyMC0lMjBDb252b2x1dGlvbnMvRXhlcmNpc2UlMjAzJTIwLSUyMFF1ZXN0aW9uLmlweW5iI3Njcm9sbFRvPXNmUVJ5YUpXQUlkZykNCg0KKiBNQVRMQUIgRGVlcCBMZWFybmluZyBieSBQaGlsIEtpbQ0KDQoNCg0K