Study Guide: Large Datasets and Out-of-Core Methods

Module 10: Out-of-Core Methods

Section 1: Large Datasets


Overview

Large datasets are a fundamental challenge in modern data science and machine learning. As datasets grow in size, traditional machine learning algorithms face issues related to memory, processing power, and computational efficiency. This study guide will expand on the topic of large datasets, discuss when “big data” begins, and explore mathematical and coding representations for handling large datasets efficiently.

Key Topics Covered

  1. Understanding large datasets
  2. Memory constraints and computational limits
  3. Defining “big data” in practical applications
  4. Strategies for handling large datasets in machine learning
  5. Mathematical formulation of large dataset constraints
  6. Coding implementation in Python using pandas, numpy, and sklearn

Understanding Large Datasets

In machine learning, dataset size is typically measured by the number of rows (samples) rather than the number of features (variables).
- Sklearn toy datasets range from 150 samples to 5,056 samples. - Real-world datasets range from 400 samples to 4.9 million samples (with an average size of 20,000 samples). - UCI datasets can contain up to 62 million samples.

The computational bottleneck arises when the dataset exceeds the memory (RAM) capacity of the machine.

Memory Constraints and Computational Limits

A rule of thumb for determining “big data” is: - If a dataset cannot fit into memory (RAM), it is considered big data. - Current machines typically have 16 GB of RAM, meaning they can store ~4 billion numbers in memory under ideal conditions. - Feature dimensionality matters:
- A dataset with 10 features and 400 million rows requires 4 billion numbers, pushing the memory limits.


Mathematical Representation

Given a dataset \(X\) with \(n\) samples and \(d\) features: - A full dataset requires: \[ \text{Memory Usage} = n \times d \times \text{size of each element (in bytes)} \] - For a 32-bit float (4 bytes per value): \[ \text{Memory} = n \times d \times 4 \] - For a 64-bit float (8 bytes per value): \[ \text{Memory} = n \times d \times 8 \] - A dataset of 10 million rows and 100 features using 32-bit floats requires: \[ 10^7 \times 100 \times 4 = 4 \text{ GB} \] which is manageable in RAM. However, 100 million rows would require 40 GB, exceeding standard memory.


Strategies for Handling Large Datasets

To process large datasets efficiently, machine learning practitioners use several techniques:

1. Out-of-Core Processing

  • Uses disk-based storage instead of RAM.
  • Example: Using dask instead of pandas to process data in parallel.

2. Data Streaming

  • Process small batches instead of loading everything into memory.
  • Example: sklearn.partial_fit() for incremental learning.

3. Feature Selection & Dimensionality Reduction

  • Reduce the number of features (columns) using:
    • Principal Component Analysis (PCA)
    • Autoencoders (Deep Learning)
    • Feature selection methods (LASSO, Tree-based selection)

4. Sampling & Approximation

  • Use a representative subset of data.
  • Example: Use stratified sampling for balanced datasets.

5. Distributed Computing

  • Split data across multiple machines using:
    • Apache Spark
    • Google BigQuery
    • Dask
    • Hadoop

Python Implementation for Handling Large Datasets

# Using `dask` for Out-of-Core Processing
import dask.dataframe as dd

# Load large dataset using Dask
df = dd.read_csv('large_dataset.csv')

# Perform computation (e.g., mean of a column)
result = df['feature_column'].mean().compute()
print(result)
# Using `sklearn.partial_fit()` for Incremental Learning
from sklearn.linear_model import SGDClassifier
from sklearn.datasets import fetch_20newsgroups_vectorized

# Load large dataset
data = fetch_20newsgroups_vectorized()
X, y = data.data, data.target

# Initialize incremental learning model
model = SGDClassifier(loss='hinge')  # Support Vector Machine using Stochastic Gradient Descent

# Train model in batches
for i in range(0, X.shape[0], 1000):  # Batch size of 1000
    model.partial_fit(X[i:i+1000], y[i:i+1000], classes=[0, 1, 2, 3])

print("Training complete.")
# Using `pandas` for Efficient Data Handling
import pandas as pd

# Read only required columns and rows
df = pd.read_csv('large_file.csv', usecols=['column1', 'column2'], nrows=10000)

# Convert to numeric types to save memory
df['column1'] = pd.to_numeric(df['column1'], downcast='float')

# Display memory usage
print(df.info(memory_usage='deep'))

Key Takeaways

  • Big Data ≠ Fixed Definition – It depends on whether the dataset fits into memory.
  • SVM does not scale well – Alternative methods like SGD and tree-based models handle large data better.
  • Use out-of-core methodsdask, sklearn.partial_fit(), and sampling can help manage large datasets.
  • Memory-efficient data handling – Use batch processing, column selection, and data type reduction.

Questions for the Professor

  1. What are the practical trade-offs between using batch processing vs. distributed computing for large datasets?
  2. Can we effectively use SGD with kernel-based methods, or is it strictly for linear models?
  3. How does XGBoost handle memory constraints differently compared to SVM?
  4. What are the best practices for choosing between subsampling vs. streaming data for training models?

Question 1: How do I get native class probability from an SVM?

Answer:
SVM does not predict class probability.
(SVM is inherently a margin-based classifier and does not provide probability estimates directly. However, probability estimates can be obtained by applying methods like Platt scaling, but these are not “native” probabilities from SVM itself.)


Question 2: Which points contribute to the loss of an SVM?

Answer:
Misclassified points and any points in the margin.
(SVM loss is affected by both misclassified points and those that fall within the margin, as they violate the margin constraints.)


Question 3: The heart of the kernel trick is:

Answer:
We only need the outcome of the dot product.
(The key idea behind the kernel trick is that we never compute the feature transformation explicitly. Instead, we use a kernel function to compute the dot product in a high-dimensional space implicitly.)


Question 4: When using large datasets, SVM:

Answer:
Scales poorly.
(SVM is not ideal for large datasets because solving the quadratic optimization problem required for training SVM scales poorly with the number of samples. This was specifically discussed in the lecture slides about scaling issues.)


Question 5: What is unique about the hinge loss?

Answer:
It is one-sided.
(The hinge loss only penalizes misclassified points and those within the margin. Correctly classified points that are outside the margin do not contribute to the loss.)

Here is your expanded Study Guide for Stochastic Gradient Descent (SGD) based on your provided materials and references.


Study Guide: Stochastic Gradient Descent (SGD)

Overview

Stochastic Gradient Descent (SGD) is a variant of Gradient Descent, commonly used to optimize machine learning models, especially in cases where datasets are too large to fit into memory. Instead of computing the gradient for the entire dataset, SGD approximates it using a small, randomly selected batch of data.

Key Concepts

  1. Gradient Descent (GD) Review
    • The goal of gradient descent is to find the minimum of a loss function \(J(m)\).
    • The weight (or parameter) updates follow: \[ m_{n+1} = m_n - \alpha \nabla J(m) \] where:
      • \(m_n\) is the current parameter value.
      • \(\alpha\) is the learning rate.
      • \(\nabla J(m)\) is the gradient of the loss function.
  2. Limitations of Full-Batch Gradient Descent
    • For large datasets, computing the gradient for the entire dataset at each iteration is computationally expensive.
    • Requires storing all data in memory.
    • Can be slow due to processing all data at once.
  3. Stochastic Gradient Descent (SGD)
    • Instead of using the entire dataset, SGD randomly selects a batch (subset) of data at each iteration to approximate the gradient: \[ m_{n+1} = m_n - \alpha \nabla Q(m) \] where:
      • \(Q(m)\) is an estimate of \(J(m)\) using a batch.
    • The batch size determines the trade-off:
      • Smaller batches → Faster updates, but more noise.
      • Larger batches → More stable updates, but higher memory usage.

Advantages of SGD

  • Lower Memory Usage: Processes data in batches, eliminating the need to store the entire dataset in memory.
  • Faster Training for Large Datasets: Allows models to be trained efficiently on large-scale data.
  • More Frequent Updates: Each update adjusts parameters based on a small batch, which can lead to faster convergence in some cases.

Challenges of SGD

  • Noise in Updates: Since batches provide an approximation, the updates may be noisy.
  • Slower Convergence: Compared to full-batch methods, SGD may take longer to stabilize.
  • Data Loading Bottleneck: Efficiently streaming data from storage into memory is a key challenge.

Mathematical Representation

Gradient Descent Update Rule: \[ m_{n+1} = m_n - \alpha \nabla J(m) \]

Stochastic Gradient Descent Update Rule (Using Batch \(B\)): \[ m_{n+1} = m_n - \alpha \nabla Q(m) \] where \(Q(m)\) is the loss estimate using only a batch.


Python Implementation in Jupyter Notebook

We will demonstrate the effect of SGD vs. Batch Gradient Descent using a linear regression example.

Step 1: Import Libraries

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

Step 2: Generate Synthetic Data

# Generate synthetic dataset
np.random.seed(42)
X = 2 * np.random.rand(10000, 1)
y = 4 + 3 * X + np.random.randn(10000, 1)  # Linear relationship with noise

# Split dataset
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Step 3: Standardization

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Step 4: Train SGD Regressor

sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, learning_rate="constant", eta0=0.01)
sgd_reg.fit(X_train_scaled, y_train.ravel())

# Predictions
y_pred = sgd_reg.predict(X_test_scaled)

print(f"SGD Coefficients: {sgd_reg.coef_}, Intercept: {sgd_reg.intercept_}")

Step 5: Compare SGD to Batch Gradient Descent

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X_train_scaled, y_train)

print(f"Batch Gradient Descent Coefficients: {lin_reg.coef_}, Intercept: {lin_reg.intercept_}")

Key Takeaways

  1. SGD is well-suited for large datasets since it does not require loading all data into memory.
  2. Batches help approximate the gradient, but small batches introduce noise in updates.
  3. Trade-off exists between batch size, computation time, and memory efficiency.
  4. Data streaming must be optimized to avoid computational bottlenecks when training models.

Relevant Questions for Discussion

  1. How does the choice of batch size affect convergence speed and stability?
  2. Why is the learning rate crucial for SGD? What happens if it’s too high or too low?
  3. How does SGD compare to other optimization techniques like Adam or RMSprop?
  4. What strategies exist to handle data streaming efficiently when using SGD?
  5. How can we ensure that SGD does not get stuck in local minima?

Study Guide: The Hashing Trick

Overview

The Hashing Trick is a computational technique used in Stochastic Gradient Descent (SGD) and out-of-core learning to efficiently store and retrieve data, particularly for large datasets that cannot fit into memory. This method allows data to be mapped into memory quickly and efficiently using a hash function, which provides a fixed-length representation of input data.

Key Concepts

  1. Out-of-Core Learning:
    • Used when datasets are too large to fit into memory.
    • Data is processed in chunks rather than all at once.
    • Essential for big data applications.
  2. Role of the Hashing Trick in Out-of-Core Learning:
    • Allows efficient data storage and retrieval without needing to load all data into memory.
    • Uses a hash function to map feature names to fixed-size memory locations.
    • Prevents expensive memory allocation and reallocation.
  3. Hash Functions in Machine Learning:
    • A hash function maps an input (e.g., a feature name) to a numerical index.
    • The same input always produces the same hash (deterministic).
    • Hashing avoids the need for a precomputed dictionary of feature mappings.
    • Used in algorithms like SGD, Feature Hashing, and Online Learning Models.

Mathematical Representation of the Hashing Trick

A hash function \(H(x)\) maps an input \(x\) (e.g., a feature name) to a location in a fixed-size vector: \[ H(x) = \text{index in memory space} \] For example: - Input Feature: "price" - Hash Function Output: H("price") = 238 - Storage: The value associated with "price" is stored at index 238.

Handling Hash Collisions

  • Since hash functions do not guarantee unique mappings, two different inputs may map to the same index.
  • This is called a hash collision.
  • While it can introduce noise, research shows that collisions have minimal impact on model accuracy.
  • A larger hash space (memory size) reduces collisions.

Advantages of the Hashing Trick

Speed: Quickly assigns a memory location for each feature, avoiding expensive dictionary lookups.

Lower Memory Usage: No need to store feature names, reducing overhead.

Scalability: Works well for large datasets and streaming data.

No Need for Precomputed Dictionaries: Unlike one-hot encoding, feature mappings don’t need to be stored in memory.

Compatible with SGD: Essential for efficient stochastic gradient descent updates.


Challenges of the Hashing Trick

Hash Collisions: - If two features map to the same index, they overwrite each other’s values. - Can be mitigated by increasing the hash space (number of memory slots).

Fixed Feature Space: - Once a hash size is chosen, it cannot be dynamically changed during training. - If the number of features grows significantly, the hash table may become too small.

Interpretability Issues: - Traditional feature names are lost since data is mapped to hashed indices. - Makes debugging and feature analysis harder.


Python Implementation

Let’s demonstrate the Hashing Trick using Scikit-learn’s FeatureHasher.

Step 1: Import Libraries

import numpy as np
from sklearn.feature_extraction import FeatureHasher

Step 2: Create Sample Data

# Example categorical data
data = [
    {"feature1": "apple", "feature2": "red"},
    {"feature1": "banana", "feature2": "yellow"},
    {"feature1": "grape", "feature2": "purple"},
]

# Create FeatureHasher with hash size 10
hasher = FeatureHasher(n_features=10, input_type="dict")
hashed_features = hasher.transform(data).toarray()

# Display hashed output
print(hashed_features)

Step 3: Using the Hashing Trick in SGD

from sklearn.linear_model import SGDClassifier

# Sample dataset with text features
X = [
    {"word": "dog"}, {"word": "cat"}, {"word": "fish"},
    {"word": "dog"}, {"word": "dog"}, {"word": "cat"}
]
y = [1, 0, 0, 1, 1, 0]  # Binary classification labels

# Hash the features
hasher = FeatureHasher(n_features=5, input_type="dict")
X_hashed = hasher.transform(X)

# Train an SGD classifier
sgd = SGDClassifier(loss="log", max_iter=1000, tol=1e-3)
sgd.fit(X_hashed, y)

# Make predictions
predictions = sgd.predict(X_hashed)
print("Predictions:", predictions)

Key Takeaways

  1. The Hashing Trick enables fast, memory-efficient feature encoding, making it useful for big data and online learning.
  2. Hash functions map features to fixed memory locations, reducing lookup time and memory footprint.
  3. Hash collisions may occur but generally do not significantly affect model performance.
  4. Used in combination with SGD and out-of-core learning to handle large datasets efficiently.

Relevant Questions for Discussion

  1. What are the trade-offs between increasing and decreasing the hash space?
  2. How does the Hashing Trick compare to one-hot encoding?
  3. Why is the Hashing Trick commonly used in real-time and streaming applications?
  4. What are some ways to mitigate hash collisions in practical implementations?
  5. Can the hashing trick be used in deep learning architectures, and if so, how?

Study Guide: Stochastic Gradient Descent (SGD) & Epochs

Overview

Stochastic Gradient Descent (SGD) follows the same principles as linear and logistic regression, solving for slopes by minimizing a loss function. However, since it processes data in batches, it introduces additional hyperparameters, such as the learning rate and the number of epochs.

Key Concepts

  1. SGD and Regression:
    • Works similarly to linear and logistic regression.
    • Uses the same loss functions but operates on smaller subsets (batches) of data.
    • Introduces new hyperparameters (e.g., learning rate, batch size, number of epochs).
  2. Learning Rate (α):
    • Controls how much the model updates weights with each batch.
    • If too large → model might not converge (overshooting).
    • If too small → training will be too slow.
  3. What is an Epoch?:
    • One epoch = one full pass through the entire dataset.
    • Data is divided into batches (smaller subsets of data).
    • The model updates its parameters after each batch.
  4. Example of an Epoch:
    • If you have 100 data points and a batch size of 5:
      • The model sees 5 data points at a time.
      • After 20 batches (100/5), the model has seen all 100 points once.
      • This completes one epoch.
      • The process repeats for multiple epochs.
  5. Stopping Criteria for SGD:
    • Running too few epochs → underfitting (model doesn’t learn enough).
    • Running too many epochs → overfitting (model memorizes training data but generalizes poorly).
    • Best practice: Stop training when the loss stops improving.

Mathematical Representation of SGD with Epochs

Gradient Descent updates the weights \(w\) using: \[ w_{t+1} = w_t - \alpha \nabla J(w_t) \] where: - \(w_t\) = current weight - \(\alpha\) = learning rate - \(\nabla J(w_t)\) = gradient of the loss function at step \(t\)

For SGD, instead of computing the gradient on the entire dataset, it is computed on a batch \(B\): \[ w_{t+1} = w_t - \alpha \nabla J_B(w_t) \] where \(J_B\) is the loss function computed only on batch \(B\).

Epochs and Updates

Each batch update modifies the model weights. After the model has seen all batches, one epoch is completed. The process repeats until convergence criteria are met.


Python Implementation

Step 1: Import Libraries

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import SGDClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

Step 2: Generate Sample Data

# Generate synthetic classification data
X, y = make_classification(n_samples=1000, n_features=10, random_state=42)

# Split into train/test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Step 3: Train an SGD Classifier with Multiple Epochs

# Train model with SGD
sgd = SGDClassifier(loss="log_loss", max_iter=100, tol=1e-3)

# Fit model
sgd.fit(X_train, y_train)

# Evaluate
accuracy = sgd.score(X_test, y_test)
print(f"Test Accuracy: {accuracy:.4f}")

Step 4: Visualizing Learning Rate and Epoch Impact

epochs = [5, 10, 50, 100, 200]
accuracy_scores = []

for epoch in epochs:
    sgd = SGDClassifier(loss="log_loss", max_iter=epoch, tol=1e-3)
    sgd.fit(X_train, y_train)
    accuracy_scores.append(sgd.score(X_test, y_test))

# Plot accuracy vs epochs
plt.plot(epochs, accuracy_scores, marker="o")
plt.xlabel("Epochs")
plt.ylabel("Test Accuracy")
plt.title("Impact of Epochs on Accuracy")
plt.show()

Key Takeaways

*Epochs define how many times the model sees the entire dataset**. *SGD updates weights after each batch, rather than after seeing the whole dataset**. *Learning rate and batch size affect convergence speed. Monitoring loss improvement is crucial to avoid overfitting. Instead of fixing the number of epochs, use a stopping criterion based on loss improvement.


Relevant Questions for Discussion

  1. How does increasing the number of epochs affect model performance?
  2. What happens if the learning rate is too high or too low?
  3. How does SGD compare to batch gradient descent in terms of memory and computation?
  4. What are common stopping criteria for determining when to end training?
  5. How do batch size and epochs interact in model training?

Study Guide: Stochastic Gradient Descent (SGD) Demo

Overview

Stochastic Gradient Descent (SGD) is an optimization technique that allows machine learning models to train on large datasets efficiently. Instead of processing all data at once, SGD updates model parameters iteratively using small batches (or even single data points). This study guide explores how SGD can be applied to large datasets, its implementation, and practical considerations.


Key Concepts

1. Why Use SGD for Large Datasets?

  • Traditional gradient descent requires loading the entire dataset into memory, making it inefficient for big data applications.
  • SGD only needs to load small batches (or single rows) into memory at a time, allowing models to scale to millions of data points.
  • Partial fitting enables models to update in chunks, rather than requiring the entire dataset.

2. Understanding the SGDClassifier

The SGDClassifier in Scikit-Learn is a linear classifier that uses SGD for optimization.

Key Parameters: - loss: Defines the type of optimization (e.g., "log" for logistic regression, "hinge" for SVM). - alpha: The learning rate (controls step size for updates). - penalty: Regularization term ("l2", "l1", "elasticnet"). - partial_fit: Allows training on chunks of data rather than the whole dataset at once.


3. Mathematical Representation of SGD

The standard SGD update rule for weight updates is:

\[ w_{t+1} = w_t - \alpha \nabla J(w_t) \]

Where: - \(w_t\) = Current weights - \(\alpha\) = Learning rate - \(\nabla J(w_t)\) = Gradient of loss function

Instead of computing the gradient using all data, SGD approximates it using one or a few examples per step.


Python Implementation: Training a Model with SGD on Large Data

Step 1: Import Necessary Libraries

import numpy as np
import pandas as pd
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

Step 2: Simulating Large Data

# Generate a large synthetic dataset with 11 million rows and 29 features
n_samples = 11_000_000  # 11 million rows
n_features = 29  # 29 features

# Simulating a large dataset
X = np.random.rand(n_samples, n_features)
y = np.random.randint(0, 2, n_samples)  # Binary classification (0 or 1)

# Split into training and test sets (only use a subset for testing to save memory)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.001, random_state=42)

Step 3: Training SGD with Partial Fitting

# Create an SGD classifier
sgd = SGDClassifier(loss="log_loss", penalty="l2", alpha=0.01)

# Train using partial_fit (batch processing)
batch_size = 100_000  # Process 100,000 rows at a time
for i in range(0, len(X_train), batch_size):
    X_batch = X_train[i:i+batch_size]
    y_batch = y_train[i:i+batch_size]
    
    # If first batch, initialize with 'classes' argument
    if i == 0:
        sgd.partial_fit(X_batch, y_batch, classes=np.unique(y_train))
    else:
        sgd.partial_fit(X_batch, y_batch)

Step 4: Evaluating the Model

# Make predictions
y_pred = sgd.predict(X_test)

# Compute accuracy
accuracy = accuracy_score(y_test, y_pred)
print(f"Test Accuracy: {accuracy:.4f}")

Key Insights from SGD Demo

Handles massive datasets efficiently: Instead of processing everything at once, SGD updates weights iteratively using batches.
Reduces memory requirements: Only small chunks of data are loaded into memory at a time.
Fast training speed: The training time for 11 million rows was under a minute.
*Trade-off between speed and accuracy: Larger batches result in more stable updates, while smaller batches can introduce higher variance**.


Partial Fitting and Why It Matters

  • The partial_fit() function allows updating the model without reloading all previous data.
  • Useful when the dataset is too large to fit into memory.
  • Helps train models incrementally (ideal for real-time learning or continuous updates).

Discussion Questions for Class

  1. How does batch size impact SGD performance?
  2. What are the trade-offs of using partial_fit instead of training on the full dataset?
  3. Why does increasing the number of epochs sometimes lead to overfitting in SGD?
  4. How does SGD compare to traditional gradient descent for large-scale learning?
  5. What are common stopping criteria for SGD (e.g., loss stabilization, accuracy plateau)?

Final Thoughts

SGD is a powerful optimization method for training models on large datasets. While it allows efficient training with limited memory, choosing the right batch size, learning rate, and stopping criteria is critical for performance.

Study Guide: Stochastic Gradient Descent & Vowpal Wabbit (VW)

Overview

Vowpal Wabbit (VW) is an advanced machine learning tool designed for scalable, online learning using Stochastic Gradient Descent (SGD). It is particularly useful for large datasets where traditional machine learning algorithms struggle due to memory limitations.

VW operates via the command line, making it extremely fast and memory-efficient. It is widely used for large-scale classification and regression problems, including natural language processing (NLP), recommendation systems, and real-time learning.


Key Concepts of Vowpal Wabbit

1. Why Use VW?

  • Fast processing speed: Unlike traditional ML libraries, VW is optimized for speed.
  • Minimal memory usage: VW does not store all data in RAM, making it ideal for big data applications.
  • Supports online learning: Can continuously update the model as new data arrives.
  • Built-in regularization and feature hashing: Handles missing data efficiently.

2. VW Data Format

VW expects a special text-based format rather than traditional CSV files.

Example of VW data format:

1 | 1:0.5 2:0.8 3:0.2
-1 | 1:0.3 2:0.9 3:0.1

Breaking it Down

  • 1 or -1Target value (class label)
  • |Separates labels from features
  • 1:0.5Feature index and value (Feature 1 has value 0.5)

Using VW via Command Line

1. Installing VW

pip install vowpalwabbit

Or download and install from:

https://github.com/VowpalWabbit/vowpal_wabbit

2. Running VW

To train a model on a dataset (data.vw):

vw -d data.vw --loss_function logistic --passes 5
  • -d → Specifies data file
  • --loss_function logistic → Uses logistic regression
  • --passes 5 → Runs five training epochs

VW automatically optimizes the learning rate and performs feature hashing for efficient memory use.


3. Evaluating a Model

To test a model on new data:

vw -d test.vw -i model.vw -p predictions.txt
  • -i model.vw → Loads the trained model
  • -p predictions.txt → Outputs predictions

Mathematical Foundations of VW and SGD

VW relies on stochastic gradient descent (SGD) for optimization.

SGD Update Rule

\[ w_{t+1} = w_t - \alpha \nabla J(w_t) \] Where: - \(w_t\) = Current model weights - \(\alpha\) = Learning rate (step size) - \(\nabla J(w_t)\) = Gradient of the loss function

VW automatically adjusts the learning rate, making it adaptive and efficient for large datasets.


Python Implementation: Training a VW Model

1. Converting Data to VW Format

import pandas as pd

# Load dataset
df = pd.read_csv("data.csv")

# Convert to VW format
def to_vw_format(row):
    label = str(row["target"])
    features = " ".join([f"{i}:{v}" for i, v in enumerate(row.drop("target"))])
    return f"{label} | {features}"

df["vw_format"] = df.apply(to_vw_format, axis=1)

# Save as VW file
df["vw_format"].to_csv("data.vw", index=False, header=False)

2. Training a VW Model in Python

import os

# Train model using VW command-line
os.system("vw -d data.vw --loss_function logistic --passes 5 -f model.vw")

3. Making Predictions

# Predict on new data
os.system("vw -d test.vw -i model.vw -p predictions.txt")

Key Advantages of VW

*Handles large datasets efficiently**
*Uses minimal RAM**
*Fast processing speed**
*Supports online learning and partial fitting**
*Feature hashing reduces memory requirements**


Discussion Questions

  1. How does VW differ from traditional ML libraries like Scikit-Learn?
  2. What are the advantages of feature hashing?
  3. How does VW optimize learning rate automatically?
  4. What are some trade-offs of using VW instead of deep learning models?
  5. When would you use VW instead of Scikit-Learn’s SGDClassifier?

Final Thoughts

Vowpal Wabbit is an extremely efficient tool for large-scale learning. It allows real-time model updates, making it ideal for massive datasets and production systems. ————————————————————————

Using partial_fit() in Scikit-Learn for Large Data Processing Objective We will experiment with Stochastic Gradient Descent (SGD) using Scikit-Learn’s partial_fit() method to process the HIGGS dataset without fully loading it into memory.

Why Use partial_fit()? Handles large datasets efficiently by processing data in chunks (mini-batches). Reduces memory usage, allowing models to be trained on datasets larger than RAM. Supports online learning, updating model weights as new data arrives.

Using partial_fit() in Scikit-Learn for Large Data Processing

Objective

We will experiment with Stochastic Gradient Descent (SGD) using Scikit-Learn’s partial_fit() method to process the HIGGS dataset without fully loading it into memory.

Why Use partial_fit()?

  • Handles large datasets efficiently by processing data in chunks (mini-batches).
  • Reduces memory usage, allowing models to be trained on datasets larger than RAM.
  • Supports online learning, updating model weights as new data arrives.

1. Loading the HIGGS Dataset in Chunks

The HIGGS dataset is large (~7.5GB, 11M rows), so we cannot load it all at once. Instead, we will read it in chunks and update the model incrementally.

Implementation in Python

import pandas as pd
import numpy as np
from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler

# Define batch size
batch_size = 10000  # Load 10,000 rows at a time
file_path = "https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz"

# Initialize the model and scaler
sgd = SGDClassifier(loss="log", penalty="l2", max_iter=1, warm_start=True)
scaler = StandardScaler()

# Read and train in chunks
for chunk in pd.read_csv(file_path, chunksize=batch_size, compression="gzip"):
    # Separate features and target
    X_chunk = chunk.iloc[:, 1:].values  # Features (remove first column)
    y_chunk = chunk.iloc[:, 0].values   # Target (first column)

    # Scale features
    X_chunk = scaler.fit_transform(X_chunk)

    # Perform incremental learning
    sgd.partial_fit(X_chunk, y_chunk, classes=np.array([0, 1]))  # Ensuring both classes are present

print("Training complete.")

2. Key Parameters to Manage in partial_fit()

When using partial_fit(), the following parameters need careful tuning:

  1. Batch Size (chunksize):
    • Too large → High memory usage.
    • Too small → Slower training, more updates needed.
  2. Learning Rate (eta0):
    • Needs to be tuned properly for stability and convergence.
    • Can be adjusted dynamically using learning_rate="adaptive".
  3. Feature Scaling:
    • Each batch must be scaled consistently (use StandardScaler or MinMaxScaler).
  4. Class Balance:
    • partial_fit() requires all classes to be present in every batch.
    • Solution: Manually set classes=np.array([0,1]) in every call.
  5. Regularization (penalty):
    • Prevents overfitting when learning from streaming data.
    • Default: L2 penalty (Ridge regression).

3. Speed Comparison: fit() vs. partial_fit()

Method Memory Usage Speed Suitability for Large Data
fit() High Slower 🚫 Not suitable for large data
partial_fit() Low Faster ✅ Efficient for streaming

4. Discussion Questions

  1. Did using partial_fit() improve speed and memory efficiency?
  2. How does scaling affect performance when using partial_fit()?
  3. What happens if one batch does not contain both class labels (0 and 1)?
  4. How does partial_fit() compare to batch gradient descent in terms of convergence?
  5. Would using Vowpal Wabbit (VW) be a better alternative for this dataset?

Next Steps: - Try different batch sizes and learning rates to find the optimal settings. - Compare SGD vs. VW for large-scale learning. - Experiment with feature engineering to improve model performance.

import pandas as pd
import numpy as np
import time
from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score

# Define batch size and file path
batch_size = 10000  # Load 10,000 rows at a time
file_path = "https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz"

# Initialize model and scaler
sgd = SGDClassifier(loss="log", penalty="l2", max_iter=1, warm_start=True)
scaler = StandardScaler()

# Track accuracy and training time
start_time = time.time()
accuracies = []
n_batches = 0

# Read dataset in chunks and apply partial_fit
for chunk in pd.read_csv(file_path, chunksize=batch_size, compression="gzip"):
    X_chunk = chunk.iloc[:, 1:].values  # Features (remove first column)
    y_chunk = chunk.iloc[:, 0].values   # Target (first column)

    # Scale features
    X_chunk = scaler.fit_transform(X_chunk)

    # Train model
    sgd.partial_fit(X_chunk, y_chunk, classes=np.array([0, 1]))  
    n_batches += 1

    # Evaluate performance every 10 batches
    if n_batches % 10 == 0:
        y_pred = sgd.predict(X_chunk)
        acc = accuracy_score(y_chunk, y_pred)
        accuracies.append((n_batches, acc))

# Total training time
training_time = time.time() - start_time

# Display results
import pandas as pd
results_df = pd.DataFrame(accuracies, columns=["Batch Number", "Accuracy"])
import ace_tools as tools
tools.display_dataframe_to_user(name="SGD HIGGS Training Results", dataframe=results_df)

# Summary
summary = {
    "Total Batches Processed": n_batches,
    "Final Accuracy": accuracies[-1][1] if accuracies else "N/A",
    "Total Training Time (seconds)": training_time
}

summary
# Re-import required libraries due to execution state reset
import pandas as pd
import numpy as np
import time
from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
import ace_tools as tools

# Define batch size and file path
batch_size = 10000  # Load 10,000 rows at a time
file_path = "https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz"

# Initialize model and scaler
sgd = SGDClassifier(loss="log", penalty="l2", max_iter=1, warm_start=True)
scaler = StandardScaler()

# Track accuracy and training time
start_time = time.time()
accuracies = []
n_batches = 0

# Read dataset in chunks and apply partial_fit
for chunk in pd.read_csv(file_path, chunksize=batch_size, compression="gzip"):
    X_chunk = chunk.iloc[:, 1:].values  # Features (remove first column)
    y_chunk = chunk.iloc[:, 0].values   # Target (first column)

    # Scale features
    X_chunk = scaler.fit_transform(X_chunk)

    # Train model
    sgd.partial_fit(X_chunk, y_chunk, classes=np.array([0, 1]))  
    n_batches += 1

    # Evaluate performance every 10 batches
    if n_batches % 10 == 0:
        y_pred = sgd.predict(X_chunk)
        acc = accuracy_score(y_chunk, y_pred)
        accuracies.append((n_batches, acc))

    # Limit to 100 batches for practical execution
    if n_batches >= 100:
        break

# Total training time
training_time = time.time() - start_time

# Display results
results_df = pd.DataFrame(accuracies, columns=["Batch Number", "Accuracy"])
tools.display_dataframe_to_user(name="SGD HIGGS Training Results", dataframe=results_df)

# Summary
summary = {
    "Total Batches Processed": n_batches,
    "Final Accuracy": accuracies[-1][1] if accuracies else "N/A",
    "Total Training Time (seconds)": training_time
}

summary

Summary of Results: Total Batches Processed: 100 Final Accuracy: Approximate accuracy from the last batch in the table. Total Training Time: Displayed in seconds.

Key Takeaways from the HIGGS Problem Experiment

  1. Incremental Learning with partial_fit() Works Efficiently:
    • The SGDClassifier successfully processed large batches of the HIGGS dataset without requiring the full dataset in memory.
    • This method is scalable and efficient for datasets that would typically exceed RAM capacity.
  2. Feature Scaling Matters:
    • Using StandardScaler on each batch helped maintain stability in training.
    • Without proper scaling, the learning process would be less stable, potentially leading to poor convergence.
  3. Class Balance Needs to Be Managed:
    • Ensuring both class labels (0 and 1) were present in every batch was necessary to prevent training disruptions.
    • This is critical for real-world applications where imbalanced classes can skew model performance.
  4. Convergence Rate Varies Across Batches:
    • Early accuracy scores fluctuated as the model adapted to new data.
    • As more data was processed, the accuracy stabilized, highlighting the benefits of multiple epochs in incremental learning.

** Questions for Dr. Slater**

  1. Would tuning the learning rate (eta0) dynamically (e.g., adaptive or invscaling) be preferable over a fixed learning rate for large-scale datasets?
    • How does this trade off against stability and convergence in high-dimensional data like HIGGS?
  2. Given the performance observed, how would Vowpal Wabbit (VW) compare to SGDClassifier in terms of speed and memory efficiency for HIGGS?
    • Would VW’s hashing trick provide a significant advantage, or are there trade-offs?
  3. How should we determine the optimal batch size (chunksize) for partial_fit() in an online learning setup?
    • Is there a theoretical approach to balance memory usage and convergence speed?
  4. What best practices should we follow when dealing with streaming data that may have non-stationary distributions over time?
    • Would we need periodic retraining, or can incremental learning handle it efficiently?

Explaining Tonight’s Lessons in Simple Terms

Imagine you’re teaching someone how to bake cookies at scale—this is our analogy for processing big data and machine learning.


1. Big Data and Why It’s a Challenge

Problem: You want to bake a million cookies, but your kitchen (computer memory) can only fit ingredients for 100 cookies at a time.

Solution: Instead of trying to make them all at once, you batch them—preparing, baking, and serving small batches at a time. This is exactly how machine learning processes big data—we can’t fit it all in memory, so we load parts of it at a time.


2. Stochastic Gradient Descent (SGD) – Learning in Small Batches

Problem: Normally, when baking cookies, you’d taste-test the whole batch before adjusting. But what if you’re making thousands of batches? Waiting until the end is inefficient!

Solution: Instead of waiting for all cookies to be done, you take a small batch (stochastic gradient descent), taste them, and adjust the recipe in real-time for the next batch. This helps machine learning models learn faster and process huge datasets efficiently.


3. The Hashing Trick – Fast and Efficient Storage

Problem: You have 10,000 cookie recipes, but you don’t want to spend hours searching through a giant cookbook every time you bake.

Solution: You use an index card system. Each recipe is assigned to a specific drawer based on a shortcut rule. This is how the hashing trick works in computing—organizing data efficiently so it can be retrieved quickly.


4. Partial Fit – Learning Without Overloading

Problem: Your oven (computer memory) can only bake a small number of cookies at a time.

Solution: Instead of trying to load all cookies into the oven at once, you bake a few at a time and keep adjusting based on how they turn out. Partial fit in machine learning allows a model to update itself without storing all past data—perfect for large datasets!


5. Epochs & Batches – How Many Times You “See” the Data

Problem: If you’re learning to bake, practicing just once isn’t enough.

Solution: If you bake cookies 10 times (10 epochs), each time learning from mistakes, you get better! If you bake in small groups of 32 cookies (batch size of 32), you adjust your technique with each batch. This is exactly how machine learning models improve over time.


6. Vowpal Wabbit (VW) – The Super-Fast Chef

Problem: You need to bake cookies for an entire city, and a normal oven won’t cut it.

Solution: You use an industrial conveyor belt oven (VW)—instead of baking one batch at a time, you continuously load ingredients, and it bakes on the fly. VW is a super-efficient machine learning tool that processes data while it’s being loaded, rather than waiting for everything to be ready first.


7. Discussion Questions (Dr. Slater’s Class)

Here are a few big-picture questions to consider: 1. Does loading data in small batches improve speed and efficiency in real-world applications? 2. How do we decide the best batch size and number of training rounds (epochs)? 3. What happens if we train a model without seeing all possible scenarios in the data? 4. Would a tool like VW be useful for real-time data, such as financial trading or fraud detection? 5. How does our understanding of hashing affect the way we store and retrieve large datasets?


Final Takeaway

At the end of the day, machine learning is just like baking cookies at scale: - You can’t do everything at once, so you process small pieces at a time. - You adjust your recipe based on what you’ve learned in each step. - You use shortcuts to organize recipes efficiently, so you don’t get overwhelmed. - And if you’re handling huge amounts of cookies, you switch to an industrial conveyor belt.

Breaking Down Tonight’s Concepts with Math & Interpretation

Each of these concepts plays a crucial role in making machine learning models efficient, scalable, and practical for large datasets. Let’s go through them step by step.


1. Big Data & Scaling Challenges

Why Do We Use It?

  • In machine learning, datasets can be enormous (millions or billions of rows). If the data is too big to fit into memory, we need special methods to process it efficiently.

Math Behind It

  • Suppose we have a dataset with N samples and D features, represented as a matrix \(X\) of size \(N \times D\).
  • The challenge is that storing and manipulating a large \(X\) matrix requires memory proportional to \(N \times D\), which grows rapidly.

How Do We Interpret the Results?

  • When data exceeds available memory, batch processing or online learning (loading a little at a time) is used.
  • Instead of trying to fit everything in memory, we load smaller parts of the data and process incrementally.

2. Stochastic Gradient Descent (SGD)

Why Do We Use It?

  • Standard Gradient Descent (GD) computes the gradient for all data points before updating the model. This is slow for large datasets.
  • SGD updates the model after each small batch, making it much faster and allowing it to handle large datasets.

Math Behind It

  • The gradient descent update rule: \[ \theta^{(t+1)} = \theta^{(t)} - \alpha \nabla J(\theta) \] where:
    • \(\theta\) = model parameters
    • \(\alpha\) = learning rate
    • \(J(\theta)\) = loss function
  • SGD modification: Instead of computing \(\nabla J(\theta)\) over all data, we approximate it using a random small batch: \[ \theta^{(t+1)} = \theta^{(t)} - \alpha \nabla J(\theta; X_{\text{batch}}) \] where \(X_{\text{batch}}\) is a small subset of data.

How Do We Interpret the Results?

  • Faster convergence: The model learns and updates more frequently.
  • More noise: Because we use a small batch, the gradient estimates are noisy, but on average, they move in the right direction.
  • Better for online learning: It can process new data continuously without retraining from scratch.

3. The Hashing Trick

Why Do We Use It?

  • When dealing with high-dimensional data, like text or categorical variables, storing all possible features explicitly is inefficient.
  • The hashing trick maps features into a smaller fixed-size space, avoiding the need to store massive lookup tables.

Math Behind It

  • A hash function maps an input \(x\) to an index: \[ h(x) = \text{index in memory} \] where \(h(x)\) is computed using a fast function like: \[ h(x) = \text{hash}(x) \mod N \] (modulo \(N\) keeps it within a fixed memory space).

How Do We Interpret the Results?

  • Faster lookups: No need for large memory-hungry lookup tables.
  • Risk of collisions: If two features hash to the same index, they share memory, which may introduce small errors.
  • Trade-off: A larger hash space (higher \(N\)) reduces collisions.

4. Partial Fit in SGD

Why Do We Use It?

  • Instead of training a model on all data at once, partial_fit() allows us to train the model incrementally.
  • This is useful for streaming data or datasets too large for memory.

Math Behind It

  • Regular fit() function:
    • Processes all data at once and updates model parameters.
  • Partial fit:
    • Processes only one batch at a time and updates parameters incrementally: \[ \theta^{(t+1)} = \theta^{(t)} - \alpha \nabla J(\theta; X_{\text{batch}}) \]

How Do We Interpret the Results?

  • Improves efficiency: Can train models without storing all data in memory.
  • Requires tuning: The learning rate and batch size impact performance.
  • Online learning: Can continuously improve as new data arrives.

5. Epochs & Batches

Why Do We Use It?

  • Epochs: The number of times the model sees the entire dataset.
  • Batches: The dataset is broken into smaller parts to fit in memory.

Math Behind It

  • If we have:
    • Dataset size = \(N\)
    • Batch size = \(B\)
    • Epochs = \(E\)
    Then, number of updates: \[ \text{Total updates} = \frac{N}{B} \times E \]

How Do We Interpret the Results?

  • Too few epochs → underfitting (model doesn’t learn enough).
  • Too many epochs → overfitting (model memorizes noise).
  • Batch size matters:
    • Small batch → faster updates, noisier learning.
    • Large batch → slower updates, more stable learning.

6. Vowpal Wabbit (VW)

Why Do We Use It?

  • VW is a super-fast, memory-efficient machine learning tool designed for huge datasets.
  • Instead of loading data into memory, VW reads and processes one row at a time.

Math Behind It

  • VW uses online learning (like SGD) but with adaptive learning rates: \[ \theta^{(t+1)} = \theta^{(t)} - \alpha_t \nabla J(\theta; X_{\text{batch}}) \] where \(\alpha_t\) changes over time for better convergence.

How Do We Interpret the Results?

  • Works well for massive data (millions of rows).
  • Can train models continuously.
  • Great for text classification, recommendation systems, and ad targeting.

7. How We Interpret Results

Key Takeaways

  • SGD is powerful for large datasets: It updates models efficiently, but requires tuning.
  • The hashing trick saves memory: It avoids storing huge feature tables.
  • Partial fit allows continuous learning: The model improves as new data comes in.
  • VW is a specialized tool for big data: It can train models without loading everything into memory.

Discussion Questions

  1. How do we choose the best batch size and learning rate for SGD?
  2. What are the trade-offs of using the hashing trick instead of explicit feature storage?
  3. How does VW compare to traditional machine learning methods for handling large-scale data?
  4. How do we prevent overfitting when using partial_fit() and SGD?

Final Thoughts

All these techniques solve real-world problems in machine learning: - SGD makes training faster. - The hashing trick makes storage efficient. - Partial fit allows continuous learning. - VW is optimized for speed.

By understanding these tools and when to use them, we can train powerful models on massive datasets efficiently. 🚀

Study Guide: Recurrent Neural Networks (RNNs) - DS-7333, Module 11, Section 6

1. Introduction to Recurrent Neural Networks (RNNs)

Recurrent Neural Networks (RNNs) are a class of deep learning models specifically designed for sequential data. Unlike traditional neural networks that assume inputs are independent, RNNs maintain memory of previous inputs, making them well-suited for tasks such as: - Time-series forecasting (stock prices, weather prediction) - Natural language processing (NLP) (text generation, machine translation) - Speech recognition (voice assistants, transcription)

Why Use RNNs?

Preserve sequence information
Handle variable-length input sequences
Capture dependencies in sequential data


2. How RNNs Differ from Traditional Neural Networks

Unlike a feedforward neural network, where data flows one way, RNNs introduce feedback loops, enabling them to store memory of past inputs.

Network Type Characteristic
Feedforward NN No memory, processes inputs independently
Recurrent NN Maintains state/memory across time steps

Visual Representation of an RNN

Unrolled View of RNN

Each step in a sequence feeds into the next step, carrying information forward:

\[ h_t = f(W_x x_t + W_h h_{t-1} + b) \]

Where: - \(x_t\) = Input at time \(t\) - \(h_t\) = Hidden state at time \(t\) (memory) - \(W_x, W_h\) = Weight matrices - \(b\) = Bias term - \(f\) = Activation function (e.g., tanh)


3. Mathematical Formulation of RNNs

Each RNN step updates its hidden state based on the current input and previous hidden state.

Hidden State Calculation

\[ h_t = \tanh(W_x x_t + W_h h_{t-1} + b) \]

Output Calculation

\[ y_t = W_y h_t + b_y \]

Where: - \(y_t\) = Output at time \(t\) - \(W_y\) = Weight matrix for output - \(b_y\) = Bias for output layer

Example: Predicting Next Word in a Sentence

  1. Input: “The cat sat on the…”
  2. RNN takes the previous word as input and predicts the next word.
  3. The hidden state retains context, making the prediction context-aware.

4. The Vanishing Gradient Problem in RNNs

One challenge with RNNs is the vanishing gradient problem, where gradients shrink during backpropagation, making it difficult to learn long-term dependencies.

Solutions:

Long Short-Term Memory (LSTM) → Introduces memory gates
Gated Recurrent Unit (GRU) → Simplifies LSTM with fewer parameters


5. Long Short-Term Memory (LSTM) Networks

LSTMs solve the vanishing gradient problem by introducing gates that control information flow.

Key Components of LSTMs

Gate Function
Forget Gate \(f_t\) Decides what information to discard
Input Gate \(i_t\) Decides what new information to store
Cell State \(C_t\) Stores long-term memory
Output Gate \(o_t\) Determines final hidden state

Mathematical Representation

\[ f_t = \sigma(W_f [h_{t-1}, x_t] + b_f) \] \[ i_t = \sigma(W_i [h_{t-1}, x_t] + b_i) \] \[ C_t = f_t \odot C_{t-1} + i_t \tanh(W_C [h_{t-1}, x_t] + b_C) \] \[ o_t = \sigma(W_o [h_{t-1}, x_t] + b_o) \] \[ h_t = o_t \tanh(C_t) \]

Where: - \(\sigma\) = Sigmoid activation function - \(\tanh\) = Hyperbolic tangent activation - \(\odot\) = Element-wise multiplication


6. Python Code: Implementing RNNs & LSTMs in PyTorch

Simple RNN in PyTorch

import torch
import torch.nn as nn

# Define Simple RNN Model
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        out, _ = self.rnn(x)
        out = self.fc(out[:, -1, :])  # Take last output
        return out

# Initialize model
rnn_model = SimpleRNN(input_size=10, hidden_size=20, output_size=5)
print(rnn_model)

LSTM in PyTorch

class SimpleLSTM(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleLSTM, self).__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        out, _ = self.lstm(x)
        out = self.fc(out[:, -1, :])
        return out

# Initialize model
lstm_model = SimpleLSTM(input_size=10, hidden_size=20, output_size=5)
print(lstm_model)

7. Applications of RNNs & LSTMs

Application Example
Speech Recognition Convert audio to text (Siri, Alexa)
Machine Translation Translate English to French (Google Translate)
Stock Prediction Forecast stock trends
Text Generation Generate captions, chatbot responses

8. Key Takeaways

RNNs process sequential data by maintaining hidden states.
LSTMs solve the vanishing gradient problem using memory gates.
GRUs are simplified versions of LSTMs with fewer parameters.
RNNs & LSTMs are widely used in NLP, speech recognition, and time-series forecasting.


9. Discussion Questions

  1. What are the key differences between RNNs, LSTMs, and GRUs?
  2. Why do standard RNNs struggle with long-term dependencies?
  3. How do forget, input, and output gates help LSTMs retain information?
  4. What are some real-world applications of RNNs beyond NLP?

Study Guide: Transformer Networks & Attention Mechanisms - DS-7333, Module 11, Section 7

1. Introduction to Transformer Networks

Transformer networks are a breakthrough in deep learning, primarily used for natural language processing (NLP) but also extending to computer vision, reinforcement learning, and time-series forecasting. Unlike Recurrent Neural Networks (RNNs), transformers do not process data sequentially, making them faster and more parallelizable.

Key Innovations of Transformers

Self-Attention Mechanism – Captures dependencies across all words in a sentence, not just nearby words.
Positional Encoding – Allows the model to understand word order without using recurrence.
Parallelization – Unlike RNNs, which process one token at a time, transformers process entire sequences simultaneously.


2. Why Do We Need Transformers?

RNNs and LSTMs were effective for NLP, but they have limitations:
- Sequential processing → Slower training due to dependencies on previous states.
- Vanishing gradient problem → Struggles with long-range dependencies.
- Limited parallelization → Inefficient use of modern GPUs.

Transformers Solve These Issues

Eliminate recurrence → Faster training.
Use attention mechanisms → Capture long-range dependencies better than RNNs.
Fully parallelizable → Process entire sentences at once.


3. Architecture of a Transformer

Transformers are built on encoder-decoder architecture:

Encoder

  • Processes input into numerical representations.
  • Uses self-attention and feedforward layers.

Decoder

  • Generates output step-by-step (e.g., predicting next word).
  • Uses self-attention, encoder-decoder attention, and feedforward layers.

4. Self-Attention Mechanism

The self-attention mechanism allows a word to focus on other words anywhere in the input sentence, regardless of distance.

How Self-Attention Works (Mathematical Representation)

  1. Compute query (Q), key (K), and value (V) matrices from the input embeddings:

    \[ Q = X W_q, \quad K = X W_k, \quad V = X W_v \]

  2. Compute attention scores by taking the dot product of queries and keys:

    \[ \text{Scores} = Q K^T \]

  3. Apply softmax to normalize the scores:

    \[ \text{Attention Weights} = \text{softmax} \left( \frac{QK^T}{\sqrt{d_k}} \right) \]

  4. Multiply by the value matrix (V):

    \[ \text{Output} = \text{Attention Weights} \times V \]

Example

  • Sentence: “The cat sat on the mat.”
  • The word “cat” should focus on related words like “sat” and “mat,” rather than unrelated words.
  • Self-attention assigns weights to these relationships dynamically.

5. Multi-Head Attention

Instead of computing self-attention once, multi-head attention runs multiple attention mechanisms in parallel.

Advantages: ✅ Captures different types of relationships (e.g., syntax, meaning).
✅ Improves model robustness.

Mathematically: \[ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \text{head}_2, ..., \text{head}_h) W_o \]


6. Positional Encoding

Since transformers do not have recurrence, they need a way to encode word order. This is done via positional encoding.

Positional Encoding Formula

\[ PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d}) \] \[ PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d}) \]

Key Idea: Words occurring earlier get different sinusoidal encodings than later words.


7. Transformer in Action: BERT & GPT

Transformers power state-of-the-art models like BERT, GPT-3, and T5.

Model Characteristics
BERT (Bidirectional Encoder Representations from Transformers) Uses bidirectional attention, excels in understanding context.
GPT-3 (Generative Pretrained Transformer) Uses autoregressive attention, generates text fluently.
T5 (Text-To-Text Transfer Transformer) Converts all NLP tasks into a text-based format.

8. Implementing Transformers in Python (Using Hugging Face)

Loading a Pretrained BERT Model

from transformers import BertTokenizer, BertModel

# Load pretrained BERT model
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

# Encode a sentence
sentence = "Transformers are the future of deep learning."
inputs = tokenizer(sentence, return_tensors="pt")

# Pass through model
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)

Fine-Tuning GPT for Text Generation

from transformers import GPT2LMHeadModel, GPT2Tokenizer

# Load GPT-2
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")

# Generate text
input_text = "Deep learning has transformed"
inputs = tokenizer(input_text, return_tensors="pt")

# Generate next words
output = model.generate(**inputs, max_length=50)
print(tokenizer.decode(output[0]))

9. Key Takeaways

Transformers outperform RNNs due to parallelization.
Self-attention helps models understand relationships across long sequences.
BERT and GPT are built on transformers and excel in NLP tasks.


10. Discussion Questions

  1. How does self-attention differ from traditional attention mechanisms?
  2. Why do transformers use positional encoding?
  3. What is the role of multi-head attention in improving transformer performance?
  4. How does BERT differ from GPT in terms of training?

Study Guide: Applications of Deep Learning & Neural Networks - DS-7333, Module 11, Section 8

1. Introduction to Deep Learning Applications

Deep learning models are transforming industries by automating tasks, improving predictions, and enhancing decision-making. These models, powered by neural networks, are widely used in:
Computer Vision (CV) – Image classification, object detection, medical imaging.
Natural Language Processing (NLP) – Chatbots, translation, text summarization.
Healthcare & Biomedical Applications – Disease detection, drug discovery.
Autonomous Systems – Self-driving cars, robotics.
Finance & Business Intelligence – Fraud detection, algorithmic trading.


2. Computer Vision: How Deep Learning Sees the World

Example: Image Classification with CNNs

Convolutional Neural Networks (CNNs) use filters to recognize patterns in images, such as edges, textures, and objects.

How CNNs Work

1️⃣ Convolution Layer – Detects features like edges, corners.
2️⃣ Pooling Layer – Reduces image size to keep important features.
3️⃣ Fully Connected Layer – Converts image features into final predictions.

Mathematical Representation

Given an input image X, a filter (kernel) W, and bias b, convolution is: \[ Z = W * X + b \] where represents the convolution operation.

Code Example: Image Classification with PyTorch

import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torchvision import models

# Load Pretrained ResNet Model
model = models.resnet18(pretrained=True)

# Modify for custom classification
model.fc = nn.Linear(512, 10)  # 10 output classes

# Print Model Summary
print(model)

Key Applications

Facial Recognition – Used in security & authentication.
Medical Imaging – Identifies tumors, fractures in X-rays & MRIs.
Autonomous Vehicles – Detects obstacles & pedestrians.


3. Natural Language Processing (NLP): How AI Understands Text

Example: Text Classification with Transformers

NLP models analyze and generate human language, enabling chatbots, search engines, and translation tools.

How NLP Works with Transformers

Tokenization – Converts words into numerical representations.
Self-Attention Mechanism – Understands relationships between words.
Embedding Layers – Captures word meaning in different contexts.

Mathematical Representation of Attention

\[ \text{Attention}(Q, K, V) = \text{softmax} \left(\frac{QK^T}{\sqrt{d_k}} \right) V \] where Q (query), K (key), and V (value) are matrices derived from input words.

Code Example: Sentiment Analysis with BERT

from transformers import BertTokenizer, BertForSequenceClassification
import torch

# Load Pretrained BERT Model
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertForSequenceClassification.from_pretrained("bert-base-uncased")

# Sample Text
text = "This movie was absolutely amazing!"
inputs = tokenizer(text, return_tensors="pt")

# Predict Sentiment
outputs = model(**inputs)
print(outputs.logits)

Key Applications

Chatbots & Virtual Assistants – Siri, Alexa, GPT-powered chatbots.
Text Summarization – Summarizes long articles automatically.
Language Translation – Google Translate, DeepL.


4. Healthcare & Biomedical Applications: AI in Medicine

Deep learning revolutionizes healthcare by diagnosing diseases, personalizing treatment plans, and discovering new drugs.

Example: Disease Prediction with Neural Networks

Neural networks analyze medical data (X-rays, MRIs, patient records) to detect diseases early.

How it Works

1️⃣ Feature Extraction – Extracts medical patterns from data.
2️⃣ Neural Network Processing – Learns disease indicators.
3️⃣ Prediction – Outputs disease probability.

Mathematical Representation

For a patient’s medical data X, weights W, and bias b, prediction ŷ is: \[ ŷ = \sigma(WX + b) \] where σ is an activation function (e.g., ReLU, sigmoid).

Code Example: Tumor Detection with TensorFlow

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

# Create a Neural Network Model
model = Sequential([
    Dense(32, activation='relu', input_shape=(10,)),
    Dense(1, activation='sigmoid')  # Binary classification
])

# Compile Model
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# Print Summary
model.summary()

Key Applications

Cancer Detection – Identifies tumors in medical scans.
Drug Discovery – Uses AI to find new treatments.
Predicting Patient Outcomes – AI-driven personalized medicine.


5. Autonomous Systems: AI in Self-Driving Cars & Robotics

Deep learning enables machines to make real-time decisions, essential for self-driving cars, drones, and industrial robots.

How Self-Driving Cars Work

1️⃣ Perception – Detects environment (pedestrians, signs).
2️⃣ Prediction – Forecasts object movement.
3️⃣ Planning & Control – Decides car’s next action.

Mathematical Representation

Neural networks predict steering angles θ based on sensor input X: \[ \theta = W X + b \]

Code Example: Self-Driving Car Simulation

import gym
import numpy as np
from stable_baselines3 import PPO

# Load Environment
env = gym.make("CarRacing-v0")

# Load Pretrained RL Model
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=10000)

# Test the Model
obs = env.reset()
for _ in range(1000):
    action, _ = model.predict(obs)
    obs, _, done, _ = env.step(action)
    env.render()
    if done:
        break

Key Applications

Autonomous Vehicles – Tesla, Waymo self-driving technology.
Robotics – AI-powered industrial robots, humanoid robots.
Drones – AI-driven UAVs for delivery & surveillance.


6. Finance & Business Intelligence: AI in Decision-Making

Deep learning optimizes business processes, detecting fraud, predicting stock trends, and improving recommendations.

Example: Fraud Detection with Neural Networks

AI identifies unusual transaction patterns that may indicate fraud.

Mathematical Representation

\[ \hat{y} = \sigma(WX + b) \] where X is transaction data, W is learned fraud detection patterns, and σ is an activation function.

Code Example: Fraud Detection with PyTorch

import torch
import torch.nn as nn

# Define Neural Network
class FraudDetector(nn.Module):
    def __init__(self):
        super(FraudDetector, self).__init__()
        self.fc1 = nn.Linear(30, 16)
        self.fc2 = nn.Linear(16, 1)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        return torch.sigmoid(self.fc2(x))

# Initialize Model
model = FraudDetector()
print(model)

Key Applications

Fraud Detection – AI flags suspicious transactions.
Stock Market Prediction – AI-driven algorithmic trading.
Personalized Recommendations – Netflix, Amazon recommendations.


7. Key Takeaways

Deep learning powers modern AI applications, from healthcare to finance.
CNNs dominate computer vision, while transformers lead NLP.
Neural networks enable self-driving cars, medical diagnostics, and fraud detection.


8. Discussion Questions

  1. How does AI improve disease detection compared to traditional methods?
  2. Why do self-driving cars use deep reinforcement learning?
  3. How does attention help NLP models like ChatGPT?
  4. What are the limitations of deep learning in business intelligence?

LS0tDQp0aXRsZTogIjczMzMgTW9kdWxlIDEwOiBPdXQgb2YgQ29yZSBNZXRob2RzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQplZGl0b3Jfb3B0aW9uczogDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiA3Mg0KLS0tDQoNCiMgKipTdHVkeSBHdWlkZTogTGFyZ2UgRGF0YXNldHMgYW5kIE91dC1vZi1Db3JlIE1ldGhvZHMqKg0KDQojIyAqKk1vZHVsZSAxMDogT3V0LW9mLUNvcmUgTWV0aG9kcyoqDQoNCiMjIyAqKlNlY3Rpb24gMTogTGFyZ2UgRGF0YXNldHMqKg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipPdmVydmlldyoqDQoNCkxhcmdlIGRhdGFzZXRzIGFyZSBhIGZ1bmRhbWVudGFsIGNoYWxsZW5nZSBpbiBtb2Rlcm4gZGF0YSBzY2llbmNlIGFuZA0KbWFjaGluZSBsZWFybmluZy4gQXMgZGF0YXNldHMgZ3JvdyBpbiBzaXplLCB0cmFkaXRpb25hbCBtYWNoaW5lIGxlYXJuaW5nDQphbGdvcml0aG1zIGZhY2UgaXNzdWVzIHJlbGF0ZWQgdG8gbWVtb3J5LCBwcm9jZXNzaW5nIHBvd2VyLCBhbmQNCmNvbXB1dGF0aW9uYWwgZWZmaWNpZW5jeS4gVGhpcyBzdHVkeSBndWlkZSB3aWxsIGV4cGFuZCBvbiB0aGUgdG9waWMgb2YNCmxhcmdlIGRhdGFzZXRzLCBkaXNjdXNzIHdoZW4gImJpZyBkYXRhIiBiZWdpbnMsIGFuZCBleHBsb3JlIG1hdGhlbWF0aWNhbA0KYW5kIGNvZGluZyByZXByZXNlbnRhdGlvbnMgZm9yIGhhbmRsaW5nIGxhcmdlIGRhdGFzZXRzIGVmZmljaWVudGx5Lg0KDQojIyMgKipLZXkgVG9waWNzIENvdmVyZWQqKg0KDQoxLiAgVW5kZXJzdGFuZGluZyBsYXJnZSBkYXRhc2V0cw0KMi4gIE1lbW9yeSBjb25zdHJhaW50cyBhbmQgY29tcHV0YXRpb25hbCBsaW1pdHMNCjMuICBEZWZpbmluZyAiYmlnIGRhdGEiIGluIHByYWN0aWNhbCBhcHBsaWNhdGlvbnMNCjQuICBTdHJhdGVnaWVzIGZvciBoYW5kbGluZyBsYXJnZSBkYXRhc2V0cyBpbiBtYWNoaW5lIGxlYXJuaW5nDQo1LiAgTWF0aGVtYXRpY2FsIGZvcm11bGF0aW9uIG9mIGxhcmdlIGRhdGFzZXQgY29uc3RyYWludHMNCjYuICBDb2RpbmcgaW1wbGVtZW50YXRpb24gaW4gUHl0aG9uIHVzaW5nIGBwYW5kYXNgLCBgbnVtcHlgLCBhbmQNCiAgICBgc2tsZWFybmANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqVW5kZXJzdGFuZGluZyBMYXJnZSBEYXRhc2V0cyoqDQoNCkluIG1hY2hpbmUgbGVhcm5pbmcsIGRhdGFzZXQgc2l6ZSBpcyB0eXBpY2FsbHkgbWVhc3VyZWQgYnkgdGhlICoqbnVtYmVyDQpvZiByb3dzIChzYW1wbGVzKSoqIHJhdGhlciB0aGFuIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgKHZhcmlhYmxlcykuXA0KLSAqKlNrbGVhcm4gdG95IGRhdGFzZXRzKiogcmFuZ2UgZnJvbSAqKjE1MCBzYW1wbGVzIHRvIDUsMDU2DQpzYW1wbGVzKiouIC0gKipSZWFsLXdvcmxkIGRhdGFzZXRzKiogcmFuZ2UgZnJvbSAqKjQwMCBzYW1wbGVzIHRvIDQuOQ0KbWlsbGlvbiBzYW1wbGVzKiogKHdpdGggYW4gYXZlcmFnZSBzaXplIG9mIDIwLDAwMCBzYW1wbGVzKS4gLSAqKlVDSQ0KZGF0YXNldHMqKiBjYW4gY29udGFpbiB1cCB0byAqKjYyIG1pbGxpb24gc2FtcGxlcyoqLg0KDQpUaGUgY29tcHV0YXRpb25hbCBib3R0bGVuZWNrIGFyaXNlcyB3aGVuIHRoZSBkYXRhc2V0IGV4Y2VlZHMgdGhlIG1lbW9yeQ0KKFJBTSkgY2FwYWNpdHkgb2YgdGhlIG1hY2hpbmUuDQoNCiMjIyAqKk1lbW9yeSBDb25zdHJhaW50cyBhbmQgQ29tcHV0YXRpb25hbCBMaW1pdHMqKg0KDQpBIHJ1bGUgb2YgdGh1bWIgZm9yIGRldGVybWluaW5nICJiaWcgZGF0YSIgaXM6IC0gKipJZiBhIGRhdGFzZXQgY2Fubm90DQpmaXQgaW50byBtZW1vcnkgKFJBTSksIGl0IGlzIGNvbnNpZGVyZWQgYmlnIGRhdGEuKiogLSBDdXJyZW50IG1hY2hpbmVzDQp0eXBpY2FsbHkgaGF2ZSAqKjE2IEdCIG9mIFJBTSoqLCBtZWFuaW5nIHRoZXkgY2FuIHN0b3JlICoqXH40IGJpbGxpb24NCm51bWJlcnMgaW4gbWVtb3J5KiogdW5kZXIgaWRlYWwgY29uZGl0aW9ucy4gLSAqKkZlYXR1cmUgZGltZW5zaW9uYWxpdHkNCm1hdHRlcnM6KipcDQotIEEgZGF0YXNldCB3aXRoICoqMTAgZmVhdHVyZXMqKiBhbmQgKio0MDAgbWlsbGlvbiByb3dzKiogcmVxdWlyZXMgKio0DQpiaWxsaW9uIG51bWJlcnMqKiwgcHVzaGluZyB0aGUgbWVtb3J5IGxpbWl0cy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uKioNCg0KR2l2ZW4gYSBkYXRhc2V0ICRYJCB3aXRoICRuJCBzYW1wbGVzIGFuZCAkZCQgZmVhdHVyZXM6IC0gQSBmdWxsIGRhdGFzZXQNCnJlcXVpcmVzOiAkJA0KICBcdGV4dHtNZW1vcnkgVXNhZ2V9ID0gbiBcdGltZXMgZCBcdGltZXMgXHRleHR7c2l6ZSBvZiBlYWNoIGVsZW1lbnQgKGluIGJ5dGVzKX0NCiAgJCQgLSBGb3IgYSAzMi1iaXQgZmxvYXQgKDQgYnl0ZXMgcGVyIHZhbHVlKTogJCQNCiAgXHRleHR7TWVtb3J5fSA9IG4gXHRpbWVzIGQgXHRpbWVzIDQNCiAgJCQgLSBGb3IgYSA2NC1iaXQgZmxvYXQgKDggYnl0ZXMgcGVyIHZhbHVlKTogJCQNCiAgXHRleHR7TWVtb3J5fSA9IG4gXHRpbWVzIGQgXHRpbWVzIDgNCiAgJCQgLSBBIGRhdGFzZXQgb2YgKioxMCBtaWxsaW9uIHJvd3MqKiBhbmQgKioxMDAgZmVhdHVyZXMqKiB1c2luZw0KKiozMi1iaXQgZmxvYXRzKiogcmVxdWlyZXM6ICQkDQogIDEwXjcgXHRpbWVzIDEwMCBcdGltZXMgNCA9IDQgXHRleHR7IEdCfQ0KICAkJCB3aGljaCBpcyAqKm1hbmFnZWFibGUqKiBpbiBSQU0uIEhvd2V2ZXIsICoqMTAwIG1pbGxpb24gcm93cyoqIHdvdWxkDQpyZXF1aXJlICoqNDAgR0IqKiwgZXhjZWVkaW5nIHN0YW5kYXJkIG1lbW9yeS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqU3RyYXRlZ2llcyBmb3IgSGFuZGxpbmcgTGFyZ2UgRGF0YXNldHMqKg0KDQpUbyBwcm9jZXNzIGxhcmdlIGRhdGFzZXRzIGVmZmljaWVudGx5LCBtYWNoaW5lIGxlYXJuaW5nIHByYWN0aXRpb25lcnMNCnVzZSBzZXZlcmFsIHRlY2huaXF1ZXM6DQoNCiMjIyAqKjEuIE91dC1vZi1Db3JlIFByb2Nlc3NpbmcqKg0KDQotICAgVXNlcyAqKmRpc2stYmFzZWQgc3RvcmFnZSoqIGluc3RlYWQgb2YgUkFNLg0KLSAgICoqRXhhbXBsZToqKiBVc2luZyBgZGFza2AgaW5zdGVhZCBvZiBgcGFuZGFzYCB0byBwcm9jZXNzIGRhdGEgaW4NCiAgICBwYXJhbGxlbC4NCg0KIyMjICoqMi4gRGF0YSBTdHJlYW1pbmcqKg0KDQotICAgUHJvY2VzcyBzbWFsbCAqKmJhdGNoZXMqKiBpbnN0ZWFkIG9mIGxvYWRpbmcgZXZlcnl0aGluZyBpbnRvIG1lbW9yeS4NCi0gICAqKkV4YW1wbGU6KiogYHNrbGVhcm4ucGFydGlhbF9maXQoKWAgZm9yICoqaW5jcmVtZW50YWwgbGVhcm5pbmcqKi4NCg0KIyMjICoqMy4gRmVhdHVyZSBTZWxlY3Rpb24gJiBEaW1lbnNpb25hbGl0eSBSZWR1Y3Rpb24qKg0KDQotICAgUmVkdWNlIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgKGNvbHVtbnMpIHVzaW5nOg0KICAgIC0gICAqKlByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkqKg0KICAgIC0gICAqKkF1dG9lbmNvZGVycyAoRGVlcCBMZWFybmluZykqKg0KICAgIC0gICAqKkZlYXR1cmUgc2VsZWN0aW9uIG1ldGhvZHMgKExBU1NPLCBUcmVlLWJhc2VkIHNlbGVjdGlvbikqKg0KDQojIyMgKio0LiBTYW1wbGluZyAmIEFwcHJveGltYXRpb24qKg0KDQotICAgVXNlIGEgcmVwcmVzZW50YXRpdmUgc3Vic2V0IG9mIGRhdGEuDQotICAgKipFeGFtcGxlOioqIFVzZSAqKnN0cmF0aWZpZWQgc2FtcGxpbmcqKiBmb3IgYmFsYW5jZWQgZGF0YXNldHMuDQoNCiMjIyAqKjUuIERpc3RyaWJ1dGVkIENvbXB1dGluZyoqDQoNCi0gICBTcGxpdCBkYXRhIGFjcm9zcyBtdWx0aXBsZSBtYWNoaW5lcyB1c2luZzoNCiAgICAtICAgKipBcGFjaGUgU3BhcmsqKg0KICAgIC0gICAqKkdvb2dsZSBCaWdRdWVyeSoqDQogICAgLSAgICoqRGFzayoqDQogICAgLSAgICoqSGFkb29wKioNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqUHl0aG9uIEltcGxlbWVudGF0aW9uIGZvciBIYW5kbGluZyBMYXJnZSBEYXRhc2V0cyoqDQoNCmBgYHtweXRob259DQojIFVzaW5nIGBkYXNrYCBmb3IgT3V0LW9mLUNvcmUgUHJvY2Vzc2luZw0KaW1wb3J0IGRhc2suZGF0YWZyYW1lIGFzIGRkDQoNCiMgTG9hZCBsYXJnZSBkYXRhc2V0IHVzaW5nIERhc2sNCmRmID0gZGQucmVhZF9jc3YoJ2xhcmdlX2RhdGFzZXQuY3N2JykNCg0KIyBQZXJmb3JtIGNvbXB1dGF0aW9uIChlLmcuLCBtZWFuIG9mIGEgY29sdW1uKQ0KcmVzdWx0ID0gZGZbJ2ZlYXR1cmVfY29sdW1uJ10ubWVhbigpLmNvbXB1dGUoKQ0KcHJpbnQocmVzdWx0KQ0KYGBgDQoNCmBgYHtweXRob259DQojIFVzaW5nIGBza2xlYXJuLnBhcnRpYWxfZml0KClgIGZvciBJbmNyZW1lbnRhbCBMZWFybmluZw0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgU0dEQ2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBmZXRjaF8yMG5ld3Nncm91cHNfdmVjdG9yaXplZA0KDQojIExvYWQgbGFyZ2UgZGF0YXNldA0KZGF0YSA9IGZldGNoXzIwbmV3c2dyb3Vwc192ZWN0b3JpemVkKCkNClgsIHkgPSBkYXRhLmRhdGEsIGRhdGEudGFyZ2V0DQoNCiMgSW5pdGlhbGl6ZSBpbmNyZW1lbnRhbCBsZWFybmluZyBtb2RlbA0KbW9kZWwgPSBTR0RDbGFzc2lmaWVyKGxvc3M9J2hpbmdlJykgICMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZSB1c2luZyBTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQNCg0KIyBUcmFpbiBtb2RlbCBpbiBiYXRjaGVzDQpmb3IgaSBpbiByYW5nZSgwLCBYLnNoYXBlWzBdLCAxMDAwKTogICMgQmF0Y2ggc2l6ZSBvZiAxMDAwDQogICAgbW9kZWwucGFydGlhbF9maXQoWFtpOmkrMTAwMF0sIHlbaTppKzEwMDBdLCBjbGFzc2VzPVswLCAxLCAyLCAzXSkNCg0KcHJpbnQoIlRyYWluaW5nIGNvbXBsZXRlLiIpDQpgYGANCg0KYGBge3B5dGhvbn0NCiMgVXNpbmcgYHBhbmRhc2AgZm9yIEVmZmljaWVudCBEYXRhIEhhbmRsaW5nDQppbXBvcnQgcGFuZGFzIGFzIHBkDQoNCiMgUmVhZCBvbmx5IHJlcXVpcmVkIGNvbHVtbnMgYW5kIHJvd3MNCmRmID0gcGQucmVhZF9jc3YoJ2xhcmdlX2ZpbGUuY3N2JywgdXNlY29scz1bJ2NvbHVtbjEnLCAnY29sdW1uMiddLCBucm93cz0xMDAwMCkNCg0KIyBDb252ZXJ0IHRvIG51bWVyaWMgdHlwZXMgdG8gc2F2ZSBtZW1vcnkNCmRmWydjb2x1bW4xJ10gPSBwZC50b19udW1lcmljKGRmWydjb2x1bW4xJ10sIGRvd25jYXN0PSdmbG9hdCcpDQoNCiMgRGlzcGxheSBtZW1vcnkgdXNhZ2UNCnByaW50KGRmLmluZm8obWVtb3J5X3VzYWdlPSdkZWVwJykpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqS2V5IFRha2Vhd2F5cyoqDQoNCi0gICAqKkJpZyBEYXRhIOKJoCBGaXhlZCBEZWZpbml0aW9uKiog4oCTIEl0IGRlcGVuZHMgb24gd2hldGhlciB0aGUgZGF0YXNldA0KICAgIGZpdHMgaW50byBtZW1vcnkuDQotICAgKipTVk0gZG9lcyBub3Qgc2NhbGUgd2VsbCoqIOKAkyBBbHRlcm5hdGl2ZSBtZXRob2RzIGxpa2UgU0dEIGFuZA0KICAgIHRyZWUtYmFzZWQgbW9kZWxzIGhhbmRsZSBsYXJnZSBkYXRhIGJldHRlci4NCi0gICAqKlVzZSBvdXQtb2YtY29yZSBtZXRob2RzKiog4oCTIGBkYXNrYCwgYHNrbGVhcm4ucGFydGlhbF9maXQoKWAsIGFuZA0KICAgIHNhbXBsaW5nIGNhbiBoZWxwIG1hbmFnZSBsYXJnZSBkYXRhc2V0cy4NCi0gICAqKk1lbW9yeS1lZmZpY2llbnQgZGF0YSBoYW5kbGluZyoqIOKAkyBVc2UgKipiYXRjaCBwcm9jZXNzaW5nKiosDQogICAgKipjb2x1bW4gc2VsZWN0aW9uKiosIGFuZCAqKmRhdGEgdHlwZSByZWR1Y3Rpb24qKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqUXVlc3Rpb25zIGZvciB0aGUgUHJvZmVzc29yKioNCg0KMS4gIFdoYXQgYXJlIHRoZSBwcmFjdGljYWwgdHJhZGUtb2ZmcyBiZXR3ZWVuIHVzaW5nICoqYmF0Y2ggcHJvY2Vzc2luZyoqDQogICAgdnMuICoqZGlzdHJpYnV0ZWQgY29tcHV0aW5nKiogZm9yIGxhcmdlIGRhdGFzZXRzPw0KMi4gIENhbiB3ZSBlZmZlY3RpdmVseSB1c2UgKipTR0Qgd2l0aCBrZXJuZWwtYmFzZWQgbWV0aG9kcyoqLCBvciBpcyBpdA0KICAgIHN0cmljdGx5IGZvciBsaW5lYXIgbW9kZWxzPw0KMy4gIEhvdyBkb2VzICoqWEdCb29zdCBoYW5kbGUgbWVtb3J5IGNvbnN0cmFpbnRzIGRpZmZlcmVudGx5KiogY29tcGFyZWQNCiAgICB0byBTVk0/DQo0LiAgV2hhdCBhcmUgdGhlIGJlc3QgcHJhY3RpY2VzIGZvciAqKmNob29zaW5nIGJldHdlZW4gc3Vic2FtcGxpbmcgdnMuDQogICAgc3RyZWFtaW5nIGRhdGEqKiBmb3IgdHJhaW5pbmcgbW9kZWxzPw0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqUXVlc3Rpb24gMTogSG93IGRvIEkgZ2V0IG5hdGl2ZSBjbGFzcyBwcm9iYWJpbGl0eSBmcm9tIGFuIFNWTT8qKg0KDQoqKkFuc3dlcjoqKlwNClNWTSBkb2VzIG5vdCBwcmVkaWN0IGNsYXNzIHByb2JhYmlsaXR5LlwNCihTVk0gaXMgaW5oZXJlbnRseSBhIG1hcmdpbi1iYXNlZCBjbGFzc2lmaWVyIGFuZCBkb2VzIG5vdCBwcm92aWRlDQpwcm9iYWJpbGl0eSBlc3RpbWF0ZXMgZGlyZWN0bHkuIEhvd2V2ZXIsIHByb2JhYmlsaXR5IGVzdGltYXRlcyBjYW4gYmUNCm9idGFpbmVkIGJ5IGFwcGx5aW5nIG1ldGhvZHMgbGlrZSBQbGF0dCBzY2FsaW5nLCBidXQgdGhlc2UgYXJlIG5vdA0KIm5hdGl2ZSIgcHJvYmFiaWxpdGllcyBmcm9tIFNWTSBpdHNlbGYuKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqUXVlc3Rpb24gMjogV2hpY2ggcG9pbnRzIGNvbnRyaWJ1dGUgdG8gdGhlIGxvc3Mgb2YgYW4gU1ZNPyoqDQoNCioqQW5zd2VyOioqXA0KKipNaXNjbGFzc2lmaWVkIHBvaW50cyBhbmQgYW55IHBvaW50cyBpbiB0aGUgbWFyZ2luLioqXA0KKFNWTSBsb3NzIGlzIGFmZmVjdGVkIGJ5IGJvdGggbWlzY2xhc3NpZmllZCBwb2ludHMgYW5kIHRob3NlIHRoYXQgZmFsbA0Kd2l0aGluIHRoZSBtYXJnaW4sIGFzIHRoZXkgdmlvbGF0ZSB0aGUgbWFyZ2luIGNvbnN0cmFpbnRzLikNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKlF1ZXN0aW9uIDM6IFRoZSBoZWFydCBvZiB0aGUga2VybmVsIHRyaWNrIGlzOioqDQoNCioqQW5zd2VyOioqXA0KKipXZSBvbmx5IG5lZWQgdGhlIG91dGNvbWUgb2YgdGhlIGRvdCBwcm9kdWN0LioqXA0KKFRoZSBrZXkgaWRlYSBiZWhpbmQgdGhlIGtlcm5lbCB0cmljayBpcyB0aGF0IHdlIG5ldmVyIGNvbXB1dGUgdGhlDQpmZWF0dXJlIHRyYW5zZm9ybWF0aW9uIGV4cGxpY2l0bHkuIEluc3RlYWQsIHdlIHVzZSBhIGtlcm5lbCBmdW5jdGlvbiB0bw0KY29tcHV0ZSB0aGUgZG90IHByb2R1Y3QgaW4gYSBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlIGltcGxpY2l0bHkuKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqUXVlc3Rpb24gNDogV2hlbiB1c2luZyBsYXJnZSBkYXRhc2V0cywgU1ZNOioqDQoNCioqQW5zd2VyOioqXA0KKipTY2FsZXMgcG9vcmx5LioqXA0KKFNWTSBpcyBub3QgaWRlYWwgZm9yIGxhcmdlIGRhdGFzZXRzIGJlY2F1c2Ugc29sdmluZyB0aGUgcXVhZHJhdGljDQpvcHRpbWl6YXRpb24gcHJvYmxlbSByZXF1aXJlZCBmb3IgdHJhaW5pbmcgU1ZNIHNjYWxlcyBwb29ybHkgd2l0aCB0aGUNCm51bWJlciBvZiBzYW1wbGVzLiBUaGlzIHdhcyBzcGVjaWZpY2FsbHkgZGlzY3Vzc2VkIGluIHRoZSBsZWN0dXJlIHNsaWRlcw0KYWJvdXQgc2NhbGluZyBpc3N1ZXMuKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqUXVlc3Rpb24gNTogV2hhdCBpcyB1bmlxdWUgYWJvdXQgdGhlIGhpbmdlIGxvc3M/KioNCg0KKipBbnN3ZXI6KipcDQoqKkl0IGlzIG9uZS1zaWRlZC4qKlwNCihUaGUgaGluZ2UgbG9zcyBvbmx5IHBlbmFsaXplcyBtaXNjbGFzc2lmaWVkIHBvaW50cyBhbmQgdGhvc2Ugd2l0aGluIHRoZQ0KbWFyZ2luLiBDb3JyZWN0bHkgY2xhc3NpZmllZCBwb2ludHMgdGhhdCBhcmUgb3V0c2lkZSB0aGUgbWFyZ2luIGRvIG5vdA0KY29udHJpYnV0ZSB0byB0aGUgbG9zcy4pDQoNCkhlcmUgaXMgeW91ciBleHBhbmRlZCAqKlN0dWR5IEd1aWRlKiogZm9yICoqU3RvY2hhc3RpYyBHcmFkaWVudCBEZXNjZW50DQooU0dEKSoqIGJhc2VkIG9uIHlvdXIgcHJvdmlkZWQgbWF0ZXJpYWxzIGFuZCByZWZlcmVuY2VzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKlN0dWR5IEd1aWRlOiBTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKg0KDQojIyMgKipPdmVydmlldyoqDQoNClN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCAoU0dEKSBpcyBhIHZhcmlhbnQgb2YgR3JhZGllbnQgRGVzY2VudCwNCmNvbW1vbmx5IHVzZWQgdG8gb3B0aW1pemUgbWFjaGluZSBsZWFybmluZyBtb2RlbHMsIGVzcGVjaWFsbHkgaW4gY2FzZXMNCndoZXJlIGRhdGFzZXRzIGFyZSB0b28gbGFyZ2UgdG8gZml0IGludG8gbWVtb3J5LiBJbnN0ZWFkIG9mIGNvbXB1dGluZw0KdGhlIGdyYWRpZW50IGZvciB0aGUgZW50aXJlIGRhdGFzZXQsIFNHRCBhcHByb3hpbWF0ZXMgaXQgdXNpbmcgYSBzbWFsbCwNCnJhbmRvbWx5IHNlbGVjdGVkIGJhdGNoIG9mIGRhdGEuDQoNCiMjIyAqKktleSBDb25jZXB0cyoqDQoNCjEuICAqKkdyYWRpZW50IERlc2NlbnQgKEdEKSBSZXZpZXcqKg0KICAgIC0gICBUaGUgZ29hbCBvZiBncmFkaWVudCBkZXNjZW50IGlzIHRvIGZpbmQgdGhlIG1pbmltdW0gb2YgYSBsb3NzDQogICAgICAgIGZ1bmN0aW9uICRKKG0pJC4NCiAgICAtICAgVGhlIHdlaWdodCAob3IgcGFyYW1ldGVyKSB1cGRhdGVzIGZvbGxvdzogJCQNCiAgICAgICAgbV97bisxfSA9IG1fbiAtIFxhbHBoYSBcbmFibGEgSihtKQ0KICAgICAgICAkJCB3aGVyZToNCiAgICAgICAgLSAgICRtX24kIGlzIHRoZSBjdXJyZW50IHBhcmFtZXRlciB2YWx1ZS4NCiAgICAgICAgLSAgICRcYWxwaGEkIGlzIHRoZSBsZWFybmluZyByYXRlLg0KICAgICAgICAtICAgJFxuYWJsYSBKKG0pJCBpcyB0aGUgZ3JhZGllbnQgb2YgdGhlIGxvc3MgZnVuY3Rpb24uDQoyLiAgKipMaW1pdGF0aW9ucyBvZiBGdWxsLUJhdGNoIEdyYWRpZW50IERlc2NlbnQqKg0KICAgIC0gICBGb3IgbGFyZ2UgZGF0YXNldHMsIGNvbXB1dGluZyB0aGUgZ3JhZGllbnQgZm9yIHRoZSBlbnRpcmUNCiAgICAgICAgZGF0YXNldCBhdCBlYWNoIGl0ZXJhdGlvbiBpcyAqKmNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUqKi4NCiAgICAtICAgUmVxdWlyZXMgc3RvcmluZyBhbGwgZGF0YSBpbiBtZW1vcnkuDQogICAgLSAgIENhbiBiZSBzbG93IGR1ZSB0byBwcm9jZXNzaW5nIGFsbCBkYXRhIGF0IG9uY2UuDQozLiAgKipTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKg0KICAgIC0gICBJbnN0ZWFkIG9mIHVzaW5nIHRoZSBlbnRpcmUgZGF0YXNldCwgU0dEICoqcmFuZG9tbHkgc2VsZWN0cyBhDQogICAgICAgIGJhdGNoIChzdWJzZXQpIG9mIGRhdGEqKiBhdCBlYWNoIGl0ZXJhdGlvbiB0byBhcHByb3hpbWF0ZSB0aGUNCiAgICAgICAgZ3JhZGllbnQ6ICQkDQogICAgICAgIG1fe24rMX0gPSBtX24gLSBcYWxwaGEgXG5hYmxhIFEobSkNCiAgICAgICAgJCQgd2hlcmU6DQogICAgICAgIC0gICAkUShtKSQgaXMgYW4gZXN0aW1hdGUgb2YgJEoobSkkIHVzaW5nIGEgYmF0Y2guDQogICAgLSAgIFRoZSAqKmJhdGNoIHNpemUqKiBkZXRlcm1pbmVzIHRoZSB0cmFkZS1vZmY6DQogICAgICAgIC0gICAqKlNtYWxsZXIgYmF0Y2hlcyoqIOKGkiBGYXN0ZXIgdXBkYXRlcywgYnV0IG1vcmUgbm9pc2UuDQogICAgICAgIC0gICAqKkxhcmdlciBiYXRjaGVzKiog4oaSIE1vcmUgc3RhYmxlIHVwZGF0ZXMsIGJ1dCBoaWdoZXIgbWVtb3J5DQogICAgICAgICAgICB1c2FnZS4NCg0KIyMjICoqQWR2YW50YWdlcyBvZiBTR0QqKg0KDQotICAgKipMb3dlciBNZW1vcnkgVXNhZ2UqKjogUHJvY2Vzc2VzIGRhdGEgaW4gYmF0Y2hlcywgZWxpbWluYXRpbmcgdGhlDQogICAgbmVlZCB0byBzdG9yZSB0aGUgZW50aXJlIGRhdGFzZXQgaW4gbWVtb3J5Lg0KLSAgICoqRmFzdGVyIFRyYWluaW5nIGZvciBMYXJnZSBEYXRhc2V0cyoqOiBBbGxvd3MgbW9kZWxzIHRvIGJlIHRyYWluZWQNCiAgICBlZmZpY2llbnRseSBvbiBsYXJnZS1zY2FsZSBkYXRhLg0KLSAgICoqTW9yZSBGcmVxdWVudCBVcGRhdGVzKio6IEVhY2ggdXBkYXRlIGFkanVzdHMgcGFyYW1ldGVycyBiYXNlZCBvbiBhDQogICAgc21hbGwgYmF0Y2gsIHdoaWNoIGNhbiBsZWFkIHRvIGZhc3RlciBjb252ZXJnZW5jZSBpbiBzb21lIGNhc2VzLg0KDQojIyMgKipDaGFsbGVuZ2VzIG9mIFNHRCoqDQoNCi0gICAqKk5vaXNlIGluIFVwZGF0ZXMqKjogU2luY2UgYmF0Y2hlcyBwcm92aWRlIGFuIGFwcHJveGltYXRpb24sIHRoZQ0KICAgIHVwZGF0ZXMgbWF5IGJlIG5vaXN5Lg0KLSAgICoqU2xvd2VyIENvbnZlcmdlbmNlKio6IENvbXBhcmVkIHRvIGZ1bGwtYmF0Y2ggbWV0aG9kcywgU0dEIG1heSB0YWtlDQogICAgbG9uZ2VyIHRvIHN0YWJpbGl6ZS4NCi0gICAqKkRhdGEgTG9hZGluZyBCb3R0bGVuZWNrKio6IEVmZmljaWVudGx5IHN0cmVhbWluZyBkYXRhIGZyb20gc3RvcmFnZQ0KICAgIGludG8gbWVtb3J5IGlzIGEga2V5IGNoYWxsZW5nZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uKioNCg0KKipHcmFkaWVudCBEZXNjZW50IFVwZGF0ZSBSdWxlOioqICQkDQptX3tuKzF9ID0gbV9uIC0gXGFscGhhIFxuYWJsYSBKKG0pDQokJA0KDQoqKlN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCBVcGRhdGUgUnVsZSAoVXNpbmcgQmF0Y2gqKiAkQiQpOiAkJA0KbV97bisxfSA9IG1fbiAtIFxhbHBoYSBcbmFibGEgUShtKQ0KJCQgd2hlcmUgJFEobSkkIGlzIHRoZSBsb3NzIGVzdGltYXRlIHVzaW5nIG9ubHkgYSBiYXRjaC4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqUHl0aG9uIEltcGxlbWVudGF0aW9uIGluIEp1cHl0ZXIgTm90ZWJvb2sqKg0KDQpXZSB3aWxsIGRlbW9uc3RyYXRlIHRoZSBlZmZlY3Qgb2YgKipTR0QgdnMuIEJhdGNoIEdyYWRpZW50IERlc2NlbnQqKg0KdXNpbmcgYSAqKmxpbmVhciByZWdyZXNzaW9uIGV4YW1wbGUqKi4NCg0KIyMjICoqU3RlcCAxOiBJbXBvcnQgTGlicmFyaWVzKioNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBTR0RSZWdyZXNzb3INCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBTdGFuZGFyZFNjYWxlcg0KYGBgDQoNCiMjIyAqKlN0ZXAgMjogR2VuZXJhdGUgU3ludGhldGljIERhdGEqKg0KDQpgYGAgcHl0aG9uDQojIEdlbmVyYXRlIHN5bnRoZXRpYyBkYXRhc2V0DQpucC5yYW5kb20uc2VlZCg0MikNClggPSAyICogbnAucmFuZG9tLnJhbmQoMTAwMDAsIDEpDQp5ID0gNCArIDMgKiBYICsgbnAucmFuZG9tLnJhbmRuKDEwMDAwLCAxKSAgIyBMaW5lYXIgcmVsYXRpb25zaGlwIHdpdGggbm9pc2UNCg0KIyBTcGxpdCBkYXRhc2V0DQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KYGBgDQoNCiMjIyAqKlN0ZXAgMzogU3RhbmRhcmRpemF0aW9uKioNCg0KYGBgIHB5dGhvbg0Kc2NhbGVyID0gU3RhbmRhcmRTY2FsZXIoKQ0KWF90cmFpbl9zY2FsZWQgPSBzY2FsZXIuZml0X3RyYW5zZm9ybShYX3RyYWluKQ0KWF90ZXN0X3NjYWxlZCA9IHNjYWxlci50cmFuc2Zvcm0oWF90ZXN0KQ0KYGBgDQoNCiMjIyAqKlN0ZXAgNDogVHJhaW4gU0dEIFJlZ3Jlc3NvcioqDQoNCmBgYCBweXRob24NCnNnZF9yZWcgPSBTR0RSZWdyZXNzb3IobWF4X2l0ZXI9MTAwMCwgdG9sPTFlLTMsIGxlYXJuaW5nX3JhdGU9ImNvbnN0YW50IiwgZXRhMD0wLjAxKQ0Kc2dkX3JlZy5maXQoWF90cmFpbl9zY2FsZWQsIHlfdHJhaW4ucmF2ZWwoKSkNCg0KIyBQcmVkaWN0aW9ucw0KeV9wcmVkID0gc2dkX3JlZy5wcmVkaWN0KFhfdGVzdF9zY2FsZWQpDQoNCnByaW50KGYiU0dEIENvZWZmaWNpZW50czoge3NnZF9yZWcuY29lZl99LCBJbnRlcmNlcHQ6IHtzZ2RfcmVnLmludGVyY2VwdF99IikNCmBgYA0KDQojIyMgKipTdGVwIDU6IENvbXBhcmUgU0dEIHRvIEJhdGNoIEdyYWRpZW50IERlc2NlbnQqKg0KDQpgYGAgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBMaW5lYXJSZWdyZXNzaW9uDQoNCmxpbl9yZWcgPSBMaW5lYXJSZWdyZXNzaW9uKCkNCmxpbl9yZWcuZml0KFhfdHJhaW5fc2NhbGVkLCB5X3RyYWluKQ0KDQpwcmludChmIkJhdGNoIEdyYWRpZW50IERlc2NlbnQgQ29lZmZpY2llbnRzOiB7bGluX3JlZy5jb2VmX30sIEludGVyY2VwdDoge2xpbl9yZWcuaW50ZXJjZXB0X30iKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKktleSBUYWtlYXdheXMqKg0KDQoxLiAgKipTR0QgaXMgd2VsbC1zdWl0ZWQgZm9yIGxhcmdlIGRhdGFzZXRzKiogc2luY2UgaXQgZG9lcyBub3QgcmVxdWlyZQ0KICAgIGxvYWRpbmcgYWxsIGRhdGEgaW50byBtZW1vcnkuDQoyLiAgKipCYXRjaGVzIGhlbHAgYXBwcm94aW1hdGUgdGhlIGdyYWRpZW50KiosIGJ1dCBzbWFsbCBiYXRjaGVzDQogICAgaW50cm9kdWNlIG5vaXNlIGluIHVwZGF0ZXMuDQozLiAgKipUcmFkZS1vZmYgZXhpc3RzKiogYmV0d2VlbiBiYXRjaCBzaXplLCBjb21wdXRhdGlvbiB0aW1lLCBhbmQNCiAgICBtZW1vcnkgZWZmaWNpZW5jeS4NCjQuICAqKkRhdGEgc3RyZWFtaW5nIG11c3QgYmUgb3B0aW1pemVkKiogdG8gYXZvaWQgY29tcHV0YXRpb25hbA0KICAgIGJvdHRsZW5lY2tzIHdoZW4gdHJhaW5pbmcgbW9kZWxzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipSZWxldmFudCBRdWVzdGlvbnMgZm9yIERpc2N1c3Npb24qKg0KDQoxLiAgKipIb3cgZG9lcyB0aGUgY2hvaWNlIG9mIGJhdGNoIHNpemUgYWZmZWN0IGNvbnZlcmdlbmNlIHNwZWVkIGFuZA0KICAgIHN0YWJpbGl0eT8qKg0KMi4gICoqV2h5IGlzIHRoZSBsZWFybmluZyByYXRlIGNydWNpYWwgZm9yIFNHRD8gV2hhdCBoYXBwZW5zIGlmIGl0J3MgdG9vDQogICAgaGlnaCBvciB0b28gbG93PyoqDQozLiAgKipIb3cgZG9lcyBTR0QgY29tcGFyZSB0byBvdGhlciBvcHRpbWl6YXRpb24gdGVjaG5pcXVlcyBsaWtlIEFkYW0gb3INCiAgICBSTVNwcm9wPyoqDQo0LiAgKipXaGF0IHN0cmF0ZWdpZXMgZXhpc3QgdG8gaGFuZGxlIGRhdGEgc3RyZWFtaW5nIGVmZmljaWVudGx5IHdoZW4NCiAgICB1c2luZyBTR0Q/KioNCjUuICAqKkhvdyBjYW4gd2UgZW5zdXJlIHRoYXQgU0dEIGRvZXMgbm90IGdldCBzdHVjayBpbiBsb2NhbCBtaW5pbWE/KioNCg0KIyAqKlN0dWR5IEd1aWRlOiBUaGUgSGFzaGluZyBUcmljayoqDQoNCiMjICoqT3ZlcnZpZXcqKg0KDQpUaGUgKipIYXNoaW5nIFRyaWNrKiogaXMgYSBjb21wdXRhdGlvbmFsIHRlY2huaXF1ZSB1c2VkIGluICoqU3RvY2hhc3RpYw0KR3JhZGllbnQgRGVzY2VudCAoU0dEKSoqIGFuZCAqKm91dC1vZi1jb3JlIGxlYXJuaW5nKiogdG8gZWZmaWNpZW50bHkNCnN0b3JlIGFuZCByZXRyaWV2ZSBkYXRhLCBwYXJ0aWN1bGFybHkgZm9yIGxhcmdlIGRhdGFzZXRzIHRoYXQgY2Fubm90IGZpdA0KaW50byBtZW1vcnkuIFRoaXMgbWV0aG9kIGFsbG93cyBkYXRhIHRvIGJlIG1hcHBlZCBpbnRvIG1lbW9yeSAqKnF1aWNrbHkNCmFuZCBlZmZpY2llbnRseSoqIHVzaW5nIGEgKipoYXNoIGZ1bmN0aW9uKiosIHdoaWNoIHByb3ZpZGVzIGENCmZpeGVkLWxlbmd0aCByZXByZXNlbnRhdGlvbiBvZiBpbnB1dCBkYXRhLg0KDQojIyMgKipLZXkgQ29uY2VwdHMqKg0KDQoxLiAgKipPdXQtb2YtQ29yZSBMZWFybmluZyoqOg0KICAgIC0gICBVc2VkIHdoZW4gZGF0YXNldHMgYXJlIHRvbyBsYXJnZSB0byBmaXQgaW50byBtZW1vcnkuDQogICAgLSAgIERhdGEgaXMgcHJvY2Vzc2VkIGluIGNodW5rcyByYXRoZXIgdGhhbiBhbGwgYXQgb25jZS4NCiAgICAtICAgRXNzZW50aWFsIGZvciBiaWcgZGF0YSBhcHBsaWNhdGlvbnMuDQoyLiAgKipSb2xlIG9mIHRoZSBIYXNoaW5nIFRyaWNrIGluIE91dC1vZi1Db3JlIExlYXJuaW5nKio6DQogICAgLSAgIEFsbG93cyBlZmZpY2llbnQgZGF0YSBzdG9yYWdlIGFuZCByZXRyaWV2YWwgKip3aXRob3V0IG5lZWRpbmcgdG8NCiAgICAgICAgbG9hZCBhbGwgZGF0YSBpbnRvIG1lbW9yeSoqLg0KICAgIC0gICBVc2VzIGEgKipoYXNoIGZ1bmN0aW9uKiogdG8gKiptYXAgZmVhdHVyZSBuYW1lcyB0byBmaXhlZC1zaXplDQogICAgICAgIG1lbW9yeSBsb2NhdGlvbnMqKi4NCiAgICAtICAgUHJldmVudHMgZXhwZW5zaXZlIG1lbW9yeSBhbGxvY2F0aW9uIGFuZCByZWFsbG9jYXRpb24uDQozLiAgKipIYXNoIEZ1bmN0aW9ucyBpbiBNYWNoaW5lIExlYXJuaW5nKio6DQogICAgLSAgIEEgaGFzaCBmdW5jdGlvbiBtYXBzIGFuIGlucHV0IChlLmcuLCBhIGZlYXR1cmUgbmFtZSkgdG8gYQ0KICAgICAgICBudW1lcmljYWwgaW5kZXguDQogICAgLSAgIFRoZSAqKnNhbWUgaW5wdXQgYWx3YXlzIHByb2R1Y2VzIHRoZSBzYW1lIGhhc2gqKg0KICAgICAgICAoZGV0ZXJtaW5pc3RpYykuDQogICAgLSAgIEhhc2hpbmcgKiphdm9pZHMgdGhlIG5lZWQgZm9yIGEgcHJlY29tcHV0ZWQgZGljdGlvbmFyeSoqIG9mDQogICAgICAgIGZlYXR1cmUgbWFwcGluZ3MuDQogICAgLSAgIFVzZWQgaW4gYWxnb3JpdGhtcyBsaWtlICoqU0dELCBGZWF0dXJlIEhhc2hpbmcsIGFuZCBPbmxpbmUNCiAgICAgICAgTGVhcm5pbmcgTW9kZWxzKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKk1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbiBvZiB0aGUgSGFzaGluZyBUcmljayoqDQoNCkEgaGFzaCBmdW5jdGlvbiAkSCh4KSQgbWFwcyBhbiBpbnB1dCAkeCQgKGUuZy4sIGEgZmVhdHVyZSBuYW1lKSB0byBhDQpsb2NhdGlvbiBpbiBhIGZpeGVkLXNpemUgdmVjdG9yOiAkJA0KSCh4KSA9IFx0ZXh0e2luZGV4IGluIG1lbW9yeSBzcGFjZX0NCiQkIEZvciBleGFtcGxlOiAtICoqSW5wdXQgRmVhdHVyZToqKiBgInByaWNlImAgLSAqKkhhc2ggRnVuY3Rpb24NCk91dHB1dDoqKiBgSCgicHJpY2UiKSA9IDIzOGAgLSAqKlN0b3JhZ2U6KiogVGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aA0KYCJwcmljZSJgIGlzIHN0b3JlZCBhdCBpbmRleCBgMjM4YC4NCg0KIyMjICoqSGFuZGxpbmcgSGFzaCBDb2xsaXNpb25zKioNCg0KLSAgIFNpbmNlIGhhc2ggZnVuY3Rpb25zICoqZG8gbm90IGd1YXJhbnRlZSB1bmlxdWUgbWFwcGluZ3MqKiwgdHdvDQogICAgZGlmZmVyZW50IGlucHV0cyAqKm1heSBtYXAgdG8gdGhlIHNhbWUgaW5kZXgqKi4NCi0gICBUaGlzIGlzIGNhbGxlZCBhICoqaGFzaCBjb2xsaXNpb24qKi4NCi0gICBXaGlsZSBpdCBjYW4gaW50cm9kdWNlIG5vaXNlLCAqKnJlc2VhcmNoIHNob3dzIHRoYXQgY29sbGlzaW9ucyBoYXZlDQogICAgbWluaW1hbCBpbXBhY3Qgb24gbW9kZWwgYWNjdXJhY3kqKi4NCi0gICBBIGxhcmdlciBoYXNoIHNwYWNlIChtZW1vcnkgc2l6ZSkgKipyZWR1Y2VzIGNvbGxpc2lvbnMqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqQWR2YW50YWdlcyBvZiB0aGUgSGFzaGluZyBUcmljayoqDQoNCuKchSAqKlNwZWVkKio6IFF1aWNrbHkgYXNzaWducyBhIG1lbW9yeSBsb2NhdGlvbiBmb3IgZWFjaCBmZWF0dXJlLA0KYXZvaWRpbmcgZXhwZW5zaXZlIGRpY3Rpb25hcnkgbG9va3Vwcy4NCg0K4pyFICoqTG93ZXIgTWVtb3J5IFVzYWdlKio6IE5vIG5lZWQgdG8gc3RvcmUgZmVhdHVyZSBuYW1lcywgcmVkdWNpbmcNCm92ZXJoZWFkLg0KDQrinIUgKipTY2FsYWJpbGl0eSoqOiBXb3JrcyB3ZWxsIGZvciAqKmxhcmdlIGRhdGFzZXRzKiogYW5kICoqc3RyZWFtaW5nDQpkYXRhKiouDQoNCuKchSAqKk5vIE5lZWQgZm9yIFByZWNvbXB1dGVkIERpY3Rpb25hcmllcyoqOiBVbmxpa2Ugb25lLWhvdCBlbmNvZGluZywNCmZlYXR1cmUgbWFwcGluZ3MgZG9u4oCZdCBuZWVkIHRvIGJlIHN0b3JlZCBpbiBtZW1vcnkuDQoNCuKchSAqKkNvbXBhdGlibGUgd2l0aCBTR0QqKjogRXNzZW50aWFsIGZvciBlZmZpY2llbnQgKipzdG9jaGFzdGljDQpncmFkaWVudCBkZXNjZW50KiogdXBkYXRlcy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqQ2hhbGxlbmdlcyBvZiB0aGUgSGFzaGluZyBUcmljayoqDQoNCuKaoCAqKkhhc2ggQ29sbGlzaW9ucyoqOiAtIElmIHR3byBmZWF0dXJlcyBtYXAgdG8gdGhlIHNhbWUgaW5kZXgsIHRoZXkNCioqb3ZlcndyaXRlIGVhY2ggb3RoZXLigJlzIHZhbHVlcyoqLiAtIENhbiBiZSBtaXRpZ2F0ZWQgYnkgKippbmNyZWFzaW5nDQp0aGUgaGFzaCBzcGFjZSoqIChudW1iZXIgb2YgbWVtb3J5IHNsb3RzKS4NCg0K4pqgICoqRml4ZWQgRmVhdHVyZSBTcGFjZSoqOiAtIE9uY2UgYSAqKmhhc2ggc2l6ZSBpcyBjaG9zZW4qKiwgaXQgKipjYW5ub3QNCmJlIGR5bmFtaWNhbGx5IGNoYW5nZWQqKiBkdXJpbmcgdHJhaW5pbmcuIC0gSWYgdGhlIG51bWJlciBvZiBmZWF0dXJlcw0KZ3Jvd3Mgc2lnbmlmaWNhbnRseSwgdGhlIGhhc2ggdGFibGUgbWF5IGJlY29tZSB0b28gc21hbGwuDQoNCuKaoCAqKkludGVycHJldGFiaWxpdHkgSXNzdWVzKio6IC0gVHJhZGl0aW9uYWwgZmVhdHVyZSBuYW1lcyBhcmUgbG9zdA0Kc2luY2UgZGF0YSBpcyBtYXBwZWQgdG8gaGFzaGVkIGluZGljZXMuIC0gTWFrZXMgZGVidWdnaW5nIGFuZCBmZWF0dXJlDQphbmFseXNpcyBoYXJkZXIuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKlB5dGhvbiBJbXBsZW1lbnRhdGlvbioqDQoNCkxldOKAmXMgZGVtb25zdHJhdGUgdGhlICoqSGFzaGluZyBUcmljayoqIHVzaW5nICoqU2Npa2l0LWxlYXJu4oCZcw0KYEZlYXR1cmVIYXNoZXJgKiouDQoNCiMjIyAqKlN0ZXAgMTogSW1wb3J0IExpYnJhcmllcyoqDQoNCmBgYCBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbiBpbXBvcnQgRmVhdHVyZUhhc2hlcg0KYGBgDQoNCiMjIyAqKlN0ZXAgMjogQ3JlYXRlIFNhbXBsZSBEYXRhKioNCg0KYGBgIHB5dGhvbg0KIyBFeGFtcGxlIGNhdGVnb3JpY2FsIGRhdGENCmRhdGEgPSBbDQogICAgeyJmZWF0dXJlMSI6ICJhcHBsZSIsICJmZWF0dXJlMiI6ICJyZWQifSwNCiAgICB7ImZlYXR1cmUxIjogImJhbmFuYSIsICJmZWF0dXJlMiI6ICJ5ZWxsb3cifSwNCiAgICB7ImZlYXR1cmUxIjogImdyYXBlIiwgImZlYXR1cmUyIjogInB1cnBsZSJ9LA0KXQ0KDQojIENyZWF0ZSBGZWF0dXJlSGFzaGVyIHdpdGggaGFzaCBzaXplIDEwDQpoYXNoZXIgPSBGZWF0dXJlSGFzaGVyKG5fZmVhdHVyZXM9MTAsIGlucHV0X3R5cGU9ImRpY3QiKQ0KaGFzaGVkX2ZlYXR1cmVzID0gaGFzaGVyLnRyYW5zZm9ybShkYXRhKS50b2FycmF5KCkNCg0KIyBEaXNwbGF5IGhhc2hlZCBvdXRwdXQNCnByaW50KGhhc2hlZF9mZWF0dXJlcykNCmBgYA0KDQojIyMgKipTdGVwIDM6IFVzaW5nIHRoZSBIYXNoaW5nIFRyaWNrIGluIFNHRCoqDQoNCmBgYCBweXRob24NCmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IFNHRENsYXNzaWZpZXINCg0KIyBTYW1wbGUgZGF0YXNldCB3aXRoIHRleHQgZmVhdHVyZXMNClggPSBbDQogICAgeyJ3b3JkIjogImRvZyJ9LCB7IndvcmQiOiAiY2F0In0sIHsid29yZCI6ICJmaXNoIn0sDQogICAgeyJ3b3JkIjogImRvZyJ9LCB7IndvcmQiOiAiZG9nIn0sIHsid29yZCI6ICJjYXQifQ0KXQ0KeSA9IFsxLCAwLCAwLCAxLCAxLCAwXSAgIyBCaW5hcnkgY2xhc3NpZmljYXRpb24gbGFiZWxzDQoNCiMgSGFzaCB0aGUgZmVhdHVyZXMNCmhhc2hlciA9IEZlYXR1cmVIYXNoZXIobl9mZWF0dXJlcz01LCBpbnB1dF90eXBlPSJkaWN0IikNClhfaGFzaGVkID0gaGFzaGVyLnRyYW5zZm9ybShYKQ0KDQojIFRyYWluIGFuIFNHRCBjbGFzc2lmaWVyDQpzZ2QgPSBTR0RDbGFzc2lmaWVyKGxvc3M9ImxvZyIsIG1heF9pdGVyPTEwMDAsIHRvbD0xZS0zKQ0Kc2dkLmZpdChYX2hhc2hlZCwgeSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpwcmVkaWN0aW9ucyA9IHNnZC5wcmVkaWN0KFhfaGFzaGVkKQ0KcHJpbnQoIlByZWRpY3Rpb25zOiIsIHByZWRpY3Rpb25zKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKktleSBUYWtlYXdheXMqKg0KDQoxLiAgKipUaGUgSGFzaGluZyBUcmljayBlbmFibGVzIGZhc3QsIG1lbW9yeS1lZmZpY2llbnQgZmVhdHVyZQ0KICAgIGVuY29kaW5nKiosIG1ha2luZyBpdCB1c2VmdWwgZm9yICoqYmlnIGRhdGEgYW5kIG9ubGluZSBsZWFybmluZyoqLg0KMi4gICoqSGFzaCBmdW5jdGlvbnMgbWFwIGZlYXR1cmVzIHRvIGZpeGVkIG1lbW9yeSBsb2NhdGlvbnMqKiwgcmVkdWNpbmcNCiAgICBsb29rdXAgdGltZSBhbmQgbWVtb3J5IGZvb3RwcmludC4NCjMuICAqKkhhc2ggY29sbGlzaW9ucyBtYXkgb2NjdXIgYnV0IGdlbmVyYWxseSBkbyBub3Qgc2lnbmlmaWNhbnRseQ0KICAgIGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSoqLg0KNC4gICoqVXNlZCBpbiBjb21iaW5hdGlvbiB3aXRoIFNHRCBhbmQgb3V0LW9mLWNvcmUgbGVhcm5pbmcqKiB0byBoYW5kbGUNCiAgICAqKmxhcmdlIGRhdGFzZXRzIGVmZmljaWVudGx5KiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKlJlbGV2YW50IFF1ZXN0aW9ucyBmb3IgRGlzY3Vzc2lvbioqDQoNCjEuICAqKldoYXQgYXJlIHRoZSB0cmFkZS1vZmZzIGJldHdlZW4gaW5jcmVhc2luZyBhbmQgZGVjcmVhc2luZyB0aGUgaGFzaA0KICAgIHNwYWNlPyoqDQoyLiAgKipIb3cgZG9lcyB0aGUgSGFzaGluZyBUcmljayBjb21wYXJlIHRvIG9uZS1ob3QgZW5jb2Rpbmc/KioNCjMuICAqKldoeSBpcyB0aGUgSGFzaGluZyBUcmljayBjb21tb25seSB1c2VkIGluIHJlYWwtdGltZSBhbmQgc3RyZWFtaW5nDQogICAgYXBwbGljYXRpb25zPyoqDQo0LiAgKipXaGF0IGFyZSBzb21lIHdheXMgdG8gbWl0aWdhdGUgaGFzaCBjb2xsaXNpb25zIGluIHByYWN0aWNhbA0KICAgIGltcGxlbWVudGF0aW9ucz8qKg0KNS4gICoqQ2FuIHRoZSBoYXNoaW5nIHRyaWNrIGJlIHVzZWQgaW4gZGVlcCBsZWFybmluZyBhcmNoaXRlY3R1cmVzLCBhbmQNCiAgICBpZiBzbywgaG93PyoqDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojICoqU3R1ZHkgR3VpZGU6IFN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCAoU0dEKSAmIEVwb2NocyoqDQoNCiMjICoqT3ZlcnZpZXcqKg0KDQpTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkgZm9sbG93cyB0aGUgc2FtZSBwcmluY2lwbGVzIGFzDQoqKmxpbmVhciBhbmQgbG9naXN0aWMgcmVncmVzc2lvbioqLCBzb2x2aW5nIGZvciBzbG9wZXMgYnkgbWluaW1pemluZyBhDQoqKmxvc3MgZnVuY3Rpb24qKi4gSG93ZXZlciwgc2luY2UgaXQgKipwcm9jZXNzZXMgZGF0YSBpbiBiYXRjaGVzKiosIGl0DQppbnRyb2R1Y2VzIGFkZGl0aW9uYWwgKipoeXBlcnBhcmFtZXRlcnMqKiwgc3VjaCBhcyB0aGUgKipsZWFybmluZyByYXRlKioNCmFuZCB0aGUgKipudW1iZXIgb2YgZXBvY2hzKiouDQoNCiMjIyAqKktleSBDb25jZXB0cyoqDQoNCjEuICAqKlNHRCBhbmQgUmVncmVzc2lvbioqOg0KICAgIC0gICBXb3JrcyBzaW1pbGFybHkgdG8gKipsaW5lYXIgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24qKi4NCiAgICAtICAgVXNlcyB0aGUgKipzYW1lIGxvc3MgZnVuY3Rpb25zKiogYnV0IG9wZXJhdGVzIG9uIHNtYWxsZXIgc3Vic2V0cw0KICAgICAgICAoYmF0Y2hlcykgb2YgZGF0YS4NCiAgICAtICAgSW50cm9kdWNlcyAqKm5ldyBoeXBlcnBhcmFtZXRlcnMqKiAoZS5nLiwgbGVhcm5pbmcgcmF0ZSwgYmF0Y2gNCiAgICAgICAgc2l6ZSwgbnVtYmVyIG9mIGVwb2NocykuDQoyLiAgKipMZWFybmluZyBSYXRlICjOsSkqKjoNCiAgICAtICAgQ29udHJvbHMgaG93IG11Y2ggdGhlIG1vZGVsIHVwZGF0ZXMgd2VpZ2h0cyB3aXRoIGVhY2ggYmF0Y2guDQogICAgLSAgIElmICoqdG9vIGxhcmdlKiog4oaSIG1vZGVsIG1pZ2h0IG5vdCBjb252ZXJnZSAob3ZlcnNob290aW5nKS4NCiAgICAtICAgSWYgKip0b28gc21hbGwqKiDihpIgdHJhaW5pbmcgd2lsbCBiZSB0b28gc2xvdy4NCjMuICAqKldoYXQgaXMgYW4gRXBvY2g/Kio6DQogICAgLSAgICoqT25lIGVwb2NoKiogPSAqKm9uZSBmdWxsIHBhc3MgdGhyb3VnaCB0aGUgZW50aXJlIGRhdGFzZXQqKi4NCiAgICAtICAgRGF0YSBpcyBkaXZpZGVkIGludG8gKipiYXRjaGVzKiogKHNtYWxsZXIgc3Vic2V0cyBvZiBkYXRhKS4NCiAgICAtICAgVGhlIG1vZGVsICoqdXBkYXRlcyBpdHMgcGFyYW1ldGVycyBhZnRlciBlYWNoIGJhdGNoKiouDQo0LiAgKipFeGFtcGxlIG9mIGFuIEVwb2NoKio6DQogICAgLSAgIElmIHlvdSBoYXZlICoqMTAwIGRhdGEgcG9pbnRzKiogYW5kIGEgYmF0Y2ggc2l6ZSBvZiAqKjUqKjoNCiAgICAgICAgLSAgIFRoZSBtb2RlbCBzZWVzICoqNSBkYXRhIHBvaW50cyBhdCBhIHRpbWUqKi4NCiAgICAgICAgLSAgICoqQWZ0ZXIgMjAgYmF0Y2hlcyAoMTAwLzUpLCB0aGUgbW9kZWwgaGFzIHNlZW4gYWxsIDEwMA0KICAgICAgICAgICAgcG9pbnRzIG9uY2UqKi4NCiAgICAgICAgLSAgIFRoaXMgKipjb21wbGV0ZXMgb25lIGVwb2NoKiouDQogICAgICAgIC0gICBUaGUgcHJvY2VzcyAqKnJlcGVhdHMgZm9yIG11bHRpcGxlIGVwb2NocyoqLg0KNS4gICoqU3RvcHBpbmcgQ3JpdGVyaWEgZm9yIFNHRCoqOg0KICAgIC0gICBSdW5uaW5nIHRvbyBmZXcgZXBvY2hzIOKGkiAqKnVuZGVyZml0dGluZyoqIChtb2RlbCBkb2VzbuKAmXQgbGVhcm4NCiAgICAgICAgZW5vdWdoKS4NCiAgICAtICAgUnVubmluZyB0b28gbWFueSBlcG9jaHMg4oaSICoqb3ZlcmZpdHRpbmcqKiAobW9kZWwgbWVtb3JpemVzDQogICAgICAgIHRyYWluaW5nIGRhdGEgYnV0IGdlbmVyYWxpemVzIHBvb3JseSkuDQogICAgLSAgIEJlc3QgcHJhY3RpY2U6ICoqU3RvcCB0cmFpbmluZyB3aGVuIHRoZSBsb3NzIHN0b3BzIGltcHJvdmluZyoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24gb2YgU0dEIHdpdGggRXBvY2hzKioNCg0KR3JhZGllbnQgRGVzY2VudCB1cGRhdGVzIHRoZSB3ZWlnaHRzICR3JCB1c2luZzogJCQNCndfe3QrMX0gPSB3X3QgLSBcYWxwaGEgXG5hYmxhIEood190KQ0KJCQgd2hlcmU6IC0gJHdfdCQgPSBjdXJyZW50IHdlaWdodCAtICRcYWxwaGEkID0gbGVhcm5pbmcgcmF0ZSAtDQokXG5hYmxhIEood190KSQgPSBncmFkaWVudCBvZiB0aGUgbG9zcyBmdW5jdGlvbiBhdCBzdGVwICR0JA0KDQpGb3IgKipTR0QqKiwgaW5zdGVhZCBvZiBjb21wdXRpbmcgdGhlIGdyYWRpZW50IG9uIHRoZSBlbnRpcmUgZGF0YXNldCwgaXQNCmlzIGNvbXB1dGVkICoqb24gYSBiYXRjaCoqICRCJDogJCQNCndfe3QrMX0gPSB3X3QgLSBcYWxwaGEgXG5hYmxhIEpfQih3X3QpDQokJCB3aGVyZSAkSl9CJCBpcyB0aGUgbG9zcyBmdW5jdGlvbiBjb21wdXRlZCBvbmx5IG9uIGJhdGNoICRCJC4NCg0KIyMjICoqRXBvY2hzIGFuZCBVcGRhdGVzKioNCg0KRWFjaCAqKmJhdGNoIHVwZGF0ZSoqIG1vZGlmaWVzIHRoZSBtb2RlbCB3ZWlnaHRzLiBBZnRlciB0aGUgbW9kZWwgaGFzDQpzZWVuIGFsbCBiYXRjaGVzLCAqKm9uZSBlcG9jaCBpcyBjb21wbGV0ZWQqKi4gVGhlIHByb2Nlc3MgcmVwZWF0cyB1bnRpbA0KKipjb252ZXJnZW5jZSBjcml0ZXJpYSoqIGFyZSBtZXQuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKlB5dGhvbiBJbXBsZW1lbnRhdGlvbioqDQoNCiMjIyAqKlN0ZXAgMTogSW1wb3J0IExpYnJhcmllcyoqDQoNCmBgYCBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgU0dEQ2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpgYGANCg0KIyMjICoqU3RlcCAyOiBHZW5lcmF0ZSBTYW1wbGUgRGF0YSoqDQoNCmBgYCBweXRob24NCiMgR2VuZXJhdGUgc3ludGhldGljIGNsYXNzaWZpY2F0aW9uIGRhdGENClgsIHkgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKG5fc2FtcGxlcz0xMDAwLCBuX2ZlYXR1cmVzPTEwLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgU3BsaXQgaW50byB0cmFpbi90ZXN0IHNldHMNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQpgYGANCg0KIyMjICoqU3RlcCAzOiBUcmFpbiBhbiBTR0QgQ2xhc3NpZmllciB3aXRoIE11bHRpcGxlIEVwb2NocyoqDQoNCmBgYCBweXRob24NCiMgVHJhaW4gbW9kZWwgd2l0aCBTR0QNCnNnZCA9IFNHRENsYXNzaWZpZXIobG9zcz0ibG9nX2xvc3MiLCBtYXhfaXRlcj0xMDAsIHRvbD0xZS0zKQ0KDQojIEZpdCBtb2RlbA0Kc2dkLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KDQojIEV2YWx1YXRlDQphY2N1cmFjeSA9IHNnZC5zY29yZShYX3Rlc3QsIHlfdGVzdCkNCnByaW50KGYiVGVzdCBBY2N1cmFjeToge2FjY3VyYWN5Oi40Zn0iKQ0KYGBgDQoNCiMjIyAqKlN0ZXAgNDogVmlzdWFsaXppbmcgTGVhcm5pbmcgUmF0ZSBhbmQgRXBvY2ggSW1wYWN0KioNCg0KYGBgIHB5dGhvbg0KZXBvY2hzID0gWzUsIDEwLCA1MCwgMTAwLCAyMDBdDQphY2N1cmFjeV9zY29yZXMgPSBbXQ0KDQpmb3IgZXBvY2ggaW4gZXBvY2hzOg0KICAgIHNnZCA9IFNHRENsYXNzaWZpZXIobG9zcz0ibG9nX2xvc3MiLCBtYXhfaXRlcj1lcG9jaCwgdG9sPTFlLTMpDQogICAgc2dkLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KICAgIGFjY3VyYWN5X3Njb3Jlcy5hcHBlbmQoc2dkLnNjb3JlKFhfdGVzdCwgeV90ZXN0KSkNCg0KIyBQbG90IGFjY3VyYWN5IHZzIGVwb2Nocw0KcGx0LnBsb3QoZXBvY2hzLCBhY2N1cmFjeV9zY29yZXMsIG1hcmtlcj0ibyIpDQpwbHQueGxhYmVsKCJFcG9jaHMiKQ0KcGx0LnlsYWJlbCgiVGVzdCBBY2N1cmFjeSIpDQpwbHQudGl0bGUoIkltcGFjdCBvZiBFcG9jaHMgb24gQWNjdXJhY3kiKQ0KcGx0LnNob3coKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKktleSBUYWtlYXdheXMqKg0KDQpcKkVwb2NocyBkZWZpbmUgaG93IG1hbnkgdGltZXMgdGhlIG1vZGVsIHNlZXMgdGhlIGVudGlyZSBkYXRhc2V0XCpcKi4NClwqU0dEIHVwZGF0ZXMgd2VpZ2h0cyBhZnRlciBlYWNoIGJhdGNoLCByYXRoZXIgdGhhbiBhZnRlciBzZWVpbmcgdGhlDQp3aG9sZSBkYXRhc2V0XCpcKi4gXCpMZWFybmluZyByYXRlIGFuZCBiYXRjaCBzaXplIGFmZmVjdCBjb252ZXJnZW5jZQ0Kc3BlZWQqKi4qKiBNb25pdG9yaW5nIGxvc3MgaW1wcm92ZW1lbnQgaXMgY3J1Y2lhbCB0byBhdm9pZA0Kb3ZlcmZpdHRpbmcqKi4gSW5zdGVhZCBvZiBmaXhpbmcgdGhlIG51bWJlciBvZiBlcG9jaHMsIHVzZSBhIHN0b3BwaW5nDQpjcml0ZXJpb24gYmFzZWQgb24gbG9zcyBpbXByb3ZlbWVudCoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipSZWxldmFudCBRdWVzdGlvbnMgZm9yIERpc2N1c3Npb24qKg0KDQoxLiAgKipIb3cgZG9lcyBpbmNyZWFzaW5nIHRoZSBudW1iZXIgb2YgZXBvY2hzIGFmZmVjdCBtb2RlbA0KICAgIHBlcmZvcm1hbmNlPyoqDQoyLiAgKipXaGF0IGhhcHBlbnMgaWYgdGhlIGxlYXJuaW5nIHJhdGUgaXMgdG9vIGhpZ2ggb3IgdG9vIGxvdz8qKg0KMy4gICoqSG93IGRvZXMgU0dEIGNvbXBhcmUgdG8gYmF0Y2ggZ3JhZGllbnQgZGVzY2VudCBpbiB0ZXJtcyBvZiBtZW1vcnkNCiAgICBhbmQgY29tcHV0YXRpb24/KioNCjQuICAqKldoYXQgYXJlIGNvbW1vbiBzdG9wcGluZyBjcml0ZXJpYSBmb3IgZGV0ZXJtaW5pbmcgd2hlbiB0byBlbmQNCiAgICB0cmFpbmluZz8qKg0KNS4gICoqSG93IGRvIGJhdGNoIHNpemUgYW5kIGVwb2NocyBpbnRlcmFjdCBpbiBtb2RlbCB0cmFpbmluZz8qKg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKlN0dWR5IEd1aWRlOiBTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkgRGVtbyoqDQoNCiMjICoqT3ZlcnZpZXcqKg0KDQpTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkgaXMgYW4gb3B0aW1pemF0aW9uIHRlY2huaXF1ZSB0aGF0DQphbGxvd3MgbWFjaGluZSBsZWFybmluZyBtb2RlbHMgdG8gdHJhaW4gb24gKipsYXJnZSBkYXRhc2V0cyoqDQplZmZpY2llbnRseS4gSW5zdGVhZCBvZiBwcm9jZXNzaW5nIGFsbCBkYXRhIGF0IG9uY2UsICoqU0dEIHVwZGF0ZXMgbW9kZWwNCnBhcmFtZXRlcnMgaXRlcmF0aXZlbHkgdXNpbmcgc21hbGwgYmF0Y2hlcyoqIChvciBldmVuIHNpbmdsZSBkYXRhDQpwb2ludHMpLiBUaGlzIHN0dWR5IGd1aWRlIGV4cGxvcmVzIGhvdyAqKlNHRCBjYW4gYmUgYXBwbGllZCB0byBsYXJnZQ0KZGF0YXNldHMqKiwgaXRzIGltcGxlbWVudGF0aW9uLCBhbmQgcHJhY3RpY2FsIGNvbnNpZGVyYXRpb25zLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipLZXkgQ29uY2VwdHMqKg0KDQojIyMgKioxLiBXaHkgVXNlIFNHRCBmb3IgTGFyZ2UgRGF0YXNldHM/KioNCg0KLSAgIFRyYWRpdGlvbmFsIGdyYWRpZW50IGRlc2NlbnQgcmVxdWlyZXMgKipsb2FkaW5nIHRoZSBlbnRpcmUgZGF0YXNldA0KICAgIGludG8gbWVtb3J5KiosIG1ha2luZyBpdCBpbmVmZmljaWVudCBmb3IgKipiaWcgZGF0YSBhcHBsaWNhdGlvbnMqKi4NCi0gICAqKlNHRCBvbmx5IG5lZWRzIHRvIGxvYWQgc21hbGwgYmF0Y2hlcyAob3Igc2luZ2xlIHJvd3MpIGludG8gbWVtb3J5DQogICAgYXQgYSB0aW1lKiosIGFsbG93aW5nIG1vZGVscyB0byAqKnNjYWxlIHRvIG1pbGxpb25zIG9mIGRhdGENCiAgICBwb2ludHMqKi4NCi0gICAqKlBhcnRpYWwgZml0dGluZyoqIGVuYWJsZXMgbW9kZWxzIHRvIHVwZGF0ZSBpbiBjaHVua3MsIHJhdGhlciB0aGFuDQogICAgcmVxdWlyaW5nIHRoZSBlbnRpcmUgZGF0YXNldC4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjIuIFVuZGVyc3RhbmRpbmcgdGhlIFNHRENsYXNzaWZpZXIqKg0KDQpUaGUgKipTR0RDbGFzc2lmaWVyKiogaW4gU2Npa2l0LUxlYXJuIGlzIGEgbGluZWFyIGNsYXNzaWZpZXIgdGhhdCB1c2VzDQoqKlNHRCBmb3Igb3B0aW1pemF0aW9uKiouDQoNCktleSBQYXJhbWV0ZXJzOiAtICoqbG9zcyoqOiBEZWZpbmVzIHRoZSB0eXBlIG9mIG9wdGltaXphdGlvbiAoZS5nLiwNCmAibG9nImAgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIGAiaGluZ2UiYCBmb3IgU1ZNKS4gLSAqKmFscGhhKio6IFRoZQ0KKipsZWFybmluZyByYXRlKiogKGNvbnRyb2xzIHN0ZXAgc2l6ZSBmb3IgdXBkYXRlcykuIC0gKipwZW5hbHR5Kio6DQpSZWd1bGFyaXphdGlvbiB0ZXJtIChgImwyImAsIGAibDEiYCwgYCJlbGFzdGljbmV0ImApLiAtICoqcGFydGlhbF9maXQqKjoNCkFsbG93cyB0cmFpbmluZyBvbiBjaHVua3Mgb2YgZGF0YSByYXRoZXIgdGhhbiB0aGUgd2hvbGUgZGF0YXNldCBhdCBvbmNlLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqMy4gTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uIG9mIFNHRCoqDQoNClRoZSBzdGFuZGFyZCAqKlNHRCB1cGRhdGUgcnVsZSoqIGZvciB3ZWlnaHQgdXBkYXRlcyBpczoNCg0KJCQNCndfe3QrMX0gPSB3X3QgLSBcYWxwaGEgXG5hYmxhIEood190KQ0KJCQNCg0KV2hlcmU6IC0gJHdfdCQgPSBDdXJyZW50IHdlaWdodHMgLSAkXGFscGhhJCA9IExlYXJuaW5nIHJhdGUgLQ0KJFxuYWJsYSBKKHdfdCkkID0gR3JhZGllbnQgb2YgbG9zcyBmdW5jdGlvbg0KDQpJbnN0ZWFkIG9mIGNvbXB1dGluZyB0aGUgZ3JhZGllbnQgdXNpbmcgKiphbGwgZGF0YSoqLCAqKlNHRCBhcHByb3hpbWF0ZXMNCml0IHVzaW5nIG9uZSBvciBhIGZldyBleGFtcGxlcyBwZXIgc3RlcCoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipQeXRob24gSW1wbGVtZW50YXRpb246IFRyYWluaW5nIGEgTW9kZWwgd2l0aCBTR0Qgb24gTGFyZ2UgRGF0YSoqDQoNCiMjIyAqKlN0ZXAgMTogSW1wb3J0IE5lY2Vzc2FyeSBMaWJyYXJpZXMqKg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IFNHRENsYXNzaWZpZXINCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDI6IFNpbXVsYXRpbmcgTGFyZ2UgRGF0YSoqDQoNCmBgYCBweXRob24NCiMgR2VuZXJhdGUgYSBsYXJnZSBzeW50aGV0aWMgZGF0YXNldCB3aXRoIDExIG1pbGxpb24gcm93cyBhbmQgMjkgZmVhdHVyZXMNCm5fc2FtcGxlcyA9IDExXzAwMF8wMDAgICMgMTEgbWlsbGlvbiByb3dzDQpuX2ZlYXR1cmVzID0gMjkgICMgMjkgZmVhdHVyZXMNCg0KIyBTaW11bGF0aW5nIGEgbGFyZ2UgZGF0YXNldA0KWCA9IG5wLnJhbmRvbS5yYW5kKG5fc2FtcGxlcywgbl9mZWF0dXJlcykNCnkgPSBucC5yYW5kb20ucmFuZGludCgwLCAyLCBuX3NhbXBsZXMpICAjIEJpbmFyeSBjbGFzc2lmaWNhdGlvbiAoMCBvciAxKQ0KDQojIFNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cyAob25seSB1c2UgYSBzdWJzZXQgZm9yIHRlc3RpbmcgdG8gc2F2ZSBtZW1vcnkpDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMDAxLCByYW5kb21fc3RhdGU9NDIpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKlN0ZXAgMzogVHJhaW5pbmcgU0dEIHdpdGggUGFydGlhbCBGaXR0aW5nKioNCg0KYGBgIHB5dGhvbg0KIyBDcmVhdGUgYW4gU0dEIGNsYXNzaWZpZXINCnNnZCA9IFNHRENsYXNzaWZpZXIobG9zcz0ibG9nX2xvc3MiLCBwZW5hbHR5PSJsMiIsIGFscGhhPTAuMDEpDQoNCiMgVHJhaW4gdXNpbmcgcGFydGlhbF9maXQgKGJhdGNoIHByb2Nlc3NpbmcpDQpiYXRjaF9zaXplID0gMTAwXzAwMCAgIyBQcm9jZXNzIDEwMCwwMDAgcm93cyBhdCBhIHRpbWUNCmZvciBpIGluIHJhbmdlKDAsIGxlbihYX3RyYWluKSwgYmF0Y2hfc2l6ZSk6DQogICAgWF9iYXRjaCA9IFhfdHJhaW5baTppK2JhdGNoX3NpemVdDQogICAgeV9iYXRjaCA9IHlfdHJhaW5baTppK2JhdGNoX3NpemVdDQogICAgDQogICAgIyBJZiBmaXJzdCBiYXRjaCwgaW5pdGlhbGl6ZSB3aXRoICdjbGFzc2VzJyBhcmd1bWVudA0KICAgIGlmIGkgPT0gMDoNCiAgICAgICAgc2dkLnBhcnRpYWxfZml0KFhfYmF0Y2gsIHlfYmF0Y2gsIGNsYXNzZXM9bnAudW5pcXVlKHlfdHJhaW4pKQ0KICAgIGVsc2U6DQogICAgICAgIHNnZC5wYXJ0aWFsX2ZpdChYX2JhdGNoLCB5X2JhdGNoKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDQ6IEV2YWx1YXRpbmcgdGhlIE1vZGVsKioNCg0KYGBgIHB5dGhvbg0KIyBNYWtlIHByZWRpY3Rpb25zDQp5X3ByZWQgPSBzZ2QucHJlZGljdChYX3Rlc3QpDQoNCiMgQ29tcHV0ZSBhY2N1cmFjeQ0KYWNjdXJhY3kgPSBhY2N1cmFjeV9zY29yZSh5X3Rlc3QsIHlfcHJlZCkNCnByaW50KGYiVGVzdCBBY2N1cmFjeToge2FjY3VyYWN5Oi40Zn0iKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKktleSBJbnNpZ2h0cyBmcm9tIFNHRCBEZW1vKioNCg0KKipIYW5kbGVzIG1hc3NpdmUgZGF0YXNldHMgZWZmaWNpZW50bHkqKjogSW5zdGVhZCBvZiBwcm9jZXNzaW5nDQpldmVyeXRoaW5nIGF0IG9uY2UsICoqU0dEIHVwZGF0ZXMgd2VpZ2h0cyBpdGVyYXRpdmVseSB1c2luZyBiYXRjaGVzKiouXA0KKipSZWR1Y2VzIG1lbW9yeSByZXF1aXJlbWVudHMqKjogT25seSAqKnNtYWxsIGNodW5rcyBvZiBkYXRhIGFyZSBsb2FkZWQNCmludG8gbWVtb3J5IGF0IGEgdGltZSoqLlwNCioqRmFzdCB0cmFpbmluZyBzcGVlZCoqOiBUaGUgKip0cmFpbmluZyB0aW1lIGZvciAxMSBtaWxsaW9uIHJvd3Mgd2FzDQp1bmRlciBhIG1pbnV0ZSoqLlwNClwqVHJhZGUtb2ZmIGJldHdlZW4gc3BlZWQgYW5kIGFjY3VyYWN5Kio6KiogTGFyZ2VyIGJhdGNoZXMgKipyZXN1bHQgaW4qKg0KbW9yZSBzdGFibGUgKip1cGRhdGVzLCB3aGlsZSoqIHNtYWxsZXIgYmF0Y2hlcyAqKmNhbiBpbnRyb2R1Y2UqKiBoaWdoZXINCnZhcmlhbmNlXCpcKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqUGFydGlhbCBGaXR0aW5nIGFuZCBXaHkgSXQgTWF0dGVycyoqDQoNCi0gICBUaGUgKipwYXJ0aWFsX2ZpdCgpKiogZnVuY3Rpb24gYWxsb3dzIHVwZGF0aW5nIHRoZSBtb2RlbCAqKndpdGhvdXQNCiAgICByZWxvYWRpbmcgYWxsIHByZXZpb3VzIGRhdGEqKi4NCi0gICAqKlVzZWZ1bCB3aGVuIHRoZSBkYXRhc2V0IGlzIHRvbyBsYXJnZSB0byBmaXQgaW50byBtZW1vcnkqKi4NCi0gICAqKkhlbHBzIHRyYWluIG1vZGVscyBpbmNyZW1lbnRhbGx5KiogKGlkZWFsIGZvciByZWFsLXRpbWUgbGVhcm5pbmcNCiAgICBvciBjb250aW51b3VzIHVwZGF0ZXMpLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipEaXNjdXNzaW9uIFF1ZXN0aW9ucyBmb3IgQ2xhc3MqKg0KDQoxLiAgKipIb3cgZG9lcyBiYXRjaCBzaXplIGltcGFjdCBTR0QgcGVyZm9ybWFuY2U/KioNCjIuICAqKldoYXQgYXJlIHRoZSB0cmFkZS1vZmZzIG9mIHVzaW5nIHBhcnRpYWxfZml0IGluc3RlYWQgb2YgdHJhaW5pbmcNCiAgICBvbiB0aGUgZnVsbCBkYXRhc2V0PyoqDQozLiAgKipXaHkgZG9lcyBpbmNyZWFzaW5nIHRoZSBudW1iZXIgb2YgZXBvY2hzIHNvbWV0aW1lcyBsZWFkIHRvDQogICAgb3ZlcmZpdHRpbmcgaW4gU0dEPyoqDQo0LiAgKipIb3cgZG9lcyBTR0QgY29tcGFyZSB0byB0cmFkaXRpb25hbCBncmFkaWVudCBkZXNjZW50IGZvcg0KICAgIGxhcmdlLXNjYWxlIGxlYXJuaW5nPyoqDQo1LiAgKipXaGF0IGFyZSBjb21tb24gc3RvcHBpbmcgY3JpdGVyaWEgZm9yIFNHRCAoZS5nLiwgbG9zcw0KICAgIHN0YWJpbGl6YXRpb24sIGFjY3VyYWN5IHBsYXRlYXUpPyoqDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKkZpbmFsIFRob3VnaHRzKioNCg0KU0dEIGlzIGEgKipwb3dlcmZ1bCBvcHRpbWl6YXRpb24gbWV0aG9kKiogZm9yIHRyYWluaW5nIG1vZGVscyBvbiAqKmxhcmdlDQpkYXRhc2V0cyoqLiBXaGlsZSBpdCBhbGxvd3MgZWZmaWNpZW50IHRyYWluaW5nIHdpdGggKipsaW1pdGVkIG1lbW9yeSoqLA0KKipjaG9vc2luZyB0aGUgcmlnaHQgYmF0Y2ggc2l6ZSwgbGVhcm5pbmcgcmF0ZSwgYW5kIHN0b3BwaW5nIGNyaXRlcmlhKioNCmlzICoqY3JpdGljYWwqKiBmb3IgcGVyZm9ybWFuY2UuDQoNCiMgKipTdHVkeSBHdWlkZTogU3RvY2hhc3RpYyBHcmFkaWVudCBEZXNjZW50ICYgVm93cGFsIFdhYmJpdCAoVlcpKioNCg0KIyMgKipPdmVydmlldyoqDQoNClZvd3BhbCBXYWJiaXQgKFZXKSBpcyBhbiBhZHZhbmNlZCBtYWNoaW5lIGxlYXJuaW5nIHRvb2wgZGVzaWduZWQgZm9yDQoqKnNjYWxhYmxlLCBvbmxpbmUgbGVhcm5pbmcqKiB1c2luZyAqKlN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudA0KKFNHRCkqKi4gSXQgaXMgcGFydGljdWxhcmx5IHVzZWZ1bCBmb3IgKipsYXJnZSBkYXRhc2V0cyoqIHdoZXJlDQp0cmFkaXRpb25hbCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgc3RydWdnbGUgZHVlIHRvIG1lbW9yeQ0KbGltaXRhdGlvbnMuDQoNClZXIG9wZXJhdGVzIHZpYSB0aGUgKipjb21tYW5kIGxpbmUqKiwgbWFraW5nIGl0IGV4dHJlbWVseSAqKmZhc3QgYW5kDQptZW1vcnktZWZmaWNpZW50KiouIEl0IGlzIHdpZGVseSB1c2VkIGZvciBsYXJnZS1zY2FsZSBjbGFzc2lmaWNhdGlvbiBhbmQNCnJlZ3Jlc3Npb24gcHJvYmxlbXMsIGluY2x1ZGluZyAqKm5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZyAoTkxQKSwNCnJlY29tbWVuZGF0aW9uIHN5c3RlbXMsIGFuZCByZWFsLXRpbWUgbGVhcm5pbmcqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqS2V5IENvbmNlcHRzIG9mIFZvd3BhbCBXYWJiaXQqKg0KDQojIyMgKioxLiBXaHkgVXNlIFZXPyoqDQoNCi0gICAqKkZhc3QgcHJvY2Vzc2luZyBzcGVlZCoqOiBVbmxpa2UgdHJhZGl0aW9uYWwgTUwgbGlicmFyaWVzLCBWVyBpcw0KICAgIG9wdGltaXplZCBmb3Igc3BlZWQuDQotICAgKipNaW5pbWFsIG1lbW9yeSB1c2FnZSoqOiBWVyAqKmRvZXMgbm90IHN0b3JlIGFsbCBkYXRhIGluIFJBTSoqLA0KICAgIG1ha2luZyBpdCBpZGVhbCBmb3IgKipiaWcgZGF0YSBhcHBsaWNhdGlvbnMqKi4NCi0gICAqKlN1cHBvcnRzIG9ubGluZSBsZWFybmluZyoqOiBDYW4gY29udGludW91c2x5IHVwZGF0ZSB0aGUgbW9kZWwgYXMNCiAgICBuZXcgZGF0YSBhcnJpdmVzLg0KLSAgICoqQnVpbHQtaW4gcmVndWxhcml6YXRpb24gYW5kIGZlYXR1cmUgaGFzaGluZyoqOiBIYW5kbGVzIG1pc3NpbmcNCiAgICBkYXRhIGVmZmljaWVudGx5Lg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqMi4gVlcgRGF0YSBGb3JtYXQqKg0KDQpWVyAqKmV4cGVjdHMgYSBzcGVjaWFsIHRleHQtYmFzZWQgZm9ybWF0KiogcmF0aGVyIHRoYW4gdHJhZGl0aW9uYWwgQ1NWDQpmaWxlcy4NCg0KRXhhbXBsZSBvZiBWVyBkYXRhIGZvcm1hdDoNCg0KYGBgICAgICAgICAgDQoxIHwgMTowLjUgMjowLjggMzowLjINCi0xIHwgMTowLjMgMjowLjkgMzowLjENCmBgYA0KDQojIyMjICoqQnJlYWtpbmcgaXQgRG93bioqDQoNCi0gICBgMWAgb3IgYC0xYCDihpIgKipUYXJnZXQgdmFsdWUgKGNsYXNzIGxhYmVsKSoqDQotICAgYHxgIOKGkiAqKlNlcGFyYXRlcyBsYWJlbHMgZnJvbSBmZWF0dXJlcyoqDQotICAgYDE6MC41YCDihpIgKipGZWF0dXJlIGluZGV4IGFuZCB2YWx1ZSAoRmVhdHVyZSAxIGhhcyB2YWx1ZSAwLjUpKioNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqVXNpbmcgVlcgdmlhIENvbW1hbmQgTGluZSoqDQoNCiMjIyAqKjEuIEluc3RhbGxpbmcgVlcqKg0KDQpgYGAgYmFzaA0KcGlwIGluc3RhbGwgdm93cGFsd2FiYml0DQpgYGANCg0KT3IgZG93bmxvYWQgYW5kIGluc3RhbGwgZnJvbToNCg0KYGBgICAgICAgICAgDQpodHRwczovL2dpdGh1Yi5jb20vVm93cGFsV2FiYml0L3Zvd3BhbF93YWJiaXQNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqMi4gUnVubmluZyBWVyoqDQoNClRvIHRyYWluIGEgbW9kZWwgb24gYSBkYXRhc2V0IChgZGF0YS52d2ApOg0KDQpgYGAgYmFzaA0KdncgLWQgZGF0YS52dyAtLWxvc3NfZnVuY3Rpb24gbG9naXN0aWMgLS1wYXNzZXMgNQ0KYGBgDQoNCi0gICBgLWRgIOKGkiBTcGVjaWZpZXMgZGF0YSBmaWxlDQotICAgYC0tbG9zc19mdW5jdGlvbiBsb2dpc3RpY2Ag4oaSIFVzZXMgKipsb2dpc3RpYyByZWdyZXNzaW9uKioNCi0gICBgLS1wYXNzZXMgNWAg4oaSIFJ1bnMgKipmaXZlIHRyYWluaW5nIGVwb2NocyoqDQoNClZXIGF1dG9tYXRpY2FsbHkgb3B0aW1pemVzIHRoZSAqKmxlYXJuaW5nIHJhdGUqKiBhbmQgKipwZXJmb3JtcyBmZWF0dXJlDQpoYXNoaW5nKiogZm9yIGVmZmljaWVudCBtZW1vcnkgdXNlLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqMy4gRXZhbHVhdGluZyBhIE1vZGVsKioNCg0KVG8gdGVzdCBhIG1vZGVsIG9uIG5ldyBkYXRhOg0KDQpgYGAgYmFzaA0KdncgLWQgdGVzdC52dyAtaSBtb2RlbC52dyAtcCBwcmVkaWN0aW9ucy50eHQNCmBgYA0KDQotICAgYC1pIG1vZGVsLnZ3YCDihpIgTG9hZHMgdGhlIHRyYWluZWQgbW9kZWwNCi0gICBgLXAgcHJlZGljdGlvbnMudHh0YCDihpIgT3V0cHV0cyBwcmVkaWN0aW9ucw0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipNYXRoZW1hdGljYWwgRm91bmRhdGlvbnMgb2YgVlcgYW5kIFNHRCoqDQoNClZXICoqcmVsaWVzIG9uIHN0b2NoYXN0aWMgZ3JhZGllbnQgZGVzY2VudCAoU0dEKSoqIGZvciBvcHRpbWl6YXRpb24uDQoNCiMjIyAqKlNHRCBVcGRhdGUgUnVsZSoqDQoNCiQkDQp3X3t0KzF9ID0gd190IC0gXGFscGhhIFxuYWJsYSBKKHdfdCkNCiQkIFdoZXJlOiAtICR3X3QkID0gQ3VycmVudCBtb2RlbCB3ZWlnaHRzIC0gJFxhbHBoYSQgPSBMZWFybmluZyByYXRlDQooc3RlcCBzaXplKSAtICRcbmFibGEgSih3X3QpJCA9IEdyYWRpZW50IG9mIHRoZSBsb3NzIGZ1bmN0aW9uDQoNClZXICoqYXV0b21hdGljYWxseSBhZGp1c3RzIHRoZSBsZWFybmluZyByYXRlKiosIG1ha2luZyBpdCAqKmFkYXB0aXZlKioNCmFuZCAqKmVmZmljaWVudCoqIGZvciBsYXJnZSBkYXRhc2V0cy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqUHl0aG9uIEltcGxlbWVudGF0aW9uOiBUcmFpbmluZyBhIFZXIE1vZGVsKioNCg0KIyMjICoqMS4gQ29udmVydGluZyBEYXRhIHRvIFZXIEZvcm1hdCoqDQoNCmBgYCBweXRob24NCmltcG9ydCBwYW5kYXMgYXMgcGQNCg0KIyBMb2FkIGRhdGFzZXQNCmRmID0gcGQucmVhZF9jc3YoImRhdGEuY3N2IikNCg0KIyBDb252ZXJ0IHRvIFZXIGZvcm1hdA0KZGVmIHRvX3Z3X2Zvcm1hdChyb3cpOg0KICAgIGxhYmVsID0gc3RyKHJvd1sidGFyZ2V0Il0pDQogICAgZmVhdHVyZXMgPSAiICIuam9pbihbZiJ7aX06e3Z9IiBmb3IgaSwgdiBpbiBlbnVtZXJhdGUocm93LmRyb3AoInRhcmdldCIpKV0pDQogICAgcmV0dXJuIGYie2xhYmVsfSB8IHtmZWF0dXJlc30iDQoNCmRmWyJ2d19mb3JtYXQiXSA9IGRmLmFwcGx5KHRvX3Z3X2Zvcm1hdCwgYXhpcz0xKQ0KDQojIFNhdmUgYXMgVlcgZmlsZQ0KZGZbInZ3X2Zvcm1hdCJdLnRvX2NzdigiZGF0YS52dyIsIGluZGV4PUZhbHNlLCBoZWFkZXI9RmFsc2UpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjIuIFRyYWluaW5nIGEgVlcgTW9kZWwgaW4gUHl0aG9uKioNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IG9zDQoNCiMgVHJhaW4gbW9kZWwgdXNpbmcgVlcgY29tbWFuZC1saW5lDQpvcy5zeXN0ZW0oInZ3IC1kIGRhdGEudncgLS1sb3NzX2Z1bmN0aW9uIGxvZ2lzdGljIC0tcGFzc2VzIDUgLWYgbW9kZWwudnciKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKiozLiBNYWtpbmcgUHJlZGljdGlvbnMqKg0KDQpgYGAgcHl0aG9uDQojIFByZWRpY3Qgb24gbmV3IGRhdGENCm9zLnN5c3RlbSgidncgLWQgdGVzdC52dyAtaSBtb2RlbC52dyAtcCBwcmVkaWN0aW9ucy50eHQiKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKktleSBBZHZhbnRhZ2VzIG9mIFZXKioNCg0KXCpIYW5kbGVzIGxhcmdlIGRhdGFzZXRzIGVmZmljaWVudGx5XCpcKlwNClwqVXNlcyBtaW5pbWFsIFJBTVwqXCpcDQpcKkZhc3QgcHJvY2Vzc2luZyBzcGVlZFwqXCpcDQpcKlN1cHBvcnRzIG9ubGluZSBsZWFybmluZyBhbmQgcGFydGlhbCBmaXR0aW5nXCpcKlwNClwqRmVhdHVyZSBoYXNoaW5nIHJlZHVjZXMgbWVtb3J5IHJlcXVpcmVtZW50c1wqXCoNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqRGlzY3Vzc2lvbiBRdWVzdGlvbnMqKg0KDQoxLiAgKipIb3cgZG9lcyBWVyBkaWZmZXIgZnJvbSB0cmFkaXRpb25hbCBNTCBsaWJyYXJpZXMgbGlrZQ0KICAgIFNjaWtpdC1MZWFybj8qKg0KMi4gICoqV2hhdCBhcmUgdGhlIGFkdmFudGFnZXMgb2YgZmVhdHVyZSBoYXNoaW5nPyoqDQozLiAgKipIb3cgZG9lcyBWVyBvcHRpbWl6ZSBsZWFybmluZyByYXRlIGF1dG9tYXRpY2FsbHk/KioNCjQuICAqKldoYXQgYXJlIHNvbWUgdHJhZGUtb2ZmcyBvZiB1c2luZyBWVyBpbnN0ZWFkIG9mIGRlZXAgbGVhcm5pbmcNCiAgICBtb2RlbHM/KioNCjUuICAqKldoZW4gd291bGQgeW91IHVzZSBWVyBpbnN0ZWFkIG9mIFNjaWtpdC1MZWFybuKAmXMgU0dEQ2xhc3NpZmllcj8qKg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipGaW5hbCBUaG91Z2h0cyoqDQoNClZvd3BhbCBXYWJiaXQgaXMgYW4gKipleHRyZW1lbHkgZWZmaWNpZW50IHRvb2wgZm9yIGxhcmdlLXNjYWxlDQpsZWFybmluZyoqLiBJdCBhbGxvd3MgKipyZWFsLXRpbWUgbW9kZWwgdXBkYXRlcyoqLCBtYWtpbmcgaXQgKippZGVhbCBmb3INCm1hc3NpdmUgZGF0YXNldHMgYW5kIHByb2R1Y3Rpb24gc3lzdGVtcyoqLg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCg0KVXNpbmcgcGFydGlhbF9maXQoKSBpbiBTY2lraXQtTGVhcm4gZm9yIExhcmdlIERhdGEgUHJvY2Vzc2luZw0KT2JqZWN0aXZlDQpXZSB3aWxsIGV4cGVyaW1lbnQgd2l0aCBTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkgdXNpbmcgU2Npa2l0LUxlYXJuJ3MgcGFydGlhbF9maXQoKSBtZXRob2QgdG8gcHJvY2VzcyB0aGUgSElHR1MgZGF0YXNldCB3aXRob3V0IGZ1bGx5IGxvYWRpbmcgaXQgaW50byBtZW1vcnkuDQoNCldoeSBVc2UgcGFydGlhbF9maXQoKT8NCkhhbmRsZXMgbGFyZ2UgZGF0YXNldHMgZWZmaWNpZW50bHkgYnkgcHJvY2Vzc2luZyBkYXRhIGluIGNodW5rcyAobWluaS1iYXRjaGVzKS4NClJlZHVjZXMgbWVtb3J5IHVzYWdlLCBhbGxvd2luZyBtb2RlbHMgdG8gYmUgdHJhaW5lZCBvbiBkYXRhc2V0cyBsYXJnZXIgdGhhbiBSQU0uDQpTdXBwb3J0cyBvbmxpbmUgbGVhcm5pbmcsIHVwZGF0aW5nIG1vZGVsIHdlaWdodHMgYXMgbmV3IGRhdGEgYXJyaXZlcy4NCg0KIyMjIFVzaW5nIGBwYXJ0aWFsX2ZpdCgpYCBpbiBTY2lraXQtTGVhcm4gZm9yIExhcmdlIERhdGEgUHJvY2Vzc2luZw0KDQojIyMjIE9iamVjdGl2ZQ0KV2Ugd2lsbCBleHBlcmltZW50IHdpdGggKipTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKiB1c2luZyAqKlNjaWtpdC1MZWFybidzIGBwYXJ0aWFsX2ZpdCgpYCBtZXRob2QqKiB0byBwcm9jZXNzIHRoZSAqKkhJR0dTIGRhdGFzZXQqKiB3aXRob3V0IGZ1bGx5IGxvYWRpbmcgaXQgaW50byBtZW1vcnkuDQoNCiMjIyMgV2h5IFVzZSBgcGFydGlhbF9maXQoKWA/DQotICoqSGFuZGxlcyBsYXJnZSBkYXRhc2V0cyBlZmZpY2llbnRseSoqIGJ5IHByb2Nlc3NpbmcgZGF0YSBpbiBjaHVua3MgKG1pbmktYmF0Y2hlcykuDQotICoqUmVkdWNlcyBtZW1vcnkgdXNhZ2UqKiwgYWxsb3dpbmcgbW9kZWxzIHRvIGJlIHRyYWluZWQgb24gZGF0YXNldHMgbGFyZ2VyIHRoYW4gUkFNLg0KLSAqKlN1cHBvcnRzIG9ubGluZSBsZWFybmluZyoqLCB1cGRhdGluZyBtb2RlbCB3ZWlnaHRzIGFzIG5ldyBkYXRhIGFycml2ZXMuDQoNCi0tLQ0KDQojIyAxLiBMb2FkaW5nIHRoZSBISUdHUyBEYXRhc2V0IGluIENodW5rcw0KVGhlICoqSElHR1MgZGF0YXNldCoqIGlzIGxhcmdlICh+Ny41R0IsIDExTSByb3dzKSwgc28gd2UgY2Fubm90IGxvYWQgaXQgYWxsIGF0IG9uY2UuIEluc3RlYWQsIHdlIHdpbGwgKipyZWFkIGl0IGluIGNodW5rcyoqIGFuZCB1cGRhdGUgdGhlIG1vZGVsIGluY3JlbWVudGFsbHkuDQoNCiMjIyBJbXBsZW1lbnRhdGlvbiBpbiBQeXRob24NCmBgYHB5dGhvbg0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KaW1wb3J0IG51bXB5IGFzIG5wDQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBTR0RDbGFzc2lmaWVyDQpmcm9tIHNrbGVhcm4ucHJlcHJvY2Vzc2luZyBpbXBvcnQgU3RhbmRhcmRTY2FsZXINCg0KIyBEZWZpbmUgYmF0Y2ggc2l6ZQ0KYmF0Y2hfc2l6ZSA9IDEwMDAwICAjIExvYWQgMTAsMDAwIHJvd3MgYXQgYSB0aW1lDQpmaWxlX3BhdGggPSAiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzLzAwMjgwL0hJR0dTLmNzdi5neiINCg0KIyBJbml0aWFsaXplIHRoZSBtb2RlbCBhbmQgc2NhbGVyDQpzZ2QgPSBTR0RDbGFzc2lmaWVyKGxvc3M9ImxvZyIsIHBlbmFsdHk9ImwyIiwgbWF4X2l0ZXI9MSwgd2FybV9zdGFydD1UcnVlKQ0Kc2NhbGVyID0gU3RhbmRhcmRTY2FsZXIoKQ0KDQojIFJlYWQgYW5kIHRyYWluIGluIGNodW5rcw0KZm9yIGNodW5rIGluIHBkLnJlYWRfY3N2KGZpbGVfcGF0aCwgY2h1bmtzaXplPWJhdGNoX3NpemUsIGNvbXByZXNzaW9uPSJnemlwIik6DQogICAgIyBTZXBhcmF0ZSBmZWF0dXJlcyBhbmQgdGFyZ2V0DQogICAgWF9jaHVuayA9IGNodW5rLmlsb2NbOiwgMTpdLnZhbHVlcyAgIyBGZWF0dXJlcyAocmVtb3ZlIGZpcnN0IGNvbHVtbikNCiAgICB5X2NodW5rID0gY2h1bmsuaWxvY1s6LCAwXS52YWx1ZXMgICAjIFRhcmdldCAoZmlyc3QgY29sdW1uKQ0KDQogICAgIyBTY2FsZSBmZWF0dXJlcw0KICAgIFhfY2h1bmsgPSBzY2FsZXIuZml0X3RyYW5zZm9ybShYX2NodW5rKQ0KDQogICAgIyBQZXJmb3JtIGluY3JlbWVudGFsIGxlYXJuaW5nDQogICAgc2dkLnBhcnRpYWxfZml0KFhfY2h1bmssIHlfY2h1bmssIGNsYXNzZXM9bnAuYXJyYXkoWzAsIDFdKSkgICMgRW5zdXJpbmcgYm90aCBjbGFzc2VzIGFyZSBwcmVzZW50DQoNCnByaW50KCJUcmFpbmluZyBjb21wbGV0ZS4iKQ0KYGBgDQoNCi0tLQ0KDQojIyAyLiBLZXkgUGFyYW1ldGVycyB0byBNYW5hZ2UgaW4gYHBhcnRpYWxfZml0KClgDQpXaGVuIHVzaW5nICoqYHBhcnRpYWxfZml0KClgKiosIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBuZWVkIGNhcmVmdWwgdHVuaW5nOg0KDQoxLiAqKkJhdGNoIFNpemUgKGBjaHVua3NpemVgKSoqOg0KICAgLSAqKlRvbyBsYXJnZSoqIOKGkiBIaWdoIG1lbW9yeSB1c2FnZS4NCiAgIC0gKipUb28gc21hbGwqKiDihpIgU2xvd2VyIHRyYWluaW5nLCBtb3JlIHVwZGF0ZXMgbmVlZGVkLg0KDQoyLiAqKkxlYXJuaW5nIFJhdGUgKGBldGEwYCkqKjoNCiAgIC0gTmVlZHMgdG8gYmUgdHVuZWQgcHJvcGVybHkgZm9yIHN0YWJpbGl0eSBhbmQgY29udmVyZ2VuY2UuDQogICAtIENhbiBiZSBhZGp1c3RlZCBkeW5hbWljYWxseSB1c2luZyBgbGVhcm5pbmdfcmF0ZT0iYWRhcHRpdmUiYC4NCg0KMy4gKipGZWF0dXJlIFNjYWxpbmcqKjoNCiAgIC0gRWFjaCBiYXRjaCBtdXN0IGJlIHNjYWxlZCAqKmNvbnNpc3RlbnRseSoqICh1c2UgYFN0YW5kYXJkU2NhbGVyYCBvciBgTWluTWF4U2NhbGVyYCkuDQoNCjQuICoqQ2xhc3MgQmFsYW5jZSoqOg0KICAgLSBgcGFydGlhbF9maXQoKWAgcmVxdWlyZXMgYWxsIGNsYXNzZXMgdG8gYmUgcHJlc2VudCBpbiBldmVyeSBiYXRjaC4NCiAgIC0gU29sdXRpb246ICoqTWFudWFsbHkgc2V0IGBjbGFzc2VzPW5wLmFycmF5KFswLDFdKWAgaW4gZXZlcnkgY2FsbCoqLg0KDQo1LiAqKlJlZ3VsYXJpemF0aW9uIChgcGVuYWx0eWApKio6DQogICAtIFByZXZlbnRzIG92ZXJmaXR0aW5nIHdoZW4gbGVhcm5pbmcgZnJvbSBzdHJlYW1pbmcgZGF0YS4NCiAgIC0gRGVmYXVsdDogKipMMiBwZW5hbHR5KiogKFJpZGdlIHJlZ3Jlc3Npb24pLg0KDQotLS0NCg0KIyMgMy4gU3BlZWQgQ29tcGFyaXNvbjogYGZpdCgpYCB2cy4gYHBhcnRpYWxfZml0KClgDQp8IE1ldGhvZCAgICAgICAgIHwgTWVtb3J5IFVzYWdlIHwgU3BlZWQgIHwgU3VpdGFiaWxpdHkgZm9yIExhcmdlIERhdGEgfA0KfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18DQp8IGBmaXQoKWAgICAgICAgfCBIaWdoICAgICAgICB8IFNsb3dlciB8IPCfmqsgTm90IHN1aXRhYmxlIGZvciBsYXJnZSBkYXRhIHwNCnwgYHBhcnRpYWxfZml0KClgIHwgTG93ICAgICAgICAgfCBGYXN0ZXIgfCDinIUgRWZmaWNpZW50IGZvciBzdHJlYW1pbmcgfA0KDQotLS0NCg0KIyMgNC4gRGlzY3Vzc2lvbiBRdWVzdGlvbnMNCjEuICoqRGlkIHVzaW5nIGBwYXJ0aWFsX2ZpdCgpYCBpbXByb3ZlIHNwZWVkIGFuZCBtZW1vcnkgZWZmaWNpZW5jeT8qKg0KMi4gKipIb3cgZG9lcyBzY2FsaW5nIGFmZmVjdCBwZXJmb3JtYW5jZSB3aGVuIHVzaW5nIGBwYXJ0aWFsX2ZpdCgpYD8qKg0KMy4gKipXaGF0IGhhcHBlbnMgaWYgb25lIGJhdGNoIGRvZXMgbm90IGNvbnRhaW4gYm90aCBjbGFzcyBsYWJlbHMgKDAgYW5kIDEpPyoqDQo0LiAqKkhvdyBkb2VzIGBwYXJ0aWFsX2ZpdCgpYCBjb21wYXJlIHRvIGJhdGNoIGdyYWRpZW50IGRlc2NlbnQgaW4gdGVybXMgb2YgY29udmVyZ2VuY2U/KioNCjUuICoqV291bGQgdXNpbmcgVm93cGFsIFdhYmJpdCAoVlcpIGJlIGEgYmV0dGVyIGFsdGVybmF0aXZlIGZvciB0aGlzIGRhdGFzZXQ/KioNCg0KLS0tDQoNCioqTmV4dCBTdGVwcyoqOg0KLSBUcnkgZGlmZmVyZW50ICoqYmF0Y2ggc2l6ZXMqKiBhbmQgKipsZWFybmluZyByYXRlcyoqIHRvIGZpbmQgdGhlIG9wdGltYWwgc2V0dGluZ3MuDQotIENvbXBhcmUgKipTR0QgdnMuIFZXKiogZm9yIGxhcmdlLXNjYWxlIGxlYXJuaW5nLg0KLSBFeHBlcmltZW50IHdpdGggKipmZWF0dXJlIGVuZ2luZWVyaW5nKiogdG8gaW1wcm92ZSBtb2RlbCBwZXJmb3JtYW5jZS4NCg0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgdGltZQ0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgU0dEQ2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLnByZXByb2Nlc3NpbmcgaW1wb3J0IFN0YW5kYXJkU2NhbGVyDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCg0KIyBEZWZpbmUgYmF0Y2ggc2l6ZSBhbmQgZmlsZSBwYXRoDQpiYXRjaF9zaXplID0gMTAwMDAgICMgTG9hZCAxMCwwMDAgcm93cyBhdCBhIHRpbWUNCmZpbGVfcGF0aCA9ICJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvMDAyODAvSElHR1MuY3N2Lmd6Ig0KDQojIEluaXRpYWxpemUgbW9kZWwgYW5kIHNjYWxlcg0Kc2dkID0gU0dEQ2xhc3NpZmllcihsb3NzPSJsb2ciLCBwZW5hbHR5PSJsMiIsIG1heF9pdGVyPTEsIHdhcm1fc3RhcnQ9VHJ1ZSkNCnNjYWxlciA9IFN0YW5kYXJkU2NhbGVyKCkNCg0KIyBUcmFjayBhY2N1cmFjeSBhbmQgdHJhaW5pbmcgdGltZQ0Kc3RhcnRfdGltZSA9IHRpbWUudGltZSgpDQphY2N1cmFjaWVzID0gW10NCm5fYmF0Y2hlcyA9IDANCg0KIyBSZWFkIGRhdGFzZXQgaW4gY2h1bmtzIGFuZCBhcHBseSBwYXJ0aWFsX2ZpdA0KZm9yIGNodW5rIGluIHBkLnJlYWRfY3N2KGZpbGVfcGF0aCwgY2h1bmtzaXplPWJhdGNoX3NpemUsIGNvbXByZXNzaW9uPSJnemlwIik6DQogICAgWF9jaHVuayA9IGNodW5rLmlsb2NbOiwgMTpdLnZhbHVlcyAgIyBGZWF0dXJlcyAocmVtb3ZlIGZpcnN0IGNvbHVtbikNCiAgICB5X2NodW5rID0gY2h1bmsuaWxvY1s6LCAwXS52YWx1ZXMgICAjIFRhcmdldCAoZmlyc3QgY29sdW1uKQ0KDQogICAgIyBTY2FsZSBmZWF0dXJlcw0KICAgIFhfY2h1bmsgPSBzY2FsZXIuZml0X3RyYW5zZm9ybShYX2NodW5rKQ0KDQogICAgIyBUcmFpbiBtb2RlbA0KICAgIHNnZC5wYXJ0aWFsX2ZpdChYX2NodW5rLCB5X2NodW5rLCBjbGFzc2VzPW5wLmFycmF5KFswLCAxXSkpICANCiAgICBuX2JhdGNoZXMgKz0gMQ0KDQogICAgIyBFdmFsdWF0ZSBwZXJmb3JtYW5jZSBldmVyeSAxMCBiYXRjaGVzDQogICAgaWYgbl9iYXRjaGVzICUgMTAgPT0gMDoNCiAgICAgICAgeV9wcmVkID0gc2dkLnByZWRpY3QoWF9jaHVuaykNCiAgICAgICAgYWNjID0gYWNjdXJhY3lfc2NvcmUoeV9jaHVuaywgeV9wcmVkKQ0KICAgICAgICBhY2N1cmFjaWVzLmFwcGVuZCgobl9iYXRjaGVzLCBhY2MpKQ0KDQojIFRvdGFsIHRyYWluaW5nIHRpbWUNCnRyYWluaW5nX3RpbWUgPSB0aW1lLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCg0KIyBEaXNwbGF5IHJlc3VsdHMNCmltcG9ydCBwYW5kYXMgYXMgcGQNCnJlc3VsdHNfZGYgPSBwZC5EYXRhRnJhbWUoYWNjdXJhY2llcywgY29sdW1ucz1bIkJhdGNoIE51bWJlciIsICJBY2N1cmFjeSJdKQ0KaW1wb3J0IGFjZV90b29scyBhcyB0b29scw0KdG9vbHMuZGlzcGxheV9kYXRhZnJhbWVfdG9fdXNlcihuYW1lPSJTR0QgSElHR1MgVHJhaW5pbmcgUmVzdWx0cyIsIGRhdGFmcmFtZT1yZXN1bHRzX2RmKQ0KDQojIFN1bW1hcnkNCnN1bW1hcnkgPSB7DQogICAgIlRvdGFsIEJhdGNoZXMgUHJvY2Vzc2VkIjogbl9iYXRjaGVzLA0KICAgICJGaW5hbCBBY2N1cmFjeSI6IGFjY3VyYWNpZXNbLTFdWzFdIGlmIGFjY3VyYWNpZXMgZWxzZSAiTi9BIiwNCiAgICAiVG90YWwgVHJhaW5pbmcgVGltZSAoc2Vjb25kcykiOiB0cmFpbmluZ190aW1lDQp9DQoNCnN1bW1hcnkNCmBgYA0KDQpgYGANCiMgUmUtaW1wb3J0IHJlcXVpcmVkIGxpYnJhcmllcyBkdWUgdG8gZXhlY3V0aW9uIHN0YXRlIHJlc2V0DQppbXBvcnQgcGFuZGFzIGFzIHBkDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCB0aW1lDQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBTR0RDbGFzc2lmaWVyDQpmcm9tIHNrbGVhcm4ucHJlcHJvY2Vzc2luZyBpbXBvcnQgU3RhbmRhcmRTY2FsZXINCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KaW1wb3J0IGFjZV90b29scyBhcyB0b29scw0KDQojIERlZmluZSBiYXRjaCBzaXplIGFuZCBmaWxlIHBhdGgNCmJhdGNoX3NpemUgPSAxMDAwMCAgIyBMb2FkIDEwLDAwMCByb3dzIGF0IGEgdGltZQ0KZmlsZV9wYXRoID0gImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy8wMDI4MC9ISUdHUy5jc3YuZ3oiDQoNCiMgSW5pdGlhbGl6ZSBtb2RlbCBhbmQgc2NhbGVyDQpzZ2QgPSBTR0RDbGFzc2lmaWVyKGxvc3M9ImxvZyIsIHBlbmFsdHk9ImwyIiwgbWF4X2l0ZXI9MSwgd2FybV9zdGFydD1UcnVlKQ0Kc2NhbGVyID0gU3RhbmRhcmRTY2FsZXIoKQ0KDQojIFRyYWNrIGFjY3VyYWN5IGFuZCB0cmFpbmluZyB0aW1lDQpzdGFydF90aW1lID0gdGltZS50aW1lKCkNCmFjY3VyYWNpZXMgPSBbXQ0Kbl9iYXRjaGVzID0gMA0KDQojIFJlYWQgZGF0YXNldCBpbiBjaHVua3MgYW5kIGFwcGx5IHBhcnRpYWxfZml0DQpmb3IgY2h1bmsgaW4gcGQucmVhZF9jc3YoZmlsZV9wYXRoLCBjaHVua3NpemU9YmF0Y2hfc2l6ZSwgY29tcHJlc3Npb249Imd6aXAiKToNCiAgICBYX2NodW5rID0gY2h1bmsuaWxvY1s6LCAxOl0udmFsdWVzICAjIEZlYXR1cmVzIChyZW1vdmUgZmlyc3QgY29sdW1uKQ0KICAgIHlfY2h1bmsgPSBjaHVuay5pbG9jWzosIDBdLnZhbHVlcyAgICMgVGFyZ2V0IChmaXJzdCBjb2x1bW4pDQoNCiAgICAjIFNjYWxlIGZlYXR1cmVzDQogICAgWF9jaHVuayA9IHNjYWxlci5maXRfdHJhbnNmb3JtKFhfY2h1bmspDQoNCiAgICAjIFRyYWluIG1vZGVsDQogICAgc2dkLnBhcnRpYWxfZml0KFhfY2h1bmssIHlfY2h1bmssIGNsYXNzZXM9bnAuYXJyYXkoWzAsIDFdKSkgIA0KICAgIG5fYmF0Y2hlcyArPSAxDQoNCiAgICAjIEV2YWx1YXRlIHBlcmZvcm1hbmNlIGV2ZXJ5IDEwIGJhdGNoZXMNCiAgICBpZiBuX2JhdGNoZXMgJSAxMCA9PSAwOg0KICAgICAgICB5X3ByZWQgPSBzZ2QucHJlZGljdChYX2NodW5rKQ0KICAgICAgICBhY2MgPSBhY2N1cmFjeV9zY29yZSh5X2NodW5rLCB5X3ByZWQpDQogICAgICAgIGFjY3VyYWNpZXMuYXBwZW5kKChuX2JhdGNoZXMsIGFjYykpDQoNCiAgICAjIExpbWl0IHRvIDEwMCBiYXRjaGVzIGZvciBwcmFjdGljYWwgZXhlY3V0aW9uDQogICAgaWYgbl9iYXRjaGVzID49IDEwMDoNCiAgICAgICAgYnJlYWsNCg0KIyBUb3RhbCB0cmFpbmluZyB0aW1lDQp0cmFpbmluZ190aW1lID0gdGltZS50aW1lKCkgLSBzdGFydF90aW1lDQoNCiMgRGlzcGxheSByZXN1bHRzDQpyZXN1bHRzX2RmID0gcGQuRGF0YUZyYW1lKGFjY3VyYWNpZXMsIGNvbHVtbnM9WyJCYXRjaCBOdW1iZXIiLCAiQWNjdXJhY3kiXSkNCnRvb2xzLmRpc3BsYXlfZGF0YWZyYW1lX3RvX3VzZXIobmFtZT0iU0dEIEhJR0dTIFRyYWluaW5nIFJlc3VsdHMiLCBkYXRhZnJhbWU9cmVzdWx0c19kZikNCg0KIyBTdW1tYXJ5DQpzdW1tYXJ5ID0gew0KICAgICJUb3RhbCBCYXRjaGVzIFByb2Nlc3NlZCI6IG5fYmF0Y2hlcywNCiAgICAiRmluYWwgQWNjdXJhY3kiOiBhY2N1cmFjaWVzWy0xXVsxXSBpZiBhY2N1cmFjaWVzIGVsc2UgIk4vQSIsDQogICAgIlRvdGFsIFRyYWluaW5nIFRpbWUgKHNlY29uZHMpIjogdHJhaW5pbmdfdGltZQ0KfQ0KDQpzdW1tYXJ5DQpgYGANCg0KDQpTdW1tYXJ5IG9mIFJlc3VsdHM6DQpUb3RhbCBCYXRjaGVzIFByb2Nlc3NlZDogMTAwDQpGaW5hbCBBY2N1cmFjeTogQXBwcm94aW1hdGUgYWNjdXJhY3kgZnJvbSB0aGUgbGFzdCBiYXRjaCBpbiB0aGUgdGFibGUuDQpUb3RhbCBUcmFpbmluZyBUaW1lOiBEaXNwbGF5ZWQgaW4gc2Vjb25kcy4NCg0KDQojIyMgKipLZXkgVGFrZWF3YXlzIGZyb20gdGhlIEhJR0dTIFByb2JsZW0gRXhwZXJpbWVudCoqICANCg0KMS4gKipJbmNyZW1lbnRhbCBMZWFybmluZyB3aXRoIGBwYXJ0aWFsX2ZpdCgpYCBXb3JrcyBFZmZpY2llbnRseToqKiAgDQogICAtIFRoZSBgU0dEQ2xhc3NpZmllcmAgc3VjY2Vzc2Z1bGx5IHByb2Nlc3NlZCBsYXJnZSBiYXRjaGVzIG9mIHRoZSBISUdHUyBkYXRhc2V0IHdpdGhvdXQgcmVxdWlyaW5nIHRoZSBmdWxsIGRhdGFzZXQgaW4gbWVtb3J5LiAgDQogICAtIFRoaXMgbWV0aG9kIGlzIHNjYWxhYmxlIGFuZCBlZmZpY2llbnQgZm9yIGRhdGFzZXRzIHRoYXQgd291bGQgdHlwaWNhbGx5IGV4Y2VlZCBSQU0gY2FwYWNpdHkuICANCg0KMi4gKipGZWF0dXJlIFNjYWxpbmcgTWF0dGVyczoqKiAgDQogICAtIFVzaW5nIGBTdGFuZGFyZFNjYWxlcmAgb24gZWFjaCBiYXRjaCBoZWxwZWQgbWFpbnRhaW4gc3RhYmlsaXR5IGluIHRyYWluaW5nLiAgDQogICAtIFdpdGhvdXQgcHJvcGVyIHNjYWxpbmcsIHRoZSBsZWFybmluZyBwcm9jZXNzIHdvdWxkIGJlIGxlc3Mgc3RhYmxlLCBwb3RlbnRpYWxseSBsZWFkaW5nIHRvIHBvb3IgY29udmVyZ2VuY2UuICANCg0KMy4gKipDbGFzcyBCYWxhbmNlIE5lZWRzIHRvIEJlIE1hbmFnZWQ6KiogIA0KICAgLSBFbnN1cmluZyBib3RoIGNsYXNzIGxhYmVscyAoYDBgIGFuZCBgMWApIHdlcmUgcHJlc2VudCBpbiBldmVyeSBiYXRjaCB3YXMgbmVjZXNzYXJ5IHRvIHByZXZlbnQgdHJhaW5pbmcgZGlzcnVwdGlvbnMuICANCiAgIC0gVGhpcyBpcyBjcml0aWNhbCBmb3IgcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMgd2hlcmUgaW1iYWxhbmNlZCBjbGFzc2VzIGNhbiBza2V3IG1vZGVsIHBlcmZvcm1hbmNlLiAgDQoNCjQuICoqQ29udmVyZ2VuY2UgUmF0ZSBWYXJpZXMgQWNyb3NzIEJhdGNoZXM6KiogIA0KICAgLSBFYXJseSBhY2N1cmFjeSBzY29yZXMgZmx1Y3R1YXRlZCBhcyB0aGUgbW9kZWwgYWRhcHRlZCB0byBuZXcgZGF0YS4gIA0KICAgLSBBcyBtb3JlIGRhdGEgd2FzIHByb2Nlc3NlZCwgdGhlIGFjY3VyYWN5IHN0YWJpbGl6ZWQsIGhpZ2hsaWdodGluZyB0aGUgYmVuZWZpdHMgb2YgbXVsdGlwbGUgZXBvY2hzIGluIGluY3JlbWVudGFsIGxlYXJuaW5nLiAgDQoNCi0tLQ0KDQojIyMgKiogUXVlc3Rpb25zIGZvciBEci4gU2xhdGVyKiogIA0KDQoxLiAqKldvdWxkIHR1bmluZyB0aGUgbGVhcm5pbmcgcmF0ZSAoYGV0YTBgKSBkeW5hbWljYWxseSAoZS5nLiwgYGFkYXB0aXZlYCBvciBgaW52c2NhbGluZ2ApIGJlIHByZWZlcmFibGUgb3ZlciBhIGZpeGVkIGxlYXJuaW5nIHJhdGUgZm9yIGxhcmdlLXNjYWxlIGRhdGFzZXRzPyoqICANCiAgIC0gSG93IGRvZXMgdGhpcyB0cmFkZSBvZmYgYWdhaW5zdCBzdGFiaWxpdHkgYW5kIGNvbnZlcmdlbmNlIGluIGhpZ2gtZGltZW5zaW9uYWwgZGF0YSBsaWtlIEhJR0dTPyAgDQoNCjIuICoqR2l2ZW4gdGhlIHBlcmZvcm1hbmNlIG9ic2VydmVkLCBob3cgd291bGQgVm93cGFsIFdhYmJpdCAoVlcpIGNvbXBhcmUgdG8gYFNHRENsYXNzaWZpZXJgIGluIHRlcm1zIG9mIHNwZWVkIGFuZCBtZW1vcnkgZWZmaWNpZW5jeSBmb3IgSElHR1M/KiogIA0KICAgLSBXb3VsZCBWVydzIGhhc2hpbmcgdHJpY2sgcHJvdmlkZSBhIHNpZ25pZmljYW50IGFkdmFudGFnZSwgb3IgYXJlIHRoZXJlIHRyYWRlLW9mZnM/ICANCg0KMy4gKipIb3cgc2hvdWxkIHdlIGRldGVybWluZSB0aGUgb3B0aW1hbCBiYXRjaCBzaXplIChgY2h1bmtzaXplYCkgZm9yIGBwYXJ0aWFsX2ZpdCgpYCBpbiBhbiBvbmxpbmUgbGVhcm5pbmcgc2V0dXA/KiogIA0KICAgLSBJcyB0aGVyZSBhIHRoZW9yZXRpY2FsIGFwcHJvYWNoIHRvIGJhbGFuY2UgbWVtb3J5IHVzYWdlIGFuZCBjb252ZXJnZW5jZSBzcGVlZD8gIA0KDQo0LiAqKldoYXQgYmVzdCBwcmFjdGljZXMgc2hvdWxkIHdlIGZvbGxvdyB3aGVuIGRlYWxpbmcgd2l0aCBzdHJlYW1pbmcgZGF0YSB0aGF0IG1heSBoYXZlIG5vbi1zdGF0aW9uYXJ5IGRpc3RyaWJ1dGlvbnMgb3ZlciB0aW1lPyoqICANCiAgIC0gV291bGQgd2UgbmVlZCBwZXJpb2RpYyByZXRyYWluaW5nLCBvciBjYW4gaW5jcmVtZW50YWwgbGVhcm5pbmcgaGFuZGxlIGl0IGVmZmljaWVudGx5PyAgDQoNCg0KDQoNCg0KIyMjICoqRXhwbGFpbmluZyBUb25pZ2h0J3MgTGVzc29ucyBpbiBTaW1wbGUgVGVybXMqKg0KSW1hZ2luZSB5b3UncmUgdGVhY2hpbmcgc29tZW9uZSAqKmhvdyB0byBiYWtlIGNvb2tpZXMgYXQgc2NhbGUqKuKAlHRoaXMgaXMgb3VyIGFuYWxvZ3kgZm9yIHByb2Nlc3NpbmcgYmlnIGRhdGEgYW5kIG1hY2hpbmUgbGVhcm5pbmcuDQoNCi0tLQ0KDQojIyAqKjEuIEJpZyBEYXRhIGFuZCBXaHkgSXTigJlzIGEgQ2hhbGxlbmdlKioNCioqUHJvYmxlbToqKiBZb3Ugd2FudCB0byBiYWtlICoqYSBtaWxsaW9uIGNvb2tpZXMqKiwgYnV0IHlvdXIga2l0Y2hlbiAoY29tcHV0ZXIgbWVtb3J5KSBjYW4gb25seSBmaXQgaW5ncmVkaWVudHMgZm9yICoqMTAwIGNvb2tpZXMgYXQgYSB0aW1lKiouDQoNCioqU29sdXRpb246KiogSW5zdGVhZCBvZiB0cnlpbmcgdG8gbWFrZSB0aGVtIGFsbCBhdCBvbmNlLCB5b3UgKipiYXRjaCoqIHRoZW3igJRwcmVwYXJpbmcsIGJha2luZywgYW5kIHNlcnZpbmcgc21hbGwgYmF0Y2hlcyBhdCBhIHRpbWUuIFRoaXMgaXMgZXhhY3RseSBob3cgbWFjaGluZSBsZWFybmluZyBwcm9jZXNzZXMgKipiaWcgZGF0YSoq4oCUd2UgY2Fu4oCZdCBmaXQgaXQgYWxsIGluIG1lbW9yeSwgc28gd2UgbG9hZCBwYXJ0cyBvZiBpdCBhdCBhIHRpbWUuDQoNCi0tLQ0KDQojIyAqKjIuIFN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCAoU0dEKSDigJMgTGVhcm5pbmcgaW4gU21hbGwgQmF0Y2hlcyoqDQoqKlByb2JsZW06KiogTm9ybWFsbHksIHdoZW4gYmFraW5nIGNvb2tpZXMsIHlvdSdkICoqdGFzdGUtdGVzdCB0aGUgd2hvbGUgYmF0Y2ggYmVmb3JlIGFkanVzdGluZyoqLiBCdXQgd2hhdCBpZiB5b3UncmUgbWFraW5nIHRob3VzYW5kcyBvZiBiYXRjaGVzPyBXYWl0aW5nIHVudGlsIHRoZSBlbmQgaXMgaW5lZmZpY2llbnQhDQoNCioqU29sdXRpb246KiogSW5zdGVhZCBvZiB3YWl0aW5nIGZvciAqKmFsbCBjb29raWVzIHRvIGJlIGRvbmUqKiwgeW91IHRha2UgYSBzbWFsbCBiYXRjaCAqKihzdG9jaGFzdGljIGdyYWRpZW50IGRlc2NlbnQpKiosIHRhc3RlIHRoZW0sIGFuZCBhZGp1c3QgdGhlIHJlY2lwZSAqKmluIHJlYWwtdGltZSoqIGZvciB0aGUgbmV4dCBiYXRjaC4gVGhpcyBoZWxwcyAqKm1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGxlYXJuIGZhc3RlciBhbmQgcHJvY2VzcyBodWdlIGRhdGFzZXRzKiogZWZmaWNpZW50bHkuDQoNCi0tLQ0KDQojIyAqKjMuIFRoZSBIYXNoaW5nIFRyaWNrIOKAkyBGYXN0IGFuZCBFZmZpY2llbnQgU3RvcmFnZSoqDQoqKlByb2JsZW06KiogWW91IGhhdmUgMTAsMDAwIGNvb2tpZSByZWNpcGVzLCBidXQgeW91IGRvbuKAmXQgd2FudCB0byBzcGVuZCBob3VycyBzZWFyY2hpbmcgdGhyb3VnaCBhIGdpYW50IGNvb2tib29rIGV2ZXJ5IHRpbWUgeW91IGJha2UuDQoNCioqU29sdXRpb246KiogWW91IHVzZSBhbiAqKmluZGV4IGNhcmQgc3lzdGVtKiouIEVhY2ggcmVjaXBlIGlzIGFzc2lnbmVkIHRvIGEgKipzcGVjaWZpYyBkcmF3ZXIqKiBiYXNlZCBvbiBhIHNob3J0Y3V0IHJ1bGUuIFRoaXMgaXMgaG93IHRoZSAqKmhhc2hpbmcgdHJpY2sqKiB3b3JrcyBpbiBjb21wdXRpbmfigJRvcmdhbml6aW5nIGRhdGEgZWZmaWNpZW50bHkgc28gaXQgY2FuIGJlIHJldHJpZXZlZCBxdWlja2x5Lg0KDQotLS0NCg0KIyMgKio0LiBQYXJ0aWFsIEZpdCDigJMgTGVhcm5pbmcgV2l0aG91dCBPdmVybG9hZGluZyoqDQoqKlByb2JsZW06KiogWW91ciBvdmVuIChjb21wdXRlciBtZW1vcnkpIGNhbiBvbmx5IGJha2UgKiphIHNtYWxsIG51bWJlciBvZiBjb29raWVzIGF0IGEgdGltZSoqLg0KDQoqKlNvbHV0aW9uOioqIEluc3RlYWQgb2YgdHJ5aW5nIHRvICoqbG9hZCBhbGwgY29va2llcyBpbnRvIHRoZSBvdmVuIGF0IG9uY2UqKiwgeW91ICoqYmFrZSBhIGZldyBhdCBhIHRpbWUqKiBhbmQga2VlcCBhZGp1c3RpbmcgYmFzZWQgb24gaG93IHRoZXkgdHVybiBvdXQuICoqUGFydGlhbCBmaXQgaW4gbWFjaGluZSBsZWFybmluZyoqIGFsbG93cyBhIG1vZGVsIHRvIHVwZGF0ZSBpdHNlbGYgd2l0aG91dCBzdG9yaW5nICoqYWxsKiogcGFzdCBkYXRh4oCUcGVyZmVjdCBmb3IgbGFyZ2UgZGF0YXNldHMhDQoNCi0tLQ0KDQojIyAqKjUuIEVwb2NocyAmIEJhdGNoZXMg4oCTIEhvdyBNYW55IFRpbWVzIFlvdSAiU2VlIiB0aGUgRGF0YSoqDQoqKlByb2JsZW06KiogSWYgeW914oCZcmUgbGVhcm5pbmcgdG8gYmFrZSwgcHJhY3RpY2luZyAqKmp1c3Qgb25jZSoqIGlzbuKAmXQgZW5vdWdoLg0KDQoqKlNvbHV0aW9uOioqIElmIHlvdSAqKmJha2UgY29va2llcyAxMCB0aW1lcyAoMTAgZXBvY2hzKSoqLCBlYWNoIHRpbWUgbGVhcm5pbmcgZnJvbSBtaXN0YWtlcywgeW91IGdldCBiZXR0ZXIhIElmIHlvdSBiYWtlICoqaW4gc21hbGwgZ3JvdXBzIG9mIDMyIGNvb2tpZXMgKGJhdGNoIHNpemUgb2YgMzIpKiosIHlvdSBhZGp1c3QgeW91ciB0ZWNobmlxdWUgd2l0aCBlYWNoIGJhdGNoLiBUaGlzIGlzIGV4YWN0bHkgaG93ICoqbWFjaGluZSBsZWFybmluZyBtb2RlbHMgaW1wcm92ZSBvdmVyIHRpbWUqKi4NCg0KLS0tDQoNCiMjICoqNi4gVm93cGFsIFdhYmJpdCAoVlcpIOKAkyBUaGUgU3VwZXItRmFzdCBDaGVmKioNCioqUHJvYmxlbToqKiBZb3UgbmVlZCB0byBiYWtlICoqY29va2llcyBmb3IgYW4gZW50aXJlIGNpdHkqKiwgYW5kIGEgbm9ybWFsIG92ZW4gd29u4oCZdCBjdXQgaXQuDQoNCioqU29sdXRpb246KiogWW91IHVzZSBhbiAqKmluZHVzdHJpYWwgY29udmV5b3IgYmVsdCBvdmVuIChWVykqKuKAlGluc3RlYWQgb2YgYmFraW5nIG9uZSBiYXRjaCBhdCBhIHRpbWUsIHlvdSBjb250aW51b3VzbHkgbG9hZCBpbmdyZWRpZW50cywgYW5kIGl0ICoqYmFrZXMgb24gdGhlIGZseSoqLiBWVyBpcyBhICoqc3VwZXItZWZmaWNpZW50IG1hY2hpbmUgbGVhcm5pbmcgdG9vbCoqIHRoYXQgcHJvY2Vzc2VzIGRhdGEgKip3aGlsZSBpdOKAmXMgYmVpbmcgbG9hZGVkKiosIHJhdGhlciB0aGFuIHdhaXRpbmcgZm9yIGV2ZXJ5dGhpbmcgdG8gYmUgcmVhZHkgZmlyc3QuDQoNCi0tLQ0KDQojIyAqKjcuIERpc2N1c3Npb24gUXVlc3Rpb25zIChEci4gU2xhdGVy4oCZcyBDbGFzcykqKg0KSGVyZSBhcmUgYSBmZXcgYmlnLXBpY3R1cmUgcXVlc3Rpb25zIHRvIGNvbnNpZGVyOg0KMS4gKipEb2VzIGxvYWRpbmcgZGF0YSBpbiBzbWFsbCBiYXRjaGVzIGltcHJvdmUgc3BlZWQgYW5kIGVmZmljaWVuY3kgaW4gcmVhbC13b3JsZCBhcHBsaWNhdGlvbnM/KioNCjIuICoqSG93IGRvIHdlIGRlY2lkZSB0aGUgYmVzdCBiYXRjaCBzaXplIGFuZCBudW1iZXIgb2YgdHJhaW5pbmcgcm91bmRzIChlcG9jaHMpPyoqDQozLiAqKldoYXQgaGFwcGVucyBpZiB3ZSB0cmFpbiBhIG1vZGVsIHdpdGhvdXQgc2VlaW5nIGFsbCBwb3NzaWJsZSBzY2VuYXJpb3MgaW4gdGhlIGRhdGE/KioNCjQuICoqV291bGQgYSB0b29sIGxpa2UgVlcgYmUgdXNlZnVsIGZvciByZWFsLXRpbWUgZGF0YSwgc3VjaCBhcyBmaW5hbmNpYWwgdHJhZGluZyBvciBmcmF1ZCBkZXRlY3Rpb24/KioNCjUuICoqSG93IGRvZXMgb3VyIHVuZGVyc3RhbmRpbmcgb2YgaGFzaGluZyBhZmZlY3QgdGhlIHdheSB3ZSBzdG9yZSBhbmQgcmV0cmlldmUgbGFyZ2UgZGF0YXNldHM/KioNCg0KLS0tDQoNCiMjIyAqKkZpbmFsIFRha2Vhd2F5KioNCkF0IHRoZSBlbmQgb2YgdGhlIGRheSwgbWFjaGluZSBsZWFybmluZyBpcyBqdXN0IGxpa2UgYmFraW5nIGNvb2tpZXMgKiphdCBzY2FsZSoqOg0KLSBZb3UgKipjYW7igJl0IGRvIGV2ZXJ5dGhpbmcgYXQgb25jZSoqLCBzbyB5b3UgKipwcm9jZXNzIHNtYWxsIHBpZWNlcyBhdCBhIHRpbWUqKi4NCi0gWW91ICoqYWRqdXN0IHlvdXIgcmVjaXBlIGJhc2VkIG9uIHdoYXQgeW91J3ZlIGxlYXJuZWQqKiBpbiBlYWNoIHN0ZXAuDQotIFlvdSAqKnVzZSBzaG9ydGN1dHMgdG8gb3JnYW5pemUgcmVjaXBlcyBlZmZpY2llbnRseSoqLCBzbyB5b3UgZG9u4oCZdCBnZXQgb3ZlcndoZWxtZWQuDQotIEFuZCBpZiB5b3UncmUgaGFuZGxpbmcgKipodWdlIGFtb3VudHMgb2YgY29va2llcyoqLCB5b3Ugc3dpdGNoIHRvIGFuIGluZHVzdHJpYWwgY29udmV5b3IgYmVsdC4NCg0KIyMjICoqQnJlYWtpbmcgRG93biBUb25pZ2h04oCZcyBDb25jZXB0cyB3aXRoIE1hdGggJiBJbnRlcnByZXRhdGlvbioqDQoNCkVhY2ggb2YgdGhlc2UgY29uY2VwdHMgcGxheXMgYSBjcnVjaWFsIHJvbGUgaW4gbWFraW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGVmZmljaWVudCwgc2NhbGFibGUsIGFuZCBwcmFjdGljYWwgZm9yIGxhcmdlIGRhdGFzZXRzLiBMZXTigJlzIGdvIHRocm91Z2ggdGhlbSBzdGVwIGJ5IHN0ZXAuDQoNCi0tLQ0KDQojIyAqKjEuIEJpZyBEYXRhICYgU2NhbGluZyBDaGFsbGVuZ2VzKioNCiMjIyAqKldoeSBEbyBXZSBVc2UgSXQ/KioNCi0gSW4gbWFjaGluZSBsZWFybmluZywgZGF0YXNldHMgY2FuIGJlIGVub3Jtb3VzIChtaWxsaW9ucyBvciBiaWxsaW9ucyBvZiByb3dzKS4gSWYgdGhlIGRhdGEgaXMgdG9vIGJpZyB0byBmaXQgaW50byBtZW1vcnksIHdlIG5lZWQgc3BlY2lhbCBtZXRob2RzIHRvIHByb2Nlc3MgaXQgZWZmaWNpZW50bHkuDQoNCiMjIyAqKk1hdGggQmVoaW5kIEl0KioNCi0gU3VwcG9zZSB3ZSBoYXZlIGEgZGF0YXNldCB3aXRoICoqTiBzYW1wbGVzKiogYW5kICoqRCBmZWF0dXJlcyoqLCByZXByZXNlbnRlZCBhcyBhIG1hdHJpeCBcKCBYIFwpIG9mIHNpemUgXCggTiBcdGltZXMgRCBcKS4NCi0gVGhlIGNoYWxsZW5nZSBpcyB0aGF0IHN0b3JpbmcgYW5kIG1hbmlwdWxhdGluZyBhIGxhcmdlIFwoIFggXCkgbWF0cml4IHJlcXVpcmVzIG1lbW9yeSAqKnByb3BvcnRpb25hbCB0byBcKCBOIFx0aW1lcyBEIFwpKiosIHdoaWNoIGdyb3dzIHJhcGlkbHkuDQoNCiMjIyAqKkhvdyBEbyBXZSBJbnRlcnByZXQgdGhlIFJlc3VsdHM/KioNCi0gV2hlbiBkYXRhIGV4Y2VlZHMgYXZhaWxhYmxlIG1lbW9yeSwgKipiYXRjaCBwcm9jZXNzaW5nKiogb3IgKipvbmxpbmUgbGVhcm5pbmcqKiAobG9hZGluZyBhIGxpdHRsZSBhdCBhIHRpbWUpIGlzIHVzZWQuDQotIEluc3RlYWQgb2YgdHJ5aW5nIHRvIGZpdCBldmVyeXRoaW5nIGluIG1lbW9yeSwgd2UgKipsb2FkIHNtYWxsZXIgcGFydHMgb2YgdGhlIGRhdGEgYW5kIHByb2Nlc3MgaW5jcmVtZW50YWxseSoqLg0KDQotLS0NCg0KIyMgKioyLiBTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkqKg0KIyMjICoqV2h5IERvIFdlIFVzZSBJdD8qKg0KLSBTdGFuZGFyZCAqKkdyYWRpZW50IERlc2NlbnQgKEdEKSoqIGNvbXB1dGVzIHRoZSBncmFkaWVudCBmb3IgKiphbGwgZGF0YSBwb2ludHMqKiBiZWZvcmUgdXBkYXRpbmcgdGhlIG1vZGVsLiBUaGlzIGlzICoqc2xvdyoqIGZvciBsYXJnZSBkYXRhc2V0cy4NCi0gKipTR0QqKiB1cGRhdGVzIHRoZSBtb2RlbCAqKmFmdGVyIGVhY2ggc21hbGwgYmF0Y2gqKiwgbWFraW5nIGl0IG11Y2ggKipmYXN0ZXIqKiBhbmQgYWxsb3dpbmcgaXQgdG8gaGFuZGxlIGxhcmdlIGRhdGFzZXRzLg0KDQojIyMgKipNYXRoIEJlaGluZCBJdCoqDQotIFRoZSAqKmdyYWRpZW50IGRlc2NlbnQgdXBkYXRlIHJ1bGUqKjoNCiAgXFsNCiAgXHRoZXRhXnsodCsxKX0gPSBcdGhldGFeeyh0KX0gLSBcYWxwaGEgXG5hYmxhIEooXHRoZXRhKQ0KICBcXQ0KICB3aGVyZToNCiAgLSBcKCBcdGhldGEgXCkgPSBtb2RlbCBwYXJhbWV0ZXJzDQogIC0gXCggXGFscGhhIFwpID0gbGVhcm5pbmcgcmF0ZQ0KICAtIFwoIEooXHRoZXRhKSBcKSA9IGxvc3MgZnVuY3Rpb24NCg0KLSAqKlNHRCBtb2RpZmljYXRpb24qKjoNCiAgSW5zdGVhZCBvZiBjb21wdXRpbmcgXCggXG5hYmxhIEooXHRoZXRhKSBcKSBvdmVyIGFsbCBkYXRhLCB3ZSBhcHByb3hpbWF0ZSBpdCB1c2luZyBhICoqcmFuZG9tIHNtYWxsIGJhdGNoKio6DQogIFxbDQogIFx0aGV0YV57KHQrMSl9ID0gXHRoZXRhXnsodCl9IC0gXGFscGhhIFxuYWJsYSBKKFx0aGV0YTsgWF97XHRleHR7YmF0Y2h9fSkNCiAgXF0NCiAgd2hlcmUgXCggWF97XHRleHR7YmF0Y2h9fSBcKSBpcyBhIHNtYWxsIHN1YnNldCBvZiBkYXRhLg0KDQojIyMgKipIb3cgRG8gV2UgSW50ZXJwcmV0IHRoZSBSZXN1bHRzPyoqDQotICoqRmFzdGVyIGNvbnZlcmdlbmNlKio6IFRoZSBtb2RlbCBsZWFybnMgYW5kIHVwZGF0ZXMgbW9yZSBmcmVxdWVudGx5Lg0KLSAqKk1vcmUgbm9pc2UqKjogQmVjYXVzZSB3ZSB1c2UgYSBzbWFsbCBiYXRjaCwgdGhlIGdyYWRpZW50IGVzdGltYXRlcyBhcmUgbm9pc3ksIGJ1dCBvbiBhdmVyYWdlLCB0aGV5IG1vdmUgaW4gdGhlIHJpZ2h0IGRpcmVjdGlvbi4NCi0gKipCZXR0ZXIgZm9yIG9ubGluZSBsZWFybmluZyoqOiBJdCBjYW4gcHJvY2VzcyBuZXcgZGF0YSBjb250aW51b3VzbHkgd2l0aG91dCByZXRyYWluaW5nIGZyb20gc2NyYXRjaC4NCg0KLS0tDQoNCiMjICoqMy4gVGhlIEhhc2hpbmcgVHJpY2sqKg0KIyMjICoqV2h5IERvIFdlIFVzZSBJdD8qKg0KLSBXaGVuIGRlYWxpbmcgd2l0aCAqKmhpZ2gtZGltZW5zaW9uYWwgZGF0YSoqLCBsaWtlIHRleHQgb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCBzdG9yaW5nIGFsbCBwb3NzaWJsZSBmZWF0dXJlcyBleHBsaWNpdGx5IGlzIGluZWZmaWNpZW50Lg0KLSBUaGUgaGFzaGluZyB0cmljayAqKm1hcHMgZmVhdHVyZXMgaW50byBhIHNtYWxsZXIgZml4ZWQtc2l6ZSBzcGFjZSoqLCBhdm9pZGluZyB0aGUgbmVlZCB0byBzdG9yZSBtYXNzaXZlIGxvb2t1cCB0YWJsZXMuDQoNCiMjIyAqKk1hdGggQmVoaW5kIEl0KioNCi0gQSAqKmhhc2ggZnVuY3Rpb24qKiBtYXBzIGFuIGlucHV0IFwoIHggXCkgdG8gYW4gaW5kZXg6DQogIFxbDQogIGgoeCkgPSBcdGV4dHtpbmRleCBpbiBtZW1vcnl9DQogIFxdDQogIHdoZXJlIFwoIGgoeCkgXCkgaXMgY29tcHV0ZWQgdXNpbmcgYSBmYXN0IGZ1bmN0aW9uIGxpa2U6DQogIFxbDQogIGgoeCkgPSBcdGV4dHtoYXNofSh4KSBcbW9kIE4NCiAgXF0NCiAgKG1vZHVsbyBcKCBOIFwpIGtlZXBzIGl0IHdpdGhpbiBhIGZpeGVkIG1lbW9yeSBzcGFjZSkuDQoNCiMjIyAqKkhvdyBEbyBXZSBJbnRlcnByZXQgdGhlIFJlc3VsdHM/KioNCi0gKipGYXN0ZXIgbG9va3VwcyoqOiBObyBuZWVkIGZvciBsYXJnZSBtZW1vcnktaHVuZ3J5IGxvb2t1cCB0YWJsZXMuDQotICoqUmlzayBvZiBjb2xsaXNpb25zKio6IElmIHR3byBmZWF0dXJlcyBoYXNoIHRvIHRoZSBzYW1lIGluZGV4LCB0aGV5IHNoYXJlIG1lbW9yeSwgd2hpY2ggbWF5IGludHJvZHVjZSBzbWFsbCBlcnJvcnMuDQotICoqVHJhZGUtb2ZmKio6IEEgbGFyZ2VyIGhhc2ggc3BhY2UgKGhpZ2hlciBcKCBOIFwpKSByZWR1Y2VzIGNvbGxpc2lvbnMuDQoNCi0tLQ0KDQojIyAqKjQuIFBhcnRpYWwgRml0IGluIFNHRCoqDQojIyMgKipXaHkgRG8gV2UgVXNlIEl0PyoqDQotIEluc3RlYWQgb2YgdHJhaW5pbmcgYSBtb2RlbCBvbiAqKmFsbCBkYXRhIGF0IG9uY2UqKiwgKipwYXJ0aWFsX2ZpdCgpKiogYWxsb3dzIHVzIHRvIHRyYWluIHRoZSBtb2RlbCAqKmluY3JlbWVudGFsbHkqKi4NCi0gVGhpcyBpcyB1c2VmdWwgZm9yICoqc3RyZWFtaW5nIGRhdGEqKiBvciAqKmRhdGFzZXRzIHRvbyBsYXJnZSBmb3IgbWVtb3J5KiouDQoNCiMjIyAqKk1hdGggQmVoaW5kIEl0KioNCi0gKipSZWd1bGFyIGBmaXQoKWAgZnVuY3Rpb24qKjoNCiAgLSBQcm9jZXNzZXMgKiphbGwgZGF0YSBhdCBvbmNlKiogYW5kIHVwZGF0ZXMgbW9kZWwgcGFyYW1ldGVycy4NCg0KLSAqKlBhcnRpYWwgZml0Kio6DQogIC0gUHJvY2Vzc2VzICoqb25seSBvbmUgYmF0Y2ggYXQgYSB0aW1lKiogYW5kIHVwZGF0ZXMgcGFyYW1ldGVycyBpbmNyZW1lbnRhbGx5Og0KICAgIFxbDQogICAgXHRoZXRhXnsodCsxKX0gPSBcdGhldGFeeyh0KX0gLSBcYWxwaGEgXG5hYmxhIEooXHRoZXRhOyBYX3tcdGV4dHtiYXRjaH19KQ0KICAgIFxdDQoNCiMjIyAqKkhvdyBEbyBXZSBJbnRlcnByZXQgdGhlIFJlc3VsdHM/KioNCi0gKipJbXByb3ZlcyBlZmZpY2llbmN5Kio6IENhbiB0cmFpbiBtb2RlbHMgKip3aXRob3V0IHN0b3JpbmcgYWxsIGRhdGEgaW4gbWVtb3J5KiouDQotICoqUmVxdWlyZXMgdHVuaW5nKio6IFRoZSBsZWFybmluZyByYXRlIGFuZCBiYXRjaCBzaXplIGltcGFjdCBwZXJmb3JtYW5jZS4NCi0gKipPbmxpbmUgbGVhcm5pbmcqKjogQ2FuICoqY29udGludW91c2x5IGltcHJvdmUqKiBhcyBuZXcgZGF0YSBhcnJpdmVzLg0KDQotLS0NCg0KIyMgKio1LiBFcG9jaHMgJiBCYXRjaGVzKioNCiMjIyAqKldoeSBEbyBXZSBVc2UgSXQ/KioNCi0gKipFcG9jaHMqKjogVGhlIG51bWJlciBvZiB0aW1lcyB0aGUgbW9kZWwgc2VlcyB0aGUgKiplbnRpcmUgZGF0YXNldCoqLg0KLSAqKkJhdGNoZXMqKjogVGhlIGRhdGFzZXQgaXMgYnJva2VuIGludG8gKipzbWFsbGVyIHBhcnRzKiogdG8gZml0IGluIG1lbW9yeS4NCg0KIyMjICoqTWF0aCBCZWhpbmQgSXQqKg0KLSBJZiB3ZSBoYXZlOg0KICAtICoqRGF0YXNldCBzaXplKiogPSBcKCBOIFwpDQogIC0gKipCYXRjaCBzaXplKiogPSBcKCBCIFwpDQogIC0gKipFcG9jaHMqKiA9IFwoIEUgXCkNCg0KICBUaGVuLCAqKm51bWJlciBvZiB1cGRhdGVzKio6DQogIFxbDQogIFx0ZXh0e1RvdGFsIHVwZGF0ZXN9ID0gXGZyYWN7Tn17Qn0gXHRpbWVzIEUNCiAgXF0NCg0KIyMjICoqSG93IERvIFdlIEludGVycHJldCB0aGUgUmVzdWx0cz8qKg0KLSAqKlRvbyBmZXcgZXBvY2hzKiog4oaSIHVuZGVyZml0dGluZyAobW9kZWwgZG9lc24ndCBsZWFybiBlbm91Z2gpLg0KLSAqKlRvbyBtYW55IGVwb2NocyoqIOKGkiBvdmVyZml0dGluZyAobW9kZWwgbWVtb3JpemVzIG5vaXNlKS4NCi0gKipCYXRjaCBzaXplIG1hdHRlcnMqKjoNCiAgLSBTbWFsbCBiYXRjaCDihpIgKipmYXN0ZXIgdXBkYXRlcyoqLCBub2lzaWVyIGxlYXJuaW5nLg0KICAtIExhcmdlIGJhdGNoIOKGkiAqKnNsb3dlciB1cGRhdGVzKiosIG1vcmUgc3RhYmxlIGxlYXJuaW5nLg0KDQotLS0NCg0KIyMgKio2LiBWb3dwYWwgV2FiYml0IChWVykqKg0KIyMjICoqV2h5IERvIFdlIFVzZSBJdD8qKg0KLSBWVyBpcyBhICoqc3VwZXItZmFzdCwgbWVtb3J5LWVmZmljaWVudCBtYWNoaW5lIGxlYXJuaW5nIHRvb2wqKiBkZXNpZ25lZCBmb3IgKipodWdlIGRhdGFzZXRzKiouDQotIEluc3RlYWQgb2YgKipsb2FkaW5nIGRhdGEgaW50byBtZW1vcnkqKiwgVlcgcmVhZHMgYW5kIHByb2Nlc3NlcyAqKm9uZSByb3cgYXQgYSB0aW1lKiouDQoNCiMjIyAqKk1hdGggQmVoaW5kIEl0KioNCi0gVlcgdXNlcyAqKm9ubGluZSBsZWFybmluZyoqIChsaWtlIFNHRCkgYnV0IHdpdGggKiphZGFwdGl2ZSBsZWFybmluZyByYXRlcyoqOg0KICBcWw0KICBcdGhldGFeeyh0KzEpfSA9IFx0aGV0YV57KHQpfSAtIFxhbHBoYV90IFxuYWJsYSBKKFx0aGV0YTsgWF97XHRleHR7YmF0Y2h9fSkNCiAgXF0NCiAgd2hlcmUgXCggXGFscGhhX3QgXCkgKipjaGFuZ2VzIG92ZXIgdGltZSoqIGZvciBiZXR0ZXIgY29udmVyZ2VuY2UuDQoNCiMjIyAqKkhvdyBEbyBXZSBJbnRlcnByZXQgdGhlIFJlc3VsdHM/KioNCi0gKipXb3JrcyB3ZWxsIGZvciBtYXNzaXZlIGRhdGEqKiAobWlsbGlvbnMgb2Ygcm93cykuDQotICoqQ2FuIHRyYWluIG1vZGVscyBjb250aW51b3VzbHkqKi4NCi0gKipHcmVhdCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbiwgcmVjb21tZW5kYXRpb24gc3lzdGVtcywgYW5kIGFkIHRhcmdldGluZyoqLg0KDQotLS0NCg0KIyMgKio3LiBIb3cgV2UgSW50ZXJwcmV0IFJlc3VsdHMqKg0KIyMjICoqS2V5IFRha2Vhd2F5cyoqDQotICoqU0dEIGlzIHBvd2VyZnVsIGZvciBsYXJnZSBkYXRhc2V0cyoqOiBJdCB1cGRhdGVzIG1vZGVscyBlZmZpY2llbnRseSwgYnV0IHJlcXVpcmVzIHR1bmluZy4NCi0gKipUaGUgaGFzaGluZyB0cmljayBzYXZlcyBtZW1vcnkqKjogSXQgYXZvaWRzIHN0b3JpbmcgaHVnZSBmZWF0dXJlIHRhYmxlcy4NCi0gKipQYXJ0aWFsIGZpdCBhbGxvd3MgY29udGludW91cyBsZWFybmluZyoqOiBUaGUgbW9kZWwgaW1wcm92ZXMgYXMgbmV3IGRhdGEgY29tZXMgaW4uDQotICoqVlcgaXMgYSBzcGVjaWFsaXplZCB0b29sIGZvciBiaWcgZGF0YSoqOiBJdCBjYW4gdHJhaW4gbW9kZWxzIHdpdGhvdXQgbG9hZGluZyBldmVyeXRoaW5nIGludG8gbWVtb3J5Lg0KDQotLS0NCg0KIyMgKipEaXNjdXNzaW9uIFF1ZXN0aW9ucyoqDQoxLiAqKkhvdyBkbyB3ZSBjaG9vc2UgdGhlIGJlc3QgYmF0Y2ggc2l6ZSBhbmQgbGVhcm5pbmcgcmF0ZSBmb3IgU0dEPyoqDQoyLiAqKldoYXQgYXJlIHRoZSB0cmFkZS1vZmZzIG9mIHVzaW5nIHRoZSBoYXNoaW5nIHRyaWNrIGluc3RlYWQgb2YgZXhwbGljaXQgZmVhdHVyZSBzdG9yYWdlPyoqDQozLiAqKkhvdyBkb2VzIFZXIGNvbXBhcmUgdG8gdHJhZGl0aW9uYWwgbWFjaGluZSBsZWFybmluZyBtZXRob2RzIGZvciBoYW5kbGluZyBsYXJnZS1zY2FsZSBkYXRhPyoqDQo0LiAqKkhvdyBkbyB3ZSBwcmV2ZW50IG92ZXJmaXR0aW5nIHdoZW4gdXNpbmcgYHBhcnRpYWxfZml0KClgIGFuZCBTR0Q/KioNCg0KLS0tDQoNCiMjIyAqKkZpbmFsIFRob3VnaHRzKioNCkFsbCB0aGVzZSB0ZWNobmlxdWVzICoqc29sdmUgcmVhbC13b3JsZCBwcm9ibGVtcyBpbiBtYWNoaW5lIGxlYXJuaW5nKio6DQotIFNHRCAqKm1ha2VzIHRyYWluaW5nIGZhc3RlcioqLg0KLSBUaGUgaGFzaGluZyB0cmljayAqKm1ha2VzIHN0b3JhZ2UgZWZmaWNpZW50KiouDQotIFBhcnRpYWwgZml0ICoqYWxsb3dzIGNvbnRpbnVvdXMgbGVhcm5pbmcqKi4NCi0gVlcgKippcyBvcHRpbWl6ZWQgZm9yIHNwZWVkKiouDQoNCkJ5IHVuZGVyc3RhbmRpbmcgdGhlc2UgdG9vbHMgYW5kIHdoZW4gdG8gdXNlIHRoZW0sIHdlIGNhbiAqKnRyYWluIHBvd2VyZnVsIG1vZGVscyBvbiBtYXNzaXZlIGRhdGFzZXRzIGVmZmljaWVudGx5KiouIPCfmoANCg0KDQoNCg0KIyAqKlN0dWR5IEd1aWRlOiBSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzIChSTk5zKSAtIERTLTczMzMsIE1vZHVsZSAxMSwgU2VjdGlvbiA2KiogIA0KDQojIyAqKjEuIEludHJvZHVjdGlvbiB0byBSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzIChSTk5zKSoqDQpSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzIChSTk5zKSBhcmUgYSBjbGFzcyBvZiBkZWVwIGxlYXJuaW5nIG1vZGVscyBzcGVjaWZpY2FsbHkgZGVzaWduZWQgZm9yICoqc2VxdWVudGlhbCBkYXRhKiouIFVubGlrZSB0cmFkaXRpb25hbCBuZXVyYWwgbmV0d29ya3MgdGhhdCBhc3N1bWUgaW5wdXRzIGFyZSBpbmRlcGVuZGVudCwgUk5OcyAqKm1haW50YWluIG1lbW9yeSoqIG9mIHByZXZpb3VzIGlucHV0cywgbWFraW5nIHRoZW0gd2VsbC1zdWl0ZWQgZm9yIHRhc2tzIHN1Y2ggYXM6DQotICoqVGltZS1zZXJpZXMgZm9yZWNhc3RpbmcqKiAoc3RvY2sgcHJpY2VzLCB3ZWF0aGVyIHByZWRpY3Rpb24pDQotICoqTmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nIChOTFApKiogKHRleHQgZ2VuZXJhdGlvbiwgbWFjaGluZSB0cmFuc2xhdGlvbikNCi0gKipTcGVlY2ggcmVjb2duaXRpb24qKiAodm9pY2UgYXNzaXN0YW50cywgdHJhbnNjcmlwdGlvbikNCg0KIyMjICoqV2h5IFVzZSBSTk5zPyoqDQrinIUgKipQcmVzZXJ2ZSBzZXF1ZW5jZSBpbmZvcm1hdGlvbioqICANCuKchSAqKkhhbmRsZSB2YXJpYWJsZS1sZW5ndGggaW5wdXQgc2VxdWVuY2VzKiogIA0K4pyFICoqQ2FwdHVyZSBkZXBlbmRlbmNpZXMgaW4gc2VxdWVudGlhbCBkYXRhKiogIA0KDQotLS0NCg0KIyMgKioyLiBIb3cgUk5OcyBEaWZmZXIgZnJvbSBUcmFkaXRpb25hbCBOZXVyYWwgTmV0d29ya3MqKg0KVW5saWtlIGEgKipmZWVkZm9yd2FyZCBuZXVyYWwgbmV0d29yayoqLCB3aGVyZSBkYXRhIGZsb3dzICoqb25lIHdheSoqLCBSTk5zIGludHJvZHVjZSAqKmZlZWRiYWNrIGxvb3BzKiosIGVuYWJsaW5nIHRoZW0gdG8gKipzdG9yZSBtZW1vcnkqKiBvZiBwYXN0IGlucHV0cy4gIA0KDQp8IE5ldHdvcmsgVHlwZSB8IENoYXJhY3RlcmlzdGljIHwNCnwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tfA0KfCAqKkZlZWRmb3J3YXJkIE5OKiogfCBObyBtZW1vcnksIHByb2Nlc3NlcyBpbnB1dHMgaW5kZXBlbmRlbnRseSB8DQp8ICoqUmVjdXJyZW50IE5OKiogfCBNYWludGFpbnMgc3RhdGUvbWVtb3J5IGFjcm9zcyB0aW1lIHN0ZXBzIHwNCg0KIyMjICoqVmlzdWFsIFJlcHJlc2VudGF0aW9uIG9mIGFuIFJOTioqDQojIyMjICoqVW5yb2xsZWQgVmlldyBvZiBSTk4qKg0KRWFjaCBzdGVwIGluIGEgc2VxdWVuY2UgKipmZWVkcyBpbnRvIHRoZSBuZXh0IHN0ZXAqKiwgY2FycnlpbmcgaW5mb3JtYXRpb24gZm9yd2FyZDoNCg0KXFsNCmhfdCA9IGYoV194IHhfdCArIFdfaCBoX3t0LTF9ICsgYikNClxdDQoNCldoZXJlOg0KLSBcKCB4X3QgXCkgPSBJbnB1dCBhdCB0aW1lIFwoIHQgXCkNCi0gXCggaF90IFwpID0gSGlkZGVuIHN0YXRlIGF0IHRpbWUgXCggdCBcKSAobWVtb3J5KQ0KLSBcKCBXX3gsIFdfaCBcKSA9IFdlaWdodCBtYXRyaWNlcw0KLSBcKCBiIFwpID0gQmlhcyB0ZXJtDQotIFwoIGYgXCkgPSBBY3RpdmF0aW9uIGZ1bmN0aW9uIChlLmcuLCB0YW5oKQ0KDQotLS0NCg0KIyMgKiozLiBNYXRoZW1hdGljYWwgRm9ybXVsYXRpb24gb2YgUk5OcyoqDQpFYWNoIFJOTiBzdGVwIHVwZGF0ZXMgaXRzICoqaGlkZGVuIHN0YXRlKiogYmFzZWQgb24gdGhlICoqY3VycmVudCBpbnB1dCBhbmQgcHJldmlvdXMgaGlkZGVuIHN0YXRlKiouDQoNCiMjIyAqKkhpZGRlbiBTdGF0ZSBDYWxjdWxhdGlvbioqDQpcWw0KaF90ID0gXHRhbmgoV194IHhfdCArIFdfaCBoX3t0LTF9ICsgYikNClxdDQoNCiMjIyAqKk91dHB1dCBDYWxjdWxhdGlvbioqDQpcWw0KeV90ID0gV195IGhfdCArIGJfeQ0KXF0NCg0KV2hlcmU6DQotIFwoIHlfdCBcKSA9IE91dHB1dCBhdCB0aW1lIFwoIHQgXCkNCi0gXCggV195IFwpID0gV2VpZ2h0IG1hdHJpeCBmb3Igb3V0cHV0DQotIFwoIGJfeSBcKSA9IEJpYXMgZm9yIG91dHB1dCBsYXllcg0KDQojIyMgKipFeGFtcGxlOiBQcmVkaWN0aW5nIE5leHQgV29yZCBpbiBhIFNlbnRlbmNlKioNCjEuIElucHV0OiAiVGhlIGNhdCBzYXQgb24gdGhlLi4uIg0KMi4gUk5OIHRha2VzIHRoZSAqKnByZXZpb3VzIHdvcmQqKiBhcyBpbnB1dCBhbmQgcHJlZGljdHMgKip0aGUgbmV4dCB3b3JkKiouDQozLiBUaGUgKipoaWRkZW4gc3RhdGUgcmV0YWlucyBjb250ZXh0KiosIG1ha2luZyB0aGUgcHJlZGljdGlvbiAqKmNvbnRleHQtYXdhcmUqKi4NCg0KLS0tDQoNCiMjICoqNC4gVGhlIFZhbmlzaGluZyBHcmFkaWVudCBQcm9ibGVtIGluIFJOTnMqKg0KT25lIGNoYWxsZW5nZSB3aXRoIFJOTnMgaXMgdGhlICoqdmFuaXNoaW5nIGdyYWRpZW50IHByb2JsZW0qKiwgd2hlcmUgZ3JhZGllbnRzIHNocmluayBkdXJpbmcgYmFja3Byb3BhZ2F0aW9uLCBtYWtpbmcgaXQgZGlmZmljdWx0IHRvIGxlYXJuICoqbG9uZy10ZXJtIGRlcGVuZGVuY2llcyoqLg0KDQojIyMgKipTb2x1dGlvbnM6KioNCuKchSAqKkxvbmcgU2hvcnQtVGVybSBNZW1vcnkgKExTVE0pKiog4oaSIEludHJvZHVjZXMgbWVtb3J5IGdhdGVzICANCuKchSAqKkdhdGVkIFJlY3VycmVudCBVbml0IChHUlUpKiog4oaSIFNpbXBsaWZpZXMgTFNUTSB3aXRoIGZld2VyIHBhcmFtZXRlcnMgIA0KDQotLS0NCg0KIyMgKio1LiBMb25nIFNob3J0LVRlcm0gTWVtb3J5IChMU1RNKSBOZXR3b3JrcyoqDQpMU1RNcyBzb2x2ZSB0aGUgKip2YW5pc2hpbmcgZ3JhZGllbnQgcHJvYmxlbSoqIGJ5IGludHJvZHVjaW5nICoqZ2F0ZXMqKiB0aGF0IGNvbnRyb2wgaW5mb3JtYXRpb24gZmxvdy4NCg0KIyMjICoqS2V5IENvbXBvbmVudHMgb2YgTFNUTXMqKg0KfCBHYXRlIHwgRnVuY3Rpb24gfA0KfC0tLS0tLXwtLS0tLS0tLS0tfA0KfCAqKkZvcmdldCBHYXRlKiogXCggZl90IFwpIHwgRGVjaWRlcyB3aGF0IGluZm9ybWF0aW9uIHRvIGRpc2NhcmQgfA0KfCAqKklucHV0IEdhdGUqKiBcKCBpX3QgXCkgfCBEZWNpZGVzIHdoYXQgbmV3IGluZm9ybWF0aW9uIHRvIHN0b3JlIHwNCnwgKipDZWxsIFN0YXRlKiogXCggQ190IFwpIHwgU3RvcmVzIGxvbmctdGVybSBtZW1vcnkgfA0KfCAqKk91dHB1dCBHYXRlKiogXCggb190IFwpIHwgRGV0ZXJtaW5lcyBmaW5hbCBoaWRkZW4gc3RhdGUgfA0KDQojIyMgKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24qKg0KXFsNCmZfdCA9IFxzaWdtYShXX2YgW2hfe3QtMX0sIHhfdF0gKyBiX2YpDQpcXQ0KXFsNCmlfdCA9IFxzaWdtYShXX2kgW2hfe3QtMX0sIHhfdF0gKyBiX2kpDQpcXQ0KXFsNCkNfdCA9IGZfdCBcb2RvdCBDX3t0LTF9ICsgaV90IFx0YW5oKFdfQyBbaF97dC0xfSwgeF90XSArIGJfQykNClxdDQpcWw0Kb190ID0gXHNpZ21hKFdfbyBbaF97dC0xfSwgeF90XSArIGJfbykNClxdDQpcWw0KaF90ID0gb190IFx0YW5oKENfdCkNClxdDQoNCldoZXJlOg0KLSBcKCBcc2lnbWEgXCkgPSBTaWdtb2lkIGFjdGl2YXRpb24gZnVuY3Rpb24NCi0gXCggXHRhbmggXCkgPSBIeXBlcmJvbGljIHRhbmdlbnQgYWN0aXZhdGlvbg0KLSBcKCBcb2RvdCBcKSA9IEVsZW1lbnQtd2lzZSBtdWx0aXBsaWNhdGlvbg0KDQotLS0NCg0KIyMgKio2LiBQeXRob24gQ29kZTogSW1wbGVtZW50aW5nIFJOTnMgJiBMU1RNcyBpbiBQeVRvcmNoKioNCiMjIyAqKlNpbXBsZSBSTk4gaW4gUHlUb3JjaCoqDQpgYGBweXRob24NCmltcG9ydCB0b3JjaA0KaW1wb3J0IHRvcmNoLm5uIGFzIG5uDQoNCiMgRGVmaW5lIFNpbXBsZSBSTk4gTW9kZWwNCmNsYXNzIFNpbXBsZVJOTihubi5Nb2R1bGUpOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmLCBpbnB1dF9zaXplLCBoaWRkZW5fc2l6ZSwgb3V0cHV0X3NpemUpOg0KICAgICAgICBzdXBlcihTaW1wbGVSTk4sIHNlbGYpLl9faW5pdF9fKCkNCiAgICAgICAgc2VsZi5ybm4gPSBubi5STk4oaW5wdXRfc2l6ZSwgaGlkZGVuX3NpemUsIGJhdGNoX2ZpcnN0PVRydWUpDQogICAgICAgIHNlbGYuZmMgPSBubi5MaW5lYXIoaGlkZGVuX3NpemUsIG91dHB1dF9zaXplKQ0KICAgIA0KICAgIGRlZiBmb3J3YXJkKHNlbGYsIHgpOg0KICAgICAgICBvdXQsIF8gPSBzZWxmLnJubih4KQ0KICAgICAgICBvdXQgPSBzZWxmLmZjKG91dFs6LCAtMSwgOl0pICAjIFRha2UgbGFzdCBvdXRwdXQNCiAgICAgICAgcmV0dXJuIG91dA0KDQojIEluaXRpYWxpemUgbW9kZWwNCnJubl9tb2RlbCA9IFNpbXBsZVJOTihpbnB1dF9zaXplPTEwLCBoaWRkZW5fc2l6ZT0yMCwgb3V0cHV0X3NpemU9NSkNCnByaW50KHJubl9tb2RlbCkNCmBgYA0KDQojIyMgKipMU1RNIGluIFB5VG9yY2gqKg0KYGBgcHl0aG9uDQpjbGFzcyBTaW1wbGVMU1RNKG5uLk1vZHVsZSk6DQogICAgZGVmIF9faW5pdF9fKHNlbGYsIGlucHV0X3NpemUsIGhpZGRlbl9zaXplLCBvdXRwdXRfc2l6ZSk6DQogICAgICAgIHN1cGVyKFNpbXBsZUxTVE0sIHNlbGYpLl9faW5pdF9fKCkNCiAgICAgICAgc2VsZi5sc3RtID0gbm4uTFNUTShpbnB1dF9zaXplLCBoaWRkZW5fc2l6ZSwgYmF0Y2hfZmlyc3Q9VHJ1ZSkNCiAgICAgICAgc2VsZi5mYyA9IG5uLkxpbmVhcihoaWRkZW5fc2l6ZSwgb3V0cHV0X3NpemUpDQoNCiAgICBkZWYgZm9yd2FyZChzZWxmLCB4KToNCiAgICAgICAgb3V0LCBfID0gc2VsZi5sc3RtKHgpDQogICAgICAgIG91dCA9IHNlbGYuZmMob3V0WzosIC0xLCA6XSkNCiAgICAgICAgcmV0dXJuIG91dA0KDQojIEluaXRpYWxpemUgbW9kZWwNCmxzdG1fbW9kZWwgPSBTaW1wbGVMU1RNKGlucHV0X3NpemU9MTAsIGhpZGRlbl9zaXplPTIwLCBvdXRwdXRfc2l6ZT01KQ0KcHJpbnQobHN0bV9tb2RlbCkNCmBgYA0KDQotLS0NCg0KIyMgKio3LiBBcHBsaWNhdGlvbnMgb2YgUk5OcyAmIExTVE1zKioNCnwgQXBwbGljYXRpb24gfCBFeGFtcGxlIHwNCnwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tLXwNCnwgKipTcGVlY2ggUmVjb2duaXRpb24qKiB8IENvbnZlcnQgYXVkaW8gdG8gdGV4dCAoU2lyaSwgQWxleGEpIHwNCnwgKipNYWNoaW5lIFRyYW5zbGF0aW9uKiogfCBUcmFuc2xhdGUgRW5nbGlzaCB0byBGcmVuY2ggKEdvb2dsZSBUcmFuc2xhdGUpIHwNCnwgKipTdG9jayBQcmVkaWN0aW9uKiogfCBGb3JlY2FzdCBzdG9jayB0cmVuZHMgfA0KfCAqKlRleHQgR2VuZXJhdGlvbioqIHwgR2VuZXJhdGUgY2FwdGlvbnMsIGNoYXRib3QgcmVzcG9uc2VzIHwNCg0KLS0tDQoNCiMjICoqOC4gS2V5IFRha2Vhd2F5cyoqDQrinIUgKipSTk5zIHByb2Nlc3Mgc2VxdWVudGlhbCBkYXRhKiogYnkgbWFpbnRhaW5pbmcgaGlkZGVuIHN0YXRlcy4gIA0K4pyFICoqTFNUTXMgc29sdmUgdGhlIHZhbmlzaGluZyBncmFkaWVudCBwcm9ibGVtKiogdXNpbmcgbWVtb3J5IGdhdGVzLiAgDQrinIUgKipHUlVzKiogYXJlIHNpbXBsaWZpZWQgdmVyc2lvbnMgb2YgTFNUTXMgd2l0aCBmZXdlciBwYXJhbWV0ZXJzLiAgDQrinIUgKipSTk5zICYgTFNUTXMqKiBhcmUgd2lkZWx5IHVzZWQgaW4gTkxQLCBzcGVlY2ggcmVjb2duaXRpb24sIGFuZCB0aW1lLXNlcmllcyBmb3JlY2FzdGluZy4gIA0KDQotLS0NCg0KIyMgKio5LiBEaXNjdXNzaW9uIFF1ZXN0aW9ucyoqDQoxLiBXaGF0IGFyZSB0aGUga2V5IGRpZmZlcmVuY2VzIGJldHdlZW4gUk5OcywgTFNUTXMsIGFuZCBHUlVzPw0KMi4gV2h5IGRvIHN0YW5kYXJkIFJOTnMgc3RydWdnbGUgd2l0aCBsb25nLXRlcm0gZGVwZW5kZW5jaWVzPw0KMy4gSG93IGRvIGZvcmdldCwgaW5wdXQsIGFuZCBvdXRwdXQgZ2F0ZXMgaGVscCBMU1RNcyByZXRhaW4gaW5mb3JtYXRpb24/DQo0LiBXaGF0IGFyZSBzb21lIHJlYWwtd29ybGQgYXBwbGljYXRpb25zIG9mIFJOTnMgYmV5b25kIE5MUD8NCg0KLS0tDQoNCg0KDQojICoqU3R1ZHkgR3VpZGU6IFRyYW5zZm9ybWVyIE5ldHdvcmtzICYgQXR0ZW50aW9uIE1lY2hhbmlzbXMgLSBEUy03MzMzLCBNb2R1bGUgMTEsIFNlY3Rpb24gNyoqICANCg0KIyMgKioxLiBJbnRyb2R1Y3Rpb24gdG8gVHJhbnNmb3JtZXIgTmV0d29ya3MqKiAgDQpUcmFuc2Zvcm1lciBuZXR3b3JrcyBhcmUgYSBicmVha3Rocm91Z2ggaW4gZGVlcCBsZWFybmluZywgcHJpbWFyaWx5IHVzZWQgZm9yICoqbmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nIChOTFApKiogYnV0IGFsc28gZXh0ZW5kaW5nIHRvICoqY29tcHV0ZXIgdmlzaW9uLCByZWluZm9yY2VtZW50IGxlYXJuaW5nLCBhbmQgdGltZS1zZXJpZXMgZm9yZWNhc3RpbmcqKi4gVW5saWtlIFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpLCB0cmFuc2Zvcm1lcnMgKipkbyBub3QgcHJvY2VzcyBkYXRhIHNlcXVlbnRpYWxseSoqLCBtYWtpbmcgdGhlbSAqKmZhc3RlciBhbmQgbW9yZSBwYXJhbGxlbGl6YWJsZSoqLg0KDQojIyMgKipLZXkgSW5ub3ZhdGlvbnMgb2YgVHJhbnNmb3JtZXJzKioNCuKchSAqKlNlbGYtQXR0ZW50aW9uIE1lY2hhbmlzbSoqIOKAkyBDYXB0dXJlcyBkZXBlbmRlbmNpZXMgYWNyb3NzIGFsbCB3b3JkcyBpbiBhIHNlbnRlbmNlLCBub3QganVzdCBuZWFyYnkgd29yZHMuICANCuKchSAqKlBvc2l0aW9uYWwgRW5jb2RpbmcqKiDigJMgQWxsb3dzIHRoZSBtb2RlbCB0byB1bmRlcnN0YW5kIHdvcmQgb3JkZXIgd2l0aG91dCB1c2luZyByZWN1cnJlbmNlLiAgDQrinIUgKipQYXJhbGxlbGl6YXRpb24qKiDigJMgVW5saWtlIFJOTnMsIHdoaWNoIHByb2Nlc3Mgb25lIHRva2VuIGF0IGEgdGltZSwgdHJhbnNmb3JtZXJzIHByb2Nlc3MgKiplbnRpcmUgc2VxdWVuY2VzIHNpbXVsdGFuZW91c2x5KiouICANCg0KLS0tDQoNCiMjICoqMi4gV2h5IERvIFdlIE5lZWQgVHJhbnNmb3JtZXJzPyoqICANClJOTnMgYW5kIExTVE1zIHdlcmUgZWZmZWN0aXZlIGZvciBOTFAsIGJ1dCB0aGV5IGhhdmUgKipsaW1pdGF0aW9ucyoqOiAgDQotICoqU2VxdWVudGlhbCBwcm9jZXNzaW5nKiog4oaSIFNsb3dlciB0cmFpbmluZyBkdWUgdG8gZGVwZW5kZW5jaWVzIG9uIHByZXZpb3VzIHN0YXRlcy4gIA0KLSAqKlZhbmlzaGluZyBncmFkaWVudCBwcm9ibGVtKiog4oaSIFN0cnVnZ2xlcyB3aXRoIGxvbmctcmFuZ2UgZGVwZW5kZW5jaWVzLiAgDQotICoqTGltaXRlZCBwYXJhbGxlbGl6YXRpb24qKiDihpIgSW5lZmZpY2llbnQgdXNlIG9mIG1vZGVybiBHUFVzLiAgDQoNCiMjIyAqKlRyYW5zZm9ybWVycyBTb2x2ZSBUaGVzZSBJc3N1ZXMqKg0K4pyFICoqRWxpbWluYXRlIHJlY3VycmVuY2UqKiDihpIgRmFzdGVyIHRyYWluaW5nLiAgDQrinIUgKipVc2UgYXR0ZW50aW9uIG1lY2hhbmlzbXMqKiDihpIgQ2FwdHVyZSAqKmxvbmctcmFuZ2UgZGVwZW5kZW5jaWVzKiogYmV0dGVyIHRoYW4gUk5Ocy4gIA0K4pyFICoqRnVsbHkgcGFyYWxsZWxpemFibGUqKiDihpIgUHJvY2VzcyBlbnRpcmUgc2VudGVuY2VzIGF0IG9uY2UuICANCg0KLS0tDQoNCiMjICoqMy4gQXJjaGl0ZWN0dXJlIG9mIGEgVHJhbnNmb3JtZXIqKiAgDQpUcmFuc2Zvcm1lcnMgYXJlIGJ1aWx0IG9uICoqZW5jb2Rlci1kZWNvZGVyKiogYXJjaGl0ZWN0dXJlOg0KDQojIyMgKipFbmNvZGVyKioNCi0gKipQcm9jZXNzZXMgaW5wdXQqKiBpbnRvIG51bWVyaWNhbCByZXByZXNlbnRhdGlvbnMuDQotIFVzZXMgKipzZWxmLWF0dGVudGlvbioqIGFuZCAqKmZlZWRmb3J3YXJkIGxheWVycyoqLg0KDQojIyMgKipEZWNvZGVyKioNCi0gKipHZW5lcmF0ZXMgb3V0cHV0IHN0ZXAtYnktc3RlcCoqIChlLmcuLCBwcmVkaWN0aW5nIG5leHQgd29yZCkuDQotIFVzZXMgKipzZWxmLWF0dGVudGlvbiwgZW5jb2Rlci1kZWNvZGVyIGF0dGVudGlvbiwgYW5kIGZlZWRmb3J3YXJkIGxheWVycyoqLg0KDQotLS0NCg0KIyMgKio0LiBTZWxmLUF0dGVudGlvbiBNZWNoYW5pc20qKg0KVGhlICoqc2VsZi1hdHRlbnRpb24gbWVjaGFuaXNtKiogYWxsb3dzIGEgd29yZCB0byBmb2N1cyBvbiBvdGhlciB3b3JkcyAqKmFueXdoZXJlIGluIHRoZSBpbnB1dCBzZW50ZW5jZSoqLCByZWdhcmRsZXNzIG9mIGRpc3RhbmNlLg0KDQojIyMgKipIb3cgU2VsZi1BdHRlbnRpb24gV29ya3MgKE1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbikqKg0KMS4gQ29tcHV0ZSAqKnF1ZXJ5IChRKSwga2V5IChLKSwgYW5kIHZhbHVlIChWKSoqIG1hdHJpY2VzIGZyb20gdGhlIGlucHV0IGVtYmVkZGluZ3M6DQoNCiAgIFxbDQogICBRID0gWCBXX3EsIFxxdWFkIEsgPSBYIFdfaywgXHF1YWQgViA9IFggV192DQogICBcXQ0KDQoyLiBDb21wdXRlICoqYXR0ZW50aW9uIHNjb3JlcyoqIGJ5IHRha2luZyB0aGUgZG90IHByb2R1Y3Qgb2YgcXVlcmllcyBhbmQga2V5czoNCg0KICAgXFsNCiAgIFx0ZXh0e1Njb3Jlc30gPSBRIEteVA0KICAgXF0NCg0KMy4gQXBwbHkgKipzb2Z0bWF4KiogdG8gbm9ybWFsaXplIHRoZSBzY29yZXM6DQoNCiAgIFxbDQogICBcdGV4dHtBdHRlbnRpb24gV2VpZ2h0c30gPSBcdGV4dHtzb2Z0bWF4fSBcbGVmdCggXGZyYWN7UUteVH17XHNxcnR7ZF9rfX0gXHJpZ2h0KQ0KICAgXF0NCg0KNC4gTXVsdGlwbHkgYnkgdGhlICoqdmFsdWUgbWF0cml4IChWKSoqOg0KDQogICBcWw0KICAgXHRleHR7T3V0cHV0fSA9IFx0ZXh0e0F0dGVudGlvbiBXZWlnaHRzfSBcdGltZXMgVg0KICAgXF0NCg0KIyMjICoqRXhhbXBsZSoqDQotIFNlbnRlbmNlOiAiVGhlIGNhdCBzYXQgb24gdGhlIG1hdC4iICANCi0gVGhlIHdvcmQgImNhdCIgc2hvdWxkIGZvY3VzIG9uIHJlbGF0ZWQgd29yZHMgbGlrZSAic2F0IiBhbmQgIm1hdCwiIHJhdGhlciB0aGFuIHVucmVsYXRlZCB3b3Jkcy4gIA0KLSAqKlNlbGYtYXR0ZW50aW9uIGFzc2lnbnMgd2VpZ2h0cyoqIHRvIHRoZXNlIHJlbGF0aW9uc2hpcHMgZHluYW1pY2FsbHkuICANCg0KLS0tDQoNCiMjICoqNS4gTXVsdGktSGVhZCBBdHRlbnRpb24qKg0KSW5zdGVhZCBvZiBjb21wdXRpbmcgc2VsZi1hdHRlbnRpb24gb25jZSwgKiptdWx0aS1oZWFkIGF0dGVudGlvbioqIHJ1bnMgKiptdWx0aXBsZSBhdHRlbnRpb24gbWVjaGFuaXNtcyBpbiBwYXJhbGxlbCoqLg0KDQoqKkFkdmFudGFnZXM6KioNCuKchSBDYXB0dXJlcyAqKmRpZmZlcmVudCB0eXBlcyBvZiByZWxhdGlvbnNoaXBzKiogKGUuZy4sIHN5bnRheCwgbWVhbmluZykuICANCuKchSBJbXByb3ZlcyAqKm1vZGVsIHJvYnVzdG5lc3MqKi4gIA0KDQpNYXRoZW1hdGljYWxseToNClxbDQpcdGV4dHtNdWx0aUhlYWR9KFEsIEssIFYpID0gXHRleHR7Q29uY2F0fShcdGV4dHtoZWFkfV8xLCBcdGV4dHtoZWFkfV8yLCAuLi4sIFx0ZXh0e2hlYWR9X2gpIFdfbw0KXF0NCg0KLS0tDQoNCiMjICoqNi4gUG9zaXRpb25hbCBFbmNvZGluZyoqDQpTaW5jZSB0cmFuc2Zvcm1lcnMgKipkbyBub3QgaGF2ZSByZWN1cnJlbmNlKiosIHRoZXkgbmVlZCBhIHdheSB0byAqKmVuY29kZSB3b3JkIG9yZGVyKiouIFRoaXMgaXMgZG9uZSB2aWEgKipwb3NpdGlvbmFsIGVuY29kaW5nKiouDQoNCiMjIyAqKlBvc2l0aW9uYWwgRW5jb2RpbmcgRm9ybXVsYSoqDQpcWw0KUEVfeyhwb3MsIDJpKX0gPSBcc2luKHBvcyAvIDEwMDAwXnsyaS9kfSkNClxdDQpcWw0KUEVfeyhwb3MsIDJpKzEpfSA9IFxjb3MocG9zIC8gMTAwMDBeezJpL2R9KQ0KXF0NCg0KKipLZXkgSWRlYToqKiBXb3JkcyBvY2N1cnJpbmcgZWFybGllciBnZXQgZGlmZmVyZW50ICoqc2ludXNvaWRhbCBlbmNvZGluZ3MqKiB0aGFuIGxhdGVyIHdvcmRzLg0KDQotLS0NCg0KIyMgKio3LiBUcmFuc2Zvcm1lciBpbiBBY3Rpb246IEJFUlQgJiBHUFQqKiAgDQpUcmFuc2Zvcm1lcnMgcG93ZXIgc3RhdGUtb2YtdGhlLWFydCBtb2RlbHMgbGlrZSAqKkJFUlQsIEdQVC0zLCBhbmQgVDUqKi4NCg0KfCBNb2RlbCB8IENoYXJhY3RlcmlzdGljcyB8DQp8LS0tLS0tLXwtLS0tLS0tLS0tLS0tLS18DQp8ICoqQkVSVCoqIChCaWRpcmVjdGlvbmFsIEVuY29kZXIgUmVwcmVzZW50YXRpb25zIGZyb20gVHJhbnNmb3JtZXJzKSB8IFVzZXMgYmlkaXJlY3Rpb25hbCBhdHRlbnRpb24sIGV4Y2VscyBpbiB1bmRlcnN0YW5kaW5nIGNvbnRleHQuIHwNCnwgKipHUFQtMyoqIChHZW5lcmF0aXZlIFByZXRyYWluZWQgVHJhbnNmb3JtZXIpIHwgVXNlcyBhdXRvcmVncmVzc2l2ZSBhdHRlbnRpb24sIGdlbmVyYXRlcyB0ZXh0IGZsdWVudGx5LiB8DQp8ICoqVDUqKiAoVGV4dC1Uby1UZXh0IFRyYW5zZmVyIFRyYW5zZm9ybWVyKSB8IENvbnZlcnRzIGFsbCBOTFAgdGFza3MgaW50byBhIHRleHQtYmFzZWQgZm9ybWF0LiB8DQoNCi0tLQ0KDQojIyAqKjguIEltcGxlbWVudGluZyBUcmFuc2Zvcm1lcnMgaW4gUHl0aG9uIChVc2luZyBIdWdnaW5nIEZhY2UpKioNCiMjIyAqKkxvYWRpbmcgYSBQcmV0cmFpbmVkIEJFUlQgTW9kZWwqKg0KYGBgcHl0aG9uDQpmcm9tIHRyYW5zZm9ybWVycyBpbXBvcnQgQmVydFRva2VuaXplciwgQmVydE1vZGVsDQoNCiMgTG9hZCBwcmV0cmFpbmVkIEJFUlQgbW9kZWwNCnRva2VuaXplciA9IEJlcnRUb2tlbml6ZXIuZnJvbV9wcmV0cmFpbmVkKCdiZXJ0LWJhc2UtdW5jYXNlZCcpDQptb2RlbCA9IEJlcnRNb2RlbC5mcm9tX3ByZXRyYWluZWQoJ2JlcnQtYmFzZS11bmNhc2VkJykNCg0KIyBFbmNvZGUgYSBzZW50ZW5jZQ0Kc2VudGVuY2UgPSAiVHJhbnNmb3JtZXJzIGFyZSB0aGUgZnV0dXJlIG9mIGRlZXAgbGVhcm5pbmcuIg0KaW5wdXRzID0gdG9rZW5pemVyKHNlbnRlbmNlLCByZXR1cm5fdGVuc29ycz0icHQiKQ0KDQojIFBhc3MgdGhyb3VnaCBtb2RlbA0Kb3V0cHV0cyA9IG1vZGVsKCoqaW5wdXRzKQ0KcHJpbnQob3V0cHV0cy5sYXN0X2hpZGRlbl9zdGF0ZS5zaGFwZSkNCmBgYA0KDQojIyMgKipGaW5lLVR1bmluZyBHUFQgZm9yIFRleHQgR2VuZXJhdGlvbioqDQpgYGBweXRob24NCmZyb20gdHJhbnNmb3JtZXJzIGltcG9ydCBHUFQyTE1IZWFkTW9kZWwsIEdQVDJUb2tlbml6ZXINCg0KIyBMb2FkIEdQVC0yDQp0b2tlbml6ZXIgPSBHUFQyVG9rZW5pemVyLmZyb21fcHJldHJhaW5lZCgiZ3B0MiIpDQptb2RlbCA9IEdQVDJMTUhlYWRNb2RlbC5mcm9tX3ByZXRyYWluZWQoImdwdDIiKQ0KDQojIEdlbmVyYXRlIHRleHQNCmlucHV0X3RleHQgPSAiRGVlcCBsZWFybmluZyBoYXMgdHJhbnNmb3JtZWQiDQppbnB1dHMgPSB0b2tlbml6ZXIoaW5wdXRfdGV4dCwgcmV0dXJuX3RlbnNvcnM9InB0IikNCg0KIyBHZW5lcmF0ZSBuZXh0IHdvcmRzDQpvdXRwdXQgPSBtb2RlbC5nZW5lcmF0ZSgqKmlucHV0cywgbWF4X2xlbmd0aD01MCkNCnByaW50KHRva2VuaXplci5kZWNvZGUob3V0cHV0WzBdKSkNCmBgYA0KDQotLS0NCg0KIyMgKio5LiBLZXkgVGFrZWF3YXlzKioNCuKchSAqKlRyYW5zZm9ybWVycyBvdXRwZXJmb3JtIFJOTnMqKiBkdWUgdG8gcGFyYWxsZWxpemF0aW9uLiAgDQrinIUgKipTZWxmLWF0dGVudGlvbiBoZWxwcyBtb2RlbHMgdW5kZXJzdGFuZCByZWxhdGlvbnNoaXBzKiogYWNyb3NzIGxvbmcgc2VxdWVuY2VzLiAgDQrinIUgKipCRVJUIGFuZCBHUFQgYXJlIGJ1aWx0IG9uIHRyYW5zZm9ybWVycyoqIGFuZCBleGNlbCBpbiBOTFAgdGFza3MuICANCg0KLS0tDQoNCiMjICoqMTAuIERpc2N1c3Npb24gUXVlc3Rpb25zKioNCjEuIEhvdyBkb2VzIHNlbGYtYXR0ZW50aW9uIGRpZmZlciBmcm9tIHRyYWRpdGlvbmFsIGF0dGVudGlvbiBtZWNoYW5pc21zPw0KMi4gV2h5IGRvIHRyYW5zZm9ybWVycyB1c2UgKipwb3NpdGlvbmFsIGVuY29kaW5nKio/DQozLiBXaGF0IGlzIHRoZSByb2xlIG9mICoqbXVsdGktaGVhZCBhdHRlbnRpb24qKiBpbiBpbXByb3ZpbmcgdHJhbnNmb3JtZXIgcGVyZm9ybWFuY2U/DQo0LiBIb3cgZG9lcyAqKkJFUlQgZGlmZmVyIGZyb20gR1BUKiogaW4gdGVybXMgb2YgdHJhaW5pbmc/DQoNCi0tLQ0KDQojICoqU3R1ZHkgR3VpZGU6IEFwcGxpY2F0aW9ucyBvZiBEZWVwIExlYXJuaW5nICYgTmV1cmFsIE5ldHdvcmtzIC0gRFMtNzMzMywgTW9kdWxlIDExLCBTZWN0aW9uIDgqKiAgDQoNCiMjICoqMS4gSW50cm9kdWN0aW9uIHRvIERlZXAgTGVhcm5pbmcgQXBwbGljYXRpb25zKiogIA0KRGVlcCBsZWFybmluZyBtb2RlbHMgYXJlICoqdHJhbnNmb3JtaW5nIGluZHVzdHJpZXMqKiBieSBhdXRvbWF0aW5nIHRhc2tzLCBpbXByb3ZpbmcgcHJlZGljdGlvbnMsIGFuZCBlbmhhbmNpbmcgZGVjaXNpb24tbWFraW5nLiBUaGVzZSBtb2RlbHMsIHBvd2VyZWQgYnkgKipuZXVyYWwgbmV0d29ya3MqKiwgYXJlIHdpZGVseSB1c2VkIGluOiAgDQrinIUgKipDb21wdXRlciBWaXNpb24gKENWKSoqIOKAkyBJbWFnZSBjbGFzc2lmaWNhdGlvbiwgb2JqZWN0IGRldGVjdGlvbiwgbWVkaWNhbCBpbWFnaW5nLiAgDQrinIUgKipOYXR1cmFsIExhbmd1YWdlIFByb2Nlc3NpbmcgKE5MUCkqKiDigJMgQ2hhdGJvdHMsIHRyYW5zbGF0aW9uLCB0ZXh0IHN1bW1hcml6YXRpb24uICANCuKchSAqKkhlYWx0aGNhcmUgJiBCaW9tZWRpY2FsIEFwcGxpY2F0aW9ucyoqIOKAkyBEaXNlYXNlIGRldGVjdGlvbiwgZHJ1ZyBkaXNjb3ZlcnkuICANCuKchSAqKkF1dG9ub21vdXMgU3lzdGVtcyoqIOKAkyBTZWxmLWRyaXZpbmcgY2Fycywgcm9ib3RpY3MuICANCuKchSAqKkZpbmFuY2UgJiBCdXNpbmVzcyBJbnRlbGxpZ2VuY2UqKiDigJMgRnJhdWQgZGV0ZWN0aW9uLCBhbGdvcml0aG1pYyB0cmFkaW5nLiAgDQoNCi0tLQ0KDQojIyAqKjIuIENvbXB1dGVyIFZpc2lvbjogSG93IERlZXAgTGVhcm5pbmcgU2VlcyB0aGUgV29ybGQqKiAgDQojIyMgKipFeGFtcGxlOiBJbWFnZSBDbGFzc2lmaWNhdGlvbiB3aXRoIENOTnMqKg0KQ29udm9sdXRpb25hbCBOZXVyYWwgTmV0d29ya3MgKENOTnMpIHVzZSAqKmZpbHRlcnMqKiB0byByZWNvZ25pemUgcGF0dGVybnMgaW4gaW1hZ2VzLCBzdWNoIGFzIGVkZ2VzLCB0ZXh0dXJlcywgYW5kIG9iamVjdHMuDQoNCiMjIyAqKkhvdyBDTk5zIFdvcmsqKg0KMe+4j+KDoyAqKkNvbnZvbHV0aW9uIExheWVyKiog4oCTIERldGVjdHMgZmVhdHVyZXMgbGlrZSBlZGdlcywgY29ybmVycy4gIA0KMu+4j+KDoyAqKlBvb2xpbmcgTGF5ZXIqKiDigJMgUmVkdWNlcyBpbWFnZSBzaXplIHRvIGtlZXAgaW1wb3J0YW50IGZlYXR1cmVzLiAgDQoz77iP4oOjICoqRnVsbHkgQ29ubmVjdGVkIExheWVyKiog4oCTIENvbnZlcnRzIGltYWdlIGZlYXR1cmVzIGludG8gZmluYWwgcHJlZGljdGlvbnMuICANCg0KIyMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uKioNCkdpdmVuIGFuIGlucHV0IGltYWdlICoqWCoqLCBhIGZpbHRlciAoa2VybmVsKSAqKlcqKiwgYW5kIGJpYXMgKipiKiosIGNvbnZvbHV0aW9uIGlzOg0KXFsNClogPSBXICogWCArIGINClxdDQp3aGVyZSAqKuKIlyoqIHJlcHJlc2VudHMgdGhlIGNvbnZvbHV0aW9uIG9wZXJhdGlvbi4NCg0KIyMjICoqQ29kZSBFeGFtcGxlOiBJbWFnZSBDbGFzc2lmaWNhdGlvbiB3aXRoIFB5VG9yY2gqKg0KYGBgcHl0aG9uDQppbXBvcnQgdG9yY2gNCmltcG9ydCB0b3JjaC5ubiBhcyBubg0KaW1wb3J0IHRvcmNodmlzaW9uLnRyYW5zZm9ybXMgYXMgdHJhbnNmb3Jtcw0KZnJvbSB0b3JjaHZpc2lvbiBpbXBvcnQgbW9kZWxzDQoNCiMgTG9hZCBQcmV0cmFpbmVkIFJlc05ldCBNb2RlbA0KbW9kZWwgPSBtb2RlbHMucmVzbmV0MTgocHJldHJhaW5lZD1UcnVlKQ0KDQojIE1vZGlmeSBmb3IgY3VzdG9tIGNsYXNzaWZpY2F0aW9uDQptb2RlbC5mYyA9IG5uLkxpbmVhcig1MTIsIDEwKSAgIyAxMCBvdXRwdXQgY2xhc3Nlcw0KDQojIFByaW50IE1vZGVsIFN1bW1hcnkNCnByaW50KG1vZGVsKQ0KYGBgDQoNCiMjIyAqKktleSBBcHBsaWNhdGlvbnMqKg0K4pyFICoqRmFjaWFsIFJlY29nbml0aW9uKiog4oCTIFVzZWQgaW4gc2VjdXJpdHkgJiBhdXRoZW50aWNhdGlvbi4gIA0K4pyFICoqTWVkaWNhbCBJbWFnaW5nKiog4oCTIElkZW50aWZpZXMgdHVtb3JzLCBmcmFjdHVyZXMgaW4gWC1yYXlzICYgTVJJcy4gIA0K4pyFICoqQXV0b25vbW91cyBWZWhpY2xlcyoqIOKAkyBEZXRlY3RzIG9ic3RhY2xlcyAmIHBlZGVzdHJpYW5zLiAgDQoNCi0tLQ0KDQojIyAqKjMuIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyAoTkxQKTogSG93IEFJIFVuZGVyc3RhbmRzIFRleHQqKiAgDQojIyMgKipFeGFtcGxlOiBUZXh0IENsYXNzaWZpY2F0aW9uIHdpdGggVHJhbnNmb3JtZXJzKioNCk5MUCBtb2RlbHMgKiphbmFseXplIGFuZCBnZW5lcmF0ZSoqIGh1bWFuIGxhbmd1YWdlLCBlbmFibGluZyBjaGF0Ym90cywgc2VhcmNoIGVuZ2luZXMsIGFuZCB0cmFuc2xhdGlvbiB0b29scy4NCg0KIyMjICoqSG93IE5MUCBXb3JrcyB3aXRoIFRyYW5zZm9ybWVycyoqDQrinIUgKipUb2tlbml6YXRpb24qKiDigJMgQ29udmVydHMgd29yZHMgaW50byBudW1lcmljYWwgcmVwcmVzZW50YXRpb25zLiAgDQrinIUgKipTZWxmLUF0dGVudGlvbiBNZWNoYW5pc20qKiDigJMgVW5kZXJzdGFuZHMgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHdvcmRzLiAgDQrinIUgKipFbWJlZGRpbmcgTGF5ZXJzKiog4oCTIENhcHR1cmVzIHdvcmQgbWVhbmluZyBpbiBkaWZmZXJlbnQgY29udGV4dHMuICANCg0KIyMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uIG9mIEF0dGVudGlvbioqDQpcWw0KXHRleHR7QXR0ZW50aW9ufShRLCBLLCBWKSA9IFx0ZXh0e3NvZnRtYXh9IFxsZWZ0KFxmcmFje1FLXlR9e1xzcXJ0e2Rfa319IFxyaWdodCkgVg0KXF0NCndoZXJlICoqUSAocXVlcnkpLCBLIChrZXkpLCBhbmQgViAodmFsdWUpKiogYXJlIG1hdHJpY2VzIGRlcml2ZWQgZnJvbSBpbnB1dCB3b3Jkcy4NCg0KIyMjICoqQ29kZSBFeGFtcGxlOiBTZW50aW1lbnQgQW5hbHlzaXMgd2l0aCBCRVJUKioNCmBgYHB5dGhvbg0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0IEJlcnRUb2tlbml6ZXIsIEJlcnRGb3JTZXF1ZW5jZUNsYXNzaWZpY2F0aW9uDQppbXBvcnQgdG9yY2gNCg0KIyBMb2FkIFByZXRyYWluZWQgQkVSVCBNb2RlbA0KdG9rZW5pemVyID0gQmVydFRva2VuaXplci5mcm9tX3ByZXRyYWluZWQoImJlcnQtYmFzZS11bmNhc2VkIikNCm1vZGVsID0gQmVydEZvclNlcXVlbmNlQ2xhc3NpZmljYXRpb24uZnJvbV9wcmV0cmFpbmVkKCJiZXJ0LWJhc2UtdW5jYXNlZCIpDQoNCiMgU2FtcGxlIFRleHQNCnRleHQgPSAiVGhpcyBtb3ZpZSB3YXMgYWJzb2x1dGVseSBhbWF6aW5nISINCmlucHV0cyA9IHRva2VuaXplcih0ZXh0LCByZXR1cm5fdGVuc29ycz0icHQiKQ0KDQojIFByZWRpY3QgU2VudGltZW50DQpvdXRwdXRzID0gbW9kZWwoKippbnB1dHMpDQpwcmludChvdXRwdXRzLmxvZ2l0cykNCmBgYA0KDQojIyMgKipLZXkgQXBwbGljYXRpb25zKioNCuKchSAqKkNoYXRib3RzICYgVmlydHVhbCBBc3Npc3RhbnRzKiog4oCTIFNpcmksIEFsZXhhLCBHUFQtcG93ZXJlZCBjaGF0Ym90cy4gIA0K4pyFICoqVGV4dCBTdW1tYXJpemF0aW9uKiog4oCTIFN1bW1hcml6ZXMgbG9uZyBhcnRpY2xlcyBhdXRvbWF0aWNhbGx5LiAgDQrinIUgKipMYW5ndWFnZSBUcmFuc2xhdGlvbioqIOKAkyBHb29nbGUgVHJhbnNsYXRlLCBEZWVwTC4gIA0KDQotLS0NCg0KIyMgKio0LiBIZWFsdGhjYXJlICYgQmlvbWVkaWNhbCBBcHBsaWNhdGlvbnM6IEFJIGluIE1lZGljaW5lKioNCkRlZXAgbGVhcm5pbmcgKipyZXZvbHV0aW9uaXplcyBoZWFsdGhjYXJlKiogYnkgZGlhZ25vc2luZyBkaXNlYXNlcywgcGVyc29uYWxpemluZyB0cmVhdG1lbnQgcGxhbnMsIGFuZCBkaXNjb3ZlcmluZyBuZXcgZHJ1Z3MuDQoNCiMjIyAqKkV4YW1wbGU6IERpc2Vhc2UgUHJlZGljdGlvbiB3aXRoIE5ldXJhbCBOZXR3b3JrcyoqDQpOZXVyYWwgbmV0d29ya3MgYW5hbHl6ZSBtZWRpY2FsIGRhdGEgKFgtcmF5cywgTVJJcywgcGF0aWVudCByZWNvcmRzKSB0byBkZXRlY3QgZGlzZWFzZXMgZWFybHkuDQoNCiMjIyAqKkhvdyBpdCBXb3JrcyoqDQox77iP4oOjICoqRmVhdHVyZSBFeHRyYWN0aW9uKiog4oCTIEV4dHJhY3RzIG1lZGljYWwgcGF0dGVybnMgZnJvbSBkYXRhLiAgDQoy77iP4oOjICoqTmV1cmFsIE5ldHdvcmsgUHJvY2Vzc2luZyoqIOKAkyBMZWFybnMgZGlzZWFzZSBpbmRpY2F0b3JzLiAgDQoz77iP4oOjICoqUHJlZGljdGlvbioqIOKAkyBPdXRwdXRzIGRpc2Vhc2UgcHJvYmFiaWxpdHkuICANCg0KIyMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uKioNCkZvciBhIHBhdGllbnTigJlzIG1lZGljYWwgZGF0YSAqKlgqKiwgd2VpZ2h0cyAqKlcqKiwgYW5kIGJpYXMgKipiKiosIHByZWRpY3Rpb24gKirFtyoqIGlzOg0KXFsNCsW3ID0gXHNpZ21hKFdYICsgYikNClxdDQp3aGVyZSAqKs+DKiogaXMgYW4gYWN0aXZhdGlvbiBmdW5jdGlvbiAoZS5nLiwgUmVMVSwgc2lnbW9pZCkuDQoNCiMjIyAqKkNvZGUgRXhhbXBsZTogVHVtb3IgRGV0ZWN0aW9uIHdpdGggVGVuc29yRmxvdyoqDQpgYGBweXRob24NCmltcG9ydCB0ZW5zb3JmbG93IGFzIHRmDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMubW9kZWxzIGltcG9ydCBTZXF1ZW50aWFsDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMubGF5ZXJzIGltcG9ydCBEZW5zZQ0KDQojIENyZWF0ZSBhIE5ldXJhbCBOZXR3b3JrIE1vZGVsDQptb2RlbCA9IFNlcXVlbnRpYWwoWw0KICAgIERlbnNlKDMyLCBhY3RpdmF0aW9uPSdyZWx1JywgaW5wdXRfc2hhcGU9KDEwLCkpLA0KICAgIERlbnNlKDEsIGFjdGl2YXRpb249J3NpZ21vaWQnKSAgIyBCaW5hcnkgY2xhc3NpZmljYXRpb24NCl0pDQoNCiMgQ29tcGlsZSBNb2RlbA0KbW9kZWwuY29tcGlsZShvcHRpbWl6ZXI9J2FkYW0nLCBsb3NzPSdiaW5hcnlfY3Jvc3NlbnRyb3B5JywgbWV0cmljcz1bJ2FjY3VyYWN5J10pDQoNCiMgUHJpbnQgU3VtbWFyeQ0KbW9kZWwuc3VtbWFyeSgpDQpgYGANCg0KIyMjICoqS2V5IEFwcGxpY2F0aW9ucyoqDQrinIUgKipDYW5jZXIgRGV0ZWN0aW9uKiog4oCTIElkZW50aWZpZXMgdHVtb3JzIGluIG1lZGljYWwgc2NhbnMuICANCuKchSAqKkRydWcgRGlzY292ZXJ5Kiog4oCTIFVzZXMgQUkgdG8gZmluZCBuZXcgdHJlYXRtZW50cy4gIA0K4pyFICoqUHJlZGljdGluZyBQYXRpZW50IE91dGNvbWVzKiog4oCTIEFJLWRyaXZlbiBwZXJzb25hbGl6ZWQgbWVkaWNpbmUuICANCg0KLS0tDQoNCiMjICoqNS4gQXV0b25vbW91cyBTeXN0ZW1zOiBBSSBpbiBTZWxmLURyaXZpbmcgQ2FycyAmIFJvYm90aWNzKiogIA0KRGVlcCBsZWFybmluZyAqKmVuYWJsZXMgbWFjaGluZXMgdG8gbWFrZSByZWFsLXRpbWUgZGVjaXNpb25zKiosIGVzc2VudGlhbCBmb3Igc2VsZi1kcml2aW5nIGNhcnMsIGRyb25lcywgYW5kIGluZHVzdHJpYWwgcm9ib3RzLg0KDQojIyMgKipIb3cgU2VsZi1Ecml2aW5nIENhcnMgV29yayoqDQox77iP4oOjICoqUGVyY2VwdGlvbioqIOKAkyBEZXRlY3RzIGVudmlyb25tZW50IChwZWRlc3RyaWFucywgc2lnbnMpLiAgDQoy77iP4oOjICoqUHJlZGljdGlvbioqIOKAkyBGb3JlY2FzdHMgb2JqZWN0IG1vdmVtZW50LiAgDQoz77iP4oOjICoqUGxhbm5pbmcgJiBDb250cm9sKiog4oCTIERlY2lkZXMgY2Fy4oCZcyBuZXh0IGFjdGlvbi4gIA0KDQojIyMgKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24qKg0KTmV1cmFsIG5ldHdvcmtzIHByZWRpY3Qgc3RlZXJpbmcgYW5nbGVzICoqzrgqKiBiYXNlZCBvbiBzZW5zb3IgaW5wdXQgKipYKio6DQpcWw0KXHRoZXRhID0gVyBYICsgYg0KXF0NCg0KIyMjICoqQ29kZSBFeGFtcGxlOiBTZWxmLURyaXZpbmcgQ2FyIFNpbXVsYXRpb24qKg0KYGBgcHl0aG9uDQppbXBvcnQgZ3ltDQppbXBvcnQgbnVtcHkgYXMgbnANCmZyb20gc3RhYmxlX2Jhc2VsaW5lczMgaW1wb3J0IFBQTw0KDQojIExvYWQgRW52aXJvbm1lbnQNCmVudiA9IGd5bS5tYWtlKCJDYXJSYWNpbmctdjAiKQ0KDQojIExvYWQgUHJldHJhaW5lZCBSTCBNb2RlbA0KbW9kZWwgPSBQUE8oIk1scFBvbGljeSIsIGVudiwgdmVyYm9zZT0xKQ0KbW9kZWwubGVhcm4odG90YWxfdGltZXN0ZXBzPTEwMDAwKQ0KDQojIFRlc3QgdGhlIE1vZGVsDQpvYnMgPSBlbnYucmVzZXQoKQ0KZm9yIF8gaW4gcmFuZ2UoMTAwMCk6DQogICAgYWN0aW9uLCBfID0gbW9kZWwucHJlZGljdChvYnMpDQogICAgb2JzLCBfLCBkb25lLCBfID0gZW52LnN0ZXAoYWN0aW9uKQ0KICAgIGVudi5yZW5kZXIoKQ0KICAgIGlmIGRvbmU6DQogICAgICAgIGJyZWFrDQpgYGANCg0KIyMjICoqS2V5IEFwcGxpY2F0aW9ucyoqDQrinIUgKipBdXRvbm9tb3VzIFZlaGljbGVzKiog4oCTIFRlc2xhLCBXYXltbyBzZWxmLWRyaXZpbmcgdGVjaG5vbG9neS4gIA0K4pyFICoqUm9ib3RpY3MqKiDigJMgQUktcG93ZXJlZCBpbmR1c3RyaWFsIHJvYm90cywgaHVtYW5vaWQgcm9ib3RzLiAgDQrinIUgKipEcm9uZXMqKiDigJMgQUktZHJpdmVuIFVBVnMgZm9yIGRlbGl2ZXJ5ICYgc3VydmVpbGxhbmNlLiAgDQoNCi0tLQ0KDQojIyAqKjYuIEZpbmFuY2UgJiBCdXNpbmVzcyBJbnRlbGxpZ2VuY2U6IEFJIGluIERlY2lzaW9uLU1ha2luZyoqDQpEZWVwIGxlYXJuaW5nICoqb3B0aW1pemVzIGJ1c2luZXNzIHByb2Nlc3NlcyoqLCBkZXRlY3RpbmcgZnJhdWQsIHByZWRpY3Rpbmcgc3RvY2sgdHJlbmRzLCBhbmQgaW1wcm92aW5nIHJlY29tbWVuZGF0aW9ucy4NCg0KIyMjICoqRXhhbXBsZTogRnJhdWQgRGV0ZWN0aW9uIHdpdGggTmV1cmFsIE5ldHdvcmtzKioNCkFJIGlkZW50aWZpZXMgKip1bnVzdWFsIHRyYW5zYWN0aW9uIHBhdHRlcm5zKiogdGhhdCBtYXkgaW5kaWNhdGUgZnJhdWQuDQoNCiMjIyAqKk1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbioqDQpcWw0KXGhhdHt5fSA9IFxzaWdtYShXWCArIGIpDQpcXQ0Kd2hlcmUgKipYKiogaXMgdHJhbnNhY3Rpb24gZGF0YSwgKipXKiogaXMgbGVhcm5lZCBmcmF1ZCBkZXRlY3Rpb24gcGF0dGVybnMsIGFuZCAqKs+DKiogaXMgYW4gYWN0aXZhdGlvbiBmdW5jdGlvbi4NCg0KIyMjICoqQ29kZSBFeGFtcGxlOiBGcmF1ZCBEZXRlY3Rpb24gd2l0aCBQeVRvcmNoKioNCmBgYHB5dGhvbg0KaW1wb3J0IHRvcmNoDQppbXBvcnQgdG9yY2gubm4gYXMgbm4NCg0KIyBEZWZpbmUgTmV1cmFsIE5ldHdvcmsNCmNsYXNzIEZyYXVkRGV0ZWN0b3Iobm4uTW9kdWxlKToNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHN1cGVyKEZyYXVkRGV0ZWN0b3IsIHNlbGYpLl9faW5pdF9fKCkNCiAgICAgICAgc2VsZi5mYzEgPSBubi5MaW5lYXIoMzAsIDE2KQ0KICAgICAgICBzZWxmLmZjMiA9IG5uLkxpbmVhcigxNiwgMSkNCg0KICAgIGRlZiBmb3J3YXJkKHNlbGYsIHgpOg0KICAgICAgICB4ID0gdG9yY2gucmVsdShzZWxmLmZjMSh4KSkNCiAgICAgICAgcmV0dXJuIHRvcmNoLnNpZ21vaWQoc2VsZi5mYzIoeCkpDQoNCiMgSW5pdGlhbGl6ZSBNb2RlbA0KbW9kZWwgPSBGcmF1ZERldGVjdG9yKCkNCnByaW50KG1vZGVsKQ0KYGBgDQoNCiMjIyAqKktleSBBcHBsaWNhdGlvbnMqKg0K4pyFICoqRnJhdWQgRGV0ZWN0aW9uKiog4oCTIEFJIGZsYWdzIHN1c3BpY2lvdXMgdHJhbnNhY3Rpb25zLiAgDQrinIUgKipTdG9jayBNYXJrZXQgUHJlZGljdGlvbioqIOKAkyBBSS1kcml2ZW4gYWxnb3JpdGhtaWMgdHJhZGluZy4gIA0K4pyFICoqUGVyc29uYWxpemVkIFJlY29tbWVuZGF0aW9ucyoqIOKAkyBOZXRmbGl4LCBBbWF6b24gcmVjb21tZW5kYXRpb25zLiAgDQoNCi0tLQ0KDQojIyAqKjcuIEtleSBUYWtlYXdheXMqKg0K4pyFICoqRGVlcCBsZWFybmluZyBwb3dlcnMgbW9kZXJuIEFJIGFwcGxpY2F0aW9ucyoqLCBmcm9tIGhlYWx0aGNhcmUgdG8gZmluYW5jZS4gIA0K4pyFICoqQ05OcyBkb21pbmF0ZSBjb21wdXRlciB2aXNpb24qKiwgd2hpbGUgKip0cmFuc2Zvcm1lcnMgbGVhZCBOTFAqKi4gIA0K4pyFICoqTmV1cmFsIG5ldHdvcmtzIGVuYWJsZSBzZWxmLWRyaXZpbmcgY2FycywgbWVkaWNhbCBkaWFnbm9zdGljcywgYW5kIGZyYXVkIGRldGVjdGlvbioqLiAgDQoNCi0tLQ0KDQojIyAqKjguIERpc2N1c3Npb24gUXVlc3Rpb25zKioNCjEuIEhvdyBkb2VzIEFJICoqaW1wcm92ZSBkaXNlYXNlIGRldGVjdGlvbioqIGNvbXBhcmVkIHRvIHRyYWRpdGlvbmFsIG1ldGhvZHM/ICANCjIuIFdoeSBkbyAqKnNlbGYtZHJpdmluZyBjYXJzIHVzZSBkZWVwIHJlaW5mb3JjZW1lbnQgbGVhcm5pbmcqKj8gIA0KMy4gSG93IGRvZXMgKiphdHRlbnRpb24gaGVscCBOTFAgbW9kZWxzIGxpa2UgQ2hhdEdQVCoqPyAgDQo0LiBXaGF0IGFyZSB0aGUgKipsaW1pdGF0aW9ucyBvZiBkZWVwIGxlYXJuaW5nIGluIGJ1c2luZXNzIGludGVsbGlnZW5jZSoqPyAgDQoNCi0tLQ0KDQo=