Study Guide: Introduction to Neural Networks
1. Understanding Neural Networks
Neural networks are inspired by the structure of the human brain. The
fundamental unit is the neuron, which receives inputs,
processes them, and produces an output. These artificial neurons mimic
biological neurons in function.
Biological Analogy:
- Neuron (Biological) → Perceptron
(Artificial)
- Dendrites (Receive input signals) → Input
layer (Features)
- Axon (Transmits signals) → Connections
(Weights)
- Synapses (Signal processing through
neurotransmitters) → Activation Function (Transforms
input to output)
- Firing of neuron when signal is strong enough →
Threshold function (Determines if a neuron
activates)
Mathematical Representation:
A perceptron, the simplest form of a neural network,
follows this equation:
\[
y = f\left( \sum w_i x_i + b \right)
\]
Where: - \(x_i\) = Inputs (features)
- \(w_i\) = Weights (importance of each
input) - \(b\) = Bias (shifts
activation threshold) - \(f(\cdot)\) =
Activation function (e.g., step function, sigmoid) - \(y\) = Output (prediction or
classification)
Step Function Activation:
\[
f(x) =
\begin{cases}
1, & \text{if } x \geq 0 \\
0, & \text{otherwise}
\end{cases}
\]
This mimics how a biological neuron fires if the signal exceeds a
threshold.
2. Python Implementation: Basic Perceptron
Let’s implement a simple perceptron in Python.
import numpy as np
class Perceptron:
def __init__(self, input_size, learning_rate=0.1, epochs=10):
self.weights = np.zeros(input_size + 1) # +1 for bias
self.learning_rate = learning_rate
self.epochs = epochs
def activation(self, x):
return 1 if x >= 0 else 0 # Step function
def predict(self, x):
x = np.insert(x, 0, 1) # Adding bias term
return self.activation(np.dot(self.weights, x))
def train(self, X, y):
for _ in range(self.epochs):
for xi, target in zip(X, y):
xi = np.insert(xi, 0, 1) # Add bias term
prediction = self.activation(np.dot(self.weights, xi))
self.weights += self.learning_rate * (target - prediction) * xi
# Example data (AND Gate)
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 0, 0, 1]) # AND function
# Train Perceptron
perceptron = Perceptron(input_size=2)
perceptron.train(X, y)
# Test Perceptron
for sample in X:
print(f"Input: {sample} -> Output: {perceptron.predict(sample)}")
Explanation: - The perceptron receives two inputs
and a bias. - It applies a weighted sum and passes it through a step
function. - The weights are updated using a simple learning rule.
3. Why Use a Sigmoid Instead of a Step
Function?
The step function is too harsh—it jumps from 0 to 1
immediately. Instead, we use a sigmoid function, which
smoothly transitions between 0 and 1.
Sigmoid Function:
\[
\sigma(x) = \frac{1}{1 + e^{-x}}
\]
Python Code for Sigmoid
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.linspace(-10, 10, 100)
y = sigmoid(x)
plt.plot(x, y)
plt.xlabel("Input")
plt.ylabel("Output")
plt.title("Sigmoid Activation Function")
plt.grid()
plt.show()
Why use sigmoid? - It allows for gradual
activation instead of a sharp jump. - It outputs probabilities
(values between 0 and 1), making it useful for
classification. - It allows gradient-based
optimization (important for training deep networks).
Analogy: Step Function vs. Sigmoid
- Step Function: Like a light switch (either ON or
OFF).
- Sigmoid Function: Like a dimmer switch (smoothly
increasing brightness).
4. Multilayer Perceptrons (MLPs) and Hidden
Layers
Now that we understand how a single-layer perceptron
works, we introduce the multilayer perceptron (MLP),
which consists of multiple layers of neurons.
Why Do We Need Hidden Layers?
- A single-layer perceptron can only model
linearly separable problems (e.g., AND, OR gates).
- Many real-world problems are non-linear (e.g.,
recognizing handwritten digits, predicting stock prices).
- Adding hidden layers allows the network to
learn complex patterns and hierarchical
features.
MLP Architecture
- Input Layer: Takes the raw data as input.
- Hidden Layers: Intermediate layers that transform
the data using weights and activation functions.
- Output Layer: Produces the final prediction.
Mathematical Representation
Each layer applies the function:
\[
h = f(WX + b)
\]
Where: - \(W\) = Weight matrix
(learned parameters) - \(X\) = Input
matrix (features from the previous layer) - \(b\) = Bias term - \(f(\cdot)\) = Activation function (e.g.,
ReLU, sigmoid,
tanh)
Each hidden neuron applies the transformation:
\[
h_i = \sigma\left(\sum w_{ij} x_j + b_i\right)
\]
The final layer outputs:
\[
y = f(W_{out} \cdot h + b_{out})
\]
5. Activation Functions
Why Do We Need Activation Functions?
- Without an activation function, each layer would just be a
linear transformation, making the neural network equivalent to
logistic regression.
- Non-linearity allows the model to learn complex
patterns.
Common Activation Functions
Sigmoid |
\(\sigma(x) = \frac{1}{1 +
e^{-x}}\) |
Binary classification |
Tanh |
\(\tanh(x) = \frac{e^x - e^{-x}}{e^x +
e^{-x}}\) |
Zero-centered sigmoid |
ReLU |
\(f(x) = \max(0, x)\) |
Most used in deep learning |
Leaky ReLU |
\(f(x) = \max(0.01x, x)\) |
Fixes dying neuron problem |
Python Code to Visualize Activation Functions
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 100)
def sigmoid(x): return 1 / (1 + np.exp(-x))
def tanh(x): return np.tanh(x)
def relu(x): return np.maximum(0, x)
def leaky_relu(x): return np.where(x > 0, x, 0.01 * x)
plt.figure(figsize=(10, 6))
plt.plot(x, sigmoid(x), label="Sigmoid")
plt.plot(x, tanh(x), label="Tanh")
plt.plot(x, relu(x), label="ReLU")
plt.plot(x, leaky_relu(x), label="Leaky ReLU")
plt.legend()
plt.title("Activation Functions")
plt.grid()
plt.show()
6. Backpropagation and Learning
How Does a Neural Network Learn?
The key to training a neural network is
backpropagation, which adjusts the weights using
gradient descent.
Steps in Backpropagation
- Forward Pass: Compute the output given the
input.
- Compute Loss: Measure the error (difference between
predicted and actual values).
- Backward Pass:
- Compute the gradient of the loss with respect to
weights using the chain rule.
- Update the weights using gradient descent:
\[
w \leftarrow w - \alpha \frac{\partial L}{\partial w}
\]
Where: - \(w\) = weight - \(\alpha\) = learning rate - \(L\) = loss function
Loss Functions
The loss function tells us how far off our
predictions are: - Mean Squared Error (MSE) for
regression:
\[
L = \frac{1}{N} \sum (y_{true} - y_{pred})^2
\]
- Binary Cross-Entropy for classification:
\[
L = -\frac{1}{N} \sum \left[y \log(\hat{y}) + (1 - y) \log(1 -
\hat{y})\right]
\]
7. Coding a Neural Network from Scratch
Python Implementation of a Simple MLP
import numpy as np
# Define activation functions
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return x * (1 - x)
# Initialize dataset (XOR problem)
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]])
# Initialize weights randomly
np.random.seed(1)
weights_input_hidden = np.random.uniform(-1, 1, (2, 2))
weights_hidden_output = np.random.uniform(-1, 1, (2, 1))
learning_rate = 0.5
# Train for 10000 epochs
for epoch in range(10000):
# Forward pass
hidden_input = np.dot(X, weights_input_hidden)
hidden_output = sigmoid(hidden_input)
final_input = np.dot(hidden_output, weights_hidden_output)
final_output = sigmoid(final_input)
# Compute error
error = y - final_output
# Backpropagation
d_output = error * sigmoid_derivative(final_output)
d_hidden = d_output.dot(weights_hidden_output.T) * sigmoid_derivative(hidden_output)
# Update weights
weights_hidden_output += hidden_output.T.dot(d_output) * learning_rate
weights_input_hidden += X.T.dot(d_hidden) * learning_rate
# Test predictions
for i in range(4):
print(f"Input: {X[i]} -> Prediction: {final_output[i]}")
Explanation
- Initialize random weights.
- Use the sigmoid function for activation.
- Train using backpropagation by updating
weights.
- Test predictions on XOR problem.
Analogy: How Backpropagation Works
Imagine you’re learning to shoot a basketball: 1.
You take a shot (forward pass). 2. You
observe if you made it or missed (compute
loss). 3. You adjust your next shot based on the
mistake (backpropagation). 4. Over time, you
improve accuracy (gradient descent updates
weights).
8. Optimizers and Learning Rates
Now that we understand how backpropagation updates
weights using gradient descent, let’s explore different
optimization methods and their impact on training.
Why Do We Need Different Optimizers?
Gradient descent can be slow and may get
stuck in local minima. Different optimizers adjust
learning to improve convergence.
Types of Gradient Descent
Batch Gradient Descent |
Uses all data at once |
Stable updates |
High memory usage |
Stochastic Gradient Descent (SGD) |
Uses one data point at a time |
Fast updates |
High variance |
Mini-Batch Gradient Descent |
Uses small batches of data |
Balance of speed and stability |
Requires tuning batch size |
Popular Optimizers
SGD |
\(w \leftarrow w - \alpha \nabla
L\) |
Updates weights per sample |
Momentum |
\(v_t = \beta v_{t-1} + \alpha \nabla
L\), \(w \leftarrow w -
v_t\) |
Reduces oscillations |
Adam |
Uses moving averages of gradients |
Adaptive learning rates |
RMSprop |
Normalizes learning rates |
Works well for deep learning |
Python Code to Compare Optimizers
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.optimizers import SGD, Adam, RMSprop
# Sample function to optimize (quadratic loss)
def loss_function(x):
return x**2 + 2*x + 1
x_vals = np.linspace(-5, 3, 100)
y_vals = loss_function(x_vals)
# Plot function
plt.plot(x_vals, y_vals, label="Loss Function")
plt.xlabel("Parameter Value")
plt.ylabel("Loss")
plt.title("Gradient Descent Optimizers")
plt.legend()
plt.show()
9. Learning Rate Selection
The learning rate (α) controls how much weights
update each step.
Effects of Learning Rate
- Too high → May overshoot the optimal point.
- Too low → Converges too slowly.
Adaptive Learning Rate Strategies
Decay |
Reduce learning rate over time |
Adaptive Optimizers (Adam, RMSprop) |
Adjust rates dynamically |
Python Example: Learning Rate Comparison
import tensorflow as tf
# Define a simple model
model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=[1])])
# Compile with different optimizers
optimizers = {
"SGD": SGD(learning_rate=0.1),
"Adam": Adam(learning_rate=0.01),
"RMSprop": RMSprop(learning_rate=0.01)
}
# Compare training speed by fitting dummy data
for name, opt in optimizers.items():
model.compile(loss="mse", optimizer=opt)
model.fit(np.array([1, 2, 3]), np.array([2, 4, 6]), epochs=10, verbose=0)
print(f"{name} optimizer trained model.")
10. Overfitting and Regularization
What is Overfitting?
- Overfitting happens when a model learns
noise instead of patterns.
- The model performs well on training data but poorly on new
data.
Regularization Techniques
L1 Regularization (Lasso) |
Shrinks less important weights to 0 |
L2 Regularization (Ridge) |
Penalizes large weights |
Dropout |
Randomly deactivates neurons during training |
Python Example: L2 Regularization
from tensorflow.keras.regularizers import l2
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
# Define a model with L2 regularization
model = Sequential([
Dense(64, activation="relu", kernel_regularizer=l2(0.01), input_shape=(10,)),
Dense(1, activation="sigmoid")
])
model.compile(optimizer="adam", loss="binary_crossentropy")
Analogy: Learning Rate and Optimizers
Imagine learning how to ride a bike: - Too
slow (low learning rate) → You wobble but never go far. -
Too fast (high learning rate) → You might crash. -
Momentum optimizer → Like using training wheels to
stabilize. - Adam optimizer → Like adjusting speed
based on terrain.
## 11. Model Architectures and Layers Neural
networks are composed of layers of neurons, each
performing mathematical transformations. Understanding different
architectures helps design models suited for various tasks. |
12. Types of Neural Networks
1. Feedforward Neural Networks (FNN)
- Structure: Data moves in one
direction, from input to output.
- Use Case: Basic classification and regression.
- Example:
- Handwritten digit recognition (MNIST dataset).
- Predicting housing prices.
2. Convolutional Neural Networks (CNN)
- Structure: Uses convolution layers
to detect spatial patterns.
- Use Case: Image processing, facial recognition,
self-driving cars.
- Example:
- Detecting tumors in medical images.
- Recognizing objects in photos.
3. Recurrent Neural Networks (RNN)
- Structure: Maintains memory using
previous time steps.
- Use Case: Sequences like text, speech, and stock
prices.
- Example:
- Predicting the next word in a sentence.
- Speech-to-text conversion.
13. Layers in a Neural Network
2. Hidden Layers
- Perform feature extraction using weights and activation
functions.
- The more layers, the deeper the network (Deep
Learning).
- Example: Detects edges in images, then shapes, then objects.
3. Output Layer
- Converts final computations into a prediction (class label,
probability).
- Example: “This is a cat” (Classification) or “Stock price will rise”
(Regression).
14. Activation Functions
Activation functions control neuron output and
introduce non-linearity, making networks more
powerful.
Sigmoid |
\(f(x) = \frac{1}{1 +
e^{-x}}\) |
Binary classification |
ReLU |
\(f(x) = \max(0, x)\) |
Most deep networks |
Tanh |
\(f(x) = \frac{e^x - e^{-x}}{e^x +
e^{-x}}\) |
Regression |
Softmax |
\(f(x_i) = \frac{e^{x_i}}{\sum
e^{x_j}}\) |
Multi-class classification |
15. Neural Network Architecture in Code
Simple Feedforward Neural Network
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
# Define model architecture
model = Sequential([
Dense(32, input_shape=(10,), activation='relu'), # Hidden layer 1
Dense(16, activation='relu'), # Hidden layer 2
Dense(1, activation='sigmoid') # Output layer for binary classification
])
# Compile model
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# View model summary
model.summary()
CNN for Image Processing
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten
cnn = Sequential([
Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)), # Convolutional layer
MaxPooling2D(pool_size=(2,2)), # Pooling to reduce dimensions
Flatten(), # Flattening to prepare for Dense layers
Dense(64, activation='relu'), # Fully connected layer
Dense(10, activation='softmax') # Output for 10 classes
])
cnn.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
cnn.summary()
16. Understanding Neural Network Depth
What is Depth in a Neural Network?
- Shallow Network: 1 hidden layer.
- Deep Network: Multiple hidden layers (Deep
Learning).
- More depth increases model capacity but can lead to
overfitting.
Trade-offs
Can learn complex features |
Simpler, faster to train |
Higher accuracy (if tuned well) |
Less prone to overfitting |
Requires more data |
Works well for small datasets |
Analogy: Neural Networks as a Bakery
Imagine a bakery: - Input Layer:
Ingredients (flour, sugar, eggs). - Hidden Layers:
Mixing, baking, frosting. - Output Layer: Finished
cake. - Activation Functions: Controls how the cake
turns out (bake time, mixing speed).
Without hidden layers, it’s just raw ingredients. More hidden layers
refine the process to produce the best cake
possible!
17. Training a Neural Network
Training a neural network means adjusting the model’s
weights to minimize the difference between predictions and
actual values. This process involves: 1. Forward
propagation – Data moves from input to output. 2. Loss
calculation – Measures how far predictions are from true
labels. 3. Backward propagation – Updates weights to
improve predictions. 4. Optimization – Adjusts model
parameters to minimize error.
18. Loss Functions
A loss function quantifies how well the network is
performing. The goal is to minimize this value.
Mean Squared Error (MSE) |
\(L = \frac{1}{n} \sum (y_i -
\hat{y}_i)^2\) |
Regression |
Mean Absolute Error (MAE) |
\(L = \frac{1}{n} \sum |y_i -
\hat{y}_i|\) |
Regression |
Binary Cross-Entropy |
\(L = -\frac{1}{n} \sum [y_i
\log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i)]\) |
Binary classification |
Categorical Cross-Entropy |
\(L = -\sum y_i
\log(\hat{y}_i)\) |
Multi-class classification |
19. Backpropagation and Gradient Descent
Backpropagation is the process of adjusting weights
based on the loss function. It uses gradient descent to
find optimal weight values.
Gradient Descent Equation
Weights are updated as: \[
w = w - \alpha \frac{\partial L}{\partial w}
\] where: - \(w\) = weight -
\(\alpha\) = learning rate (step size)
- \(\frac{\partial L}{\partial w}\) =
gradient (rate of change of loss)
Variants of Gradient Descent
Batch Gradient Descent |
Uses all data at once |
More stable updates |
Slow for large datasets |
Stochastic Gradient Descent (SGD) |
Updates per sample |
Fast updates |
High variance in updates |
Mini-Batch Gradient Descent |
Uses small batches |
Balance of speed & stability |
Requires tuning batch size |
20. Optimizers
Optimizers improve gradient descent by adjusting
learning rates and weight updates dynamically.
SGD |
Simple but noisy updates |
Momentum |
Uses past gradients to smooth updates |
Adam (Adaptive Moment Estimation) |
Adjusts learning rate dynamically (most used) |
RMSprop |
Normalizes gradient magnitude for stable updates |
Code Implementation of Optimizers
from tensorflow.keras.optimizers import SGD, Adam
# Define model with different optimizers
model.compile(optimizer=SGD(learning_rate=0.01), loss='mse') # Basic SGD
model.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy') # Adam for classification
21. Epochs and Batch Size
- Epoch: One complete pass of the dataset through the
network.
- Batch size: Number of samples processed before
updating weights.
Finding the Right Values
- Too few epochs → Underfitting (model hasn’t learned
enough).
- Too many epochs → Overfitting (model memorizes
data).
- Small batch size → More updates, better
generalization.
- Large batch size → Faster training, but may
overfit.
Example Calculation: - Dataset size
= 100,000 - Batch size = 100 - Epochs
= 4
\[
\text{Batches per epoch} = \frac{100,000}{100} = 1,000
\]
\[
\text{Total updates} = 1,000 \times 4 = 4,000
\]
Analogy: Training a Neural Network Like Learning to Shoot
Basketball
Imagine you’re learning how to shoot a basketball:
1. Loss function → Measures how often you miss. 2.
Backpropagation → Adjusts your technique after each
shot. 3. Gradient descent → Helps improve shot accuracy
over time. 4. Epochs → The number of practice sessions.
5. Batch size → Whether you shoot one ball at a time or
multiple.
Without enough practice (epochs), you won’t improve. But if
you keep practicing after you’re perfect, you just waste energy
(overfitting).
23. What is Overfitting?
Overfitting happens when a neural network memorizes
training data instead of learning patterns. The model
performs well on training data but poorly on new (test)
data.
Signs of Overfitting
- High training accuracy, but low test accuracy.
- Loss decreases on training but remains high on test
data.
- Model predicts training examples correctly but fails on
unseen data.
24. Bias-Variance Tradeoff
Overfitting is part of the bias-variance tradeoff in
machine learning.
High Bias (Underfitting) |
Model is too simple, fails to learn patterns. |
A student who only memorizes 2+2=4 but can’t solve 5+3. |
High Variance (Overfitting) |
Model is too complex, memorizes data instead of generalizing. |
A student who memorizes every possible question but struggles with
new ones. |
The goal is to balance bias and variance.
25. Regularization Techniques
Regularization prevents overfitting by simplifying
the model and reducing unnecessary complexity.
1. L1 and L2 Regularization
- L1 Regularization (Lasso Regression): Adds a
penalty for large weights, forcing some weights to
become zero.
- L2 Regularization (Ridge Regression): Adds a
penalty for large weights, reducing them but keeping
all.
\[
L1: \quad Loss = \sum (y_i - \hat{y}_i)^2 + \lambda \sum |w_i|
\]
\[
L2: \quad Loss = \sum (y_i - \hat{y}_i)^2 + \lambda \sum w_i^2
\]
Python Implementation:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.regularizers import l1, l2
model = Sequential([
Dense(64, activation='relu', kernel_regularizer=l2(0.01)), # L2 Regularization
Dense(32, activation='relu', kernel_regularizer=l1(0.01)), # L1 Regularization
Dense(1, activation='sigmoid')
])
2. Dropout Regularization
Dropout randomly turns off neurons during training
to prevent over-reliance on specific connections.
0% (No Dropout) |
May lead to overfitting |
20%-50% |
Helps prevent overfitting |
80%-90% |
Model may underperform (too much dropout) |
Python Implementation:
from tensorflow.keras.layers import Dropout
model = Sequential([
Dense(64, activation='relu'),
Dropout(0.3), # 30% of neurons are dropped
Dense(32, activation='relu'),
Dropout(0.3),
Dense(1, activation='sigmoid')
])
3. Early Stopping
Stops training when performance stops improving to
prevent overfitting.
Python Implementation:
from tensorflow.keras.callbacks import EarlyStopping
early_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=100, callbacks=[early_stop])
4. Data Augmentation
Instead of modifying the model, we modify the data
to prevent overfitting.
For images, augmentation includes: -
Flipping - Rotating - Adding
noise
Python Implementation:
from tensorflow.keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(rotation_range=20, horizontal_flip=True)
train_generator = datagen.flow(X_train, y_train, batch_size=32)
26. Evaluating Overfitting
Use learning curves to diagnose overfitting.
1. Training vs. Validation Loss
- Overfitting: Training loss decreases, validation
loss increases.
- Good Fit: Both losses decrease and stabilize.
- Underfitting: Both losses remain high.
Python Implementation for Visualization:
import matplotlib.pyplot as plt
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.legend()
plt.show()
Analogy: Overfitting is Like Memorizing Exam
Answers
- Imagine you’re studying for a test.
- Overfitting: You memorize exact questions and
answers. But if the teacher changes the question slightly, you get
confused.
- Good Learning: You understand concepts and can
apply them to different questions.
- Underfitting: You don’t study enough and struggle
even with simple questions.
Key Takeaways
✅ Regularization reduces overfitting by simplifying
the model.
✅ Dropout prevents reliance on specific neurons.
✅ Early stopping prevents unnecessary training.
✅ Data augmentation increases data variability.
27. What are
Activation Functions?
Activation functions help a neural network decide
whether a neuron should “fire” or stay inactive. They introduce
non-linearity, allowing neural networks to learn
complex patterns.
Why Do We Need Activation Functions?
If we don’t use activation functions, neural networks behave like
linear regressions, making them unable to model complex
data.
28. Types of Activation Functions
Each activation function has different properties,
and choosing the right one impacts a model’s performance.
Step Function |
\(f(x) = 1\) if \(x > 0\), else \(0\) |
Rarely used (too simple) |
Sigmoid |
\(f(x) = \frac{1}{1 +
e^{-x}}\) |
Output probabilities (last layer) |
Tanh |
\(f(x) = \frac{e^x - e^{-x}}{e^x +
e^{-x}}\) |
Centered around zero |
ReLU |
\(f(x) = \max(0, x)\) |
Default for hidden layers |
Leaky ReLU |
\(f(x) = x\) if \(x > 0\), else \(0.01x\) |
Prevents dying neurons |
Softmax |
\(f(x_i) = \frac{e^{x_i}}{\sum
e^{x_j}}\) |
Multiclass classification |
29. Step Function (Binary Thresholding)
A neuron fires only if input is above a
threshold.
\[
f(x) =
\begin{cases}
1, & x > 0 \\
0, & x \leq 0
\end{cases}
\]
Problem: Step functions aren’t
differentiable, making learning difficult.
30. Sigmoid Function (S-Shaped Curve)
The sigmoid function squashes inputs between
0 and 1, making it useful for
probabilities.
\[
f(x) = \frac{1}{1 + e^{-x}}
\]
Pros:
✔️ Used in binary classification (last layer).
✔️ Outputs a probability between 0 and 1.
Cons:
❌ Vanishing Gradient Problem: Small gradients slow
learning in deep networks.
❌ Not Zero-Centered: Outputs always positive, making
optimization harder.
Python Example:
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.linspace(-10, 10, 100)
plt.plot(x, sigmoid(x))
plt.title("Sigmoid Function")
plt.show()
31. Tanh Function (Centered Sigmoid)
Like sigmoid, but squashes values between -1
and 1.
\[
f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}
\]
Pros:
✔️ Zero-Centered: Helps optimization.
✔️ Stronger gradients than sigmoid.
Cons:
❌ Still suffers from the vanishing gradient
problem.
32. ReLU (Rectified Linear Unit)
The most commonly used activation function
today.
\[
f(x) = \max(0, x)
\]
Pros:
✔️ Computationally efficient (fast to
compute).
✔️ Solves vanishing gradient problem (for positive
values).
Cons:
❌ Dying Neurons Problem: If many neurons output
0, they stop learning.
Python Example:
def relu(x):
return np.maximum(0, x)
plt.plot(x, relu(x))
plt.title("ReLU Activation Function")
plt.show()
33. Leaky ReLU (Fix for Dying Neurons)
Fixes ReLU’s problem by allowing a small slope for
negative values.
\[
f(x) =
\begin{cases}
x, & x > 0 \\
0.01x, & x \leq 0
\end{cases}
\]
✔️ Prevents dead neurons by giving them a small
gradient.
✔️ Works better than ReLU in some cases.
Python Example:
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, alpha * x)
plt.plot(x, leaky_relu(x))
plt.title("Leaky ReLU Activation Function")
plt.show()
34. Softmax (For Multiclass Classification)
Softmax converts scores into probabilities that sum to
1.
\[
f(x_i) = \frac{e^{x_i}}{\sum e^{x_j}}
\]
✔️ Used in the last layer for multiclass
problems.
✔️ Outputs probability distribution over multiple
classes.
35. Choosing the Right Activation Function
Binary Classification |
Sigmoid (last layer) |
Multiclass Classification |
Softmax (last layer) |
Hidden Layers (General) |
ReLU (default), Leaky ReLU (if ReLU fails) |
Recurrent Neural Networks (RNNs) |
Tanh or ReLU |
Analogy: Activation Functions Are Like Decision-Making in
Real Life
Think of activation functions like thresholds for
making decisions: - Step function: Like a light switch
(on/off). - Sigmoid: Like grading a student (pass/fail
probability). - ReLU: Like hiring an employee (consider
only positive qualifications). - Leaky
ReLU: Like giving partial credit (even small efforts count). -
Softmax: Like picking a restaurant (probability of
choosing each).
Key Takeaways
✅ Activation functions allow networks to learn complex
patterns.
✅ ReLU is the default choice, but Leaky ReLU
can prevent dying neurons.
✅ Sigmoid and Softmax are used for output
layers.
✅ Choosing the right function impacts speed and
accuracy.
36. What is Backpropagation?
Backpropagation is the learning algorithm that
allows neural networks to adjust their weights and
become better at making predictions.
Why Do We Need Backpropagation?
- When training a neural network, we start with random
weights.
- The network makes predictions, but initially, they’re not
accurate.
- We need a way to measure errors and adjust the
weights—this is what backpropagation
does.
37. The Steps of Backpropagation
Backpropagation is an optimization technique that
minimizes the error between predicted and actual values.
- Forward Propagation:
- Inputs flow through the network.
- Predictions are made using current weights.
- Calculate Loss:
- Compare predictions to actual values.
- Use a loss function to measure error.
- Backward Propagation:
- Compute how much each weight contributed to the error.
- Adjust the weights using Gradient Descent.
- Repeat Until Convergence:
- This process continues until the error is
minimized.
38. The Math Behind Backpropagation
Backpropagation uses calculus and chain rule
differentiation to update weights.
Step 1: Compute the Error
We calculate the loss using a function like
Mean Squared Error (MSE):
\[
L = \frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2
\]
where: - \(y_i\) is the actual
value. - \(\hat{y}_i\) is the predicted
value. - \(N\) is the number of
examples.
Step 2: Compute the Gradient (Rate of Change)
To update weights, we take the derivative of the loss
function with respect to each weight:
\[
\frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}}
\times \frac{\partial \hat{y}}{\partial w}
\]
This tells us how much each weight contributes to the
error.
Step 3: Update Weights
Weights are updated using Gradient Descent:
\[
w = w - \alpha \frac{\partial L}{\partial w}
\]
where: - \(\alpha\) is the
learning rate. - \(\frac{\partial L}{\partial w}\) is the
gradient (amount of change needed).
39. Understanding Gradient Descent
Gradient Descent helps minimize the loss function by
adjusting weights step by step.
Types of Gradient Descent
Batch Gradient Descent |
Uses all data at once (slow for large
datasets). |
Stochastic Gradient Descent (SGD) |
Uses one sample at a time (faster, but
noisier). |
Mini-Batch Gradient Descent |
Uses small groups of samples (balanced
approach). |
Python Example: Gradient Descent
import numpy as np
# Simple Gradient Descent
def gradient_descent(w, learning_rate, gradient):
return w - learning_rate * gradient
# Example
w = 0.5 # Initial weight
learning_rate = 0.1
gradient = 2.0 # Example gradient
new_w = gradient_descent(w, learning_rate, gradient)
print("Updated Weight:", new_w)
40. The Chain Rule in Backpropagation
Since neural networks have many layers, we use the
Chain Rule to compute gradients.
For an activation function \(f(x)\) and a loss function
\(L\):
\[
\frac{\partial L}{\partial w} = \frac{\partial L}{\partial f} \times
\frac{\partial f}{\partial x} \times \frac{\partial x}{\partial w}
\]
Each layer passes the error backward, adjusting
weights layer by layer.
41. Example: Backpropagation in Python
Let’s implement a simple backpropagation step in
Python.
import numpy as np
# Example inputs, weights, and expected output
x = np.array([0.5, 0.8])
w = np.array([0.1, -0.2])
y_true = 1.0
learning_rate = 0.1
# Forward pass
z = np.dot(x, w) # Linear combination
y_pred = 1 / (1 + np.exp(-z)) # Sigmoid activation
# Compute loss
error = y_true - y_pred
# Compute gradient
gradient = error * y_pred * (1 - y_pred) * x
# Update weights
w = w + learning_rate * gradient
print("Updated Weights:", w)
42. Why Backpropagation is Important
✅ It allows neural networks to learn from
mistakes.
✅ It optimizes weights efficiently using
calculus.
✅ It’s the backbone of deep learning models.
Analogy: Backpropagation is Like Learning from
Mistakes
Imagine you’re learning to throw darts.
- You throw a dart and see how far you missed the
target. - You adjust your aim based on the
mistake. - Over time, you get better and hit the
bullseye.
Backpropagation does the same thing—it adjusts
weights step by step to reduce error.
Key Takeaways
✅ Backpropagation updates neural network weights based on
error.
✅ Gradient Descent helps minimize loss using small weight
changes.
✅ The Chain Rule allows error to propagate through
layers.
✅ Without backpropagation, deep learning wouldn’t
work!
43. What is an Optimizer?
An optimizer is an algorithm that updates the
weights of a neural network to minimize the
loss function.
Why Do We Need Optimizers?
- Optimizers adjust the weights to reduce error.
- They help speed up convergence.
- They prevent overfitting or underfitting.
44. The Role of the Learning Rate (α)
The learning rate controls how much we update
weights at each step.
Too High (e.g., 1.0) |
Jumps over the minimum, never converges |
Too Low (e.g., 0.0001) |
Takes too long to reach the minimum |
Optimal (e.g., 0.01 - 0.1) |
Finds the minimum efficiently |
Graphical Representation
📉 A small learning rate moves slowly towards the minimum, while a
large learning rate may oscillate or diverge.
45. Types of Optimizers
There are different optimization algorithms that
improve weight updates.
1. Gradient Descent Variants
Batch Gradient Descent |
Uses all data at once (slow for big datasets). |
Stochastic Gradient Descent (SGD) |
Uses one sample at a time (faster, but noisy). |
Mini-Batch Gradient Descent |
Uses small groups of samples (balanced). |
2. Adaptive Optimizers
Momentum |
Uses past updates to move faster. |
RMSprop |
Adjusts learning rate dynamically for stability. |
Adam (Adaptive Moment Estimation) |
Combines Momentum + RMSprop (most popular). |
AdaGrad |
Adjusts learning rate for each weight separately. |
🚀 Adam is the most commonly used optimizer in deep
learning.
46. Math Behind Optimizers
Gradient Descent Weight Update Rule
Weights are updated as:
\[
w = w - \alpha \frac{\partial L}{\partial w}
\]
where: - \(w\) = weight, - \(\alpha\) = learning rate, - \(\frac{\partial L}{\partial w}\) = gradient
(rate of change of loss).
48. Tuning the Learning Rate
Finding the right learning rate is crucial.
Methods to Tune Learning Rate
- Manual Tuning: Try values like 0.1, 0.01,
0.001, 0.0001.
- Learning Rate Decay: Reduce \(\alpha\) over time.
- Cyclical Learning Rate (CLR): Alternate between
high and low values.
- Learning Rate Finder: Train with many rates, choose
the best.
Example: Learning Rate Decay
\[
\alpha_t = \frac{\alpha_0}{1 + \lambda t}
\] where: - \(\alpha_t\) =
learning rate at step \(t\), - \(\alpha_0\) = initial learning rate, - \(\lambda\) = decay factor.
49. Understanding Convergence
- Too high a learning rate → weights oscillate, never
converge.
- Too low a learning rate → takes forever to reach
the optimal point.
- Adaptive optimizers like Adam adjust
learning rates dynamically.
Visual Representation
🟢 Good learning rate → smooth descent
🔴 Too high → erratic jumps
🔵 Too low → slow convergence
50. Analogy: Learning to Ride a Bike
- If you pedal too fast (high learning rate), you may
lose control.
- If you pedal too slow (low learning rate), you
won’t move forward.
- Optimal pedaling speed (right learning rate) helps
you balance speed & control.
Key Takeaways
✅ Optimizers improve weight updates for faster
learning.
✅ Adam is the most commonly used optimizer.
✅ Learning rate tuning is critical for
convergence.
✅ Too high or too low a learning rate can cause
issues.
51. Overview: Putting It All Together
Now that we have learned about neural networks, activation functions,
optimizers, and training methods, it’s time to build a neural
network from scratch using Python and
TensorFlow/Keras.
We will: ✅ Define the network architecture.
✅ Choose activation functions and an optimizer.
✅ Train the network on real data.
✅ Evaluate performance.
52. Steps to Build a Neural Network
1️⃣ Load the Data
2️⃣ Preprocess the Data
3️⃣ Define the Model Architecture
4️⃣ Compile the Model (Choose Loss &
Optimizer)
5️⃣ Train the Model
6️⃣ Evaluate the Model Performance
7️⃣ Make Predictions
53. Example: Neural Network for Classification
We will build a binary classifier for a dataset.
Step 1: Import Libraries
import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
Step 2: Load and Preprocess the Data
# Load dataset (Example: Breast Cancer dataset from sklearn)
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X, y = data.data, data.target
# Split into train and test sets (80% train, 20% test)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Standardize features
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Step 3: Define the Model Architecture
# Create a Sequential Model
model = tf.keras.Sequential([
tf.keras.layers.Dense(16, activation='relu', input_shape=(X_train.shape[1],)), # Hidden Layer 1
tf.keras.layers.Dense(8, activation='relu'), # Hidden Layer 2
tf.keras.layers.Dense(1, activation='sigmoid') # Output Layer
])
📌 Key Points: - Input Layer: Takes
in X_train.shape[1]
features. - Hidden
Layers: Two layers with ReLU activation. -
Output Layer: Uses Sigmoid since it’s
a binary classification problem.
Step 4: Compile the Model
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
📌 Key Points: - Loss function:
binary_crossentropy
(used for classification). -
Optimizer: adam
(best general-purpose
optimizer). - Metrics: We track
accuracy.
Step 5: Train the Model
history = model.fit(X_train, y_train, epochs=50, batch_size=32, validation_data=(X_test, y_test))
📌 Key Parameters: - epochs=50
: The
model will see the full dataset 50 times. -
batch_size=32
: We process 32 samples at a
time. - validation_data=(X_test, y_test)
: Check
performance on unseen data.
⏳ Training takes a few seconds to minutes, depending on
hardware.
Step 7: Make Predictions
# Predict on new data
predictions = model.predict(X_test)
predicted_classes = (predictions > 0.5).astype(int) # Convert probabilities to 0 or 1
📌 Key Points: - Predictions are
probabilities (between 0 and 1). - We
threshold at 0.5
to convert to class
labels.
54. Understanding the Training Process
Loss Curve
A loss curve helps us understand convergence.
import matplotlib.pyplot as plt
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
📌 Interpreting the Curve: - Loss
decreasing ✅ → Model is learning. - Loss
increasing ❌ → Model is overfitting.
55. Fine-Tuning the Neural Network
If performance is not great, try: ✅ Adding more
layers (deep networks learn better).
✅ Increasing epochs (train longer).
✅ Tuning learning rate (too high → unstable, too low →
slow learning).
✅ Using dropout layers (prevent overfitting).
Example:
tf.keras.layers.Dropout(0.3) # 30% of neurons are randomly disabled per epoch
56. Analogy: Training a Neural Network = Teaching a
Student
Think of training a neural network like teaching a
student: - The student (model) learns from
practice (data). - The teacher
(optimizer) gives feedback to adjust learning.
- The student improves over time (epochs). -
Too much studying (overfitting) → Student memorizes
answers instead of understanding. - Too little studying
(underfitting) → Student guesses answers randomly.
57. Key Takeaways
✅ Neural networks are ensembles of
regressors.
✅ Each layer extracts deeper features.
✅ Activation functions allow non-linearity.
✅ Optimizers adjust weights for better learning.
✅ Hyperparameters (epochs, batch size, learning rate) must be
tuned.
✅ Neural networks excel at pattern recognition &
classification.
LS0tDQp0aXRsZTogIlN0dWR5IEd1aWRlOiBJbnRyb2R1Y3Rpb24gdG8gTmV1cmFsIE5ldHdvcmtzIC0gRFM3MzMzIFF1YW50aWZ5aW5nIHRoZSBXb3JsZCBNb2R1bGUgMTEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCiMgKipTdHVkeSBHdWlkZTogSW50cm9kdWN0aW9uIHRvIE5ldXJhbCBOZXR3b3JrcyoqDQoNCiMjICoqMS4gVW5kZXJzdGFuZGluZyBOZXVyYWwgTmV0d29ya3MqKg0KTmV1cmFsIG5ldHdvcmtzIGFyZSBpbnNwaXJlZCBieSB0aGUgc3RydWN0dXJlIG9mIHRoZSBodW1hbiBicmFpbi4gVGhlIGZ1bmRhbWVudGFsIHVuaXQgaXMgdGhlICoqbmV1cm9uKiosIHdoaWNoIHJlY2VpdmVzIGlucHV0cywgcHJvY2Vzc2VzIHRoZW0sIGFuZCBwcm9kdWNlcyBhbiBvdXRwdXQuIFRoZXNlIGFydGlmaWNpYWwgbmV1cm9ucyBtaW1pYyBiaW9sb2dpY2FsIG5ldXJvbnMgaW4gZnVuY3Rpb24uDQoNCiMjIyAqKkJpb2xvZ2ljYWwgQW5hbG9neToqKg0KLSAqKk5ldXJvbiAoQmlvbG9naWNhbCkqKiDihpIgKipQZXJjZXB0cm9uIChBcnRpZmljaWFsKSoqDQotICoqRGVuZHJpdGVzIChSZWNlaXZlIGlucHV0IHNpZ25hbHMpKiog4oaSICoqSW5wdXQgbGF5ZXIgKEZlYXR1cmVzKSoqDQotICoqQXhvbiAoVHJhbnNtaXRzIHNpZ25hbHMpKiog4oaSICoqQ29ubmVjdGlvbnMgKFdlaWdodHMpKioNCi0gKipTeW5hcHNlcyAoU2lnbmFsIHByb2Nlc3NpbmcgdGhyb3VnaCBuZXVyb3RyYW5zbWl0dGVycykqKiDihpIgKipBY3RpdmF0aW9uIEZ1bmN0aW9uIChUcmFuc2Zvcm1zIGlucHV0IHRvIG91dHB1dCkqKg0KLSAqKkZpcmluZyBvZiBuZXVyb24gd2hlbiBzaWduYWwgaXMgc3Ryb25nIGVub3VnaCoqIOKGkiAqKlRocmVzaG9sZCBmdW5jdGlvbiAoRGV0ZXJtaW5lcyBpZiBhIG5ldXJvbiBhY3RpdmF0ZXMpKioNCg0KIyMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uOioqDQpBICoqcGVyY2VwdHJvbioqLCB0aGUgc2ltcGxlc3QgZm9ybSBvZiBhIG5ldXJhbCBuZXR3b3JrLCBmb2xsb3dzIHRoaXMgZXF1YXRpb246DQoNClxbDQp5ID0gZlxsZWZ0KCBcc3VtIHdfaSB4X2kgKyBiIFxyaWdodCkNClxdDQoNCldoZXJlOg0KLSBcKCB4X2kgXCkgPSBJbnB1dHMgKGZlYXR1cmVzKQ0KLSBcKCB3X2kgXCkgPSBXZWlnaHRzIChpbXBvcnRhbmNlIG9mIGVhY2ggaW5wdXQpDQotIFwoIGIgXCkgPSBCaWFzIChzaGlmdHMgYWN0aXZhdGlvbiB0aHJlc2hvbGQpDQotIFwoIGYoXGNkb3QpIFwpID0gQWN0aXZhdGlvbiBmdW5jdGlvbiAoZS5nLiwgc3RlcCBmdW5jdGlvbiwgc2lnbW9pZCkNCi0gXCggeSBcKSA9IE91dHB1dCAocHJlZGljdGlvbiBvciBjbGFzc2lmaWNhdGlvbikNCg0KIyMjIyAqKlN0ZXAgRnVuY3Rpb24gQWN0aXZhdGlvbjoqKg0KXFsNCmYoeCkgPQ0KXGJlZ2lue2Nhc2VzfSANCjEsICYgXHRleHR7aWYgfSB4IFxnZXEgMCBcXA0KMCwgJiBcdGV4dHtvdGhlcndpc2V9DQpcZW5ke2Nhc2VzfQ0KXF0NCg0KVGhpcyBtaW1pY3MgaG93IGEgYmlvbG9naWNhbCBuZXVyb24gZmlyZXMgaWYgdGhlIHNpZ25hbCBleGNlZWRzIGEgdGhyZXNob2xkLg0KDQotLS0NCg0KIyMgKioyLiBQeXRob24gSW1wbGVtZW50YXRpb246IEJhc2ljIFBlcmNlcHRyb24qKg0KTGV0J3MgaW1wbGVtZW50IGEgc2ltcGxlIHBlcmNlcHRyb24gaW4gUHl0aG9uLg0KDQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KDQpjbGFzcyBQZXJjZXB0cm9uOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmLCBpbnB1dF9zaXplLCBsZWFybmluZ19yYXRlPTAuMSwgZXBvY2hzPTEwKToNCiAgICAgICAgc2VsZi53ZWlnaHRzID0gbnAuemVyb3MoaW5wdXRfc2l6ZSArIDEpICAjICsxIGZvciBiaWFzDQogICAgICAgIHNlbGYubGVhcm5pbmdfcmF0ZSA9IGxlYXJuaW5nX3JhdGUNCiAgICAgICAgc2VsZi5lcG9jaHMgPSBlcG9jaHMNCg0KICAgIGRlZiBhY3RpdmF0aW9uKHNlbGYsIHgpOg0KICAgICAgICByZXR1cm4gMSBpZiB4ID49IDAgZWxzZSAwICAjIFN0ZXAgZnVuY3Rpb24NCg0KICAgIGRlZiBwcmVkaWN0KHNlbGYsIHgpOg0KICAgICAgICB4ID0gbnAuaW5zZXJ0KHgsIDAsIDEpICAjIEFkZGluZyBiaWFzIHRlcm0NCiAgICAgICAgcmV0dXJuIHNlbGYuYWN0aXZhdGlvbihucC5kb3Qoc2VsZi53ZWlnaHRzLCB4KSkNCg0KICAgIGRlZiB0cmFpbihzZWxmLCBYLCB5KToNCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uoc2VsZi5lcG9jaHMpOg0KICAgICAgICAgICAgZm9yIHhpLCB0YXJnZXQgaW4gemlwKFgsIHkpOg0KICAgICAgICAgICAgICAgIHhpID0gbnAuaW5zZXJ0KHhpLCAwLCAxKSAgIyBBZGQgYmlhcyB0ZXJtDQogICAgICAgICAgICAgICAgcHJlZGljdGlvbiA9IHNlbGYuYWN0aXZhdGlvbihucC5kb3Qoc2VsZi53ZWlnaHRzLCB4aSkpDQogICAgICAgICAgICAgICAgc2VsZi53ZWlnaHRzICs9IHNlbGYubGVhcm5pbmdfcmF0ZSAqICh0YXJnZXQgLSBwcmVkaWN0aW9uKSAqIHhpDQoNCiMgRXhhbXBsZSBkYXRhIChBTkQgR2F0ZSkNClggPSBucC5hcnJheShbWzAsIDBdLCBbMCwgMV0sIFsxLCAwXSwgWzEsIDFdXSkNCnkgPSBucC5hcnJheShbMCwgMCwgMCwgMV0pICAjIEFORCBmdW5jdGlvbg0KDQojIFRyYWluIFBlcmNlcHRyb24NCnBlcmNlcHRyb24gPSBQZXJjZXB0cm9uKGlucHV0X3NpemU9MikNCnBlcmNlcHRyb24udHJhaW4oWCwgeSkNCg0KIyBUZXN0IFBlcmNlcHRyb24NCmZvciBzYW1wbGUgaW4gWDoNCiAgICBwcmludChmIklucHV0OiB7c2FtcGxlfSAtPiBPdXRwdXQ6IHtwZXJjZXB0cm9uLnByZWRpY3Qoc2FtcGxlKX0iKQ0KYGBgDQoNCioqRXhwbGFuYXRpb246KioNCi0gVGhlIHBlcmNlcHRyb24gcmVjZWl2ZXMgdHdvIGlucHV0cyBhbmQgYSBiaWFzLg0KLSBJdCBhcHBsaWVzIGEgd2VpZ2h0ZWQgc3VtIGFuZCBwYXNzZXMgaXQgdGhyb3VnaCBhIHN0ZXAgZnVuY3Rpb24uDQotIFRoZSB3ZWlnaHRzIGFyZSB1cGRhdGVkIHVzaW5nIGEgc2ltcGxlIGxlYXJuaW5nIHJ1bGUuDQoNCi0tLQ0KDQojIyAqKjMuIFdoeSBVc2UgYSBTaWdtb2lkIEluc3RlYWQgb2YgYSBTdGVwIEZ1bmN0aW9uPyoqDQpUaGUgKipzdGVwIGZ1bmN0aW9uKiogaXMgdG9vIGhhcnNo4oCUaXQganVtcHMgZnJvbSAwIHRvIDEgaW1tZWRpYXRlbHkuIEluc3RlYWQsIHdlIHVzZSBhICoqc2lnbW9pZCBmdW5jdGlvbioqLCB3aGljaCBzbW9vdGhseSB0cmFuc2l0aW9ucyBiZXR3ZWVuIDAgYW5kIDEuDQoNCiMjIyAqKlNpZ21vaWQgRnVuY3Rpb246KioNClxbDQpcc2lnbWEoeCkgPSBcZnJhY3sxfXsxICsgZV57LXh9fQ0KXF0NCg0KIyMjIyAqKlB5dGhvbiBDb2RlIGZvciBTaWdtb2lkKioNCmBgYHB5dGhvbg0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQpkZWYgc2lnbW9pZCh4KToNCiAgICByZXR1cm4gMSAvICgxICsgbnAuZXhwKC14KSkNCg0KeCA9IG5wLmxpbnNwYWNlKC0xMCwgMTAsIDEwMCkNCnkgPSBzaWdtb2lkKHgpDQoNCnBsdC5wbG90KHgsIHkpDQpwbHQueGxhYmVsKCJJbnB1dCIpDQpwbHQueWxhYmVsKCJPdXRwdXQiKQ0KcGx0LnRpdGxlKCJTaWdtb2lkIEFjdGl2YXRpb24gRnVuY3Rpb24iKQ0KcGx0LmdyaWQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCioqV2h5IHVzZSBzaWdtb2lkPyoqDQotIEl0IGFsbG93cyBmb3IgKipncmFkdWFsIGFjdGl2YXRpb24qKiBpbnN0ZWFkIG9mIGEgc2hhcnAganVtcC4NCi0gSXQgb3V0cHV0cyBwcm9iYWJpbGl0aWVzICh2YWx1ZXMgYmV0d2VlbiAwIGFuZCAxKSwgbWFraW5nIGl0IHVzZWZ1bCBmb3IgKipjbGFzc2lmaWNhdGlvbioqLg0KLSBJdCBhbGxvd3MgKipncmFkaWVudC1iYXNlZCBvcHRpbWl6YXRpb24qKiAoaW1wb3J0YW50IGZvciB0cmFpbmluZyBkZWVwIG5ldHdvcmtzKS4NCg0KLS0tDQoNCiMjIyAqKkFuYWxvZ3k6IFN0ZXAgRnVuY3Rpb24gdnMuIFNpZ21vaWQqKg0KLSAqKlN0ZXAgRnVuY3Rpb246KiogTGlrZSBhIGxpZ2h0IHN3aXRjaCAoZWl0aGVyIE9OIG9yIE9GRikuDQotICoqU2lnbW9pZCBGdW5jdGlvbjoqKiBMaWtlIGEgZGltbWVyIHN3aXRjaCAoc21vb3RobHkgaW5jcmVhc2luZyBicmlnaHRuZXNzKS4NCg0KLS0tDQoNCiMjICoqNC4gTXVsdGlsYXllciBQZXJjZXB0cm9ucyAoTUxQcykgYW5kIEhpZGRlbiBMYXllcnMqKg0KTm93IHRoYXQgd2UgdW5kZXJzdGFuZCBob3cgYSAqKnNpbmdsZS1sYXllciBwZXJjZXB0cm9uKiogd29ya3MsIHdlIGludHJvZHVjZSB0aGUgKiptdWx0aWxheWVyIHBlcmNlcHRyb24gKE1MUCkqKiwgd2hpY2ggY29uc2lzdHMgb2YgbXVsdGlwbGUgbGF5ZXJzIG9mIG5ldXJvbnMuDQoNCiMjIyAqKldoeSBEbyBXZSBOZWVkIEhpZGRlbiBMYXllcnM/KioNCi0gQSAqKnNpbmdsZS1sYXllciBwZXJjZXB0cm9uKiogY2FuIG9ubHkgbW9kZWwgKipsaW5lYXJseSBzZXBhcmFibGUqKiBwcm9ibGVtcyAoZS5nLiwgQU5ELCBPUiBnYXRlcykuDQotIE1hbnkgcmVhbC13b3JsZCBwcm9ibGVtcyBhcmUgKipub24tbGluZWFyKiogKGUuZy4sIHJlY29nbml6aW5nIGhhbmR3cml0dGVuIGRpZ2l0cywgcHJlZGljdGluZyBzdG9jayBwcmljZXMpLg0KLSBBZGRpbmcgKipoaWRkZW4gbGF5ZXJzKiogYWxsb3dzIHRoZSBuZXR3b3JrIHRvICoqbGVhcm4gY29tcGxleCBwYXR0ZXJucyoqIGFuZCAqKmhpZXJhcmNoaWNhbCBmZWF0dXJlcyoqLg0KDQojIyMgKipNTFAgQXJjaGl0ZWN0dXJlKioNCjEuICoqSW5wdXQgTGF5ZXIqKjogVGFrZXMgdGhlIHJhdyBkYXRhIGFzIGlucHV0Lg0KMi4gKipIaWRkZW4gTGF5ZXJzKio6IEludGVybWVkaWF0ZSBsYXllcnMgdGhhdCB0cmFuc2Zvcm0gdGhlIGRhdGEgdXNpbmcgd2VpZ2h0cyBhbmQgYWN0aXZhdGlvbiBmdW5jdGlvbnMuDQozLiAqKk91dHB1dCBMYXllcioqOiBQcm9kdWNlcyB0aGUgZmluYWwgcHJlZGljdGlvbi4NCg0KIyMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uKioNCkVhY2ggbGF5ZXIgYXBwbGllcyB0aGUgZnVuY3Rpb246DQoNClxbDQpoID0gZihXWCArIGIpDQpcXQ0KDQpXaGVyZToNCi0gXCggVyBcKSA9IFdlaWdodCBtYXRyaXggKGxlYXJuZWQgcGFyYW1ldGVycykNCi0gXCggWCBcKSA9IElucHV0IG1hdHJpeCAoZmVhdHVyZXMgZnJvbSB0aGUgcHJldmlvdXMgbGF5ZXIpDQotIFwoIGIgXCkgPSBCaWFzIHRlcm0NCi0gXCggZihcY2RvdCkgXCkgPSBBY3RpdmF0aW9uIGZ1bmN0aW9uIChlLmcuLCAqKlJlTFUqKiwgKipzaWdtb2lkKiosICoqdGFuaCoqKQ0KDQpFYWNoIGhpZGRlbiBuZXVyb24gYXBwbGllcyB0aGUgdHJhbnNmb3JtYXRpb246DQoNClxbDQpoX2kgPSBcc2lnbWFcbGVmdChcc3VtIHdfe2lqfSB4X2ogKyBiX2lccmlnaHQpDQpcXQ0KDQpUaGUgZmluYWwgbGF5ZXIgb3V0cHV0czoNCg0KXFsNCnkgPSBmKFdfe291dH0gXGNkb3QgaCArIGJfe291dH0pDQpcXQ0KDQotLS0NCg0KIyMgKio1LiBBY3RpdmF0aW9uIEZ1bmN0aW9ucyoqDQojIyMgKipXaHkgRG8gV2UgTmVlZCBBY3RpdmF0aW9uIEZ1bmN0aW9ucz8qKg0KLSBXaXRob3V0IGFuIGFjdGl2YXRpb24gZnVuY3Rpb24sICoqZWFjaCBsYXllciB3b3VsZCBqdXN0IGJlIGEgbGluZWFyIHRyYW5zZm9ybWF0aW9uKiosIG1ha2luZyB0aGUgbmV1cmFsIG5ldHdvcmsgZXF1aXZhbGVudCB0byAqKmxvZ2lzdGljIHJlZ3Jlc3Npb24qKi4NCi0gKipOb24tbGluZWFyaXR5KiogYWxsb3dzIHRoZSBtb2RlbCB0byBsZWFybiBjb21wbGV4IHBhdHRlcm5zLg0KDQojIyMgKipDb21tb24gQWN0aXZhdGlvbiBGdW5jdGlvbnMqKg0KfCBBY3RpdmF0aW9uIEZ1bmN0aW9uIHwgRm9ybXVsYSB8IFVzZSBDYXNlIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKlNpZ21vaWQqKiB8IFwoIFxzaWdtYSh4KSA9IFxmcmFjezF9ezEgKyBlXnsteH19IFwpIHwgQmluYXJ5IGNsYXNzaWZpY2F0aW9uIHwNCnwgKipUYW5oKiogfCBcKCBcdGFuaCh4KSA9IFxmcmFje2VeeCAtIGVeey14fX17ZV54ICsgZV57LXh9fSBcKSB8IFplcm8tY2VudGVyZWQgc2lnbW9pZCB8DQp8ICoqUmVMVSoqIHwgXCggZih4KSA9IFxtYXgoMCwgeCkgXCkgfCBNb3N0IHVzZWQgaW4gZGVlcCBsZWFybmluZyB8DQp8ICoqTGVha3kgUmVMVSoqIHwgXCggZih4KSA9IFxtYXgoMC4wMXgsIHgpIFwpIHwgRml4ZXMgZHlpbmcgbmV1cm9uIHByb2JsZW0gfA0KDQojIyMjICoqUHl0aG9uIENvZGUgdG8gVmlzdWFsaXplIEFjdGl2YXRpb24gRnVuY3Rpb25zKioNCmBgYHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQoNCnggPSBucC5saW5zcGFjZSgtMTAsIDEwLCAxMDApDQoNCmRlZiBzaWdtb2lkKHgpOiByZXR1cm4gMSAvICgxICsgbnAuZXhwKC14KSkNCmRlZiB0YW5oKHgpOiByZXR1cm4gbnAudGFuaCh4KQ0KZGVmIHJlbHUoeCk6IHJldHVybiBucC5tYXhpbXVtKDAsIHgpDQpkZWYgbGVha3lfcmVsdSh4KTogcmV0dXJuIG5wLndoZXJlKHggPiAwLCB4LCAwLjAxICogeCkNCg0KcGx0LmZpZ3VyZShmaWdzaXplPSgxMCwgNikpDQpwbHQucGxvdCh4LCBzaWdtb2lkKHgpLCBsYWJlbD0iU2lnbW9pZCIpDQpwbHQucGxvdCh4LCB0YW5oKHgpLCBsYWJlbD0iVGFuaCIpDQpwbHQucGxvdCh4LCByZWx1KHgpLCBsYWJlbD0iUmVMVSIpDQpwbHQucGxvdCh4LCBsZWFreV9yZWx1KHgpLCBsYWJlbD0iTGVha3kgUmVMVSIpDQpwbHQubGVnZW5kKCkNCnBsdC50aXRsZSgiQWN0aXZhdGlvbiBGdW5jdGlvbnMiKQ0KcGx0LmdyaWQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCi0tLQ0KDQojIyAqKjYuIEJhY2twcm9wYWdhdGlvbiBhbmQgTGVhcm5pbmcqKg0KIyMjICoqSG93IERvZXMgYSBOZXVyYWwgTmV0d29yayBMZWFybj8qKg0KVGhlIGtleSB0byB0cmFpbmluZyBhIG5ldXJhbCBuZXR3b3JrIGlzICoqYmFja3Byb3BhZ2F0aW9uKiosIHdoaWNoIGFkanVzdHMgdGhlIHdlaWdodHMgdXNpbmcgKipncmFkaWVudCBkZXNjZW50KiouDQoNCiMjIyAqKlN0ZXBzIGluIEJhY2twcm9wYWdhdGlvbioqDQoxLiAqKkZvcndhcmQgUGFzcyoqOiBDb21wdXRlIHRoZSBvdXRwdXQgZ2l2ZW4gdGhlIGlucHV0Lg0KMi4gKipDb21wdXRlIExvc3MqKjogTWVhc3VyZSB0aGUgZXJyb3IgKGRpZmZlcmVuY2UgYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIGFjdHVhbCB2YWx1ZXMpLg0KMy4gKipCYWNrd2FyZCBQYXNzKio6DQogICAtIENvbXB1dGUgdGhlICoqZ3JhZGllbnQqKiBvZiB0aGUgbG9zcyB3aXRoIHJlc3BlY3QgdG8gd2VpZ2h0cyB1c2luZyB0aGUgKipjaGFpbiBydWxlKiouDQogICAtIFVwZGF0ZSB0aGUgd2VpZ2h0cyB1c2luZyAqKmdyYWRpZW50IGRlc2NlbnQqKjoNCg0KXFsNCncgXGxlZnRhcnJvdyB3IC0gXGFscGhhIFxmcmFje1xwYXJ0aWFsIEx9e1xwYXJ0aWFsIHd9DQpcXQ0KDQpXaGVyZToNCi0gXCggdyBcKSA9IHdlaWdodA0KLSBcKCBcYWxwaGEgXCkgPSBsZWFybmluZyByYXRlDQotIFwoIEwgXCkgPSBsb3NzIGZ1bmN0aW9uDQoNCiMjIyAqKkxvc3MgRnVuY3Rpb25zKioNClRoZSBsb3NzIGZ1bmN0aW9uIHRlbGxzIHVzICoqaG93IGZhciBvZmYqKiBvdXIgcHJlZGljdGlvbnMgYXJlOg0KLSAqKk1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKSoqIGZvciByZWdyZXNzaW9uOg0KDQpcWw0KTCA9IFxmcmFjezF9e059IFxzdW0gKHlfe3RydWV9IC0geV97cHJlZH0pXjINClxdDQoNCi0gKipCaW5hcnkgQ3Jvc3MtRW50cm9weSoqIGZvciBjbGFzc2lmaWNhdGlvbjoNCg0KXFsNCkwgPSAtXGZyYWN7MX17Tn0gXHN1bSBcbGVmdFt5IFxsb2coXGhhdHt5fSkgKyAoMSAtIHkpIFxsb2coMSAtIFxoYXR7eX0pXHJpZ2h0XQ0KXF0NCg0KLS0tDQoNCiMjICoqNy4gQ29kaW5nIGEgTmV1cmFsIE5ldHdvcmsgZnJvbSBTY3JhdGNoKioNCiMjIyAqKlB5dGhvbiBJbXBsZW1lbnRhdGlvbiBvZiBhIFNpbXBsZSBNTFAqKg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCg0KIyBEZWZpbmUgYWN0aXZhdGlvbiBmdW5jdGlvbnMNCmRlZiBzaWdtb2lkKHgpOg0KICAgIHJldHVybiAxIC8gKDEgKyBucC5leHAoLXgpKQ0KDQpkZWYgc2lnbW9pZF9kZXJpdmF0aXZlKHgpOg0KICAgIHJldHVybiB4ICogKDEgLSB4KQ0KDQojIEluaXRpYWxpemUgZGF0YXNldCAoWE9SIHByb2JsZW0pDQpYID0gbnAuYXJyYXkoW1swLDBdLFswLDFdLFsxLDBdLFsxLDFdXSkNCnkgPSBucC5hcnJheShbWzBdLFsxXSxbMV0sWzBdXSkNCg0KIyBJbml0aWFsaXplIHdlaWdodHMgcmFuZG9tbHkNCm5wLnJhbmRvbS5zZWVkKDEpDQp3ZWlnaHRzX2lucHV0X2hpZGRlbiA9IG5wLnJhbmRvbS51bmlmb3JtKC0xLCAxLCAoMiwgMikpDQp3ZWlnaHRzX2hpZGRlbl9vdXRwdXQgPSBucC5yYW5kb20udW5pZm9ybSgtMSwgMSwgKDIsIDEpKQ0KbGVhcm5pbmdfcmF0ZSA9IDAuNQ0KDQojIFRyYWluIGZvciAxMDAwMCBlcG9jaHMNCmZvciBlcG9jaCBpbiByYW5nZSgxMDAwMCk6DQogICAgIyBGb3J3YXJkIHBhc3MNCiAgICBoaWRkZW5faW5wdXQgPSBucC5kb3QoWCwgd2VpZ2h0c19pbnB1dF9oaWRkZW4pDQogICAgaGlkZGVuX291dHB1dCA9IHNpZ21vaWQoaGlkZGVuX2lucHV0KQ0KICAgIGZpbmFsX2lucHV0ID0gbnAuZG90KGhpZGRlbl9vdXRwdXQsIHdlaWdodHNfaGlkZGVuX291dHB1dCkNCiAgICBmaW5hbF9vdXRwdXQgPSBzaWdtb2lkKGZpbmFsX2lucHV0KQ0KDQogICAgIyBDb21wdXRlIGVycm9yDQogICAgZXJyb3IgPSB5IC0gZmluYWxfb3V0cHV0DQoNCiAgICAjIEJhY2twcm9wYWdhdGlvbg0KICAgIGRfb3V0cHV0ID0gZXJyb3IgKiBzaWdtb2lkX2Rlcml2YXRpdmUoZmluYWxfb3V0cHV0KQ0KICAgIGRfaGlkZGVuID0gZF9vdXRwdXQuZG90KHdlaWdodHNfaGlkZGVuX291dHB1dC5UKSAqIHNpZ21vaWRfZGVyaXZhdGl2ZShoaWRkZW5fb3V0cHV0KQ0KDQogICAgIyBVcGRhdGUgd2VpZ2h0cw0KICAgIHdlaWdodHNfaGlkZGVuX291dHB1dCArPSBoaWRkZW5fb3V0cHV0LlQuZG90KGRfb3V0cHV0KSAqIGxlYXJuaW5nX3JhdGUNCiAgICB3ZWlnaHRzX2lucHV0X2hpZGRlbiArPSBYLlQuZG90KGRfaGlkZGVuKSAqIGxlYXJuaW5nX3JhdGUNCg0KIyBUZXN0IHByZWRpY3Rpb25zDQpmb3IgaSBpbiByYW5nZSg0KToNCiAgICBwcmludChmIklucHV0OiB7WFtpXX0gLT4gUHJlZGljdGlvbjoge2ZpbmFsX291dHB1dFtpXX0iKQ0KYGBgDQoNCiMjIyAqKkV4cGxhbmF0aW9uKioNCjEuICoqSW5pdGlhbGl6ZSByYW5kb20gd2VpZ2h0cyoqLg0KMi4gKipVc2UgdGhlIHNpZ21vaWQgZnVuY3Rpb24qKiBmb3IgYWN0aXZhdGlvbi4NCjMuICoqVHJhaW4gdXNpbmcgYmFja3Byb3BhZ2F0aW9uKiogYnkgdXBkYXRpbmcgd2VpZ2h0cy4NCjQuICoqVGVzdCBwcmVkaWN0aW9ucyoqIG9uIFhPUiBwcm9ibGVtLg0KDQotLS0NCg0KIyMjICoqQW5hbG9neTogSG93IEJhY2twcm9wYWdhdGlvbiBXb3JrcyoqDQpJbWFnaW5lIHlvdSdyZSAqKmxlYXJuaW5nIHRvIHNob290IGEgYmFza2V0YmFsbCoqOg0KMS4gWW91ICoqdGFrZSBhIHNob3QqKiAoKipmb3J3YXJkIHBhc3MqKikuDQoyLiBZb3UgKipvYnNlcnZlIGlmIHlvdSBtYWRlIGl0IG9yIG1pc3NlZCoqICgqKmNvbXB1dGUgbG9zcyoqKS4NCjMuIFlvdSAqKmFkanVzdCB5b3VyIG5leHQgc2hvdCBiYXNlZCBvbiB0aGUgbWlzdGFrZSoqICgqKmJhY2twcm9wYWdhdGlvbioqKS4NCjQuIE92ZXIgdGltZSwgeW91ICoqaW1wcm92ZSBhY2N1cmFjeSoqICgqKmdyYWRpZW50IGRlc2NlbnQgdXBkYXRlcyB3ZWlnaHRzKiopLg0KDQotLS0NCg0KIyMgKio4LiBPcHRpbWl6ZXJzIGFuZCBMZWFybmluZyBSYXRlcyoqDQpOb3cgdGhhdCB3ZSB1bmRlcnN0YW5kIGhvdyAqKmJhY2twcm9wYWdhdGlvbioqIHVwZGF0ZXMgd2VpZ2h0cyB1c2luZyAqKmdyYWRpZW50IGRlc2NlbnQqKiwgbGV0J3MgZXhwbG9yZSBkaWZmZXJlbnQgb3B0aW1pemF0aW9uIG1ldGhvZHMgYW5kIHRoZWlyIGltcGFjdCBvbiB0cmFpbmluZy4NCg0KIyMjICoqV2h5IERvIFdlIE5lZWQgRGlmZmVyZW50IE9wdGltaXplcnM/KioNCkdyYWRpZW50IGRlc2NlbnQgY2FuIGJlICoqc2xvdyoqIGFuZCBtYXkgZ2V0ICoqc3R1Y2sgaW4gbG9jYWwgbWluaW1hKiouIERpZmZlcmVudCBvcHRpbWl6ZXJzIGFkanVzdCBsZWFybmluZyB0byBpbXByb3ZlIGNvbnZlcmdlbmNlLg0KDQojIyMgKipUeXBlcyBvZiBHcmFkaWVudCBEZXNjZW50KioNCnwgVHlwZSB8IERlc2NyaXB0aW9uIHwgUHJvcyB8IENvbnMgfA0KfC0tLS0tLXwtLS0tLS0tLS0tLS18LS0tLS0tfC0tLS0tLXwNCnwgKipCYXRjaCBHcmFkaWVudCBEZXNjZW50KiogfCBVc2VzIGFsbCBkYXRhIGF0IG9uY2UgfCBTdGFibGUgdXBkYXRlcyB8IEhpZ2ggbWVtb3J5IHVzYWdlIHwNCnwgKipTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKiB8IFVzZXMgb25lIGRhdGEgcG9pbnQgYXQgYSB0aW1lIHwgRmFzdCB1cGRhdGVzIHwgSGlnaCB2YXJpYW5jZSB8DQp8ICoqTWluaS1CYXRjaCBHcmFkaWVudCBEZXNjZW50KiogfCBVc2VzIHNtYWxsIGJhdGNoZXMgb2YgZGF0YSB8IEJhbGFuY2Ugb2Ygc3BlZWQgYW5kIHN0YWJpbGl0eSB8IFJlcXVpcmVzIHR1bmluZyBiYXRjaCBzaXplIHwNCg0KIyMjICoqUG9wdWxhciBPcHRpbWl6ZXJzKioNCnwgT3B0aW1pemVyIHwgRm9ybXVsYSB8IEtleSBGZWF0dXJlIHwNCnwtLS0tLS0tLS0tLXwtLS0tLS0tLS18LS0tLS0tLS0tLS0tLXwNCnwgKipTR0QqKiB8IFwoIHcgXGxlZnRhcnJvdyB3IC0gXGFscGhhIFxuYWJsYSBMIFwpIHwgVXBkYXRlcyB3ZWlnaHRzIHBlciBzYW1wbGUgfA0KfCAqKk1vbWVudHVtKiogfCBcKCB2X3QgPSBcYmV0YSB2X3t0LTF9ICsgXGFscGhhIFxuYWJsYSBMIFwpLCBcKCB3IFxsZWZ0YXJyb3cgdyAtIHZfdCBcKSB8IFJlZHVjZXMgb3NjaWxsYXRpb25zIHwNCnwgKipBZGFtKiogfCBVc2VzIG1vdmluZyBhdmVyYWdlcyBvZiBncmFkaWVudHMgfCBBZGFwdGl2ZSBsZWFybmluZyByYXRlcyB8DQp8ICoqUk1TcHJvcCoqIHwgTm9ybWFsaXplcyBsZWFybmluZyByYXRlcyB8IFdvcmtzIHdlbGwgZm9yIGRlZXAgbGVhcm5pbmcgfA0KDQojIyMgKipQeXRob24gQ29kZSB0byBDb21wYXJlIE9wdGltaXplcnMqKg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gdGVuc29yZmxvdy5rZXJhcy5vcHRpbWl6ZXJzIGltcG9ydCBTR0QsIEFkYW0sIFJNU3Byb3ANCg0KIyBTYW1wbGUgZnVuY3Rpb24gdG8gb3B0aW1pemUgKHF1YWRyYXRpYyBsb3NzKQ0KZGVmIGxvc3NfZnVuY3Rpb24oeCk6DQogICAgcmV0dXJuIHgqKjIgKyAyKnggKyAxDQoNCnhfdmFscyA9IG5wLmxpbnNwYWNlKC01LCAzLCAxMDApDQp5X3ZhbHMgPSBsb3NzX2Z1bmN0aW9uKHhfdmFscykNCg0KIyBQbG90IGZ1bmN0aW9uDQpwbHQucGxvdCh4X3ZhbHMsIHlfdmFscywgbGFiZWw9Ikxvc3MgRnVuY3Rpb24iKQ0KcGx0LnhsYWJlbCgiUGFyYW1ldGVyIFZhbHVlIikNCnBsdC55bGFiZWwoIkxvc3MiKQ0KcGx0LnRpdGxlKCJHcmFkaWVudCBEZXNjZW50IE9wdGltaXplcnMiKQ0KcGx0LmxlZ2VuZCgpDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tDQoNCiMjICoqOS4gTGVhcm5pbmcgUmF0ZSBTZWxlY3Rpb24qKg0KVGhlICoqbGVhcm5pbmcgcmF0ZSAozrEpKiogY29udHJvbHMgaG93IG11Y2ggd2VpZ2h0cyB1cGRhdGUgZWFjaCBzdGVwLg0KDQojIyMgKipFZmZlY3RzIG9mIExlYXJuaW5nIFJhdGUqKg0KLSAqKlRvbyBoaWdoKiog4oaSIE1heSBvdmVyc2hvb3QgdGhlIG9wdGltYWwgcG9pbnQuDQotICoqVG9vIGxvdyoqIOKGkiBDb252ZXJnZXMgdG9vIHNsb3dseS4NCg0KIyMjICoqQWRhcHRpdmUgTGVhcm5pbmcgUmF0ZSBTdHJhdGVnaWVzKioNCnwgTWV0aG9kIHwgRmVhdHVyZSB8DQp8LS0tLS0tLS18LS0tLS0tLS0tfA0KfCAqKkRlY2F5KiogfCBSZWR1Y2UgbGVhcm5pbmcgcmF0ZSBvdmVyIHRpbWUgfA0KfCAqKkFkYXB0aXZlIE9wdGltaXplcnMgKEFkYW0sIFJNU3Byb3ApKiogfCBBZGp1c3QgcmF0ZXMgZHluYW1pY2FsbHkgfA0KDQojIyMgKipQeXRob24gRXhhbXBsZTogTGVhcm5pbmcgUmF0ZSBDb21wYXJpc29uKioNCmBgYHB5dGhvbg0KaW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYNCg0KIyBEZWZpbmUgYSBzaW1wbGUgbW9kZWwNCm1vZGVsID0gdGYua2VyYXMuU2VxdWVudGlhbChbdGYua2VyYXMubGF5ZXJzLkRlbnNlKDEsIGlucHV0X3NoYXBlPVsxXSldKQ0KDQojIENvbXBpbGUgd2l0aCBkaWZmZXJlbnQgb3B0aW1pemVycw0Kb3B0aW1pemVycyA9IHsNCiAgICAiU0dEIjogU0dEKGxlYXJuaW5nX3JhdGU9MC4xKSwNCiAgICAiQWRhbSI6IEFkYW0obGVhcm5pbmdfcmF0ZT0wLjAxKSwNCiAgICAiUk1TcHJvcCI6IFJNU3Byb3AobGVhcm5pbmdfcmF0ZT0wLjAxKQ0KfQ0KDQojIENvbXBhcmUgdHJhaW5pbmcgc3BlZWQgYnkgZml0dGluZyBkdW1teSBkYXRhDQpmb3IgbmFtZSwgb3B0IGluIG9wdGltaXplcnMuaXRlbXMoKToNCiAgICBtb2RlbC5jb21waWxlKGxvc3M9Im1zZSIsIG9wdGltaXplcj1vcHQpDQogICAgbW9kZWwuZml0KG5wLmFycmF5KFsxLCAyLCAzXSksIG5wLmFycmF5KFsyLCA0LCA2XSksIGVwb2Nocz0xMCwgdmVyYm9zZT0wKQ0KICAgIHByaW50KGYie25hbWV9IG9wdGltaXplciB0cmFpbmVkIG1vZGVsLiIpDQpgYGANCg0KLS0tDQoNCiMjICoqMTAuIE92ZXJmaXR0aW5nIGFuZCBSZWd1bGFyaXphdGlvbioqDQojIyMgKipXaGF0IGlzIE92ZXJmaXR0aW5nPyoqDQotICoqT3ZlcmZpdHRpbmcqKiBoYXBwZW5zIHdoZW4gYSBtb2RlbCBsZWFybnMgKipub2lzZSBpbnN0ZWFkIG9mIHBhdHRlcm5zKiouDQotIFRoZSBtb2RlbCBwZXJmb3JtcyB3ZWxsIG9uIHRyYWluaW5nIGRhdGEgYnV0IHBvb3JseSBvbiBuZXcgZGF0YS4NCg0KIyMjICoqUmVndWxhcml6YXRpb24gVGVjaG5pcXVlcyoqDQp8IE1ldGhvZCB8IFB1cnBvc2UgfA0KfC0tLS0tLS0tfC0tLS0tLS0tLXwNCnwgKipMMSBSZWd1bGFyaXphdGlvbiAoTGFzc28pKiogfCBTaHJpbmtzIGxlc3MgaW1wb3J0YW50IHdlaWdodHMgdG8gMCB8DQp8ICoqTDIgUmVndWxhcml6YXRpb24gKFJpZGdlKSoqIHwgUGVuYWxpemVzIGxhcmdlIHdlaWdodHMgfA0KfCAqKkRyb3BvdXQqKiB8IFJhbmRvbWx5IGRlYWN0aXZhdGVzIG5ldXJvbnMgZHVyaW5nIHRyYWluaW5nIHwNCg0KIyMjICoqUHl0aG9uIEV4YW1wbGU6IEwyIFJlZ3VsYXJpemF0aW9uKioNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLnJlZ3VsYXJpemVycyBpbXBvcnQgbDINCmZyb20gdGVuc29yZmxvdy5rZXJhcy5sYXllcnMgaW1wb3J0IERlbnNlDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMubW9kZWxzIGltcG9ydCBTZXF1ZW50aWFsDQoNCiMgRGVmaW5lIGEgbW9kZWwgd2l0aCBMMiByZWd1bGFyaXphdGlvbg0KbW9kZWwgPSBTZXF1ZW50aWFsKFsNCiAgICBEZW5zZSg2NCwgYWN0aXZhdGlvbj0icmVsdSIsIGtlcm5lbF9yZWd1bGFyaXplcj1sMigwLjAxKSwgaW5wdXRfc2hhcGU9KDEwLCkpLA0KICAgIERlbnNlKDEsIGFjdGl2YXRpb249InNpZ21vaWQiKQ0KXSkNCg0KbW9kZWwuY29tcGlsZShvcHRpbWl6ZXI9ImFkYW0iLCBsb3NzPSJiaW5hcnlfY3Jvc3NlbnRyb3B5IikNCmBgYA0KDQotLS0NCg0KIyMgKipBbmFsb2d5OiBMZWFybmluZyBSYXRlIGFuZCBPcHRpbWl6ZXJzKioNCkltYWdpbmUgbGVhcm5pbmcgKipob3cgdG8gcmlkZSBhIGJpa2UqKjoNCi0gKipUb28gc2xvdyAobG93IGxlYXJuaW5nIHJhdGUpKiog4oaSIFlvdSB3b2JibGUgYnV0IG5ldmVyIGdvIGZhci4NCi0gKipUb28gZmFzdCAoaGlnaCBsZWFybmluZyByYXRlKSoqIOKGkiBZb3UgbWlnaHQgY3Jhc2guDQotICoqTW9tZW50dW0gb3B0aW1pemVyKiog4oaSIExpa2UgdXNpbmcgdHJhaW5pbmcgd2hlZWxzIHRvIHN0YWJpbGl6ZS4NCi0gKipBZGFtIG9wdGltaXplcioqIOKGkiBMaWtlIGFkanVzdGluZyBzcGVlZCBiYXNlZCBvbiB0ZXJyYWluLg0KDQotLS0NCiMjICoqMTEuIE1vZGVsIEFyY2hpdGVjdHVyZXMgYW5kIExheWVycyoqDQpOZXVyYWwgbmV0d29ya3MgYXJlIGNvbXBvc2VkIG9mICoqbGF5ZXJzIG9mIG5ldXJvbnMqKiwgZWFjaCBwZXJmb3JtaW5nIG1hdGhlbWF0aWNhbCB0cmFuc2Zvcm1hdGlvbnMuIFVuZGVyc3RhbmRpbmcgZGlmZmVyZW50IGFyY2hpdGVjdHVyZXMgaGVscHMgZGVzaWduIG1vZGVscyBzdWl0ZWQgZm9yIHZhcmlvdXMgdGFza3MuDQoNCi0tLQ0KDQojIyAqKjEyLiBUeXBlcyBvZiBOZXVyYWwgTmV0d29ya3MqKg0KIyMjICoqMS4gRmVlZGZvcndhcmQgTmV1cmFsIE5ldHdvcmtzIChGTk4pKioNCi0gKipTdHJ1Y3R1cmUqKjogRGF0YSBtb3ZlcyBpbiAqKm9uZSBkaXJlY3Rpb24qKiwgZnJvbSBpbnB1dCB0byBvdXRwdXQuDQotICoqVXNlIENhc2UqKjogQmFzaWMgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24uDQotICoqRXhhbXBsZSoqOg0KICAtIEhhbmR3cml0dGVuIGRpZ2l0IHJlY29nbml0aW9uIChNTklTVCBkYXRhc2V0KS4NCiAgLSBQcmVkaWN0aW5nIGhvdXNpbmcgcHJpY2VzLg0KDQojIyMgKioyLiBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrcyAoQ05OKSoqDQotICoqU3RydWN0dXJlKio6IFVzZXMgKipjb252b2x1dGlvbiBsYXllcnMqKiB0byBkZXRlY3Qgc3BhdGlhbCBwYXR0ZXJucy4NCi0gKipVc2UgQ2FzZSoqOiBJbWFnZSBwcm9jZXNzaW5nLCBmYWNpYWwgcmVjb2duaXRpb24sIHNlbGYtZHJpdmluZyBjYXJzLg0KLSAqKkV4YW1wbGUqKjoNCiAgLSBEZXRlY3RpbmcgdHVtb3JzIGluIG1lZGljYWwgaW1hZ2VzLg0KICAtIFJlY29nbml6aW5nIG9iamVjdHMgaW4gcGhvdG9zLg0KDQojIyMgKiozLiBSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzIChSTk4pKioNCi0gKipTdHJ1Y3R1cmUqKjogTWFpbnRhaW5zICoqbWVtb3J5KiogdXNpbmcgcHJldmlvdXMgdGltZSBzdGVwcy4NCi0gKipVc2UgQ2FzZSoqOiBTZXF1ZW5jZXMgbGlrZSB0ZXh0LCBzcGVlY2gsIGFuZCBzdG9jayBwcmljZXMuDQotICoqRXhhbXBsZSoqOg0KICAtIFByZWRpY3RpbmcgdGhlIG5leHQgd29yZCBpbiBhIHNlbnRlbmNlLg0KICAtIFNwZWVjaC10by10ZXh0IGNvbnZlcnNpb24uDQoNCiMjIyAqKjQuIFRyYW5zZm9ybWVycyoqDQotICoqU3RydWN0dXJlKio6IFVzZXMgKiphdHRlbnRpb24gbWVjaGFuaXNtcyoqIHRvIHByb2Nlc3MgZW50aXJlIHNlcXVlbmNlcyBhdCBvbmNlLg0KLSAqKlVzZSBDYXNlKio6IE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyAoTkxQKSwgY2hhdGJvdHMsIG1hY2hpbmUgdHJhbnNsYXRpb24uDQotICoqRXhhbXBsZSoqOg0KICAtIEdQVCBtb2RlbHMgKGxpa2UgQ2hhdEdQVCkuDQogIC0gR29vZ2xlIFRyYW5zbGF0ZS4NCg0KLS0tDQoNCiMjICoqMTMuIExheWVycyBpbiBhIE5ldXJhbCBOZXR3b3JrKioNCiMjIyAqKjEuIElucHV0IExheWVyKioNCi0gUmVjZWl2ZXMgcmF3IGRhdGEgKGltYWdlcywgdGV4dCwgbnVtYmVycykuDQotIE5vIGNhbGN1bGF0aW9ucyBvY2N1ciBoZXJlLg0KLSBFeGFtcGxlOiBQaXhlbCB2YWx1ZXMgb2YgYW4gaW1hZ2UuDQoNCiMjIyAqKjIuIEhpZGRlbiBMYXllcnMqKg0KLSBQZXJmb3JtIGZlYXR1cmUgZXh0cmFjdGlvbiB1c2luZyAqKndlaWdodHMgYW5kIGFjdGl2YXRpb24gZnVuY3Rpb25zKiouDQotIFRoZSBtb3JlIGxheWVycywgdGhlIGRlZXBlciB0aGUgbmV0d29yayAoKipEZWVwIExlYXJuaW5nKiopLg0KLSBFeGFtcGxlOiBEZXRlY3RzIGVkZ2VzIGluIGltYWdlcywgdGhlbiBzaGFwZXMsIHRoZW4gb2JqZWN0cy4NCg0KIyMjICoqMy4gT3V0cHV0IExheWVyKioNCi0gQ29udmVydHMgZmluYWwgY29tcHV0YXRpb25zIGludG8gYSBwcmVkaWN0aW9uIChjbGFzcyBsYWJlbCwgcHJvYmFiaWxpdHkpLg0KLSBFeGFtcGxlOiAiVGhpcyBpcyBhIGNhdCIgKENsYXNzaWZpY2F0aW9uKSBvciAiU3RvY2sgcHJpY2Ugd2lsbCByaXNlIiAoUmVncmVzc2lvbikuDQoNCi0tLQ0KDQojIyAqKjE0LiBBY3RpdmF0aW9uIEZ1bmN0aW9ucyoqDQpBY3RpdmF0aW9uIGZ1bmN0aW9ucyAqKmNvbnRyb2wgbmV1cm9uIG91dHB1dCoqIGFuZCBpbnRyb2R1Y2UgKipub24tbGluZWFyaXR5KiosIG1ha2luZyBuZXR3b3JrcyBtb3JlIHBvd2VyZnVsLg0KDQp8IEFjdGl2YXRpb24gfCBGb3JtdWxhIHwgVXNlIENhc2UgfA0KfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfA0KfCAqKlNpZ21vaWQqKiB8IFwoIGYoeCkgPSBcZnJhY3sxfXsxICsgZV57LXh9fSBcKSB8IEJpbmFyeSBjbGFzc2lmaWNhdGlvbiB8DQp8ICoqUmVMVSoqIHwgXCggZih4KSA9IFxtYXgoMCwgeCkgXCkgfCBNb3N0IGRlZXAgbmV0d29ya3MgfA0KfCAqKlRhbmgqKiB8IFwoIGYoeCkgPSBcZnJhY3tlXnggLSBlXnsteH19e2VeeCArIGVeey14fX0gXCkgfCBSZWdyZXNzaW9uIHwNCnwgKipTb2Z0bWF4KiogfCBcKCBmKHhfaSkgPSBcZnJhY3tlXnt4X2l9fXtcc3VtIGVee3hfan19IFwpIHwgTXVsdGktY2xhc3MgY2xhc3NpZmljYXRpb24gfA0KDQotLS0NCg0KIyMgKioxNS4gTmV1cmFsIE5ldHdvcmsgQXJjaGl0ZWN0dXJlIGluIENvZGUqKg0KIyMjICoqU2ltcGxlIEZlZWRmb3J3YXJkIE5ldXJhbCBOZXR3b3JrKioNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm1vZGVscyBpbXBvcnQgU2VxdWVudGlhbA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRGVuc2UsIEFjdGl2YXRpb24NCg0KIyBEZWZpbmUgbW9kZWwgYXJjaGl0ZWN0dXJlDQptb2RlbCA9IFNlcXVlbnRpYWwoWw0KICAgIERlbnNlKDMyLCBpbnB1dF9zaGFwZT0oMTAsKSwgYWN0aXZhdGlvbj0ncmVsdScpLCAgIyBIaWRkZW4gbGF5ZXIgMQ0KICAgIERlbnNlKDE2LCBhY3RpdmF0aW9uPSdyZWx1JyksICAjIEhpZGRlbiBsYXllciAyDQogICAgRGVuc2UoMSwgYWN0aXZhdGlvbj0nc2lnbW9pZCcpICAjIE91dHB1dCBsYXllciBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uDQpdKQ0KDQojIENvbXBpbGUgbW9kZWwNCm1vZGVsLmNvbXBpbGUob3B0aW1pemVyPSdhZGFtJywgbG9zcz0nYmluYXJ5X2Nyb3NzZW50cm9weScsIG1ldHJpY3M9WydhY2N1cmFjeSddKQ0KDQojIFZpZXcgbW9kZWwgc3VtbWFyeQ0KbW9kZWwuc3VtbWFyeSgpDQpgYGANCg0KIyMjICoqQ05OIGZvciBJbWFnZSBQcm9jZXNzaW5nKioNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgQ29udjJELCBNYXhQb29saW5nMkQsIEZsYXR0ZW4NCg0KY25uID0gU2VxdWVudGlhbChbDQogICAgQ29udjJEKDMyLCAoMywzKSwgYWN0aXZhdGlvbj0ncmVsdScsIGlucHV0X3NoYXBlPSgyOCwyOCwxKSksICAjIENvbnZvbHV0aW9uYWwgbGF5ZXINCiAgICBNYXhQb29saW5nMkQocG9vbF9zaXplPSgyLDIpKSwgICMgUG9vbGluZyB0byByZWR1Y2UgZGltZW5zaW9ucw0KICAgIEZsYXR0ZW4oKSwgICMgRmxhdHRlbmluZyB0byBwcmVwYXJlIGZvciBEZW5zZSBsYXllcnMNCiAgICBEZW5zZSg2NCwgYWN0aXZhdGlvbj0ncmVsdScpLCAgIyBGdWxseSBjb25uZWN0ZWQgbGF5ZXINCiAgICBEZW5zZSgxMCwgYWN0aXZhdGlvbj0nc29mdG1heCcpICAjIE91dHB1dCBmb3IgMTAgY2xhc3Nlcw0KXSkNCg0KY25uLmNvbXBpbGUob3B0aW1pemVyPSdhZGFtJywgbG9zcz0nY2F0ZWdvcmljYWxfY3Jvc3NlbnRyb3B5JywgbWV0cmljcz1bJ2FjY3VyYWN5J10pDQpjbm4uc3VtbWFyeSgpDQpgYGANCg0KLS0tDQoNCiMjICoqMTYuIFVuZGVyc3RhbmRpbmcgTmV1cmFsIE5ldHdvcmsgRGVwdGgqKg0KIyMjICoqV2hhdCBpcyBEZXB0aCBpbiBhIE5ldXJhbCBOZXR3b3JrPyoqDQotICoqU2hhbGxvdyBOZXR3b3JrKio6IDEgaGlkZGVuIGxheWVyLg0KLSAqKkRlZXAgTmV0d29yayoqOiBNdWx0aXBsZSBoaWRkZW4gbGF5ZXJzICgqKkRlZXAgTGVhcm5pbmcqKikuDQotIE1vcmUgZGVwdGggKippbmNyZWFzZXMgbW9kZWwgY2FwYWNpdHkqKiBidXQgY2FuIGxlYWQgdG8gKipvdmVyZml0dGluZyoqLg0KDQojIyMgKipUcmFkZS1vZmZzKioNCnwgTW9yZSBMYXllcnMgfCBGZXdlciBMYXllcnMgfA0KfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfA0KfCBDYW4gbGVhcm4gY29tcGxleCBmZWF0dXJlcyB8IFNpbXBsZXIsIGZhc3RlciB0byB0cmFpbiB8DQp8IEhpZ2hlciBhY2N1cmFjeSAoaWYgdHVuZWQgd2VsbCkgfCBMZXNzIHByb25lIHRvIG92ZXJmaXR0aW5nIHwNCnwgUmVxdWlyZXMgbW9yZSBkYXRhIHwgV29ya3Mgd2VsbCBmb3Igc21hbGwgZGF0YXNldHMgfA0KDQotLS0NCg0KIyMgKipBbmFsb2d5OiBOZXVyYWwgTmV0d29ya3MgYXMgYSBCYWtlcnkqKg0KSW1hZ2luZSBhICoqYmFrZXJ5Kio6DQotICoqSW5wdXQgTGF5ZXIqKjogSW5ncmVkaWVudHMgKGZsb3VyLCBzdWdhciwgZWdncykuDQotICoqSGlkZGVuIExheWVycyoqOiBNaXhpbmcsIGJha2luZywgZnJvc3RpbmcuDQotICoqT3V0cHV0IExheWVyKio6IEZpbmlzaGVkIGNha2UuDQotICoqQWN0aXZhdGlvbiBGdW5jdGlvbnMqKjogQ29udHJvbHMgaG93IHRoZSBjYWtlIHR1cm5zIG91dCAoYmFrZSB0aW1lLCBtaXhpbmcgc3BlZWQpLg0KDQpXaXRob3V0IGhpZGRlbiBsYXllcnMsIGl0J3MganVzdCByYXcgaW5ncmVkaWVudHMuIE1vcmUgaGlkZGVuIGxheWVycyByZWZpbmUgdGhlIHByb2Nlc3MgdG8gcHJvZHVjZSAqKnRoZSBiZXN0IGNha2UgcG9zc2libGUqKiENCg0KLS0tDQoNCg0KDQojIyAqKjE3LiBUcmFpbmluZyBhIE5ldXJhbCBOZXR3b3JrKioNClRyYWluaW5nIGEgbmV1cmFsIG5ldHdvcmsgbWVhbnMgKiphZGp1c3RpbmcgdGhlIG1vZGVs4oCZcyB3ZWlnaHRzKiogdG8gbWluaW1pemUgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwcmVkaWN0aW9ucyBhbmQgYWN0dWFsIHZhbHVlcy4gVGhpcyBwcm9jZXNzIGludm9sdmVzOg0KMS4gKipGb3J3YXJkIHByb3BhZ2F0aW9uKiog4oCTIERhdGEgbW92ZXMgZnJvbSBpbnB1dCB0byBvdXRwdXQuDQoyLiAqKkxvc3MgY2FsY3VsYXRpb24qKiDigJMgTWVhc3VyZXMgaG93IGZhciBwcmVkaWN0aW9ucyBhcmUgZnJvbSB0cnVlIGxhYmVscy4NCjMuICoqQmFja3dhcmQgcHJvcGFnYXRpb24qKiDigJMgVXBkYXRlcyB3ZWlnaHRzIHRvIGltcHJvdmUgcHJlZGljdGlvbnMuDQo0LiAqKk9wdGltaXphdGlvbioqIOKAkyBBZGp1c3RzIG1vZGVsIHBhcmFtZXRlcnMgdG8gbWluaW1pemUgZXJyb3IuDQoNCi0tLQ0KDQojIyAqKjE4LiBMb3NzIEZ1bmN0aW9ucyoqDQpBICoqbG9zcyBmdW5jdGlvbioqIHF1YW50aWZpZXMgaG93IHdlbGwgdGhlIG5ldHdvcmsgaXMgcGVyZm9ybWluZy4gVGhlIGdvYWwgaXMgdG8gbWluaW1pemUgdGhpcyB2YWx1ZS4NCg0KfCAqKkxvc3MgRnVuY3Rpb24qKiB8ICoqRm9ybXVsYSoqIHwgKipVc2UgQ2FzZSoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18DQp8ICoqTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpKiogfCBcKCBMID0gXGZyYWN7MX17bn0gXHN1bSAoeV9pIC0gXGhhdHt5fV9pKV4yIFwpIHwgUmVncmVzc2lvbiB8DQp8ICoqTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSoqIHwgXCggTCA9IFxmcmFjezF9e259IFxzdW0gfHlfaSAtIFxoYXR7eX1faXwgXCkgfCBSZWdyZXNzaW9uIHwNCnwgKipCaW5hcnkgQ3Jvc3MtRW50cm9weSoqIHwgXCggTCA9IC1cZnJhY3sxfXtufSBcc3VtIFt5X2kgXGxvZyhcaGF0e3l9X2kpICsgKDEgLSB5X2kpIFxsb2coMSAtIFxoYXR7eX1faSldIFwpIHwgQmluYXJ5IGNsYXNzaWZpY2F0aW9uIHwNCnwgKipDYXRlZ29yaWNhbCBDcm9zcy1FbnRyb3B5KiogfCBcKCBMID0gLVxzdW0geV9pIFxsb2coXGhhdHt5fV9pKSBcKSB8IE11bHRpLWNsYXNzIGNsYXNzaWZpY2F0aW9uIHwNCg0KLS0tDQoNCiMjICoqMTkuIEJhY2twcm9wYWdhdGlvbiBhbmQgR3JhZGllbnQgRGVzY2VudCoqDQpCYWNrcHJvcGFnYXRpb24gaXMgdGhlIHByb2Nlc3Mgb2YgKiphZGp1c3Rpbmcgd2VpZ2h0cyoqIGJhc2VkIG9uIHRoZSBsb3NzIGZ1bmN0aW9uLiBJdCB1c2VzICoqZ3JhZGllbnQgZGVzY2VudCoqIHRvIGZpbmQgb3B0aW1hbCB3ZWlnaHQgdmFsdWVzLg0KDQojIyMgKipHcmFkaWVudCBEZXNjZW50IEVxdWF0aW9uKioNCldlaWdodHMgYXJlIHVwZGF0ZWQgYXM6DQpcWw0KdyA9IHcgLSBcYWxwaGEgXGZyYWN7XHBhcnRpYWwgTH17XHBhcnRpYWwgd30NClxdDQp3aGVyZToNCi0gXCggdyBcKSA9IHdlaWdodA0KLSBcKCBcYWxwaGEgXCkgPSBsZWFybmluZyByYXRlIChzdGVwIHNpemUpDQotIFwoIFxmcmFje1xwYXJ0aWFsIEx9e1xwYXJ0aWFsIHd9IFwpID0gZ3JhZGllbnQgKHJhdGUgb2YgY2hhbmdlIG9mIGxvc3MpDQoNCiMjIyAqKlZhcmlhbnRzIG9mIEdyYWRpZW50IERlc2NlbnQqKg0KfCAqKkFsZ29yaXRobSoqIHwgKipVcGRhdGUgTWV0aG9kKiogfCAqKlByb3MqKiB8ICoqQ29ucyoqIHwNCnwtLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLXwtLS0tLS0tLS18DQp8ICoqQmF0Y2ggR3JhZGllbnQgRGVzY2VudCoqIHwgVXNlcyBhbGwgZGF0YSBhdCBvbmNlIHwgTW9yZSBzdGFibGUgdXBkYXRlcyB8IFNsb3cgZm9yIGxhcmdlIGRhdGFzZXRzIHwNCnwgKipTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKiB8IFVwZGF0ZXMgcGVyIHNhbXBsZSB8IEZhc3QgdXBkYXRlcyB8IEhpZ2ggdmFyaWFuY2UgaW4gdXBkYXRlcyB8DQp8ICoqTWluaS1CYXRjaCBHcmFkaWVudCBEZXNjZW50KiogfCBVc2VzIHNtYWxsIGJhdGNoZXMgfCBCYWxhbmNlIG9mIHNwZWVkICYgc3RhYmlsaXR5IHwgUmVxdWlyZXMgdHVuaW5nIGJhdGNoIHNpemUgfA0KDQotLS0NCg0KIyMgKioyMC4gT3B0aW1pemVycyoqDQpPcHRpbWl6ZXJzIGltcHJvdmUgKipncmFkaWVudCBkZXNjZW50KiogYnkgYWRqdXN0aW5nIGxlYXJuaW5nIHJhdGVzIGFuZCB3ZWlnaHQgdXBkYXRlcyBkeW5hbWljYWxseS4NCg0KfCAqKk9wdGltaXplcioqIHwgKipDaGFyYWN0ZXJpc3RpY3MqKiB8DQp8LS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKlNHRCoqIHwgU2ltcGxlIGJ1dCBub2lzeSB1cGRhdGVzIHwNCnwgKipNb21lbnR1bSoqIHwgVXNlcyBwYXN0IGdyYWRpZW50cyB0byBzbW9vdGggdXBkYXRlcyB8DQp8ICoqQWRhbSAoQWRhcHRpdmUgTW9tZW50IEVzdGltYXRpb24pKiogfCBBZGp1c3RzIGxlYXJuaW5nIHJhdGUgZHluYW1pY2FsbHkgKG1vc3QgdXNlZCkgfA0KfCAqKlJNU3Byb3AqKiB8IE5vcm1hbGl6ZXMgZ3JhZGllbnQgbWFnbml0dWRlIGZvciBzdGFibGUgdXBkYXRlcyB8DQoNCiMjIyAqKkNvZGUgSW1wbGVtZW50YXRpb24gb2YgT3B0aW1pemVycyoqDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5vcHRpbWl6ZXJzIGltcG9ydCBTR0QsIEFkYW0NCg0KIyBEZWZpbmUgbW9kZWwgd2l0aCBkaWZmZXJlbnQgb3B0aW1pemVycw0KbW9kZWwuY29tcGlsZShvcHRpbWl6ZXI9U0dEKGxlYXJuaW5nX3JhdGU9MC4wMSksIGxvc3M9J21zZScpICAjIEJhc2ljIFNHRA0KbW9kZWwuY29tcGlsZShvcHRpbWl6ZXI9QWRhbShsZWFybmluZ19yYXRlPTAuMDAxKSwgbG9zcz0nYmluYXJ5X2Nyb3NzZW50cm9weScpICAjIEFkYW0gZm9yIGNsYXNzaWZpY2F0aW9uDQpgYGANCg0KLS0tDQoNCiMjICoqMjEuIEVwb2NocyBhbmQgQmF0Y2ggU2l6ZSoqDQotICoqRXBvY2gqKjogT25lIGNvbXBsZXRlIHBhc3Mgb2YgdGhlIGRhdGFzZXQgdGhyb3VnaCB0aGUgbmV0d29yay4NCi0gKipCYXRjaCBzaXplKio6IE51bWJlciBvZiBzYW1wbGVzIHByb2Nlc3NlZCBiZWZvcmUgdXBkYXRpbmcgd2VpZ2h0cy4NCg0KIyMjICoqRmluZGluZyB0aGUgUmlnaHQgVmFsdWVzKioNCi0gKipUb28gZmV3IGVwb2NocyoqIOKGkiBVbmRlcmZpdHRpbmcgKG1vZGVsIGhhc27igJl0IGxlYXJuZWQgZW5vdWdoKS4NCi0gKipUb28gbWFueSBlcG9jaHMqKiDihpIgT3ZlcmZpdHRpbmcgKG1vZGVsIG1lbW9yaXplcyBkYXRhKS4NCi0gKipTbWFsbCBiYXRjaCBzaXplKiog4oaSIE1vcmUgdXBkYXRlcywgYmV0dGVyIGdlbmVyYWxpemF0aW9uLg0KLSAqKkxhcmdlIGJhdGNoIHNpemUqKiDihpIgRmFzdGVyIHRyYWluaW5nLCBidXQgbWF5IG92ZXJmaXQuDQoNCioqRXhhbXBsZSBDYWxjdWxhdGlvbioqOg0KLSAqKkRhdGFzZXQgc2l6ZSoqID0gMTAwLDAwMA0KLSAqKkJhdGNoIHNpemUqKiA9IDEwMA0KLSAqKkVwb2NocyoqID0gNA0KDQpcWw0KXHRleHR7QmF0Y2hlcyBwZXIgZXBvY2h9ID0gXGZyYWN7MTAwLDAwMH17MTAwfSA9IDEsMDAwDQpcXQ0KDQpcWw0KXHRleHR7VG90YWwgdXBkYXRlc30gPSAxLDAwMCBcdGltZXMgNCA9IDQsMDAwDQpcXQ0KDQotLS0NCg0KIyMgKioyMi4gRXZhbHVhdGluZyBNb2RlbCBQZXJmb3JtYW5jZSoqDQpOZXVyYWwgbmV0d29ya3MgYXJlIGV2YWx1YXRlZCB1c2luZyAqKnRyYWluaW5nKiogYW5kICoqdGVzdCBkYXRhKiouDQoNCiMjIyAqKjEuIFRyYWluaW5nIHZzLiBWYWxpZGF0aW9uIHZzLiBUZXN0IFNldCoqDQp8ICoqRGF0YXNldCoqIHwgKipQdXJwb3NlKiogfA0KfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18DQp8ICoqVHJhaW5pbmcgU2V0KiogfCBNb2RlbCBsZWFybnMgZnJvbSB0aGlzIGRhdGEgfA0KfCAqKlZhbGlkYXRpb24gU2V0KiogfCBUdW5lcyBoeXBlcnBhcmFtZXRlcnMsIHByZXZlbnRzIG92ZXJmaXR0aW5nIHwNCnwgKipUZXN0IFNldCoqIHwgRmluYWwgZXZhbHVhdGlvbiBvbiB1bnNlZW4gZGF0YSB8DQoNCiMjIyAqKjIuIE1ldHJpY3MgZm9yIE1vZGVsIFBlcmZvcm1hbmNlKioNCnwgKipNZXRyaWMqKiB8ICoqVXNlIENhc2UqKiB8DQp8LS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfA0KfCAqKkFjY3VyYWN5KiogfCBDbGFzc2lmaWNhdGlvbiAod2hlbiBjbGFzc2VzIGFyZSBiYWxhbmNlZCkgfA0KfCAqKlByZWNpc2lvbiAmIFJlY2FsbCoqIHwgQ2xhc3NpZmljYXRpb24gKGltYmFsYW5jZWQgZGF0YXNldHMpIHwNCnwgKipGMS1TY29yZSoqIHwgVHJhZGUtb2ZmIGJldHdlZW4gcHJlY2lzaW9uICYgcmVjYWxsIHwNCnwgKipSwrIgU2NvcmUqKiB8IFJlZ3Jlc3Npb24gKGV4cGxhaW5zIHZhcmlhbmNlKSB8DQoNCi0tLQ0KDQojIyAqKkFuYWxvZ3k6IFRyYWluaW5nIGEgTmV1cmFsIE5ldHdvcmsgTGlrZSBMZWFybmluZyB0byBTaG9vdCBCYXNrZXRiYWxsKioNCkltYWdpbmUgeW91J3JlIGxlYXJuaW5nIGhvdyB0byBzaG9vdCBhICoqYmFza2V0YmFsbCoqOg0KMS4gKipMb3NzIGZ1bmN0aW9uKiog4oaSIE1lYXN1cmVzIGhvdyBvZnRlbiB5b3UgbWlzcy4NCjIuICoqQmFja3Byb3BhZ2F0aW9uKiog4oaSIEFkanVzdHMgeW91ciB0ZWNobmlxdWUgYWZ0ZXIgZWFjaCBzaG90Lg0KMy4gKipHcmFkaWVudCBkZXNjZW50Kiog4oaSIEhlbHBzIGltcHJvdmUgc2hvdCBhY2N1cmFjeSBvdmVyIHRpbWUuDQo0LiAqKkVwb2NocyoqIOKGkiBUaGUgbnVtYmVyIG9mIHByYWN0aWNlIHNlc3Npb25zLg0KNS4gKipCYXRjaCBzaXplKiog4oaSIFdoZXRoZXIgeW91IHNob290IG9uZSBiYWxsIGF0IGEgdGltZSBvciBtdWx0aXBsZS4NCg0KKipXaXRob3V0IGVub3VnaCBwcmFjdGljZSAoZXBvY2hzKSwgeW91IHdvbuKAmXQgaW1wcm92ZS4gQnV0IGlmIHlvdSBrZWVwIHByYWN0aWNpbmcgYWZ0ZXIgeW91J3JlIHBlcmZlY3QsIHlvdSBqdXN0IHdhc3RlIGVuZXJneSAob3ZlcmZpdHRpbmcpLioqDQoNCi0tLQ0KDQojIyAqKjIzLiBXaGF0IGlzIE92ZXJmaXR0aW5nPyoqDQpPdmVyZml0dGluZyBoYXBwZW5zIHdoZW4gYSBuZXVyYWwgbmV0d29yayAqKm1lbW9yaXplcyoqIHRyYWluaW5nIGRhdGEgaW5zdGVhZCBvZiBsZWFybmluZyAqKnBhdHRlcm5zKiouIFRoZSBtb2RlbCBwZXJmb3JtcyB3ZWxsIG9uIHRyYWluaW5nIGRhdGEgYnV0ICoqcG9vcmx5IG9uIG5ldyAodGVzdCkgZGF0YSoqLg0KDQojIyMgKipTaWducyBvZiBPdmVyZml0dGluZyoqDQotICoqSGlnaCB0cmFpbmluZyBhY2N1cmFjeSwgYnV0IGxvdyB0ZXN0IGFjY3VyYWN5KiouDQotICoqTG9zcyBkZWNyZWFzZXMgb24gdHJhaW5pbmcgYnV0IHJlbWFpbnMgaGlnaCBvbiB0ZXN0IGRhdGEqKi4NCi0gKipNb2RlbCBwcmVkaWN0cyB0cmFpbmluZyBleGFtcGxlcyBjb3JyZWN0bHkgYnV0IGZhaWxzIG9uIHVuc2VlbiBkYXRhKiouDQoNCi0tLQ0KDQojIyAqKjI0LiBCaWFzLVZhcmlhbmNlIFRyYWRlb2ZmKioNCk92ZXJmaXR0aW5nIGlzIHBhcnQgb2YgdGhlICoqYmlhcy12YXJpYW5jZSB0cmFkZW9mZioqIGluIG1hY2hpbmUgbGVhcm5pbmcuDQoNCnwgKipDb25jZXB0KiogfCAqKkRlc2NyaXB0aW9uKiogfCAqKkV4YW1wbGUqKiB8DQp8LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tfA0KfCAqKkhpZ2ggQmlhcyAoVW5kZXJmaXR0aW5nKSoqIHwgTW9kZWwgaXMgdG9vIHNpbXBsZSwgZmFpbHMgdG8gbGVhcm4gcGF0dGVybnMuIHwgQSBzdHVkZW50IHdobyBvbmx5IG1lbW9yaXplcyAyKzI9NCBidXQgY2FuJ3Qgc29sdmUgNSszLiB8DQp8ICoqSGlnaCBWYXJpYW5jZSAoT3ZlcmZpdHRpbmcpKiogfCBNb2RlbCBpcyB0b28gY29tcGxleCwgbWVtb3JpemVzIGRhdGEgaW5zdGVhZCBvZiBnZW5lcmFsaXppbmcuIHwgQSBzdHVkZW50IHdobyBtZW1vcml6ZXMgZXZlcnkgcG9zc2libGUgcXVlc3Rpb24gYnV0IHN0cnVnZ2xlcyB3aXRoIG5ldyBvbmVzLiB8DQoNClRoZSBnb2FsIGlzIHRvICoqYmFsYW5jZSBiaWFzIGFuZCB2YXJpYW5jZSoqLg0KDQotLS0NCg0KIyMgKioyNS4gUmVndWxhcml6YXRpb24gVGVjaG5pcXVlcyoqDQpSZWd1bGFyaXphdGlvbiBwcmV2ZW50cyBvdmVyZml0dGluZyBieSAqKnNpbXBsaWZ5aW5nKiogdGhlIG1vZGVsIGFuZCByZWR1Y2luZyB1bm5lY2Vzc2FyeSBjb21wbGV4aXR5Lg0KDQojIyMgKioxLiBMMSBhbmQgTDIgUmVndWxhcml6YXRpb24qKg0KLSAqKkwxIFJlZ3VsYXJpemF0aW9uIChMYXNzbyBSZWdyZXNzaW9uKSoqOiBBZGRzIGEgKipwZW5hbHR5IGZvciBsYXJnZSB3ZWlnaHRzKiosIGZvcmNpbmcgc29tZSB3ZWlnaHRzIHRvIGJlY29tZSAqKnplcm8qKi4NCi0gKipMMiBSZWd1bGFyaXphdGlvbiAoUmlkZ2UgUmVncmVzc2lvbikqKjogQWRkcyBhICoqcGVuYWx0eSBmb3IgbGFyZ2Ugd2VpZ2h0cyoqLCByZWR1Y2luZyB0aGVtIGJ1dCBrZWVwaW5nIGFsbC4NCg0KXFsNCkwxOiBccXVhZCBMb3NzID0gXHN1bSAoeV9pIC0gXGhhdHt5fV9pKV4yICsgXGxhbWJkYSBcc3VtIHx3X2l8DQpcXQ0KDQpcWw0KTDI6IFxxdWFkIExvc3MgPSBcc3VtICh5X2kgLSBcaGF0e3l9X2kpXjIgKyBcbGFtYmRhIFxzdW0gd19pXjINClxdDQoNCioqUHl0aG9uIEltcGxlbWVudGF0aW9uOioqDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5tb2RlbHMgaW1wb3J0IFNlcXVlbnRpYWwNCmZyb20gdGVuc29yZmxvdy5rZXJhcy5sYXllcnMgaW1wb3J0IERlbnNlDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMucmVndWxhcml6ZXJzIGltcG9ydCBsMSwgbDINCg0KbW9kZWwgPSBTZXF1ZW50aWFsKFsNCiAgICBEZW5zZSg2NCwgYWN0aXZhdGlvbj0ncmVsdScsIGtlcm5lbF9yZWd1bGFyaXplcj1sMigwLjAxKSksICAjIEwyIFJlZ3VsYXJpemF0aW9uDQogICAgRGVuc2UoMzIsIGFjdGl2YXRpb249J3JlbHUnLCBrZXJuZWxfcmVndWxhcml6ZXI9bDEoMC4wMSkpLCAgIyBMMSBSZWd1bGFyaXphdGlvbg0KICAgIERlbnNlKDEsIGFjdGl2YXRpb249J3NpZ21vaWQnKQ0KXSkNCmBgYA0KDQotLS0NCg0KIyMjICoqMi4gRHJvcG91dCBSZWd1bGFyaXphdGlvbioqDQpEcm9wb3V0IHJhbmRvbWx5ICoqdHVybnMgb2ZmIG5ldXJvbnMqKiBkdXJpbmcgdHJhaW5pbmcgdG8gcHJldmVudCBvdmVyLXJlbGlhbmNlIG9uIHNwZWNpZmljIGNvbm5lY3Rpb25zLg0KDQp8ICoqRHJvcG91dCBSYXRlKiogfCAqKkVmZmVjdCoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18DQp8ICoqMCUgKE5vIERyb3BvdXQpKiogfCBNYXkgbGVhZCB0byBvdmVyZml0dGluZyB8DQp8ICoqMjAlLTUwJSoqIHwgSGVscHMgcHJldmVudCBvdmVyZml0dGluZyB8DQp8ICoqODAlLTkwJSoqIHwgTW9kZWwgbWF5IHVuZGVycGVyZm9ybSAodG9vIG11Y2ggZHJvcG91dCkgfA0KDQoqKlB5dGhvbiBJbXBsZW1lbnRhdGlvbjoqKg0KYGBgcHl0aG9uDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMubGF5ZXJzIGltcG9ydCBEcm9wb3V0DQoNCm1vZGVsID0gU2VxdWVudGlhbChbDQogICAgRGVuc2UoNjQsIGFjdGl2YXRpb249J3JlbHUnKSwNCiAgICBEcm9wb3V0KDAuMyksICAjIDMwJSBvZiBuZXVyb25zIGFyZSBkcm9wcGVkDQogICAgRGVuc2UoMzIsIGFjdGl2YXRpb249J3JlbHUnKSwNCiAgICBEcm9wb3V0KDAuMyksDQogICAgRGVuc2UoMSwgYWN0aXZhdGlvbj0nc2lnbW9pZCcpDQpdKQ0KYGBgDQoNCi0tLQ0KDQojIyMgKiozLiBFYXJseSBTdG9wcGluZyoqDQpTdG9wcyB0cmFpbmluZyAqKndoZW4gcGVyZm9ybWFuY2Ugc3RvcHMgaW1wcm92aW5nKiogdG8gcHJldmVudCBvdmVyZml0dGluZy4NCg0KKipQeXRob24gSW1wbGVtZW50YXRpb246KioNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmNhbGxiYWNrcyBpbXBvcnQgRWFybHlTdG9wcGluZw0KDQplYXJseV9zdG9wID0gRWFybHlTdG9wcGluZyhtb25pdG9yPSd2YWxfbG9zcycsIHBhdGllbmNlPTUsIHJlc3RvcmVfYmVzdF93ZWlnaHRzPVRydWUpDQoNCm1vZGVsLmZpdChYX3RyYWluLCB5X3RyYWluLCB2YWxpZGF0aW9uX2RhdGE9KFhfdmFsLCB5X3ZhbCksIGVwb2Nocz0xMDAsIGNhbGxiYWNrcz1bZWFybHlfc3RvcF0pDQpgYGANCg0KLS0tDQoNCiMjIyAqKjQuIERhdGEgQXVnbWVudGF0aW9uKioNCkluc3RlYWQgb2YgbW9kaWZ5aW5nIHRoZSBtb2RlbCwgKip3ZSBtb2RpZnkgdGhlIGRhdGEqKiB0byBwcmV2ZW50IG92ZXJmaXR0aW5nLg0KDQpGb3IgKippbWFnZXMqKiwgYXVnbWVudGF0aW9uIGluY2x1ZGVzOg0KLSAqKkZsaXBwaW5nKioNCi0gKipSb3RhdGluZyoqDQotICoqQWRkaW5nIG5vaXNlKioNCg0KKipQeXRob24gSW1wbGVtZW50YXRpb246KioNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLnByZXByb2Nlc3NpbmcuaW1hZ2UgaW1wb3J0IEltYWdlRGF0YUdlbmVyYXRvcg0KDQpkYXRhZ2VuID0gSW1hZ2VEYXRhR2VuZXJhdG9yKHJvdGF0aW9uX3JhbmdlPTIwLCBob3Jpem9udGFsX2ZsaXA9VHJ1ZSkNCnRyYWluX2dlbmVyYXRvciA9IGRhdGFnZW4uZmxvdyhYX3RyYWluLCB5X3RyYWluLCBiYXRjaF9zaXplPTMyKQ0KYGBgDQoNCi0tLQ0KDQojIyAqKjI2LiBFdmFsdWF0aW5nIE92ZXJmaXR0aW5nKioNClVzZSAqKmxlYXJuaW5nIGN1cnZlcyoqIHRvIGRpYWdub3NlIG92ZXJmaXR0aW5nLg0KDQojIyMgKioxLiBUcmFpbmluZyB2cy4gVmFsaWRhdGlvbiBMb3NzKioNCi0gKipPdmVyZml0dGluZzoqKiBUcmFpbmluZyBsb3NzIGRlY3JlYXNlcywgdmFsaWRhdGlvbiBsb3NzIGluY3JlYXNlcy4NCi0gKipHb29kIEZpdDoqKiBCb3RoIGxvc3NlcyBkZWNyZWFzZSBhbmQgc3RhYmlsaXplLg0KLSAqKlVuZGVyZml0dGluZzoqKiBCb3RoIGxvc3NlcyByZW1haW4gaGlnaC4NCg0KKipQeXRob24gSW1wbGVtZW50YXRpb24gZm9yIFZpc3VhbGl6YXRpb246KioNCmBgYHB5dGhvbg0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQpwbHQucGxvdChoaXN0b3J5Lmhpc3RvcnlbJ2xvc3MnXSwgbGFiZWw9J1RyYWluaW5nIExvc3MnKQ0KcGx0LnBsb3QoaGlzdG9yeS5oaXN0b3J5Wyd2YWxfbG9zcyddLCBsYWJlbD0nVmFsaWRhdGlvbiBMb3NzJykNCnBsdC5sZWdlbmQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCi0tLQ0KDQojIyAqKkFuYWxvZ3k6IE92ZXJmaXR0aW5nIGlzIExpa2UgTWVtb3JpemluZyBFeGFtIEFuc3dlcnMqKg0KLSBJbWFnaW5lIHlvdeKAmXJlIHN0dWR5aW5nIGZvciBhIHRlc3QuDQotICoqT3ZlcmZpdHRpbmc6KiogWW91IG1lbW9yaXplIGV4YWN0IHF1ZXN0aW9ucyBhbmQgYW5zd2Vycy4gQnV0IGlmIHRoZSB0ZWFjaGVyIGNoYW5nZXMgdGhlIHF1ZXN0aW9uIHNsaWdodGx5LCB5b3UgZ2V0IGNvbmZ1c2VkLg0KLSAqKkdvb2QgTGVhcm5pbmc6KiogWW91IHVuZGVyc3RhbmQgY29uY2VwdHMgYW5kIGNhbiBhcHBseSB0aGVtIHRvIGRpZmZlcmVudCBxdWVzdGlvbnMuDQotICoqVW5kZXJmaXR0aW5nOioqIFlvdSBkb27igJl0IHN0dWR5IGVub3VnaCBhbmQgc3RydWdnbGUgZXZlbiB3aXRoIHNpbXBsZSBxdWVzdGlvbnMuDQoNCi0tLQ0KDQojIyMgKipLZXkgVGFrZWF3YXlzKioNCuKchSBSZWd1bGFyaXphdGlvbiAqKnJlZHVjZXMgb3ZlcmZpdHRpbmcqKiBieSBzaW1wbGlmeWluZyB0aGUgbW9kZWwuICANCuKchSBEcm9wb3V0ICoqcHJldmVudHMgcmVsaWFuY2Ugb24gc3BlY2lmaWMgbmV1cm9ucyoqLiAgDQrinIUgRWFybHkgc3RvcHBpbmcgKipwcmV2ZW50cyB1bm5lY2Vzc2FyeSB0cmFpbmluZyoqLiAgDQrinIUgRGF0YSBhdWdtZW50YXRpb24gKippbmNyZWFzZXMgZGF0YSB2YXJpYWJpbGl0eSoqLiAgDQoNCi0tLQ0KDQoNCjcuDQojIyAqKjI3LiBXaGF0IGFyZSBBY3RpdmF0aW9uIEZ1bmN0aW9ucz8qKg0KQWN0aXZhdGlvbiBmdW5jdGlvbnMgaGVscCBhIG5ldXJhbCBuZXR3b3JrICoqZGVjaWRlKiogd2hldGhlciBhIG5ldXJvbiBzaG91bGQgImZpcmUiIG9yIHN0YXkgaW5hY3RpdmUuIFRoZXkgaW50cm9kdWNlICoqbm9uLWxpbmVhcml0eSoqLCBhbGxvd2luZyBuZXVyYWwgbmV0d29ya3MgdG8gbGVhcm4gY29tcGxleCBwYXR0ZXJucy4NCg0KIyMjICoqV2h5IERvIFdlIE5lZWQgQWN0aXZhdGlvbiBGdW5jdGlvbnM/KioNCklmIHdlIGRvbuKAmXQgdXNlIGFjdGl2YXRpb24gZnVuY3Rpb25zLCBuZXVyYWwgbmV0d29ya3MgYmVoYXZlIGxpa2UgKipsaW5lYXIgcmVncmVzc2lvbnMqKiwgbWFraW5nIHRoZW0gdW5hYmxlIHRvIG1vZGVsIGNvbXBsZXggZGF0YS4NCg0KLS0tDQoNCiMjICoqMjguIFR5cGVzIG9mIEFjdGl2YXRpb24gRnVuY3Rpb25zKioNCkVhY2ggYWN0aXZhdGlvbiBmdW5jdGlvbiBoYXMgKipkaWZmZXJlbnQgcHJvcGVydGllcyoqLCBhbmQgY2hvb3NpbmcgdGhlIHJpZ2h0IG9uZSBpbXBhY3RzIGEgbW9kZWzigJlzIHBlcmZvcm1hbmNlLg0KDQp8ICoqQWN0aXZhdGlvbiBGdW5jdGlvbioqIHwgKipGb3JtdWxhKiogfCAqKlVzZSBDYXNlKiogfA0KfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS18DQp8ICoqU3RlcCBGdW5jdGlvbioqIHwgXCggZih4KSA9IDEgXCkgaWYgXCggeCA+IDAgXCksIGVsc2UgXCggMCBcKSB8IFJhcmVseSB1c2VkICh0b28gc2ltcGxlKSB8DQp8ICoqU2lnbW9pZCoqIHwgXCggZih4KSA9IFxmcmFjezF9ezEgKyBlXnsteH19IFwpIHwgT3V0cHV0IHByb2JhYmlsaXRpZXMgKGxhc3QgbGF5ZXIpIHwNCnwgKipUYW5oKiogfCBcKCBmKHgpID0gXGZyYWN7ZV54IC0gZV57LXh9fXtlXnggKyBlXnsteH19IFwpIHwgQ2VudGVyZWQgYXJvdW5kIHplcm8gfA0KfCAqKlJlTFUqKiB8IFwoIGYoeCkgPSBcbWF4KDAsIHgpIFwpIHwgRGVmYXVsdCBmb3IgaGlkZGVuIGxheWVycyB8DQp8ICoqTGVha3kgUmVMVSoqIHwgXCggZih4KSA9IHggXCkgaWYgXCggeCA+IDAgXCksIGVsc2UgXCggMC4wMXggXCkgfCBQcmV2ZW50cyBkeWluZyBuZXVyb25zIHwNCnwgKipTb2Z0bWF4KiogfCBcKCBmKHhfaSkgPSBcZnJhY3tlXnt4X2l9fXtcc3VtIGVee3hfan19IFwpIHwgTXVsdGljbGFzcyBjbGFzc2lmaWNhdGlvbiB8DQoNCi0tLQ0KDQojIyAqKjI5LiBTdGVwIEZ1bmN0aW9uIChCaW5hcnkgVGhyZXNob2xkaW5nKSoqDQpBIG5ldXJvbiBmaXJlcyAqKm9ubHkgaWYgaW5wdXQgaXMgYWJvdmUgYSB0aHJlc2hvbGQqKi4NCg0KXFsNCmYoeCkgPQ0KXGJlZ2lue2Nhc2VzfSANCiAgMSwgJiB4ID4gMCBcXA0KICAwLCAmIHggXGxlcSAwDQpcZW5ke2Nhc2VzfQ0KXF0NCg0KKipQcm9ibGVtOioqIFN0ZXAgZnVuY3Rpb25zICoqYXJlbuKAmXQgZGlmZmVyZW50aWFibGUqKiwgbWFraW5nIGxlYXJuaW5nIGRpZmZpY3VsdC4NCg0KLS0tDQoNCiMjICoqMzAuIFNpZ21vaWQgRnVuY3Rpb24gKFMtU2hhcGVkIEN1cnZlKSoqDQpUaGUgKipzaWdtb2lkIGZ1bmN0aW9uKiogc3F1YXNoZXMgaW5wdXRzIGJldHdlZW4gKiowIGFuZCAxKiosIG1ha2luZyBpdCB1c2VmdWwgZm9yICoqcHJvYmFiaWxpdGllcyoqLg0KDQpcWw0KZih4KSA9IFxmcmFjezF9ezEgKyBlXnsteH19DQpcXQ0KDQojIyMgKipQcm9zOioqDQrinJTvuI8gVXNlZCBpbiBiaW5hcnkgY2xhc3NpZmljYXRpb24gKGxhc3QgbGF5ZXIpLiAgDQrinJTvuI8gT3V0cHV0cyBhIHByb2JhYmlsaXR5IGJldHdlZW4gKiowIGFuZCAxKiouDQoNCiMjIyAqKkNvbnM6KioNCuKdjCAqKlZhbmlzaGluZyBHcmFkaWVudCBQcm9ibGVtKio6IFNtYWxsIGdyYWRpZW50cyBzbG93IGxlYXJuaW5nIGluIGRlZXAgbmV0d29ya3MuICANCuKdjCAqKk5vdCBaZXJvLUNlbnRlcmVkKio6IE91dHB1dHMgYWx3YXlzIHBvc2l0aXZlLCBtYWtpbmcgb3B0aW1pemF0aW9uIGhhcmRlci4NCg0KKipQeXRob24gRXhhbXBsZToqKg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KZGVmIHNpZ21vaWQoeCk6DQogICAgcmV0dXJuIDEgLyAoMSArIG5wLmV4cCgteCkpDQoNCnggPSBucC5saW5zcGFjZSgtMTAsIDEwLCAxMDApDQpwbHQucGxvdCh4LCBzaWdtb2lkKHgpKQ0KcGx0LnRpdGxlKCJTaWdtb2lkIEZ1bmN0aW9uIikNCnBsdC5zaG93KCkNCmBgYA0KDQotLS0NCg0KIyMgKiozMS4gVGFuaCBGdW5jdGlvbiAoQ2VudGVyZWQgU2lnbW9pZCkqKg0KTGlrZSAqKnNpZ21vaWQqKiwgYnV0IHNxdWFzaGVzIHZhbHVlcyBiZXR3ZWVuICoqLTEgYW5kIDEqKi4NCg0KXFsNCmYoeCkgPSBcZnJhY3tlXnggLSBlXnsteH19e2VeeCArIGVeey14fX0NClxdDQoNCiMjIyAqKlByb3M6KioNCuKclO+4jyAqKlplcm8tQ2VudGVyZWQqKjogSGVscHMgb3B0aW1pemF0aW9uLiAgDQrinJTvuI8gU3Ryb25nZXIgZ3JhZGllbnRzIHRoYW4gc2lnbW9pZC4NCg0KIyMjICoqQ29uczoqKg0K4p2MIFN0aWxsIHN1ZmZlcnMgZnJvbSB0aGUgKip2YW5pc2hpbmcgZ3JhZGllbnQgcHJvYmxlbSoqLg0KDQotLS0NCg0KIyMgKiozMi4gUmVMVSAoUmVjdGlmaWVkIExpbmVhciBVbml0KSoqDQpUaGUgKiptb3N0IGNvbW1vbmx5IHVzZWQqKiBhY3RpdmF0aW9uIGZ1bmN0aW9uIHRvZGF5Lg0KDQpcWw0KZih4KSA9IFxtYXgoMCwgeCkNClxdDQoNCiMjIyAqKlByb3M6KioNCuKclO+4jyAqKkNvbXB1dGF0aW9uYWxseSBlZmZpY2llbnQqKiAoZmFzdCB0byBjb21wdXRlKS4gIA0K4pyU77iPICoqU29sdmVzIHZhbmlzaGluZyBncmFkaWVudCBwcm9ibGVtKiogKGZvciBwb3NpdGl2ZSB2YWx1ZXMpLg0KDQojIyMgKipDb25zOioqDQrinYwgKipEeWluZyBOZXVyb25zIFByb2JsZW0qKjogSWYgbWFueSBuZXVyb25zIG91dHB1dCAqKjAqKiwgdGhleSBzdG9wIGxlYXJuaW5nLg0KDQoqKlB5dGhvbiBFeGFtcGxlOioqDQpgYGBweXRob24NCmRlZiByZWx1KHgpOg0KICAgIHJldHVybiBucC5tYXhpbXVtKDAsIHgpDQoNCnBsdC5wbG90KHgsIHJlbHUoeCkpDQpwbHQudGl0bGUoIlJlTFUgQWN0aXZhdGlvbiBGdW5jdGlvbiIpDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tDQoNCiMjICoqMzMuIExlYWt5IFJlTFUgKEZpeCBmb3IgRHlpbmcgTmV1cm9ucykqKg0KRml4ZXMgUmVMVeKAmXMgcHJvYmxlbSBieSBhbGxvd2luZyBhICoqc21hbGwgc2xvcGUqKiBmb3IgbmVnYXRpdmUgdmFsdWVzLg0KDQpcWw0KZih4KSA9DQpcYmVnaW57Y2FzZXN9IA0KICB4LCAmIHggPiAwIFxcDQogIDAuMDF4LCAmIHggXGxlcSAwDQpcZW5ke2Nhc2VzfQ0KXF0NCg0K4pyU77iPIFByZXZlbnRzICoqZGVhZCBuZXVyb25zKiogYnkgZ2l2aW5nIHRoZW0gYSBzbWFsbCBncmFkaWVudC4gIA0K4pyU77iPIFdvcmtzIGJldHRlciB0aGFuIFJlTFUgaW4gc29tZSBjYXNlcy4NCg0KKipQeXRob24gRXhhbXBsZToqKg0KYGBgcHl0aG9uDQpkZWYgbGVha3lfcmVsdSh4LCBhbHBoYT0wLjAxKToNCiAgICByZXR1cm4gbnAud2hlcmUoeCA+IDAsIHgsIGFscGhhICogeCkNCg0KcGx0LnBsb3QoeCwgbGVha3lfcmVsdSh4KSkNCnBsdC50aXRsZSgiTGVha3kgUmVMVSBBY3RpdmF0aW9uIEZ1bmN0aW9uIikNCnBsdC5zaG93KCkNCmBgYA0KDQotLS0NCg0KIyMgKiozNC4gU29mdG1heCAoRm9yIE11bHRpY2xhc3MgQ2xhc3NpZmljYXRpb24pKioNClNvZnRtYXggY29udmVydHMgc2NvcmVzIGludG8gKipwcm9iYWJpbGl0aWVzIHRoYXQgc3VtIHRvIDEqKi4NCg0KXFsNCmYoeF9pKSA9IFxmcmFje2Vee3hfaX19e1xzdW0gZV57eF9qfX0NClxdDQoNCuKclO+4jyBVc2VkIGluIHRoZSAqKmxhc3QgbGF5ZXIgZm9yIG11bHRpY2xhc3MgcHJvYmxlbXMqKi4gIA0K4pyU77iPIE91dHB1dHMgcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9uIG92ZXIgKiptdWx0aXBsZSBjbGFzc2VzKiouDQoNCi0tLQ0KDQojIyAqKjM1LiBDaG9vc2luZyB0aGUgUmlnaHQgQWN0aXZhdGlvbiBGdW5jdGlvbioqDQp8ICoqVGFzayoqIHwgKipCZXN0IEFjdGl2YXRpb24qKiB8DQp8LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS18DQp8ICoqQmluYXJ5IENsYXNzaWZpY2F0aW9uKiogfCBTaWdtb2lkIChsYXN0IGxheWVyKSB8DQp8ICoqTXVsdGljbGFzcyBDbGFzc2lmaWNhdGlvbioqIHwgU29mdG1heCAobGFzdCBsYXllcikgfA0KfCAqKkhpZGRlbiBMYXllcnMgKEdlbmVyYWwpKiogfCBSZUxVIChkZWZhdWx0KSwgTGVha3kgUmVMVSAoaWYgUmVMVSBmYWlscykgfA0KfCAqKlJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpKiogfCBUYW5oIG9yIFJlTFUgfA0KDQotLS0NCg0KIyMgKipBbmFsb2d5OiBBY3RpdmF0aW9uIEZ1bmN0aW9ucyBBcmUgTGlrZSBEZWNpc2lvbi1NYWtpbmcgaW4gUmVhbCBMaWZlKioNClRoaW5rIG9mIGFjdGl2YXRpb24gZnVuY3Rpb25zIGxpa2UgKip0aHJlc2hvbGRzKiogZm9yIG1ha2luZyBkZWNpc2lvbnM6DQotICoqU3RlcCBmdW5jdGlvbjoqKiBMaWtlIGEgbGlnaHQgc3dpdGNoIChvbi9vZmYpLg0KLSAqKlNpZ21vaWQ6KiogTGlrZSBncmFkaW5nIGEgc3R1ZGVudCAocGFzcy9mYWlsIHByb2JhYmlsaXR5KS4NCi0gKipSZUxVOioqIExpa2UgaGlyaW5nIGFuIGVtcGxveWVlIChjb25zaWRlciAqKm9ubHkgcG9zaXRpdmUqKiBxdWFsaWZpY2F0aW9ucykuDQotICoqTGVha3kgUmVMVToqKiBMaWtlIGdpdmluZyBwYXJ0aWFsIGNyZWRpdCAoZXZlbiBzbWFsbCBlZmZvcnRzIGNvdW50KS4NCi0gKipTb2Z0bWF4OioqIExpa2UgcGlja2luZyBhIHJlc3RhdXJhbnQgKHByb2JhYmlsaXR5IG9mIGNob29zaW5nIGVhY2gpLg0KDQotLS0NCg0KIyMjICoqS2V5IFRha2Vhd2F5cyoqDQrinIUgKipBY3RpdmF0aW9uIGZ1bmN0aW9ucyBhbGxvdyBuZXR3b3JrcyB0byBsZWFybiBjb21wbGV4IHBhdHRlcm5zKiouICANCuKchSAqKlJlTFUgaXMgdGhlIGRlZmF1bHQgY2hvaWNlKiosIGJ1dCAqKkxlYWt5IFJlTFUgY2FuIHByZXZlbnQgZHlpbmcgbmV1cm9ucyoqLiAgDQrinIUgKipTaWdtb2lkIGFuZCBTb2Z0bWF4IGFyZSB1c2VkIGZvciBvdXRwdXQgbGF5ZXJzKiouICANCuKchSAqKkNob29zaW5nIHRoZSByaWdodCBmdW5jdGlvbiBpbXBhY3RzIHNwZWVkIGFuZCBhY2N1cmFjeSoqLiAgDQoNCi0tLQ0KDQoNCg0KDQojIyAqKjM2LiBXaGF0IGlzIEJhY2twcm9wYWdhdGlvbj8qKg0KQmFja3Byb3BhZ2F0aW9uIGlzIHRoZSAqKmxlYXJuaW5nIGFsZ29yaXRobSoqIHRoYXQgYWxsb3dzIG5ldXJhbCBuZXR3b3JrcyB0byAqKmFkanVzdCB0aGVpciB3ZWlnaHRzKiogYW5kIGJlY29tZSBiZXR0ZXIgYXQgbWFraW5nIHByZWRpY3Rpb25zLg0KDQojIyMgKipXaHkgRG8gV2UgTmVlZCBCYWNrcHJvcGFnYXRpb24/KioNCi0gV2hlbiB0cmFpbmluZyBhIG5ldXJhbCBuZXR3b3JrLCB3ZSAqKnN0YXJ0IHdpdGggcmFuZG9tIHdlaWdodHMqKi4NCi0gVGhlIG5ldHdvcmsgbWFrZXMgcHJlZGljdGlvbnMsIGJ1dCBpbml0aWFsbHksIHRoZXnigJlyZSAqKm5vdCBhY2N1cmF0ZSoqLg0KLSBXZSBuZWVkIGEgd2F5IHRvICoqbWVhc3VyZSBlcnJvcnMgYW5kIGFkanVzdCB0aGUgd2VpZ2h0cyoq4oCUdGhpcyBpcyB3aGF0ICoqYmFja3Byb3BhZ2F0aW9uKiogZG9lcy4NCg0KLS0tDQoNCiMjICoqMzcuIFRoZSBTdGVwcyBvZiBCYWNrcHJvcGFnYXRpb24qKg0KQmFja3Byb3BhZ2F0aW9uIGlzIGFuICoqb3B0aW1pemF0aW9uIHRlY2huaXF1ZSoqIHRoYXQgbWluaW1pemVzIHRoZSBlcnJvciBiZXR3ZWVuIHByZWRpY3RlZCBhbmQgYWN0dWFsIHZhbHVlcy4NCg0KMS4gKipGb3J3YXJkIFByb3BhZ2F0aW9uOioqDQogICAtIElucHV0cyBmbG93ICoqdGhyb3VnaCB0aGUgbmV0d29yayoqLg0KICAgLSBQcmVkaWN0aW9ucyBhcmUgbWFkZSB1c2luZyAqKmN1cnJlbnQgd2VpZ2h0cyoqLg0KICAgDQoyLiAqKkNhbGN1bGF0ZSBMb3NzOioqDQogICAtIENvbXBhcmUgcHJlZGljdGlvbnMgdG8gYWN0dWFsIHZhbHVlcy4NCiAgIC0gVXNlIGEgKipsb3NzIGZ1bmN0aW9uKiogdG8gbWVhc3VyZSBlcnJvci4NCg0KMy4gKipCYWNrd2FyZCBQcm9wYWdhdGlvbjoqKg0KICAgLSBDb21wdXRlIGhvdyBtdWNoIGVhY2ggd2VpZ2h0IGNvbnRyaWJ1dGVkIHRvIHRoZSBlcnJvci4NCiAgIC0gQWRqdXN0IHRoZSB3ZWlnaHRzIHVzaW5nICoqR3JhZGllbnQgRGVzY2VudCoqLg0KDQo0LiAqKlJlcGVhdCBVbnRpbCBDb252ZXJnZW5jZToqKg0KICAgLSBUaGlzIHByb2Nlc3MgY29udGludWVzIHVudGlsIHRoZSAqKmVycm9yIGlzIG1pbmltaXplZCoqLg0KDQotLS0NCg0KIyMgKiozOC4gVGhlIE1hdGggQmVoaW5kIEJhY2twcm9wYWdhdGlvbioqDQpCYWNrcHJvcGFnYXRpb24gdXNlcyAqKmNhbGN1bHVzIGFuZCBjaGFpbiBydWxlIGRpZmZlcmVudGlhdGlvbioqIHRvIHVwZGF0ZSB3ZWlnaHRzLg0KDQojIyMgKipTdGVwIDE6IENvbXB1dGUgdGhlIEVycm9yKioNCldlIGNhbGN1bGF0ZSB0aGUgKipsb3NzKiogdXNpbmcgYSBmdW5jdGlvbiBsaWtlICoqTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpKio6DQoNClxbDQpMID0gXGZyYWN7MX17Tn0gXHN1bV97aT0xfV57Tn0gKHlfaSAtIFxoYXR7eX1faSleMg0KXF0NCg0Kd2hlcmU6DQotIFwoIHlfaSBcKSBpcyB0aGUgYWN0dWFsIHZhbHVlLg0KLSBcKCBcaGF0e3l9X2kgXCkgaXMgdGhlIHByZWRpY3RlZCB2YWx1ZS4NCi0gXCggTiBcKSBpcyB0aGUgbnVtYmVyIG9mIGV4YW1wbGVzLg0KDQotLS0NCg0KIyMjICoqU3RlcCAyOiBDb21wdXRlIHRoZSBHcmFkaWVudCAoUmF0ZSBvZiBDaGFuZ2UpKioNClRvIHVwZGF0ZSB3ZWlnaHRzLCB3ZSB0YWtlIHRoZSAqKmRlcml2YXRpdmUgb2YgdGhlIGxvc3MgZnVuY3Rpb24qKiB3aXRoIHJlc3BlY3QgdG8gZWFjaCB3ZWlnaHQ6DQoNClxbDQpcZnJhY3tccGFydGlhbCBMfXtccGFydGlhbCB3fSA9IFxmcmFje1xwYXJ0aWFsIEx9e1xwYXJ0aWFsIFxoYXR7eX19IFx0aW1lcyBcZnJhY3tccGFydGlhbCBcaGF0e3l9fXtccGFydGlhbCB3fQ0KXF0NCg0KVGhpcyB0ZWxscyB1cyAqKmhvdyBtdWNoIGVhY2ggd2VpZ2h0IGNvbnRyaWJ1dGVzIHRvIHRoZSBlcnJvcioqLg0KDQotLS0NCg0KIyMjICoqU3RlcCAzOiBVcGRhdGUgV2VpZ2h0cyoqDQpXZWlnaHRzIGFyZSB1cGRhdGVkIHVzaW5nICoqR3JhZGllbnQgRGVzY2VudCoqOg0KDQpcWw0KdyA9IHcgLSBcYWxwaGEgXGZyYWN7XHBhcnRpYWwgTH17XHBhcnRpYWwgd30NClxdDQoNCndoZXJlOg0KLSBcKCBcYWxwaGEgXCkgaXMgdGhlICoqbGVhcm5pbmcgcmF0ZSoqLg0KLSBcKCBcZnJhY3tccGFydGlhbCBMfXtccGFydGlhbCB3fSBcKSBpcyB0aGUgZ3JhZGllbnQgKGFtb3VudCBvZiBjaGFuZ2UgbmVlZGVkKS4NCg0KLS0tDQoNCiMjICoqMzkuIFVuZGVyc3RhbmRpbmcgR3JhZGllbnQgRGVzY2VudCoqDQpHcmFkaWVudCBEZXNjZW50IGhlbHBzICoqbWluaW1pemUgdGhlIGxvc3MgZnVuY3Rpb24qKiBieSBhZGp1c3Rpbmcgd2VpZ2h0cyBzdGVwIGJ5IHN0ZXAuDQoNCiMjIyAqKlR5cGVzIG9mIEdyYWRpZW50IERlc2NlbnQqKg0KfCBUeXBlIHwgRGVzY3JpcHRpb24gfA0KfC0tLS0tLXwtLS0tLS0tLS0tLS0tfA0KfCAqKkJhdGNoIEdyYWRpZW50IERlc2NlbnQqKiB8IFVzZXMgKiphbGwgZGF0YSoqIGF0IG9uY2UgKHNsb3cgZm9yIGxhcmdlIGRhdGFzZXRzKS4gfA0KfCAqKlN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCAoU0dEKSoqIHwgVXNlcyAqKm9uZSBzYW1wbGUgYXQgYSB0aW1lKiogKGZhc3RlciwgYnV0IG5vaXNpZXIpLiB8DQp8ICoqTWluaS1CYXRjaCBHcmFkaWVudCBEZXNjZW50KiogfCBVc2VzICoqc21hbGwgZ3JvdXBzIG9mIHNhbXBsZXMqKiAoYmFsYW5jZWQgYXBwcm9hY2gpLiB8DQoNCiMjIyAqKlB5dGhvbiBFeGFtcGxlOiBHcmFkaWVudCBEZXNjZW50KioNCmBgYHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQoNCiMgU2ltcGxlIEdyYWRpZW50IERlc2NlbnQNCmRlZiBncmFkaWVudF9kZXNjZW50KHcsIGxlYXJuaW5nX3JhdGUsIGdyYWRpZW50KToNCiAgICByZXR1cm4gdyAtIGxlYXJuaW5nX3JhdGUgKiBncmFkaWVudA0KDQojIEV4YW1wbGUNCncgPSAwLjUgICMgSW5pdGlhbCB3ZWlnaHQNCmxlYXJuaW5nX3JhdGUgPSAwLjENCmdyYWRpZW50ID0gMi4wICAjIEV4YW1wbGUgZ3JhZGllbnQNCg0KbmV3X3cgPSBncmFkaWVudF9kZXNjZW50KHcsIGxlYXJuaW5nX3JhdGUsIGdyYWRpZW50KQ0KcHJpbnQoIlVwZGF0ZWQgV2VpZ2h0OiIsIG5ld193KQ0KYGBgDQoNCi0tLQ0KDQojIyAqKjQwLiBUaGUgQ2hhaW4gUnVsZSBpbiBCYWNrcHJvcGFnYXRpb24qKg0KU2luY2UgbmV1cmFsIG5ldHdvcmtzIGhhdmUgKiptYW55IGxheWVycyoqLCB3ZSB1c2UgdGhlICoqQ2hhaW4gUnVsZSoqIHRvIGNvbXB1dGUgZ3JhZGllbnRzLg0KDQpGb3IgYW4gYWN0aXZhdGlvbiBmdW5jdGlvbiAqKlwoIGYoeCkgXCkqKiBhbmQgYSBsb3NzIGZ1bmN0aW9uICoqXCggTCBcKSoqOg0KDQpcWw0KXGZyYWN7XHBhcnRpYWwgTH17XHBhcnRpYWwgd30gPSBcZnJhY3tccGFydGlhbCBMfXtccGFydGlhbCBmfSBcdGltZXMgXGZyYWN7XHBhcnRpYWwgZn17XHBhcnRpYWwgeH0gXHRpbWVzIFxmcmFje1xwYXJ0aWFsIHh9e1xwYXJ0aWFsIHd9DQpcXQ0KDQpFYWNoICoqbGF5ZXIgcGFzc2VzIHRoZSBlcnJvciBiYWNrd2FyZCoqLCBhZGp1c3Rpbmcgd2VpZ2h0cyBsYXllciBieSBsYXllci4NCg0KLS0tDQoNCiMjICoqNDEuIEV4YW1wbGU6IEJhY2twcm9wYWdhdGlvbiBpbiBQeXRob24qKg0KTGV0J3MgaW1wbGVtZW50IGEgKipzaW1wbGUgYmFja3Byb3BhZ2F0aW9uIHN0ZXAqKiBpbiBQeXRob24uDQoNCmBgYHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQoNCiMgRXhhbXBsZSBpbnB1dHMsIHdlaWdodHMsIGFuZCBleHBlY3RlZCBvdXRwdXQNCnggPSBucC5hcnJheShbMC41LCAwLjhdKQ0KdyA9IG5wLmFycmF5KFswLjEsIC0wLjJdKQ0KeV90cnVlID0gMS4wDQpsZWFybmluZ19yYXRlID0gMC4xDQoNCiMgRm9yd2FyZCBwYXNzDQp6ID0gbnAuZG90KHgsIHcpICAjIExpbmVhciBjb21iaW5hdGlvbg0KeV9wcmVkID0gMSAvICgxICsgbnAuZXhwKC16KSkgICMgU2lnbW9pZCBhY3RpdmF0aW9uDQoNCiMgQ29tcHV0ZSBsb3NzDQplcnJvciA9IHlfdHJ1ZSAtIHlfcHJlZA0KDQojIENvbXB1dGUgZ3JhZGllbnQNCmdyYWRpZW50ID0gZXJyb3IgKiB5X3ByZWQgKiAoMSAtIHlfcHJlZCkgKiB4DQoNCiMgVXBkYXRlIHdlaWdodHMNCncgPSB3ICsgbGVhcm5pbmdfcmF0ZSAqIGdyYWRpZW50DQoNCnByaW50KCJVcGRhdGVkIFdlaWdodHM6IiwgdykNCmBgYA0KDQotLS0NCg0KIyMgKio0Mi4gV2h5IEJhY2twcm9wYWdhdGlvbiBpcyBJbXBvcnRhbnQqKg0K4pyFICoqSXQgYWxsb3dzIG5ldXJhbCBuZXR3b3JrcyB0byBsZWFybiBmcm9tIG1pc3Rha2VzLioqICANCuKchSAqKkl0IG9wdGltaXplcyB3ZWlnaHRzIGVmZmljaWVudGx5IHVzaW5nIGNhbGN1bHVzLioqICANCuKchSAqKkl04oCZcyB0aGUgYmFja2JvbmUgb2YgZGVlcCBsZWFybmluZyBtb2RlbHMuKioNCg0KLS0tDQoNCiMjICoqQW5hbG9neTogQmFja3Byb3BhZ2F0aW9uIGlzIExpa2UgTGVhcm5pbmcgZnJvbSBNaXN0YWtlcyoqDQpJbWFnaW5lIHlvdSdyZSAqKmxlYXJuaW5nIHRvIHRocm93IGRhcnRzKiouICANCi0gWW91IHRocm93IGEgZGFydCBhbmQgKipzZWUgaG93IGZhciB5b3UgbWlzc2VkIHRoZSB0YXJnZXQqKi4NCi0gWW91ICoqYWRqdXN0IHlvdXIgYWltKiogYmFzZWQgb24gdGhlIG1pc3Rha2UuDQotIE92ZXIgdGltZSwgKip5b3UgZ2V0IGJldHRlciBhbmQgaGl0IHRoZSBidWxsc2V5ZSoqLg0KDQpCYWNrcHJvcGFnYXRpb24gZG9lcyB0aGUgc2FtZSB0aGluZ+KAlGl0ICoqYWRqdXN0cyB3ZWlnaHRzKiogc3RlcCBieSBzdGVwIHRvIHJlZHVjZSBlcnJvci4NCg0KLS0tDQoNCiMjIyAqKktleSBUYWtlYXdheXMqKg0K4pyFICoqQmFja3Byb3BhZ2F0aW9uIHVwZGF0ZXMgbmV1cmFsIG5ldHdvcmsgd2VpZ2h0cyBiYXNlZCBvbiBlcnJvci4qKiAgDQrinIUgKipHcmFkaWVudCBEZXNjZW50IGhlbHBzIG1pbmltaXplIGxvc3MgdXNpbmcgc21hbGwgd2VpZ2h0IGNoYW5nZXMuKiogIA0K4pyFICoqVGhlIENoYWluIFJ1bGUgYWxsb3dzIGVycm9yIHRvIHByb3BhZ2F0ZSB0aHJvdWdoIGxheWVycy4qKiAgDQrinIUgKipXaXRob3V0IGJhY2twcm9wYWdhdGlvbiwgZGVlcCBsZWFybmluZyB3b3VsZG7igJl0IHdvcmshKiogIA0KDQotLS0NCg0KDQoNCiMjICoqNDMuIFdoYXQgaXMgYW4gT3B0aW1pemVyPyoqDQpBbiAqKm9wdGltaXplcioqIGlzIGFuIGFsZ29yaXRobSB0aGF0IHVwZGF0ZXMgdGhlICoqd2VpZ2h0cyoqIG9mIGEgbmV1cmFsIG5ldHdvcmsgdG8gbWluaW1pemUgdGhlICoqbG9zcyBmdW5jdGlvbioqLg0KDQojIyMgKipXaHkgRG8gV2UgTmVlZCBPcHRpbWl6ZXJzPyoqDQotIE9wdGltaXplcnMgYWRqdXN0IHRoZSB3ZWlnaHRzIHRvICoqcmVkdWNlIGVycm9yKiouDQotIFRoZXkgaGVscCAqKnNwZWVkIHVwIGNvbnZlcmdlbmNlKiouDQotIFRoZXkgKipwcmV2ZW50IG92ZXJmaXR0aW5nIG9yIHVuZGVyZml0dGluZyoqLg0KDQotLS0NCg0KIyMgKio0NC4gVGhlIFJvbGUgb2YgdGhlIExlYXJuaW5nIFJhdGUgKM6xKSoqDQpUaGUgKipsZWFybmluZyByYXRlKiogY29udHJvbHMgaG93IG11Y2ggd2UgdXBkYXRlIHdlaWdodHMgYXQgZWFjaCBzdGVwLg0KDQp8IExlYXJuaW5nIFJhdGUgXCggXGFscGhhIFwpIHwgRWZmZWN0IHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKlRvbyBIaWdoIChlLmcuLCAxLjApKiogfCBKdW1wcyBvdmVyIHRoZSBtaW5pbXVtLCBuZXZlciBjb252ZXJnZXMgfA0KfCAqKlRvbyBMb3cgKGUuZy4sIDAuMDAwMSkqKiB8IFRha2VzIHRvbyBsb25nIHRvIHJlYWNoIHRoZSBtaW5pbXVtIHwNCnwgKipPcHRpbWFsIChlLmcuLCAwLjAxIC0gMC4xKSoqIHwgRmluZHMgdGhlIG1pbmltdW0gZWZmaWNpZW50bHkgfA0KDQojIyMgKipHcmFwaGljYWwgUmVwcmVzZW50YXRpb24qKg0K8J+TiSBBIHNtYWxsIGxlYXJuaW5nIHJhdGUgbW92ZXMgc2xvd2x5IHRvd2FyZHMgdGhlIG1pbmltdW0sIHdoaWxlIGEgbGFyZ2UgbGVhcm5pbmcgcmF0ZSBtYXkgb3NjaWxsYXRlIG9yIGRpdmVyZ2UuDQoNCi0tLQ0KDQojIyAqKjQ1LiBUeXBlcyBvZiBPcHRpbWl6ZXJzKioNClRoZXJlIGFyZSBkaWZmZXJlbnQgKipvcHRpbWl6YXRpb24gYWxnb3JpdGhtcyoqIHRoYXQgaW1wcm92ZSB3ZWlnaHQgdXBkYXRlcy4NCg0KIyMjICoqMS4gR3JhZGllbnQgRGVzY2VudCBWYXJpYW50cyoqDQp8IE9wdGltaXplciB8IERlc2NyaXB0aW9uIHwNCnwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfA0KfCAqKkJhdGNoIEdyYWRpZW50IERlc2NlbnQqKiB8IFVzZXMgKiphbGwgZGF0YSoqIGF0IG9uY2UgKHNsb3cgZm9yIGJpZyBkYXRhc2V0cykuIHwNCnwgKipTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKiB8IFVzZXMgKipvbmUgc2FtcGxlIGF0IGEgdGltZSoqIChmYXN0ZXIsIGJ1dCBub2lzeSkuIHwNCnwgKipNaW5pLUJhdGNoIEdyYWRpZW50IERlc2NlbnQqKiB8IFVzZXMgKipzbWFsbCBncm91cHMgb2Ygc2FtcGxlcyoqIChiYWxhbmNlZCkuIHwNCg0KIyMjICoqMi4gQWRhcHRpdmUgT3B0aW1pemVycyoqDQp8IE9wdGltaXplciB8IEtleSBGZWF0dXJlIHwNCnwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfA0KfCAqKk1vbWVudHVtKiogfCBVc2VzIHBhc3QgdXBkYXRlcyB0byBtb3ZlIGZhc3Rlci4gfA0KfCAqKlJNU3Byb3AqKiB8IEFkanVzdHMgbGVhcm5pbmcgcmF0ZSBkeW5hbWljYWxseSBmb3Igc3RhYmlsaXR5LiB8DQp8ICoqQWRhbSAoQWRhcHRpdmUgTW9tZW50IEVzdGltYXRpb24pKiogfCBDb21iaW5lcyBNb21lbnR1bSArIFJNU3Byb3AgKG1vc3QgcG9wdWxhcikuIHwNCnwgKipBZGFHcmFkKiogfCBBZGp1c3RzIGxlYXJuaW5nIHJhdGUgZm9yIGVhY2ggd2VpZ2h0IHNlcGFyYXRlbHkuIHwNCg0K8J+agCAqKkFkYW0gaXMgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBvcHRpbWl6ZXIgaW4gZGVlcCBsZWFybmluZy4qKg0KDQotLS0NCg0KIyMgKio0Ni4gTWF0aCBCZWhpbmQgT3B0aW1pemVycyoqDQojIyMgKipHcmFkaWVudCBEZXNjZW50IFdlaWdodCBVcGRhdGUgUnVsZSoqDQpXZWlnaHRzIGFyZSB1cGRhdGVkIGFzOg0KDQpcWw0KdyA9IHcgLSBcYWxwaGEgXGZyYWN7XHBhcnRpYWwgTH17XHBhcnRpYWwgd30NClxdDQoNCndoZXJlOg0KLSBcKCB3IFwpID0gd2VpZ2h0LA0KLSBcKCBcYWxwaGEgXCkgPSBsZWFybmluZyByYXRlLA0KLSBcKCBcZnJhY3tccGFydGlhbCBMfXtccGFydGlhbCB3fSBcKSA9IGdyYWRpZW50IChyYXRlIG9mIGNoYW5nZSBvZiBsb3NzKS4NCg0KLS0tDQoNCiMjICoqNDcuIE9wdGltaXplciBQZXJmb3JtYW5jZSBDb21wYXJpc29uKioNCnwgT3B0aW1pemVyIHwgU3BlZWQgfCBTdGFiaWxpdHkgfCBCZXN0IEZvciB8DQp8LS0tLS0tLS0tLS18LS0tLS0tLS18LS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLXwNCnwgKipTR0QqKiB8IEZhc3QgfCBOb2lzeSB8IFNpbXBsZSBkYXRhc2V0cyB8DQp8ICoqTW9tZW50dW0qKiB8IEZhc3RlciB0aGFuIFNHRCB8IE1vcmUgc3RhYmxlIHwgTWVkaXVtIGRhdGFzZXRzIHwNCnwgKipBZGFtKiogfCBGYXN0ZXN0IHwgVmVyeSBzdGFibGUgfCBEZWVwIGxlYXJuaW5nIHwNCg0KIyMjICoqUHl0aG9uIEV4YW1wbGU6IFVzaW5nIEFkYW0gT3B0aW1pemVyKioNCmBgYHB5dGhvbg0KaW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYNCg0KIyBDcmVhdGUgYSBzaW1wbGUgbW9kZWwNCm1vZGVsID0gdGYua2VyYXMuU2VxdWVudGlhbChbDQogICAgdGYua2VyYXMubGF5ZXJzLkRlbnNlKDEwLCBhY3RpdmF0aW9uPSJyZWx1IiksDQogICAgdGYua2VyYXMubGF5ZXJzLkRlbnNlKDEsIGFjdGl2YXRpb249InNpZ21vaWQiKQ0KXSkNCg0KIyBDb21waWxlIHdpdGggQWRhbSBvcHRpbWl6ZXINCm1vZGVsLmNvbXBpbGUob3B0aW1pemVyPXRmLmtlcmFzLm9wdGltaXplcnMuQWRhbShsZWFybmluZ19yYXRlPTAuMDEpLCBsb3NzPSJiaW5hcnlfY3Jvc3NlbnRyb3B5IikNCmBgYA0KDQotLS0NCg0KIyMgKio0OC4gVHVuaW5nIHRoZSBMZWFybmluZyBSYXRlKioNCkZpbmRpbmcgdGhlIHJpZ2h0ICoqbGVhcm5pbmcgcmF0ZSoqIGlzIGNydWNpYWwuDQoNCiMjIyAqKk1ldGhvZHMgdG8gVHVuZSBMZWFybmluZyBSYXRlKioNCjEuICoqTWFudWFsIFR1bmluZyoqOiBUcnkgdmFsdWVzIGxpa2UgKiowLjEsIDAuMDEsIDAuMDAxLCAwLjAwMDEqKi4NCjIuICoqTGVhcm5pbmcgUmF0ZSBEZWNheSoqOiBSZWR1Y2UgXCggXGFscGhhIFwpIG92ZXIgdGltZS4NCjMuICoqQ3ljbGljYWwgTGVhcm5pbmcgUmF0ZSAoQ0xSKSoqOiBBbHRlcm5hdGUgYmV0d2VlbiBoaWdoIGFuZCBsb3cgdmFsdWVzLg0KNC4gKipMZWFybmluZyBSYXRlIEZpbmRlcioqOiBUcmFpbiB3aXRoIG1hbnkgcmF0ZXMsIGNob29zZSB0aGUgYmVzdC4NCg0KIyMjICoqRXhhbXBsZTogTGVhcm5pbmcgUmF0ZSBEZWNheSoqDQpcWw0KXGFscGhhX3QgPSBcZnJhY3tcYWxwaGFfMH17MSArIFxsYW1iZGEgdH0NClxdDQp3aGVyZToNCi0gXCggXGFscGhhX3QgXCkgPSBsZWFybmluZyByYXRlIGF0IHN0ZXAgXCggdCBcKSwNCi0gXCggXGFscGhhXzAgXCkgPSBpbml0aWFsIGxlYXJuaW5nIHJhdGUsDQotIFwoIFxsYW1iZGEgXCkgPSBkZWNheSBmYWN0b3IuDQoNCi0tLQ0KDQojIyAqKjQ5LiBVbmRlcnN0YW5kaW5nIENvbnZlcmdlbmNlKioNCi0gKipUb28gaGlnaCBhIGxlYXJuaW5nIHJhdGUqKiDihpIgd2VpZ2h0cyBvc2NpbGxhdGUsIG5ldmVyIGNvbnZlcmdlLg0KLSAqKlRvbyBsb3cgYSBsZWFybmluZyByYXRlKiog4oaSIHRha2VzIGZvcmV2ZXIgdG8gcmVhY2ggdGhlIG9wdGltYWwgcG9pbnQuDQotICoqQWRhcHRpdmUgb3B0aW1pemVycyoqIGxpa2UgQWRhbSAqKmFkanVzdCBsZWFybmluZyByYXRlcyBkeW5hbWljYWxseSoqLg0KDQojIyMgKipWaXN1YWwgUmVwcmVzZW50YXRpb24qKg0K8J+foiAqKkdvb2QgbGVhcm5pbmcgcmF0ZSoqIOKGkiBzbW9vdGggZGVzY2VudCAgDQrwn5S0ICoqVG9vIGhpZ2gqKiDihpIgZXJyYXRpYyBqdW1wcyAgDQrwn5S1ICoqVG9vIGxvdyoqIOKGkiBzbG93IGNvbnZlcmdlbmNlICANCg0KLS0tDQoNCiMjICoqNTAuIEFuYWxvZ3k6IExlYXJuaW5nIHRvIFJpZGUgYSBCaWtlKioNCi0gSWYgeW91IHBlZGFsICoqdG9vIGZhc3QqKiAoaGlnaCBsZWFybmluZyByYXRlKSwgeW91IG1heSBsb3NlIGNvbnRyb2wuDQotIElmIHlvdSBwZWRhbCAqKnRvbyBzbG93KiogKGxvdyBsZWFybmluZyByYXRlKSwgeW91IHdvbid0IG1vdmUgZm9yd2FyZC4NCi0gKipPcHRpbWFsIHBlZGFsaW5nIHNwZWVkKiogKHJpZ2h0IGxlYXJuaW5nIHJhdGUpIGhlbHBzIHlvdSBiYWxhbmNlIHNwZWVkICYgY29udHJvbC4NCg0KLS0tDQoNCiMjIyAqKktleSBUYWtlYXdheXMqKg0K4pyFICoqT3B0aW1pemVycyBpbXByb3ZlIHdlaWdodCB1cGRhdGVzIGZvciBmYXN0ZXIgbGVhcm5pbmcuKiogIA0K4pyFICoqQWRhbSBpcyB0aGUgbW9zdCBjb21tb25seSB1c2VkIG9wdGltaXplci4qKiAgDQrinIUgKipMZWFybmluZyByYXRlIHR1bmluZyBpcyBjcml0aWNhbCBmb3IgY29udmVyZ2VuY2UuKiogIA0K4pyFICoqVG9vIGhpZ2ggb3IgdG9vIGxvdyBhIGxlYXJuaW5nIHJhdGUgY2FuIGNhdXNlIGlzc3Vlcy4qKiAgDQoNCi0tLQ0KDQoNCiMjICoqNTEuIE92ZXJ2aWV3OiBQdXR0aW5nIEl0IEFsbCBUb2dldGhlcioqDQpOb3cgdGhhdCB3ZSBoYXZlIGxlYXJuZWQgYWJvdXQgbmV1cmFsIG5ldHdvcmtzLCBhY3RpdmF0aW9uIGZ1bmN0aW9ucywgb3B0aW1pemVycywgYW5kIHRyYWluaW5nIG1ldGhvZHMsIGl04oCZcyB0aW1lIHRvICoqYnVpbGQgYSBuZXVyYWwgbmV0d29yayBmcm9tIHNjcmF0Y2gqKiB1c2luZyAqKlB5dGhvbiBhbmQgVGVuc29yRmxvdy9LZXJhcyoqLg0KDQpXZSB3aWxsOg0K4pyFIERlZmluZSB0aGUgbmV0d29yayBhcmNoaXRlY3R1cmUuICANCuKchSBDaG9vc2UgYWN0aXZhdGlvbiBmdW5jdGlvbnMgYW5kIGFuIG9wdGltaXplci4gIA0K4pyFIFRyYWluIHRoZSBuZXR3b3JrIG9uIHJlYWwgZGF0YS4gIA0K4pyFIEV2YWx1YXRlIHBlcmZvcm1hbmNlLiAgDQoNCi0tLQ0KDQojIyAqKjUyLiBTdGVwcyB0byBCdWlsZCBhIE5ldXJhbCBOZXR3b3JrKioNCjHvuI/ig6MgKipMb2FkIHRoZSBEYXRhKiogIA0KMu+4j+KDoyAqKlByZXByb2Nlc3MgdGhlIERhdGEqKiAgDQoz77iP4oOjICoqRGVmaW5lIHRoZSBNb2RlbCBBcmNoaXRlY3R1cmUqKiAgDQo077iP4oOjICoqQ29tcGlsZSB0aGUgTW9kZWwgKENob29zZSBMb3NzICYgT3B0aW1pemVyKSoqICANCjXvuI/ig6MgKipUcmFpbiB0aGUgTW9kZWwqKiAgDQo277iP4oOjICoqRXZhbHVhdGUgdGhlIE1vZGVsIFBlcmZvcm1hbmNlKiogIA0KN++4j+KDoyAqKk1ha2UgUHJlZGljdGlvbnMqKiAgDQoNCi0tLQ0KDQojIyAqKjUzLiBFeGFtcGxlOiBOZXVyYWwgTmV0d29yayBmb3IgQ2xhc3NpZmljYXRpb24qKg0KV2Ugd2lsbCBidWlsZCBhICoqYmluYXJ5IGNsYXNzaWZpZXIqKiBmb3IgYSBkYXRhc2V0Lg0KDQojIyMgKipTdGVwIDE6IEltcG9ydCBMaWJyYXJpZXMqKg0KYGBgcHl0aG9uDQppbXBvcnQgdGVuc29yZmxvdyBhcyB0Zg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgcGFuZGFzIGFzIHBkDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4ucHJlcHJvY2Vzc2luZyBpbXBvcnQgU3RhbmRhcmRTY2FsZXINCmBgYA0KDQotLS0NCg0KIyMjICoqU3RlcCAyOiBMb2FkIGFuZCBQcmVwcm9jZXNzIHRoZSBEYXRhKioNCmBgYHB5dGhvbg0KIyBMb2FkIGRhdGFzZXQgKEV4YW1wbGU6IEJyZWFzdCBDYW5jZXIgZGF0YXNldCBmcm9tIHNrbGVhcm4pDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGxvYWRfYnJlYXN0X2NhbmNlcg0KZGF0YSA9IGxvYWRfYnJlYXN0X2NhbmNlcigpDQpYLCB5ID0gZGF0YS5kYXRhLCBkYXRhLnRhcmdldA0KDQojIFNwbGl0IGludG8gdHJhaW4gYW5kIHRlc3Qgc2V0cyAoODAlIHRyYWluLCAyMCUgdGVzdCkNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgU3RhbmRhcmRpemUgZmVhdHVyZXMNCnNjYWxlciA9IFN0YW5kYXJkU2NhbGVyKCkNClhfdHJhaW4gPSBzY2FsZXIuZml0X3RyYW5zZm9ybShYX3RyYWluKQ0KWF90ZXN0ID0gc2NhbGVyLnRyYW5zZm9ybShYX3Rlc3QpDQpgYGANCg0KLS0tDQoNCiMjIyAqKlN0ZXAgMzogRGVmaW5lIHRoZSBNb2RlbCBBcmNoaXRlY3R1cmUqKg0KYGBgcHl0aG9uDQojIENyZWF0ZSBhIFNlcXVlbnRpYWwgTW9kZWwNCm1vZGVsID0gdGYua2VyYXMuU2VxdWVudGlhbChbDQogICAgdGYua2VyYXMubGF5ZXJzLkRlbnNlKDE2LCBhY3RpdmF0aW9uPSdyZWx1JywgaW5wdXRfc2hhcGU9KFhfdHJhaW4uc2hhcGVbMV0sKSksICMgSGlkZGVuIExheWVyIDENCiAgICB0Zi5rZXJhcy5sYXllcnMuRGVuc2UoOCwgYWN0aXZhdGlvbj0ncmVsdScpLCAjIEhpZGRlbiBMYXllciAyDQogICAgdGYua2VyYXMubGF5ZXJzLkRlbnNlKDEsIGFjdGl2YXRpb249J3NpZ21vaWQnKSAjIE91dHB1dCBMYXllcg0KXSkNCmBgYA0KDQrwn5OMICoqS2V5IFBvaW50cyoqOg0KLSAqKklucHV0IExheWVyKio6IFRha2VzIGluIGBYX3RyYWluLnNoYXBlWzFdYCBmZWF0dXJlcy4NCi0gKipIaWRkZW4gTGF5ZXJzKio6IFR3byBsYXllcnMgd2l0aCAqKlJlTFUgYWN0aXZhdGlvbioqLg0KLSAqKk91dHB1dCBMYXllcioqOiBVc2VzICoqU2lnbW9pZCoqIHNpbmNlIGl0J3MgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbS4NCg0KLS0tDQoNCiMjIyAqKlN0ZXAgNDogQ29tcGlsZSB0aGUgTW9kZWwqKg0KYGBgcHl0aG9uDQptb2RlbC5jb21waWxlKG9wdGltaXplcj0nYWRhbScsIA0KICAgICAgICAgICAgICBsb3NzPSdiaW5hcnlfY3Jvc3NlbnRyb3B5JywgDQogICAgICAgICAgICAgIG1ldHJpY3M9WydhY2N1cmFjeSddKQ0KYGBgDQrwn5OMICoqS2V5IFBvaW50cyoqOg0KLSAqKkxvc3MgZnVuY3Rpb24qKjogYGJpbmFyeV9jcm9zc2VudHJvcHlgICh1c2VkIGZvciBjbGFzc2lmaWNhdGlvbikuDQotICoqT3B0aW1pemVyKio6IGBhZGFtYCAoYmVzdCBnZW5lcmFsLXB1cnBvc2Ugb3B0aW1pemVyKS4NCi0gKipNZXRyaWNzKio6IFdlIHRyYWNrICoqYWNjdXJhY3kqKi4NCg0KLS0tDQoNCiMjIyAqKlN0ZXAgNTogVHJhaW4gdGhlIE1vZGVsKioNCmBgYHB5dGhvbg0KaGlzdG9yeSA9IG1vZGVsLmZpdChYX3RyYWluLCB5X3RyYWluLCBlcG9jaHM9NTAsIGJhdGNoX3NpemU9MzIsIHZhbGlkYXRpb25fZGF0YT0oWF90ZXN0LCB5X3Rlc3QpKQ0KYGBgDQrwn5OMICoqS2V5IFBhcmFtZXRlcnMqKjoNCi0gYGVwb2Nocz01MGA6IFRoZSBtb2RlbCB3aWxsIHNlZSB0aGUgZnVsbCBkYXRhc2V0ICoqNTAgdGltZXMqKi4NCi0gYGJhdGNoX3NpemU9MzJgOiBXZSBwcm9jZXNzICoqMzIgc2FtcGxlcyBhdCBhIHRpbWUqKi4NCi0gYHZhbGlkYXRpb25fZGF0YT0oWF90ZXN0LCB5X3Rlc3QpYDogQ2hlY2sgcGVyZm9ybWFuY2Ugb24gdW5zZWVuIGRhdGEuDQoNCuKPsyAqKlRyYWluaW5nIHRha2VzIGEgZmV3IHNlY29uZHMgdG8gbWludXRlcywgZGVwZW5kaW5nIG9uIGhhcmR3YXJlLioqDQoNCi0tLQ0KDQojIyMgKipTdGVwIDY6IEV2YWx1YXRlIFBlcmZvcm1hbmNlKioNCmBgYHB5dGhvbg0KbG9zcywgYWNjdXJhY3kgPSBtb2RlbC5ldmFsdWF0ZShYX3Rlc3QsIHlfdGVzdCkNCnByaW50KGYiVGVzdCBBY2N1cmFjeToge2FjY3VyYWN5KjEwMDouMmZ9JSIpDQpgYGANCvCfk4wgKipJbnRlcnByZXRpbmcgUmVzdWx0cyoqOg0KLSBJZiAqKmFjY3VyYWN5IGlzIGhpZ2ggKH45NSUrKSwqKiB0aGUgbW9kZWwgZ2VuZXJhbGl6ZXMgd2VsbC4g4pyFDQotIElmICoqYWNjdXJhY3kgaXMgbG93ICh+NTAtNjAlKSwqKiB0aGUgbW9kZWwgbWlnaHQgbmVlZCAqKmJldHRlciBmZWF0dXJlcywgbW9yZSBkYXRhLCBvciBoeXBlcnBhcmFtZXRlciB0dW5pbmcuKiog8J+UhA0KDQotLS0NCg0KIyMjICoqU3RlcCA3OiBNYWtlIFByZWRpY3Rpb25zKioNCmBgYHB5dGhvbg0KIyBQcmVkaWN0IG9uIG5ldyBkYXRhDQpwcmVkaWN0aW9ucyA9IG1vZGVsLnByZWRpY3QoWF90ZXN0KQ0KcHJlZGljdGVkX2NsYXNzZXMgPSAocHJlZGljdGlvbnMgPiAwLjUpLmFzdHlwZShpbnQpICAjIENvbnZlcnQgcHJvYmFiaWxpdGllcyB0byAwIG9yIDENCmBgYA0KDQrwn5OMICoqS2V5IFBvaW50cyoqOg0KLSAqKlByZWRpY3Rpb25zIGFyZSBwcm9iYWJpbGl0aWVzKiogKGJldHdlZW4gMCBhbmQgMSkuDQotIFdlICoqdGhyZXNob2xkKiogYXQgYDAuNWAgdG8gY29udmVydCB0byBjbGFzcyBsYWJlbHMuDQoNCi0tLQ0KDQojIyAqKjU0LiBVbmRlcnN0YW5kaW5nIHRoZSBUcmFpbmluZyBQcm9jZXNzKioNCiMjIyAqKkxvc3MgQ3VydmUqKg0KQSBsb3NzIGN1cnZlIGhlbHBzIHVzICoqdW5kZXJzdGFuZCBjb252ZXJnZW5jZSoqLg0KDQpgYGBweXRob24NCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KcGx0LnBsb3QoaGlzdG9yeS5oaXN0b3J5Wydsb3NzJ10sIGxhYmVsPSdUcmFpbiBMb3NzJykNCnBsdC5wbG90KGhpc3RvcnkuaGlzdG9yeVsndmFsX2xvc3MnXSwgbGFiZWw9J1ZhbGlkYXRpb24gTG9zcycpDQpwbHQueGxhYmVsKCdFcG9jaHMnKQ0KcGx0LnlsYWJlbCgnTG9zcycpDQpwbHQubGVnZW5kKCkNCnBsdC5zaG93KCkNCmBgYA0K8J+TjCAqKkludGVycHJldGluZyB0aGUgQ3VydmUqKjoNCi0gKipMb3NzIGRlY3JlYXNpbmcqKiDinIUg4oaSIE1vZGVsIGlzIGxlYXJuaW5nLg0KLSAqKkxvc3MgaW5jcmVhc2luZyoqIOKdjCDihpIgTW9kZWwgaXMgb3ZlcmZpdHRpbmcuDQoNCi0tLQ0KDQojIyAqKjU1LiBGaW5lLVR1bmluZyB0aGUgTmV1cmFsIE5ldHdvcmsqKg0KSWYgcGVyZm9ybWFuY2UgaXMgbm90IGdyZWF0LCB0cnk6DQrinIUgKipBZGRpbmcgbW9yZSBsYXllcnMqKiAoZGVlcCBuZXR3b3JrcyBsZWFybiBiZXR0ZXIpLiAgDQrinIUgKipJbmNyZWFzaW5nIGVwb2NocyoqICh0cmFpbiBsb25nZXIpLiAgDQrinIUgKipUdW5pbmcgbGVhcm5pbmcgcmF0ZSoqICh0b28gaGlnaCDihpIgdW5zdGFibGUsIHRvbyBsb3cg4oaSIHNsb3cgbGVhcm5pbmcpLiAgDQrinIUgKipVc2luZyBkcm9wb3V0IGxheWVycyoqIChwcmV2ZW50IG92ZXJmaXR0aW5nKS4gIA0KDQpFeGFtcGxlOg0KYGBgcHl0aG9uDQp0Zi5rZXJhcy5sYXllcnMuRHJvcG91dCgwLjMpICAjIDMwJSBvZiBuZXVyb25zIGFyZSByYW5kb21seSBkaXNhYmxlZCBwZXIgZXBvY2gNCmBgYA0KDQotLS0NCg0KIyMgKio1Ni4gQW5hbG9neTogVHJhaW5pbmcgYSBOZXVyYWwgTmV0d29yayA9IFRlYWNoaW5nIGEgU3R1ZGVudCoqDQpUaGluayBvZiB0cmFpbmluZyBhIG5ldXJhbCBuZXR3b3JrIGxpa2UgKip0ZWFjaGluZyBhIHN0dWRlbnQqKjoNCi0gVGhlICoqc3R1ZGVudCAobW9kZWwpKiogbGVhcm5zIGZyb20gKipwcmFjdGljZSAoZGF0YSkqKi4NCi0gVGhlICoqdGVhY2hlciAob3B0aW1pemVyKSoqIGdpdmVzIGZlZWRiYWNrIHRvICoqYWRqdXN0IGxlYXJuaW5nKiouDQotIFRoZSAqKnN0dWRlbnQgaW1wcm92ZXMgb3ZlciB0aW1lIChlcG9jaHMpKiouDQotICoqVG9vIG11Y2ggc3R1ZHlpbmcgKG92ZXJmaXR0aW5nKSoqIOKGkiBTdHVkZW50IG1lbW9yaXplcyBhbnN3ZXJzIGluc3RlYWQgb2YgdW5kZXJzdGFuZGluZy4NCi0gKipUb28gbGl0dGxlIHN0dWR5aW5nICh1bmRlcmZpdHRpbmcpKiog4oaSIFN0dWRlbnQgZ3Vlc3NlcyBhbnN3ZXJzIHJhbmRvbWx5Lg0KDQotLS0NCg0KIyMgKio1Ny4gS2V5IFRha2Vhd2F5cyoqDQrinIUgKipOZXVyYWwgbmV0d29ya3MgYXJlIGVuc2VtYmxlcyBvZiByZWdyZXNzb3JzLioqICANCuKchSAqKkVhY2ggbGF5ZXIgZXh0cmFjdHMgZGVlcGVyIGZlYXR1cmVzLioqICANCuKchSAqKkFjdGl2YXRpb24gZnVuY3Rpb25zIGFsbG93IG5vbi1saW5lYXJpdHkuKiogIA0K4pyFICoqT3B0aW1pemVycyBhZGp1c3Qgd2VpZ2h0cyBmb3IgYmV0dGVyIGxlYXJuaW5nLioqICANCuKchSAqKkh5cGVycGFyYW1ldGVycyAoZXBvY2hzLCBiYXRjaCBzaXplLCBsZWFybmluZyByYXRlKSBtdXN0IGJlIHR1bmVkLioqICANCuKchSAqKk5ldXJhbCBuZXR3b3JrcyBleGNlbCBhdCBwYXR0ZXJuIHJlY29nbml0aW9uICYgY2xhc3NpZmljYXRpb24uKiogIA0KDQotLS0NCg0K