Discussion Questions
- What are the benefits and limitations of using a
Bag-of-Words model?
- Why does TF-IDF often outperform simple CountVectorizer for
text classification?
- How can we improve our custom CountVectorizer to include
stopword removal and stemming?
—Here is your summarized and expanded study guide based on QTW Module
6 – Naive Bayes, including key concepts, mathematical formulas, Python
code for CPU and GPU utilization, plus three key takeaways and three
discussion questions. Type “proceed” if you need more, and I will
continue.
Study Guide: QTW Module 6 – Naive Bayes
- Overview of Bayes’ Rule Bayes’ Rule helps update an initial belief
(prior) after observing new evidence (likelihood). It is expressed
mathematically as:
P(A | B) = [ P(B | A) × P(A) ] / P(B)
• P(A | B): Posterior Probability (updated belief) • P(B | A):
Likelihood (probability of observing B given A is true) • P(A): Prior
Probability (initial belief) • P(B): Normalizing Factor (ensures the
result is a valid probability)
Example: Drug Test - True Positive Rate = 99% - True Negative Rate =
97% - Prevalence = 5%
P(A | B) = [0.99 × 0.05] / [ (0.99 × 0.05) + (0.03 × 0.95 ) ] ≈
63.5%
- Bayes’ Rule for Multiple Variables When dealing with multiple
evidence variables (B, C, D, …), we can generalize Bayes’ Rule as:
P(A | B, C, D) ∝ P(A) × P(B | A) × P(C | A) × P(D | A)
The “naive” assumption is that all evidence variables B, C, D are
conditionally independent given A, allowing us to multiply probabilities
rather than consider their complex joint distributions.
- Bayes’ Rule with Continuous Variables For continuous attributes
(temperature, weight), we use probability density functions (e.g.,
Gaussian) to estimate P(X = x). If the data is assumed normally
distributed, we can compute:
P(x; μ, σ) = (1 / (σ√(2π))) × exp[−(x − μ)² / (2σ²)]
We then insert these probabilities into Bayes’ framework just as we
do with discrete or categorical data.
Naive Bayes in Practice (Demo) • Often used for text
classification with bag-of-words or tf-idf features.
• MultinomialNB from sklearn is commonly applied: – Alpha parameter
(Laplace smoothing) handles zero-frequency words.
– Very fast, even on large datasets.
Naive Bayes vs. Logistic Regression • Naive Bayes (generative
model) and Logistic Regression (discriminative model) form a
generative-discriminative pair.
• Naive Bayes converges faster to a near-optimal solution with limited
data; Logistic Regression can yield a lower asymptotic error with
sufficient data.
• Both are deeply connected through the logit function and ratio forms
of probability.
Bag of Words Exercise • Task: Build a custom CountVectorizer
using Python.
• Compare with sklearn’s CountVectorizer to observe differences in
tokenization, vocabulary building, and performance.
Mathematical Representation (Core Formulas) 1. Bayes’ Rule (Single
Variable) P(A | B) = [P(B | A) P(A)] / P(B)
Bayes’ Rule (Multivariable) P(A | B, C, D) ∝ P(A) × P(B | A) ×
P(C | A) × P(D | A)
Gaussian Density (Continuous Example) p(x) = 1 / (σ√(2π)) ×
exp[−(x − μ)² / (2σ²)]
Code Snippets
A. CPU Implementation (Naive Bayes – Sklearn)
from sklearn.naive_bayes import MultinomialNB
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics import accuracy_score
# 1) Load Data
train_data = fetch_20newsgroups(subset='train', remove=('headers','footers','quotes'))
test_data = fetch_20newsgroups(subset='test', remove=('headers','footers','quotes'))
# 2) Transform Text (CountVectorizer)
cv = CountVectorizer()
X_train_cv = cv.fit_transform(train_data.data)
X_test_cv = cv.transform(test_data.data)
# 3) Train Naive Bayes
model_cv = MultinomialNB()
model_cv.fit(X_train_cv, train_data.target)
# 4) Evaluate
preds_cv = model_cv.predict(X_test_cv)
print("Accuracy with CountVectorizer:", accuracy_score(test_data.target, preds_cv))
# 5) Using TF-IDF
tfidf = TfidfVectorizer()
X_train_tfidf = tfidf.fit_transform(train_data.data)
X_test_tfidf = tfidf.transform(test_data.data)
model_tfidf = MultinomialNB()
model_tfidf.fit(X_train_tfidf, train_data.target)
preds_tfidf = model_tfidf.predict(X_test_tfidf)
print("Accuracy with TF-IDF:", accuracy_score(test_data.target, preds_tfidf))
B. Simple GPU Implementation (Naive Bayes with cuML) (You need an
environment with RAPIDS/cuml installed for this.)
import cupy as cp
from cuml.naive_bayes import MultinomialNB as cuMultinomialNB
from sklearn.feature_extraction.text import TfidfVectorizer
# Assume you have train_data & test_data from above
tfidf_gpu = TfidfVectorizer()
X_train_gpu = tfidf_gpu.fit_transform(train_data.data)
X_test_gpu = tfidf_gpu.transform(test_data.data)
# Convert to GPU arrays
X_train_gpu_csr = cp.sparse.csr_matrix(X_train_gpu)
X_test_gpu_csr = cp.sparse.csr_matrix(X_test_gpu)
y_train_gpu = cp.asarray(train_data.target)
y_test_gpu = cp.asarray(test_data.target)
# Train
model_gpu = cuMultinomialNB()
model_gpu.fit(X_train_gpu_csr, y_train_gpu)
# Predict
preds_gpu = model_gpu.predict(X_test_gpu_csr)
accuracy_gpu = (preds_gpu.get() == y_test_gpu.get()).mean()
print("GPU-based Naive Bayes Accuracy:", accuracy_gpu)
Study Guide Format (Condensed)
I. Introduction - Bayes’ Rule, Prior, Likelihood, Posterior
- Connection to logistic regression
- Mathematical Foundations
- P(A|B) = (P(B|A)*P(A))/P(B)
- Multivariate Extension
- Continuous Variables
- Practical Implementation
- Text Classification
- Bag-of-Words & TF-IDF
- Naive Bayes (MultinomialNB)
- Comparison
- Naive Bayes vs. Logistic Regression
- Generative vs. Discriminative
V. Key Takeaways 1. Naive Bayes uses Bayes’ Rule with a simplifying
(naive) independence assumption, yet often performs competitively in
classification tasks, especially text.
2. Combining TF-IDF with MultinomialNB frequently yields better accuracy
than bag-of-words.
3. Generative (Naive Bayes) and Discriminative (Logistic Regression)
models are mathematically related through the logit (odds ratio)
framework.
- Discussion Questions
- Why does Naive Bayes often perform well in high-dimensional text
classification, despite the naive independence assumption?
- How do smoothing parameters (like alpha) influence Naive Bayes
performance?
- In what cases might Logistic Regression eventually outperform Naive
Bayes, and why?
Below is a more detailed walkthrough of Part 6 – The Bag of
Words Exercise, showing how to build a custom CountVectorizer
and comparing it with sklearn’s version. This will help solidify your
understanding of how text is converted into numerical features for Naive
Bayes and other machine learning models.
Bag of Words Exercise – Step-by-Step
Understanding the Goal We want to: • Load the 20 Newsgroups
dataset (without headers, footers, and quotes).
• Create our own Bag-of-Words approach (basic
CountVectorizer).
• Compare against the sklearn implementation
(CountVectorizer).
Loading the 20 Newsgroups Dataset The dataset can be fetched
with: data = fetch_20newsgroups(remove=(‘headers’, ‘footers’,
‘quotes’))
This returns a dictionary-like object, including .data (list of
documents) and .target (list of numeric labels).
- Building a Custom CountVectorizer Our custom version will: •
Tokenize each document into words (split on whitespace, remove
punctuation).
• Build a vocabulary (unique words mapped to indices).
• Count how often each word appears in each document.
Below is sample code to illustrate these steps.
import re
from collections import Counter
from sklearn.datasets import fetch_20newsgroups
def custom_count_vectorizer(corpus):
# 1. Clean and lowercase text, remove punctuation
cleaned_texts = [re.sub(r'[^a-zA-Z\s]', '', doc.lower()) for doc in corpus]
# 2. Tokenize by splitting on whitespace
tokenized_docs = [doc.split() for doc in cleaned_texts]
# 3. Build vocabulary (all unique words)
# Convert to a sorted list to ensure consistent ordering.
vocabulary = sorted(set(word for doc in tokenized_docs for word in doc))
# 4. Create a dict mapping each word to its index
word2index = {word: idx for idx, word in enumerate(vocabulary)}
# 5. Construct the count matrix
# Each row represents a document, each column a word in our vocabulary.
count_matrix = []
for doc in tokenized_docs:
counts = Counter(doc)
# Build a row of length = size of vocabulary
row_vector = [counts.get(word, 0) for word in vocabulary]
count_matrix.append(row_vector)
return vocabulary, count_matrix
# 1) Fetch data
data = fetch_20newsgroups(remove=('headers', 'footers', 'quotes'))
corpus = data.data[:5] # Using first 5 docs as a small example
# 2) Apply our custom CountVectorizer
vocab, matrix = custom_count_vectorizer(corpus)
print("Custom Vocabulary (first 20 words):", vocab[:20])
print("\nFirst Document's Vector Representation:")
matrix[0])
- Comparing with sklearn’s CountVectorizer Now let’s use the sklearn
version to see how it differs.
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(stop_words='english') # optionally remove stopwords
X = cv.fit_transform(corpus)
print("\nSklearn Vocabulary Size:", len(cv.vocabulary_))
print("Sklearn First Document's Vector:")
print(X.toarray()[0])
Differences You Might Notice • Stopword Removal: By
default, sklearn’s CountVectorizer can remove common words like “the,”
“and,” etc., if you specify stop_words='english'
. Our
custom version didn’t remove them unless we code it.
• Punctuation Handling: sklearn automatically handles
punctuation in multiple ways. Our custom version uses a simple
regex.
• Sparse Representation: sklearn returns a sparse
matrix (efficient for large text). Ours is a plain list of lists (dense
matrix).
- (Optional) GPU-Accelerated Version If you have the RAPIDS
environment set up (with CuPy and cuML), you could adapt your code to
build counts on the GPU. However, for a “homegrown” solution,
you’d:
import cupy as cp
def gpu_count_vectorizer(corpus):
# Step 1: Preprocess & Tokenize on CPU (or GPU if you prefer)
cleaned_texts = [re.sub(r'[^a-zA-Z\s]', '', doc.lower()) for doc in corpus]
tokenized_docs = [doc.split() for doc in cleaned_texts]
# Step 2: Build vocabulary on CPU for simplicity
vocabulary = sorted(set(word for doc in tokenized_docs for word in doc))
word2index = {word: idx for idx, word in enumerate(vocabulary)}
# Step 3: Initialize a GPU array to hold counts
rows, cols = len(tokenized_docs), len(vocabulary)
gpu_matrix = cp.zeros((rows, cols), dtype=cp.int32)
# Step 4: Fill in the matrix
for i, doc in enumerate(tokenized_docs):
doc_counts = Counter(doc)
for word, count in doc_counts.items():
if word in word2index:
j = word2index[word]
gpu_matrix[i, j] = count
return vocabulary, gpu_matrix
# Example usage
vocab_gpu, matrix_gpu = gpu_count_vectorizer(corpus[:5])
print("GPU Count Matrix shape:", matrix_gpu.shape)
print("GPU Count Matrix (first row, back to CPU):", matrix_gpu[0].get())
Note: This example shows the concept. In practice, you’d want to
handle more steps on GPU to maximize speed.
Summary of the Exercise • Objective: Implement a
simplified Bag-of-Words manually.
• Key Observation: The core steps – tokenizing,
building a vocabulary, counting words – are straightforward, but sklearn
optimizes them for performance and adds many extra features (stopword
removal, n-grams, etc.).
Three Key Takeaways (Part 6 Focus) 1. Implementation
Control: Building CountVectorizer from scratch clarifies how
text preprocessing works under the hood, but libraries like sklearn are
more flexible and optimized.
2. Vocabulary Size: Real-world text data sets can
contain tens or hundreds of thousands of unique tokens; efficient data
structures (sparse matrices) are critical.
3. Preprocessing Matters: Removing stopwords, handling
punctuation, or applying stemming/lemmatization can significantly change
your results.
Three Discussion Questions 1. How would you modify your
custom CountVectorizer to remove stopwords, or to handle n-grams (like
two-word phrases)?
2. What might be the tradeoffs between using a dense vs. sparse
matrix representation for large corpora?
3. How could we extend this approach for advanced text
processing, such as adding TF-IDF or word embeddings (GloVe,
Word2Vec)?
LS0tDQp0aXRsZTogIjczMzMgTW9kdWxlIDYgLSBOYWl2ZSBCYXllcyINCmF1dGhvcjogIkplc3NpY2EgTWNQaGF1bCAtIGZvciBRVFcgU3ByaW5nIDIwMjUuIERyLiBTbGF0ZXIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCi0tLQ0KDQojICoqU3R1ZHkgR3VpZGU6IFFUVyBNb2R1bGUgNiAtIE5haXZlIEJheWVzKioNCg0KIyMgKioxLiBCYXllcycgUnVsZTogT3ZlcnZpZXcgYW5kIE1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbioqDQpCYXllcycgUnVsZSBpcyBhIGZ1bmRhbWVudGFsIHRoZW9yZW0gaW4gcHJvYmFiaWxpdHkgdGhhdCBoZWxwcyB1cGRhdGUgcHJpb3IgYmVsaWVmcyBiYXNlZCBvbiBuZXcgZXZpZGVuY2UuIEl0IGlzIHRoZSBiYWNrYm9uZSBvZiBCYXllc2lhbiBpbmZlcmVuY2UgYW5kIHBsYXlzIGEgY3J1Y2lhbCByb2xlIGluIHByb2JhYmlsaXN0aWMgbW9kZWxpbmcuDQoNCiMjIyAqKk1hdGhlbWF0aWNhbCBGb3JtdWxhKioNClxbDQpQKEEgfCBCKSA9IFxmcmFje1AoQiB8IEEpIFAoQSl9e1AoQil9DQpcXQ0KV2hlcmU6DQotIFwoIFAoQSB8IEIpIFwpID0gKipQb3N0ZXJpb3IgUHJvYmFiaWxpdHkqKiAodXBkYXRlZCBiZWxpZWYgYWZ0ZXIgc2VlaW5nIGV2aWRlbmNlKQ0KLSBcKCBQKEIgfCBBKSBcKSA9ICoqTGlrZWxpaG9vZCoqIChob3cgbGlrZWx5IEIgb2NjdXJzIGdpdmVuIEEgaXMgdHJ1ZSkNCi0gXCggUChBKSBcKSA9ICoqUHJpb3IgUHJvYmFiaWxpdHkqKiAoaW5pdGlhbCBiZWxpZWYgYmVmb3JlIHNlZWluZyBldmlkZW5jZSkNCi0gXCggUChCKSBcKSA9ICoqTm9ybWFsaXphdGlvbiBGYWN0b3IqKiAoZW5zdXJlcyB2YWxpZCBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24pDQoNCiMjIyMgKipFeGFtcGxlOiBEcnVnIFRlc3RpbmcqKg0KR2l2ZW46DQotICoqVHJ1ZSBwb3NpdGl2ZSByYXRlKiogPSA5OSUgKFwoIFAoQiB8IEEpID0gMC45OSBcKSkNCi0gKipGYWxzZSBwb3NpdGl2ZSByYXRlKiogPSAzJSAoXCggUChCIHwgXG5lZyBBKSA9IDAuMDMgXCkpDQotICoqUHJldmFsZW5jZSBvZiBkcnVnIHVzZSoqID0gNSUgKFwoIFAoQSkgPSAwLjA1IFwpKQ0KDQpUaGUgcHJvYmFiaWxpdHkgdGhhdCBhIHBlcnNvbiB3aG8gdGVzdGVkIHBvc2l0aXZlIGlzIGFjdHVhbGx5IGEgZHJ1ZyB1c2VyIGlzOg0KXFsNClAoQSB8IEIpID0gXGZyYWN7KDAuOTkgXHRpbWVzIDAuMDUpfXsoMC45OSBcdGltZXMgMC4wNSkgKyAoMC4wMyBcdGltZXMgMC45NSl9DQpcXQ0KDQpVc2luZyBQeXRob246DQoNCmBgYHB5dGhvbg0KUF9BID0gMC4wNSAgIyBQcmlvciBwcm9iYWJpbGl0eSBvZiBiZWluZyBhIGRydWcgdXNlcg0KUF9CX2dpdmVuX0EgPSAwLjk5ICAjIFRydWUgcG9zaXRpdmUgcmF0ZQ0KUF9CX2dpdmVuX25vdF9BID0gMC4wMyAgIyBGYWxzZSBwb3NpdGl2ZSByYXRlDQpQX25vdF9BID0gMSAtIFBfQQ0KDQpQX0IgPSAoUF9CX2dpdmVuX0EgKiBQX0EpICsgKFBfQl9naXZlbl9ub3RfQSAqIFBfbm90X0EpICAjIE5vcm1hbGl6YXRpb24gZmFjdG9yDQpQX0FfZ2l2ZW5fQiA9IChQX0JfZ2l2ZW5fQSAqIFBfQSkgLyBQX0IgICMgQmF5ZXMnIFJ1bGUNCg0KcHJpbnQoZiJQcm9iYWJpbGl0eSBvZiBiZWluZyBhIGRydWcgdXNlciBnaXZlbiBhIHBvc2l0aXZlIHRlc3Q6IHtQX0FfZ2l2ZW5fQjouM2Z9IikNCmBgYA0KDQotLS0NCg0KIyMgKioyLiBCYXllcycgUnVsZSBmb3IgTXVsdGl2YXJpYWJsZXMqKg0KV2hlbiB0aGVyZSBhcmUgbXVsdGlwbGUgZXZpZGVuY2UgdmFyaWFibGVzIFwoIEIsIEMsIEQsIC4uLiBcKSwgd2UgZ2VuZXJhbGl6ZSBCYXllcycgUnVsZToNClxbDQpQKEEgfCBCLCBDLCBEKSA9IFxmcmFje1AoQiwgQywgRCB8IEEpIFAoQSl9e1AoQiwgQywgRCl9DQpcXQ0KQnkgYXNzdW1pbmcgY29uZGl0aW9uYWwgaW5kZXBlbmRlbmNlIChuYWl2ZSBhc3N1bXB0aW9uKSwgd2Ugc2ltcGxpZnk6DQpcWw0KUChBIHwgQiwgQywgRCkgXHByb3B0byBQKEEpIFAoQiB8IEEpIFAoQyB8IEEpIFAoRCB8IEEpDQpcXQ0KDQpUaGlzIGZvcm1zIHRoZSBmb3VuZGF0aW9uIG9mICoqTmFpdmUgQmF5ZXMgQ2xhc3NpZmljYXRpb24qKi4NCg0KIyMjIyAqKlB5dGhvbiBFeGFtcGxlIGZvciBUZXh0IENsYXNzaWZpY2F0aW9uKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBNdWx0aW5vbWlhbE5CDQpmcm9tIHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHQgaW1wb3J0IENvdW50VmVjdG9yaXplcg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBmZXRjaF8yMG5ld3Nncm91cHMNCg0KIyBMb2FkIGRhdGFzZXQNCm5ld3Nncm91cHMgPSBmZXRjaF8yMG5ld3Nncm91cHMoc3Vic2V0PSd0cmFpbicsIHJlbW92ZT0oJ2hlYWRlcnMnLCAnZm9vdGVycycsICdxdW90ZXMnKSkNCnZlY3Rvcml6ZXIgPSBDb3VudFZlY3Rvcml6ZXIoKQ0KWCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShuZXdzZ3JvdXBzLmRhdGEpICAjIENvbnZlcnQgdGV4dCB0byBiYWctb2Ytd29yZHMNCnkgPSBuZXdzZ3JvdXBzLnRhcmdldA0KDQojIFRyYWluIE5haXZlIEJheWVzIENsYXNzaWZpZXINCm5iID0gTXVsdGlub21pYWxOQigpDQpuYi5maXQoWCwgeSkNCg0KIyBQcmVkaWN0IG9uIG5ldyBkYXRhDQpzYW1wbGVfdGV4dCA9IFsiUXVhbnR1bSBjb21wdXRpbmcgaXMgdGhlIGZ1dHVyZSBvZiBBSSJdDQpYX3NhbXBsZSA9IHZlY3Rvcml6ZXIudHJhbnNmb3JtKHNhbXBsZV90ZXh0KQ0KcHJlZGljdGlvbiA9IG5iLnByZWRpY3QoWF9zYW1wbGUpDQpwcmludChmIlByZWRpY3RlZCBjYXRlZ29yeToge25ld3Nncm91cHMudGFyZ2V0X25hbWVzW3ByZWRpY3Rpb25bMF1dfSIpDQpgYGANCg0KLS0tDQoNCiMjICoqMy4gQmF5ZXMnIFJ1bGUgZm9yIENvbnRpbnVvdXMgVmFyaWFibGVzKioNCkZvciBjb250aW51b3VzIHZhcmlhYmxlcyAoZS5nLiwgdGVtcGVyYXR1cmUsIHdlaWdodCksIHdlIHVzZSAqKlByb2JhYmlsaXR5IERlbnNpdHkgRnVuY3Rpb25zIChQREZzKSoqIHN1Y2ggYXMgdGhlICoqR2F1c3NpYW4gRGlzdHJpYnV0aW9uKio6DQpcWw0KUChYID0geCkgPSBcZnJhY3sxfXtcc2lnbWEgXHNxcnR7MlxwaX19IGVeey1cZnJhY3soeCAtIFxtdSleMn17MlxzaWdtYV4yfX0NClxdDQp3aGVyZSBcKCBcbXUgXCkgaXMgdGhlIG1lYW4gYW5kIFwoIFxzaWdtYSBcKSBpcyB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLg0KDQojIyMjICoqUHl0aG9uIEltcGxlbWVudGF0aW9uKioNCmBgYHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQpmcm9tIHNjaXB5LnN0YXRzIGltcG9ydCBub3JtDQoNCiMgQXNzdW1lIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuPTcwLCBzdGQ9MTANCm11LCBzaWdtYSA9IDcwLCAxMA0KeF92YWx1ZSA9IDc1DQpwcm9iYWJpbGl0eSA9IG5vcm0ucGRmKHhfdmFsdWUsIG11LCBzaWdtYSkNCg0KcHJpbnQoZiJQcm9iYWJpbGl0eSBkZW5zaXR5IG9mIHdlaWdodCA3NSBnaXZlbiBtZWFuPTcwIGFuZCBzdGQ9MTA6IHtwcm9iYWJpbGl0eTouNGZ9IikNCmBgYA0KDQotLS0NCg0KIyMgKio0LiBOYWl2ZSBCYXllcyBJbXBsZW1lbnRhdGlvbiBmb3IgVGV4dCBDbGFzc2lmaWNhdGlvbioqDQpVc2luZyAqKkNvdW50VmVjdG9yaXplcioqIGFuZCAqKlRGLUlERioqLCB3ZSB0cmFpbiBhICoqTXVsdGlub21pYWwgTmFpdmUgQmF5ZXMgbW9kZWwqKi4NCg0KIyMjIyAqKlB5dGhvbiBJbXBsZW1lbnRhdGlvbioqDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgVGZpZGZWZWN0b3JpemVyDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCg0KIyBDb252ZXJ0IGRhdGFzZXQgdXNpbmcgVEYtSURGDQp0ZmlkZl92ZWN0b3JpemVyID0gVGZpZGZWZWN0b3JpemVyKCkNClhfdGZpZGYgPSB0ZmlkZl92ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0obmV3c2dyb3Vwcy5kYXRhKQ0KDQojIFRyYWluIE5haXZlIEJheWVzIHdpdGggVEYtSURGDQpuYl90ZmlkZiA9IE11bHRpbm9taWFsTkIoKQ0KbmJfdGZpZGYuZml0KFhfdGZpZGYsIHkpDQoNCiMgUHJlZGljdCBvbiB0ZXN0IGRhdGENCm5ld3Nncm91cHNfdGVzdCA9IGZldGNoXzIwbmV3c2dyb3VwcyhzdWJzZXQ9J3Rlc3QnLCByZW1vdmU9KCdoZWFkZXJzJywgJ2Zvb3RlcnMnLCAncXVvdGVzJykpDQpYX3Rlc3RfdGZpZGYgPSB0ZmlkZl92ZWN0b3JpemVyLnRyYW5zZm9ybShuZXdzZ3JvdXBzX3Rlc3QuZGF0YSkNCnlfdGVzdCA9IG5ld3Nncm91cHNfdGVzdC50YXJnZXQNCg0KcHJlZGljdGlvbnMgPSBuYl90ZmlkZi5wcmVkaWN0KFhfdGVzdF90ZmlkZikNCmFjY3VyYWN5ID0gYWNjdXJhY3lfc2NvcmUoeV90ZXN0LCBwcmVkaWN0aW9ucykNCnByaW50KGYiTmFpdmUgQmF5ZXMgVEYtSURGIEFjY3VyYWN5OiB7YWNjdXJhY3k6LjJmfSIpDQpgYGANCg0KLS0tDQoNCiMjICoqNS4gQ1BVIHZzLiBHUFUgSW1wbGVtZW50YXRpb24qKg0KIyMjICoqQ1BVIEltcGxlbWVudGF0aW9uKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBHYXVzc2lhbk5CDQoNCiMgU2ltdWxhdGluZyBkYXRhDQpYX2NwdSA9IG5wLnJhbmRvbS5yYW5kKDEwMDAwLCAxMCkNCnlfY3B1ID0gbnAucmFuZG9tLnJhbmRpbnQoMiwgc2l6ZT0xMDAwMCkNCg0KIyBUcmFpbiBHYXVzc2lhbiBOYWl2ZSBCYXllcw0KZ25iID0gR2F1c3NpYW5OQigpDQpnbmIuZml0KFhfY3B1LCB5X2NwdSkNCmBgYA0KDQojIyMgKipHUFUgSW1wbGVtZW50YXRpb24gd2l0aCBDdVB5KioNCmBgYHB5dGhvbg0KaW1wb3J0IGN1cHkgYXMgY3ANCmZyb20gY3VtbC5uYWl2ZV9iYXllcyBpbXBvcnQgR2F1c3NpYW5OQiBhcyBjdUdhdXNzaWFuTkINCg0KIyBDb252ZXJ0IGRhdGEgdG8gR1BVDQpYX2dwdSA9IGNwLmFzYXJyYXkoWF9jcHUpDQp5X2dwdSA9IGNwLmFzYXJyYXkoeV9jcHUpDQoNCiMgVHJhaW4gb24gR1BVDQpnbmJfZ3B1ID0gY3VHYXVzc2lhbk5CKCkNCmduYl9ncHUuZml0KFhfZ3B1LCB5X2dwdSkNCmBgYA0KDQotLS0NCg0KIyMgKio2LiBCYWcgb2YgV29yZHMgRXhlcmNpc2UqKg0KQnVpbGQgYSAqKkNvdW50VmVjdG9yaXplcioqIGZyb20gc2NyYXRjaCBhbmQgY29tcGFyZSB3aXRoIGBza2xlYXJuYC4NCg0KIyMjICoqTWFudWFsIENvdW50IFZlY3Rvcml6ZXIqKg0KYGBgcHl0aG9uDQpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBDb3VudGVyDQoNCmRlZiBjb3VudF92ZWN0b3JpemVyKGNvcnB1cyk6DQogICAgdm9jYWIgPSBzZXQod29yZCBmb3IgdGV4dCBpbiBjb3JwdXMgZm9yIHdvcmQgaW4gdGV4dC5zcGxpdCgpKQ0KICAgIHZlY3Rvcml6ZWQgPSBbe3dvcmQ6IHRleHQuc3BsaXQoKS5jb3VudCh3b3JkKSBmb3Igd29yZCBpbiB2b2NhYn0gZm9yIHRleHQgaW4gY29ycHVzXQ0KICAgIHJldHVybiB2ZWN0b3JpemVkLCBsaXN0KHZvY2FiKQ0KDQojIFNhbXBsZSBkYXRhDQpjb3JwdXMgPSBbIk5haXZlIEJheWVzIGlzIHNpbXBsZSIsICJCYXllcyBtb2RlbHMgYXJlIHBvd2VyZnVsIl0NCnZlY3Rvcml6ZWRfZGF0YSwgdm9jYWIgPSBjb3VudF92ZWN0b3JpemVyKGNvcnB1cykNCg0KcHJpbnQoZiJWb2NhYnVsYXJ5OiB7dm9jYWJ9IikNCnByaW50KGYiVmVjdG9yaXplZCBEYXRhOiB7dmVjdG9yaXplZF9kYXRhfSIpDQpgYGANCg0KKipDb21wYXJpc29uIHdpdGggU2Npa2l0LWxlYXJuKioNCmBgYHB5dGhvbg0KY3YgPSBDb3VudFZlY3Rvcml6ZXIoKQ0KWF9jdiA9IGN2LmZpdF90cmFuc2Zvcm0oY29ycHVzKQ0KcHJpbnQoZiJTa2xlYXJuIENvdW50VmVjdG9yaXplciBPdXRwdXQ6XG57WF9jdi50b2FycmF5KCl9IikNCmBgYA0KDQotLS0NCg0KIyAqKktleSBUYWtlYXdheXMqKg0KMS4gKipCYXllcycgUnVsZSBpcyBhIHBvd2VyZnVsIHByb2JhYmlsaXN0aWMgdG9vbCoqIGZvciB1cGRhdGluZyBiZWxpZWZzIGJhc2VkIG9uIGV2aWRlbmNlLg0KMi4gKipOYWl2ZSBCYXllcyBhc3N1bWVzIGZlYXR1cmUgaW5kZXBlbmRlbmNlKiogYnV0IHN0aWxsIHBlcmZvcm1zIHdlbGwgaW4gbWFueSBjbGFzc2lmaWNhdGlvbiB0YXNrcy4NCjMuICoqSXQgaXMgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudCoqIGFuZCB3ZWxsLXN1aXRlZCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbiwgc3BhbSBkZXRlY3Rpb24sIGFuZCBtZWRpY2FsIGRpYWdub3N0aWNzLg0KDQotLS0NCg0KIyAqKkRpc2N1c3Npb24gUXVlc3Rpb25zKioNCjEuICoqV2hhdCBhcmUgdGhlIGxpbWl0YXRpb25zIG9mIHRoZSBuYWl2ZSBhc3N1bXB0aW9uIGluIE5haXZlIEJheWVzPyoqDQoyLiAqKldoeSBkb2VzIE5haXZlIEJheWVzIHBlcmZvcm0gd2VsbCBpbiB0ZXh0IGNsYXNzaWZpY2F0aW9uIGRlc3BpdGUgZmVhdHVyZSBpbmRlcGVuZGVuY2U/KioNCjMuICoqSG93IGNhbiB3ZSBpbXByb3ZlIE5haXZlIEJheWVzIGZvciBjb250aW51b3VzIGRhdGEgZGlzdHJpYnV0aW9ucz8qKg0KDQotLS0NCg0KIyMjICoqQmFnIG9mIFdvcmRzIEV4ZXJjaXNlOiBTdGVwLWJ5LVN0ZXAgV2Fsa3Rocm91Z2gqKg0KDQpUaGlzIGV4ZXJjaXNlIGludm9sdmVzIG1hbnVhbGx5IGltcGxlbWVudGluZyAqKkNvdW50VmVjdG9yaXplcioqLCB3aGljaCBpcyBhIGZ1bmRhbWVudGFsIHByZXByb2Nlc3Npbmcgc3RlcCBpbiBOYXR1cmFsIExhbmd1YWdlIFByb2Nlc3NpbmcgKE5MUCkuIFRoZSBnb2FsIGlzIHRvICoqY29udmVydCB0ZXh0IGludG8gYSBudW1lcmljYWwgcmVwcmVzZW50YXRpb24qKiBiZWZvcmUgYXBwbHlpbmcgbWFjaGluZSBsZWFybmluZyBtb2RlbHMuDQoNCi0tLQ0KDQojIyAqKlN0ZXAgMTogVW5kZXJzdGFuZGluZyBDb3VudFZlY3Rvcml6ZXIqKg0KYENvdW50VmVjdG9yaXplcmAgaXMgYSBtZXRob2Qgb2YgY29udmVydGluZyB0ZXh0IGludG8gYSAqKmJhZy1vZi13b3JkcyAoQm9XKSoqIHJlcHJlc2VudGF0aW9uLCB3aGVyZToNCi0gRWFjaCB1bmlxdWUgd29yZCBpbiB0aGUgZGF0YXNldCBpcyBhc3NpZ25lZCBhbiBpbmRleCBpbiBhIHZvY2FidWxhcnkuDQotIFRoZSB0ZXh0IGRhdGEgaXMgdHJhbnNmb3JtZWQgaW50byBhIG1hdHJpeCwgd2hlcmUgZWFjaCByb3cgcmVwcmVzZW50cyBhIGRvY3VtZW50IGFuZCBlYWNoIGNvbHVtbiByZXByZXNlbnRzIGEgd29yZC4NCi0gVGhlIHZhbHVlcyBpbiB0aGUgbWF0cml4IGluZGljYXRlIGhvdyBtYW55IHRpbWVzIGEgd29yZCBhcHBlYXJzIGluIGEgZG9jdW1lbnQuDQoNCiMjIyAqKkV4YW1wbGUqKg0KRm9yIHRoZSB0ZXh0Og0KYGBgdGV4dA0KIk5haXZlIEJheWVzIGlzIHNpbXBsZSINCiJCYXllcyBtb2RlbHMgYXJlIHBvd2VyZnVsIg0KYGBgDQpUaGUgKipiYWctb2Ytd29yZHMgcmVwcmVzZW50YXRpb24qKiB3b3VsZCBjb3VudCB0aGUgb2NjdXJyZW5jZXMgb2YgZWFjaCB3b3JkIGFjcm9zcyBkb2N1bWVudHMuDQoNCi0tLQ0KDQojIyAqKlN0ZXAgMjogTG9hZGluZyB0aGUgMjAgTmV3c2dyb3VwIERhdGFzZXQqKg0KVGhlICoqMjAgTmV3c2dyb3VwcyBkYXRhc2V0KiogaXMgYSBjb2xsZWN0aW9uIG9mIHRleHQgZG9jdW1lbnRzIGNhdGVnb3JpemVkIGludG8gMjAgdG9waWNzLg0KDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgZmV0Y2hfMjBuZXdzZ3JvdXBzDQoNCiMgTG9hZCBkYXRhc2V0IHdpdGhvdXQgaGVhZGVycywgZm9vdGVycywgYW5kIHF1b3Rlcw0KZGF0YSA9IGZldGNoXzIwbmV3c2dyb3VwcyhyZW1vdmU9KCdoZWFkZXJzJywgJ2Zvb3RlcnMnLCAncXVvdGVzJykpDQpkb2N1bWVudHMgPSBkYXRhLmRhdGFbOjVdICAjIExvYWQgb25seSBmaXJzdCA1IGRvY3VtZW50cyBmb3IgZGVtb25zdHJhdGlvbg0KDQojIERpc3BsYXkgc2FtcGxlIGRhdGENCmZvciBpLCBkb2MgaW4gZW51bWVyYXRlKGRvY3VtZW50cyk6DQogICAgcHJpbnQoZiJEb2N1bWVudCB7aSsxfTpcbntkb2N9XG57Jy0nKjUwfSIpDQpgYGANClRoaXMgZGF0YXNldCBjb250YWlucyAqKnJhdyB0ZXh0KiogdGhhdCBuZWVkcyB0byBiZSBwcm9jZXNzZWQgaW50byBhIG51bWVyaWNhbCBmb3JtYXQuDQoNCi0tLQ0KDQojIyAqKlN0ZXAgMzogSW1wbGVtZW50aW5nIE1hbnVhbCBDb3VudFZlY3Rvcml6ZXIqKg0KIyMjICoqMS4gVG9rZW5pemF0aW9uKioNCkNvbnZlcnQgdGV4dCBpbnRvIHdvcmRzIGJ5IHNwbGl0dGluZyBvbiBzcGFjZXMgYW5kIHJlbW92aW5nIHB1bmN0dWF0aW9uLg0KDQojIyMgKioyLiBCdWlsZGluZyBWb2NhYnVsYXJ5KioNCkNyZWF0ZSBhIGRpY3Rpb25hcnkgbWFwcGluZyBlYWNoIHVuaXF1ZSB3b3JkIHRvIGFuIGluZGV4Lg0KDQojIyMgKiozLiBDb25zdHJ1Y3RpbmcgdGhlIENvdW50IE1hdHJpeCoqDQpGb3IgZWFjaCBkb2N1bWVudCwgY291bnQgaG93IG1hbnkgdGltZXMgZWFjaCB3b3JkIGFwcGVhcnMuDQoNCmBgYHB5dGhvbg0KZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcg0KaW1wb3J0IHJlDQoNCmRlZiBjdXN0b21fY291bnRfdmVjdG9yaXplcihjb3JwdXMpOg0KICAgICMgUHJlcHJvY2VzczogUmVtb3ZlIHB1bmN0dWF0aW9uIGFuZCBsb3dlcmNhc2UNCiAgICBjb3JwdXNfY2xlYW4gPSBbcmUuc3ViKHInXFcrJywgJyAnLCBkb2MpLmxvd2VyKCkgZm9yIGRvYyBpbiBjb3JwdXNdDQoNCiAgICAjIFRva2VuaXplDQogICAgdG9rZW5pemVkX2RvY3MgPSBbZG9jLnNwbGl0KCkgZm9yIGRvYyBpbiBjb3JwdXNfY2xlYW5dDQoNCiAgICAjIEJ1aWxkIHZvY2FidWxhcnkNCiAgICB2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCh3b3JkIGZvciBkb2MgaW4gdG9rZW5pemVkX2RvY3MgZm9yIHdvcmQgaW4gZG9jKSkNCiAgICB3b3JkX2luZGV4ID0ge3dvcmQ6IGlkeCBmb3IgaWR4LCB3b3JkIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCg0KICAgICMgQ291bnQgd29yZCBvY2N1cnJlbmNlcw0KICAgIG1hdHJpeCA9IFtdDQogICAgZm9yIGRvYyBpbiB0b2tlbml6ZWRfZG9jczoNCiAgICAgICAgd29yZF9jb3VudHMgPSBDb3VudGVyKGRvYykNCiAgICAgICAgcm93ID0gW3dvcmRfY291bnRzLmdldCh3b3JkLCAwKSBmb3Igd29yZCBpbiB2b2NhYnVsYXJ5XQ0KICAgICAgICBtYXRyaXguYXBwZW5kKHJvdykNCg0KICAgIHJldHVybiB2b2NhYnVsYXJ5LCBtYXRyaXgNCg0KIyBBcHBseSBjdXN0b20gQ291bnRWZWN0b3JpemVyDQp2b2NhYiwgY291bnRfbWF0cml4ID0gY3VzdG9tX2NvdW50X3ZlY3Rvcml6ZXIoZG9jdW1lbnRzWzo1XSkNCg0KIyBEaXNwbGF5IHJlc3VsdHMNCnByaW50KCJWb2NhYnVsYXJ5OlxuIiwgdm9jYWIpDQpwcmludCgiXG5Db3VudCBNYXRyaXg6XG4iLCBjb3VudF9tYXRyaXgpDQpgYGANCg0KLS0tDQoNCiMjICoqU3RlcCA0OiBDb21wYXJpbmcgd2l0aCBTa2xlYXJuJ3MgQ291bnRWZWN0b3JpemVyKioNClRvIHZhbGlkYXRlIG91ciBpbXBsZW1lbnRhdGlvbiwgd2UgY29tcGFyZSBpdCBhZ2FpbnN0IFNjaWtpdC1sZWFybidzIGBDb3VudFZlY3Rvcml6ZXJgLg0KDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyDQoNCiMgQXBwbHkgU2Npa2l0LWxlYXJuJ3MgQ291bnRWZWN0b3JpemVyDQp2ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKCkNClggPSB2ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzWzo1XSkNCg0KIyBEaXNwbGF5IHZvY2FidWxhcnkNCnByaW50KCJcblNjaWtpdC1sZWFybiBWb2NhYnVsYXJ5OlxuIiwgdmVjdG9yaXplci5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkNCg0KIyBEaXNwbGF5IHRyYW5zZm9ybWVkIHRleHQgcmVwcmVzZW50YXRpb24NCnByaW50KCJcblNjaWtpdC1sZWFybiBDb3VudCBNYXRyaXg6XG4iLCBYLnRvYXJyYXkoKSkNCmBgYA0KDQotLS0NCg0KIyMgKipTdGVwIDU6IEtleSBEaWZmZXJlbmNlcyBCZXR3ZWVuIE1hbnVhbCBhbmQgU2tsZWFybiBDb3VudFZlY3Rvcml6ZXIqKg0KfCBGZWF0dXJlIHwgQ3VzdG9tIEltcGxlbWVudGF0aW9uIHwgU2tsZWFybiBDb3VudFZlY3Rvcml6ZXIgfA0KfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKlRva2VuaXphdGlvbioqIHwgQmFzaWMgc3BhY2Utc3BsaXR0aW5nIGFuZCByZWdleCB8IEhhbmRsZXMgc3BlY2lhbCBjYXNlcyAoc3RvcHdvcmRzLCBuLWdyYW1zLCBldGMuKSB8DQp8ICoqVm9jYWJ1bGFyeSoqIHwgQnVpbHQgbWFudWFsbHkgdXNpbmcgYSBzZXQgfCBBdXRvbWF0aWNhbGx5IG9wdGltaXplZCB8DQp8ICoqQ291bnQgTWF0cml4KiogfCBDcmVhdGVkIHdpdGggYENvdW50ZXJgIHwgT3B0aW1pemVkIHNwYXJzZSBtYXRyaXggfA0KfCAqKlNwZWVkKiogfCBTbG93ZXIgZm9yIGxhcmdlIGRhdGFzZXRzIHwgSGlnaGx5IG9wdGltaXplZCBDKysgYmFja2VuZCB8DQoNCi0tLQ0KDQojIyAqKlN0ZXAgNjogUGVyZm9ybWFuY2UgQ29uc2lkZXJhdGlvbnMqKg0KU2luY2UgYENvdW50VmVjdG9yaXplcmAgaXMgYSAqKkNQVS1iYXNlZCBtZXRob2QqKiwgbGV04oCZcyBvcHRpbWl6ZSBpdCBmb3IgYSAqKkdQVSoqIGltcGxlbWVudGF0aW9uIHVzaW5nICoqQ3VQeSoqLg0KDQpgYGBweXRob24NCmltcG9ydCBjdXB5IGFzIGNwDQoNCmRlZiBncHVfY291bnRfdmVjdG9yaXplcihjb3JwdXMpOg0KICAgIGNvcnB1c19jbGVhbiA9IFtyZS5zdWIocidcVysnLCAnICcsIGRvYykubG93ZXIoKSBmb3IgZG9jIGluIGNvcnB1c10NCiAgICB0b2tlbml6ZWRfZG9jcyA9IFtkb2Muc3BsaXQoKSBmb3IgZG9jIGluIGNvcnB1c19jbGVhbl0NCiAgICANCiAgICB2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCh3b3JkIGZvciBkb2MgaW4gdG9rZW5pemVkX2RvY3MgZm9yIHdvcmQgaW4gZG9jKSkNCiAgICB3b3JkX2luZGV4ID0ge3dvcmQ6IGlkeCBmb3IgaWR4LCB3b3JkIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCiAgICANCiAgICBtYXRyaXggPSBjcC56ZXJvcygobGVuKHRva2VuaXplZF9kb2NzKSwgbGVuKHZvY2FidWxhcnkpKSwgZHR5cGU9Y3AuaW50MzIpDQogICAgDQogICAgZm9yIGRvY19pZHgsIGRvYyBpbiBlbnVtZXJhdGUodG9rZW5pemVkX2RvY3MpOg0KICAgICAgICB3b3JkX2NvdW50cyA9IENvdW50ZXIoZG9jKQ0KICAgICAgICBmb3Igd29yZCwgY291bnQgaW4gd29yZF9jb3VudHMuaXRlbXMoKToNCiAgICAgICAgICAgIGlmIHdvcmQgaW4gd29yZF9pbmRleDoNCiAgICAgICAgICAgICAgICBtYXRyaXhbZG9jX2lkeCwgd29yZF9pbmRleFt3b3JkXV0gPSBjb3VudA0KICAgIA0KICAgIHJldHVybiB2b2NhYnVsYXJ5LCBtYXRyaXgNCg0KIyBBcHBseSBHUFUgQ291bnRWZWN0b3JpemVyDQpncHVfdm9jYWIsIGdwdV9jb3VudF9tYXRyaXggPSBncHVfY291bnRfdmVjdG9yaXplcihkb2N1bWVudHNbOjVdKQ0KDQpwcmludCgiXG5HUFUgVm9jYWJ1bGFyeToiLCBncHVfdm9jYWIpDQpwcmludCgiXG5HUFUgQ291bnQgTWF0cml4OlxuIiwgZ3B1X2NvdW50X21hdHJpeC5nZXQoKSkNCmBgYA0KDQotLS0NCg0KIyAqKktleSBUYWtlYXdheXMqKg0KMS4gKipCYWctb2YtV29yZHMgaXMgYW4gZXNzZW50aWFsIHRleHQgcHJlcHJvY2Vzc2luZyBzdGVwKiogaW4gTkxQLCB0cmFuc2Zvcm1pbmcgdGV4dCBpbnRvIGEgbnVtZXJpY2FsIGZvcm1hdC4NCjIuICoqTmFpdmUgQmF5ZXMgY2FuIHdvcmsgZWZmZWN0aXZlbHkgb24gdGV4dCBjbGFzc2lmaWNhdGlvbioqIGRlc3BpdGUgaXRzIHNpbXBsaWZ5aW5nIGFzc3VtcHRpb25zLg0KMy4gKipHUFUgYWNjZWxlcmF0aW9uIGNhbiBpbXByb3ZlIGVmZmljaWVuY3kqKiB3aGVuIGhhbmRsaW5nIGxhcmdlLXNjYWxlIHRleHQgZGF0YXNldHMuDQoNCi0tLQ0KDQojICoqRGlzY3Vzc2lvbiBRdWVzdGlvbnMqKg0KMS4gKipXaGF0IGFyZSB0aGUgYmVuZWZpdHMgYW5kIGxpbWl0YXRpb25zIG9mIHVzaW5nIGEgQmFnLW9mLVdvcmRzIG1vZGVsPyoqDQoyLiAqKldoeSBkb2VzIFRGLUlERiBvZnRlbiBvdXRwZXJmb3JtIHNpbXBsZSBDb3VudFZlY3Rvcml6ZXIgZm9yIHRleHQgY2xhc3NpZmljYXRpb24/KioNCjMuICoqSG93IGNhbiB3ZSBpbXByb3ZlIG91ciBjdXN0b20gQ291bnRWZWN0b3JpemVyIHRvIGluY2x1ZGUgc3RvcHdvcmQgcmVtb3ZhbCBhbmQgc3RlbW1pbmc/KioNCg0KLS0tSGVyZSBpcyB5b3VyIHN1bW1hcml6ZWQgYW5kIGV4cGFuZGVkIHN0dWR5IGd1aWRlIGJhc2VkIG9uIFFUVyBNb2R1bGUgNiDigJMgTmFpdmUgQmF5ZXMsIGluY2x1ZGluZyBrZXkgY29uY2VwdHMsIG1hdGhlbWF0aWNhbCBmb3JtdWxhcywgUHl0aG9uIGNvZGUgZm9yIENQVSBhbmQgR1BVIHV0aWxpemF0aW9uLCBwbHVzIHRocmVlIGtleSB0YWtlYXdheXMgYW5kIHRocmVlIGRpc2N1c3Npb24gcXVlc3Rpb25zLiBUeXBlICJwcm9jZWVkIiBpZiB5b3UgbmVlZCBtb3JlLCBhbmQgSSB3aWxsIGNvbnRpbnVlLg0KDQpTdHVkeSBHdWlkZTogUVRXIE1vZHVsZSA2IOKAkyBOYWl2ZSBCYXllcw0KDQoxLiBPdmVydmlldyBvZiBCYXllc+KAmSBSdWxlDQpCYXllc+KAmSBSdWxlIGhlbHBzIHVwZGF0ZSBhbiBpbml0aWFsIGJlbGllZiAocHJpb3IpIGFmdGVyIG9ic2VydmluZyBuZXcgZXZpZGVuY2UgKGxpa2VsaWhvb2QpLiBJdCBpcyBleHByZXNzZWQgbWF0aGVtYXRpY2FsbHkgYXM6DQoNClAoQSB8IEIpID0gWyBQKEIgfCBBKSDDlyBQKEEpIF0gLyBQKEIpDQoNCuKAoiBQKEEgfCBCKTogUG9zdGVyaW9yIFByb2JhYmlsaXR5ICh1cGRhdGVkIGJlbGllZikNCuKAoiBQKEIgfCBBKTogTGlrZWxpaG9vZCAocHJvYmFiaWxpdHkgb2Ygb2JzZXJ2aW5nIEIgZ2l2ZW4gQSBpcyB0cnVlKQ0K4oCiIFAoQSk6IFByaW9yIFByb2JhYmlsaXR5IChpbml0aWFsIGJlbGllZikNCuKAoiBQKEIpOiBOb3JtYWxpemluZyBGYWN0b3IgKGVuc3VyZXMgdGhlIHJlc3VsdCBpcyBhIHZhbGlkIHByb2JhYmlsaXR5KQ0KDQpFeGFtcGxlOiBEcnVnIFRlc3QNCi0gVHJ1ZSBQb3NpdGl2ZSBSYXRlID0gOTklDQotIFRydWUgTmVnYXRpdmUgUmF0ZSA9IDk3JQ0KLSBQcmV2YWxlbmNlID0gNSUNCg0KUChBIHwgQikgPSBbMC45OSDDlyAwLjA1XSAvIFsgKDAuOTkgw5cgMC4wNSkgKyAoMC4wMyDDlyAwLjk1ICkgXSDiiYggNjMuNSUNCg0KMi4gQmF5ZXPigJkgUnVsZSBmb3IgTXVsdGlwbGUgVmFyaWFibGVzDQpXaGVuIGRlYWxpbmcgd2l0aCBtdWx0aXBsZSBldmlkZW5jZSB2YXJpYWJsZXMgKEIsIEMsIEQsIC4uLiksIHdlIGNhbiBnZW5lcmFsaXplIEJheWVz4oCZIFJ1bGUgYXM6DQoNClAoQSB8IEIsIEMsIEQpIOKInSBQKEEpIMOXIFAoQiB8IEEpIMOXIFAoQyB8IEEpIMOXIFAoRCB8IEEpDQoNClRoZSDigJxuYWl2ZeKAnSBhc3N1bXB0aW9uIGlzIHRoYXQgYWxsIGV2aWRlbmNlIHZhcmlhYmxlcyBCLCBDLCBEIGFyZSBjb25kaXRpb25hbGx5IGluZGVwZW5kZW50IGdpdmVuIEEsIGFsbG93aW5nIHVzIHRvIG11bHRpcGx5IHByb2JhYmlsaXRpZXMgcmF0aGVyIHRoYW4gY29uc2lkZXIgdGhlaXIgY29tcGxleCBqb2ludCBkaXN0cmlidXRpb25zLg0KDQozLiBCYXllc+KAmSBSdWxlIHdpdGggQ29udGludW91cyBWYXJpYWJsZXMNCkZvciBjb250aW51b3VzIGF0dHJpYnV0ZXMgKHRlbXBlcmF0dXJlLCB3ZWlnaHQpLCB3ZSB1c2UgcHJvYmFiaWxpdHkgZGVuc2l0eSBmdW5jdGlvbnMgKGUuZy4sIEdhdXNzaWFuKSB0byBlc3RpbWF0ZSBQKFggPSB4KS4gSWYgdGhlIGRhdGEgaXMgYXNzdW1lZCBub3JtYWxseSBkaXN0cmlidXRlZCwgd2UgY2FuIGNvbXB1dGU6DQoNClAoeDsgzrwsIM+DKSA9ICgxIC8gKM+D4oiaKDLPgCkpKSDDlyBleHBb4oiSKHgg4oiSIM68KcKyIC8gKDLPg8KyKV0NCg0KV2UgdGhlbiBpbnNlcnQgdGhlc2UgcHJvYmFiaWxpdGllcyBpbnRvIEJheWVz4oCZIGZyYW1ld29yayBqdXN0IGFzIHdlIGRvIHdpdGggZGlzY3JldGUgb3IgY2F0ZWdvcmljYWwgZGF0YS4NCg0KNC4gTmFpdmUgQmF5ZXMgaW4gUHJhY3RpY2UgKERlbW8pDQrigKIgT2Z0ZW4gdXNlZCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbiB3aXRoIGJhZy1vZi13b3JkcyBvciB0Zi1pZGYgZmVhdHVyZXMuICANCuKAoiBNdWx0aW5vbWlhbE5CIGZyb20gc2tsZWFybiBpcyBjb21tb25seSBhcHBsaWVkOg0KICDigJMgQWxwaGEgcGFyYW1ldGVyIChMYXBsYWNlIHNtb290aGluZykgaGFuZGxlcyB6ZXJvLWZyZXF1ZW5jeSB3b3Jkcy4gIA0KICDigJMgVmVyeSBmYXN0LCBldmVuIG9uIGxhcmdlIGRhdGFzZXRzLg0KDQo1LiBOYWl2ZSBCYXllcyB2cy4gTG9naXN0aWMgUmVncmVzc2lvbg0K4oCiIE5haXZlIEJheWVzIChnZW5lcmF0aXZlIG1vZGVsKSBhbmQgTG9naXN0aWMgUmVncmVzc2lvbiAoZGlzY3JpbWluYXRpdmUgbW9kZWwpIGZvcm0gYSBnZW5lcmF0aXZlLWRpc2NyaW1pbmF0aXZlIHBhaXIuICANCuKAoiBOYWl2ZSBCYXllcyBjb252ZXJnZXMgZmFzdGVyIHRvIGEgbmVhci1vcHRpbWFsIHNvbHV0aW9uIHdpdGggbGltaXRlZCBkYXRhOyBMb2dpc3RpYyBSZWdyZXNzaW9uIGNhbiB5aWVsZCBhIGxvd2VyIGFzeW1wdG90aWMgZXJyb3Igd2l0aCBzdWZmaWNpZW50IGRhdGEuICANCuKAoiBCb3RoIGFyZSBkZWVwbHkgY29ubmVjdGVkIHRocm91Z2ggdGhlIGxvZ2l0IGZ1bmN0aW9uIGFuZCByYXRpbyBmb3JtcyBvZiBwcm9iYWJpbGl0eS4NCg0KNi4gQmFnIG9mIFdvcmRzIEV4ZXJjaXNlDQrigKIgVGFzazogQnVpbGQgYSBjdXN0b20gQ291bnRWZWN0b3JpemVyIHVzaW5nIFB5dGhvbi4gIA0K4oCiIENvbXBhcmUgd2l0aCBza2xlYXJu4oCZcyBDb3VudFZlY3Rvcml6ZXIgdG8gb2JzZXJ2ZSBkaWZmZXJlbmNlcyBpbiB0b2tlbml6YXRpb24sIHZvY2FidWxhcnkgYnVpbGRpbmcsIGFuZCBwZXJmb3JtYW5jZS4NCg0KTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uIChDb3JlIEZvcm11bGFzKQ0KMS4gQmF5ZXPigJkgUnVsZSAoU2luZ2xlIFZhcmlhYmxlKQ0KICAgUChBIHwgQikgPSBbUChCIHwgQSkgUChBKV0gLyBQKEIpDQoNCjIuIEJheWVz4oCZIFJ1bGUgKE11bHRpdmFyaWFibGUpDQogICBQKEEgfCBCLCBDLCBEKSDiiJ0gUChBKSDDlyBQKEIgfCBBKSDDlyBQKEMgfCBBKSDDlyBQKEQgfCBBKQ0KDQozLiBHYXVzc2lhbiBEZW5zaXR5IChDb250aW51b3VzIEV4YW1wbGUpDQogICBwKHgpID0gMSAvICjPg+KImigyz4ApKSDDlyBleHBb4oiSKHgg4oiSIM68KcKyIC8gKDLPg8KyKV0NCg0KQ29kZSBTbmlwcGV0cw0KDQpBLiBDUFUgSW1wbGVtZW50YXRpb24gKE5haXZlIEJheWVzIOKAkyBTa2xlYXJuKQ0KDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5uYWl2ZV9iYXllcyBpbXBvcnQgTXVsdGlub21pYWxOQg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBmZXRjaF8yMG5ld3Nncm91cHMNCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyLCBUZmlkZlZlY3Rvcml6ZXINCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KDQojIDEpIExvYWQgRGF0YQ0KdHJhaW5fZGF0YSA9IGZldGNoXzIwbmV3c2dyb3VwcyhzdWJzZXQ9J3RyYWluJywgcmVtb3ZlPSgnaGVhZGVycycsJ2Zvb3RlcnMnLCdxdW90ZXMnKSkNCnRlc3RfZGF0YSAgPSBmZXRjaF8yMG5ld3Nncm91cHMoc3Vic2V0PSd0ZXN0JywgIHJlbW92ZT0oJ2hlYWRlcnMnLCdmb290ZXJzJywncXVvdGVzJykpDQoNCiMgMikgVHJhbnNmb3JtIFRleHQgKENvdW50VmVjdG9yaXplcikNCmN2ID0gQ291bnRWZWN0b3JpemVyKCkNClhfdHJhaW5fY3YgPSBjdi5maXRfdHJhbnNmb3JtKHRyYWluX2RhdGEuZGF0YSkNClhfdGVzdF9jdiAgPSBjdi50cmFuc2Zvcm0odGVzdF9kYXRhLmRhdGEpDQoNCiMgMykgVHJhaW4gTmFpdmUgQmF5ZXMNCm1vZGVsX2N2ID0gTXVsdGlub21pYWxOQigpDQptb2RlbF9jdi5maXQoWF90cmFpbl9jdiwgdHJhaW5fZGF0YS50YXJnZXQpDQoNCiMgNCkgRXZhbHVhdGUNCnByZWRzX2N2ID0gbW9kZWxfY3YucHJlZGljdChYX3Rlc3RfY3YpDQpwcmludCgiQWNjdXJhY3kgd2l0aCBDb3VudFZlY3Rvcml6ZXI6IiwgYWNjdXJhY3lfc2NvcmUodGVzdF9kYXRhLnRhcmdldCwgcHJlZHNfY3YpKQ0KDQojIDUpIFVzaW5nIFRGLUlERg0KdGZpZGYgPSBUZmlkZlZlY3Rvcml6ZXIoKQ0KWF90cmFpbl90ZmlkZiA9IHRmaWRmLmZpdF90cmFuc2Zvcm0odHJhaW5fZGF0YS5kYXRhKQ0KWF90ZXN0X3RmaWRmICA9IHRmaWRmLnRyYW5zZm9ybSh0ZXN0X2RhdGEuZGF0YSkNCg0KbW9kZWxfdGZpZGYgPSBNdWx0aW5vbWlhbE5CKCkNCm1vZGVsX3RmaWRmLmZpdChYX3RyYWluX3RmaWRmLCB0cmFpbl9kYXRhLnRhcmdldCkNCg0KcHJlZHNfdGZpZGYgPSBtb2RlbF90ZmlkZi5wcmVkaWN0KFhfdGVzdF90ZmlkZikNCnByaW50KCJBY2N1cmFjeSB3aXRoIFRGLUlERjoiLCBhY2N1cmFjeV9zY29yZSh0ZXN0X2RhdGEudGFyZ2V0LCBwcmVkc190ZmlkZikpDQpgYGANCkIuIFNpbXBsZSBHUFUgSW1wbGVtZW50YXRpb24gKE5haXZlIEJheWVzIHdpdGggY3VNTCkNCihZb3UgbmVlZCBhbiBlbnZpcm9ubWVudCB3aXRoIFJBUElEUy9jdW1sIGluc3RhbGxlZCBmb3IgdGhpcy4pDQoNCmBgYHB5dGhvbg0KaW1wb3J0IGN1cHkgYXMgY3ANCmZyb20gY3VtbC5uYWl2ZV9iYXllcyBpbXBvcnQgTXVsdGlub21pYWxOQiBhcyBjdU11bHRpbm9taWFsTkINCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgVGZpZGZWZWN0b3JpemVyDQoNCiMgQXNzdW1lIHlvdSBoYXZlIHRyYWluX2RhdGEgJiB0ZXN0X2RhdGEgZnJvbSBhYm92ZQ0KdGZpZGZfZ3B1ID0gVGZpZGZWZWN0b3JpemVyKCkNClhfdHJhaW5fZ3B1ID0gdGZpZGZfZ3B1LmZpdF90cmFuc2Zvcm0odHJhaW5fZGF0YS5kYXRhKQ0KWF90ZXN0X2dwdSAgPSB0ZmlkZl9ncHUudHJhbnNmb3JtKHRlc3RfZGF0YS5kYXRhKQ0KDQojIENvbnZlcnQgdG8gR1BVIGFycmF5cw0KWF90cmFpbl9ncHVfY3NyID0gY3Auc3BhcnNlLmNzcl9tYXRyaXgoWF90cmFpbl9ncHUpDQpYX3Rlc3RfZ3B1X2NzciAgPSBjcC5zcGFyc2UuY3NyX21hdHJpeChYX3Rlc3RfZ3B1KQ0KeV90cmFpbl9ncHUgICAgID0gY3AuYXNhcnJheSh0cmFpbl9kYXRhLnRhcmdldCkNCnlfdGVzdF9ncHUgICAgICA9IGNwLmFzYXJyYXkodGVzdF9kYXRhLnRhcmdldCkNCg0KIyBUcmFpbg0KbW9kZWxfZ3B1ID0gY3VNdWx0aW5vbWlhbE5CKCkNCm1vZGVsX2dwdS5maXQoWF90cmFpbl9ncHVfY3NyLCB5X3RyYWluX2dwdSkNCg0KIyBQcmVkaWN0DQpwcmVkc19ncHUgPSBtb2RlbF9ncHUucHJlZGljdChYX3Rlc3RfZ3B1X2NzcikNCmFjY3VyYWN5X2dwdSA9IChwcmVkc19ncHUuZ2V0KCkgPT0geV90ZXN0X2dwdS5nZXQoKSkubWVhbigpDQpwcmludCgiR1BVLWJhc2VkIE5haXZlIEJheWVzIEFjY3VyYWN5OiIsIGFjY3VyYWN5X2dwdSkNCmBgYA0KDQpTdHVkeSBHdWlkZSBGb3JtYXQgKENvbmRlbnNlZCkNCg0KSS4gSW50cm9kdWN0aW9uDQogIC0gQmF5ZXPigJkgUnVsZSwgUHJpb3IsIExpa2VsaWhvb2QsIFBvc3RlcmlvciAgDQogIC0gQ29ubmVjdGlvbiB0byBsb2dpc3RpYyByZWdyZXNzaW9uICANCg0KSUkuIE1hdGhlbWF0aWNhbCBGb3VuZGF0aW9ucw0KICAtIFAoQXxCKSA9IChQKEJ8QSkqUChBKSkvUChCKSAgDQogIC0gTXVsdGl2YXJpYXRlIEV4dGVuc2lvbiAgDQogIC0gQ29udGludW91cyBWYXJpYWJsZXMgIA0KDQpJSUkuIFByYWN0aWNhbCBJbXBsZW1lbnRhdGlvbg0KICAtIFRleHQgQ2xhc3NpZmljYXRpb24gIA0KICAtIEJhZy1vZi1Xb3JkcyAmIFRGLUlERiAgDQogIC0gTmFpdmUgQmF5ZXMgKE11bHRpbm9taWFsTkIpICANCg0KSVYuIENvbXBhcmlzb24NCiAgLSBOYWl2ZSBCYXllcyB2cy4gTG9naXN0aWMgUmVncmVzc2lvbiAgDQogIC0gR2VuZXJhdGl2ZSB2cy4gRGlzY3JpbWluYXRpdmUgIA0KDQpWLiBLZXkgVGFrZWF3YXlzDQogIDEuIE5haXZlIEJheWVzIHVzZXMgQmF5ZXPigJkgUnVsZSB3aXRoIGEgc2ltcGxpZnlpbmcgKG5haXZlKSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbiwgeWV0IG9mdGVuIHBlcmZvcm1zIGNvbXBldGl0aXZlbHkgaW4gY2xhc3NpZmljYXRpb24gdGFza3MsIGVzcGVjaWFsbHkgdGV4dC4gIA0KICAyLiBDb21iaW5pbmcgVEYtSURGIHdpdGggTXVsdGlub21pYWxOQiBmcmVxdWVudGx5IHlpZWxkcyBiZXR0ZXIgYWNjdXJhY3kgdGhhbiBiYWctb2Ytd29yZHMuICANCiAgMy4gR2VuZXJhdGl2ZSAoTmFpdmUgQmF5ZXMpIGFuZCBEaXNjcmltaW5hdGl2ZSAoTG9naXN0aWMgUmVncmVzc2lvbikgbW9kZWxzIGFyZSBtYXRoZW1hdGljYWxseSByZWxhdGVkIHRocm91Z2ggdGhlIGxvZ2l0IChvZGRzIHJhdGlvKSBmcmFtZXdvcmsuDQoNClZJLiBEaXNjdXNzaW9uIFF1ZXN0aW9ucw0KICAxLiBXaHkgZG9lcyBOYWl2ZSBCYXllcyBvZnRlbiBwZXJmb3JtIHdlbGwgaW4gaGlnaC1kaW1lbnNpb25hbCB0ZXh0IGNsYXNzaWZpY2F0aW9uLCBkZXNwaXRlIHRoZSBuYWl2ZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbj8gIA0KICAyLiBIb3cgZG8gc21vb3RoaW5nIHBhcmFtZXRlcnMgKGxpa2UgYWxwaGEpIGluZmx1ZW5jZSBOYWl2ZSBCYXllcyBwZXJmb3JtYW5jZT8gIA0KICAzLiBJbiB3aGF0IGNhc2VzIG1pZ2h0IExvZ2lzdGljIFJlZ3Jlc3Npb24gZXZlbnR1YWxseSBvdXRwZXJmb3JtIE5haXZlIEJheWVzLCBhbmQgd2h5Pw0KDQoNCkJlbG93IGlzIGEgbW9yZSBkZXRhaWxlZCB3YWxrdGhyb3VnaCBvZiAqKlBhcnQgNiDigJMgVGhlIEJhZyBvZiBXb3JkcyBFeGVyY2lzZSoqLCBzaG93aW5nIGhvdyB0byBidWlsZCBhIGN1c3RvbSBDb3VudFZlY3Rvcml6ZXIgYW5kIGNvbXBhcmluZyBpdCB3aXRoIHNrbGVhcm7igJlzIHZlcnNpb24uIFRoaXMgd2lsbCBoZWxwIHNvbGlkaWZ5IHlvdXIgdW5kZXJzdGFuZGluZyBvZiBob3cgdGV4dCBpcyBjb252ZXJ0ZWQgaW50byBudW1lcmljYWwgZmVhdHVyZXMgZm9yIE5haXZlIEJheWVzIGFuZCBvdGhlciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4NCg0KQmFnIG9mIFdvcmRzIEV4ZXJjaXNlIOKAkyBTdGVwLWJ5LVN0ZXANCg0KMSkgVW5kZXJzdGFuZGluZyB0aGUgR29hbA0KV2Ugd2FudCB0bzoNCuKAoiBMb2FkIHRoZSAyMCBOZXdzZ3JvdXBzIGRhdGFzZXQgKHdpdGhvdXQgaGVhZGVycywgZm9vdGVycywgYW5kIHF1b3RlcykuICANCuKAoiBDcmVhdGUgb3VyICoqb3duKiogQmFnLW9mLVdvcmRzIGFwcHJvYWNoIChiYXNpYyBDb3VudFZlY3Rvcml6ZXIpLiAgDQrigKIgQ29tcGFyZSBhZ2FpbnN0IHRoZSAqKnNrbGVhcm4qKiBpbXBsZW1lbnRhdGlvbiAoQ291bnRWZWN0b3JpemVyKS4gIA0KDQoyKSBMb2FkaW5nIHRoZSAyMCBOZXdzZ3JvdXBzIERhdGFzZXQNClRoZSBkYXRhc2V0IGNhbiBiZSBmZXRjaGVkIHdpdGg6DQpkYXRhID0gZmV0Y2hfMjBuZXdzZ3JvdXBzKHJlbW92ZT0oJ2hlYWRlcnMnLCAnZm9vdGVycycsICdxdW90ZXMnKSkNCg0KVGhpcyByZXR1cm5zIGEgZGljdGlvbmFyeS1saWtlIG9iamVjdCwgaW5jbHVkaW5nIC5kYXRhIChsaXN0IG9mIGRvY3VtZW50cykgYW5kIC50YXJnZXQgKGxpc3Qgb2YgbnVtZXJpYyBsYWJlbHMpLg0KDQozKSBCdWlsZGluZyBhIEN1c3RvbSBDb3VudFZlY3Rvcml6ZXINCk91ciBjdXN0b20gdmVyc2lvbiB3aWxsOg0K4oCiIFRva2VuaXplIGVhY2ggZG9jdW1lbnQgaW50byB3b3JkcyAoc3BsaXQgb24gd2hpdGVzcGFjZSwgcmVtb3ZlIHB1bmN0dWF0aW9uKS4gIA0K4oCiIEJ1aWxkIGEgdm9jYWJ1bGFyeSAodW5pcXVlIHdvcmRzIG1hcHBlZCB0byBpbmRpY2VzKS4gIA0K4oCiIENvdW50IGhvdyBvZnRlbiBlYWNoIHdvcmQgYXBwZWFycyBpbiBlYWNoIGRvY3VtZW50LiAgDQoNCkJlbG93IGlzIHNhbXBsZSBjb2RlIHRvIGlsbHVzdHJhdGUgdGhlc2Ugc3RlcHMuDQoNCmBgYHB5dGhvbg0KaW1wb3J0IHJlDQpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBDb3VudGVyDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGZldGNoXzIwbmV3c2dyb3Vwcw0KDQpkZWYgY3VzdG9tX2NvdW50X3ZlY3Rvcml6ZXIoY29ycHVzKToNCiAgICAjIDEuIENsZWFuIGFuZCBsb3dlcmNhc2UgdGV4dCwgcmVtb3ZlIHB1bmN0dWF0aW9uDQogICAgY2xlYW5lZF90ZXh0cyA9IFtyZS5zdWIocidbXmEtekEtWlxzXScsICcnLCBkb2MubG93ZXIoKSkgZm9yIGRvYyBpbiBjb3JwdXNdDQogICAgDQogICAgIyAyLiBUb2tlbml6ZSBieSBzcGxpdHRpbmcgb24gd2hpdGVzcGFjZQ0KICAgIHRva2VuaXplZF9kb2NzID0gW2RvYy5zcGxpdCgpIGZvciBkb2MgaW4gY2xlYW5lZF90ZXh0c10NCiAgICANCiAgICAjIDMuIEJ1aWxkIHZvY2FidWxhcnkgKGFsbCB1bmlxdWUgd29yZHMpDQogICAgIyAgICBDb252ZXJ0IHRvIGEgc29ydGVkIGxpc3QgdG8gZW5zdXJlIGNvbnNpc3RlbnQgb3JkZXJpbmcuDQogICAgdm9jYWJ1bGFyeSA9IHNvcnRlZChzZXQod29yZCBmb3IgZG9jIGluIHRva2VuaXplZF9kb2NzIGZvciB3b3JkIGluIGRvYykpDQogICAgDQogICAgIyA0LiBDcmVhdGUgYSBkaWN0IG1hcHBpbmcgZWFjaCB3b3JkIHRvIGl0cyBpbmRleA0KICAgIHdvcmQyaW5kZXggPSB7d29yZDogaWR4IGZvciBpZHgsIHdvcmQgaW4gZW51bWVyYXRlKHZvY2FidWxhcnkpfQ0KICAgIA0KICAgICMgNS4gQ29uc3RydWN0IHRoZSBjb3VudCBtYXRyaXgNCiAgICAjICAgIEVhY2ggcm93IHJlcHJlc2VudHMgYSBkb2N1bWVudCwgZWFjaCBjb2x1bW4gYSB3b3JkIGluIG91ciB2b2NhYnVsYXJ5Lg0KICAgIGNvdW50X21hdHJpeCA9IFtdDQogICAgZm9yIGRvYyBpbiB0b2tlbml6ZWRfZG9jczoNCiAgICAgICAgY291bnRzID0gQ291bnRlcihkb2MpDQogICAgICAgICMgQnVpbGQgYSByb3cgb2YgbGVuZ3RoID0gc2l6ZSBvZiB2b2NhYnVsYXJ5DQogICAgICAgIHJvd192ZWN0b3IgPSBbY291bnRzLmdldCh3b3JkLCAwKSBmb3Igd29yZCBpbiB2b2NhYnVsYXJ5XQ0KICAgICAgICBjb3VudF9tYXRyaXguYXBwZW5kKHJvd192ZWN0b3IpDQogICAgDQogICAgcmV0dXJuIHZvY2FidWxhcnksIGNvdW50X21hdHJpeA0KDQojIDEpIEZldGNoIGRhdGENCmRhdGEgPSBmZXRjaF8yMG5ld3Nncm91cHMocmVtb3ZlPSgnaGVhZGVycycsICdmb290ZXJzJywgJ3F1b3RlcycpKQ0KY29ycHVzID0gZGF0YS5kYXRhWzo1XSAgIyBVc2luZyBmaXJzdCA1IGRvY3MgYXMgYSBzbWFsbCBleGFtcGxlDQoNCiMgMikgQXBwbHkgb3VyIGN1c3RvbSBDb3VudFZlY3Rvcml6ZXINCnZvY2FiLCBtYXRyaXggPSBjdXN0b21fY291bnRfdmVjdG9yaXplcihjb3JwdXMpDQoNCnByaW50KCJDdXN0b20gVm9jYWJ1bGFyeSAoZmlyc3QgMjAgd29yZHMpOiIsIHZvY2FiWzoyMF0pDQpwcmludCgiXG5GaXJzdCBEb2N1bWVudCdzIFZlY3RvciBSZXByZXNlbnRhdGlvbjoiKQ0KbWF0cml4WzBdKQ0KYGBgDQoNCg0KNCkgQ29tcGFyaW5nIHdpdGggc2tsZWFybuKAmXMgQ291bnRWZWN0b3JpemVyDQpOb3cgbGV04oCZcyB1c2UgdGhlIHNrbGVhcm4gdmVyc2lvbiB0byBzZWUgaG93IGl0IGRpZmZlcnMuDQoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBDb3VudFZlY3Rvcml6ZXINCg0KY3YgPSBDb3VudFZlY3Rvcml6ZXIoc3RvcF93b3Jkcz0nZW5nbGlzaCcpICAjIG9wdGlvbmFsbHkgcmVtb3ZlIHN0b3B3b3Jkcw0KWCA9IGN2LmZpdF90cmFuc2Zvcm0oY29ycHVzKQ0KDQpwcmludCgiXG5Ta2xlYXJuIFZvY2FidWxhcnkgU2l6ZToiLCBsZW4oY3Yudm9jYWJ1bGFyeV8pKQ0KcHJpbnQoIlNrbGVhcm4gRmlyc3QgRG9jdW1lbnQncyBWZWN0b3I6IikNCnByaW50KFgudG9hcnJheSgpWzBdKQ0KYGBgDQoNCg0KRGlmZmVyZW5jZXMgWW91IE1pZ2h0IE5vdGljZQ0K4oCiICoqU3RvcHdvcmQgUmVtb3ZhbCoqOiBCeSBkZWZhdWx0LCBza2xlYXJu4oCZcyBDb3VudFZlY3Rvcml6ZXIgY2FuIHJlbW92ZSBjb21tb24gd29yZHMgbGlrZSDigJx0aGUs4oCdIOKAnGFuZCzigJ0gZXRjLiwgaWYgeW91IHNwZWNpZnkgYHN0b3Bfd29yZHM9J2VuZ2xpc2gnYC4gT3VyIGN1c3RvbSB2ZXJzaW9uIGRpZG7igJl0IHJlbW92ZSB0aGVtIHVubGVzcyB3ZSBjb2RlIGl0LiAgDQrigKIgKipQdW5jdHVhdGlvbiBIYW5kbGluZyoqOiBza2xlYXJuIGF1dG9tYXRpY2FsbHkgaGFuZGxlcyBwdW5jdHVhdGlvbiBpbiBtdWx0aXBsZSB3YXlzLiBPdXIgY3VzdG9tIHZlcnNpb24gdXNlcyBhIHNpbXBsZSByZWdleC4gIA0K4oCiICoqU3BhcnNlIFJlcHJlc2VudGF0aW9uKio6IHNrbGVhcm4gcmV0dXJucyBhIHNwYXJzZSBtYXRyaXggKGVmZmljaWVudCBmb3IgbGFyZ2UgdGV4dCkuIE91cnMgaXMgYSBwbGFpbiBsaXN0IG9mIGxpc3RzIChkZW5zZSBtYXRyaXgpLiAgDQoNCjUpIChPcHRpb25hbCkgR1BVLUFjY2VsZXJhdGVkIFZlcnNpb24NCklmIHlvdSBoYXZlIHRoZSBSQVBJRFMgZW52aXJvbm1lbnQgc2V0IHVwICh3aXRoIEN1UHkgYW5kIGN1TUwpLCB5b3UgY291bGQgYWRhcHQgeW91ciBjb2RlIHRvIGJ1aWxkIGNvdW50cyBvbiB0aGUgR1BVLiBIb3dldmVyLCBmb3IgYSDigJxob21lZ3Jvd27igJ0gc29sdXRpb24sIHlvdeKAmWQ6DQoNCmBgYHB5dGhvbg0KaW1wb3J0IGN1cHkgYXMgY3ANCg0KZGVmIGdwdV9jb3VudF92ZWN0b3JpemVyKGNvcnB1cyk6DQogICAgIyBTdGVwIDE6IFByZXByb2Nlc3MgJiBUb2tlbml6ZSBvbiBDUFUgKG9yIEdQVSBpZiB5b3UgcHJlZmVyKQ0KICAgIGNsZWFuZWRfdGV4dHMgPSBbcmUuc3ViKHInW15hLXpBLVpcc10nLCAnJywgZG9jLmxvd2VyKCkpIGZvciBkb2MgaW4gY29ycHVzXQ0KICAgIHRva2VuaXplZF9kb2NzID0gW2RvYy5zcGxpdCgpIGZvciBkb2MgaW4gY2xlYW5lZF90ZXh0c10NCiAgICANCiAgICAjIFN0ZXAgMjogQnVpbGQgdm9jYWJ1bGFyeSBvbiBDUFUgZm9yIHNpbXBsaWNpdHkNCiAgICB2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCh3b3JkIGZvciBkb2MgaW4gdG9rZW5pemVkX2RvY3MgZm9yIHdvcmQgaW4gZG9jKSkNCiAgICB3b3JkMmluZGV4ID0ge3dvcmQ6IGlkeCBmb3IgaWR4LCB3b3JkIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCg0KICAgICMgU3RlcCAzOiBJbml0aWFsaXplIGEgR1BVIGFycmF5IHRvIGhvbGQgY291bnRzDQogICAgcm93cywgY29scyA9IGxlbih0b2tlbml6ZWRfZG9jcyksIGxlbih2b2NhYnVsYXJ5KQ0KICAgIGdwdV9tYXRyaXggPSBjcC56ZXJvcygocm93cywgY29scyksIGR0eXBlPWNwLmludDMyKQ0KICAgIA0KICAgICMgU3RlcCA0OiBGaWxsIGluIHRoZSBtYXRyaXgNCiAgICBmb3IgaSwgZG9jIGluIGVudW1lcmF0ZSh0b2tlbml6ZWRfZG9jcyk6DQogICAgICAgIGRvY19jb3VudHMgPSBDb3VudGVyKGRvYykNCiAgICAgICAgZm9yIHdvcmQsIGNvdW50IGluIGRvY19jb3VudHMuaXRlbXMoKToNCiAgICAgICAgICAgIGlmIHdvcmQgaW4gd29yZDJpbmRleDoNCiAgICAgICAgICAgICAgICBqID0gd29yZDJpbmRleFt3b3JkXQ0KICAgICAgICAgICAgICAgIGdwdV9tYXRyaXhbaSwgal0gPSBjb3VudA0KICAgIA0KICAgIHJldHVybiB2b2NhYnVsYXJ5LCBncHVfbWF0cml4DQoNCiMgRXhhbXBsZSB1c2FnZQ0Kdm9jYWJfZ3B1LCBtYXRyaXhfZ3B1ID0gZ3B1X2NvdW50X3ZlY3Rvcml6ZXIoY29ycHVzWzo1XSkNCnByaW50KCJHUFUgQ291bnQgTWF0cml4IHNoYXBlOiIsIG1hdHJpeF9ncHUuc2hhcGUpDQpwcmludCgiR1BVIENvdW50IE1hdHJpeCAoZmlyc3Qgcm93LCBiYWNrIHRvIENQVSk6IiwgbWF0cml4X2dwdVswXS5nZXQoKSkNCmBgYA0KDQpOb3RlOiBUaGlzIGV4YW1wbGUgc2hvd3MgdGhlIGNvbmNlcHQuIEluIHByYWN0aWNlLCB5b3XigJlkIHdhbnQgdG8gaGFuZGxlIG1vcmUgc3RlcHMgb24gR1BVIHRvIG1heGltaXplIHNwZWVkLg0KDQpTdW1tYXJ5IG9mIHRoZSBFeGVyY2lzZQ0K4oCiICoqT2JqZWN0aXZlKio6IEltcGxlbWVudCBhIHNpbXBsaWZpZWQgQmFnLW9mLVdvcmRzIG1hbnVhbGx5LiAgDQrigKIgKipLZXkgT2JzZXJ2YXRpb24qKjogVGhlIGNvcmUgc3RlcHMg4oCTIHRva2VuaXppbmcsIGJ1aWxkaW5nIGEgdm9jYWJ1bGFyeSwgY291bnRpbmcgd29yZHMg4oCTIGFyZSBzdHJhaWdodGZvcndhcmQsIGJ1dCBza2xlYXJuIG9wdGltaXplcyB0aGVtIGZvciBwZXJmb3JtYW5jZSBhbmQgYWRkcyBtYW55IGV4dHJhIGZlYXR1cmVzIChzdG9wd29yZCByZW1vdmFsLCBuLWdyYW1zLCBldGMuKS4gIA0KDQpUaHJlZSBLZXkgVGFrZWF3YXlzIChQYXJ0IDYgRm9jdXMpDQoxLiAqKkltcGxlbWVudGF0aW9uIENvbnRyb2wqKjogQnVpbGRpbmcgQ291bnRWZWN0b3JpemVyIGZyb20gc2NyYXRjaCBjbGFyaWZpZXMgaG93IHRleHQgcHJlcHJvY2Vzc2luZyB3b3JrcyB1bmRlciB0aGUgaG9vZCwgYnV0IGxpYnJhcmllcyBsaWtlIHNrbGVhcm4gYXJlIG1vcmUgZmxleGlibGUgYW5kIG9wdGltaXplZC4gIA0KMi4gKipWb2NhYnVsYXJ5IFNpemUqKjogUmVhbC13b3JsZCB0ZXh0IGRhdGEgc2V0cyBjYW4gY29udGFpbiB0ZW5zIG9yIGh1bmRyZWRzIG9mIHRob3VzYW5kcyBvZiB1bmlxdWUgdG9rZW5zOyBlZmZpY2llbnQgZGF0YSBzdHJ1Y3R1cmVzIChzcGFyc2UgbWF0cmljZXMpIGFyZSBjcml0aWNhbC4gIA0KMy4gKipQcmVwcm9jZXNzaW5nIE1hdHRlcnMqKjogUmVtb3Zpbmcgc3RvcHdvcmRzLCBoYW5kbGluZyBwdW5jdHVhdGlvbiwgb3IgYXBwbHlpbmcgc3RlbW1pbmcvbGVtbWF0aXphdGlvbiBjYW4gc2lnbmlmaWNhbnRseSBjaGFuZ2UgeW91ciByZXN1bHRzLg0KDQpUaHJlZSBEaXNjdXNzaW9uIFF1ZXN0aW9ucw0KMS4gKipIb3cgd291bGQgeW91IG1vZGlmeSB5b3VyIGN1c3RvbSBDb3VudFZlY3Rvcml6ZXIgdG8gcmVtb3ZlIHN0b3B3b3Jkcywgb3IgdG8gaGFuZGxlIG4tZ3JhbXMgKGxpa2UgdHdvLXdvcmQgcGhyYXNlcyk/KiogIA0KMi4gKipXaGF0IG1pZ2h0IGJlIHRoZSB0cmFkZW9mZnMgYmV0d2VlbiB1c2luZyBhIGRlbnNlIHZzLiBzcGFyc2UgbWF0cml4IHJlcHJlc2VudGF0aW9uIGZvciBsYXJnZSBjb3Jwb3JhPyoqICANCjMuICoqSG93IGNvdWxkIHdlIGV4dGVuZCB0aGlzIGFwcHJvYWNoIGZvciBhZHZhbmNlZCB0ZXh0IHByb2Nlc3NpbmcsIHN1Y2ggYXMgYWRkaW5nIFRGLUlERiBvciB3b3JkIGVtYmVkZGluZ3MgKEdsb1ZlLCBXb3JkMlZlYyk/KioNCg0KDQoNCg0KIyBCYXllcycgUnVsZSBhbmQgTmFpdmUgQmF5ZXMgU3R1ZHkgR3VpZGUgLSBQYXJ0IDENCg0KIyMgSS4gRnVuZGFtZW50YWxzIG9mIEJheWVzJyBSdWxlDQoNCiMjIyBDb3JlIEVxdWF0aW9uDQpCYXllcycgUnVsZSBpcyBleHByZXNzZWQgYXM6DQoNCmBgYA0KUChBfEIpID0gUChCfEEpIMOXIFAoQSkgLyBQKEIpDQpgYGANCg0KV2hlcmU6DQotIFAoQXxCKSA9IFBvc3RlcmlvciBQcm9iYWJpbGl0eQ0KLSBQKEJ8QSkgPSBMaWtlbGlob29kDQotIFAoQSkgPSBQcmlvciBQcm9iYWJpbGl0eQ0KLSBQKEIpID0gRXZpZGVuY2UvTm9ybWFsaXphdGlvbg0KDQojIyMgQ29tcG9uZW50cyBCcmVha2Rvd246DQoxLiAqKlBvc3RlcmlvciBEaXN0cmlidXRpb24gLSBQKEF8QikqKg0KICAgLSBUaGUgcHJvYmFiaWxpdHkgb2YgZXZlbnQgQSBvY2N1cnJpbmcgZ2l2ZW4gQiBoYXMgb2NjdXJyZWQNCiAgIC0gV2hhdCB3ZSdyZSB0eXBpY2FsbHkgdHJ5aW5nIHRvIGNhbGN1bGF0ZQ0KICAgLSBVcGRhdGVkIHByb2JhYmlsaXR5IGFmdGVyIGNvbnNpZGVyaW5nIGV2aWRlbmNlDQoNCjIuICoqUHJpb3IgLSBQKEEpKioNCiAgIC0gSW5pdGlhbCBwcm9iYWJpbGl0eSBiZWZvcmUgY29uc2lkZXJpbmcgbmV3IGV2aWRlbmNlDQogICAtIEJhc2VkIG9uIHByZXZpb3VzIGtub3dsZWRnZSBvciBhc3N1bXB0aW9ucw0KICAgLSBTdGFydGluZyBwb2ludCBmb3IgQmF5ZXNpYW4gaW5mZXJlbmNlDQoNCiMjIElJLiBCYXNpYyBQeXRob24gSW1wbGVtZW50YXRpb24gKENQVSkNCg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmZyb20gc2tsZWFybi5uYWl2ZV9iYXllcyBpbXBvcnQgR2F1c3NpYW5OQg0KDQpjbGFzcyBCYXllc2lhbkNhbGN1bGF0b3I6DQogICAgZGVmIHNpbXBsZV9iYXllcyhzZWxmLCBwcmlvcl9hLCBsaWtlbGlob29kX2JfZ2l2ZW5fYSwgZXZpZGVuY2VfYik6DQogICAgICAgICIiIg0KICAgICAgICBDYWxjdWxhdGUgcG9zdGVyaW9yIHByb2JhYmlsaXR5IHVzaW5nIEJheWVzJyBSdWxlDQogICAgICAgICIiIg0KICAgICAgICBwb3N0ZXJpb3IgPSAobGlrZWxpaG9vZF9iX2dpdmVuX2EgKiBwcmlvcl9hKSAvIGV2aWRlbmNlX2INCiAgICAgICAgcmV0dXJuIHBvc3Rlcmlvcg0KDQogICAgZGVmIG5haXZlX2JheWVzX2V4YW1wbGUoc2VsZik6DQogICAgICAgICMgU2FtcGxlIGRhdGENCiAgICAgICAgWCA9IG5wLmFycmF5KFtbMSwgMl0sIFsyLCAzXSwgWzMsIDRdLCBbNCwgNV1dKQ0KICAgICAgICB5ID0gbnAuYXJyYXkoWzAsIDAsIDEsIDFdKQ0KICAgICAgICANCiAgICAgICAgIyBJbml0aWFsaXplIGFuZCB0cmFpbiBtb2RlbA0KICAgICAgICBtb2RlbCA9IEdhdXNzaWFuTkIoKQ0KICAgICAgICBtb2RlbC5maXQoWCwgeSkNCiAgICAgICAgDQogICAgICAgICMgTWFrZSBwcmVkaWN0aW9uDQogICAgICAgIHByZWRpY3Rpb24gPSBtb2RlbC5wcmVkaWN0KFtbMiwgM11dKQ0KICAgICAgICByZXR1cm4gcHJlZGljdGlvbg0KYGBgDQoNCiMjIElJSS4gR1BVIEltcGxlbWVudGF0aW9uIHdpdGggQ3VQeQ0KDQpgYGBweXRob24NCmltcG9ydCBjdXB5IGFzIGNwDQoNCmNsYXNzIEdQVUJheWVzaWFuQ2FsY3VsYXRvcjoNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHNlbGYuZGV2aWNlID0gJ2dwdScgaWYgY3AuY3VkYS5pc19hdmFpbGFibGUoKSBlbHNlICdjcHUnDQogICAgDQogICAgZGVmIGdwdV9uYWl2ZV9iYXllcyhzZWxmLCBYLCB5KToNCiAgICAgICAgIiIiDQogICAgICAgIEdQVS1hY2NlbGVyYXRlZCBOYWl2ZSBCYXllcyBpbXBsZW1lbnRhdGlvbg0KICAgICAgICAiIiINCiAgICAgICAgaWYgc2VsZi5kZXZpY2UgPT0gJ2dwdSc6DQogICAgICAgICAgICBYID0gY3AuYXJyYXkoWCkNCiAgICAgICAgICAgIHkgPSBjcC5hcnJheSh5KQ0KICAgICAgICAgICAgDQogICAgICAgICAgICAjIENhbGN1bGF0ZSBjbGFzcyBwcmlvcnMNCiAgICAgICAgICAgIGNsYXNzZXMgPSBjcC51bmlxdWUoeSkNCiAgICAgICAgICAgIGNsYXNzX3Byb2JzID0gY3AuYXJyYXkoW2NwLm1lYW4oeSA9PSBjKSBmb3IgYyBpbiBjbGFzc2VzXSkNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgcmV0dXJuIGNsYXNzX3Byb2JzLmdldCgpICAjIFRyYW5zZmVyIGJhY2sgdG8gQ1BVDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICByZXR1cm4gIkdQVSBub3QgYXZhaWxhYmxlIg0KYGBgDQoNCiMgQmF5ZXMnIFJ1bGUgYW5kIE5haXZlIEJheWVzIFN0dWR5IEd1aWRlIC0gUGFydCAyDQoNCiMjIElWLiBOYWl2ZSBCYXllcyBDb3JlIENvbmNlcHRzDQoNCiMjIyBDcml0aWNhbCBBc3N1bXB0aW9uDQpUaGUgZnVuZGFtZW50YWwgYXNzdW1wdGlvbiBvZiBOYWl2ZSBCYXllcyBpcyBmZWF0dXJlIGluZGVwZW5kZW5jZS4gTWF0aGVtYXRpY2FsbHk6DQoNCmBgYA0KUChY4oKBLFjigoIsLi4uLFjigpl8WSkgPSBQKFjigoF8WSkgw5cgUChY4oKCfFkpIMOXIC4uLiDDlyBQKFjigpl8WSkNCmBgYA0KDQojIyMgTXVsdGljbGFzcyBDbGFzc2lmaWNhdGlvbg0KRm9yIGEgcHJvYmxlbSB3aXRoIGsgY2xhc3Nlcywgd2UgY2FsY3VsYXRlOg0KDQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBNdWx0aW5vbWlhbE5CDQoNCmNsYXNzIE11bHRpY2xhc3NOYWl2ZUJheWVzOg0KICAgIGRlZiBtdWx0aWNsYXNzX2V4YW1wbGUoc2VsZik6DQogICAgICAgICMgRXhhbXBsZSB3aXRoIG11bHRpcGxlIGNsYXNzZXMNCiAgICAgICAgWCA9IG5wLmFycmF5KFtbMSwyXSwgWzIsM10sIFszLDRdLCBbNCw1XSwgWzUsNl1dKQ0KICAgICAgICB5ID0gbnAuYXJyYXkoWzAsIDEsIDIsIDEsIDJdKSAgIyBUaHJlZSBjbGFzc2VzOiAwLCAxLCAyDQogICAgICAgIA0KICAgICAgICAjIEluaXRpYWxpemUgYW5kIHRyYWluDQogICAgICAgIGNsZiA9IE11bHRpbm9taWFsTkIoKQ0KICAgICAgICBjbGYuZml0KFgsIHkpDQogICAgICAgIA0KICAgICAgICAjIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KICAgICAgICBwcm9icyA9IGNsZi5wcmVkaWN0X3Byb2JhKFtbMyw0XV0pDQogICAgICAgICMgUmV0dXJucyBwcm9iYWJpbGl0eSBmb3IgZWFjaCBjbGFzcw0KICAgICAgICByZXR1cm4gcHJvYnMNCmBgYA0KDQojIyBWLiBBZHZhbmNlZCBJbXBsZW1lbnRhdGlvbiB3aXRoIEdQVSBBY2NlbGVyYXRpb24NCg0KYGBgcHl0aG9uDQppbXBvcnQgY3VweSBhcyBjcA0KZnJvbSBjdXB5LmxpbmFsZyBpbXBvcnQgbm9ybQ0KDQpjbGFzcyBHUFVOYWl2ZUJheWVzOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmKToNCiAgICAgICAgc2VsZi5jbGFzc2VzXyA9IE5vbmUNCiAgICAgICAgc2VsZi5jbGFzc19wcmlvcnNfID0gTm9uZQ0KICAgICAgICBzZWxmLmNsYXNzX21lYW5zXyA9IE5vbmUNCiAgICAgICAgc2VsZi5jbGFzc192YXJzXyA9IE5vbmUNCg0KICAgIGRlZiBmaXQoc2VsZiwgWCwgeSk6DQogICAgICAgIGlmIG5vdCBjcC5jdWRhLmlzX2F2YWlsYWJsZSgpOg0KICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKCJDVURBIGlzIG5vdCBhdmFpbGFibGUiKQ0KICAgICAgICAgICAgDQogICAgICAgICMgQ29udmVydCB0byBHUFUgYXJyYXlzDQogICAgICAgIFggPSBjcC5hcnJheShYKQ0KICAgICAgICB5ID0gY3AuYXJyYXkoeSkNCiAgICAgICAgDQogICAgICAgIHNlbGYuY2xhc3Nlc18gPSBjcC51bmlxdWUoeSkNCiAgICAgICAgbl9jbGFzc2VzID0gbGVuKHNlbGYuY2xhc3Nlc18pDQogICAgICAgIG5fZmVhdHVyZXMgPSBYLnNoYXBlWzFdDQogICAgICAgIA0KICAgICAgICAjIEluaXRpYWxpemUgcGFyYW1ldGVycw0KICAgICAgICBzZWxmLmNsYXNzX3ByaW9yc18gPSBjcC56ZXJvcyhuX2NsYXNzZXMpDQogICAgICAgIHNlbGYuY2xhc3NfbWVhbnNfID0gY3AuemVyb3MoKG5fY2xhc3Nlcywgbl9mZWF0dXJlcykpDQogICAgICAgIHNlbGYuY2xhc3NfdmFyc18gPSBjcC56ZXJvcygobl9jbGFzc2VzLCBuX2ZlYXR1cmVzKSkNCiAgICAgICAgDQogICAgICAgICMgQ2FsY3VsYXRlIHBhcmFtZXRlcnMgZm9yIGVhY2ggY2xhc3MNCiAgICAgICAgZm9yIGksIGMgaW4gZW51bWVyYXRlKHNlbGYuY2xhc3Nlc18pOg0KICAgICAgICAgICAgWF9jID0gWFt5ID09IGNdDQogICAgICAgICAgICBzZWxmLmNsYXNzX3ByaW9yc19baV0gPSBYX2Muc2hhcGVbMF0gLyBYLnNoYXBlWzBdDQogICAgICAgICAgICBzZWxmLmNsYXNzX21lYW5zX1tpXSA9IFhfYy5tZWFuKGF4aXM9MCkNCiAgICAgICAgICAgIHNlbGYuY2xhc3NfdmFyc19baV0gPSBYX2MudmFyKGF4aXM9MCkNCiAgICAgICAgICAgIA0KICAgICAgICByZXR1cm4gc2VsZg0KYGBgDQoNCiMjIFZJLiBDb21wYXJpc29uIHdpdGggTGluZWFyIFJlZ3Jlc3Npb24NCg0KS2V5IERpZmZlcmVuY2VzOg0KMS4gKipQdXJwb3NlKioNCiAgIC0gTmFpdmUgQmF5ZXM6IENsYXNzaWZpY2F0aW9uDQogICAtIExpbmVhciBSZWdyZXNzaW9uOiBDb250aW51b3VzIHZhbHVlIHByZWRpY3Rpb24NCg0KMi4gKipNYXRoZW1hdGljYWwgRm91bmRhdGlvbioqDQogICBgYGANCiAgIE5haXZlIEJheWVzOiBQKFl8WCkg4oidIFAoWHxZKVAoWSkNCiAgIExpbmVhciBSZWdyZXNzaW9uOiBZID0gzrJYICsgzrUNCiAgIGBgYA0KDQozLiAqKk91dHB1dCBUeXBlKioNCiAgIC0gTmFpdmUgQmF5ZXM6IFByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCiAgIC0gTGluZWFyIFJlZ3Jlc3Npb246IENvbnRpbnVvdXMgdmFsdWVzDQoNCiMjIFZJSS4gS2V5IFRha2Vhd2F5cw0KDQoxLiBOYWl2ZSBCYXllcyBkZXJpdmVzIGl0cyBwb3dlciBmcm9tIEJheWVzJyB0aGVvcmVtIHdoaWxlIGFzc3VtaW5nIGZlYXR1cmUgaW5kZXBlbmRlbmNlDQoyLiBJdCBuYXR1cmFsbHkgaGFuZGxlcyBtdWx0aWNsYXNzIHByb2JsZW1zIHdpdGhvdXQgc3BlY2lhbCBtb2RpZmljYXRpb25zDQozLiBHUFUgaW1wbGVtZW50YXRpb24gY2FuIHNpZ25pZmljYW50bHkgc3BlZWQgdXAgY2FsY3VsYXRpb25zIGZvciBsYXJnZSBkYXRhc2V0cw0KDQojIyBWSUlJLiBQcmFjdGljZSBRdWVzdGlvbnMNCg0KMS4gSG93IGRvZXMgdGhlIGluZGVwZW5kZW5jZSBhc3N1bXB0aW9uIGluIE5haXZlIEJheWVzIGFmZmVjdCBpdHMgcGVyZm9ybWFuY2Ugb24gcmVhbC13b3JsZCBkYXRhc2V0cyB3aGVyZSBmZWF0dXJlcyBhcmUgb2Z0ZW4gY29ycmVsYXRlZD8NCg0KMi4gSW4gd2hhdCBzY2VuYXJpb3Mgd291bGQgeW91IGNob29zZSBOYWl2ZSBCYXllcyBvdmVyIG90aGVyIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXM/DQoNCjMuIEhvdyBkb2VzIHRoZSBjb21wdXRhdGlvbmFsIGNvbXBsZXhpdHkgb2YgTmFpdmUgQmF5ZXMgY29tcGFyZSBiZXR3ZWVuIENQVSBhbmQgR1BVIGltcGxlbWVudGF0aW9ucyBmb3IgZGlmZmVyZW50IGRhdGFzZXQgc2l6ZXM/DQoNCg0KIyBCYXllcycgUnVsZSBhbmQgTmFpdmUgQmF5ZXMgU3R1ZHkgR3VpZGUgLSBQYXJ0IDMNCg0KIyMgSVguIFByYWN0aWNhbCBBcHBsaWNhdGlvbnMgYW5kIFVzZSBDYXNlcw0KDQojIyMgQ29tbW9uIEFwcGxpY2F0aW9ucw0KMS4gVGV4dCBDbGFzc2lmaWNhdGlvbg0KMi4gU3BhbSBEZXRlY3Rpb24NCjMuIFNlbnRpbWVudCBBbmFseXNpcw0KNC4gTWVkaWNhbCBEaWFnbm9zaXMNCg0KIyMjIEltcGxlbWVudGF0aW9uIEV4YW1wbGUgZm9yIFRleHQgQ2xhc3NpZmljYXRpb24NCg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyDQpmcm9tIHNrbGVhcm4ubmFpdmVfYmF5ZXMgaW1wb3J0IE11bHRpbm9taWFsTkINCmltcG9ydCBjdXB5IGFzIGNwDQoNCmNsYXNzIFRleHRDbGFzc2lmaWNhdGlvbk5COg0KICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1PUZhbHNlKToNCiAgICAgICAgc2VsZi52ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKCkNCiAgICAgICAgc2VsZi5jbGFzc2lmaWVyID0gTXVsdGlub21pYWxOQigpDQogICAgICAgIHNlbGYudXNlX2dwdSA9IHVzZV9ncHUgYW5kIGNwLmN1ZGEuaXNfYXZhaWxhYmxlKCkNCiAgICAgICAgDQogICAgZGVmIHByZXByb2Nlc3NfdGV4dChzZWxmLCB0ZXh0cyk6DQogICAgICAgICMgQ29udmVydCB0ZXh0IHRvIG51bWVyaWNhbCBmZWF0dXJlcw0KICAgICAgICBYID0gc2VsZi52ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0odGV4dHMpDQogICAgICAgIGlmIHNlbGYudXNlX2dwdToNCiAgICAgICAgICAgIHJldHVybiBjcC5hcnJheShYLnRvYXJyYXkoKSkNCiAgICAgICAgcmV0dXJuIFgNCiAgICANCiAgICBkZWYgdHJhaW4oc2VsZiwgdGV4dHMsIGxhYmVscyk6DQogICAgICAgIFggPSBzZWxmLnByZXByb2Nlc3NfdGV4dCh0ZXh0cykNCiAgICAgICAgaWYgc2VsZi51c2VfZ3B1Og0KICAgICAgICAgICAgbGFiZWxzID0gY3AuYXJyYXkobGFiZWxzKQ0KICAgICAgICBzZWxmLmNsYXNzaWZpZXIuZml0KFgsIGxhYmVscykNCiAgICAgICAgDQogICAgZGVmIHByZWRpY3Qoc2VsZiwgbmV3X3RleHRzKToNCiAgICAgICAgWCA9IHNlbGYudmVjdG9yaXplci50cmFuc2Zvcm0obmV3X3RleHRzKQ0KICAgICAgICBpZiBzZWxmLnVzZV9ncHU6DQogICAgICAgICAgICBYID0gY3AuYXJyYXkoWC50b2FycmF5KCkpDQogICAgICAgIHJldHVybiBzZWxmLmNsYXNzaWZpZXIucHJlZGljdChYKQ0KYGBgDQoNCiMjIFguIFBlcmZvcm1hbmNlIE9wdGltaXphdGlvbiBUaXBzDQoNCiMjIyAxLiBEYXRhIFByZXByb2Nlc3NpbmcNCmBgYHB5dGhvbg0KY2xhc3MgRGF0YVByZXByb2Nlc3NvcjoNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHNlbGYuc2NhbGVyID0gTm9uZQ0KICAgICAgICANCiAgICBkZWYgaGFuZGxlX21pc3NpbmdfdmFsdWVzKHNlbGYsIFgpOg0KICAgICAgICAjIFJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgd2l0aCBtZWFuDQogICAgICAgIHJldHVybiBucC5uYW5fdG9fbnVtKFgsIG5hbj1ucC5uYW5tZWFuKFgpKQ0KICAgICAgICANCiAgICBkZWYgbm9ybWFsaXplX2ZlYXR1cmVzKHNlbGYsIFgpOg0KICAgICAgICAjIExvZyB0cmFuc2Zvcm1hdGlvbiBmb3Igc2tld2VkIGZlYXR1cmVzDQogICAgICAgIHJldHVybiBucC5sb2cxcChYKQ0KICAgICAgICANCiAgICBkZWYgaGFuZGxlX2NhdGVnb3JpY2FsX2ZlYXR1cmVzKHNlbGYsIFgpOg0KICAgICAgICAjIE9uZS1ob3QgZW5jb2RpbmcNCiAgICAgICAgcmV0dXJuIHBkLmdldF9kdW1taWVzKFgpLnZhbHVlcw0KYGBgDQoNCiMjIyAyLiBHUFUgTWVtb3J5IE1hbmFnZW1lbnQNCmBgYHB5dGhvbg0KY2xhc3MgR1BVTWVtb3J5TWFuYWdlcjoNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHNlbGYubWVtb3J5X3Bvb2wgPSBjcC5jdWRhLk1lbW9yeVBvb2woKQ0KICAgICAgICANCiAgICBkZWYgX19lbnRlcl9fKHNlbGYpOg0KICAgICAgICBjcC5jdWRhLnNldF9hbGxvY2F0b3Ioc2VsZi5tZW1vcnlfcG9vbC5tYWxsb2MpDQogICAgICAgIA0KICAgIGRlZiBfX2V4aXRfXyhzZWxmLCAqYXJncyk6DQogICAgICAgIHNlbGYubWVtb3J5X3Bvb2wuZnJlZV9hbGxfYmxvY2tzKCkNCmBgYA0KDQojIyBYSS4gQ29tbW9uIFBpdGZhbGxzIGFuZCBTb2x1dGlvbnMNCg0KIyMjIDEuIFplcm8gUHJvYmFiaWxpdHkgUHJvYmxlbQ0KYGBgcHl0aG9uDQpjbGFzcyBMYXBsYWNlU21vb3RoaW5nTkI6DQogICAgZGVmIF9faW5pdF9fKHNlbGYsIGFscGhhPTEuMCk6DQogICAgICAgIHNlbGYuYWxwaGEgPSBhbHBoYSAgIyBTbW9vdGhpbmcgcGFyYW1ldGVyDQogICAgICAgIA0KICAgIGRlZiBjYWxjdWxhdGVfcHJvYmFiaWxpdHkoc2VsZiwgZmVhdHVyZV9jb3VudHMsIHRvdGFsX2NvdW50cyk6DQogICAgICAgICMgQWRkIHNtb290aGluZyB0byBwcmV2ZW50IHplcm8gcHJvYmFiaWxpdGllcw0KICAgICAgICByZXR1cm4gKGZlYXR1cmVfY291bnRzICsgc2VsZi5hbHBoYSkgLyAodG90YWxfY291bnRzICsgc2VsZi5hbHBoYSAqIGxlbihmZWF0dXJlX2NvdW50cykpDQpgYGANCg0KIyMjIDIuIE51bWVyaWNhbCBTdGFiaWxpdHkNCmBgYHB5dGhvbg0KZGVmIGxvZ19zdW1fZXhwKHgpOg0KICAgICIiIk51bWVyaWNhbGx5IHN0YWJsZSBsb2cgc3VtIGV4cC4iIiINCiAgICBtYXhfeCA9IG5wLm1heCh4KQ0KICAgIHJldHVybiBtYXhfeCArIG5wLmxvZyhucC5zdW0obnAuZXhwKHggLSBtYXhfeCkpKQ0KYGBgDQoNCiMjIFhJSS4gQWR2YW5jZWQgVGVjaG5pcXVlcw0KDQojIyMgMS4gRmVhdHVyZSBTZWxlY3Rpb24NCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLmZlYXR1cmVfc2VsZWN0aW9uIGltcG9ydCBTZWxlY3RLQmVzdCwgY2hpMg0KDQpjbGFzcyBGZWF0dXJlU2VsZWN0b3I6DQogICAgZGVmIF9faW5pdF9fKHNlbGYsIGs9MTApOg0KICAgICAgICBzZWxmLnNlbGVjdG9yID0gU2VsZWN0S0Jlc3QoY2hpMiwgaz1rKQ0KICAgICAgICANCiAgICBkZWYgc2VsZWN0X2ZlYXR1cmVzKHNlbGYsIFgsIHkpOg0KICAgICAgICByZXR1cm4gc2VsZi5zZWxlY3Rvci5maXRfdHJhbnNmb3JtKFgsIHkpDQpgYGANCg0KIyMjIDIuIENyb3NzLVZhbGlkYXRpb24NCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgY3Jvc3NfdmFsX3Njb3JlDQoNCmRlZiBldmFsdWF0ZV9tb2RlbChtb2RlbCwgWCwgeSwgY3Y9NSk6DQogICAgc2NvcmVzID0gY3Jvc3NfdmFsX3Njb3JlKG1vZGVsLCBYLCB5LCBjdj1jdikNCiAgICByZXR1cm4gew0KICAgICAgICAnbWVhbl9hY2N1cmFjeSc6IHNjb3Jlcy5tZWFuKCksDQogICAgICAgICdzdGRfYWNjdXJhY3knOiBzY29yZXMuc3RkKCkNCiAgICB9DQpgYGANCg0KIyMgWElJSS4gRmluYWwgS2V5IFRha2Vhd2F5cw0KDQoxLiBVbmRlcnN0YW5kaW5nIHRoZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbiBpcyBjcnVjaWFsIGZvciBlZmZlY3RpdmUgaW1wbGVtZW50YXRpb24NCjIuIEdQVSBhY2NlbGVyYXRpb24gY2FuIHByb3ZpZGUgc2lnbmlmaWNhbnQgc3BlZWR1cCBmb3IgbGFyZ2UgZGF0YXNldHMNCjMuIFByb3BlciBwcmVwcm9jZXNzaW5nIGFuZCBoYW5kbGluZyBvZiBlZGdlIGNhc2VzIGlzIGVzc2VudGlhbCBmb3Igcm9idXN0IG1vZGVscw0KDQojIyBYSVYuIEFkZGl0aW9uYWwgUHJhY3RpY2UgUXVlc3Rpb25zDQoNCjEuIEhvdyB3b3VsZCB5b3UgaGFuZGxlIGhpZ2hseSBpbWJhbGFuY2VkIGRhdGFzZXRzIHdoZW4gdXNpbmcgTmFpdmUgQmF5ZXM/DQoyLiBXaGF0IHRlY2huaXF1ZXMgY2FuIGJlIHVzZWQgdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIHNtb290aGluZyBwYXJhbWV0ZXI/DQozLiBJbiB3aGF0IHNjZW5hcmlvcyBtaWdodCBHUFUgYWNjZWxlcmF0aW9uIGFjdHVhbGx5IHNsb3cgZG93biBjb21wdXRhdGlvbj8NCg0KIyMgWFYuIFN0dWR5IFRpcHMNCg0KMS4gRm9jdXMgb24gdW5kZXJzdGFuZGluZyB0aGUgcHJvYmFiaWxpc3RpYyBmb3VuZGF0aW9ucw0KMi4gUHJhY3RpY2Ugd2l0aCByZWFsLXdvcmxkIGRhdGFzZXRzDQozLiBFeHBlcmltZW50IHdpdGggYm90aCBDUFUgYW5kIEdQVSBpbXBsZW1lbnRhdGlvbnMNCjQuIFBheSBhdHRlbnRpb24gdG8gcHJlcHJvY2Vzc2luZyBzdGVwcw0KNS4gVW5kZXJzdGFuZCB0aGUgbGltaXRhdGlvbnMgYW5kIGFzc3VtcHRpb25zDQoNCg0KDQpUaHJlZSB0aG91Z2h0LXByb3Zva2luZyBhbmQgbG9naWNhbGx5IGNvbXByZWhlbnNpdmUgcXVlc3Rpb25zIGFib3V0IE5haXZlIEJheWVzOg0KDQoxLiAqKiJXaHkgZG9lcyBOYWl2ZSBCYXllcyBvZnRlbiBwZXJmb3JtIHdlbGwgaW4gaGlnaC1kaW1lbnNpb25hbCB0ZXh0IGNsYXNzaWZpY2F0aW9uLCBkZXNwaXRlIHRoZSBuYWl2ZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbj8iKioNCiAgIC0gQWRkcmVzc2VzIHRoZSBmdW5kYW1lbnRhbCBwYXJhZG94IG9mIE5haXZlIEJheWVzDQogICAtIFJlcXVpcmVzIHVuZGVyc3RhbmRpbmcgb2YgYm90aCB0aGVvcnkgYW5kIHByYWN0aWNhbCBhcHBsaWNhdGlvbnMNCiAgIC0gQ2hhbGxlbmdlcyBzdHVkZW50cyB0byB0aGluayBhYm91dCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbW9kZWwgYXNzdW1wdGlvbnMgYW5kIHJlYWwtd29ybGQgcGVyZm9ybWFuY2UNCiAgIC0gQ29ubmVjdHMgdG8gcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMgaW4gdGV4dCBjbGFzc2lmaWNhdGlvbg0KDQoyLiAqKiJIb3cgZG9lcyB0aGUgaW5kZXBlbmRlbmNlIGFzc3VtcHRpb24gaW4gTmFpdmUgQmF5ZXMgYWZmZWN0IGl0cyBwZXJmb3JtYW5jZSBvbiByZWFsLXdvcmxkIGRhdGFzZXRzIHdoZXJlIGZlYXR1cmVzIGFyZSBvZnRlbiBjb3JyZWxhdGVkPyIqKg0KICAgLSBGb3JjZXMgY29uc2lkZXJhdGlvbiBvZiB0aGUgY29yZSBhc3N1bXB0aW9ucw0KICAgLSBSZXF1aXJlcyBhbmFseXNpcyBvZiByZWFsLXdvcmxkIGltcGxpY2F0aW9ucw0KICAgLSBFbmNvdXJhZ2VzIGNyaXRpY2FsIHRoaW5raW5nIGFib3V0IG1vZGVsIGxpbWl0YXRpb25zDQogICAtIENvbm5lY3RzIHRoZW9yZXRpY2FsIGNvbmNlcHRzIHRvIHByYWN0aWNhbCBhcHBsaWNhdGlvbnMNCg0KMy4gKioiSW4gd2hhdCBzY2VuYXJpb3Mgd291bGQgeW91IGNob29zZSBOYWl2ZSBCYXllcyBvdmVyIG90aGVyIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXM/IioqDQogICAtIFJlcXVpcmVzIGNvbXBhcmF0aXZlIGFuYWx5c2lzDQogICAtIERlbWFuZHMgdW5kZXJzdGFuZGluZyBvZiB2YXJpb3VzIGFsZ29yaXRobXMnIHN0cmVuZ3RocyBhbmQgd2Vha25lc3Nlcw0KICAgLSBGb2N1c2VzIG9uIHByYWN0aWNhbCBkZWNpc2lvbi1tYWtpbmcNCiAgIC0gRW5jb3VyYWdlcyBjb25zaWRlcmF0aW9uIG9mIHJlYWwtd29ybGQgY29uc3RyYWludHMgYW5kIHJlcXVpcmVtZW50cw0KDQoNClRha2Vhd2F5czogDQoxLiAqKlRoZSBJbmRlcGVuZGVuY2UgQXNzdW1wdGlvbidzIFBhcmFkb3gqKg0KICAgLSBEZXNwaXRlIGl0cyAibmFpdmUiIGFzc3VtcHRpb24gdGhhdCBmZWF0dXJlcyBhcmUgaW5kZXBlbmRlbnQgKHdoaWNoIGlzIHJhcmVseSB0cnVlIGluIHJlYWwtd29ybGQgZGF0YSksIE5haXZlIEJheWVzIG9mdGVuIHBlcmZvcm1zIHN1cnByaXNpbmdseSB3ZWxsIGluIHByYWN0aWNlDQogICAtIFRoaXMgaXMgcGFydGljdWxhcmx5IHRydWUgaW4gdGV4dCBjbGFzc2lmaWNhdGlvbiBhbmQgaGlnaC1kaW1lbnNpb25hbCBwcm9ibGVtcw0KICAgLSBVbmRlcnN0YW5kaW5nOiBUaGUgbW9kZWwncyBzaW1wbGljaXR5IGFuZCBlZmZpY2llbmN5IG9mdGVuIG91dHdlaWdoIHRoZSBsaW1pdGF0aW9ucyBvZiB0aGUgaW5kZXBlbmRlbmNlIGFzc3VtcHRpb24NCiAgIC0gTWF0aGVtYXRpY2FsIHJlcHJlc2VudGF0aW9uOiBQKHjigoEseOKCgnx5KSA9IFAoeOKCgXx5KSDDlyBQKHjigoJ8eSkNCg0KMi4gKipQcm9iYWJpbGlzdGljIEZvdW5kYXRpb24gYW5kIFNjYWxhYmlsaXR5KioNCiAgIC0gTmFpdmUgQmF5ZXMgaXMgZnVuZGFtZW50YWxseSBhIHByb2JhYmlsaXN0aWMgY2xhc3NpZmllciBiYXNlZCBvbiBCYXllcycgVGhlb3JlbQ0KICAgLSBJdCdzIGhpZ2hseSBzY2FsYWJsZSBhbmQgY2FuIGhhbmRsZSBsYXJnZSBkYXRhc2V0cyBlZmZpY2llbnRseQ0KICAgLSBUcmFpbmluZyBpcyBPKG4pIGNvbXBsZXhpdHksIG1ha2luZyBpdCBvbmUgb2YgdGhlIGZhc3Rlc3QgY2xhc3NpZmllcnMNCiAgIC0gS2V5IGVxdWF0aW9uOiBQKHl8eCkgPSBQKHh8eSlQKHkpL1AoeCkNCiAgIC0gVGhpcyBtYWtlcyBpdCBwYXJ0aWN1bGFybHkgdmFsdWFibGUgZm9yIHJlYWwtdGltZSBhcHBsaWNhdGlvbnMgYW5kIGxhcmdlLXNjYWxlIGNsYXNzaWZpY2F0aW9uIHRhc2tzDQoNCjMuICoqU3VzY2VwdGliaWxpdHkgdG8gWmVybyBQcm9iYWJpbGl0eSBQcm9ibGVtKioNCiAgIC0gV2hlbiBhIGZlYXR1cmUgdmFsdWUgaW4gdGhlIHRlc3QgZGF0YSBuZXZlciBhcHBlYXJzIGluIHRoZSB0cmFpbmluZyBkYXRhLCBpdCBsZWFkcyB0byB6ZXJvIHByb2JhYmlsaXR5DQogICAtIFRoaXMgaXMgc29sdmVkIHRocm91Z2ggc21vb3RoaW5nIHRlY2huaXF1ZXMgKGxpa2UgTGFwbGFjZS9BZGRpdGl2ZSBzbW9vdGhpbmcpDQogICAtIENyaXRpY2FsIGZvciBwcmFjdGljYWwgaW1wbGVtZW50YXRpb246IFAoeHx5KSA9IChjb3VudCh4LHkpICsgzrEpLyhjb3VudCh5KSArIM6xfFZ8KQ0KICAgLSBVbmRlcnN0YW5kaW5nIHRoaXMgbGltaXRhdGlvbiBhbmQgaXRzIHNvbHV0aW9ucyBpcyBjcnVjaWFsIGZvciBlZmZlY3RpdmUgaW1wbGVtZW50YXRpb24NCg0KVGhlc2UgdGFrZWF3YXlzIGFyZSBwYXJ0aWN1bGFybHkgaW1wb3J0YW50IGJlY2F1c2UgdGhleToNCi0gQ292ZXIgYm90aCB0aGVvcmV0aWNhbCBmb3VuZGF0aW9ucyBhbmQgcHJhY3RpY2FsIGltcGxpY2F0aW9ucw0KLSBBZGRyZXNzIGNvbW1vbiBjaGFsbGVuZ2VzIGFuZCBzb2x1dGlvbnMNCi0gRXhwbGFpbiB3aHkgYW5kIHdoZW4gdGhlIGFsZ29yaXRobSB3b3JrcyB3ZWxsDQotIFByb3ZpZGUgZXNzZW50aWFsIGtub3dsZWRnZSBmb3IgcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMNCi0gSGVscCBpbiBtYWtpbmcgaW5mb3JtZWQgZGVjaXNpb25zIGFib3V0IHdoZW4gdG8gdXNlIE5haXZlIEJheWVzDQoNCkVhY2ggb2YgdGhlc2UgcG9pbnRzIGFmZmVjdHM6DQotIE1vZGVsIHNlbGVjdGlvbiBkZWNpc2lvbnMNCi0gSW1wbGVtZW50YXRpb24gYXBwcm9hY2hlcw0KLSBQZXJmb3JtYW5jZSBvcHRpbWl6YXRpb24NCi0gUHJvYmxlbS1zb2x2aW5nIHN0cmF0ZWdpZXMNCi0gVW5kZXJzdGFuZGluZyBvZiByZXN1bHRzDQo=