QTW 7333 Module 12: Convolutional Neural Networks Study
Guide
1. What Is a Convolution?
- A convolution is the integral of the product of two
functions after one is reversed and shifted.
- In discrete terms, we “slide” a small function (called a
filter or kernel) across an input
(like an image or signal), and compute an output value based on their
overlap.
Mathematical Representation:
For continuous functions:
y(t) = ∫ f(τ) g(t - τ) dτ
For discrete functions (used in images and signals):
y[n] = Σ f[k] * g[n - k]
In neural networks, this becomes:
output = Σ (input * filter)
This is a dot product between the filter and the
part of the input it overlaps.
2. Convolution in Neural Networks
- We slide the filter (e.g., a 3x3 matrix) over the input (e.g., an
image matrix).
- At each position, we take an element-wise multiplication of
overlapping values, then sum to get a single number.
- That number becomes a feature in the output (also
called a feature map or activation
map).
Example:
If you convolve a 5x5 image with a 3x3 filter: - Output size becomes
(5-3)+1 = 3 (in each dimension), unless you apply
padding.
3. Padding
- Padding helps maintain the original size of the
image after convolution.
- We add extra pixels (usually 0s) around the border.
Without Padding: Output size = (W - F + 1)
With Padding (P): Output size = ((W - F + 2P) / S) +
1
Where: - W = input width/height - F = filter size - P = padding - S =
stride
4. Stride
- Stride controls how many pixels we move the filter at a time.
- Stride = 1 → move filter 1 pixel at a time.
- Larger stride → smaller output and less
overlap.
5. Filter Examples
Each of these is used to extract different features (edges, textures,
etc.).
6. Neural Network Architecture Recap
From Module 11 (transcript):
Each neuron is essentially a regression
function:
z = Wx + b → σ(z)
Where: - W = weights - x = inputs - b = bias - σ = activation
function (e.g., sigmoid, ReLU)
In CNNs, this regression is replaced with convolution
operations.
7. Matrix Dimensions & Output Size
(Important)
If: - Input = 28x28 - Filter = 3x3 - Padding = 1 - Stride = 1
Then: - Output = ((28 - 3 + 2×1) / 1) + 1 = 28
→ Output has same dimension as input.
8. Summary of Key Concepts
- Convolution: Overlapping of input and filter to
extract features.
- Padding: Adds border to control output size.
- Stride: Controls step size of the filter
movement.
- Filters/Kernels: Detect edges, textures,
patterns.
- Output: Feature maps used in downstream
layers.
- Multiple filters: Used to extract different feature
types.
9. Key Takeaways
- Convolutions are powerful because they preserve spatial
relationships.
- CNNs learn filters automatically during training.
- Padding + stride lets you control the size of your
output feature maps.
- Edge detection and blurring are
simple real-world examples of convolution filters.
10. Practice Questions
- What does a convolution operation do in image processing?
- Why do we use padding in convolutional neural networks?
- How does the stride affect the output size?
- What happens when you apply an edge detection filter to an
image?
- Derive the output size of a convolutional layer given the input
size, filter size, padding, and stride.
- What is the role of the bias vector in a convolutional layer?
Suggested Reading
From the Elements of Statistical Learning (Hastie, Tibshirani,
Friedman):
- Chapter 11: Neural Networks
- Chapter 5: Basis Expansions and Regularization (for understanding
convolution-like operations)
URL for textbook:
https://web.stanford.edu/~hastie/ElemStatLearn/
🧠 Module 12: Convolutional Neural Networks — Study Guide
1. Core Concept: What is a Convolution?
Definition:
A convolution is the overlap between two functions,
calculated by sliding a function (called a filter or
kernel) over input data (usually an image), multiplying
overlapping values, and summing the results.
This helps extract important features like edges, textures, or color
gradients from images.
2. Mathematical Representation
Continuous form:
y(t) = ∫ f(τ)·g(t−τ) dτ
Discrete (for digital images and neural
networks):
y[i, j] = Σ Σ input[m+i, n+j] * filter[m, n]
Where: - input
is your image matrix -
filter
is the kernel (e.g., edge detection) -
y
is the output feature map
3. CNNs in Action: Python Code Example (Beginner-Friendly)
import numpy as np
from scipy.signal import convolve2d
import matplotlib.pyplot as plt
# Example input image (5x5)
image = np.array([
[1, 2, 3, 0, 1],
[0, 1, 2, 3, 0],
[1, 0, 1, 2, 1],
[2, 1, 0, 1, 2],
[1, 2, 1, 0, 1]
])
# Example edge detection filter (3x3)
filter_kernel = np.array([
[0, -1, 0],
[-1, 4, -1],
[0, -1, 0]
])
# Apply convolution
output = convolve2d(image, filter_kernel, mode='valid')
print("Output after convolution:")
print(output)
Output: A smaller matrix that highlights where edges
or features were detected.
4. CNN Layers Breakdown
- Convolutional Layer: Applies filters to extract
features.
- Padding: Adds zeros around the image to preserve
size.
- Stride: How many steps the filter moves.
- Activation (ReLU): Applies a function like
max(0, x)
to make the model non-linear.
- Pooling: Reduces dimensionality (MaxPool,
AvgPool).
5. Visual Example from Class Slides
Averaging filter smooths the image using a
uniform kernel.
Edge detection filter highlights boundaries
using:
[[ 0, -1, 0],
[-1, 4, -1],
[ 0, -1, 0]]
6. Key Takeaways
- Convolution = Sliding + Multiplying + Summing
- Padding helps maintain the input size
- Stride affects how much we reduce dimensionality
- Filters are learned during training to extract
useful features
- Convolutions are why CNNs outperform traditional
methods in image tasks
7. Relevant Questions to Test Yourself
- What does a convolution do in a neural network?
- Why is padding used in convolution layers?
- What effect does increasing the stride have?
- How does an edge detection filter work?
- What is the output size if you apply a 3x3 filter to a 5x5 image
with no padding and stride 1?
8. Layman’s Explanation – Pizza Cutter Analogy
Imagine you’re cutting a large pizza with a stencil
that is 3x3 inches in size. You place your stencil on the pizza, look at
just that square, and rate it from 1 to 10 based on how much pepperoni
it has.
Then, you move the stencil over a little (by 1 inch = stride), and do
it again. You’re scanning the entire pizza, one small patch at a
time.
Your stencil is the filter. The pizza is the
image. Your rating is the output
feature. If you want the edges to also be analyzed (not just
the center), you place napkins (zeros) around the edge — that’s
padding.
This is what convolution does: it helps see the important
parts of an image (like cheese, crust, or pepperoni) and
condense it into useful info — which is what a neural network needs to
make a decision.
9. Textbook References for Deeper Reading
From “The Elements of Statistical Learning” by Hastie et
al.
- Chapter 11: Neural Networks
URL: https://web.stanford.edu/~hastie/ElemStatLearn/
From “The Statistical Sleuth” by Ramsey &
Schafer
- Not image-focused but useful for regression background
Here’s your complete study guide for Convolutional Neural
Networks (CNNs), integrating all class visuals, transcripts,
textbook knowledge, mathematical concepts, Python code, key takeaways,
and an easy-to-understand real-world analogy.
QTW 7333 – Module 12
Study Guide: Convolutional Neural Networks (CNNs)
🧠 Overview of CNNs
A Convolutional Neural Network is a type of neural network
architecture optimized for processing images. It mimics
the human visual cortex where only nearby
neurons communicate—meaning filters (kernels)
focus on local patterns (edges, corners, etc.) instead of the entire
image at once.
🧩 Layers in a CNN
- Convolutional Layer
- Activation Function (usually ReLU)
- Pooling Layer
- Flatten Layer
- Dense (Fully Connected) Layer
- Output Layer (often Softmax for
classification)
🧮 Mathematical Concepts
1. Convolution Layer:
Applies a filter (kernel) across the image.
Equation (2D discrete convolution):
Y(i, j) = Σ_m Σ_n X(i+m, j+n) · K(m, n)
Where:
- X = input image
- K = kernel
- Y = output feature map
2. Pooling Layer:
Reduces spatial size by selecting max or
average values.
- Max Pooling (2×2 with stride 2):
From:
[[1, 1],
[5, 6]] → max = 6
3. Flatten Layer:
Converts multi-dimensional tensor into 1D vector for dense
layers.
Example: [[0, -1, 0],
[-1, 4, -1],
[0, -1, 0]]
→ [0, -1, 0, -1, 4, -1, 0, -1, 0]
💻 Python Code Example (Simple CNN with Keras)
import tensorflow as tf
from tensorflow.keras import layers, models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
# Flatten and Dense
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
🎨 Layman’s Analogy
Imagine you’re looking at a Where’s Waldo book: -
Your eyes scan small sections of the image (like a
filter). - You’re checking patterns like hats, glasses,
red-white shirts. - When something stands out, your brain
stores that info. - Once enough features are collected, you
decide where Waldo is.
Similarly: - Convolutional layers scan for patterns.
- Pooling summarizes those patterns (shrinks detail). -
Flattening stacks everything into a list. -
Dense layers make a final decision (e.g., this is
Waldo!).
📘 Textbook Support
From Elements of Statistical Learning: -
Chapter 11 – Neural Networks
https://web.stanford.edu/~hastie/ElemStatLearn/
Also explore CS231n by Stanford (Visual CNN
walkthrough):
http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture5.pdf
🧠 Key Takeaways
- CNNs are ideal for image classification due to spatial
awareness.
- Convolutional layers extract local patterns.
- Pooling layers reduce spatial dimensions.
- Flattening reshapes the image into a 1D vector for Dense
layers.
- CNNs reduce parameters compared to traditional
dense networks while improving accuracy.
❓ Practice Questions
- What is the purpose of a convolutional layer?
- What does max pooling do? Why is it useful?
- Why do we flatten data before sending it to a dense layer?
- In what way is a CNN biologically inspired?
- How does increasing the number of filters affect the output?
📘 QTW 7333 – Module 12: Transfer Learning Study
Guide
🔍 1. Concept Overview: What Is Transfer Learning?
Definition:
Transfer Learning is a technique where we take a pre-trained
model (often trained on large datasets using powerful hardware)
and reuse its early layers while retraining
only the final layers for a new, often smaller, task.
Instead of training everything from scratch, we “transfer”
the learning from one task to another.
🧠 2. Why Does It Work?
From your transcript: - Early layers (top of the network) learn
general features (edges, colors, shapes). - Later
layers (dense layers) learn specific task-related
features. - General features are reusable,
even across different tasks (e.g., dogs vs. cats, or cars vs. planes). -
Saves time, data, and compute resources.
💻 4. Python Code Example (Using Keras)
from tensorflow.keras.applications import VGG16
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras import Input
# Load pre-trained VGG16 without top layers
base_model = VGG16(weights='imagenet', include_top=False, input_tensor=Input(shape=(224, 224, 3)))
# Freeze base model layers
for layer in base_model.layers:
layer.trainable = False
# Add new dense layers on top
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(128, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x) # for 10-class problem
# Final model
model = Model(inputs=base_model.input, outputs=predictions)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
This uses ImageNet-trained features and trains only
the classifier on your custom data.
📚 5. Reference from Textbooks
The Elements of Statistical Learning
(Hastie, Tibshirani, Friedman)
- Chapter 11 (Neural Networks): explains how lower-level representations
generalize across problems.
Link: https://web.stanford.edu/~hastie/ElemStatLearn/
✅ 6. Key Takeaways
- You can reuse expensive, pre-trained models to solve your own
problem.
- You only need to train a few layers (usually the last dense
layers).
- Great for limited data, faster
training, and cross-domain tasks.
- Pre-trained models are available via TensorFlow
Hub, PyTorch Hub, Hugging
Face, etc.
❓ 7. Relevant Questions
- Why is transfer learning useful in deep learning?
- Which parts of the model are typically frozen and which are
retrained?
- Can transfer learning work across domains (e.g., vision to
language)?
- What role does the vanishing gradient play in motivating transfer
learning?
🧸 8. Layman Explanation (Real-World Analogy)
Imagine you’re learning to play music.
- You trained 10 years on the piano.
- Now you want to learn the guitar.
You don’t need to relearn music theory, rhythm, notes, or
timing. You just need to learn how to hold and strum
the guitar.
That’s transfer learning: - Your music theory = pre-trained layers
(frozen). - Learning guitar specifics = final layers (retrained). -
You’re not starting from scratch — you’re adapting.
Just like your brain doesn’t relearn how sound works each time, a CNN
doesn’t need to relearn edge detection or textures when classifying new
images.
📘 QTW 7333 – Module 12: CNN Part I – Set Up Your Data Study
Guide
🔍 1. What Are We Doing?
We’re: - Importing CIFAR-10 image dataset - Visualizing the dataset -
Normalizing the pixel values - Preparing everything to feed into a
Convolutional Neural Network (CNN)
🧪 2. Why Normalize and Visualize?
- Normalization scales pixel values from
[0, 255]
to [0.0, 1.0]
→ helps the neural
network converge faster.
- Visualization lets us verify that the images and
labels are correctly aligned before training.
📚 3. Textbook Support
From The Elements of Statistical Learning by Hastie,
Tibshirani & Friedman:
- Chapter 11 (Neural Networks)
- Chapter 7 (Model Assessment and Selection)
Link: https://web.stanford.edu/~hastie/ElemStatLearn/
Also helpful: TensorFlow official docs:
https://www.tensorflow.org/tutorials/images/cnn
📊 4. Mathematical Explanation
Each image is a tensor of shape (32, 32, 3)
(height, width, RGB channels).
Normalization:
For each pixel value \(x\) in the
image:
\(x_{\text{normalized}} =
\frac{x}{255.0}\)
Labels are integer-encoded:
e.g., 0 = airplane, 1 = car, …, 9 = truck
💻 5. Python Code (Beginner-Friendly Setup)
# Step 1: Import libraries
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
# Step 2: Load CIFAR-10 dataset
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
# Step 3: Normalize pixel values to [0, 1]
train_images, test_images = train_images / 255.0, test_images / 255.0
# Step 4: Define class names
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
# Step 5: Plot first 25 images
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1)
plt.xticks([]) # Remove x-axis
plt.yticks([]) # Remove y-axis
plt.grid(False)
plt.imshow(train_images[i])
plt.xlabel(class_names[train_labels[i][0]])
plt.show()
✅ 6. Key Takeaways
- CIFAR-10 is a great starter dataset for image
classification with 10 common object classes.
- Normalization is essential in neural networks to
ensure pixel values are in a standard range.
- Plotting images helps validate data quality before training.
- This setup phase is crucial. Garbage in = garbage out.
❓ 7. Questions to Test Yourself
- Why do we divide pixel values by 255 in image datasets?
- What are the dimensions of each CIFAR-10 image?
- Why is it useful to visualize the images before training a CNN?
- What does
train_labels[i][0]
mean in the plotting
loop?
🧸 8. Layman’s Analogy: Filing Photos for an Album
Imagine you’re creating a photo album: - Each photo
= an image - The photo label = the name written below
it (dog, truck, etc.) - But all your photos are in different
formats and brightness levels.
Before pasting them in: 1. You resize all to the same dimensions
(32x32 pixels). 2. You adjust brightness (normalization). 3. You label
them (class names). 4. Then you lay them out in the album
(plotting).
This setup makes your album (dataset) organized and easy to interpret
— just like it makes the CNN’s job easier during training.
Here is your complete Study Guide for CNN Part II: Building
the Model from QTW 7333, including textbook links, transcript
summary, mathematical explanation, Python code, key takeaways, self-test
questions, and a beginner analogy.
📘 QTW 7333 – Module 12: CNN Part II – Building the
Model
🔍 1. What Are We Doing in This Module?
We’re: - Building a CNN architecture using Keras’
Sequential
API. - Adding convolution, pooling, flattening,
and dense layers. - Compiling the model with an optimizer and loss
function. - Training the model and validating performance. - Making
predictions and visualizing results.
🧠 2. Key Concepts
- Conv2D: Applies convolutional filters to extract
patterns.
- MaxPooling2D: Reduces spatial dimensions, retains
important features.
- Flatten: Converts 2D output to 1D for dense layer
input.
- Dense (fully connected layer): Final decision
layers.
- Softmax: Output layer for classification across
multiple classes.
🧮 3. Mathematical Representation
Each Conv2D layer:
output = ReLU(W * input + b)
Where: - *
= convolution operation - W
=
filter/kernel weights - b
= bias - ReLU
=
max(0, x), element-wise activation
MaxPooling2D:
Reduces matrix by selecting the max value in each window (e.g.,
2x2).
Dense layer:
output = Softmax(Wx + b)
💻 4. Complete Python Code
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
import numpy as np
# Load and normalize CIFAR-10
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
train_images, test_images = train_images / 255.0, test_images / 255.0
# Define CNN model
my_model = models.Sequential()
my_model.add(layers.Conv2D(32, (2, 2), activation='relu', input_shape=(32, 32, 3)))
my_model.add(layers.Conv2D(64, (3, 3), activation='relu'))
my_model.add(layers.MaxPooling2D((2, 2)))
my_model.add(layers.Conv2D(17, (2, 3), activation='relu'))
my_model.add(layers.Conv2D(14, (4, 4), activation='relu'))
my_model.add(layers.MaxPooling2D((2, 2)))
my_model.add(layers.Flatten())
my_model.add(layers.Dense(100, activation='relu'))
my_model.add(layers.Dense(10, activation='softmax'))
# Compile the model
my_model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['accuracy'])
# Train the model
history = my_model.fit(train_images, train_labels, epochs=5, batch_size=50,
validation_data=(test_images, test_labels))
# Predict and visualize results
results = my_model.predict(test_images)
# Show predictions vs true labels
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1)
plt.xticks([]); plt.yticks([]); plt.grid(False)
plt.imshow(test_images[i], cmap=plt.cm.binary)
true_label = test_labels[i][0]
pred_label = np.argmax(results[i])
plt.xlabel(f"True: {true_label}, Pred: {pred_label}")
plt.show()
📊 5. Accuracy Output Example
Training output (sample):
- val_accuracy: 0.6112 → 0.6368 → 0.6776
- Each epoch improves accuracy.
- Final prediction probabilities have shape (10000, 10), one row per
test image, 10 columns per class.
✅ 6. Key Takeaways
- CNNs excel at image recognition because they learn spatial
hierarchies (edges → shapes → objects).
- ReLU is the default activation for convolutional layers.
- Softmax turns raw outputs into probabilities for multi-class
classification.
- You must flatten the convolutional output before
connecting it to a dense layer.
- Model performance improves with deeper
architectures, good normalization, and
more epochs.
❓ 7. Practice Questions
- What does the Flatten layer do?
- Why do we use MaxPooling in a CNN?
- What does the final Dense(10, activation=‘softmax’) layer
represent?
- Why is the Adam optimizer commonly used?
- What shape do the CNN predictions have and why?
🧸 8. Layman Explanation – The “Photo Sorting Machine”
Imagine building a smart photo sorter: - The first
machine part looks for edges (where colors change
suddenly). - The next part identifies shapes (cars,
cats, etc.). - Finally, a decision-maker reads everything and says,
“This is a truck!”
Each “part” is a layer in your neural network: -
Convolution layers = find patterns (like textures) -
Pooling layers = zoom out to reduce clutter -
Dense layers = make decisions based on what was found -
Softmax = gives the final vote (percent chance it’s
each object)
Just like sorting your photos by eye, CNNs do this automatically—but
with math and training!
📚 Textbook Support
The Elements of Statistical Learning
- Chapter 11: Neural Networks (pg. 392+)
https://web.stanford.edu/~hastie/ElemStatLearn/
TensorFlow Tutorial (Official)
https://www.tensorflow.org/tutorials/images/cnn
—generate this as: - A Jupyter Notebook for
immediate use? - A PDF Study Sheet for printing? - An
interactive HTML presentation with sliders?
Perfect. I’ve compiled your materials into a structured study guide
that covers the following topics:
- Convolutional Layer Fundamentals
- Transfer Learning Concepts
- CNN Part I: Dataset Setup
- CNN Part II: Model Building
- Transfer Learning: VGG16 + Custom Classifier
I’ll walk you through each section in an easy-to-follow format:
Convolutional Neural Networks: Beginner Study Guide
Section 1: What is a Convolution?
Concept:
A convolution is when we slide a filter (small matrix) over data (like
an image) to extract meaningful features (like edges or patterns). Each
overlap is multiplied element-wise and summed into a new output
value.
Math:
If I is an image matrix and K is a
filter:
S(i,j) = ∑∑ K(m,n) * I(i+m, j+n)
This is done for all valid positions of the filter.
Python Example:
import numpy as np
from scipy.signal import convolve2d
image = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
kernel = np.array([[0, 1],
[1, 0]])
result = convolve2d(image, kernel, mode='valid')
print(result)
Key Terms: - Padding: Adds extra
borders to maintain original size. - Stride: How many
steps the filter moves. - Filter: Also called a
kernel.
Takeaways: - Filters are learned during training. -
Convolutions detect patterns like edges, color, and textures.
Relevant Questions: - Why do we use padding? - What
happens when you increase the stride?
Layman Analogy:
Imagine using a small window to look at different parts of a big
painting. Each time, you write down a summary of what you see (like
color intensity or sharpness). You repeat this until you’ve scanned the
whole picture.
Section 2: Transfer Learning Explained
Concept:
Transfer learning lets us reuse a powerful pretrained model (like VGG16)
and just train the final layers on our specific task.
Why?
Advanced models take weeks and lots of compute. Transfer learning lets
us use pretrained features and train only the “head” (final layers).
Diagram Summary:
Left = pretrained convolution layers
Right = new dense layers
We keep the left side “frozen” and train only the right.
Mathematics: If F is the feature
extractor and W are trainable weights:
Prediction = softmax(W * F(x))
Python Example:
base_model = tf.keras.applications.VGG16(input_shape=(160,160,3),
include_top=False,
weights='imagenet')
base_model.trainable = False
Takeaways: - Saves compute and time. - Works with
few samples. - Can be used across domains.
Relevant Questions: - What layers should you freeze?
- Why is softmax used in the final layer?
Layman Analogy:
Imagine you’re learning to bake cakes. Instead of learning everything
from scratch, you buy a cake mix (pretrained model) and just focus on
decorating it to your style (custom layers).
Section 3: CNN, Part I: Set Up Your Data
Code Snippet:
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
train_images = train_images / 255.0 # normalize pixel values
test_images = test_images / 255.0
Visualization:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1)
plt.xticks([]), plt.yticks([])
plt.grid(False)
plt.imshow(train_images[i], cmap=plt.cm.binary)
plt.xlabel(class_names[train_labels[i][0]])
plt.show()
Takeaways: - Normalizing image data (0–255 → 0–1) is
crucial. - Visualizing helps verify your labels and structure.
Relevant Questions: - Why normalize pixel values? -
What does CIFAR-10 contain?
Section 4: CNN, Part II: Build the Model
Architecture Overview:
model = models.Sequential()
model.add(layers.Conv2D(32, (2,2), activation='relu', input_shape=(32,32,3)))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (4,3), activation='relu'))
model.add(layers.Conv2D(128, (4,4), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(100, activation='relu'))
model.add(layers.Dense(10, activation='softmax')) # For 10 classes
Compile and Train:
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=50, validation_data=(test_images, test_labels))
Takeaways: - Conv2D
layers extract
features. - MaxPooling2D
reduces size and keeps important
features. - Dense
layers make final predictions.
Relevant Questions: - What does the flatten layer
do? - Why is softmax good for multiclass classification?
Layman Analogy:
Imagine your model is like a detective. The convolutional layers are
like the detective gathering clues (edges, colors), the flatten layer
organizes all the clues in a single file, and the dense layers use that
file to figure out: “Is this a cat or a truck?”
Section 5: Transfer Learning in Practice (VGG16 + Cats vs
Dogs)
Steps Summary: 1. Load dataset with
image_dataset_from_directory
2. Normalize with
Rescaling
3. Load VGG16 base (exclude top) 4. Freeze base
model 5. Add global average pooling, dropout, and dense output 6.
Compile and train
Key Code:
base_model = tf.keras.applications.VGG16(include_top=False, weights='imagenet', input_shape=(160,160,3))
base_model.trainable = False
model = tf.keras.Sequential([
tf.keras.layers.Rescaling(1./127.5, offset=-1),
base_model,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(1) # binary classification
])
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
model.fit(train_dataset, epochs=10, validation_data=validation_dataset)
Takeaways: - VGG16 has 14 million+ parameters; we
only train a few hundred. - Fast training, even on small datasets. -
Accuracy improves in just a few epochs.
Relevant Questions: - What is the difference between
categorical and binary crossentropy? - What does dropout do?
Layman Analogy:
Think of the VGG16 model as an expert art critic who has studied
millions of paintings. You’re training a small assistant to only tell
apart cats and dogs. You don’t retrain the expert, just use their
opinions (features) and teach your assistant how to interpret them for a
very specific task.
Here’s a distilled synthesis from everything we’ve done
today—including the screenshots, class transcripts, and the concepts in
your PDF materials—into three general takeaways and
three thoughtful questions for Dr. Slater:
Three General Takeaways
1. Transfer Learning is a Game-Changer for Resource-Limited
Training
By freezing the convolutional base of pretrained models (like VGG16) and
training only the classifier head, we unlock the power of highly complex
models without needing massive compute. This approach significantly
accelerates training while maintaining strong performance, even with
limited data.
2. CNNs Rely Heavily on Proper Data Handling and
Architectural Balance
Performance is strongly influenced by seemingly simple preprocessing
steps (e.g., normalization by dividing by 255, resizing images, proper
label encoding). Additionally, the depth of convolution and pooling
layers must be balanced with memory constraints and task complexity.
3. The Shift from Dense to Convolutional Networks Reflects a
Change in How Models “See” Data
Dense networks treat input as flat vectors; CNNs treat it spatially.
This is crucial for images, where structure and locality matter. CNNs
learn hierarchical features—from edges in early layers to complex shapes
in deeper ones—mimicking how visual cortex neurons operate.
Three Thought-Provoking Questions for
Dr. Slater
1. Given the benefits of transfer learning, how do we assess
when it’s more appropriate to fine-tune versus freeze layers, especially
when working with scientific or niche datasets like medical scans or
satellite imagery?
2. In real-world deployment, how do we mitigate the risks of
CNN misclassification (e.g., frog vs. dog) in high-stakes applications
such as autonomous vehicles, military surveillance, or healthcare
diagnostics?
3. From a pedagogical or research standpoint, where do you
see the biggest conceptual gaps in student understanding when
transitioning from dense to convolutional neural networks—and how can we
better bridge that leap, perhaps with visual tools or hands-on
demos?
\section*{General Takeaways from CNNs and Transfer Learning}
\begin{enumerate}
\item \textbf{Transfer Learning is a Game-Changer for Resource-Limited Training} \\
Freezing the convolutional base of pretrained models like VGG16 and training only the dense classifier head allows us to leverage powerful models without the need for massive compute resources. This greatly reduces training time while maintaining strong performance, especially when working with smaller datasets.
\item \textbf{CNNs Rely Heavily on Proper Data Handling and Architectural Balance} \\
Performance is significantly influenced by preprocessing steps such as normalization (e.g., dividing pixel values by 255), image resizing, and label formatting. The architecture’s depth (number and size of convolution and pooling layers) must be tuned in relation to the complexity of the task and available memory.
\item \textbf{The Shift from Dense to Convolutional Networks Reflects a Change in How Models "See" Data} \\
Dense networks treat data as flat vectors; CNNs process it spatially. This is essential for image tasks where spatial locality matters. CNNs build hierarchical feature maps—from simple edges to complex objects—similar to the human visual system.
\end{enumerate}
\vspace{0.5cm}
\section*{Thought-Provoking Questions for Dr. Slater}
\begin{enumerate}
\item \textbf{How should we decide whether to freeze or fine-tune pretrained convolutional layers when working with domain-specific datasets (e.g., medical imaging, satellite data, or environmental analysis)?}
\item \textbf{What safeguards or model evaluation strategies would you recommend when deploying CNNs in high-stakes environments (e.g., defense, transportation, healthcare) to reduce risks from misclassifications like mistaking a frog for a dog?}
\item \textbf{From your experience teaching deep learning, where do students typically struggle most when transitioning from dense to convolutional networks, and how could we better support that shift using visual or interactive resources?}
\end{enumerate}
Jessica McPhaul 7333 – QTW Module 12 Presession Week 12
________________________________________ Three General Takeaways 1.
Transfer Learning is a Game-Changer for Resource-Limited Training By
freezing the convolutional base of pretrained models (like VGG16) and
training only the classifier head, we unlock the power of highly complex
models without needing massive compute. This approach significantly
accelerates training while maintaining strong performance, even with
limited data. 2. CNNs Rely Heavily on Proper Data Handling and
Architectural Balance Performance is strongly influenced by seemingly
simple preprocessing steps (e.g., normalization by dividing by 255,
resizing images, proper label encoding). Additionally, the depth of
convolution and pooling layers must be balanced with memory constraints
and task complexity. 3. The Shift from Dense to Convolutional Networks
Reflects a Change in How Models “See” Data Dense networks treat input as
flat vectors; CNNs treat it spatially. This is crucial for images, where
structure and locality matter. CNNs learn hierarchical features—from
edges in early layers to complex shapes in deeper ones—mimicking how
visual cortex neurons operate. ________________________________________
3 Questiosn 1. Given the benefits of transfer learning, how do we assess
when it’s more appropriate to fine-tune versus freeze layers, especially
when working with scientific or niche datasets like medical scans or
satellite imagery? 2. In real-world deployment, how do we mitigate the
risks of CNN misclassification (e.g., frog vs. dog) in high-stakes
applications such as autonomous vehicles, military surveillance, or
healthcare diagnostics? 3. From a pedagogical or research standpoint,
where do you see the biggest conceptual gaps in student understanding
when transitioning from dense to convolutional neural networks—and how
can we better bridge that leap, perhaps with visual tools or hands-on
demos? ________________________________________
LS0tDQp0aXRsZTogIjczMzMgTW9kdWxlIDEyIENOTiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KKipRVFcgNzMzMyBNb2R1bGUgMTI6IENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzIFN0dWR5IEd1aWRlKioNCg0KKioxLiBXaGF0IElzIGEgQ29udm9sdXRpb24/KioNCg0KLSBBICoqY29udm9sdXRpb24qKiBpcyB0aGUgaW50ZWdyYWwgb2YgdGhlIHByb2R1Y3Qgb2YgdHdvIGZ1bmN0aW9ucyBhZnRlciBvbmUgaXMgcmV2ZXJzZWQgYW5kIHNoaWZ0ZWQuDQotIEluIGRpc2NyZXRlIHRlcm1zLCB3ZSAic2xpZGUiIGEgc21hbGwgZnVuY3Rpb24gKGNhbGxlZCBhICoqZmlsdGVyKiogb3IgKiprZXJuZWwqKikgYWNyb3NzIGFuIGlucHV0IChsaWtlIGFuIGltYWdlIG9yIHNpZ25hbCksIGFuZCBjb21wdXRlIGFuIG91dHB1dCB2YWx1ZSBiYXNlZCBvbiB0aGVpciBvdmVybGFwLg0KDQoqKk1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbjoqKg0KDQpGb3IgY29udGludW91cyBmdW5jdGlvbnM6DQogIA0KICB5KHQpID0g4oirIGYoz4QpIGcodCAtIM+EKSBkz4QNCg0KRm9yIGRpc2NyZXRlIGZ1bmN0aW9ucyAodXNlZCBpbiBpbWFnZXMgYW5kIHNpZ25hbHMpOg0KDQogIHlbbl0gPSDOoyBmW2tdICogZ1tuIC0ga10NCg0KKipJbiBuZXVyYWwgbmV0d29ya3MsIHRoaXMgYmVjb21lczoqKg0KDQogIG91dHB1dCA9IM6jIChpbnB1dCAqIGZpbHRlcikNCg0KVGhpcyBpcyBhICoqZG90IHByb2R1Y3QqKiBiZXR3ZWVuIHRoZSBmaWx0ZXIgYW5kIHRoZSBwYXJ0IG9mIHRoZSBpbnB1dCBpdCBvdmVybGFwcy4NCg0KLS0tDQoNCioqMi4gQ29udm9sdXRpb24gaW4gTmV1cmFsIE5ldHdvcmtzKioNCg0KLSBXZSBzbGlkZSB0aGUgZmlsdGVyIChlLmcuLCBhIDN4MyBtYXRyaXgpIG92ZXIgdGhlIGlucHV0IChlLmcuLCBhbiBpbWFnZSBtYXRyaXgpLg0KLSBBdCBlYWNoIHBvc2l0aW9uLCB3ZSB0YWtlIGFuIGVsZW1lbnQtd2lzZSBtdWx0aXBsaWNhdGlvbiBvZiBvdmVybGFwcGluZyB2YWx1ZXMsIHRoZW4gc3VtIHRvIGdldCBhIHNpbmdsZSBudW1iZXIuDQotIFRoYXQgbnVtYmVyIGJlY29tZXMgYSAqKmZlYXR1cmUqKiBpbiB0aGUgb3V0cHV0IChhbHNvIGNhbGxlZCBhICoqZmVhdHVyZSBtYXAqKiBvciAqKmFjdGl2YXRpb24gbWFwKiopLg0KDQoqKkV4YW1wbGU6KioNCiAgDQpJZiB5b3UgY29udm9sdmUgYSA1eDUgaW1hZ2Ugd2l0aCBhIDN4MyBmaWx0ZXI6DQotIE91dHB1dCBzaXplIGJlY29tZXMgKDUtMykrMSA9IDMgKGluIGVhY2ggZGltZW5zaW9uKSwgdW5sZXNzIHlvdSBhcHBseSAqKnBhZGRpbmcqKi4NCg0KLS0tDQoNCioqMy4gUGFkZGluZyoqDQoNCi0gKipQYWRkaW5nKiogaGVscHMgbWFpbnRhaW4gdGhlIG9yaWdpbmFsIHNpemUgb2YgdGhlIGltYWdlIGFmdGVyIGNvbnZvbHV0aW9uLg0KLSBXZSBhZGQgZXh0cmEgcGl4ZWxzICh1c3VhbGx5IDBzKSBhcm91bmQgdGhlIGJvcmRlci4NCg0KKipXaXRob3V0IFBhZGRpbmc6KioNCiAgT3V0cHV0IHNpemUgPSAoVyAtIEYgKyAxKQ0KDQoqKldpdGggUGFkZGluZyAoUCk6KioNCiAgT3V0cHV0IHNpemUgPSAoKFcgLSBGICsgMlApIC8gUykgKyAxDQoNCldoZXJlOg0KLSBXID0gaW5wdXQgd2lkdGgvaGVpZ2h0DQotIEYgPSBmaWx0ZXIgc2l6ZQ0KLSBQID0gcGFkZGluZw0KLSBTID0gc3RyaWRlDQoNCi0tLQ0KDQoqKjQuIFN0cmlkZSoqDQoNCi0gU3RyaWRlIGNvbnRyb2xzIGhvdyBtYW55IHBpeGVscyB3ZSBtb3ZlIHRoZSBmaWx0ZXIgYXQgYSB0aW1lLg0KLSAqKlN0cmlkZSA9IDEqKiDihpIgbW92ZSBmaWx0ZXIgMSBwaXhlbCBhdCBhIHRpbWUuDQotICoqTGFyZ2VyIHN0cmlkZSoqIOKGkiBzbWFsbGVyIG91dHB1dCBhbmQgbGVzcyBvdmVybGFwLg0KDQotLS0NCg0KKio1LiBGaWx0ZXIgRXhhbXBsZXMqKg0KDQotICoqQXZlcmFnaW5nIEZpbHRlcioqIChzbW9vdGhpbmcpOg0KDQogIDEvMjUgw5cgIA0KICBbMSAxIDEgIA0KICAgMSAxIDEgIA0KICAgMSAxIDFdDQoNCi0gKipFZGdlIERldGVjdGlvbiBGaWx0ZXIqKjoNCg0KICBbIDAgIC0xICAgMCAgDQogICAtMSAgNCAgLTEgIA0KICAgIDAgIC0xICAwXQ0KDQpFYWNoIG9mIHRoZXNlIGlzIHVzZWQgdG8gZXh0cmFjdCBkaWZmZXJlbnQgZmVhdHVyZXMgKGVkZ2VzLCB0ZXh0dXJlcywgZXRjLikuDQoNCi0tLQ0KDQoqKjYuIE5ldXJhbCBOZXR3b3JrIEFyY2hpdGVjdHVyZSBSZWNhcCoqDQoNCkZyb20gTW9kdWxlIDExICh0cmFuc2NyaXB0KToNCg0KRWFjaCAqKm5ldXJvbioqIGlzIGVzc2VudGlhbGx5IGEgcmVncmVzc2lvbiBmdW5jdGlvbjogIA0KICB6ID0gV3ggKyBiIOKGkiDPgyh6KQ0KDQpXaGVyZToNCi0gVyA9IHdlaWdodHMNCi0geCA9IGlucHV0cw0KLSBiID0gYmlhcw0KLSDPgyA9IGFjdGl2YXRpb24gZnVuY3Rpb24gKGUuZy4sIHNpZ21vaWQsIFJlTFUpDQoNCkluIENOTnMsIHRoaXMgcmVncmVzc2lvbiBpcyByZXBsYWNlZCB3aXRoICoqY29udm9sdXRpb24gb3BlcmF0aW9ucyoqLg0KDQotLS0NCg0KKio3LiBNYXRyaXggRGltZW5zaW9ucyAmIE91dHB1dCBTaXplIChJbXBvcnRhbnQpKioNCg0KSWY6DQotIElucHV0ID0gMjh4MjgNCi0gRmlsdGVyID0gM3gzDQotIFBhZGRpbmcgPSAxDQotIFN0cmlkZSA9IDENCg0KVGhlbjoNCi0gT3V0cHV0ID0gKCgyOCAtIDMgKyAyw5cxKSAvIDEpICsgMSA9IDI4ICANCuKGkiBPdXRwdXQgaGFzIHNhbWUgZGltZW5zaW9uIGFzIGlucHV0Lg0KDQotLS0NCg0KKio4LiBTdW1tYXJ5IG9mIEtleSBDb25jZXB0cyoqDQoNCi0gKipDb252b2x1dGlvbioqOiBPdmVybGFwcGluZyBvZiBpbnB1dCBhbmQgZmlsdGVyIHRvIGV4dHJhY3QgZmVhdHVyZXMuDQotICoqUGFkZGluZyoqOiBBZGRzIGJvcmRlciB0byBjb250cm9sIG91dHB1dCBzaXplLg0KLSAqKlN0cmlkZSoqOiBDb250cm9scyBzdGVwIHNpemUgb2YgdGhlIGZpbHRlciBtb3ZlbWVudC4NCi0gKipGaWx0ZXJzL0tlcm5lbHMqKjogRGV0ZWN0IGVkZ2VzLCB0ZXh0dXJlcywgcGF0dGVybnMuDQotICoqT3V0cHV0Kio6IEZlYXR1cmUgbWFwcyB1c2VkIGluIGRvd25zdHJlYW0gbGF5ZXJzLg0KLSAqKk11bHRpcGxlIGZpbHRlcnMqKjogVXNlZCB0byBleHRyYWN0IGRpZmZlcmVudCBmZWF0dXJlIHR5cGVzLg0KDQotLS0NCg0KKio5LiBLZXkgVGFrZWF3YXlzKioNCg0KLSBDb252b2x1dGlvbnMgYXJlIHBvd2VyZnVsIGJlY2F1c2UgdGhleSAqKnByZXNlcnZlIHNwYXRpYWwgcmVsYXRpb25zaGlwcyoqLg0KLSBDTk5zIGxlYXJuIGZpbHRlcnMgYXV0b21hdGljYWxseSBkdXJpbmcgdHJhaW5pbmcuDQotICoqUGFkZGluZyArIHN0cmlkZSoqIGxldHMgeW91IGNvbnRyb2wgdGhlIHNpemUgb2YgeW91ciBvdXRwdXQgZmVhdHVyZSBtYXBzLg0KLSAqKkVkZ2UgZGV0ZWN0aW9uKiogYW5kICoqYmx1cnJpbmcqKiBhcmUgc2ltcGxlIHJlYWwtd29ybGQgZXhhbXBsZXMgb2YgY29udm9sdXRpb24gZmlsdGVycy4NCg0KLS0tDQoNCioqMTAuIFByYWN0aWNlIFF1ZXN0aW9ucyoqDQoNCjEuIFdoYXQgZG9lcyBhIGNvbnZvbHV0aW9uIG9wZXJhdGlvbiBkbyBpbiBpbWFnZSBwcm9jZXNzaW5nPw0KMi4gV2h5IGRvIHdlIHVzZSBwYWRkaW5nIGluIGNvbnZvbHV0aW9uYWwgbmV1cmFsIG5ldHdvcmtzPw0KMy4gSG93IGRvZXMgdGhlIHN0cmlkZSBhZmZlY3QgdGhlIG91dHB1dCBzaXplPw0KNC4gV2hhdCBoYXBwZW5zIHdoZW4geW91IGFwcGx5IGFuIGVkZ2UgZGV0ZWN0aW9uIGZpbHRlciB0byBhbiBpbWFnZT8NCjUuIERlcml2ZSB0aGUgb3V0cHV0IHNpemUgb2YgYSBjb252b2x1dGlvbmFsIGxheWVyIGdpdmVuIHRoZSBpbnB1dCBzaXplLCBmaWx0ZXIgc2l6ZSwgcGFkZGluZywgYW5kIHN0cmlkZS4NCjYuIFdoYXQgaXMgdGhlIHJvbGUgb2YgdGhlIGJpYXMgdmVjdG9yIGluIGEgY29udm9sdXRpb25hbCBsYXllcj8NCg0KLS0tDQoNCioqU3VnZ2VzdGVkIFJlYWRpbmcqKg0KDQpGcm9tIHRoZSBFbGVtZW50cyBvZiBTdGF0aXN0aWNhbCBMZWFybmluZyAoSGFzdGllLCBUaWJzaGlyYW5pLCBGcmllZG1hbik6DQoNCi0gQ2hhcHRlciAxMTogTmV1cmFsIE5ldHdvcmtzICANCi0gQ2hhcHRlciA1OiBCYXNpcyBFeHBhbnNpb25zIGFuZCBSZWd1bGFyaXphdGlvbiAoZm9yIHVuZGVyc3RhbmRpbmcgY29udm9sdXRpb24tbGlrZSBvcGVyYXRpb25zKQ0KDQpVUkwgZm9yIHRleHRib29rOiAgDQpodHRwczovL3dlYi5zdGFuZm9yZC5lZHUvfmhhc3RpZS9FbGVtU3RhdExlYXJuLw0KDQotLS0NCg0KDQojIyMg8J+noCBNb2R1bGUgMTI6IENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzIOKAlCBTdHVkeSBHdWlkZQ0KDQotLS0NCg0KIyMjIDEuIENvcmUgQ29uY2VwdDogV2hhdCBpcyBhIENvbnZvbHV0aW9uPw0KDQoqKkRlZmluaXRpb246KiogIA0KQSAqKmNvbnZvbHV0aW9uKiogaXMgdGhlIG92ZXJsYXAgYmV0d2VlbiB0d28gZnVuY3Rpb25zLCBjYWxjdWxhdGVkIGJ5IHNsaWRpbmcgYSBmdW5jdGlvbiAoY2FsbGVkIGEgKipmaWx0ZXIqKiBvciAqKmtlcm5lbCoqKSBvdmVyIGlucHV0IGRhdGEgKHVzdWFsbHkgYW4gaW1hZ2UpLCBtdWx0aXBseWluZyBvdmVybGFwcGluZyB2YWx1ZXMsIGFuZCBzdW1taW5nIHRoZSByZXN1bHRzLg0KDQpUaGlzIGhlbHBzIGV4dHJhY3QgaW1wb3J0YW50IGZlYXR1cmVzIGxpa2UgZWRnZXMsIHRleHR1cmVzLCBvciBjb2xvciBncmFkaWVudHMgZnJvbSBpbWFnZXMuDQoNCi0tLQ0KDQojIyMgMi4gTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uDQoNCioqQ29udGludW91cyBmb3JtOioqDQogIA0KICB5KHQpID0g4oirIGYoz4QpwrdnKHTiiJLPhCkgZM+EDQoNCioqRGlzY3JldGUgKGZvciBkaWdpdGFsIGltYWdlcyBhbmQgbmV1cmFsIG5ldHdvcmtzKToqKg0KDQogIHlbaSwgal0gPSDOoyDOoyBpbnB1dFttK2ksIG4ral0gKiBmaWx0ZXJbbSwgbl0NCg0KV2hlcmU6DQotIGBpbnB1dGAgaXMgeW91ciBpbWFnZSBtYXRyaXgNCi0gYGZpbHRlcmAgaXMgdGhlIGtlcm5lbCAoZS5nLiwgZWRnZSBkZXRlY3Rpb24pDQotIGB5YCBpcyB0aGUgb3V0cHV0IGZlYXR1cmUgbWFwDQoNCi0tLQ0KDQojIyMgMy4gQ05OcyBpbiBBY3Rpb246IFB5dGhvbiBDb2RlIEV4YW1wbGUgKEJlZ2lubmVyLUZyaWVuZGx5KQ0KDQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KZnJvbSBzY2lweS5zaWduYWwgaW1wb3J0IGNvbnZvbHZlMmQNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KIyBFeGFtcGxlIGlucHV0IGltYWdlICg1eDUpDQppbWFnZSA9IG5wLmFycmF5KFsNCiAgICBbMSwgMiwgMywgMCwgMV0sDQogICAgWzAsIDEsIDIsIDMsIDBdLA0KICAgIFsxLCAwLCAxLCAyLCAxXSwNCiAgICBbMiwgMSwgMCwgMSwgMl0sDQogICAgWzEsIDIsIDEsIDAsIDFdDQpdKQ0KDQojIEV4YW1wbGUgZWRnZSBkZXRlY3Rpb24gZmlsdGVyICgzeDMpDQpmaWx0ZXJfa2VybmVsID0gbnAuYXJyYXkoWw0KICAgIFswLCAtMSwgMF0sDQogICAgWy0xLCA0LCAtMV0sDQogICAgWzAsIC0xLCAwXQ0KXSkNCg0KIyBBcHBseSBjb252b2x1dGlvbg0Kb3V0cHV0ID0gY29udm9sdmUyZChpbWFnZSwgZmlsdGVyX2tlcm5lbCwgbW9kZT0ndmFsaWQnKQ0KDQpwcmludCgiT3V0cHV0IGFmdGVyIGNvbnZvbHV0aW9uOiIpDQpwcmludChvdXRwdXQpDQpgYGANCg0KKipPdXRwdXQ6KiogQSBzbWFsbGVyIG1hdHJpeCB0aGF0IGhpZ2hsaWdodHMgd2hlcmUgZWRnZXMgb3IgZmVhdHVyZXMgd2VyZSBkZXRlY3RlZC4NCg0KLS0tDQoNCiMjIyA0LiBDTk4gTGF5ZXJzIEJyZWFrZG93bg0KDQotICoqQ29udm9sdXRpb25hbCBMYXllcioqOiBBcHBsaWVzIGZpbHRlcnMgdG8gZXh0cmFjdCBmZWF0dXJlcy4NCi0gKipQYWRkaW5nKio6IEFkZHMgemVyb3MgYXJvdW5kIHRoZSBpbWFnZSB0byBwcmVzZXJ2ZSBzaXplLg0KLSAqKlN0cmlkZSoqOiBIb3cgbWFueSBzdGVwcyB0aGUgZmlsdGVyIG1vdmVzLg0KLSAqKkFjdGl2YXRpb24gKFJlTFUpKio6IEFwcGxpZXMgYSBmdW5jdGlvbiBsaWtlIGBtYXgoMCwgeClgIHRvIG1ha2UgdGhlIG1vZGVsIG5vbi1saW5lYXIuDQotICoqUG9vbGluZyoqOiBSZWR1Y2VzIGRpbWVuc2lvbmFsaXR5IChNYXhQb29sLCBBdmdQb29sKS4NCg0KLS0tDQoNCiMjIyA1LiBWaXN1YWwgRXhhbXBsZSBmcm9tIENsYXNzIFNsaWRlcw0KDQotICoqQXZlcmFnaW5nIGZpbHRlcioqIHNtb290aHMgdGhlIGltYWdlIHVzaW5nIGEgdW5pZm9ybSBrZXJuZWwuDQotICoqRWRnZSBkZXRlY3Rpb24gZmlsdGVyKiogaGlnaGxpZ2h0cyBib3VuZGFyaWVzIHVzaW5nOg0KICANCiAgYGBgDQogIFtbIDAsIC0xLCAgMF0sDQogICBbLTEsICA0LCAtMV0sDQogICBbIDAsIC0xLCAgMF1dDQogIGBgYA0KDQotLS0NCg0KIyMjIDYuIEtleSBUYWtlYXdheXMNCg0KLSBDb252b2x1dGlvbiA9IFNsaWRpbmcgKyBNdWx0aXBseWluZyArIFN1bW1pbmcNCi0gUGFkZGluZyBoZWxwcyBtYWludGFpbiB0aGUgaW5wdXQgc2l6ZQ0KLSBTdHJpZGUgYWZmZWN0cyBob3cgbXVjaCB3ZSByZWR1Y2UgZGltZW5zaW9uYWxpdHkNCi0gRmlsdGVycyBhcmUgKipsZWFybmVkKiogZHVyaW5nIHRyYWluaW5nIHRvIGV4dHJhY3QgdXNlZnVsIGZlYXR1cmVzDQotIENvbnZvbHV0aW9ucyBhcmUgKip3aHkgQ05OcyBvdXRwZXJmb3JtIHRyYWRpdGlvbmFsIG1ldGhvZHMqKiBpbiBpbWFnZSB0YXNrcw0KDQotLS0NCg0KIyMjIDcuIFJlbGV2YW50IFF1ZXN0aW9ucyB0byBUZXN0IFlvdXJzZWxmDQoNCjEuIFdoYXQgZG9lcyBhIGNvbnZvbHV0aW9uIGRvIGluIGEgbmV1cmFsIG5ldHdvcms/DQoyLiBXaHkgaXMgcGFkZGluZyB1c2VkIGluIGNvbnZvbHV0aW9uIGxheWVycz8NCjMuIFdoYXQgZWZmZWN0IGRvZXMgaW5jcmVhc2luZyB0aGUgc3RyaWRlIGhhdmU/DQo0LiBIb3cgZG9lcyBhbiBlZGdlIGRldGVjdGlvbiBmaWx0ZXIgd29yaz8NCjUuIFdoYXQgaXMgdGhlIG91dHB1dCBzaXplIGlmIHlvdSBhcHBseSBhIDN4MyBmaWx0ZXIgdG8gYSA1eDUgaW1hZ2Ugd2l0aCBubyBwYWRkaW5nIGFuZCBzdHJpZGUgMT8NCg0KLS0tDQoNCiMjIyA4LiBMYXltYW7igJlzIEV4cGxhbmF0aW9uIOKAkyBQaXp6YSBDdXR0ZXIgQW5hbG9neQ0KDQpJbWFnaW5lIHlvdeKAmXJlIGN1dHRpbmcgYSBsYXJnZSBwaXp6YSB3aXRoIGEgKipzdGVuY2lsKiogdGhhdCBpcyAzeDMgaW5jaGVzIGluIHNpemUuIFlvdSBwbGFjZSB5b3VyIHN0ZW5jaWwgb24gdGhlIHBpenphLCBsb29rIGF0IGp1c3QgdGhhdCBzcXVhcmUsIGFuZCByYXRlIGl0IGZyb20gMSB0byAxMCBiYXNlZCBvbiBob3cgbXVjaCBwZXBwZXJvbmkgaXQgaGFzLg0KDQpUaGVuLCB5b3UgbW92ZSB0aGUgc3RlbmNpbCBvdmVyIGEgbGl0dGxlIChieSAxIGluY2ggPSBzdHJpZGUpLCBhbmQgZG8gaXQgYWdhaW4uIFlvdSdyZSBzY2FubmluZyB0aGUgZW50aXJlIHBpenphLCBvbmUgc21hbGwgcGF0Y2ggYXQgYSB0aW1lLg0KDQpZb3VyIHN0ZW5jaWwgaXMgdGhlICoqZmlsdGVyKiouDQpUaGUgcGl6emEgaXMgdGhlICoqaW1hZ2UqKi4NCllvdXIgcmF0aW5nIGlzIHRoZSAqKm91dHB1dCBmZWF0dXJlKiouDQpJZiB5b3Ugd2FudCB0aGUgZWRnZXMgdG8gYWxzbyBiZSBhbmFseXplZCAobm90IGp1c3QgdGhlIGNlbnRlciksIHlvdSBwbGFjZSBuYXBraW5zICh6ZXJvcykgYXJvdW5kIHRoZSBlZGdlIOKAlCB0aGF0J3MgKipwYWRkaW5nKiouDQoNClRoaXMgaXMgd2hhdCBjb252b2x1dGlvbiBkb2VzOiBpdCBoZWxwcyAqKnNlZSB0aGUgaW1wb3J0YW50IHBhcnRzKiogb2YgYW4gaW1hZ2UgKGxpa2UgY2hlZXNlLCBjcnVzdCwgb3IgcGVwcGVyb25pKSBhbmQgY29uZGVuc2UgaXQgaW50byB1c2VmdWwgaW5mbyDigJQgd2hpY2ggaXMgd2hhdCBhIG5ldXJhbCBuZXR3b3JrIG5lZWRzIHRvIG1ha2UgYSBkZWNpc2lvbi4NCg0KLS0tDQoNCiMjIyA5LiBUZXh0Ym9vayBSZWZlcmVuY2VzIGZvciBEZWVwZXIgUmVhZGluZw0KDQoqKkZyb20g4oCcVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5n4oCdIGJ5IEhhc3RpZSBldCBhbC4qKiAgDQotIENoYXB0ZXIgMTE6IE5ldXJhbCBOZXR3b3JrcyAgDQogIFVSTDogaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L35oYXN0aWUvRWxlbVN0YXRMZWFybi8NCg0KKipGcm9tIOKAnFRoZSBTdGF0aXN0aWNhbCBTbGV1dGjigJ0gYnkgUmFtc2V5ICYgU2NoYWZlcioqICANCi0gTm90IGltYWdlLWZvY3VzZWQgYnV0IHVzZWZ1bCBmb3IgcmVncmVzc2lvbiBiYWNrZ3JvdW5kDQoNCi0tLQ0KDQoNCg0KDQoNCkhlcmXigJlzIHlvdXIgKipjb21wbGV0ZSBzdHVkeSBndWlkZSBmb3IgQ29udm9sdXRpb25hbCBOZXVyYWwgTmV0d29ya3MgKENOTnMpKiosIGludGVncmF0aW5nIGFsbCBjbGFzcyB2aXN1YWxzLCB0cmFuc2NyaXB0cywgdGV4dGJvb2sga25vd2xlZGdlLCBtYXRoZW1hdGljYWwgY29uY2VwdHMsIFB5dGhvbiBjb2RlLCBrZXkgdGFrZWF3YXlzLCBhbmQgYW4gZWFzeS10by11bmRlcnN0YW5kIHJlYWwtd29ybGQgYW5hbG9neS4NCg0KLS0tDQoNClFUVyA3MzMzIOKAkyBNb2R1bGUgMTIgIA0KKipTdHVkeSBHdWlkZTogQ29udm9sdXRpb25hbCBOZXVyYWwgTmV0d29ya3MgKENOTnMpKioNCg0KLS0tDQoNCioq8J+noCBPdmVydmlldyBvZiBDTk5zKioNCg0KQSBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrIGlzIGEgdHlwZSBvZiBuZXVyYWwgbmV0d29yayBhcmNoaXRlY3R1cmUgKipvcHRpbWl6ZWQgZm9yIHByb2Nlc3NpbmcgaW1hZ2VzKiouIEl0IG1pbWljcyB0aGUgKipodW1hbiB2aXN1YWwgY29ydGV4Kiogd2hlcmUgKipvbmx5IG5lYXJieSBuZXVyb25zIGNvbW11bmljYXRlKirigJRtZWFuaW5nICoqZmlsdGVycyAoa2VybmVscykqKiBmb2N1cyBvbiBsb2NhbCBwYXR0ZXJucyAoZWRnZXMsIGNvcm5lcnMsIGV0Yy4pIGluc3RlYWQgb2YgdGhlIGVudGlyZSBpbWFnZSBhdCBvbmNlLg0KDQotLS0NCg0KKirwn6epIExheWVycyBpbiBhIENOTioqDQoNCjEuICoqQ29udm9sdXRpb25hbCBMYXllcioqDQoyLiAqKkFjdGl2YXRpb24gRnVuY3Rpb24gKHVzdWFsbHkgUmVMVSkqKg0KMy4gKipQb29saW5nIExheWVyKioNCjQuICoqRmxhdHRlbiBMYXllcioqDQo1LiAqKkRlbnNlIChGdWxseSBDb25uZWN0ZWQpIExheWVyKioNCjYuICoqT3V0cHV0IExheWVyIChvZnRlbiBTb2Z0bWF4IGZvciBjbGFzc2lmaWNhdGlvbikqKg0KDQotLS0NCg0KIyMjIPCfp64gTWF0aGVtYXRpY2FsIENvbmNlcHRzDQoNCiMjIyMgMS4gQ29udm9sdXRpb24gTGF5ZXI6DQpBcHBsaWVzIGEgZmlsdGVyIChrZXJuZWwpIGFjcm9zcyB0aGUgaW1hZ2UuDQoNCioqRXF1YXRpb24gKDJEIGRpc2NyZXRlIGNvbnZvbHV0aW9uKToqKiAgDQpZKGksIGopID0gzqNfbSDOo19uIFgoaSttLCBqK24pIMK3IEsobSwgbikNCg0KV2hlcmU6ICANCi0gWCA9IGlucHV0IGltYWdlICANCi0gSyA9IGtlcm5lbCAgDQotIFkgPSBvdXRwdXQgZmVhdHVyZSBtYXANCg0KIyMjIyAyLiBQb29saW5nIExheWVyOg0KUmVkdWNlcyBzcGF0aWFsIHNpemUgYnkgc2VsZWN0aW5nICoqbWF4Kiogb3IgKiphdmVyYWdlKiogdmFsdWVzLg0KDQotICoqTWF4IFBvb2xpbmcgKDLDlzIgd2l0aCBzdHJpZGUgMikqKjogIA0KRnJvbTogIA0KW1sxLCAxXSwgIA0KIFs1LCA2XV0g4oaSIG1heCA9IDYNCg0KIyMjIyAzLiBGbGF0dGVuIExheWVyOg0KQ29udmVydHMgbXVsdGktZGltZW5zaW9uYWwgdGVuc29yIGludG8gMUQgdmVjdG9yIGZvciBkZW5zZSBsYXllcnMuDQoNCkV4YW1wbGU6DQpbWzAsIC0xLCAwXSwgIA0KIFstMSwgNCwgLTFdLCAgDQogWzAsIC0xLCAwXV0gIA0K4oaSIFswLCAtMSwgMCwgLTEsIDQsIC0xLCAwLCAtMSwgMF0NCg0KLS0tDQoNCiMjIyDwn5K7IFB5dGhvbiBDb2RlIEV4YW1wbGUgKFNpbXBsZSBDTk4gd2l0aCBLZXJhcykNCg0KYGBgcHl0aG9uDQppbXBvcnQgdGVuc29yZmxvdyBhcyB0Zg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzIGltcG9ydCBsYXllcnMsIG1vZGVscw0KDQptb2RlbCA9IG1vZGVscy5TZXF1ZW50aWFsKCkNCm1vZGVsLmFkZChsYXllcnMuQ29udjJEKDMyLCAoMywgMyksIGFjdGl2YXRpb249J3JlbHUnLCBpbnB1dF9zaGFwZT0oMjgsIDI4LCAxKSkpDQptb2RlbC5hZGQobGF5ZXJzLk1heFBvb2xpbmcyRCgoMiwgMikpKQ0KbW9kZWwuYWRkKGxheWVycy5Db252MkQoNjQsICgzLCAzKSwgYWN0aXZhdGlvbj0ncmVsdScpKQ0KbW9kZWwuYWRkKGxheWVycy5NYXhQb29saW5nMkQoKDIsIDIpKSkNCm1vZGVsLmFkZChsYXllcnMuQ29udjJEKDY0LCAoMywgMyksIGFjdGl2YXRpb249J3JlbHUnKSkNCg0KIyBGbGF0dGVuIGFuZCBEZW5zZQ0KbW9kZWwuYWRkKGxheWVycy5GbGF0dGVuKCkpDQptb2RlbC5hZGQobGF5ZXJzLkRlbnNlKDY0LCBhY3RpdmF0aW9uPSdyZWx1JykpDQptb2RlbC5hZGQobGF5ZXJzLkRlbnNlKDEwLCBhY3RpdmF0aW9uPSdzb2Z0bWF4JykpDQoNCm1vZGVsLnN1bW1hcnkoKQ0KYGBgDQoNCi0tLQ0KDQojIyMg8J+OqCBMYXltYW7igJlzIEFuYWxvZ3kNCg0KSW1hZ2luZSB5b3XigJlyZSBsb29raW5nIGF0IGEgKipXaGVyZeKAmXMgV2FsZG8qKiBib29rOg0KLSBZb3VyICoqZXllcyBzY2FuIHNtYWxsIHNlY3Rpb25zKiogb2YgdGhlIGltYWdlIChsaWtlIGEgZmlsdGVyKS4NCi0gWW914oCZcmUgY2hlY2tpbmcgKipwYXR0ZXJucyBsaWtlIGhhdHMsIGdsYXNzZXMsIHJlZC13aGl0ZSBzaGlydHMqKi4NCi0gV2hlbiBzb21ldGhpbmcgc3RhbmRzIG91dCwgeW91ciBicmFpbiBzdG9yZXMgdGhhdCBpbmZvLg0KLSBPbmNlIGVub3VnaCBmZWF0dXJlcyBhcmUgY29sbGVjdGVkLCB5b3UgKipkZWNpZGUgd2hlcmUgV2FsZG8gaXMqKi4NCg0KU2ltaWxhcmx5Og0KLSAqKkNvbnZvbHV0aW9uYWwgbGF5ZXJzKiogc2NhbiBmb3IgcGF0dGVybnMuDQotICoqUG9vbGluZyoqIHN1bW1hcml6ZXMgdGhvc2UgcGF0dGVybnMgKHNocmlua3MgZGV0YWlsKS4NCi0gKipGbGF0dGVuaW5nKiogc3RhY2tzIGV2ZXJ5dGhpbmcgaW50byBhIGxpc3QuDQotICoqRGVuc2UgbGF5ZXJzKiogbWFrZSBhIGZpbmFsIGRlY2lzaW9uIChlLmcuLCB0aGlzIGlzIFdhbGRvISkuDQoNCi0tLQ0KDQojIyMg8J+TmCBUZXh0Ym9vayBTdXBwb3J0DQoNCkZyb20gKipFbGVtZW50cyBvZiBTdGF0aXN0aWNhbCBMZWFybmluZyoqOg0KLSAqKkNoYXB0ZXIgMTEg4oCTIE5ldXJhbCBOZXR3b3JrcyoqICANCmh0dHBzOi8vd2ViLnN0YW5mb3JkLmVkdS9+aGFzdGllL0VsZW1TdGF0TGVhcm4vDQoNCkFsc28gZXhwbG9yZSAqKkNTMjMxbiBieSBTdGFuZm9yZCoqIChWaXN1YWwgQ05OIHdhbGt0aHJvdWdoKTogIA0KaHR0cDovL2NzMjMxbi5zdGFuZm9yZC5lZHUvc2xpZGVzLzIwMTkvY3MyMzFuXzIwMTlfbGVjdHVyZTUucGRmDQoNCi0tLQ0KDQojIyMg8J+noCBLZXkgVGFrZWF3YXlzDQoNCi0gQ05OcyBhcmUgaWRlYWwgZm9yIGltYWdlIGNsYXNzaWZpY2F0aW9uIGR1ZSB0byBzcGF0aWFsIGF3YXJlbmVzcy4NCi0gQ29udm9sdXRpb25hbCBsYXllcnMgZXh0cmFjdCAqKmxvY2FsIHBhdHRlcm5zKiouDQotIFBvb2xpbmcgbGF5ZXJzIHJlZHVjZSAqKnNwYXRpYWwgZGltZW5zaW9ucyoqLg0KLSBGbGF0dGVuaW5nIHJlc2hhcGVzIHRoZSBpbWFnZSBpbnRvIGEgMUQgdmVjdG9yIGZvciBEZW5zZSBsYXllcnMuDQotIENOTnMgKipyZWR1Y2UgcGFyYW1ldGVycyoqIGNvbXBhcmVkIHRvIHRyYWRpdGlvbmFsIGRlbnNlIG5ldHdvcmtzIHdoaWxlIGltcHJvdmluZyBhY2N1cmFjeS4NCg0KLS0tDQoNCiMjIyDinZMgUHJhY3RpY2UgUXVlc3Rpb25zDQoNCjEuIFdoYXQgaXMgdGhlIHB1cnBvc2Ugb2YgYSBjb252b2x1dGlvbmFsIGxheWVyPw0KMi4gV2hhdCBkb2VzIG1heCBwb29saW5nIGRvPyBXaHkgaXMgaXQgdXNlZnVsPw0KMy4gV2h5IGRvIHdlIGZsYXR0ZW4gZGF0YSBiZWZvcmUgc2VuZGluZyBpdCB0byBhIGRlbnNlIGxheWVyPw0KNC4gSW4gd2hhdCB3YXkgaXMgYSBDTk4gYmlvbG9naWNhbGx5IGluc3BpcmVkPw0KNS4gSG93IGRvZXMgaW5jcmVhc2luZyB0aGUgbnVtYmVyIG9mIGZpbHRlcnMgYWZmZWN0IHRoZSBvdXRwdXQ/DQoNCg0KLS0tDQoNCioq8J+TmCBRVFcgNzMzMyDigJMgTW9kdWxlIDEyOiBUcmFuc2ZlciBMZWFybmluZyBTdHVkeSBHdWlkZSoqDQoNCi0tLQ0KDQojIyMg8J+UjSAxLiBDb25jZXB0IE92ZXJ2aWV3OiBXaGF0IElzIFRyYW5zZmVyIExlYXJuaW5nPw0KDQoqKkRlZmluaXRpb246KiogIA0KVHJhbnNmZXIgTGVhcm5pbmcgaXMgYSB0ZWNobmlxdWUgd2hlcmUgd2UgdGFrZSBhICoqcHJlLXRyYWluZWQgbW9kZWwqKiAob2Z0ZW4gdHJhaW5lZCBvbiBsYXJnZSBkYXRhc2V0cyB1c2luZyBwb3dlcmZ1bCBoYXJkd2FyZSkgYW5kICoqcmV1c2UgaXRzIGVhcmx5IGxheWVycyoqIHdoaWxlICoqcmV0cmFpbmluZyBvbmx5IHRoZSBmaW5hbCBsYXllcnMqKiBmb3IgYSBuZXcsIG9mdGVuIHNtYWxsZXIsIHRhc2suDQoNCkluc3RlYWQgb2YgdHJhaW5pbmcgZXZlcnl0aGluZyBmcm9tIHNjcmF0Y2gsIHdlICoq4oCcdHJhbnNmZXLigJ0gdGhlIGxlYXJuaW5nKiogZnJvbSBvbmUgdGFzayB0byBhbm90aGVyLg0KDQotLS0NCg0KIyMjIPCfp6AgMi4gV2h5IERvZXMgSXQgV29yaz8NCg0KRnJvbSB5b3VyIHRyYW5zY3JpcHQ6DQotIEVhcmx5IGxheWVycyAodG9wIG9mIHRoZSBuZXR3b3JrKSBsZWFybiAqKmdlbmVyYWwgZmVhdHVyZXMqKiAoZWRnZXMsIGNvbG9ycywgc2hhcGVzKS4NCi0gTGF0ZXIgbGF5ZXJzIChkZW5zZSBsYXllcnMpIGxlYXJuICoqc3BlY2lmaWMgdGFzay1yZWxhdGVkIGZlYXR1cmVzKiouDQotIEdlbmVyYWwgZmVhdHVyZXMgYXJlICoqcmV1c2FibGUqKiwgZXZlbiBhY3Jvc3MgZGlmZmVyZW50IHRhc2tzIChlLmcuLCBkb2dzIHZzLiBjYXRzLCBvciBjYXJzIHZzLiBwbGFuZXMpLg0KLSBTYXZlcyB0aW1lLCBkYXRhLCBhbmQgY29tcHV0ZSByZXNvdXJjZXMuDQoNCi0tLQ0KDQojIyMg8J+TiiAzLiBNYXRoZW1hdGljYWwgRm9ybXVsYXRpb24NCg0KTGV0Og0KDQotICoqZih4OyDOuCkqKiBiZSB0aGUgb3JpZ2luYWwgbmV1cmFsIG5ldHdvcmsNCi0gzrggPSBbzrhfZ2VuZXJhbCwgzrhfc3BlY2lmaWNdDQoNCldlIGZyZWV6ZSDOuF9nZW5lcmFsIGFuZCByZXRyYWluIG9ubHkgzrhfc3BlY2lmaWMuDQoNCioqTmV3IG91dHB1dDoqKg0KICANCiAgeScgPSBmKHg7IM64X2dlbmVyYWwsIM64J19zcGVjaWZpYykgIA0KICAod2hlcmUgzrhfZ2VuZXJhbCBpcyBwcmUtdHJhaW5lZCBhbmQgZnJvemVuLCDOuCdfc3BlY2lmaWMgaXMgbmV3bHkgdHJhaW5lZCkNCg0KLS0tDQoNCiMjIyDwn5K7IDQuIFB5dGhvbiBDb2RlIEV4YW1wbGUgKFVzaW5nIEtlcmFzKQ0KDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5hcHBsaWNhdGlvbnMgaW1wb3J0IFZHRzE2DQpmcm9tIHRlbnNvcmZsb3cua2VyYXMubW9kZWxzIGltcG9ydCBNb2RlbA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRGVuc2UsIEdsb2JhbEF2ZXJhZ2VQb29saW5nMkQNCmZyb20gdGVuc29yZmxvdy5rZXJhcyBpbXBvcnQgSW5wdXQNCg0KIyBMb2FkIHByZS10cmFpbmVkIFZHRzE2IHdpdGhvdXQgdG9wIGxheWVycw0KYmFzZV9tb2RlbCA9IFZHRzE2KHdlaWdodHM9J2ltYWdlbmV0JywgaW5jbHVkZV90b3A9RmFsc2UsIGlucHV0X3RlbnNvcj1JbnB1dChzaGFwZT0oMjI0LCAyMjQsIDMpKSkNCg0KIyBGcmVlemUgYmFzZSBtb2RlbCBsYXllcnMNCmZvciBsYXllciBpbiBiYXNlX21vZGVsLmxheWVyczoNCiAgICBsYXllci50cmFpbmFibGUgPSBGYWxzZQ0KDQojIEFkZCBuZXcgZGVuc2UgbGF5ZXJzIG9uIHRvcA0KeCA9IGJhc2VfbW9kZWwub3V0cHV0DQp4ID0gR2xvYmFsQXZlcmFnZVBvb2xpbmcyRCgpKHgpDQp4ID0gRGVuc2UoMTI4LCBhY3RpdmF0aW9uPSdyZWx1JykoeCkNCnByZWRpY3Rpb25zID0gRGVuc2UoMTAsIGFjdGl2YXRpb249J3NvZnRtYXgnKSh4KSAgIyBmb3IgMTAtY2xhc3MgcHJvYmxlbQ0KDQojIEZpbmFsIG1vZGVsDQptb2RlbCA9IE1vZGVsKGlucHV0cz1iYXNlX21vZGVsLmlucHV0LCBvdXRwdXRzPXByZWRpY3Rpb25zKQ0KbW9kZWwuY29tcGlsZShvcHRpbWl6ZXI9J2FkYW0nLCBsb3NzPSdjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknLCBtZXRyaWNzPVsnYWNjdXJhY3knXSkNCmBgYA0KDQpUaGlzIHVzZXMgKipJbWFnZU5ldC10cmFpbmVkIGZlYXR1cmVzKiogYW5kIHRyYWlucyBvbmx5IHRoZSBjbGFzc2lmaWVyIG9uIHlvdXIgY3VzdG9tIGRhdGEuDQoNCi0tLQ0KDQojIyMg8J+TmiA1LiBSZWZlcmVuY2UgZnJvbSBUZXh0Ym9va3MNCg0KKipfVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nXyoqIChIYXN0aWUsIFRpYnNoaXJhbmksIEZyaWVkbWFuKSAgDQotIENoYXB0ZXIgMTEgKE5ldXJhbCBOZXR3b3Jrcyk6IGV4cGxhaW5zIGhvdyBsb3dlci1sZXZlbCByZXByZXNlbnRhdGlvbnMgZ2VuZXJhbGl6ZSBhY3Jvc3MgcHJvYmxlbXMuICANCiAgTGluazogaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L35oYXN0aWUvRWxlbVN0YXRMZWFybi8NCg0KLS0tDQoNCiMjIyDinIUgNi4gS2V5IFRha2Vhd2F5cw0KDQotIFlvdSBjYW4gcmV1c2UgZXhwZW5zaXZlLCBwcmUtdHJhaW5lZCBtb2RlbHMgdG8gc29sdmUgeW91ciBvd24gcHJvYmxlbS4NCi0gWW91IG9ubHkgbmVlZCB0byB0cmFpbiBhIGZldyBsYXllcnMgKHVzdWFsbHkgdGhlIGxhc3QgZGVuc2UgbGF5ZXJzKS4NCi0gR3JlYXQgZm9yICoqbGltaXRlZCBkYXRhKiosICoqZmFzdGVyIHRyYWluaW5nKiosIGFuZCAqKmNyb3NzLWRvbWFpbiB0YXNrcyoqLg0KLSBQcmUtdHJhaW5lZCBtb2RlbHMgYXJlIGF2YWlsYWJsZSB2aWEgKipUZW5zb3JGbG93IEh1YioqLCAqKlB5VG9yY2ggSHViKiosICoqSHVnZ2luZyBGYWNlKiosIGV0Yy4NCg0KLS0tDQoNCiMjIyDinZMgNy4gUmVsZXZhbnQgUXVlc3Rpb25zDQoNCjEuIFdoeSBpcyB0cmFuc2ZlciBsZWFybmluZyB1c2VmdWwgaW4gZGVlcCBsZWFybmluZz8NCjIuIFdoaWNoIHBhcnRzIG9mIHRoZSBtb2RlbCBhcmUgdHlwaWNhbGx5IGZyb3plbiBhbmQgd2hpY2ggYXJlIHJldHJhaW5lZD8NCjMuIENhbiB0cmFuc2ZlciBsZWFybmluZyB3b3JrIGFjcm9zcyBkb21haW5zIChlLmcuLCB2aXNpb24gdG8gbGFuZ3VhZ2UpPw0KNC4gV2hhdCByb2xlIGRvZXMgdGhlIHZhbmlzaGluZyBncmFkaWVudCBwbGF5IGluIG1vdGl2YXRpbmcgdHJhbnNmZXIgbGVhcm5pbmc/DQoNCi0tLQ0KDQojIyMg8J+nuCA4LiBMYXltYW4gRXhwbGFuYXRpb24gKFJlYWwtV29ybGQgQW5hbG9neSkNCg0KKipJbWFnaW5lIHlvdeKAmXJlIGxlYXJuaW5nIHRvIHBsYXkgbXVzaWMuKioNCg0KLSBZb3UgdHJhaW5lZCAxMCB5ZWFycyBvbiB0aGUgcGlhbm8uDQotIE5vdyB5b3Ugd2FudCB0byBsZWFybiB0aGUgZ3VpdGFyLg0KDQpZb3UgZG9u4oCZdCBuZWVkIHRvIHJlbGVhcm4gKiptdXNpYyB0aGVvcnksIHJoeXRobSwgbm90ZXMsIG9yIHRpbWluZyoqLiBZb3UganVzdCBuZWVkIHRvIGxlYXJuICoqaG93IHRvIGhvbGQgYW5kIHN0cnVtIHRoZSBndWl0YXIqKi4NCg0KVGhhdOKAmXMgdHJhbnNmZXIgbGVhcm5pbmc6DQotIFlvdXIgbXVzaWMgdGhlb3J5ID0gcHJlLXRyYWluZWQgbGF5ZXJzIChmcm96ZW4pLg0KLSBMZWFybmluZyBndWl0YXIgc3BlY2lmaWNzID0gZmluYWwgbGF5ZXJzIChyZXRyYWluZWQpLg0KLSBZb3XigJlyZSBub3Qgc3RhcnRpbmcgZnJvbSBzY3JhdGNoIOKAlCB5b3XigJlyZSBhZGFwdGluZy4NCg0KSnVzdCBsaWtlIHlvdXIgYnJhaW4gZG9lc27igJl0IHJlbGVhcm4gaG93IHNvdW5kIHdvcmtzIGVhY2ggdGltZSwgYSBDTk4gZG9lc27igJl0IG5lZWQgdG8gcmVsZWFybiBlZGdlIGRldGVjdGlvbiBvciB0ZXh0dXJlcyB3aGVuIGNsYXNzaWZ5aW5nIG5ldyBpbWFnZXMuDQoNCi0tLQ0KDQoqKvCfk5ggUVRXIDczMzMg4oCTIE1vZHVsZSAxMjogQ05OIFBhcnQgSSDigJMgU2V0IFVwIFlvdXIgRGF0YSBTdHVkeSBHdWlkZSoqDQoNCi0tLQ0KDQojIyMg8J+UjSAxLiBXaGF0IEFyZSBXZSBEb2luZz8NCg0KV2XigJlyZToNCi0gSW1wb3J0aW5nIENJRkFSLTEwIGltYWdlIGRhdGFzZXQNCi0gVmlzdWFsaXppbmcgdGhlIGRhdGFzZXQNCi0gTm9ybWFsaXppbmcgdGhlIHBpeGVsIHZhbHVlcw0KLSBQcmVwYXJpbmcgZXZlcnl0aGluZyB0byBmZWVkIGludG8gYSBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrIChDTk4pDQoNCi0tLQ0KDQojIyMg8J+nqiAyLiBXaHkgTm9ybWFsaXplIGFuZCBWaXN1YWxpemU/DQoNCi0gKipOb3JtYWxpemF0aW9uKiogc2NhbGVzIHBpeGVsIHZhbHVlcyBmcm9tIGBbMCwgMjU1XWAgdG8gYFswLjAsIDEuMF1gIOKGkiBoZWxwcyB0aGUgbmV1cmFsIG5ldHdvcmsgY29udmVyZ2UgZmFzdGVyLg0KLSAqKlZpc3VhbGl6YXRpb24qKiBsZXRzIHVzIHZlcmlmeSB0aGF0IHRoZSBpbWFnZXMgYW5kIGxhYmVscyBhcmUgY29ycmVjdGx5IGFsaWduZWQgYmVmb3JlIHRyYWluaW5nLg0KDQotLS0NCg0KIyMjIPCfk5ogMy4gVGV4dGJvb2sgU3VwcG9ydA0KDQpGcm9tIF9UaGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmdfIGJ5IEhhc3RpZSwgVGlic2hpcmFuaSAmIEZyaWVkbWFuOiAgDQotIENoYXB0ZXIgMTEgKE5ldXJhbCBOZXR3b3JrcykgIA0KLSBDaGFwdGVyIDcgKE1vZGVsIEFzc2Vzc21lbnQgYW5kIFNlbGVjdGlvbikgIA0KTGluazogaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L35oYXN0aWUvRWxlbVN0YXRMZWFybi8NCg0KQWxzbyBoZWxwZnVsOiBUZW5zb3JGbG93IG9mZmljaWFsIGRvY3M6ICANCmh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL3R1dG9yaWFscy9pbWFnZXMvY25uDQoNCi0tLQ0KDQojIyMg8J+TiiA0LiBNYXRoZW1hdGljYWwgRXhwbGFuYXRpb24NCg0KLSBFYWNoIGltYWdlIGlzIGEgKip0ZW5zb3Igb2Ygc2hhcGUgKDMyLCAzMiwgMykqKiAoaGVpZ2h0LCB3aWR0aCwgUkdCIGNoYW5uZWxzKS4NCi0gTm9ybWFsaXphdGlvbjoNCiAgDQogIEZvciBlYWNoIHBpeGVsIHZhbHVlIFwoIHggXCkgaW4gdGhlIGltYWdlOiAgDQogIFwoIHhfe1x0ZXh0e25vcm1hbGl6ZWR9fSA9IFxmcmFje3h9ezI1NS4wfSBcKQ0KDQotIExhYmVscyBhcmUgaW50ZWdlci1lbmNvZGVkOiAgDQogIGUuZy4sIDAgPSBhaXJwbGFuZSwgMSA9IGNhciwgLi4uLCA5ID0gdHJ1Y2sNCg0KLS0tDQoNCiMjIyDwn5K7IDUuIFB5dGhvbiBDb2RlIChCZWdpbm5lci1GcmllbmRseSBTZXR1cCkNCg0KYGBgcHl0aG9uDQojIFN0ZXAgMTogSW1wb3J0IGxpYnJhcmllcw0KaW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYNCmZyb20gdGVuc29yZmxvdy5rZXJhcyBpbXBvcnQgZGF0YXNldHMsIGxheWVycywgbW9kZWxzDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQoNCiMgU3RlcCAyOiBMb2FkIENJRkFSLTEwIGRhdGFzZXQNCih0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscyksICh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpID0gZGF0YXNldHMuY2lmYXIxMC5sb2FkX2RhdGEoKQ0KDQojIFN0ZXAgMzogTm9ybWFsaXplIHBpeGVsIHZhbHVlcyB0byBbMCwgMV0NCnRyYWluX2ltYWdlcywgdGVzdF9pbWFnZXMgPSB0cmFpbl9pbWFnZXMgLyAyNTUuMCwgdGVzdF9pbWFnZXMgLyAyNTUuMA0KDQojIFN0ZXAgNDogRGVmaW5lIGNsYXNzIG5hbWVzDQpjbGFzc19uYW1lcyA9IFsnYWlycGxhbmUnLCAnYXV0b21vYmlsZScsICdiaXJkJywgJ2NhdCcsICdkZWVyJywNCiAgICAgICAgICAgICAgICdkb2cnLCAnZnJvZycsICdob3JzZScsICdzaGlwJywgJ3RydWNrJ10NCg0KIyBTdGVwIDU6IFBsb3QgZmlyc3QgMjUgaW1hZ2VzDQpwbHQuZmlndXJlKGZpZ3NpemU9KDEwLDEwKSkNCmZvciBpIGluIHJhbmdlKDI1KToNCiAgICBwbHQuc3VicGxvdCg1LDUsaSsxKQ0KICAgIHBsdC54dGlja3MoW10pICAjIFJlbW92ZSB4LWF4aXMNCiAgICBwbHQueXRpY2tzKFtdKSAgIyBSZW1vdmUgeS1heGlzDQogICAgcGx0LmdyaWQoRmFsc2UpDQogICAgcGx0Lmltc2hvdyh0cmFpbl9pbWFnZXNbaV0pDQogICAgcGx0LnhsYWJlbChjbGFzc19uYW1lc1t0cmFpbl9sYWJlbHNbaV1bMF1dKQ0KcGx0LnNob3coKQ0KYGBgDQoNCi0tLQ0KDQojIyMg4pyFIDYuIEtleSBUYWtlYXdheXMNCg0KLSAqKkNJRkFSLTEwKiogaXMgYSBncmVhdCBzdGFydGVyIGRhdGFzZXQgZm9yIGltYWdlIGNsYXNzaWZpY2F0aW9uIHdpdGggMTAgY29tbW9uIG9iamVjdCBjbGFzc2VzLg0KLSAqKk5vcm1hbGl6YXRpb24qKiBpcyBlc3NlbnRpYWwgaW4gbmV1cmFsIG5ldHdvcmtzIHRvIGVuc3VyZSBwaXhlbCB2YWx1ZXMgYXJlIGluIGEgc3RhbmRhcmQgcmFuZ2UuDQotIFBsb3R0aW5nIGltYWdlcyBoZWxwcyB2YWxpZGF0ZSBkYXRhIHF1YWxpdHkgYmVmb3JlIHRyYWluaW5nLg0KLSBUaGlzIHNldHVwIHBoYXNlIGlzIGNydWNpYWwuIEdhcmJhZ2UgaW4gPSBnYXJiYWdlIG91dC4NCg0KLS0tDQoNCiMjIyDinZMgNy4gUXVlc3Rpb25zIHRvIFRlc3QgWW91cnNlbGYNCg0KMS4gV2h5IGRvIHdlIGRpdmlkZSBwaXhlbCB2YWx1ZXMgYnkgMjU1IGluIGltYWdlIGRhdGFzZXRzPw0KMi4gV2hhdCBhcmUgdGhlIGRpbWVuc2lvbnMgb2YgZWFjaCBDSUZBUi0xMCBpbWFnZT8NCjMuIFdoeSBpcyBpdCB1c2VmdWwgdG8gdmlzdWFsaXplIHRoZSBpbWFnZXMgYmVmb3JlIHRyYWluaW5nIGEgQ05OPw0KNC4gV2hhdCBkb2VzIGB0cmFpbl9sYWJlbHNbaV1bMF1gIG1lYW4gaW4gdGhlIHBsb3R0aW5nIGxvb3A/DQoNCi0tLQ0KDQojIyMg8J+nuCA4LiBMYXltYW7igJlzIEFuYWxvZ3k6IEZpbGluZyBQaG90b3MgZm9yIGFuIEFsYnVtDQoNCkltYWdpbmUgeW914oCZcmUgY3JlYXRpbmcgYSBwaG90byBhbGJ1bToNCi0gKipFYWNoIHBob3RvKiogPSBhbiBpbWFnZQ0KLSAqKlRoZSBwaG90byBsYWJlbCoqID0gdGhlIG5hbWUgd3JpdHRlbiBiZWxvdyBpdCAoZG9nLCB0cnVjaywgZXRjLikNCi0gQnV0IGFsbCB5b3VyIHBob3RvcyBhcmUgaW4gKipkaWZmZXJlbnQgZm9ybWF0cyBhbmQgYnJpZ2h0bmVzcyoqIGxldmVscy4NCg0KQmVmb3JlIHBhc3RpbmcgdGhlbSBpbjoNCjEuIFlvdSByZXNpemUgYWxsIHRvIHRoZSBzYW1lIGRpbWVuc2lvbnMgKDMyeDMyIHBpeGVscykuDQoyLiBZb3UgYWRqdXN0IGJyaWdodG5lc3MgKG5vcm1hbGl6YXRpb24pLg0KMy4gWW91IGxhYmVsIHRoZW0gKGNsYXNzIG5hbWVzKS4NCjQuIFRoZW4geW91IGxheSB0aGVtIG91dCBpbiB0aGUgYWxidW0gKHBsb3R0aW5nKS4NCg0KVGhpcyBzZXR1cCBtYWtlcyB5b3VyIGFsYnVtIChkYXRhc2V0KSBvcmdhbml6ZWQgYW5kIGVhc3kgdG8gaW50ZXJwcmV0IOKAlCBqdXN0IGxpa2UgaXQgbWFrZXMgdGhlIENOTuKAmXMgam9iIGVhc2llciBkdXJpbmcgdHJhaW5pbmcuDQoNCi0tLQ0KDQoNCg0KSGVyZSBpcyB5b3VyIGNvbXBsZXRlICoqU3R1ZHkgR3VpZGUgZm9yIENOTiBQYXJ0IElJOiBCdWlsZGluZyB0aGUgTW9kZWwqKiBmcm9tIFFUVyA3MzMzLCBpbmNsdWRpbmcgdGV4dGJvb2sgbGlua3MsIHRyYW5zY3JpcHQgc3VtbWFyeSwgbWF0aGVtYXRpY2FsIGV4cGxhbmF0aW9uLCBQeXRob24gY29kZSwga2V5IHRha2Vhd2F5cywgc2VsZi10ZXN0IHF1ZXN0aW9ucywgYW5kIGEgYmVnaW5uZXIgYW5hbG9neS4NCg0KLS0tDQoNCioq8J+TmCBRVFcgNzMzMyDigJMgTW9kdWxlIDEyOiBDTk4gUGFydCBJSSDigJMgQnVpbGRpbmcgdGhlIE1vZGVsKioNCg0KLS0tDQoNCiMjIyDwn5SNIDEuIFdoYXQgQXJlIFdlIERvaW5nIGluIFRoaXMgTW9kdWxlPw0KDQpXZeKAmXJlOg0KLSBCdWlsZGluZyBhIENOTiBhcmNoaXRlY3R1cmUgdXNpbmcgS2VyYXMnIGBTZXF1ZW50aWFsYCBBUEkuDQotIEFkZGluZyBjb252b2x1dGlvbiwgcG9vbGluZywgZmxhdHRlbmluZywgYW5kIGRlbnNlIGxheWVycy4NCi0gQ29tcGlsaW5nIHRoZSBtb2RlbCB3aXRoIGFuIG9wdGltaXplciBhbmQgbG9zcyBmdW5jdGlvbi4NCi0gVHJhaW5pbmcgdGhlIG1vZGVsIGFuZCB2YWxpZGF0aW5nIHBlcmZvcm1hbmNlLg0KLSBNYWtpbmcgcHJlZGljdGlvbnMgYW5kIHZpc3VhbGl6aW5nIHJlc3VsdHMuDQoNCi0tLQ0KDQojIyMg8J+noCAyLiBLZXkgQ29uY2VwdHMNCg0KLSAqKkNvbnYyRCoqOiBBcHBsaWVzIGNvbnZvbHV0aW9uYWwgZmlsdGVycyB0byBleHRyYWN0IHBhdHRlcm5zLg0KLSAqKk1heFBvb2xpbmcyRCoqOiBSZWR1Y2VzIHNwYXRpYWwgZGltZW5zaW9ucywgcmV0YWlucyBpbXBvcnRhbnQgZmVhdHVyZXMuDQotICoqRmxhdHRlbioqOiBDb252ZXJ0cyAyRCBvdXRwdXQgdG8gMUQgZm9yIGRlbnNlIGxheWVyIGlucHV0Lg0KLSAqKkRlbnNlIChmdWxseSBjb25uZWN0ZWQgbGF5ZXIpKio6IEZpbmFsIGRlY2lzaW9uIGxheWVycy4NCi0gKipTb2Z0bWF4Kio6IE91dHB1dCBsYXllciBmb3IgY2xhc3NpZmljYXRpb24gYWNyb3NzIG11bHRpcGxlIGNsYXNzZXMuDQoNCi0tLQ0KDQojIyMg8J+nriAzLiBNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24NCg0KRWFjaCAqKkNvbnYyRCoqIGxheWVyOg0KICANCiAgb3V0cHV0ID0gUmVMVShXICogaW5wdXQgKyBiKQ0KDQpXaGVyZToNCi0gYCpgID0gY29udm9sdXRpb24gb3BlcmF0aW9uDQotIGBXYCA9IGZpbHRlci9rZXJuZWwgd2VpZ2h0cw0KLSBgYmAgPSBiaWFzDQotIGBSZUxVYCA9IG1heCgwLCB4KSwgZWxlbWVudC13aXNlIGFjdGl2YXRpb24NCg0KKipNYXhQb29saW5nMkQqKjoNCiAgDQogIFJlZHVjZXMgbWF0cml4IGJ5IHNlbGVjdGluZyB0aGUgbWF4IHZhbHVlIGluIGVhY2ggd2luZG93IChlLmcuLCAyeDIpLg0KDQoqKkRlbnNlIGxheWVyKio6DQogIA0KICBvdXRwdXQgPSBTb2Z0bWF4KFd4ICsgYikNCg0KLS0tDQoNCiMjIyDwn5K7IDQuIENvbXBsZXRlIFB5dGhvbiBDb2RlDQoNCmBgYHB5dGhvbg0KaW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYNCmZyb20gdGVuc29yZmxvdy5rZXJhcyBpbXBvcnQgZGF0YXNldHMsIGxheWVycywgbW9kZWxzDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgbnVtcHkgYXMgbnANCg0KIyBMb2FkIGFuZCBub3JtYWxpemUgQ0lGQVItMTANCih0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscyksICh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpID0gZGF0YXNldHMuY2lmYXIxMC5sb2FkX2RhdGEoKQ0KdHJhaW5faW1hZ2VzLCB0ZXN0X2ltYWdlcyA9IHRyYWluX2ltYWdlcyAvIDI1NS4wLCB0ZXN0X2ltYWdlcyAvIDI1NS4wDQoNCiMgRGVmaW5lIENOTiBtb2RlbA0KbXlfbW9kZWwgPSBtb2RlbHMuU2VxdWVudGlhbCgpDQpteV9tb2RlbC5hZGQobGF5ZXJzLkNvbnYyRCgzMiwgKDIsIDIpLCBhY3RpdmF0aW9uPSdyZWx1JywgaW5wdXRfc2hhcGU9KDMyLCAzMiwgMykpKQ0KbXlfbW9kZWwuYWRkKGxheWVycy5Db252MkQoNjQsICgzLCAzKSwgYWN0aXZhdGlvbj0ncmVsdScpKQ0KbXlfbW9kZWwuYWRkKGxheWVycy5NYXhQb29saW5nMkQoKDIsIDIpKSkNCm15X21vZGVsLmFkZChsYXllcnMuQ29udjJEKDE3LCAoMiwgMyksIGFjdGl2YXRpb249J3JlbHUnKSkNCm15X21vZGVsLmFkZChsYXllcnMuQ29udjJEKDE0LCAoNCwgNCksIGFjdGl2YXRpb249J3JlbHUnKSkNCm15X21vZGVsLmFkZChsYXllcnMuTWF4UG9vbGluZzJEKCgyLCAyKSkpDQpteV9tb2RlbC5hZGQobGF5ZXJzLkZsYXR0ZW4oKSkNCm15X21vZGVsLmFkZChsYXllcnMuRGVuc2UoMTAwLCBhY3RpdmF0aW9uPSdyZWx1JykpDQpteV9tb2RlbC5hZGQobGF5ZXJzLkRlbnNlKDEwLCBhY3RpdmF0aW9uPSdzb2Z0bWF4JykpDQoNCiMgQ29tcGlsZSB0aGUgbW9kZWwNCm15X21vZGVsLmNvbXBpbGUob3B0aW1pemVyPSdhZGFtJywNCiAgICAgICAgICAgICAgICAgbG9zcz10Zi5rZXJhcy5sb3NzZXMuU3BhcnNlQ2F0ZWdvcmljYWxDcm9zc2VudHJvcHkoZnJvbV9sb2dpdHM9RmFsc2UpLA0KICAgICAgICAgICAgICAgICBtZXRyaWNzPVsnYWNjdXJhY3knXSkNCg0KIyBUcmFpbiB0aGUgbW9kZWwNCmhpc3RvcnkgPSBteV9tb2RlbC5maXQodHJhaW5faW1hZ2VzLCB0cmFpbl9sYWJlbHMsIGVwb2Nocz01LCBiYXRjaF9zaXplPTUwLA0KICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2RhdGE9KHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscykpDQoNCiMgUHJlZGljdCBhbmQgdmlzdWFsaXplIHJlc3VsdHMNCnJlc3VsdHMgPSBteV9tb2RlbC5wcmVkaWN0KHRlc3RfaW1hZ2VzKQ0KDQojIFNob3cgcHJlZGljdGlvbnMgdnMgdHJ1ZSBsYWJlbHMNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTAsMTApKQ0KZm9yIGkgaW4gcmFuZ2UoMjUpOg0KICAgIHBsdC5zdWJwbG90KDUsNSxpKzEpDQogICAgcGx0Lnh0aWNrcyhbXSk7IHBsdC55dGlja3MoW10pOyBwbHQuZ3JpZChGYWxzZSkNCiAgICBwbHQuaW1zaG93KHRlc3RfaW1hZ2VzW2ldLCBjbWFwPXBsdC5jbS5iaW5hcnkpDQogICAgdHJ1ZV9sYWJlbCA9IHRlc3RfbGFiZWxzW2ldWzBdDQogICAgcHJlZF9sYWJlbCA9IG5wLmFyZ21heChyZXN1bHRzW2ldKQ0KICAgIHBsdC54bGFiZWwoZiJUcnVlOiB7dHJ1ZV9sYWJlbH0sIFByZWQ6IHtwcmVkX2xhYmVsfSIpDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tDQoNCiMjIyDwn5OKIDUuIEFjY3VyYWN5IE91dHB1dCBFeGFtcGxlDQoNClRyYWluaW5nIG91dHB1dCAoc2FtcGxlKToNCg0KLSB2YWxfYWNjdXJhY3k6IDAuNjExMiDihpIgMC42MzY4IOKGkiAwLjY3NzYgIA0KLSBFYWNoIGVwb2NoIGltcHJvdmVzIGFjY3VyYWN5Lg0KLSBGaW5hbCBwcmVkaWN0aW9uIHByb2JhYmlsaXRpZXMgaGF2ZSBzaGFwZSAoMTAwMDAsIDEwKSwgb25lIHJvdyBwZXIgdGVzdCBpbWFnZSwgMTAgY29sdW1ucyBwZXIgY2xhc3MuDQoNCi0tLQ0KDQojIyMg4pyFIDYuIEtleSBUYWtlYXdheXMNCg0KLSBDTk5zIGV4Y2VsIGF0IGltYWdlIHJlY29nbml0aW9uIGJlY2F1c2UgdGhleSBsZWFybiAqKnNwYXRpYWwgaGllcmFyY2hpZXMqKiAoZWRnZXMg4oaSIHNoYXBlcyDihpIgb2JqZWN0cykuDQotIFJlTFUgaXMgdGhlIGRlZmF1bHQgYWN0aXZhdGlvbiBmb3IgY29udm9sdXRpb25hbCBsYXllcnMuDQotIFNvZnRtYXggdHVybnMgcmF3IG91dHB1dHMgaW50byBwcm9iYWJpbGl0aWVzIGZvciBtdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbi4NCi0gWW91IG11c3QgKipmbGF0dGVuKiogdGhlIGNvbnZvbHV0aW9uYWwgb3V0cHV0IGJlZm9yZSBjb25uZWN0aW5nIGl0IHRvIGEgZGVuc2UgbGF5ZXIuDQotIE1vZGVsIHBlcmZvcm1hbmNlIGltcHJvdmVzIHdpdGggKipkZWVwZXIgYXJjaGl0ZWN0dXJlcyoqLCAqKmdvb2Qgbm9ybWFsaXphdGlvbioqLCBhbmQgKiptb3JlIGVwb2NocyoqLg0KDQotLS0NCg0KIyMjIOKdkyA3LiBQcmFjdGljZSBRdWVzdGlvbnMNCg0KMS4gV2hhdCBkb2VzIHRoZSBGbGF0dGVuIGxheWVyIGRvPw0KMi4gV2h5IGRvIHdlIHVzZSBNYXhQb29saW5nIGluIGEgQ05OPw0KMy4gV2hhdCBkb2VzIHRoZSBmaW5hbCBEZW5zZSgxMCwgYWN0aXZhdGlvbj0nc29mdG1heCcpIGxheWVyIHJlcHJlc2VudD8NCjQuIFdoeSBpcyB0aGUgQWRhbSBvcHRpbWl6ZXIgY29tbW9ubHkgdXNlZD8NCjUuIFdoYXQgc2hhcGUgZG8gdGhlIENOTiBwcmVkaWN0aW9ucyBoYXZlIGFuZCB3aHk/DQoNCi0tLQ0KDQojIyMg8J+nuCA4LiBMYXltYW4gRXhwbGFuYXRpb24g4oCTIFRoZSAiUGhvdG8gU29ydGluZyBNYWNoaW5lIg0KDQpJbWFnaW5lIGJ1aWxkaW5nIGEgc21hcnQgKipwaG90byBzb3J0ZXIqKjoNCi0gVGhlIGZpcnN0IG1hY2hpbmUgcGFydCBsb29rcyBmb3IgKiplZGdlcyoqICh3aGVyZSBjb2xvcnMgY2hhbmdlIHN1ZGRlbmx5KS4NCi0gVGhlIG5leHQgcGFydCBpZGVudGlmaWVzICoqc2hhcGVzKiogKGNhcnMsIGNhdHMsIGV0Yy4pLg0KLSBGaW5hbGx5LCBhIGRlY2lzaW9uLW1ha2VyIHJlYWRzIGV2ZXJ5dGhpbmcgYW5kIHNheXMsIOKAnFRoaXMgaXMgYSB0cnVjayHigJ0NCg0KRWFjaCAicGFydCIgaXMgYSAqKmxheWVyKiogaW4geW91ciBuZXVyYWwgbmV0d29yazoNCi0gKipDb252b2x1dGlvbiBsYXllcnMqKiA9IGZpbmQgcGF0dGVybnMgKGxpa2UgdGV4dHVyZXMpDQotICoqUG9vbGluZyBsYXllcnMqKiA9IHpvb20gb3V0IHRvIHJlZHVjZSBjbHV0dGVyDQotICoqRGVuc2UgbGF5ZXJzKiogPSBtYWtlIGRlY2lzaW9ucyBiYXNlZCBvbiB3aGF0IHdhcyBmb3VuZA0KLSAqKlNvZnRtYXgqKiA9IGdpdmVzIHRoZSBmaW5hbCB2b3RlIChwZXJjZW50IGNoYW5jZSBpdCdzIGVhY2ggb2JqZWN0KQ0KDQpKdXN0IGxpa2Ugc29ydGluZyB5b3VyIHBob3RvcyBieSBleWUsIENOTnMgZG8gdGhpcyBhdXRvbWF0aWNhbGx54oCUYnV0IHdpdGggbWF0aCBhbmQgdHJhaW5pbmchDQoNCi0tLQ0KDQojIyMg8J+TmiBUZXh0Ym9vayBTdXBwb3J0DQoNCioqX1RoZSBFbGVtZW50cyBvZiBTdGF0aXN0aWNhbCBMZWFybmluZ18qKiAgDQotIENoYXB0ZXIgMTE6IE5ldXJhbCBOZXR3b3JrcyAocGcuIDM5MispICANCmh0dHBzOi8vd2ViLnN0YW5mb3JkLmVkdS9+aGFzdGllL0VsZW1TdGF0TGVhcm4vDQoNCioqX1RlbnNvckZsb3cgVHV0b3JpYWwgKE9mZmljaWFsKV8qKiAgDQpodHRwczovL3d3dy50ZW5zb3JmbG93Lm9yZy90dXRvcmlhbHMvaW1hZ2VzL2Nubg0KDQotLS1nZW5lcmF0ZSB0aGlzIGFzOg0KLSBBICoqSnVweXRlciBOb3RlYm9vayoqIGZvciBpbW1lZGlhdGUgdXNlPw0KLSBBICoqUERGIFN0dWR5IFNoZWV0KiogZm9yIHByaW50aW5nPw0KLSBBbiAqKmludGVyYWN0aXZlIEhUTUwgcHJlc2VudGF0aW9uKiogd2l0aCBzbGlkZXJzPw0KDQpQZXJmZWN0LiBJ4oCZdmUgY29tcGlsZWQgeW91ciBtYXRlcmlhbHMgaW50byBhIHN0cnVjdHVyZWQgc3R1ZHkgZ3VpZGUgdGhhdCBjb3ZlcnMgdGhlIGZvbGxvd2luZyB0b3BpY3M6DQoNCjEuICoqQ29udm9sdXRpb25hbCBMYXllciBGdW5kYW1lbnRhbHMqKg0KMi4gKipUcmFuc2ZlciBMZWFybmluZyBDb25jZXB0cyoqDQozLiAqKkNOTiBQYXJ0IEk6IERhdGFzZXQgU2V0dXAqKg0KNC4gKipDTk4gUGFydCBJSTogTW9kZWwgQnVpbGRpbmcqKg0KNS4gKipUcmFuc2ZlciBMZWFybmluZzogVkdHMTYgKyBDdXN0b20gQ2xhc3NpZmllcioqDQoNCkknbGwgd2FsayB5b3UgdGhyb3VnaCBlYWNoIHNlY3Rpb24gaW4gYW4gZWFzeS10by1mb2xsb3cgZm9ybWF0Og0KDQotLS0NCg0KQ29udm9sdXRpb25hbCBOZXVyYWwgTmV0d29ya3M6IEJlZ2lubmVyIFN0dWR5IEd1aWRlICANCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNClNlY3Rpb24gMTogKipXaGF0IGlzIGEgQ29udm9sdXRpb24/KiogIA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoqKkNvbmNlcHQqKjogIA0KQSBjb252b2x1dGlvbiBpcyB3aGVuIHdlIHNsaWRlIGEgZmlsdGVyIChzbWFsbCBtYXRyaXgpIG92ZXIgZGF0YSAobGlrZSBhbiBpbWFnZSkgdG8gZXh0cmFjdCBtZWFuaW5nZnVsIGZlYXR1cmVzIChsaWtlIGVkZ2VzIG9yIHBhdHRlcm5zKS4gRWFjaCBvdmVybGFwIGlzIG11bHRpcGxpZWQgZWxlbWVudC13aXNlIGFuZCBzdW1tZWQgaW50byBhIG5ldyBvdXRwdXQgdmFsdWUuDQoNCioqTWF0aCoqOiAgDQpJZiAqKkkqKiBpcyBhbiBpbWFnZSBtYXRyaXggYW5kICoqSyoqIGlzIGEgZmlsdGVyOg0KDQogICAgUyhpLGopID0g4oiR4oiRIEsobSxuKSAqIEkoaSttLCBqK24pDQoNClRoaXMgaXMgZG9uZSBmb3IgYWxsIHZhbGlkIHBvc2l0aW9ucyBvZiB0aGUgZmlsdGVyLg0KDQoqKlB5dGhvbiBFeGFtcGxlKio6DQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KZnJvbSBzY2lweS5zaWduYWwgaW1wb3J0IGNvbnZvbHZlMmQNCg0KaW1hZ2UgPSBucC5hcnJheShbWzEsIDIsIDNdLA0KICAgICAgICAgICAgICAgICAgWzQsIDUsIDZdLA0KICAgICAgICAgICAgICAgICAgWzcsIDgsIDldXSkNCg0Ka2VybmVsID0gbnAuYXJyYXkoW1swLCAxXSwNCiAgICAgICAgICAgICAgICAgICBbMSwgMF1dKQ0KDQpyZXN1bHQgPSBjb252b2x2ZTJkKGltYWdlLCBrZXJuZWwsIG1vZGU9J3ZhbGlkJykNCnByaW50KHJlc3VsdCkNCmBgYA0KDQoqKktleSBUZXJtcyoqOg0KLSAqKlBhZGRpbmcqKjogQWRkcyBleHRyYSBib3JkZXJzIHRvIG1haW50YWluIG9yaWdpbmFsIHNpemUuDQotICoqU3RyaWRlKio6IEhvdyBtYW55IHN0ZXBzIHRoZSBmaWx0ZXIgbW92ZXMuDQotICoqRmlsdGVyKio6IEFsc28gY2FsbGVkIGEga2VybmVsLg0KDQoqKlRha2Vhd2F5cyoqOg0KLSBGaWx0ZXJzIGFyZSBsZWFybmVkIGR1cmluZyB0cmFpbmluZy4NCi0gQ29udm9sdXRpb25zIGRldGVjdCBwYXR0ZXJucyBsaWtlIGVkZ2VzLCBjb2xvciwgYW5kIHRleHR1cmVzLg0KDQoqKlJlbGV2YW50IFF1ZXN0aW9ucyoqOg0KLSBXaHkgZG8gd2UgdXNlIHBhZGRpbmc/DQotIFdoYXQgaGFwcGVucyB3aGVuIHlvdSBpbmNyZWFzZSB0aGUgc3RyaWRlPw0KDQoqKkxheW1hbiBBbmFsb2d5Kio6ICANCkltYWdpbmUgdXNpbmcgYSBzbWFsbCB3aW5kb3cgdG8gbG9vayBhdCBkaWZmZXJlbnQgcGFydHMgb2YgYSBiaWcgcGFpbnRpbmcuIEVhY2ggdGltZSwgeW91IHdyaXRlIGRvd24gYSBzdW1tYXJ5IG9mIHdoYXQgeW91IHNlZSAobGlrZSBjb2xvciBpbnRlbnNpdHkgb3Igc2hhcnBuZXNzKS4gWW91IHJlcGVhdCB0aGlzIHVudGlsIHlvdSd2ZSBzY2FubmVkIHRoZSB3aG9sZSBwaWN0dXJlLg0KDQotLS0NCg0KU2VjdGlvbiAyOiAqKlRyYW5zZmVyIExlYXJuaW5nIEV4cGxhaW5lZCoqICANCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoqKkNvbmNlcHQqKjogIA0KVHJhbnNmZXIgbGVhcm5pbmcgbGV0cyB1cyByZXVzZSBhIHBvd2VyZnVsIHByZXRyYWluZWQgbW9kZWwgKGxpa2UgVkdHMTYpIGFuZCBqdXN0IHRyYWluIHRoZSBmaW5hbCBsYXllcnMgb24gb3VyIHNwZWNpZmljIHRhc2suDQoNCioqV2h5PyoqICANCkFkdmFuY2VkIG1vZGVscyB0YWtlIHdlZWtzIGFuZCBsb3RzIG9mIGNvbXB1dGUuIFRyYW5zZmVyIGxlYXJuaW5nIGxldHMgdXMgdXNlIHByZXRyYWluZWQgZmVhdHVyZXMgYW5kIHRyYWluIG9ubHkgdGhlICJoZWFkIiAoZmluYWwgbGF5ZXJzKS4NCg0KKipEaWFncmFtIFN1bW1hcnkqKjogIA0KTGVmdCA9IHByZXRyYWluZWQgY29udm9sdXRpb24gbGF5ZXJzICANClJpZ2h0ID0gbmV3IGRlbnNlIGxheWVycyAgDQpXZSBrZWVwIHRoZSBsZWZ0IHNpZGUgImZyb3plbiIgYW5kIHRyYWluIG9ubHkgdGhlIHJpZ2h0Lg0KDQoqKk1hdGhlbWF0aWNzKio6DQpJZiAqKkYqKiBpcyB0aGUgZmVhdHVyZSBleHRyYWN0b3IgYW5kICoqVyoqIGFyZSB0cmFpbmFibGUgd2VpZ2h0czoNCg0KICAgIFByZWRpY3Rpb24gPSBzb2Z0bWF4KFcgKiBGKHgpKQ0KDQoqKlB5dGhvbiBFeGFtcGxlKio6DQpgYGBweXRob24NCmJhc2VfbW9kZWwgPSB0Zi5rZXJhcy5hcHBsaWNhdGlvbnMuVkdHMTYoaW5wdXRfc2hhcGU9KDE2MCwxNjAsMyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdG9wPUZhbHNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzPSdpbWFnZW5ldCcpDQpiYXNlX21vZGVsLnRyYWluYWJsZSA9IEZhbHNlDQpgYGANCg0KKipUYWtlYXdheXMqKjoNCi0gU2F2ZXMgY29tcHV0ZSBhbmQgdGltZS4NCi0gV29ya3Mgd2l0aCBmZXcgc2FtcGxlcy4NCi0gQ2FuIGJlIHVzZWQgYWNyb3NzIGRvbWFpbnMuDQoNCioqUmVsZXZhbnQgUXVlc3Rpb25zKio6DQotIFdoYXQgbGF5ZXJzIHNob3VsZCB5b3UgZnJlZXplPw0KLSBXaHkgaXMgc29mdG1heCB1c2VkIGluIHRoZSBmaW5hbCBsYXllcj8NCg0KKipMYXltYW4gQW5hbG9neSoqOiAgDQpJbWFnaW5lIHlvdeKAmXJlIGxlYXJuaW5nIHRvIGJha2UgY2FrZXMuIEluc3RlYWQgb2YgbGVhcm5pbmcgZXZlcnl0aGluZyBmcm9tIHNjcmF0Y2gsIHlvdSBidXkgYSBjYWtlIG1peCAocHJldHJhaW5lZCBtb2RlbCkgYW5kIGp1c3QgZm9jdXMgb24gZGVjb3JhdGluZyBpdCB0byB5b3VyIHN0eWxlIChjdXN0b20gbGF5ZXJzKS4NCg0KLS0tDQoNClNlY3Rpb24gMzogKipDTk4sIFBhcnQgSTogU2V0IFVwIFlvdXIgRGF0YSoqICANCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCioqQ29kZSBTbmlwcGV0Kio6DQpgYGBweXRob24NCmltcG9ydCB0ZW5zb3JmbG93IGFzIHRmDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMgaW1wb3J0IGRhdGFzZXRzLCBsYXllcnMsIG1vZGVscw0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQoodHJhaW5faW1hZ2VzLCB0cmFpbl9sYWJlbHMpLCAodGVzdF9pbWFnZXMsIHRlc3RfbGFiZWxzKSA9IGRhdGFzZXRzLmNpZmFyMTAubG9hZF9kYXRhKCkNCnRyYWluX2ltYWdlcyA9IHRyYWluX2ltYWdlcyAvIDI1NS4wICAjIG5vcm1hbGl6ZSBwaXhlbCB2YWx1ZXMNCnRlc3RfaW1hZ2VzID0gdGVzdF9pbWFnZXMgLyAyNTUuMA0KYGBgDQoNCioqVmlzdWFsaXphdGlvbioqOg0KYGBgcHl0aG9uDQpjbGFzc19uYW1lcyA9IFsnYWlycGxhbmUnLCAnYXV0b21vYmlsZScsICdiaXJkJywgJ2NhdCcsICdkZWVyJywNCiAgICAgICAgICAgICAgICdkb2cnLCAnZnJvZycsICdob3JzZScsICdzaGlwJywgJ3RydWNrJ10NCg0KcGx0LmZpZ3VyZShmaWdzaXplPSgxMCwxMCkpDQpmb3IgaSBpbiByYW5nZSgyNSk6DQogICAgcGx0LnN1YnBsb3QoNSw1LGkrMSkNCiAgICBwbHQueHRpY2tzKFtdKSwgcGx0Lnl0aWNrcyhbXSkNCiAgICBwbHQuZ3JpZChGYWxzZSkNCiAgICBwbHQuaW1zaG93KHRyYWluX2ltYWdlc1tpXSwgY21hcD1wbHQuY20uYmluYXJ5KQ0KICAgIHBsdC54bGFiZWwoY2xhc3NfbmFtZXNbdHJhaW5fbGFiZWxzW2ldWzBdXSkNCnBsdC5zaG93KCkNCmBgYA0KDQoqKlRha2Vhd2F5cyoqOg0KLSBOb3JtYWxpemluZyBpbWFnZSBkYXRhICgw4oCTMjU1IOKGkiAw4oCTMSkgaXMgY3J1Y2lhbC4NCi0gVmlzdWFsaXppbmcgaGVscHMgdmVyaWZ5IHlvdXIgbGFiZWxzIGFuZCBzdHJ1Y3R1cmUuDQoNCioqUmVsZXZhbnQgUXVlc3Rpb25zKio6DQotIFdoeSBub3JtYWxpemUgcGl4ZWwgdmFsdWVzPw0KLSBXaGF0IGRvZXMgQ0lGQVItMTAgY29udGFpbj8NCg0KLS0tDQoNClNlY3Rpb24gNDogKipDTk4sIFBhcnQgSUk6IEJ1aWxkIHRoZSBNb2RlbCoqICANCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCioqQXJjaGl0ZWN0dXJlIE92ZXJ2aWV3Kio6DQpgYGBweXRob24NCm1vZGVsID0gbW9kZWxzLlNlcXVlbnRpYWwoKQ0KbW9kZWwuYWRkKGxheWVycy5Db252MkQoMzIsICgyLDIpLCBhY3RpdmF0aW9uPSdyZWx1JywgaW5wdXRfc2hhcGU9KDMyLDMyLDMpKSkNCm1vZGVsLmFkZChsYXllcnMuQ29udjJEKDY0LCAoMywzKSwgYWN0aXZhdGlvbj0ncmVsdScpKQ0KbW9kZWwuYWRkKGxheWVycy5NYXhQb29saW5nMkQoKDIsMikpKQ0KDQptb2RlbC5hZGQobGF5ZXJzLkNvbnYyRCgxMjgsICg0LDMpLCBhY3RpdmF0aW9uPSdyZWx1JykpDQptb2RlbC5hZGQobGF5ZXJzLkNvbnYyRCgxMjgsICg0LDQpLCBhY3RpdmF0aW9uPSdyZWx1JykpDQptb2RlbC5hZGQobGF5ZXJzLk1heFBvb2xpbmcyRCgoMiwyKSkpDQoNCm1vZGVsLmFkZChsYXllcnMuRmxhdHRlbigpKQ0KbW9kZWwuYWRkKGxheWVycy5EZW5zZSgxMDAsIGFjdGl2YXRpb249J3JlbHUnKSkNCm1vZGVsLmFkZChsYXllcnMuRGVuc2UoMTAsIGFjdGl2YXRpb249J3NvZnRtYXgnKSkgICMgRm9yIDEwIGNsYXNzZXMNCmBgYA0KDQoqKkNvbXBpbGUgYW5kIFRyYWluKio6DQpgYGBweXRob24NCm1vZGVsLmNvbXBpbGUob3B0aW1pemVyPSdhZGFtJywNCiAgICAgICAgICAgICAgbG9zcz10Zi5rZXJhcy5sb3NzZXMuU3BhcnNlQ2F0ZWdvcmljYWxDcm9zc2VudHJvcHkoZnJvbV9sb2dpdHM9RmFsc2UpLA0KICAgICAgICAgICAgICBtZXRyaWNzPVsnYWNjdXJhY3knXSkNCg0KbW9kZWwuZml0KHRyYWluX2ltYWdlcywgdHJhaW5fbGFiZWxzLCBlcG9jaHM9NSwgYmF0Y2hfc2l6ZT01MCwgdmFsaWRhdGlvbl9kYXRhPSh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpKQ0KYGBgDQoNCioqVGFrZWF3YXlzKio6DQotIGBDb252MkRgIGxheWVycyBleHRyYWN0IGZlYXR1cmVzLg0KLSBgTWF4UG9vbGluZzJEYCByZWR1Y2VzIHNpemUgYW5kIGtlZXBzIGltcG9ydGFudCBmZWF0dXJlcy4NCi0gYERlbnNlYCBsYXllcnMgbWFrZSBmaW5hbCBwcmVkaWN0aW9ucy4NCg0KKipSZWxldmFudCBRdWVzdGlvbnMqKjoNCi0gV2hhdCBkb2VzIHRoZSBmbGF0dGVuIGxheWVyIGRvPw0KLSBXaHkgaXMgc29mdG1heCBnb29kIGZvciBtdWx0aWNsYXNzIGNsYXNzaWZpY2F0aW9uPw0KDQoqKkxheW1hbiBBbmFsb2d5Kio6ICANCkltYWdpbmUgeW91ciBtb2RlbCBpcyBsaWtlIGEgZGV0ZWN0aXZlLiBUaGUgY29udm9sdXRpb25hbCBsYXllcnMgYXJlIGxpa2UgdGhlIGRldGVjdGl2ZSBnYXRoZXJpbmcgY2x1ZXMgKGVkZ2VzLCBjb2xvcnMpLCB0aGUgZmxhdHRlbiBsYXllciBvcmdhbml6ZXMgYWxsIHRoZSBjbHVlcyBpbiBhIHNpbmdsZSBmaWxlLCBhbmQgdGhlIGRlbnNlIGxheWVycyB1c2UgdGhhdCBmaWxlIHRvIGZpZ3VyZSBvdXQ6IOKAnElzIHRoaXMgYSBjYXQgb3IgYSB0cnVjaz/igJ0NCg0KLS0tDQoNClNlY3Rpb24gNTogKipUcmFuc2ZlciBMZWFybmluZyBpbiBQcmFjdGljZSAoVkdHMTYgKyBDYXRzIHZzIERvZ3MpKiogIA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCioqU3RlcHMgU3VtbWFyeSoqOg0KMS4gTG9hZCBkYXRhc2V0IHdpdGggYGltYWdlX2RhdGFzZXRfZnJvbV9kaXJlY3RvcnlgDQoyLiBOb3JtYWxpemUgd2l0aCBgUmVzY2FsaW5nYA0KMy4gTG9hZCBWR0cxNiBiYXNlIChleGNsdWRlIHRvcCkNCjQuIEZyZWV6ZSBiYXNlIG1vZGVsDQo1LiBBZGQgZ2xvYmFsIGF2ZXJhZ2UgcG9vbGluZywgZHJvcG91dCwgYW5kIGRlbnNlIG91dHB1dA0KNi4gQ29tcGlsZSBhbmQgdHJhaW4NCg0KKipLZXkgQ29kZSoqOg0KYGBgcHl0aG9uDQpiYXNlX21vZGVsID0gdGYua2VyYXMuYXBwbGljYXRpb25zLlZHRzE2KGluY2x1ZGVfdG9wPUZhbHNlLCB3ZWlnaHRzPSdpbWFnZW5ldCcsIGlucHV0X3NoYXBlPSgxNjAsMTYwLDMpKQ0KYmFzZV9tb2RlbC50cmFpbmFibGUgPSBGYWxzZQ0KDQptb2RlbCA9IHRmLmtlcmFzLlNlcXVlbnRpYWwoWw0KICAgIHRmLmtlcmFzLmxheWVycy5SZXNjYWxpbmcoMS4vMTI3LjUsIG9mZnNldD0tMSksDQogICAgYmFzZV9tb2RlbCwNCiAgICB0Zi5rZXJhcy5sYXllcnMuR2xvYmFsQXZlcmFnZVBvb2xpbmcyRCgpLA0KICAgIHRmLmtlcmFzLmxheWVycy5Ecm9wb3V0KDAuMiksDQogICAgdGYua2VyYXMubGF5ZXJzLkRlbnNlKDEpICAjIGJpbmFyeSBjbGFzc2lmaWNhdGlvbg0KXSkNCg0KbW9kZWwuY29tcGlsZShvcHRpbWl6ZXI9dGYua2VyYXMub3B0aW1pemVycy5BZGFtKGxlYXJuaW5nX3JhdGU9MC4wMDAxKSwNCiAgICAgICAgICAgICAgbG9zcz10Zi5rZXJhcy5sb3NzZXMuQmluYXJ5Q3Jvc3NlbnRyb3B5KGZyb21fbG9naXRzPVRydWUpLA0KICAgICAgICAgICAgICBtZXRyaWNzPVsnYWNjdXJhY3knXSkNCg0KbW9kZWwuZml0KHRyYWluX2RhdGFzZXQsIGVwb2Nocz0xMCwgdmFsaWRhdGlvbl9kYXRhPXZhbGlkYXRpb25fZGF0YXNldCkNCmBgYA0KDQoqKlRha2Vhd2F5cyoqOg0KLSBWR0cxNiBoYXMgMTQgbWlsbGlvbisgcGFyYW1ldGVyczsgd2Ugb25seSB0cmFpbiBhIGZldyBodW5kcmVkLg0KLSBGYXN0IHRyYWluaW5nLCBldmVuIG9uIHNtYWxsIGRhdGFzZXRzLg0KLSBBY2N1cmFjeSBpbXByb3ZlcyBpbiBqdXN0IGEgZmV3IGVwb2Nocy4NCg0KKipSZWxldmFudCBRdWVzdGlvbnMqKjoNCi0gV2hhdCBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGNhdGVnb3JpY2FsIGFuZCBiaW5hcnkgY3Jvc3NlbnRyb3B5Pw0KLSBXaGF0IGRvZXMgZHJvcG91dCBkbz8NCg0KKipMYXltYW4gQW5hbG9neSoqOiAgDQpUaGluayBvZiB0aGUgVkdHMTYgbW9kZWwgYXMgYW4gZXhwZXJ0IGFydCBjcml0aWMgd2hvIGhhcyBzdHVkaWVkIG1pbGxpb25zIG9mIHBhaW50aW5ncy4gWW914oCZcmUgdHJhaW5pbmcgYSBzbWFsbCBhc3Npc3RhbnQgdG8gb25seSB0ZWxsIGFwYXJ0IGNhdHMgYW5kIGRvZ3MuIFlvdSBkb27igJl0IHJldHJhaW4gdGhlIGV4cGVydCwganVzdCB1c2UgdGhlaXIgb3BpbmlvbnMgKGZlYXR1cmVzKSBhbmQgdGVhY2ggeW91ciBhc3Npc3RhbnQgaG93IHRvIGludGVycHJldCB0aGVtIGZvciBhIHZlcnkgc3BlY2lmaWMgdGFzay4NCg0KLS0tDQoNCkhlcmXigJlzIGEgZGlzdGlsbGVkIHN5bnRoZXNpcyBmcm9tIGV2ZXJ5dGhpbmcgd2XigJl2ZSBkb25lIHRvZGF54oCUaW5jbHVkaW5nIHRoZSBzY3JlZW5zaG90cywgY2xhc3MgdHJhbnNjcmlwdHMsIGFuZCB0aGUgY29uY2VwdHMgaW4geW91ciBQREYgbWF0ZXJpYWxz4oCUaW50byAqKnRocmVlIGdlbmVyYWwgdGFrZWF3YXlzKiogYW5kICoqdGhyZWUgdGhvdWdodGZ1bCBxdWVzdGlvbnMgZm9yIERyLiBTbGF0ZXIqKjoNCg0KLS0tDQoNCioqVGhyZWUgR2VuZXJhbCBUYWtlYXdheXMqKiAgDQo9PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KKioxLiBUcmFuc2ZlciBMZWFybmluZyBpcyBhIEdhbWUtQ2hhbmdlciBmb3IgUmVzb3VyY2UtTGltaXRlZCBUcmFpbmluZyoqICANCkJ5IGZyZWV6aW5nIHRoZSBjb252b2x1dGlvbmFsIGJhc2Ugb2YgcHJldHJhaW5lZCBtb2RlbHMgKGxpa2UgVkdHMTYpIGFuZCB0cmFpbmluZyBvbmx5IHRoZSBjbGFzc2lmaWVyIGhlYWQsIHdlIHVubG9jayB0aGUgcG93ZXIgb2YgaGlnaGx5IGNvbXBsZXggbW9kZWxzIHdpdGhvdXQgbmVlZGluZyBtYXNzaXZlIGNvbXB1dGUuIFRoaXMgYXBwcm9hY2ggc2lnbmlmaWNhbnRseSBhY2NlbGVyYXRlcyB0cmFpbmluZyB3aGlsZSBtYWludGFpbmluZyBzdHJvbmcgcGVyZm9ybWFuY2UsIGV2ZW4gd2l0aCBsaW1pdGVkIGRhdGEuDQoNCioqMi4gQ05OcyBSZWx5IEhlYXZpbHkgb24gUHJvcGVyIERhdGEgSGFuZGxpbmcgYW5kIEFyY2hpdGVjdHVyYWwgQmFsYW5jZSoqICANClBlcmZvcm1hbmNlIGlzIHN0cm9uZ2x5IGluZmx1ZW5jZWQgYnkgc2VlbWluZ2x5IHNpbXBsZSBwcmVwcm9jZXNzaW5nIHN0ZXBzIChlLmcuLCBub3JtYWxpemF0aW9uIGJ5IGRpdmlkaW5nIGJ5IDI1NSwgcmVzaXppbmcgaW1hZ2VzLCBwcm9wZXIgbGFiZWwgZW5jb2RpbmcpLiBBZGRpdGlvbmFsbHksIHRoZSBkZXB0aCBvZiBjb252b2x1dGlvbiBhbmQgcG9vbGluZyBsYXllcnMgbXVzdCBiZSBiYWxhbmNlZCB3aXRoIG1lbW9yeSBjb25zdHJhaW50cyBhbmQgdGFzayBjb21wbGV4aXR5Lg0KDQoqKjMuIFRoZSBTaGlmdCBmcm9tIERlbnNlIHRvIENvbnZvbHV0aW9uYWwgTmV0d29ya3MgUmVmbGVjdHMgYSBDaGFuZ2UgaW4gSG93IE1vZGVscyAiU2VlIiBEYXRhKiogIA0KRGVuc2UgbmV0d29ya3MgdHJlYXQgaW5wdXQgYXMgZmxhdCB2ZWN0b3JzOyBDTk5zIHRyZWF0IGl0IHNwYXRpYWxseS4gVGhpcyBpcyBjcnVjaWFsIGZvciBpbWFnZXMsIHdoZXJlIHN0cnVjdHVyZSBhbmQgbG9jYWxpdHkgbWF0dGVyLiBDTk5zIGxlYXJuIGhpZXJhcmNoaWNhbCBmZWF0dXJlc+KAlGZyb20gZWRnZXMgaW4gZWFybHkgbGF5ZXJzIHRvIGNvbXBsZXggc2hhcGVzIGluIGRlZXBlciBvbmVz4oCUbWltaWNraW5nIGhvdyB2aXN1YWwgY29ydGV4IG5ldXJvbnMgb3BlcmF0ZS4NCg0KLS0tDQoNCioqVGhyZWUgVGhvdWdodC1Qcm92b2tpbmcgUXVlc3Rpb25zIGZvciBEci4gU2xhdGVyKiogIA0KPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQoqKjEuIEdpdmVuIHRoZSBiZW5lZml0cyBvZiB0cmFuc2ZlciBsZWFybmluZywgaG93IGRvIHdlIGFzc2VzcyB3aGVuIGl0J3MgbW9yZSBhcHByb3ByaWF0ZSB0byBmaW5lLXR1bmUgdmVyc3VzIGZyZWV6ZSBsYXllcnMsIGVzcGVjaWFsbHkgd2hlbiB3b3JraW5nIHdpdGggc2NpZW50aWZpYyBvciBuaWNoZSBkYXRhc2V0cyBsaWtlIG1lZGljYWwgc2NhbnMgb3Igc2F0ZWxsaXRlIGltYWdlcnk/KioNCg0KKioyLiBJbiByZWFsLXdvcmxkIGRlcGxveW1lbnQsIGhvdyBkbyB3ZSBtaXRpZ2F0ZSB0aGUgcmlza3Mgb2YgQ05OIG1pc2NsYXNzaWZpY2F0aW9uIChlLmcuLCBmcm9nIHZzLiBkb2cpIGluIGhpZ2gtc3Rha2VzIGFwcGxpY2F0aW9ucyBzdWNoIGFzIGF1dG9ub21vdXMgdmVoaWNsZXMsIG1pbGl0YXJ5IHN1cnZlaWxsYW5jZSwgb3IgaGVhbHRoY2FyZSBkaWFnbm9zdGljcz8qKg0KDQoqKjMuIEZyb20gYSBwZWRhZ29naWNhbCBvciByZXNlYXJjaCBzdGFuZHBvaW50LCB3aGVyZSBkbyB5b3Ugc2VlIHRoZSBiaWdnZXN0IGNvbmNlcHR1YWwgZ2FwcyBpbiBzdHVkZW50IHVuZGVyc3RhbmRpbmcgd2hlbiB0cmFuc2l0aW9uaW5nIGZyb20gZGVuc2UgdG8gY29udm9sdXRpb25hbCBuZXVyYWwgbmV0d29ya3PigJRhbmQgaG93IGNhbiB3ZSBiZXR0ZXIgYnJpZGdlIHRoYXQgbGVhcCwgcGVyaGFwcyB3aXRoIHZpc3VhbCB0b29scyBvciBoYW5kcy1vbiBkZW1vcz8qKg0KDQotLS0NCg0KDQpgYGBsYXRleA0KXHNlY3Rpb24qe0dlbmVyYWwgVGFrZWF3YXlzIGZyb20gQ05OcyBhbmQgVHJhbnNmZXIgTGVhcm5pbmd9DQoNClxiZWdpbntlbnVtZXJhdGV9DQogICAgXGl0ZW0gXHRleHRiZntUcmFuc2ZlciBMZWFybmluZyBpcyBhIEdhbWUtQ2hhbmdlciBmb3IgUmVzb3VyY2UtTGltaXRlZCBUcmFpbmluZ30gXFwNCiAgICBGcmVlemluZyB0aGUgY29udm9sdXRpb25hbCBiYXNlIG9mIHByZXRyYWluZWQgbW9kZWxzIGxpa2UgVkdHMTYgYW5kIHRyYWluaW5nIG9ubHkgdGhlIGRlbnNlIGNsYXNzaWZpZXIgaGVhZCBhbGxvd3MgdXMgdG8gbGV2ZXJhZ2UgcG93ZXJmdWwgbW9kZWxzIHdpdGhvdXQgdGhlIG5lZWQgZm9yIG1hc3NpdmUgY29tcHV0ZSByZXNvdXJjZXMuIFRoaXMgZ3JlYXRseSByZWR1Y2VzIHRyYWluaW5nIHRpbWUgd2hpbGUgbWFpbnRhaW5pbmcgc3Ryb25nIHBlcmZvcm1hbmNlLCBlc3BlY2lhbGx5IHdoZW4gd29ya2luZyB3aXRoIHNtYWxsZXIgZGF0YXNldHMuDQoNCiAgICBcaXRlbSBcdGV4dGJme0NOTnMgUmVseSBIZWF2aWx5IG9uIFByb3BlciBEYXRhIEhhbmRsaW5nIGFuZCBBcmNoaXRlY3R1cmFsIEJhbGFuY2V9IFxcDQogICAgUGVyZm9ybWFuY2UgaXMgc2lnbmlmaWNhbnRseSBpbmZsdWVuY2VkIGJ5IHByZXByb2Nlc3Npbmcgc3RlcHMgc3VjaCBhcyBub3JtYWxpemF0aW9uIChlLmcuLCBkaXZpZGluZyBwaXhlbCB2YWx1ZXMgYnkgMjU1KSwgaW1hZ2UgcmVzaXppbmcsIGFuZCBsYWJlbCBmb3JtYXR0aW5nLiBUaGUgYXJjaGl0ZWN0dXJl4oCZcyBkZXB0aCAobnVtYmVyIGFuZCBzaXplIG9mIGNvbnZvbHV0aW9uIGFuZCBwb29saW5nIGxheWVycykgbXVzdCBiZSB0dW5lZCBpbiByZWxhdGlvbiB0byB0aGUgY29tcGxleGl0eSBvZiB0aGUgdGFzayBhbmQgYXZhaWxhYmxlIG1lbW9yeS4NCg0KICAgIFxpdGVtIFx0ZXh0YmZ7VGhlIFNoaWZ0IGZyb20gRGVuc2UgdG8gQ29udm9sdXRpb25hbCBOZXR3b3JrcyBSZWZsZWN0cyBhIENoYW5nZSBpbiBIb3cgTW9kZWxzICJTZWUiIERhdGF9IFxcDQogICAgRGVuc2UgbmV0d29ya3MgdHJlYXQgZGF0YSBhcyBmbGF0IHZlY3RvcnM7IENOTnMgcHJvY2VzcyBpdCBzcGF0aWFsbHkuIFRoaXMgaXMgZXNzZW50aWFsIGZvciBpbWFnZSB0YXNrcyB3aGVyZSBzcGF0aWFsIGxvY2FsaXR5IG1hdHRlcnMuIENOTnMgYnVpbGQgaGllcmFyY2hpY2FsIGZlYXR1cmUgbWFwc+KAlGZyb20gc2ltcGxlIGVkZ2VzIHRvIGNvbXBsZXggb2JqZWN0c+KAlHNpbWlsYXIgdG8gdGhlIGh1bWFuIHZpc3VhbCBzeXN0ZW0uDQpcZW5ke2VudW1lcmF0ZX0NCg0KXHZzcGFjZXswLjVjbX0NClxzZWN0aW9uKntUaG91Z2h0LVByb3Zva2luZyBRdWVzdGlvbnMgZm9yIERyLiBTbGF0ZXJ9DQoNClxiZWdpbntlbnVtZXJhdGV9DQogICAgXGl0ZW0gXHRleHRiZntIb3cgc2hvdWxkIHdlIGRlY2lkZSB3aGV0aGVyIHRvIGZyZWV6ZSBvciBmaW5lLXR1bmUgcHJldHJhaW5lZCBjb252b2x1dGlvbmFsIGxheWVycyB3aGVuIHdvcmtpbmcgd2l0aCBkb21haW4tc3BlY2lmaWMgZGF0YXNldHMgKGUuZy4sIG1lZGljYWwgaW1hZ2luZywgc2F0ZWxsaXRlIGRhdGEsIG9yIGVudmlyb25tZW50YWwgYW5hbHlzaXMpP30NCg0KICAgIFxpdGVtIFx0ZXh0YmZ7V2hhdCBzYWZlZ3VhcmRzIG9yIG1vZGVsIGV2YWx1YXRpb24gc3RyYXRlZ2llcyB3b3VsZCB5b3UgcmVjb21tZW5kIHdoZW4gZGVwbG95aW5nIENOTnMgaW4gaGlnaC1zdGFrZXMgZW52aXJvbm1lbnRzIChlLmcuLCBkZWZlbnNlLCB0cmFuc3BvcnRhdGlvbiwgaGVhbHRoY2FyZSkgdG8gcmVkdWNlIHJpc2tzIGZyb20gbWlzY2xhc3NpZmljYXRpb25zIGxpa2UgbWlzdGFraW5nIGEgZnJvZyBmb3IgYSBkb2c/fQ0KDQogICAgXGl0ZW0gXHRleHRiZntGcm9tIHlvdXIgZXhwZXJpZW5jZSB0ZWFjaGluZyBkZWVwIGxlYXJuaW5nLCB3aGVyZSBkbyBzdHVkZW50cyB0eXBpY2FsbHkgc3RydWdnbGUgbW9zdCB3aGVuIHRyYW5zaXRpb25pbmcgZnJvbSBkZW5zZSB0byBjb252b2x1dGlvbmFsIG5ldHdvcmtzLCBhbmQgaG93IGNvdWxkIHdlIGJldHRlciBzdXBwb3J0IHRoYXQgc2hpZnQgdXNpbmcgdmlzdWFsIG9yIGludGVyYWN0aXZlIHJlc291cmNlcz99DQpcZW5ke2VudW1lcmF0ZX0NCmBgYA0KDQoNCg0KSmVzc2ljYSBNY1BoYXVsDQo3MzMzIOKAkyBRVFcgTW9kdWxlIDEyDQpQcmVzZXNzaW9uIFdlZWsgMTINCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18NClRocmVlIEdlbmVyYWwgVGFrZWF3YXlzDQoxLiBUcmFuc2ZlciBMZWFybmluZyBpcyBhIEdhbWUtQ2hhbmdlciBmb3IgUmVzb3VyY2UtTGltaXRlZCBUcmFpbmluZw0KQnkgZnJlZXppbmcgdGhlIGNvbnZvbHV0aW9uYWwgYmFzZSBvZiBwcmV0cmFpbmVkIG1vZGVscyAobGlrZSBWR0cxNikgYW5kIHRyYWluaW5nIG9ubHkgdGhlIGNsYXNzaWZpZXIgaGVhZCwgd2UgdW5sb2NrIHRoZSBwb3dlciBvZiBoaWdobHkgY29tcGxleCBtb2RlbHMgd2l0aG91dCBuZWVkaW5nIG1hc3NpdmUgY29tcHV0ZS4gVGhpcyBhcHByb2FjaCBzaWduaWZpY2FudGx5IGFjY2VsZXJhdGVzIHRyYWluaW5nIHdoaWxlIG1haW50YWluaW5nIHN0cm9uZyBwZXJmb3JtYW5jZSwgZXZlbiB3aXRoIGxpbWl0ZWQgZGF0YS4NCjIuIENOTnMgUmVseSBIZWF2aWx5IG9uIFByb3BlciBEYXRhIEhhbmRsaW5nIGFuZCBBcmNoaXRlY3R1cmFsIEJhbGFuY2UNClBlcmZvcm1hbmNlIGlzIHN0cm9uZ2x5IGluZmx1ZW5jZWQgYnkgc2VlbWluZ2x5IHNpbXBsZSBwcmVwcm9jZXNzaW5nIHN0ZXBzIChlLmcuLCBub3JtYWxpemF0aW9uIGJ5IGRpdmlkaW5nIGJ5IDI1NSwgcmVzaXppbmcgaW1hZ2VzLCBwcm9wZXIgbGFiZWwgZW5jb2RpbmcpLiBBZGRpdGlvbmFsbHksIHRoZSBkZXB0aCBvZiBjb252b2x1dGlvbiBhbmQgcG9vbGluZyBsYXllcnMgbXVzdCBiZSBiYWxhbmNlZCB3aXRoIG1lbW9yeSBjb25zdHJhaW50cyBhbmQgdGFzayBjb21wbGV4aXR5Lg0KMy4gVGhlIFNoaWZ0IGZyb20gRGVuc2UgdG8gQ29udm9sdXRpb25hbCBOZXR3b3JrcyBSZWZsZWN0cyBhIENoYW5nZSBpbiBIb3cgTW9kZWxzICJTZWUiIERhdGENCkRlbnNlIG5ldHdvcmtzIHRyZWF0IGlucHV0IGFzIGZsYXQgdmVjdG9yczsgQ05OcyB0cmVhdCBpdCBzcGF0aWFsbHkuIFRoaXMgaXMgY3J1Y2lhbCBmb3IgaW1hZ2VzLCB3aGVyZSBzdHJ1Y3R1cmUgYW5kIGxvY2FsaXR5IG1hdHRlci4gQ05OcyBsZWFybiBoaWVyYXJjaGljYWwgZmVhdHVyZXPigJRmcm9tIGVkZ2VzIGluIGVhcmx5IGxheWVycyB0byBjb21wbGV4IHNoYXBlcyBpbiBkZWVwZXIgb25lc+KAlG1pbWlja2luZyBob3cgdmlzdWFsIGNvcnRleCBuZXVyb25zIG9wZXJhdGUuDQpfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fDQozIFF1ZXN0aW9zbg0KMS4gR2l2ZW4gdGhlIGJlbmVmaXRzIG9mIHRyYW5zZmVyIGxlYXJuaW5nLCBob3cgZG8gd2UgYXNzZXNzIHdoZW4gaXQncyBtb3JlIGFwcHJvcHJpYXRlIHRvIGZpbmUtdHVuZSB2ZXJzdXMgZnJlZXplIGxheWVycywgZXNwZWNpYWxseSB3aGVuIHdvcmtpbmcgd2l0aCBzY2llbnRpZmljIG9yIG5pY2hlIGRhdGFzZXRzIGxpa2UgbWVkaWNhbCBzY2FucyBvciBzYXRlbGxpdGUgaW1hZ2VyeT8NCjIuIEluIHJlYWwtd29ybGQgZGVwbG95bWVudCwgaG93IGRvIHdlIG1pdGlnYXRlIHRoZSByaXNrcyBvZiBDTk4gbWlzY2xhc3NpZmljYXRpb24gKGUuZy4sIGZyb2cgdnMuIGRvZykgaW4gaGlnaC1zdGFrZXMgYXBwbGljYXRpb25zIHN1Y2ggYXMgYXV0b25vbW91cyB2ZWhpY2xlcywgbWlsaXRhcnkgc3VydmVpbGxhbmNlLCBvciBoZWFsdGhjYXJlIGRpYWdub3N0aWNzPw0KMy4gRnJvbSBhIHBlZGFnb2dpY2FsIG9yIHJlc2VhcmNoIHN0YW5kcG9pbnQsIHdoZXJlIGRvIHlvdSBzZWUgdGhlIGJpZ2dlc3QgY29uY2VwdHVhbCBnYXBzIGluIHN0dWRlbnQgdW5kZXJzdGFuZGluZyB3aGVuIHRyYW5zaXRpb25pbmcgZnJvbSBkZW5zZSB0byBjb252b2x1dGlvbmFsIG5ldXJhbCBuZXR3b3Jrc+KAlGFuZCBob3cgY2FuIHdlIGJldHRlciBicmlkZ2UgdGhhdCBsZWFwLCBwZXJoYXBzIHdpdGggdmlzdWFsIHRvb2xzIG9yIGhhbmRzLW9uIGRlbW9zPw0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXw0KDQo=