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 enoughThreshold 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

  1. Input Layer: Takes the raw data as input.
  2. Hidden Layers: Intermediate layers that transform the data using weights and activation functions.
  3. 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

Activation Function Formula Use Case
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

  1. Forward Pass: Compute the output given the input.
  2. Compute Loss: Measure the error (difference between predicted and actual values).
  3. 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

  1. Initialize random weights.
  2. Use the sigmoid function for activation.
  3. Train using backpropagation by updating weights.
  4. 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

Type Description Pros Cons
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

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

Method Feature
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

Method Purpose
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.

4. Transformers

  • Structure: Uses attention mechanisms to process entire sequences at once.
  • Use Case: Natural Language Processing (NLP), chatbots, machine translation.
  • Example:
    • GPT models (like ChatGPT).
    • Google Translate.

13. Layers in a Neural Network

1. Input Layer

  • Receives raw data (images, text, numbers).
  • No calculations occur here.
  • Example: Pixel values of an image.

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.

Activation Formula Use Case
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

More Layers Fewer Layers
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.

Loss Function Formula Use Case
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

Algorithm Update Method Pros Cons
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.

Optimizer Characteristics
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 \]


22. Evaluating Model Performance

Neural networks are evaluated using training and test data.

1. Training vs. Validation vs. Test Set

Dataset Purpose
Training Set Model learns from this data
Validation Set Tunes hyperparameters, prevents overfitting
Test Set Final evaluation on unseen data

2. Metrics for Model Performance

Metric Use Case
Accuracy Classification (when classes are balanced)
Precision & Recall Classification (imbalanced datasets)
F1-Score Trade-off between precision & recall
R² Score Regression (explains variance)

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.

Concept Description Example
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.

Dropout Rate Effect
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.


  1. 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.

Activation Function Formula Use Case
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

Task Best Activation
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.

  1. Forward Propagation:
    • Inputs flow through the network.
    • Predictions are made using current weights.
  2. Calculate Loss:
    • Compare predictions to actual values.
    • Use a loss function to measure error.
  3. Backward Propagation:
    • Compute how much each weight contributed to the error.
    • Adjust the weights using Gradient Descent.
  4. 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

Type Description
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.

Learning Rate \(\alpha\) Effect
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

Optimizer Description
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

Optimizer Key Feature
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).


47. Optimizer Performance Comparison

Optimizer Speed Stability Best For
SGD Fast Noisy Simple datasets
Momentum Faster than SGD More stable Medium datasets
Adam Fastest Very stable Deep learning

Python Example: Using Adam Optimizer

import tensorflow as tf

# Create a simple model
model = tf.keras.Sequential([
    tf.keras.layers.Dense(10, activation="relu"),
    tf.keras.layers.Dense(1, activation="sigmoid")
])

# Compile with Adam optimizer
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.01), loss="binary_crossentropy")

48. Tuning the Learning Rate

Finding the right learning rate is crucial.

Methods to Tune Learning Rate

  1. Manual Tuning: Try values like 0.1, 0.01, 0.001, 0.0001.
  2. Learning Rate Decay: Reduce \(\alpha\) over time.
  3. Cyclical Learning Rate (CLR): Alternate between high and low values.
  4. 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 6: Evaluate Performance

loss, accuracy = model.evaluate(X_test, y_test)
print(f"Test Accuracy: {accuracy*100:.2f}%")

📌 Interpreting Results: - If accuracy is high (~95%+), the model generalizes well. ✅ - If accuracy is low (~50-60%), the model might need better features, more data, or hyperparameter tuning. 🔄


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