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
- Understanding large datasets
- Memory constraints and computational limits
- Defining “big data” in practical applications
- Strategies for handling large datasets in machine learning
- Mathematical formulation of large dataset constraints
- 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 methods –
dask
,
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
- What are the practical trade-offs between using batch
processing vs. distributed computing for large
datasets?
- Can we effectively use SGD with kernel-based
methods, or is it strictly for linear models?
- How does XGBoost handle memory constraints
differently compared to SVM?
- 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: 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
- 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.
- 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.
- 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
- The Hashing Trick enables fast, memory-efficient feature
encoding, making it useful for big data and online
learning.
- Hash functions map features to fixed memory
locations, reducing lookup time and memory footprint.
- Hash collisions may occur but generally do not significantly
affect model performance.
- Used in combination with SGD and out-of-core
learning to handle large datasets
efficiently.
Relevant Questions for Discussion
- What are the trade-offs between increasing and decreasing
the hash space?
- How does the Hashing Trick compare to one-hot
encoding?
- Why is the Hashing Trick commonly used in real-time and
streaming applications?
- What are some ways to mitigate hash collisions in practical
implementations?
- Can the hashing trick be used in deep learning
architectures, and if so, how?
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.
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
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
- How does VW differ from traditional ML libraries like
Scikit-Learn?
- What are the advantages of feature hashing?
- How does VW optimize learning rate
automatically?
- What are some trade-offs of using VW instead of deep
learning models?
- 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:
- Batch Size (
chunksize
):
- Too large → High memory usage.
- Too small → Slower training, more updates
needed.
- Learning Rate (
eta0
):
- Needs to be tuned properly for stability and convergence.
- Can be adjusted dynamically using
learning_rate="adaptive"
.
- Feature Scaling:
- Each batch must be scaled consistently (use
StandardScaler
or MinMaxScaler
).
- Class Balance:
partial_fit()
requires all classes to be present in
every batch.
- Solution: Manually set
classes=np.array([0,1])
in every call.
- Regularization (
penalty
):
- Prevents overfitting when learning from streaming data.
- Default: L2 penalty (Ridge regression).
3. Speed Comparison: fit()
vs. partial_fit()
fit() |
High |
Slower |
🚫 Not suitable for large data |
partial_fit() |
Low |
Faster |
✅ Efficient for streaming |
4. Discussion Questions
- Did using
partial_fit()
improve speed and
memory efficiency?
- How does scaling affect performance when using
partial_fit()
?
- What happens if one batch does not contain both class labels
(0 and 1)?
- How does
partial_fit()
compare to batch
gradient descent in terms of convergence?
- 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
- 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.
- 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.
- 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.
- 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**
- 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?
- 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?
- 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?
- 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
- How do we choose the best batch size and learning rate for
SGD?
- What are the trade-offs of using the hashing trick instead
of explicit feature storage?
- How does VW compare to traditional machine learning methods
for handling large-scale data?
- 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.
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)
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
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
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
- What are the key differences between RNNs, LSTMs, and GRUs?
- Why do standard RNNs struggle with long-term dependencies?
- How do forget, input, and output gates help LSTMs retain
information?
- What are some real-world applications of RNNs beyond NLP?
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.
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
- How does AI improve disease detection compared to
traditional methods?
- Why do self-driving cars use deep reinforcement
learning?
- How does attention help NLP models like
ChatGPT?
- 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=