Study Guide: QTW Module 6 - Naive Bayes

1. Bayes’ Rule: Overview and Mathematical Representation

Bayes’ Rule is a fundamental theorem in probability that helps update prior beliefs based on new evidence. It is the backbone of Bayesian inference and plays a crucial role in probabilistic modeling.

Mathematical Formula

\[ P(A | B) = \frac{P(B | A) P(A)}{P(B)} \] Where: - \(P(A | B)\) = Posterior Probability (updated belief after seeing evidence) - \(P(B | A)\) = Likelihood (how likely B occurs given A is true) - \(P(A)\) = Prior Probability (initial belief before seeing evidence) - \(P(B)\) = Normalization Factor (ensures valid probability distribution)

Example: Drug Testing

Given: - True positive rate = 99% (\(P(B | A) = 0.99\)) - False positive rate = 3% (\(P(B | \neg A) = 0.03\)) - Prevalence of drug use = 5% (\(P(A) = 0.05\))

The probability that a person who tested positive is actually a drug user is: \[ P(A | B) = \frac{(0.99 \times 0.05)}{(0.99 \times 0.05) + (0.03 \times 0.95)} \]

Using Python:

P_A = 0.05  # Prior probability of being a drug user
P_B_given_A = 0.99  # True positive rate
P_B_given_not_A = 0.03  # False positive rate
P_not_A = 1 - P_A

P_B = (P_B_given_A * P_A) + (P_B_given_not_A * P_not_A)  # Normalization factor
P_A_given_B = (P_B_given_A * P_A) / P_B  # Bayes' Rule

print(f"Probability of being a drug user given a positive test: {P_A_given_B:.3f}")

2. Bayes’ Rule for Multivariables

When there are multiple evidence variables \(B, C, D, ...\), we generalize Bayes’ Rule: \[ P(A | B, C, D) = \frac{P(B, C, D | A) P(A)}{P(B, C, D)} \] By assuming conditional independence (naive assumption), we simplify: \[ P(A | B, C, D) \propto P(A) P(B | A) P(C | A) P(D | A) \]

This forms the foundation of Naive Bayes Classification.

Python Example for Text Classification

from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.datasets import fetch_20newsgroups

# Load dataset
newsgroups = fetch_20newsgroups(subset='train', remove=('headers', 'footers', 'quotes'))
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(newsgroups.data)  # Convert text to bag-of-words
y = newsgroups.target

# Train Naive Bayes Classifier
nb = MultinomialNB()
nb.fit(X, y)

# Predict on new data
sample_text = ["Quantum computing is the future of AI"]
X_sample = vectorizer.transform(sample_text)
prediction = nb.predict(X_sample)
print(f"Predicted category: {newsgroups.target_names[prediction[0]]}")

3. Bayes’ Rule for Continuous Variables

For continuous variables (e.g., temperature, weight), we use Probability Density Functions (PDFs) such as the Gaussian Distribution: \[ P(X = x) = \frac{1}{\sigma \sqrt{2\pi}} e^{-\frac{(x - \mu)^2}{2\sigma^2}} \] where \(\mu\) is the mean and \(\sigma\) is the standard deviation.

Python Implementation

import numpy as np
from scipy.stats import norm

# Assume normal distribution with mean=70, std=10
mu, sigma = 70, 10
x_value = 75
probability = norm.pdf(x_value, mu, sigma)

print(f"Probability density of weight 75 given mean=70 and std=10: {probability:.4f}")

4. Naive Bayes Implementation for Text Classification

Using CountVectorizer and TF-IDF, we train a Multinomial Naive Bayes model.

Python Implementation

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import accuracy_score

# Convert dataset using TF-IDF
tfidf_vectorizer = TfidfVectorizer()
X_tfidf = tfidf_vectorizer.fit_transform(newsgroups.data)

# Train Naive Bayes with TF-IDF
nb_tfidf = MultinomialNB()
nb_tfidf.fit(X_tfidf, y)

# Predict on test data
newsgroups_test = fetch_20newsgroups(subset='test', remove=('headers', 'footers', 'quotes'))
X_test_tfidf = tfidf_vectorizer.transform(newsgroups_test.data)
y_test = newsgroups_test.target

predictions = nb_tfidf.predict(X_test_tfidf)
accuracy = accuracy_score(y_test, predictions)
print(f"Naive Bayes TF-IDF Accuracy: {accuracy:.2f}")

5. CPU vs. GPU Implementation

CPU Implementation

from sklearn.naive_bayes import GaussianNB

# Simulating data
X_cpu = np.random.rand(10000, 10)
y_cpu = np.random.randint(2, size=10000)

# Train Gaussian Naive Bayes
gnb = GaussianNB()
gnb.fit(X_cpu, y_cpu)

GPU Implementation with CuPy

import cupy as cp
from cuml.naive_bayes import GaussianNB as cuGaussianNB

# Convert data to GPU
X_gpu = cp.asarray(X_cpu)
y_gpu = cp.asarray(y_cpu)

# Train on GPU
gnb_gpu = cuGaussianNB()
gnb_gpu.fit(X_gpu, y_gpu)

6. Bag of Words Exercise

Build a CountVectorizer from scratch and compare with sklearn.

Manual Count Vectorizer

from collections import Counter

def count_vectorizer(corpus):
    vocab = set(word for text in corpus for word in text.split())
    vectorized = [{word: text.split().count(word) for word in vocab} for text in corpus]
    return vectorized, list(vocab)

# Sample data
corpus = ["Naive Bayes is simple", "Bayes models are powerful"]
vectorized_data, vocab = count_vectorizer(corpus)

print(f"Vocabulary: {vocab}")
print(f"Vectorized Data: {vectorized_data}")

Comparison with Scikit-learn

cv = CountVectorizer()
X_cv = cv.fit_transform(corpus)
print(f"Sklearn CountVectorizer Output:\n{X_cv.toarray()}")

Key Takeaways

  1. Bayes’ Rule is a powerful probabilistic tool for updating beliefs based on evidence.
  2. Naive Bayes assumes feature independence but still performs well in many classification tasks.
  3. It is computationally efficient and well-suited for text classification, spam detection, and medical diagnostics.

Discussion Questions

  1. What are the limitations of the naive assumption in Naive Bayes?
  2. Why does Naive Bayes perform well in text classification despite feature independence?
  3. How can we improve Naive Bayes for continuous data distributions?

Bag of Words Exercise: Step-by-Step Walkthrough

This exercise involves manually implementing CountVectorizer, which is a fundamental preprocessing step in Natural Language Processing (NLP). The goal is to convert text into a numerical representation before applying machine learning models.


Step 1: Understanding CountVectorizer

CountVectorizer is a method of converting text into a bag-of-words (BoW) representation, where: - Each unique word in the dataset is assigned an index in a vocabulary. - The text data is transformed into a matrix, where each row represents a document and each column represents a word. - The values in the matrix indicate how many times a word appears in a document.

Example

For the text:

"Naive Bayes is simple"
"Bayes models are powerful"

The bag-of-words representation would count the occurrences of each word across documents.


Step 2: Loading the 20 Newsgroup Dataset

The 20 Newsgroups dataset is a collection of text documents categorized into 20 topics.

from sklearn.datasets import fetch_20newsgroups

# Load dataset without headers, footers, and quotes
data = fetch_20newsgroups(remove=('headers', 'footers', 'quotes'))
documents = data.data[:5]  # Load only first 5 documents for demonstration

# Display sample data
for i, doc in enumerate(documents):
    print(f"Document {i+1}:\n{doc}\n{'-'*50}")

This dataset contains raw text that needs to be processed into a numerical format.


Step 3: Implementing Manual CountVectorizer

1. Tokenization

Convert text into words by splitting on spaces and removing punctuation.

2. Building Vocabulary

Create a dictionary mapping each unique word to an index.

3. Constructing the Count Matrix

For each document, count how many times each word appears.

from collections import Counter
import re

def custom_count_vectorizer(corpus):
    # Preprocess: Remove punctuation and lowercase
    corpus_clean = [re.sub(r'\W+', ' ', doc).lower() for doc in corpus]

    # Tokenize
    tokenized_docs = [doc.split() for doc in corpus_clean]

    # Build vocabulary
    vocabulary = sorted(set(word for doc in tokenized_docs for word in doc))
    word_index = {word: idx for idx, word in enumerate(vocabulary)}

    # Count word occurrences
    matrix = []
    for doc in tokenized_docs:
        word_counts = Counter(doc)
        row = [word_counts.get(word, 0) for word in vocabulary]
        matrix.append(row)

    return vocabulary, matrix

# Apply custom CountVectorizer
vocab, count_matrix = custom_count_vectorizer(documents[:5])

# Display results
print("Vocabulary:\n", vocab)
print("\nCount Matrix:\n", count_matrix)

Step 4: Comparing with Sklearn’s CountVectorizer

To validate our implementation, we compare it against Scikit-learn’s CountVectorizer.

from sklearn.feature_extraction.text import CountVectorizer

# Apply Scikit-learn's CountVectorizer
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(documents[:5])

# Display vocabulary
print("\nScikit-learn Vocabulary:\n", vectorizer.get_feature_names_out())

# Display transformed text representation
print("\nScikit-learn Count Matrix:\n", X.toarray())

Step 5: Key Differences Between Manual and Sklearn CountVectorizer

Feature Custom Implementation Sklearn CountVectorizer
Tokenization Basic space-splitting and regex Handles special cases (stopwords, n-grams, etc.)
Vocabulary Built manually using a set Automatically optimized
Count Matrix Created with Counter Optimized sparse matrix
Speed Slower for large datasets Highly optimized C++ backend

Step 6: Performance Considerations

Since CountVectorizer is a CPU-based method, let’s optimize it for a GPU implementation using CuPy.

import cupy as cp

def gpu_count_vectorizer(corpus):
    corpus_clean = [re.sub(r'\W+', ' ', doc).lower() for doc in corpus]
    tokenized_docs = [doc.split() for doc in corpus_clean]
    
    vocabulary = sorted(set(word for doc in tokenized_docs for word in doc))
    word_index = {word: idx for idx, word in enumerate(vocabulary)}
    
    matrix = cp.zeros((len(tokenized_docs), len(vocabulary)), dtype=cp.int32)
    
    for doc_idx, doc in enumerate(tokenized_docs):
        word_counts = Counter(doc)
        for word, count in word_counts.items():
            if word in word_index:
                matrix[doc_idx, word_index[word]] = count
    
    return vocabulary, matrix

# Apply GPU CountVectorizer
gpu_vocab, gpu_count_matrix = gpu_count_vectorizer(documents[:5])

print("\nGPU Vocabulary:", gpu_vocab)
print("\nGPU Count Matrix:\n", gpu_count_matrix.get())

Key Takeaways

  1. Bag-of-Words is an essential text preprocessing step in NLP, transforming text into a numerical format.
  2. Naive Bayes can work effectively on text classification despite its simplifying assumptions.
  3. GPU acceleration can improve efficiency when handling large-scale text datasets.

Discussion Questions

  1. What are the benefits and limitations of using a Bag-of-Words model?
  2. Why does TF-IDF often outperform simple CountVectorizer for text classification?
  3. 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

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

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

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

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

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

  3. 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)

  1. Bayes’ Rule (Multivariable) P(A | B, C, D) ∝ P(A) × P(B | A) × P(C | A) × P(D | A)

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

  1. Mathematical Foundations
  1. Practical Implementation
  1. Comparison

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.

  1. Discussion Questions
  1. Why does Naive Bayes often perform well in high-dimensional text classification, despite the naive independence assumption?
  2. How do smoothing parameters (like alpha) influence Naive Bayes performance?
  3. 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

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

  2. 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).

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

  1. (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)?

Bayes’ Rule and Naive Bayes Study Guide - Part 1

I. Fundamentals of Bayes’ Rule

Core Equation

Bayes’ Rule is expressed as:

P(A|B) = P(B|A) × P(A) / P(B)

Where: - P(A|B) = Posterior Probability - P(B|A) = Likelihood - P(A) = Prior Probability - P(B) = Evidence/Normalization

Components Breakdown:

  1. Posterior Distribution - P(A|B)
    • The probability of event A occurring given B has occurred
    • What we’re typically trying to calculate
    • Updated probability after considering evidence
  2. Prior - P(A)
    • Initial probability before considering new evidence
    • Based on previous knowledge or assumptions
    • Starting point for Bayesian inference

II. Basic Python Implementation (CPU)

import numpy as np
from sklearn.naive_bayes import GaussianNB

class BayesianCalculator:
    def simple_bayes(self, prior_a, likelihood_b_given_a, evidence_b):
        """
        Calculate posterior probability using Bayes' Rule
        """
        posterior = (likelihood_b_given_a * prior_a) / evidence_b
        return posterior

    def naive_bayes_example(self):
        # Sample data
        X = np.array([[1, 2], [2, 3], [3, 4], [4, 5]])
        y = np.array([0, 0, 1, 1])
        
        # Initialize and train model
        model = GaussianNB()
        model.fit(X, y)
        
        # Make prediction
        prediction = model.predict([[2, 3]])
        return prediction

III. GPU Implementation with CuPy

import cupy as cp

class GPUBayesianCalculator:
    def __init__(self):
        self.device = 'gpu' if cp.cuda.is_available() else 'cpu'
    
    def gpu_naive_bayes(self, X, y):
        """
        GPU-accelerated Naive Bayes implementation
        """
        if self.device == 'gpu':
            X = cp.array(X)
            y = cp.array(y)
            
            # Calculate class priors
            classes = cp.unique(y)
            class_probs = cp.array([cp.mean(y == c) for c in classes])
            
            return class_probs.get()  # Transfer back to CPU
        else:
            return "GPU not available"

Bayes’ Rule and Naive Bayes Study Guide - Part 2

IV. Naive Bayes Core Concepts

Critical Assumption

The fundamental assumption of Naive Bayes is feature independence. Mathematically:

P(X₁,X₂,...,Xₙ|Y) = P(X₁|Y) × P(X₂|Y) × ... × P(Xₙ|Y)

Multiclass Classification

For a problem with k classes, we calculate:

import numpy as np
from sklearn.naive_bayes import MultinomialNB

class MulticlassNaiveBayes:
    def multiclass_example(self):
        # Example with multiple classes
        X = np.array([[1,2], [2,3], [3,4], [4,5], [5,6]])
        y = np.array([0, 1, 2, 1, 2])  # Three classes: 0, 1, 2
        
        # Initialize and train
        clf = MultinomialNB()
        clf.fit(X, y)
        
        # Predict probabilities for each class
        probs = clf.predict_proba([[3,4]])
        # Returns probability for each class
        return probs

V. Advanced Implementation with GPU Acceleration

import cupy as cp
from cupy.linalg import norm

class GPUNaiveBayes:
    def __init__(self):
        self.classes_ = None
        self.class_priors_ = None
        self.class_means_ = None
        self.class_vars_ = None

    def fit(self, X, y):
        if not cp.cuda.is_available():
            raise RuntimeError("CUDA is not available")
            
        # Convert to GPU arrays
        X = cp.array(X)
        y = cp.array(y)
        
        self.classes_ = cp.unique(y)
        n_classes = len(self.classes_)
        n_features = X.shape[1]
        
        # Initialize parameters
        self.class_priors_ = cp.zeros(n_classes)
        self.class_means_ = cp.zeros((n_classes, n_features))
        self.class_vars_ = cp.zeros((n_classes, n_features))
        
        # Calculate parameters for each class
        for i, c in enumerate(self.classes_):
            X_c = X[y == c]
            self.class_priors_[i] = X_c.shape[0] / X.shape[0]
            self.class_means_[i] = X_c.mean(axis=0)
            self.class_vars_[i] = X_c.var(axis=0)
            
        return self

VI. Comparison with Linear Regression

Key Differences: 1. Purpose - Naive Bayes: Classification - Linear Regression: Continuous value prediction

  1. Mathematical Foundation

    Naive Bayes: P(Y|X) ∝ P(X|Y)P(Y)
    Linear Regression: Y = βX + ε
  2. Output Type

    • Naive Bayes: Probabilities for each class
    • Linear Regression: Continuous values

VII. Key Takeaways

  1. Naive Bayes derives its power from Bayes’ theorem while assuming feature independence
  2. It naturally handles multiclass problems without special modifications
  3. GPU implementation can significantly speed up calculations for large datasets

VIII. Practice Questions

  1. How does the independence assumption in Naive Bayes affect its performance on real-world datasets where features are often correlated?

  2. In what scenarios would you choose Naive Bayes over other classification algorithms?

  3. How does the computational complexity of Naive Bayes compare between CPU and GPU implementations for different dataset sizes?

Bayes’ Rule and Naive Bayes Study Guide - Part 3

IX. Practical Applications and Use Cases

Common Applications

  1. Text Classification
  2. Spam Detection
  3. Sentiment Analysis
  4. Medical Diagnosis

Implementation Example for Text Classification

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
import cupy as cp

class TextClassificationNB:
    def __init__(self, use_gpu=False):
        self.vectorizer = CountVectorizer()
        self.classifier = MultinomialNB()
        self.use_gpu = use_gpu and cp.cuda.is_available()
        
    def preprocess_text(self, texts):
        # Convert text to numerical features
        X = self.vectorizer.fit_transform(texts)
        if self.use_gpu:
            return cp.array(X.toarray())
        return X
    
    def train(self, texts, labels):
        X = self.preprocess_text(texts)
        if self.use_gpu:
            labels = cp.array(labels)
        self.classifier.fit(X, labels)
        
    def predict(self, new_texts):
        X = self.vectorizer.transform(new_texts)
        if self.use_gpu:
            X = cp.array(X.toarray())
        return self.classifier.predict(X)

X. Performance Optimization Tips

1. Data Preprocessing

class DataPreprocessor:
    def __init__(self):
        self.scaler = None
        
    def handle_missing_values(self, X):
        # Replace missing values with mean
        return np.nan_to_num(X, nan=np.nanmean(X))
        
    def normalize_features(self, X):
        # Log transformation for skewed features
        return np.log1p(X)
        
    def handle_categorical_features(self, X):
        # One-hot encoding
        return pd.get_dummies(X).values

2. GPU Memory Management

class GPUMemoryManager:
    def __init__(self):
        self.memory_pool = cp.cuda.MemoryPool()
        
    def __enter__(self):
        cp.cuda.set_allocator(self.memory_pool.malloc)
        
    def __exit__(self, *args):
        self.memory_pool.free_all_blocks()

XI. Common Pitfalls and Solutions

1. Zero Probability Problem

class LaplaceSmoothingNB:
    def __init__(self, alpha=1.0):
        self.alpha = alpha  # Smoothing parameter
        
    def calculate_probability(self, feature_counts, total_counts):
        # Add smoothing to prevent zero probabilities
        return (feature_counts + self.alpha) / (total_counts + self.alpha * len(feature_counts))

2. Numerical Stability

def log_sum_exp(x):
    """Numerically stable log sum exp."""
    max_x = np.max(x)
    return max_x + np.log(np.sum(np.exp(x - max_x)))

XII. Advanced Techniques

1. Feature Selection

from sklearn.feature_selection import SelectKBest, chi2

class FeatureSelector:
    def __init__(self, k=10):
        self.selector = SelectKBest(chi2, k=k)
        
    def select_features(self, X, y):
        return self.selector.fit_transform(X, y)

2. Cross-Validation

from sklearn.model_selection import cross_val_score

def evaluate_model(model, X, y, cv=5):
    scores = cross_val_score(model, X, y, cv=cv)
    return {
        'mean_accuracy': scores.mean(),
        'std_accuracy': scores.std()
    }

XIII. Final Key Takeaways

  1. Understanding the independence assumption is crucial for effective implementation
  2. GPU acceleration can provide significant speedup for large datasets
  3. Proper preprocessing and handling of edge cases is essential for robust models

XIV. Additional Practice Questions

  1. How would you handle highly imbalanced datasets when using Naive Bayes?
  2. What techniques can be used to determine the optimal smoothing parameter?
  3. In what scenarios might GPU acceleration actually slow down computation?

XV. Study Tips

  1. Focus on understanding the probabilistic foundations
  2. Practice with real-world datasets
  3. Experiment with both CPU and GPU implementations
  4. Pay attention to preprocessing steps
  5. Understand the limitations and assumptions

Three thought-provoking and logically comprehensive questions about Naive Bayes:

  1. “Why does Naive Bayes often perform well in high-dimensional text classification, despite the naive independence assumption?”
    • Addresses the fundamental paradox of Naive Bayes
    • Requires understanding of both theory and practical applications
    • Challenges students to think about the relationship between model assumptions and real-world performance
    • Connects to real-world applications in text classification
  2. “How does the independence assumption in Naive Bayes affect its performance on real-world datasets where features are often correlated?”
    • Forces consideration of the core assumptions
    • Requires analysis of real-world implications
    • Encourages critical thinking about model limitations
    • Connects theoretical concepts to practical applications
  3. “In what scenarios would you choose Naive Bayes over other classification algorithms?”
    • Requires comparative analysis
    • Demands understanding of various algorithms’ strengths and weaknesses
    • Focuses on practical decision-making
    • Encourages consideration of real-world constraints and requirements

Takeaways: 1. The Independence Assumption’s Paradox - Despite its “naive” assumption that features are independent (which is rarely true in real-world data), Naive Bayes often performs surprisingly well in practice - This is particularly true in text classification and high-dimensional problems - Understanding: The model’s simplicity and efficiency often outweigh the limitations of the independence assumption - Mathematical representation: P(x₁,x₂|y) = P(x₁|y) × P(x₂|y)

  1. Probabilistic Foundation and Scalability
    • Naive Bayes is fundamentally a probabilistic classifier based on Bayes’ Theorem
    • It’s highly scalable and can handle large datasets efficiently
    • Training is O(n) complexity, making it one of the fastest classifiers
    • Key equation: P(y|x) = P(x|y)P(y)/P(x)
    • This makes it particularly valuable for real-time applications and large-scale classification tasks
  2. Susceptibility to Zero Probability Problem
    • When a feature value in the test data never appears in the training data, it leads to zero probability
    • This is solved through smoothing techniques (like Laplace/Additive smoothing)
    • Critical for practical implementation: P(x|y) = (count(x,y) + α)/(count(y) + α|V|)
    • Understanding this limitation and its solutions is crucial for effective implementation

These takeaways are particularly important because they: - Cover both theoretical foundations and practical implications - Address common challenges and solutions - Explain why and when the algorithm works well - Provide essential knowledge for real-world applications - Help in making informed decisions about when to use Naive Bayes

Each of these points affects: - Model selection decisions - Implementation approaches - Performance optimization - Problem-solving strategies - Understanding of results

LS0tDQp0aXRsZTogIjczMzMgTW9kdWxlIDYgLSBOYWl2ZSBCYXllcyINCmF1dGhvcjogIkplc3NpY2EgTWNQaGF1bCAtIGZvciBRVFcgU3ByaW5nIDIwMjUuIERyLiBTbGF0ZXIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCi0tLQ0KDQojICoqU3R1ZHkgR3VpZGU6IFFUVyBNb2R1bGUgNiAtIE5haXZlIEJheWVzKioNCg0KIyMgKioxLiBCYXllcycgUnVsZTogT3ZlcnZpZXcgYW5kIE1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbioqDQpCYXllcycgUnVsZSBpcyBhIGZ1bmRhbWVudGFsIHRoZW9yZW0gaW4gcHJvYmFiaWxpdHkgdGhhdCBoZWxwcyB1cGRhdGUgcHJpb3IgYmVsaWVmcyBiYXNlZCBvbiBuZXcgZXZpZGVuY2UuIEl0IGlzIHRoZSBiYWNrYm9uZSBvZiBCYXllc2lhbiBpbmZlcmVuY2UgYW5kIHBsYXlzIGEgY3J1Y2lhbCByb2xlIGluIHByb2JhYmlsaXN0aWMgbW9kZWxpbmcuDQoNCiMjIyAqKk1hdGhlbWF0aWNhbCBGb3JtdWxhKioNClxbDQpQKEEgfCBCKSA9IFxmcmFje1AoQiB8IEEpIFAoQSl9e1AoQil9DQpcXQ0KV2hlcmU6DQotIFwoIFAoQSB8IEIpIFwpID0gKipQb3N0ZXJpb3IgUHJvYmFiaWxpdHkqKiAodXBkYXRlZCBiZWxpZWYgYWZ0ZXIgc2VlaW5nIGV2aWRlbmNlKQ0KLSBcKCBQKEIgfCBBKSBcKSA9ICoqTGlrZWxpaG9vZCoqIChob3cgbGlrZWx5IEIgb2NjdXJzIGdpdmVuIEEgaXMgdHJ1ZSkNCi0gXCggUChBKSBcKSA9ICoqUHJpb3IgUHJvYmFiaWxpdHkqKiAoaW5pdGlhbCBiZWxpZWYgYmVmb3JlIHNlZWluZyBldmlkZW5jZSkNCi0gXCggUChCKSBcKSA9ICoqTm9ybWFsaXphdGlvbiBGYWN0b3IqKiAoZW5zdXJlcyB2YWxpZCBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24pDQoNCiMjIyMgKipFeGFtcGxlOiBEcnVnIFRlc3RpbmcqKg0KR2l2ZW46DQotICoqVHJ1ZSBwb3NpdGl2ZSByYXRlKiogPSA5OSUgKFwoIFAoQiB8IEEpID0gMC45OSBcKSkNCi0gKipGYWxzZSBwb3NpdGl2ZSByYXRlKiogPSAzJSAoXCggUChCIHwgXG5lZyBBKSA9IDAuMDMgXCkpDQotICoqUHJldmFsZW5jZSBvZiBkcnVnIHVzZSoqID0gNSUgKFwoIFAoQSkgPSAwLjA1IFwpKQ0KDQpUaGUgcHJvYmFiaWxpdHkgdGhhdCBhIHBlcnNvbiB3aG8gdGVzdGVkIHBvc2l0aXZlIGlzIGFjdHVhbGx5IGEgZHJ1ZyB1c2VyIGlzOg0KXFsNClAoQSB8IEIpID0gXGZyYWN7KDAuOTkgXHRpbWVzIDAuMDUpfXsoMC45OSBcdGltZXMgMC4wNSkgKyAoMC4wMyBcdGltZXMgMC45NSl9DQpcXQ0KDQpVc2luZyBQeXRob246DQoNCmBgYHB5dGhvbg0KUF9BID0gMC4wNSAgIyBQcmlvciBwcm9iYWJpbGl0eSBvZiBiZWluZyBhIGRydWcgdXNlcg0KUF9CX2dpdmVuX0EgPSAwLjk5ICAjIFRydWUgcG9zaXRpdmUgcmF0ZQ0KUF9CX2dpdmVuX25vdF9BID0gMC4wMyAgIyBGYWxzZSBwb3NpdGl2ZSByYXRlDQpQX25vdF9BID0gMSAtIFBfQQ0KDQpQX0IgPSAoUF9CX2dpdmVuX0EgKiBQX0EpICsgKFBfQl9naXZlbl9ub3RfQSAqIFBfbm90X0EpICAjIE5vcm1hbGl6YXRpb24gZmFjdG9yDQpQX0FfZ2l2ZW5fQiA9IChQX0JfZ2l2ZW5fQSAqIFBfQSkgLyBQX0IgICMgQmF5ZXMnIFJ1bGUNCg0KcHJpbnQoZiJQcm9iYWJpbGl0eSBvZiBiZWluZyBhIGRydWcgdXNlciBnaXZlbiBhIHBvc2l0aXZlIHRlc3Q6IHtQX0FfZ2l2ZW5fQjouM2Z9IikNCmBgYA0KDQotLS0NCg0KIyMgKioyLiBCYXllcycgUnVsZSBmb3IgTXVsdGl2YXJpYWJsZXMqKg0KV2hlbiB0aGVyZSBhcmUgbXVsdGlwbGUgZXZpZGVuY2UgdmFyaWFibGVzIFwoIEIsIEMsIEQsIC4uLiBcKSwgd2UgZ2VuZXJhbGl6ZSBCYXllcycgUnVsZToNClxbDQpQKEEgfCBCLCBDLCBEKSA9IFxmcmFje1AoQiwgQywgRCB8IEEpIFAoQSl9e1AoQiwgQywgRCl9DQpcXQ0KQnkgYXNzdW1pbmcgY29uZGl0aW9uYWwgaW5kZXBlbmRlbmNlIChuYWl2ZSBhc3N1bXB0aW9uKSwgd2Ugc2ltcGxpZnk6DQpcWw0KUChBIHwgQiwgQywgRCkgXHByb3B0byBQKEEpIFAoQiB8IEEpIFAoQyB8IEEpIFAoRCB8IEEpDQpcXQ0KDQpUaGlzIGZvcm1zIHRoZSBmb3VuZGF0aW9uIG9mICoqTmFpdmUgQmF5ZXMgQ2xhc3NpZmljYXRpb24qKi4NCg0KIyMjIyAqKlB5dGhvbiBFeGFtcGxlIGZvciBUZXh0IENsYXNzaWZpY2F0aW9uKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBNdWx0aW5vbWlhbE5CDQpmcm9tIHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHQgaW1wb3J0IENvdW50VmVjdG9yaXplcg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBmZXRjaF8yMG5ld3Nncm91cHMNCg0KIyBMb2FkIGRhdGFzZXQNCm5ld3Nncm91cHMgPSBmZXRjaF8yMG5ld3Nncm91cHMoc3Vic2V0PSd0cmFpbicsIHJlbW92ZT0oJ2hlYWRlcnMnLCAnZm9vdGVycycsICdxdW90ZXMnKSkNCnZlY3Rvcml6ZXIgPSBDb3VudFZlY3Rvcml6ZXIoKQ0KWCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShuZXdzZ3JvdXBzLmRhdGEpICAjIENvbnZlcnQgdGV4dCB0byBiYWctb2Ytd29yZHMNCnkgPSBuZXdzZ3JvdXBzLnRhcmdldA0KDQojIFRyYWluIE5haXZlIEJheWVzIENsYXNzaWZpZXINCm5iID0gTXVsdGlub21pYWxOQigpDQpuYi5maXQoWCwgeSkNCg0KIyBQcmVkaWN0IG9uIG5ldyBkYXRhDQpzYW1wbGVfdGV4dCA9IFsiUXVhbnR1bSBjb21wdXRpbmcgaXMgdGhlIGZ1dHVyZSBvZiBBSSJdDQpYX3NhbXBsZSA9IHZlY3Rvcml6ZXIudHJhbnNmb3JtKHNhbXBsZV90ZXh0KQ0KcHJlZGljdGlvbiA9IG5iLnByZWRpY3QoWF9zYW1wbGUpDQpwcmludChmIlByZWRpY3RlZCBjYXRlZ29yeToge25ld3Nncm91cHMudGFyZ2V0X25hbWVzW3ByZWRpY3Rpb25bMF1dfSIpDQpgYGANCg0KLS0tDQoNCiMjICoqMy4gQmF5ZXMnIFJ1bGUgZm9yIENvbnRpbnVvdXMgVmFyaWFibGVzKioNCkZvciBjb250aW51b3VzIHZhcmlhYmxlcyAoZS5nLiwgdGVtcGVyYXR1cmUsIHdlaWdodCksIHdlIHVzZSAqKlByb2JhYmlsaXR5IERlbnNpdHkgRnVuY3Rpb25zIChQREZzKSoqIHN1Y2ggYXMgdGhlICoqR2F1c3NpYW4gRGlzdHJpYnV0aW9uKio6DQpcWw0KUChYID0geCkgPSBcZnJhY3sxfXtcc2lnbWEgXHNxcnR7MlxwaX19IGVeey1cZnJhY3soeCAtIFxtdSleMn17MlxzaWdtYV4yfX0NClxdDQp3aGVyZSBcKCBcbXUgXCkgaXMgdGhlIG1lYW4gYW5kIFwoIFxzaWdtYSBcKSBpcyB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLg0KDQojIyMjICoqUHl0aG9uIEltcGxlbWVudGF0aW9uKioNCmBgYHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQpmcm9tIHNjaXB5LnN0YXRzIGltcG9ydCBub3JtDQoNCiMgQXNzdW1lIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuPTcwLCBzdGQ9MTANCm11LCBzaWdtYSA9IDcwLCAxMA0KeF92YWx1ZSA9IDc1DQpwcm9iYWJpbGl0eSA9IG5vcm0ucGRmKHhfdmFsdWUsIG11LCBzaWdtYSkNCg0KcHJpbnQoZiJQcm9iYWJpbGl0eSBkZW5zaXR5IG9mIHdlaWdodCA3NSBnaXZlbiBtZWFuPTcwIGFuZCBzdGQ9MTA6IHtwcm9iYWJpbGl0eTouNGZ9IikNCmBgYA0KDQotLS0NCg0KIyMgKio0LiBOYWl2ZSBCYXllcyBJbXBsZW1lbnRhdGlvbiBmb3IgVGV4dCBDbGFzc2lmaWNhdGlvbioqDQpVc2luZyAqKkNvdW50VmVjdG9yaXplcioqIGFuZCAqKlRGLUlERioqLCB3ZSB0cmFpbiBhICoqTXVsdGlub21pYWwgTmFpdmUgQmF5ZXMgbW9kZWwqKi4NCg0KIyMjIyAqKlB5dGhvbiBJbXBsZW1lbnRhdGlvbioqDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgVGZpZGZWZWN0b3JpemVyDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCg0KIyBDb252ZXJ0IGRhdGFzZXQgdXNpbmcgVEYtSURGDQp0ZmlkZl92ZWN0b3JpemVyID0gVGZpZGZWZWN0b3JpemVyKCkNClhfdGZpZGYgPSB0ZmlkZl92ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0obmV3c2dyb3Vwcy5kYXRhKQ0KDQojIFRyYWluIE5haXZlIEJheWVzIHdpdGggVEYtSURGDQpuYl90ZmlkZiA9IE11bHRpbm9taWFsTkIoKQ0KbmJfdGZpZGYuZml0KFhfdGZpZGYsIHkpDQoNCiMgUHJlZGljdCBvbiB0ZXN0IGRhdGENCm5ld3Nncm91cHNfdGVzdCA9IGZldGNoXzIwbmV3c2dyb3VwcyhzdWJzZXQ9J3Rlc3QnLCByZW1vdmU9KCdoZWFkZXJzJywgJ2Zvb3RlcnMnLCAncXVvdGVzJykpDQpYX3Rlc3RfdGZpZGYgPSB0ZmlkZl92ZWN0b3JpemVyLnRyYW5zZm9ybShuZXdzZ3JvdXBzX3Rlc3QuZGF0YSkNCnlfdGVzdCA9IG5ld3Nncm91cHNfdGVzdC50YXJnZXQNCg0KcHJlZGljdGlvbnMgPSBuYl90ZmlkZi5wcmVkaWN0KFhfdGVzdF90ZmlkZikNCmFjY3VyYWN5ID0gYWNjdXJhY3lfc2NvcmUoeV90ZXN0LCBwcmVkaWN0aW9ucykNCnByaW50KGYiTmFpdmUgQmF5ZXMgVEYtSURGIEFjY3VyYWN5OiB7YWNjdXJhY3k6LjJmfSIpDQpgYGANCg0KLS0tDQoNCiMjICoqNS4gQ1BVIHZzLiBHUFUgSW1wbGVtZW50YXRpb24qKg0KIyMjICoqQ1BVIEltcGxlbWVudGF0aW9uKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBHYXVzc2lhbk5CDQoNCiMgU2ltdWxhdGluZyBkYXRhDQpYX2NwdSA9IG5wLnJhbmRvbS5yYW5kKDEwMDAwLCAxMCkNCnlfY3B1ID0gbnAucmFuZG9tLnJhbmRpbnQoMiwgc2l6ZT0xMDAwMCkNCg0KIyBUcmFpbiBHYXVzc2lhbiBOYWl2ZSBCYXllcw0KZ25iID0gR2F1c3NpYW5OQigpDQpnbmIuZml0KFhfY3B1LCB5X2NwdSkNCmBgYA0KDQojIyMgKipHUFUgSW1wbGVtZW50YXRpb24gd2l0aCBDdVB5KioNCmBgYHB5dGhvbg0KaW1wb3J0IGN1cHkgYXMgY3ANCmZyb20gY3VtbC5uYWl2ZV9iYXllcyBpbXBvcnQgR2F1c3NpYW5OQiBhcyBjdUdhdXNzaWFuTkINCg0KIyBDb252ZXJ0IGRhdGEgdG8gR1BVDQpYX2dwdSA9IGNwLmFzYXJyYXkoWF9jcHUpDQp5X2dwdSA9IGNwLmFzYXJyYXkoeV9jcHUpDQoNCiMgVHJhaW4gb24gR1BVDQpnbmJfZ3B1ID0gY3VHYXVzc2lhbk5CKCkNCmduYl9ncHUuZml0KFhfZ3B1LCB5X2dwdSkNCmBgYA0KDQotLS0NCg0KIyMgKio2LiBCYWcgb2YgV29yZHMgRXhlcmNpc2UqKg0KQnVpbGQgYSAqKkNvdW50VmVjdG9yaXplcioqIGZyb20gc2NyYXRjaCBhbmQgY29tcGFyZSB3aXRoIGBza2xlYXJuYC4NCg0KIyMjICoqTWFudWFsIENvdW50IFZlY3Rvcml6ZXIqKg0KYGBgcHl0aG9uDQpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBDb3VudGVyDQoNCmRlZiBjb3VudF92ZWN0b3JpemVyKGNvcnB1cyk6DQogICAgdm9jYWIgPSBzZXQod29yZCBmb3IgdGV4dCBpbiBjb3JwdXMgZm9yIHdvcmQgaW4gdGV4dC5zcGxpdCgpKQ0KICAgIHZlY3Rvcml6ZWQgPSBbe3dvcmQ6IHRleHQuc3BsaXQoKS5jb3VudCh3b3JkKSBmb3Igd29yZCBpbiB2b2NhYn0gZm9yIHRleHQgaW4gY29ycHVzXQ0KICAgIHJldHVybiB2ZWN0b3JpemVkLCBsaXN0KHZvY2FiKQ0KDQojIFNhbXBsZSBkYXRhDQpjb3JwdXMgPSBbIk5haXZlIEJheWVzIGlzIHNpbXBsZSIsICJCYXllcyBtb2RlbHMgYXJlIHBvd2VyZnVsIl0NCnZlY3Rvcml6ZWRfZGF0YSwgdm9jYWIgPSBjb3VudF92ZWN0b3JpemVyKGNvcnB1cykNCg0KcHJpbnQoZiJWb2NhYnVsYXJ5OiB7dm9jYWJ9IikNCnByaW50KGYiVmVjdG9yaXplZCBEYXRhOiB7dmVjdG9yaXplZF9kYXRhfSIpDQpgYGANCg0KKipDb21wYXJpc29uIHdpdGggU2Npa2l0LWxlYXJuKioNCmBgYHB5dGhvbg0KY3YgPSBDb3VudFZlY3Rvcml6ZXIoKQ0KWF9jdiA9IGN2LmZpdF90cmFuc2Zvcm0oY29ycHVzKQ0KcHJpbnQoZiJTa2xlYXJuIENvdW50VmVjdG9yaXplciBPdXRwdXQ6XG57WF9jdi50b2FycmF5KCl9IikNCmBgYA0KDQotLS0NCg0KIyAqKktleSBUYWtlYXdheXMqKg0KMS4gKipCYXllcycgUnVsZSBpcyBhIHBvd2VyZnVsIHByb2JhYmlsaXN0aWMgdG9vbCoqIGZvciB1cGRhdGluZyBiZWxpZWZzIGJhc2VkIG9uIGV2aWRlbmNlLg0KMi4gKipOYWl2ZSBCYXllcyBhc3N1bWVzIGZlYXR1cmUgaW5kZXBlbmRlbmNlKiogYnV0IHN0aWxsIHBlcmZvcm1zIHdlbGwgaW4gbWFueSBjbGFzc2lmaWNhdGlvbiB0YXNrcy4NCjMuICoqSXQgaXMgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudCoqIGFuZCB3ZWxsLXN1aXRlZCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbiwgc3BhbSBkZXRlY3Rpb24sIGFuZCBtZWRpY2FsIGRpYWdub3N0aWNzLg0KDQotLS0NCg0KIyAqKkRpc2N1c3Npb24gUXVlc3Rpb25zKioNCjEuICoqV2hhdCBhcmUgdGhlIGxpbWl0YXRpb25zIG9mIHRoZSBuYWl2ZSBhc3N1bXB0aW9uIGluIE5haXZlIEJheWVzPyoqDQoyLiAqKldoeSBkb2VzIE5haXZlIEJheWVzIHBlcmZvcm0gd2VsbCBpbiB0ZXh0IGNsYXNzaWZpY2F0aW9uIGRlc3BpdGUgZmVhdHVyZSBpbmRlcGVuZGVuY2U/KioNCjMuICoqSG93IGNhbiB3ZSBpbXByb3ZlIE5haXZlIEJheWVzIGZvciBjb250aW51b3VzIGRhdGEgZGlzdHJpYnV0aW9ucz8qKg0KDQotLS0NCg0KIyMjICoqQmFnIG9mIFdvcmRzIEV4ZXJjaXNlOiBTdGVwLWJ5LVN0ZXAgV2Fsa3Rocm91Z2gqKg0KDQpUaGlzIGV4ZXJjaXNlIGludm9sdmVzIG1hbnVhbGx5IGltcGxlbWVudGluZyAqKkNvdW50VmVjdG9yaXplcioqLCB3aGljaCBpcyBhIGZ1bmRhbWVudGFsIHByZXByb2Nlc3Npbmcgc3RlcCBpbiBOYXR1cmFsIExhbmd1YWdlIFByb2Nlc3NpbmcgKE5MUCkuIFRoZSBnb2FsIGlzIHRvICoqY29udmVydCB0ZXh0IGludG8gYSBudW1lcmljYWwgcmVwcmVzZW50YXRpb24qKiBiZWZvcmUgYXBwbHlpbmcgbWFjaGluZSBsZWFybmluZyBtb2RlbHMuDQoNCi0tLQ0KDQojIyAqKlN0ZXAgMTogVW5kZXJzdGFuZGluZyBDb3VudFZlY3Rvcml6ZXIqKg0KYENvdW50VmVjdG9yaXplcmAgaXMgYSBtZXRob2Qgb2YgY29udmVydGluZyB0ZXh0IGludG8gYSAqKmJhZy1vZi13b3JkcyAoQm9XKSoqIHJlcHJlc2VudGF0aW9uLCB3aGVyZToNCi0gRWFjaCB1bmlxdWUgd29yZCBpbiB0aGUgZGF0YXNldCBpcyBhc3NpZ25lZCBhbiBpbmRleCBpbiBhIHZvY2FidWxhcnkuDQotIFRoZSB0ZXh0IGRhdGEgaXMgdHJhbnNmb3JtZWQgaW50byBhIG1hdHJpeCwgd2hlcmUgZWFjaCByb3cgcmVwcmVzZW50cyBhIGRvY3VtZW50IGFuZCBlYWNoIGNvbHVtbiByZXByZXNlbnRzIGEgd29yZC4NCi0gVGhlIHZhbHVlcyBpbiB0aGUgbWF0cml4IGluZGljYXRlIGhvdyBtYW55IHRpbWVzIGEgd29yZCBhcHBlYXJzIGluIGEgZG9jdW1lbnQuDQoNCiMjIyAqKkV4YW1wbGUqKg0KRm9yIHRoZSB0ZXh0Og0KYGBgdGV4dA0KIk5haXZlIEJheWVzIGlzIHNpbXBsZSINCiJCYXllcyBtb2RlbHMgYXJlIHBvd2VyZnVsIg0KYGBgDQpUaGUgKipiYWctb2Ytd29yZHMgcmVwcmVzZW50YXRpb24qKiB3b3VsZCBjb3VudCB0aGUgb2NjdXJyZW5jZXMgb2YgZWFjaCB3b3JkIGFjcm9zcyBkb2N1bWVudHMuDQoNCi0tLQ0KDQojIyAqKlN0ZXAgMjogTG9hZGluZyB0aGUgMjAgTmV3c2dyb3VwIERhdGFzZXQqKg0KVGhlICoqMjAgTmV3c2dyb3VwcyBkYXRhc2V0KiogaXMgYSBjb2xsZWN0aW9uIG9mIHRleHQgZG9jdW1lbnRzIGNhdGVnb3JpemVkIGludG8gMjAgdG9waWNzLg0KDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgZmV0Y2hfMjBuZXdzZ3JvdXBzDQoNCiMgTG9hZCBkYXRhc2V0IHdpdGhvdXQgaGVhZGVycywgZm9vdGVycywgYW5kIHF1b3Rlcw0KZGF0YSA9IGZldGNoXzIwbmV3c2dyb3VwcyhyZW1vdmU9KCdoZWFkZXJzJywgJ2Zvb3RlcnMnLCAncXVvdGVzJykpDQpkb2N1bWVudHMgPSBkYXRhLmRhdGFbOjVdICAjIExvYWQgb25seSBmaXJzdCA1IGRvY3VtZW50cyBmb3IgZGVtb25zdHJhdGlvbg0KDQojIERpc3BsYXkgc2FtcGxlIGRhdGENCmZvciBpLCBkb2MgaW4gZW51bWVyYXRlKGRvY3VtZW50cyk6DQogICAgcHJpbnQoZiJEb2N1bWVudCB7aSsxfTpcbntkb2N9XG57Jy0nKjUwfSIpDQpgYGANClRoaXMgZGF0YXNldCBjb250YWlucyAqKnJhdyB0ZXh0KiogdGhhdCBuZWVkcyB0byBiZSBwcm9jZXNzZWQgaW50byBhIG51bWVyaWNhbCBmb3JtYXQuDQoNCi0tLQ0KDQojIyAqKlN0ZXAgMzogSW1wbGVtZW50aW5nIE1hbnVhbCBDb3VudFZlY3Rvcml6ZXIqKg0KIyMjICoqMS4gVG9rZW5pemF0aW9uKioNCkNvbnZlcnQgdGV4dCBpbnRvIHdvcmRzIGJ5IHNwbGl0dGluZyBvbiBzcGFjZXMgYW5kIHJlbW92aW5nIHB1bmN0dWF0aW9uLg0KDQojIyMgKioyLiBCdWlsZGluZyBWb2NhYnVsYXJ5KioNCkNyZWF0ZSBhIGRpY3Rpb25hcnkgbWFwcGluZyBlYWNoIHVuaXF1ZSB3b3JkIHRvIGFuIGluZGV4Lg0KDQojIyMgKiozLiBDb25zdHJ1Y3RpbmcgdGhlIENvdW50IE1hdHJpeCoqDQpGb3IgZWFjaCBkb2N1bWVudCwgY291bnQgaG93IG1hbnkgdGltZXMgZWFjaCB3b3JkIGFwcGVhcnMuDQoNCmBgYHB5dGhvbg0KZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcg0KaW1wb3J0IHJlDQoNCmRlZiBjdXN0b21fY291bnRfdmVjdG9yaXplcihjb3JwdXMpOg0KICAgICMgUHJlcHJvY2VzczogUmVtb3ZlIHB1bmN0dWF0aW9uIGFuZCBsb3dlcmNhc2UNCiAgICBjb3JwdXNfY2xlYW4gPSBbcmUuc3ViKHInXFcrJywgJyAnLCBkb2MpLmxvd2VyKCkgZm9yIGRvYyBpbiBjb3JwdXNdDQoNCiAgICAjIFRva2VuaXplDQogICAgdG9rZW5pemVkX2RvY3MgPSBbZG9jLnNwbGl0KCkgZm9yIGRvYyBpbiBjb3JwdXNfY2xlYW5dDQoNCiAgICAjIEJ1aWxkIHZvY2FidWxhcnkNCiAgICB2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCh3b3JkIGZvciBkb2MgaW4gdG9rZW5pemVkX2RvY3MgZm9yIHdvcmQgaW4gZG9jKSkNCiAgICB3b3JkX2luZGV4ID0ge3dvcmQ6IGlkeCBmb3IgaWR4LCB3b3JkIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCg0KICAgICMgQ291bnQgd29yZCBvY2N1cnJlbmNlcw0KICAgIG1hdHJpeCA9IFtdDQogICAgZm9yIGRvYyBpbiB0b2tlbml6ZWRfZG9jczoNCiAgICAgICAgd29yZF9jb3VudHMgPSBDb3VudGVyKGRvYykNCiAgICAgICAgcm93ID0gW3dvcmRfY291bnRzLmdldCh3b3JkLCAwKSBmb3Igd29yZCBpbiB2b2NhYnVsYXJ5XQ0KICAgICAgICBtYXRyaXguYXBwZW5kKHJvdykNCg0KICAgIHJldHVybiB2b2NhYnVsYXJ5LCBtYXRyaXgNCg0KIyBBcHBseSBjdXN0b20gQ291bnRWZWN0b3JpemVyDQp2b2NhYiwgY291bnRfbWF0cml4ID0gY3VzdG9tX2NvdW50X3ZlY3Rvcml6ZXIoZG9jdW1lbnRzWzo1XSkNCg0KIyBEaXNwbGF5IHJlc3VsdHMNCnByaW50KCJWb2NhYnVsYXJ5OlxuIiwgdm9jYWIpDQpwcmludCgiXG5Db3VudCBNYXRyaXg6XG4iLCBjb3VudF9tYXRyaXgpDQpgYGANCg0KLS0tDQoNCiMjICoqU3RlcCA0OiBDb21wYXJpbmcgd2l0aCBTa2xlYXJuJ3MgQ291bnRWZWN0b3JpemVyKioNClRvIHZhbGlkYXRlIG91ciBpbXBsZW1lbnRhdGlvbiwgd2UgY29tcGFyZSBpdCBhZ2FpbnN0IFNjaWtpdC1sZWFybidzIGBDb3VudFZlY3Rvcml6ZXJgLg0KDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyDQoNCiMgQXBwbHkgU2Npa2l0LWxlYXJuJ3MgQ291bnRWZWN0b3JpemVyDQp2ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKCkNClggPSB2ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzWzo1XSkNCg0KIyBEaXNwbGF5IHZvY2FidWxhcnkNCnByaW50KCJcblNjaWtpdC1sZWFybiBWb2NhYnVsYXJ5OlxuIiwgdmVjdG9yaXplci5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkNCg0KIyBEaXNwbGF5IHRyYW5zZm9ybWVkIHRleHQgcmVwcmVzZW50YXRpb24NCnByaW50KCJcblNjaWtpdC1sZWFybiBDb3VudCBNYXRyaXg6XG4iLCBYLnRvYXJyYXkoKSkNCmBgYA0KDQotLS0NCg0KIyMgKipTdGVwIDU6IEtleSBEaWZmZXJlbmNlcyBCZXR3ZWVuIE1hbnVhbCBhbmQgU2tsZWFybiBDb3VudFZlY3Rvcml6ZXIqKg0KfCBGZWF0dXJlIHwgQ3VzdG9tIEltcGxlbWVudGF0aW9uIHwgU2tsZWFybiBDb3VudFZlY3Rvcml6ZXIgfA0KfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKlRva2VuaXphdGlvbioqIHwgQmFzaWMgc3BhY2Utc3BsaXR0aW5nIGFuZCByZWdleCB8IEhhbmRsZXMgc3BlY2lhbCBjYXNlcyAoc3RvcHdvcmRzLCBuLWdyYW1zLCBldGMuKSB8DQp8ICoqVm9jYWJ1bGFyeSoqIHwgQnVpbHQgbWFudWFsbHkgdXNpbmcgYSBzZXQgfCBBdXRvbWF0aWNhbGx5IG9wdGltaXplZCB8DQp8ICoqQ291bnQgTWF0cml4KiogfCBDcmVhdGVkIHdpdGggYENvdW50ZXJgIHwgT3B0aW1pemVkIHNwYXJzZSBtYXRyaXggfA0KfCAqKlNwZWVkKiogfCBTbG93ZXIgZm9yIGxhcmdlIGRhdGFzZXRzIHwgSGlnaGx5IG9wdGltaXplZCBDKysgYmFja2VuZCB8DQoNCi0tLQ0KDQojIyAqKlN0ZXAgNjogUGVyZm9ybWFuY2UgQ29uc2lkZXJhdGlvbnMqKg0KU2luY2UgYENvdW50VmVjdG9yaXplcmAgaXMgYSAqKkNQVS1iYXNlZCBtZXRob2QqKiwgbGV04oCZcyBvcHRpbWl6ZSBpdCBmb3IgYSAqKkdQVSoqIGltcGxlbWVudGF0aW9uIHVzaW5nICoqQ3VQeSoqLg0KDQpgYGBweXRob24NCmltcG9ydCBjdXB5IGFzIGNwDQoNCmRlZiBncHVfY291bnRfdmVjdG9yaXplcihjb3JwdXMpOg0KICAgIGNvcnB1c19jbGVhbiA9IFtyZS5zdWIocidcVysnLCAnICcsIGRvYykubG93ZXIoKSBmb3IgZG9jIGluIGNvcnB1c10NCiAgICB0b2tlbml6ZWRfZG9jcyA9IFtkb2Muc3BsaXQoKSBmb3IgZG9jIGluIGNvcnB1c19jbGVhbl0NCiAgICANCiAgICB2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCh3b3JkIGZvciBkb2MgaW4gdG9rZW5pemVkX2RvY3MgZm9yIHdvcmQgaW4gZG9jKSkNCiAgICB3b3JkX2luZGV4ID0ge3dvcmQ6IGlkeCBmb3IgaWR4LCB3b3JkIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCiAgICANCiAgICBtYXRyaXggPSBjcC56ZXJvcygobGVuKHRva2VuaXplZF9kb2NzKSwgbGVuKHZvY2FidWxhcnkpKSwgZHR5cGU9Y3AuaW50MzIpDQogICAgDQogICAgZm9yIGRvY19pZHgsIGRvYyBpbiBlbnVtZXJhdGUodG9rZW5pemVkX2RvY3MpOg0KICAgICAgICB3b3JkX2NvdW50cyA9IENvdW50ZXIoZG9jKQ0KICAgICAgICBmb3Igd29yZCwgY291bnQgaW4gd29yZF9jb3VudHMuaXRlbXMoKToNCiAgICAgICAgICAgIGlmIHdvcmQgaW4gd29yZF9pbmRleDoNCiAgICAgICAgICAgICAgICBtYXRyaXhbZG9jX2lkeCwgd29yZF9pbmRleFt3b3JkXV0gPSBjb3VudA0KICAgIA0KICAgIHJldHVybiB2b2NhYnVsYXJ5LCBtYXRyaXgNCg0KIyBBcHBseSBHUFUgQ291bnRWZWN0b3JpemVyDQpncHVfdm9jYWIsIGdwdV9jb3VudF9tYXRyaXggPSBncHVfY291bnRfdmVjdG9yaXplcihkb2N1bWVudHNbOjVdKQ0KDQpwcmludCgiXG5HUFUgVm9jYWJ1bGFyeToiLCBncHVfdm9jYWIpDQpwcmludCgiXG5HUFUgQ291bnQgTWF0cml4OlxuIiwgZ3B1X2NvdW50X21hdHJpeC5nZXQoKSkNCmBgYA0KDQotLS0NCg0KIyAqKktleSBUYWtlYXdheXMqKg0KMS4gKipCYWctb2YtV29yZHMgaXMgYW4gZXNzZW50aWFsIHRleHQgcHJlcHJvY2Vzc2luZyBzdGVwKiogaW4gTkxQLCB0cmFuc2Zvcm1pbmcgdGV4dCBpbnRvIGEgbnVtZXJpY2FsIGZvcm1hdC4NCjIuICoqTmFpdmUgQmF5ZXMgY2FuIHdvcmsgZWZmZWN0aXZlbHkgb24gdGV4dCBjbGFzc2lmaWNhdGlvbioqIGRlc3BpdGUgaXRzIHNpbXBsaWZ5aW5nIGFzc3VtcHRpb25zLg0KMy4gKipHUFUgYWNjZWxlcmF0aW9uIGNhbiBpbXByb3ZlIGVmZmljaWVuY3kqKiB3aGVuIGhhbmRsaW5nIGxhcmdlLXNjYWxlIHRleHQgZGF0YXNldHMuDQoNCi0tLQ0KDQojICoqRGlzY3Vzc2lvbiBRdWVzdGlvbnMqKg0KMS4gKipXaGF0IGFyZSB0aGUgYmVuZWZpdHMgYW5kIGxpbWl0YXRpb25zIG9mIHVzaW5nIGEgQmFnLW9mLVdvcmRzIG1vZGVsPyoqDQoyLiAqKldoeSBkb2VzIFRGLUlERiBvZnRlbiBvdXRwZXJmb3JtIHNpbXBsZSBDb3VudFZlY3Rvcml6ZXIgZm9yIHRleHQgY2xhc3NpZmljYXRpb24/KioNCjMuICoqSG93IGNhbiB3ZSBpbXByb3ZlIG91ciBjdXN0b20gQ291bnRWZWN0b3JpemVyIHRvIGluY2x1ZGUgc3RvcHdvcmQgcmVtb3ZhbCBhbmQgc3RlbW1pbmc/KioNCg0KLS0tSGVyZSBpcyB5b3VyIHN1bW1hcml6ZWQgYW5kIGV4cGFuZGVkIHN0dWR5IGd1aWRlIGJhc2VkIG9uIFFUVyBNb2R1bGUgNiDigJMgTmFpdmUgQmF5ZXMsIGluY2x1ZGluZyBrZXkgY29uY2VwdHMsIG1hdGhlbWF0aWNhbCBmb3JtdWxhcywgUHl0aG9uIGNvZGUgZm9yIENQVSBhbmQgR1BVIHV0aWxpemF0aW9uLCBwbHVzIHRocmVlIGtleSB0YWtlYXdheXMgYW5kIHRocmVlIGRpc2N1c3Npb24gcXVlc3Rpb25zLiBUeXBlICJwcm9jZWVkIiBpZiB5b3UgbmVlZCBtb3JlLCBhbmQgSSB3aWxsIGNvbnRpbnVlLg0KDQpTdHVkeSBHdWlkZTogUVRXIE1vZHVsZSA2IOKAkyBOYWl2ZSBCYXllcw0KDQoxLiBPdmVydmlldyBvZiBCYXllc+KAmSBSdWxlDQpCYXllc+KAmSBSdWxlIGhlbHBzIHVwZGF0ZSBhbiBpbml0aWFsIGJlbGllZiAocHJpb3IpIGFmdGVyIG9ic2VydmluZyBuZXcgZXZpZGVuY2UgKGxpa2VsaWhvb2QpLiBJdCBpcyBleHByZXNzZWQgbWF0aGVtYXRpY2FsbHkgYXM6DQoNClAoQSB8IEIpID0gWyBQKEIgfCBBKSDDlyBQKEEpIF0gLyBQKEIpDQoNCuKAoiBQKEEgfCBCKTogUG9zdGVyaW9yIFByb2JhYmlsaXR5ICh1cGRhdGVkIGJlbGllZikNCuKAoiBQKEIgfCBBKTogTGlrZWxpaG9vZCAocHJvYmFiaWxpdHkgb2Ygb2JzZXJ2aW5nIEIgZ2l2ZW4gQSBpcyB0cnVlKQ0K4oCiIFAoQSk6IFByaW9yIFByb2JhYmlsaXR5IChpbml0aWFsIGJlbGllZikNCuKAoiBQKEIpOiBOb3JtYWxpemluZyBGYWN0b3IgKGVuc3VyZXMgdGhlIHJlc3VsdCBpcyBhIHZhbGlkIHByb2JhYmlsaXR5KQ0KDQpFeGFtcGxlOiBEcnVnIFRlc3QNCi0gVHJ1ZSBQb3NpdGl2ZSBSYXRlID0gOTklDQotIFRydWUgTmVnYXRpdmUgUmF0ZSA9IDk3JQ0KLSBQcmV2YWxlbmNlID0gNSUNCg0KUChBIHwgQikgPSBbMC45OSDDlyAwLjA1XSAvIFsgKDAuOTkgw5cgMC4wNSkgKyAoMC4wMyDDlyAwLjk1ICkgXSDiiYggNjMuNSUNCg0KMi4gQmF5ZXPigJkgUnVsZSBmb3IgTXVsdGlwbGUgVmFyaWFibGVzDQpXaGVuIGRlYWxpbmcgd2l0aCBtdWx0aXBsZSBldmlkZW5jZSB2YXJpYWJsZXMgKEIsIEMsIEQsIC4uLiksIHdlIGNhbiBnZW5lcmFsaXplIEJheWVz4oCZIFJ1bGUgYXM6DQoNClAoQSB8IEIsIEMsIEQpIOKInSBQKEEpIMOXIFAoQiB8IEEpIMOXIFAoQyB8IEEpIMOXIFAoRCB8IEEpDQoNClRoZSDigJxuYWl2ZeKAnSBhc3N1bXB0aW9uIGlzIHRoYXQgYWxsIGV2aWRlbmNlIHZhcmlhYmxlcyBCLCBDLCBEIGFyZSBjb25kaXRpb25hbGx5IGluZGVwZW5kZW50IGdpdmVuIEEsIGFsbG93aW5nIHVzIHRvIG11bHRpcGx5IHByb2JhYmlsaXRpZXMgcmF0aGVyIHRoYW4gY29uc2lkZXIgdGhlaXIgY29tcGxleCBqb2ludCBkaXN0cmlidXRpb25zLg0KDQozLiBCYXllc+KAmSBSdWxlIHdpdGggQ29udGludW91cyBWYXJpYWJsZXMNCkZvciBjb250aW51b3VzIGF0dHJpYnV0ZXMgKHRlbXBlcmF0dXJlLCB3ZWlnaHQpLCB3ZSB1c2UgcHJvYmFiaWxpdHkgZGVuc2l0eSBmdW5jdGlvbnMgKGUuZy4sIEdhdXNzaWFuKSB0byBlc3RpbWF0ZSBQKFggPSB4KS4gSWYgdGhlIGRhdGEgaXMgYXNzdW1lZCBub3JtYWxseSBkaXN0cmlidXRlZCwgd2UgY2FuIGNvbXB1dGU6DQoNClAoeDsgzrwsIM+DKSA9ICgxIC8gKM+D4oiaKDLPgCkpKSDDlyBleHBb4oiSKHgg4oiSIM68KcKyIC8gKDLPg8KyKV0NCg0KV2UgdGhlbiBpbnNlcnQgdGhlc2UgcHJvYmFiaWxpdGllcyBpbnRvIEJheWVz4oCZIGZyYW1ld29yayBqdXN0IGFzIHdlIGRvIHdpdGggZGlzY3JldGUgb3IgY2F0ZWdvcmljYWwgZGF0YS4NCg0KNC4gTmFpdmUgQmF5ZXMgaW4gUHJhY3RpY2UgKERlbW8pDQrigKIgT2Z0ZW4gdXNlZCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbiB3aXRoIGJhZy1vZi13b3JkcyBvciB0Zi1pZGYgZmVhdHVyZXMuICANCuKAoiBNdWx0aW5vbWlhbE5CIGZyb20gc2tsZWFybiBpcyBjb21tb25seSBhcHBsaWVkOg0KICDigJMgQWxwaGEgcGFyYW1ldGVyIChMYXBsYWNlIHNtb290aGluZykgaGFuZGxlcyB6ZXJvLWZyZXF1ZW5jeSB3b3Jkcy4gIA0KICDigJMgVmVyeSBmYXN0LCBldmVuIG9uIGxhcmdlIGRhdGFzZXRzLg0KDQo1LiBOYWl2ZSBCYXllcyB2cy4gTG9naXN0aWMgUmVncmVzc2lvbg0K4oCiIE5haXZlIEJheWVzIChnZW5lcmF0aXZlIG1vZGVsKSBhbmQgTG9naXN0aWMgUmVncmVzc2lvbiAoZGlzY3JpbWluYXRpdmUgbW9kZWwpIGZvcm0gYSBnZW5lcmF0aXZlLWRpc2NyaW1pbmF0aXZlIHBhaXIuICANCuKAoiBOYWl2ZSBCYXllcyBjb252ZXJnZXMgZmFzdGVyIHRvIGEgbmVhci1vcHRpbWFsIHNvbHV0aW9uIHdpdGggbGltaXRlZCBkYXRhOyBMb2dpc3RpYyBSZWdyZXNzaW9uIGNhbiB5aWVsZCBhIGxvd2VyIGFzeW1wdG90aWMgZXJyb3Igd2l0aCBzdWZmaWNpZW50IGRhdGEuICANCuKAoiBCb3RoIGFyZSBkZWVwbHkgY29ubmVjdGVkIHRocm91Z2ggdGhlIGxvZ2l0IGZ1bmN0aW9uIGFuZCByYXRpbyBmb3JtcyBvZiBwcm9iYWJpbGl0eS4NCg0KNi4gQmFnIG9mIFdvcmRzIEV4ZXJjaXNlDQrigKIgVGFzazogQnVpbGQgYSBjdXN0b20gQ291bnRWZWN0b3JpemVyIHVzaW5nIFB5dGhvbi4gIA0K4oCiIENvbXBhcmUgd2l0aCBza2xlYXJu4oCZcyBDb3VudFZlY3Rvcml6ZXIgdG8gb2JzZXJ2ZSBkaWZmZXJlbmNlcyBpbiB0b2tlbml6YXRpb24sIHZvY2FidWxhcnkgYnVpbGRpbmcsIGFuZCBwZXJmb3JtYW5jZS4NCg0KTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uIChDb3JlIEZvcm11bGFzKQ0KMS4gQmF5ZXPigJkgUnVsZSAoU2luZ2xlIFZhcmlhYmxlKQ0KICAgUChBIHwgQikgPSBbUChCIHwgQSkgUChBKV0gLyBQKEIpDQoNCjIuIEJheWVz4oCZIFJ1bGUgKE11bHRpdmFyaWFibGUpDQogICBQKEEgfCBCLCBDLCBEKSDiiJ0gUChBKSDDlyBQKEIgfCBBKSDDlyBQKEMgfCBBKSDDlyBQKEQgfCBBKQ0KDQozLiBHYXVzc2lhbiBEZW5zaXR5IChDb250aW51b3VzIEV4YW1wbGUpDQogICBwKHgpID0gMSAvICjPg+KImigyz4ApKSDDlyBleHBb4oiSKHgg4oiSIM68KcKyIC8gKDLPg8KyKV0NCg0KQ29kZSBTbmlwcGV0cw0KDQpBLiBDUFUgSW1wbGVtZW50YXRpb24gKE5haXZlIEJheWVzIOKAkyBTa2xlYXJuKQ0KDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5uYWl2ZV9iYXllcyBpbXBvcnQgTXVsdGlub21pYWxOQg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBmZXRjaF8yMG5ld3Nncm91cHMNCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyLCBUZmlkZlZlY3Rvcml6ZXINCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KDQojIDEpIExvYWQgRGF0YQ0KdHJhaW5fZGF0YSA9IGZldGNoXzIwbmV3c2dyb3VwcyhzdWJzZXQ9J3RyYWluJywgcmVtb3ZlPSgnaGVhZGVycycsJ2Zvb3RlcnMnLCdxdW90ZXMnKSkNCnRlc3RfZGF0YSAgPSBmZXRjaF8yMG5ld3Nncm91cHMoc3Vic2V0PSd0ZXN0JywgIHJlbW92ZT0oJ2hlYWRlcnMnLCdmb290ZXJzJywncXVvdGVzJykpDQoNCiMgMikgVHJhbnNmb3JtIFRleHQgKENvdW50VmVjdG9yaXplcikNCmN2ID0gQ291bnRWZWN0b3JpemVyKCkNClhfdHJhaW5fY3YgPSBjdi5maXRfdHJhbnNmb3JtKHRyYWluX2RhdGEuZGF0YSkNClhfdGVzdF9jdiAgPSBjdi50cmFuc2Zvcm0odGVzdF9kYXRhLmRhdGEpDQoNCiMgMykgVHJhaW4gTmFpdmUgQmF5ZXMNCm1vZGVsX2N2ID0gTXVsdGlub21pYWxOQigpDQptb2RlbF9jdi5maXQoWF90cmFpbl9jdiwgdHJhaW5fZGF0YS50YXJnZXQpDQoNCiMgNCkgRXZhbHVhdGUNCnByZWRzX2N2ID0gbW9kZWxfY3YucHJlZGljdChYX3Rlc3RfY3YpDQpwcmludCgiQWNjdXJhY3kgd2l0aCBDb3VudFZlY3Rvcml6ZXI6IiwgYWNjdXJhY3lfc2NvcmUodGVzdF9kYXRhLnRhcmdldCwgcHJlZHNfY3YpKQ0KDQojIDUpIFVzaW5nIFRGLUlERg0KdGZpZGYgPSBUZmlkZlZlY3Rvcml6ZXIoKQ0KWF90cmFpbl90ZmlkZiA9IHRmaWRmLmZpdF90cmFuc2Zvcm0odHJhaW5fZGF0YS5kYXRhKQ0KWF90ZXN0X3RmaWRmICA9IHRmaWRmLnRyYW5zZm9ybSh0ZXN0X2RhdGEuZGF0YSkNCg0KbW9kZWxfdGZpZGYgPSBNdWx0aW5vbWlhbE5CKCkNCm1vZGVsX3RmaWRmLmZpdChYX3RyYWluX3RmaWRmLCB0cmFpbl9kYXRhLnRhcmdldCkNCg0KcHJlZHNfdGZpZGYgPSBtb2RlbF90ZmlkZi5wcmVkaWN0KFhfdGVzdF90ZmlkZikNCnByaW50KCJBY2N1cmFjeSB3aXRoIFRGLUlERjoiLCBhY2N1cmFjeV9zY29yZSh0ZXN0X2RhdGEudGFyZ2V0LCBwcmVkc190ZmlkZikpDQpgYGANCkIuIFNpbXBsZSBHUFUgSW1wbGVtZW50YXRpb24gKE5haXZlIEJheWVzIHdpdGggY3VNTCkNCihZb3UgbmVlZCBhbiBlbnZpcm9ubWVudCB3aXRoIFJBUElEUy9jdW1sIGluc3RhbGxlZCBmb3IgdGhpcy4pDQoNCmBgYHB5dGhvbg0KaW1wb3J0IGN1cHkgYXMgY3ANCmZyb20gY3VtbC5uYWl2ZV9iYXllcyBpbXBvcnQgTXVsdGlub21pYWxOQiBhcyBjdU11bHRpbm9taWFsTkINCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgVGZpZGZWZWN0b3JpemVyDQoNCiMgQXNzdW1lIHlvdSBoYXZlIHRyYWluX2RhdGEgJiB0ZXN0X2RhdGEgZnJvbSBhYm92ZQ0KdGZpZGZfZ3B1ID0gVGZpZGZWZWN0b3JpemVyKCkNClhfdHJhaW5fZ3B1ID0gdGZpZGZfZ3B1LmZpdF90cmFuc2Zvcm0odHJhaW5fZGF0YS5kYXRhKQ0KWF90ZXN0X2dwdSAgPSB0ZmlkZl9ncHUudHJhbnNmb3JtKHRlc3RfZGF0YS5kYXRhKQ0KDQojIENvbnZlcnQgdG8gR1BVIGFycmF5cw0KWF90cmFpbl9ncHVfY3NyID0gY3Auc3BhcnNlLmNzcl9tYXRyaXgoWF90cmFpbl9ncHUpDQpYX3Rlc3RfZ3B1X2NzciAgPSBjcC5zcGFyc2UuY3NyX21hdHJpeChYX3Rlc3RfZ3B1KQ0KeV90cmFpbl9ncHUgICAgID0gY3AuYXNhcnJheSh0cmFpbl9kYXRhLnRhcmdldCkNCnlfdGVzdF9ncHUgICAgICA9IGNwLmFzYXJyYXkodGVzdF9kYXRhLnRhcmdldCkNCg0KIyBUcmFpbg0KbW9kZWxfZ3B1ID0gY3VNdWx0aW5vbWlhbE5CKCkNCm1vZGVsX2dwdS5maXQoWF90cmFpbl9ncHVfY3NyLCB5X3RyYWluX2dwdSkNCg0KIyBQcmVkaWN0DQpwcmVkc19ncHUgPSBtb2RlbF9ncHUucHJlZGljdChYX3Rlc3RfZ3B1X2NzcikNCmFjY3VyYWN5X2dwdSA9IChwcmVkc19ncHUuZ2V0KCkgPT0geV90ZXN0X2dwdS5nZXQoKSkubWVhbigpDQpwcmludCgiR1BVLWJhc2VkIE5haXZlIEJheWVzIEFjY3VyYWN5OiIsIGFjY3VyYWN5X2dwdSkNCmBgYA0KDQpTdHVkeSBHdWlkZSBGb3JtYXQgKENvbmRlbnNlZCkNCg0KSS4gSW50cm9kdWN0aW9uDQogIC0gQmF5ZXPigJkgUnVsZSwgUHJpb3IsIExpa2VsaWhvb2QsIFBvc3RlcmlvciAgDQogIC0gQ29ubmVjdGlvbiB0byBsb2dpc3RpYyByZWdyZXNzaW9uICANCg0KSUkuIE1hdGhlbWF0aWNhbCBGb3VuZGF0aW9ucw0KICAtIFAoQXxCKSA9IChQKEJ8QSkqUChBKSkvUChCKSAgDQogIC0gTXVsdGl2YXJpYXRlIEV4dGVuc2lvbiAgDQogIC0gQ29udGludW91cyBWYXJpYWJsZXMgIA0KDQpJSUkuIFByYWN0aWNhbCBJbXBsZW1lbnRhdGlvbg0KICAtIFRleHQgQ2xhc3NpZmljYXRpb24gIA0KICAtIEJhZy1vZi1Xb3JkcyAmIFRGLUlERiAgDQogIC0gTmFpdmUgQmF5ZXMgKE11bHRpbm9taWFsTkIpICANCg0KSVYuIENvbXBhcmlzb24NCiAgLSBOYWl2ZSBCYXllcyB2cy4gTG9naXN0aWMgUmVncmVzc2lvbiAgDQogIC0gR2VuZXJhdGl2ZSB2cy4gRGlzY3JpbWluYXRpdmUgIA0KDQpWLiBLZXkgVGFrZWF3YXlzDQogIDEuIE5haXZlIEJheWVzIHVzZXMgQmF5ZXPigJkgUnVsZSB3aXRoIGEgc2ltcGxpZnlpbmcgKG5haXZlKSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbiwgeWV0IG9mdGVuIHBlcmZvcm1zIGNvbXBldGl0aXZlbHkgaW4gY2xhc3NpZmljYXRpb24gdGFza3MsIGVzcGVjaWFsbHkgdGV4dC4gIA0KICAyLiBDb21iaW5pbmcgVEYtSURGIHdpdGggTXVsdGlub21pYWxOQiBmcmVxdWVudGx5IHlpZWxkcyBiZXR0ZXIgYWNjdXJhY3kgdGhhbiBiYWctb2Ytd29yZHMuICANCiAgMy4gR2VuZXJhdGl2ZSAoTmFpdmUgQmF5ZXMpIGFuZCBEaXNjcmltaW5hdGl2ZSAoTG9naXN0aWMgUmVncmVzc2lvbikgbW9kZWxzIGFyZSBtYXRoZW1hdGljYWxseSByZWxhdGVkIHRocm91Z2ggdGhlIGxvZ2l0IChvZGRzIHJhdGlvKSBmcmFtZXdvcmsuDQoNClZJLiBEaXNjdXNzaW9uIFF1ZXN0aW9ucw0KICAxLiBXaHkgZG9lcyBOYWl2ZSBCYXllcyBvZnRlbiBwZXJmb3JtIHdlbGwgaW4gaGlnaC1kaW1lbnNpb25hbCB0ZXh0IGNsYXNzaWZpY2F0aW9uLCBkZXNwaXRlIHRoZSBuYWl2ZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbj8gIA0KICAyLiBIb3cgZG8gc21vb3RoaW5nIHBhcmFtZXRlcnMgKGxpa2UgYWxwaGEpIGluZmx1ZW5jZSBOYWl2ZSBCYXllcyBwZXJmb3JtYW5jZT8gIA0KICAzLiBJbiB3aGF0IGNhc2VzIG1pZ2h0IExvZ2lzdGljIFJlZ3Jlc3Npb24gZXZlbnR1YWxseSBvdXRwZXJmb3JtIE5haXZlIEJheWVzLCBhbmQgd2h5Pw0KDQoNCkJlbG93IGlzIGEgbW9yZSBkZXRhaWxlZCB3YWxrdGhyb3VnaCBvZiAqKlBhcnQgNiDigJMgVGhlIEJhZyBvZiBXb3JkcyBFeGVyY2lzZSoqLCBzaG93aW5nIGhvdyB0byBidWlsZCBhIGN1c3RvbSBDb3VudFZlY3Rvcml6ZXIgYW5kIGNvbXBhcmluZyBpdCB3aXRoIHNrbGVhcm7igJlzIHZlcnNpb24uIFRoaXMgd2lsbCBoZWxwIHNvbGlkaWZ5IHlvdXIgdW5kZXJzdGFuZGluZyBvZiBob3cgdGV4dCBpcyBjb252ZXJ0ZWQgaW50byBudW1lcmljYWwgZmVhdHVyZXMgZm9yIE5haXZlIEJheWVzIGFuZCBvdGhlciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4NCg0KQmFnIG9mIFdvcmRzIEV4ZXJjaXNlIOKAkyBTdGVwLWJ5LVN0ZXANCg0KMSkgVW5kZXJzdGFuZGluZyB0aGUgR29hbA0KV2Ugd2FudCB0bzoNCuKAoiBMb2FkIHRoZSAyMCBOZXdzZ3JvdXBzIGRhdGFzZXQgKHdpdGhvdXQgaGVhZGVycywgZm9vdGVycywgYW5kIHF1b3RlcykuICANCuKAoiBDcmVhdGUgb3VyICoqb3duKiogQmFnLW9mLVdvcmRzIGFwcHJvYWNoIChiYXNpYyBDb3VudFZlY3Rvcml6ZXIpLiAgDQrigKIgQ29tcGFyZSBhZ2FpbnN0IHRoZSAqKnNrbGVhcm4qKiBpbXBsZW1lbnRhdGlvbiAoQ291bnRWZWN0b3JpemVyKS4gIA0KDQoyKSBMb2FkaW5nIHRoZSAyMCBOZXdzZ3JvdXBzIERhdGFzZXQNClRoZSBkYXRhc2V0IGNhbiBiZSBmZXRjaGVkIHdpdGg6DQpkYXRhID0gZmV0Y2hfMjBuZXdzZ3JvdXBzKHJlbW92ZT0oJ2hlYWRlcnMnLCAnZm9vdGVycycsICdxdW90ZXMnKSkNCg0KVGhpcyByZXR1cm5zIGEgZGljdGlvbmFyeS1saWtlIG9iamVjdCwgaW5jbHVkaW5nIC5kYXRhIChsaXN0IG9mIGRvY3VtZW50cykgYW5kIC50YXJnZXQgKGxpc3Qgb2YgbnVtZXJpYyBsYWJlbHMpLg0KDQozKSBCdWlsZGluZyBhIEN1c3RvbSBDb3VudFZlY3Rvcml6ZXINCk91ciBjdXN0b20gdmVyc2lvbiB3aWxsOg0K4oCiIFRva2VuaXplIGVhY2ggZG9jdW1lbnQgaW50byB3b3JkcyAoc3BsaXQgb24gd2hpdGVzcGFjZSwgcmVtb3ZlIHB1bmN0dWF0aW9uKS4gIA0K4oCiIEJ1aWxkIGEgdm9jYWJ1bGFyeSAodW5pcXVlIHdvcmRzIG1hcHBlZCB0byBpbmRpY2VzKS4gIA0K4oCiIENvdW50IGhvdyBvZnRlbiBlYWNoIHdvcmQgYXBwZWFycyBpbiBlYWNoIGRvY3VtZW50LiAgDQoNCkJlbG93IGlzIHNhbXBsZSBjb2RlIHRvIGlsbHVzdHJhdGUgdGhlc2Ugc3RlcHMuDQoNCmBgYHB5dGhvbg0KaW1wb3J0IHJlDQpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBDb3VudGVyDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGZldGNoXzIwbmV3c2dyb3Vwcw0KDQpkZWYgY3VzdG9tX2NvdW50X3ZlY3Rvcml6ZXIoY29ycHVzKToNCiAgICAjIDEuIENsZWFuIGFuZCBsb3dlcmNhc2UgdGV4dCwgcmVtb3ZlIHB1bmN0dWF0aW9uDQogICAgY2xlYW5lZF90ZXh0cyA9IFtyZS5zdWIocidbXmEtekEtWlxzXScsICcnLCBkb2MubG93ZXIoKSkgZm9yIGRvYyBpbiBjb3JwdXNdDQogICAgDQogICAgIyAyLiBUb2tlbml6ZSBieSBzcGxpdHRpbmcgb24gd2hpdGVzcGFjZQ0KICAgIHRva2VuaXplZF9kb2NzID0gW2RvYy5zcGxpdCgpIGZvciBkb2MgaW4gY2xlYW5lZF90ZXh0c10NCiAgICANCiAgICAjIDMuIEJ1aWxkIHZvY2FidWxhcnkgKGFsbCB1bmlxdWUgd29yZHMpDQogICAgIyAgICBDb252ZXJ0IHRvIGEgc29ydGVkIGxpc3QgdG8gZW5zdXJlIGNvbnNpc3RlbnQgb3JkZXJpbmcuDQogICAgdm9jYWJ1bGFyeSA9IHNvcnRlZChzZXQod29yZCBmb3IgZG9jIGluIHRva2VuaXplZF9kb2NzIGZvciB3b3JkIGluIGRvYykpDQogICAgDQogICAgIyA0LiBDcmVhdGUgYSBkaWN0IG1hcHBpbmcgZWFjaCB3b3JkIHRvIGl0cyBpbmRleA0KICAgIHdvcmQyaW5kZXggPSB7d29yZDogaWR4IGZvciBpZHgsIHdvcmQgaW4gZW51bWVyYXRlKHZvY2FidWxhcnkpfQ0KICAgIA0KICAgICMgNS4gQ29uc3RydWN0IHRoZSBjb3VudCBtYXRyaXgNCiAgICAjICAgIEVhY2ggcm93IHJlcHJlc2VudHMgYSBkb2N1bWVudCwgZWFjaCBjb2x1bW4gYSB3b3JkIGluIG91ciB2b2NhYnVsYXJ5Lg0KICAgIGNvdW50X21hdHJpeCA9IFtdDQogICAgZm9yIGRvYyBpbiB0b2tlbml6ZWRfZG9jczoNCiAgICAgICAgY291bnRzID0gQ291bnRlcihkb2MpDQogICAgICAgICMgQnVpbGQgYSByb3cgb2YgbGVuZ3RoID0gc2l6ZSBvZiB2b2NhYnVsYXJ5DQogICAgICAgIHJvd192ZWN0b3IgPSBbY291bnRzLmdldCh3b3JkLCAwKSBmb3Igd29yZCBpbiB2b2NhYnVsYXJ5XQ0KICAgICAgICBjb3VudF9tYXRyaXguYXBwZW5kKHJvd192ZWN0b3IpDQogICAgDQogICAgcmV0dXJuIHZvY2FidWxhcnksIGNvdW50X21hdHJpeA0KDQojIDEpIEZldGNoIGRhdGENCmRhdGEgPSBmZXRjaF8yMG5ld3Nncm91cHMocmVtb3ZlPSgnaGVhZGVycycsICdmb290ZXJzJywgJ3F1b3RlcycpKQ0KY29ycHVzID0gZGF0YS5kYXRhWzo1XSAgIyBVc2luZyBmaXJzdCA1IGRvY3MgYXMgYSBzbWFsbCBleGFtcGxlDQoNCiMgMikgQXBwbHkgb3VyIGN1c3RvbSBDb3VudFZlY3Rvcml6ZXINCnZvY2FiLCBtYXRyaXggPSBjdXN0b21fY291bnRfdmVjdG9yaXplcihjb3JwdXMpDQoNCnByaW50KCJDdXN0b20gVm9jYWJ1bGFyeSAoZmlyc3QgMjAgd29yZHMpOiIsIHZvY2FiWzoyMF0pDQpwcmludCgiXG5GaXJzdCBEb2N1bWVudCdzIFZlY3RvciBSZXByZXNlbnRhdGlvbjoiKQ0KbWF0cml4WzBdKQ0KYGBgDQoNCg0KNCkgQ29tcGFyaW5nIHdpdGggc2tsZWFybuKAmXMgQ291bnRWZWN0b3JpemVyDQpOb3cgbGV04oCZcyB1c2UgdGhlIHNrbGVhcm4gdmVyc2lvbiB0byBzZWUgaG93IGl0IGRpZmZlcnMuDQoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBDb3VudFZlY3Rvcml6ZXINCg0KY3YgPSBDb3VudFZlY3Rvcml6ZXIoc3RvcF93b3Jkcz0nZW5nbGlzaCcpICAjIG9wdGlvbmFsbHkgcmVtb3ZlIHN0b3B3b3Jkcw0KWCA9IGN2LmZpdF90cmFuc2Zvcm0oY29ycHVzKQ0KDQpwcmludCgiXG5Ta2xlYXJuIFZvY2FidWxhcnkgU2l6ZToiLCBsZW4oY3Yudm9jYWJ1bGFyeV8pKQ0KcHJpbnQoIlNrbGVhcm4gRmlyc3QgRG9jdW1lbnQncyBWZWN0b3I6IikNCnByaW50KFgudG9hcnJheSgpWzBdKQ0KYGBgDQoNCg0KRGlmZmVyZW5jZXMgWW91IE1pZ2h0IE5vdGljZQ0K4oCiICoqU3RvcHdvcmQgUmVtb3ZhbCoqOiBCeSBkZWZhdWx0LCBza2xlYXJu4oCZcyBDb3VudFZlY3Rvcml6ZXIgY2FuIHJlbW92ZSBjb21tb24gd29yZHMgbGlrZSDigJx0aGUs4oCdIOKAnGFuZCzigJ0gZXRjLiwgaWYgeW91IHNwZWNpZnkgYHN0b3Bfd29yZHM9J2VuZ2xpc2gnYC4gT3VyIGN1c3RvbSB2ZXJzaW9uIGRpZG7igJl0IHJlbW92ZSB0aGVtIHVubGVzcyB3ZSBjb2RlIGl0LiAgDQrigKIgKipQdW5jdHVhdGlvbiBIYW5kbGluZyoqOiBza2xlYXJuIGF1dG9tYXRpY2FsbHkgaGFuZGxlcyBwdW5jdHVhdGlvbiBpbiBtdWx0aXBsZSB3YXlzLiBPdXIgY3VzdG9tIHZlcnNpb24gdXNlcyBhIHNpbXBsZSByZWdleC4gIA0K4oCiICoqU3BhcnNlIFJlcHJlc2VudGF0aW9uKio6IHNrbGVhcm4gcmV0dXJucyBhIHNwYXJzZSBtYXRyaXggKGVmZmljaWVudCBmb3IgbGFyZ2UgdGV4dCkuIE91cnMgaXMgYSBwbGFpbiBsaXN0IG9mIGxpc3RzIChkZW5zZSBtYXRyaXgpLiAgDQoNCjUpIChPcHRpb25hbCkgR1BVLUFjY2VsZXJhdGVkIFZlcnNpb24NCklmIHlvdSBoYXZlIHRoZSBSQVBJRFMgZW52aXJvbm1lbnQgc2V0IHVwICh3aXRoIEN1UHkgYW5kIGN1TUwpLCB5b3UgY291bGQgYWRhcHQgeW91ciBjb2RlIHRvIGJ1aWxkIGNvdW50cyBvbiB0aGUgR1BVLiBIb3dldmVyLCBmb3IgYSDigJxob21lZ3Jvd27igJ0gc29sdXRpb24sIHlvdeKAmWQ6DQoNCmBgYHB5dGhvbg0KaW1wb3J0IGN1cHkgYXMgY3ANCg0KZGVmIGdwdV9jb3VudF92ZWN0b3JpemVyKGNvcnB1cyk6DQogICAgIyBTdGVwIDE6IFByZXByb2Nlc3MgJiBUb2tlbml6ZSBvbiBDUFUgKG9yIEdQVSBpZiB5b3UgcHJlZmVyKQ0KICAgIGNsZWFuZWRfdGV4dHMgPSBbcmUuc3ViKHInW15hLXpBLVpcc10nLCAnJywgZG9jLmxvd2VyKCkpIGZvciBkb2MgaW4gY29ycHVzXQ0KICAgIHRva2VuaXplZF9kb2NzID0gW2RvYy5zcGxpdCgpIGZvciBkb2MgaW4gY2xlYW5lZF90ZXh0c10NCiAgICANCiAgICAjIFN0ZXAgMjogQnVpbGQgdm9jYWJ1bGFyeSBvbiBDUFUgZm9yIHNpbXBsaWNpdHkNCiAgICB2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCh3b3JkIGZvciBkb2MgaW4gdG9rZW5pemVkX2RvY3MgZm9yIHdvcmQgaW4gZG9jKSkNCiAgICB3b3JkMmluZGV4ID0ge3dvcmQ6IGlkeCBmb3IgaWR4LCB3b3JkIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCg0KICAgICMgU3RlcCAzOiBJbml0aWFsaXplIGEgR1BVIGFycmF5IHRvIGhvbGQgY291bnRzDQogICAgcm93cywgY29scyA9IGxlbih0b2tlbml6ZWRfZG9jcyksIGxlbih2b2NhYnVsYXJ5KQ0KICAgIGdwdV9tYXRyaXggPSBjcC56ZXJvcygocm93cywgY29scyksIGR0eXBlPWNwLmludDMyKQ0KICAgIA0KICAgICMgU3RlcCA0OiBGaWxsIGluIHRoZSBtYXRyaXgNCiAgICBmb3IgaSwgZG9jIGluIGVudW1lcmF0ZSh0b2tlbml6ZWRfZG9jcyk6DQogICAgICAgIGRvY19jb3VudHMgPSBDb3VudGVyKGRvYykNCiAgICAgICAgZm9yIHdvcmQsIGNvdW50IGluIGRvY19jb3VudHMuaXRlbXMoKToNCiAgICAgICAgICAgIGlmIHdvcmQgaW4gd29yZDJpbmRleDoNCiAgICAgICAgICAgICAgICBqID0gd29yZDJpbmRleFt3b3JkXQ0KICAgICAgICAgICAgICAgIGdwdV9tYXRyaXhbaSwgal0gPSBjb3VudA0KICAgIA0KICAgIHJldHVybiB2b2NhYnVsYXJ5LCBncHVfbWF0cml4DQoNCiMgRXhhbXBsZSB1c2FnZQ0Kdm9jYWJfZ3B1LCBtYXRyaXhfZ3B1ID0gZ3B1X2NvdW50X3ZlY3Rvcml6ZXIoY29ycHVzWzo1XSkNCnByaW50KCJHUFUgQ291bnQgTWF0cml4IHNoYXBlOiIsIG1hdHJpeF9ncHUuc2hhcGUpDQpwcmludCgiR1BVIENvdW50IE1hdHJpeCAoZmlyc3Qgcm93LCBiYWNrIHRvIENQVSk6IiwgbWF0cml4X2dwdVswXS5nZXQoKSkNCmBgYA0KDQpOb3RlOiBUaGlzIGV4YW1wbGUgc2hvd3MgdGhlIGNvbmNlcHQuIEluIHByYWN0aWNlLCB5b3XigJlkIHdhbnQgdG8gaGFuZGxlIG1vcmUgc3RlcHMgb24gR1BVIHRvIG1heGltaXplIHNwZWVkLg0KDQpTdW1tYXJ5IG9mIHRoZSBFeGVyY2lzZQ0K4oCiICoqT2JqZWN0aXZlKio6IEltcGxlbWVudCBhIHNpbXBsaWZpZWQgQmFnLW9mLVdvcmRzIG1hbnVhbGx5LiAgDQrigKIgKipLZXkgT2JzZXJ2YXRpb24qKjogVGhlIGNvcmUgc3RlcHMg4oCTIHRva2VuaXppbmcsIGJ1aWxkaW5nIGEgdm9jYWJ1bGFyeSwgY291bnRpbmcgd29yZHMg4oCTIGFyZSBzdHJhaWdodGZvcndhcmQsIGJ1dCBza2xlYXJuIG9wdGltaXplcyB0aGVtIGZvciBwZXJmb3JtYW5jZSBhbmQgYWRkcyBtYW55IGV4dHJhIGZlYXR1cmVzIChzdG9wd29yZCByZW1vdmFsLCBuLWdyYW1zLCBldGMuKS4gIA0KDQpUaHJlZSBLZXkgVGFrZWF3YXlzIChQYXJ0IDYgRm9jdXMpDQoxLiAqKkltcGxlbWVudGF0aW9uIENvbnRyb2wqKjogQnVpbGRpbmcgQ291bnRWZWN0b3JpemVyIGZyb20gc2NyYXRjaCBjbGFyaWZpZXMgaG93IHRleHQgcHJlcHJvY2Vzc2luZyB3b3JrcyB1bmRlciB0aGUgaG9vZCwgYnV0IGxpYnJhcmllcyBsaWtlIHNrbGVhcm4gYXJlIG1vcmUgZmxleGlibGUgYW5kIG9wdGltaXplZC4gIA0KMi4gKipWb2NhYnVsYXJ5IFNpemUqKjogUmVhbC13b3JsZCB0ZXh0IGRhdGEgc2V0cyBjYW4gY29udGFpbiB0ZW5zIG9yIGh1bmRyZWRzIG9mIHRob3VzYW5kcyBvZiB1bmlxdWUgdG9rZW5zOyBlZmZpY2llbnQgZGF0YSBzdHJ1Y3R1cmVzIChzcGFyc2UgbWF0cmljZXMpIGFyZSBjcml0aWNhbC4gIA0KMy4gKipQcmVwcm9jZXNzaW5nIE1hdHRlcnMqKjogUmVtb3Zpbmcgc3RvcHdvcmRzLCBoYW5kbGluZyBwdW5jdHVhdGlvbiwgb3IgYXBwbHlpbmcgc3RlbW1pbmcvbGVtbWF0aXphdGlvbiBjYW4gc2lnbmlmaWNhbnRseSBjaGFuZ2UgeW91ciByZXN1bHRzLg0KDQpUaHJlZSBEaXNjdXNzaW9uIFF1ZXN0aW9ucw0KMS4gKipIb3cgd291bGQgeW91IG1vZGlmeSB5b3VyIGN1c3RvbSBDb3VudFZlY3Rvcml6ZXIgdG8gcmVtb3ZlIHN0b3B3b3Jkcywgb3IgdG8gaGFuZGxlIG4tZ3JhbXMgKGxpa2UgdHdvLXdvcmQgcGhyYXNlcyk/KiogIA0KMi4gKipXaGF0IG1pZ2h0IGJlIHRoZSB0cmFkZW9mZnMgYmV0d2VlbiB1c2luZyBhIGRlbnNlIHZzLiBzcGFyc2UgbWF0cml4IHJlcHJlc2VudGF0aW9uIGZvciBsYXJnZSBjb3Jwb3JhPyoqICANCjMuICoqSG93IGNvdWxkIHdlIGV4dGVuZCB0aGlzIGFwcHJvYWNoIGZvciBhZHZhbmNlZCB0ZXh0IHByb2Nlc3NpbmcsIHN1Y2ggYXMgYWRkaW5nIFRGLUlERiBvciB3b3JkIGVtYmVkZGluZ3MgKEdsb1ZlLCBXb3JkMlZlYyk/KioNCg0KDQoNCg0KIyBCYXllcycgUnVsZSBhbmQgTmFpdmUgQmF5ZXMgU3R1ZHkgR3VpZGUgLSBQYXJ0IDENCg0KIyMgSS4gRnVuZGFtZW50YWxzIG9mIEJheWVzJyBSdWxlDQoNCiMjIyBDb3JlIEVxdWF0aW9uDQpCYXllcycgUnVsZSBpcyBleHByZXNzZWQgYXM6DQoNCmBgYA0KUChBfEIpID0gUChCfEEpIMOXIFAoQSkgLyBQKEIpDQpgYGANCg0KV2hlcmU6DQotIFAoQXxCKSA9IFBvc3RlcmlvciBQcm9iYWJpbGl0eQ0KLSBQKEJ8QSkgPSBMaWtlbGlob29kDQotIFAoQSkgPSBQcmlvciBQcm9iYWJpbGl0eQ0KLSBQKEIpID0gRXZpZGVuY2UvTm9ybWFsaXphdGlvbg0KDQojIyMgQ29tcG9uZW50cyBCcmVha2Rvd246DQoxLiAqKlBvc3RlcmlvciBEaXN0cmlidXRpb24gLSBQKEF8QikqKg0KICAgLSBUaGUgcHJvYmFiaWxpdHkgb2YgZXZlbnQgQSBvY2N1cnJpbmcgZ2l2ZW4gQiBoYXMgb2NjdXJyZWQNCiAgIC0gV2hhdCB3ZSdyZSB0eXBpY2FsbHkgdHJ5aW5nIHRvIGNhbGN1bGF0ZQ0KICAgLSBVcGRhdGVkIHByb2JhYmlsaXR5IGFmdGVyIGNvbnNpZGVyaW5nIGV2aWRlbmNlDQoNCjIuICoqUHJpb3IgLSBQKEEpKioNCiAgIC0gSW5pdGlhbCBwcm9iYWJpbGl0eSBiZWZvcmUgY29uc2lkZXJpbmcgbmV3IGV2aWRlbmNlDQogICAtIEJhc2VkIG9uIHByZXZpb3VzIGtub3dsZWRnZSBvciBhc3N1bXB0aW9ucw0KICAgLSBTdGFydGluZyBwb2ludCBmb3IgQmF5ZXNpYW4gaW5mZXJlbmNlDQoNCiMjIElJLiBCYXNpYyBQeXRob24gSW1wbGVtZW50YXRpb24gKENQVSkNCg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmZyb20gc2tsZWFybi5uYWl2ZV9iYXllcyBpbXBvcnQgR2F1c3NpYW5OQg0KDQpjbGFzcyBCYXllc2lhbkNhbGN1bGF0b3I6DQogICAgZGVmIHNpbXBsZV9iYXllcyhzZWxmLCBwcmlvcl9hLCBsaWtlbGlob29kX2JfZ2l2ZW5fYSwgZXZpZGVuY2VfYik6DQogICAgICAgICIiIg0KICAgICAgICBDYWxjdWxhdGUgcG9zdGVyaW9yIHByb2JhYmlsaXR5IHVzaW5nIEJheWVzJyBSdWxlDQogICAgICAgICIiIg0KICAgICAgICBwb3N0ZXJpb3IgPSAobGlrZWxpaG9vZF9iX2dpdmVuX2EgKiBwcmlvcl9hKSAvIGV2aWRlbmNlX2INCiAgICAgICAgcmV0dXJuIHBvc3Rlcmlvcg0KDQogICAgZGVmIG5haXZlX2JheWVzX2V4YW1wbGUoc2VsZik6DQogICAgICAgICMgU2FtcGxlIGRhdGENCiAgICAgICAgWCA9IG5wLmFycmF5KFtbMSwgMl0sIFsyLCAzXSwgWzMsIDRdLCBbNCwgNV1dKQ0KICAgICAgICB5ID0gbnAuYXJyYXkoWzAsIDAsIDEsIDFdKQ0KICAgICAgICANCiAgICAgICAgIyBJbml0aWFsaXplIGFuZCB0cmFpbiBtb2RlbA0KICAgICAgICBtb2RlbCA9IEdhdXNzaWFuTkIoKQ0KICAgICAgICBtb2RlbC5maXQoWCwgeSkNCiAgICAgICAgDQogICAgICAgICMgTWFrZSBwcmVkaWN0aW9uDQogICAgICAgIHByZWRpY3Rpb24gPSBtb2RlbC5wcmVkaWN0KFtbMiwgM11dKQ0KICAgICAgICByZXR1cm4gcHJlZGljdGlvbg0KYGBgDQoNCiMjIElJSS4gR1BVIEltcGxlbWVudGF0aW9uIHdpdGggQ3VQeQ0KDQpgYGBweXRob24NCmltcG9ydCBjdXB5IGFzIGNwDQoNCmNsYXNzIEdQVUJheWVzaWFuQ2FsY3VsYXRvcjoNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHNlbGYuZGV2aWNlID0gJ2dwdScgaWYgY3AuY3VkYS5pc19hdmFpbGFibGUoKSBlbHNlICdjcHUnDQogICAgDQogICAgZGVmIGdwdV9uYWl2ZV9iYXllcyhzZWxmLCBYLCB5KToNCiAgICAgICAgIiIiDQogICAgICAgIEdQVS1hY2NlbGVyYXRlZCBOYWl2ZSBCYXllcyBpbXBsZW1lbnRhdGlvbg0KICAgICAgICAiIiINCiAgICAgICAgaWYgc2VsZi5kZXZpY2UgPT0gJ2dwdSc6DQogICAgICAgICAgICBYID0gY3AuYXJyYXkoWCkNCiAgICAgICAgICAgIHkgPSBjcC5hcnJheSh5KQ0KICAgICAgICAgICAgDQogICAgICAgICAgICAjIENhbGN1bGF0ZSBjbGFzcyBwcmlvcnMNCiAgICAgICAgICAgIGNsYXNzZXMgPSBjcC51bmlxdWUoeSkNCiAgICAgICAgICAgIGNsYXNzX3Byb2JzID0gY3AuYXJyYXkoW2NwLm1lYW4oeSA9PSBjKSBmb3IgYyBpbiBjbGFzc2VzXSkNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgcmV0dXJuIGNsYXNzX3Byb2JzLmdldCgpICAjIFRyYW5zZmVyIGJhY2sgdG8gQ1BVDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICByZXR1cm4gIkdQVSBub3QgYXZhaWxhYmxlIg0KYGBgDQoNCiMgQmF5ZXMnIFJ1bGUgYW5kIE5haXZlIEJheWVzIFN0dWR5IEd1aWRlIC0gUGFydCAyDQoNCiMjIElWLiBOYWl2ZSBCYXllcyBDb3JlIENvbmNlcHRzDQoNCiMjIyBDcml0aWNhbCBBc3N1bXB0aW9uDQpUaGUgZnVuZGFtZW50YWwgYXNzdW1wdGlvbiBvZiBOYWl2ZSBCYXllcyBpcyBmZWF0dXJlIGluZGVwZW5kZW5jZS4gTWF0aGVtYXRpY2FsbHk6DQoNCmBgYA0KUChY4oKBLFjigoIsLi4uLFjigpl8WSkgPSBQKFjigoF8WSkgw5cgUChY4oKCfFkpIMOXIC4uLiDDlyBQKFjigpl8WSkNCmBgYA0KDQojIyMgTXVsdGljbGFzcyBDbGFzc2lmaWNhdGlvbg0KRm9yIGEgcHJvYmxlbSB3aXRoIGsgY2xhc3Nlcywgd2UgY2FsY3VsYXRlOg0KDQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBNdWx0aW5vbWlhbE5CDQoNCmNsYXNzIE11bHRpY2xhc3NOYWl2ZUJheWVzOg0KICAgIGRlZiBtdWx0aWNsYXNzX2V4YW1wbGUoc2VsZik6DQogICAgICAgICMgRXhhbXBsZSB3aXRoIG11bHRpcGxlIGNsYXNzZXMNCiAgICAgICAgWCA9IG5wLmFycmF5KFtbMSwyXSwgWzIsM10sIFszLDRdLCBbNCw1XSwgWzUsNl1dKQ0KICAgICAgICB5ID0gbnAuYXJyYXkoWzAsIDEsIDIsIDEsIDJdKSAgIyBUaHJlZSBjbGFzc2VzOiAwLCAxLCAyDQogICAgICAgIA0KICAgICAgICAjIEluaXRpYWxpemUgYW5kIHRyYWluDQogICAgICAgIGNsZiA9IE11bHRpbm9taWFsTkIoKQ0KICAgICAgICBjbGYuZml0KFgsIHkpDQogICAgICAgIA0KICAgICAgICAjIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KICAgICAgICBwcm9icyA9IGNsZi5wcmVkaWN0X3Byb2JhKFtbMyw0XV0pDQogICAgICAgICMgUmV0dXJucyBwcm9iYWJpbGl0eSBmb3IgZWFjaCBjbGFzcw0KICAgICAgICByZXR1cm4gcHJvYnMNCmBgYA0KDQojIyBWLiBBZHZhbmNlZCBJbXBsZW1lbnRhdGlvbiB3aXRoIEdQVSBBY2NlbGVyYXRpb24NCg0KYGBgcHl0aG9uDQppbXBvcnQgY3VweSBhcyBjcA0KZnJvbSBjdXB5LmxpbmFsZyBpbXBvcnQgbm9ybQ0KDQpjbGFzcyBHUFVOYWl2ZUJheWVzOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmKToNCiAgICAgICAgc2VsZi5jbGFzc2VzXyA9IE5vbmUNCiAgICAgICAgc2VsZi5jbGFzc19wcmlvcnNfID0gTm9uZQ0KICAgICAgICBzZWxmLmNsYXNzX21lYW5zXyA9IE5vbmUNCiAgICAgICAgc2VsZi5jbGFzc192YXJzXyA9IE5vbmUNCg0KICAgIGRlZiBmaXQoc2VsZiwgWCwgeSk6DQogICAgICAgIGlmIG5vdCBjcC5jdWRhLmlzX2F2YWlsYWJsZSgpOg0KICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKCJDVURBIGlzIG5vdCBhdmFpbGFibGUiKQ0KICAgICAgICAgICAgDQogICAgICAgICMgQ29udmVydCB0byBHUFUgYXJyYXlzDQogICAgICAgIFggPSBjcC5hcnJheShYKQ0KICAgICAgICB5ID0gY3AuYXJyYXkoeSkNCiAgICAgICAgDQogICAgICAgIHNlbGYuY2xhc3Nlc18gPSBjcC51bmlxdWUoeSkNCiAgICAgICAgbl9jbGFzc2VzID0gbGVuKHNlbGYuY2xhc3Nlc18pDQogICAgICAgIG5fZmVhdHVyZXMgPSBYLnNoYXBlWzFdDQogICAgICAgIA0KICAgICAgICAjIEluaXRpYWxpemUgcGFyYW1ldGVycw0KICAgICAgICBzZWxmLmNsYXNzX3ByaW9yc18gPSBjcC56ZXJvcyhuX2NsYXNzZXMpDQogICAgICAgIHNlbGYuY2xhc3NfbWVhbnNfID0gY3AuemVyb3MoKG5fY2xhc3Nlcywgbl9mZWF0dXJlcykpDQogICAgICAgIHNlbGYuY2xhc3NfdmFyc18gPSBjcC56ZXJvcygobl9jbGFzc2VzLCBuX2ZlYXR1cmVzKSkNCiAgICAgICAgDQogICAgICAgICMgQ2FsY3VsYXRlIHBhcmFtZXRlcnMgZm9yIGVhY2ggY2xhc3MNCiAgICAgICAgZm9yIGksIGMgaW4gZW51bWVyYXRlKHNlbGYuY2xhc3Nlc18pOg0KICAgICAgICAgICAgWF9jID0gWFt5ID09IGNdDQogICAgICAgICAgICBzZWxmLmNsYXNzX3ByaW9yc19baV0gPSBYX2Muc2hhcGVbMF0gLyBYLnNoYXBlWzBdDQogICAgICAgICAgICBzZWxmLmNsYXNzX21lYW5zX1tpXSA9IFhfYy5tZWFuKGF4aXM9MCkNCiAgICAgICAgICAgIHNlbGYuY2xhc3NfdmFyc19baV0gPSBYX2MudmFyKGF4aXM9MCkNCiAgICAgICAgICAgIA0KICAgICAgICByZXR1cm4gc2VsZg0KYGBgDQoNCiMjIFZJLiBDb21wYXJpc29uIHdpdGggTGluZWFyIFJlZ3Jlc3Npb24NCg0KS2V5IERpZmZlcmVuY2VzOg0KMS4gKipQdXJwb3NlKioNCiAgIC0gTmFpdmUgQmF5ZXM6IENsYXNzaWZpY2F0aW9uDQogICAtIExpbmVhciBSZWdyZXNzaW9uOiBDb250aW51b3VzIHZhbHVlIHByZWRpY3Rpb24NCg0KMi4gKipNYXRoZW1hdGljYWwgRm91bmRhdGlvbioqDQogICBgYGANCiAgIE5haXZlIEJheWVzOiBQKFl8WCkg4oidIFAoWHxZKVAoWSkNCiAgIExpbmVhciBSZWdyZXNzaW9uOiBZID0gzrJYICsgzrUNCiAgIGBgYA0KDQozLiAqKk91dHB1dCBUeXBlKioNCiAgIC0gTmFpdmUgQmF5ZXM6IFByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCiAgIC0gTGluZWFyIFJlZ3Jlc3Npb246IENvbnRpbnVvdXMgdmFsdWVzDQoNCiMjIFZJSS4gS2V5IFRha2Vhd2F5cw0KDQoxLiBOYWl2ZSBCYXllcyBkZXJpdmVzIGl0cyBwb3dlciBmcm9tIEJheWVzJyB0aGVvcmVtIHdoaWxlIGFzc3VtaW5nIGZlYXR1cmUgaW5kZXBlbmRlbmNlDQoyLiBJdCBuYXR1cmFsbHkgaGFuZGxlcyBtdWx0aWNsYXNzIHByb2JsZW1zIHdpdGhvdXQgc3BlY2lhbCBtb2RpZmljYXRpb25zDQozLiBHUFUgaW1wbGVtZW50YXRpb24gY2FuIHNpZ25pZmljYW50bHkgc3BlZWQgdXAgY2FsY3VsYXRpb25zIGZvciBsYXJnZSBkYXRhc2V0cw0KDQojIyBWSUlJLiBQcmFjdGljZSBRdWVzdGlvbnMNCg0KMS4gSG93IGRvZXMgdGhlIGluZGVwZW5kZW5jZSBhc3N1bXB0aW9uIGluIE5haXZlIEJheWVzIGFmZmVjdCBpdHMgcGVyZm9ybWFuY2Ugb24gcmVhbC13b3JsZCBkYXRhc2V0cyB3aGVyZSBmZWF0dXJlcyBhcmUgb2Z0ZW4gY29ycmVsYXRlZD8NCg0KMi4gSW4gd2hhdCBzY2VuYXJpb3Mgd291bGQgeW91IGNob29zZSBOYWl2ZSBCYXllcyBvdmVyIG90aGVyIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXM/DQoNCjMuIEhvdyBkb2VzIHRoZSBjb21wdXRhdGlvbmFsIGNvbXBsZXhpdHkgb2YgTmFpdmUgQmF5ZXMgY29tcGFyZSBiZXR3ZWVuIENQVSBhbmQgR1BVIGltcGxlbWVudGF0aW9ucyBmb3IgZGlmZmVyZW50IGRhdGFzZXQgc2l6ZXM/DQoNCg0KIyBCYXllcycgUnVsZSBhbmQgTmFpdmUgQmF5ZXMgU3R1ZHkgR3VpZGUgLSBQYXJ0IDMNCg0KIyMgSVguIFByYWN0aWNhbCBBcHBsaWNhdGlvbnMgYW5kIFVzZSBDYXNlcw0KDQojIyMgQ29tbW9uIEFwcGxpY2F0aW9ucw0KMS4gVGV4dCBDbGFzc2lmaWNhdGlvbg0KMi4gU3BhbSBEZXRlY3Rpb24NCjMuIFNlbnRpbWVudCBBbmFseXNpcw0KNC4gTWVkaWNhbCBEaWFnbm9zaXMNCg0KIyMjIEltcGxlbWVudGF0aW9uIEV4YW1wbGUgZm9yIFRleHQgQ2xhc3NpZmljYXRpb24NCg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyDQpmcm9tIHNrbGVhcm4ubmFpdmVfYmF5ZXMgaW1wb3J0IE11bHRpbm9taWFsTkINCmltcG9ydCBjdXB5IGFzIGNwDQoNCmNsYXNzIFRleHRDbGFzc2lmaWNhdGlvbk5COg0KICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1PUZhbHNlKToNCiAgICAgICAgc2VsZi52ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKCkNCiAgICAgICAgc2VsZi5jbGFzc2lmaWVyID0gTXVsdGlub21pYWxOQigpDQogICAgICAgIHNlbGYudXNlX2dwdSA9IHVzZV9ncHUgYW5kIGNwLmN1ZGEuaXNfYXZhaWxhYmxlKCkNCiAgICAgICAgDQogICAgZGVmIHByZXByb2Nlc3NfdGV4dChzZWxmLCB0ZXh0cyk6DQogICAgICAgICMgQ29udmVydCB0ZXh0IHRvIG51bWVyaWNhbCBmZWF0dXJlcw0KICAgICAgICBYID0gc2VsZi52ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0odGV4dHMpDQogICAgICAgIGlmIHNlbGYudXNlX2dwdToNCiAgICAgICAgICAgIHJldHVybiBjcC5hcnJheShYLnRvYXJyYXkoKSkNCiAgICAgICAgcmV0dXJuIFgNCiAgICANCiAgICBkZWYgdHJhaW4oc2VsZiwgdGV4dHMsIGxhYmVscyk6DQogICAgICAgIFggPSBzZWxmLnByZXByb2Nlc3NfdGV4dCh0ZXh0cykNCiAgICAgICAgaWYgc2VsZi51c2VfZ3B1Og0KICAgICAgICAgICAgbGFiZWxzID0gY3AuYXJyYXkobGFiZWxzKQ0KICAgICAgICBzZWxmLmNsYXNzaWZpZXIuZml0KFgsIGxhYmVscykNCiAgICAgICAgDQogICAgZGVmIHByZWRpY3Qoc2VsZiwgbmV3X3RleHRzKToNCiAgICAgICAgWCA9IHNlbGYudmVjdG9yaXplci50cmFuc2Zvcm0obmV3X3RleHRzKQ0KICAgICAgICBpZiBzZWxmLnVzZV9ncHU6DQogICAgICAgICAgICBYID0gY3AuYXJyYXkoWC50b2FycmF5KCkpDQogICAgICAgIHJldHVybiBzZWxmLmNsYXNzaWZpZXIucHJlZGljdChYKQ0KYGBgDQoNCiMjIFguIFBlcmZvcm1hbmNlIE9wdGltaXphdGlvbiBUaXBzDQoNCiMjIyAxLiBEYXRhIFByZXByb2Nlc3NpbmcNCmBgYHB5dGhvbg0KY2xhc3MgRGF0YVByZXByb2Nlc3NvcjoNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHNlbGYuc2NhbGVyID0gTm9uZQ0KICAgICAgICANCiAgICBkZWYgaGFuZGxlX21pc3NpbmdfdmFsdWVzKHNlbGYsIFgpOg0KICAgICAgICAjIFJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgd2l0aCBtZWFuDQogICAgICAgIHJldHVybiBucC5uYW5fdG9fbnVtKFgsIG5hbj1ucC5uYW5tZWFuKFgpKQ0KICAgICAgICANCiAgICBkZWYgbm9ybWFsaXplX2ZlYXR1cmVzKHNlbGYsIFgpOg0KICAgICAgICAjIExvZyB0cmFuc2Zvcm1hdGlvbiBmb3Igc2tld2VkIGZlYXR1cmVzDQogICAgICAgIHJldHVybiBucC5sb2cxcChYKQ0KICAgICAgICANCiAgICBkZWYgaGFuZGxlX2NhdGVnb3JpY2FsX2ZlYXR1cmVzKHNlbGYsIFgpOg0KICAgICAgICAjIE9uZS1ob3QgZW5jb2RpbmcNCiAgICAgICAgcmV0dXJuIHBkLmdldF9kdW1taWVzKFgpLnZhbHVlcw0KYGBgDQoNCiMjIyAyLiBHUFUgTWVtb3J5IE1hbmFnZW1lbnQNCmBgYHB5dGhvbg0KY2xhc3MgR1BVTWVtb3J5TWFuYWdlcjoNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHNlbGYubWVtb3J5X3Bvb2wgPSBjcC5jdWRhLk1lbW9yeVBvb2woKQ0KICAgICAgICANCiAgICBkZWYgX19lbnRlcl9fKHNlbGYpOg0KICAgICAgICBjcC5jdWRhLnNldF9hbGxvY2F0b3Ioc2VsZi5tZW1vcnlfcG9vbC5tYWxsb2MpDQogICAgICAgIA0KICAgIGRlZiBfX2V4aXRfXyhzZWxmLCAqYXJncyk6DQogICAgICAgIHNlbGYubWVtb3J5X3Bvb2wuZnJlZV9hbGxfYmxvY2tzKCkNCmBgYA0KDQojIyBYSS4gQ29tbW9uIFBpdGZhbGxzIGFuZCBTb2x1dGlvbnMNCg0KIyMjIDEuIFplcm8gUHJvYmFiaWxpdHkgUHJvYmxlbQ0KYGBgcHl0aG9uDQpjbGFzcyBMYXBsYWNlU21vb3RoaW5nTkI6DQogICAgZGVmIF9faW5pdF9fKHNlbGYsIGFscGhhPTEuMCk6DQogICAgICAgIHNlbGYuYWxwaGEgPSBhbHBoYSAgIyBTbW9vdGhpbmcgcGFyYW1ldGVyDQogICAgICAgIA0KICAgIGRlZiBjYWxjdWxhdGVfcHJvYmFiaWxpdHkoc2VsZiwgZmVhdHVyZV9jb3VudHMsIHRvdGFsX2NvdW50cyk6DQogICAgICAgICMgQWRkIHNtb290aGluZyB0byBwcmV2ZW50IHplcm8gcHJvYmFiaWxpdGllcw0KICAgICAgICByZXR1cm4gKGZlYXR1cmVfY291bnRzICsgc2VsZi5hbHBoYSkgLyAodG90YWxfY291bnRzICsgc2VsZi5hbHBoYSAqIGxlbihmZWF0dXJlX2NvdW50cykpDQpgYGANCg0KIyMjIDIuIE51bWVyaWNhbCBTdGFiaWxpdHkNCmBgYHB5dGhvbg0KZGVmIGxvZ19zdW1fZXhwKHgpOg0KICAgICIiIk51bWVyaWNhbGx5IHN0YWJsZSBsb2cgc3VtIGV4cC4iIiINCiAgICBtYXhfeCA9IG5wLm1heCh4KQ0KICAgIHJldHVybiBtYXhfeCArIG5wLmxvZyhucC5zdW0obnAuZXhwKHggLSBtYXhfeCkpKQ0KYGBgDQoNCiMjIFhJSS4gQWR2YW5jZWQgVGVjaG5pcXVlcw0KDQojIyMgMS4gRmVhdHVyZSBTZWxlY3Rpb24NCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLmZlYXR1cmVfc2VsZWN0aW9uIGltcG9ydCBTZWxlY3RLQmVzdCwgY2hpMg0KDQpjbGFzcyBGZWF0dXJlU2VsZWN0b3I6DQogICAgZGVmIF9faW5pdF9fKHNlbGYsIGs9MTApOg0KICAgICAgICBzZWxmLnNlbGVjdG9yID0gU2VsZWN0S0Jlc3QoY2hpMiwgaz1rKQ0KICAgICAgICANCiAgICBkZWYgc2VsZWN0X2ZlYXR1cmVzKHNlbGYsIFgsIHkpOg0KICAgICAgICByZXR1cm4gc2VsZi5zZWxlY3Rvci5maXRfdHJhbnNmb3JtKFgsIHkpDQpgYGANCg0KIyMjIDIuIENyb3NzLVZhbGlkYXRpb24NCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgY3Jvc3NfdmFsX3Njb3JlDQoNCmRlZiBldmFsdWF0ZV9tb2RlbChtb2RlbCwgWCwgeSwgY3Y9NSk6DQogICAgc2NvcmVzID0gY3Jvc3NfdmFsX3Njb3JlKG1vZGVsLCBYLCB5LCBjdj1jdikNCiAgICByZXR1cm4gew0KICAgICAgICAnbWVhbl9hY2N1cmFjeSc6IHNjb3Jlcy5tZWFuKCksDQogICAgICAgICdzdGRfYWNjdXJhY3knOiBzY29yZXMuc3RkKCkNCiAgICB9DQpgYGANCg0KIyMgWElJSS4gRmluYWwgS2V5IFRha2Vhd2F5cw0KDQoxLiBVbmRlcnN0YW5kaW5nIHRoZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbiBpcyBjcnVjaWFsIGZvciBlZmZlY3RpdmUgaW1wbGVtZW50YXRpb24NCjIuIEdQVSBhY2NlbGVyYXRpb24gY2FuIHByb3ZpZGUgc2lnbmlmaWNhbnQgc3BlZWR1cCBmb3IgbGFyZ2UgZGF0YXNldHMNCjMuIFByb3BlciBwcmVwcm9jZXNzaW5nIGFuZCBoYW5kbGluZyBvZiBlZGdlIGNhc2VzIGlzIGVzc2VudGlhbCBmb3Igcm9idXN0IG1vZGVscw0KDQojIyBYSVYuIEFkZGl0aW9uYWwgUHJhY3RpY2UgUXVlc3Rpb25zDQoNCjEuIEhvdyB3b3VsZCB5b3UgaGFuZGxlIGhpZ2hseSBpbWJhbGFuY2VkIGRhdGFzZXRzIHdoZW4gdXNpbmcgTmFpdmUgQmF5ZXM/DQoyLiBXaGF0IHRlY2huaXF1ZXMgY2FuIGJlIHVzZWQgdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIHNtb290aGluZyBwYXJhbWV0ZXI/DQozLiBJbiB3aGF0IHNjZW5hcmlvcyBtaWdodCBHUFUgYWNjZWxlcmF0aW9uIGFjdHVhbGx5IHNsb3cgZG93biBjb21wdXRhdGlvbj8NCg0KIyMgWFYuIFN0dWR5IFRpcHMNCg0KMS4gRm9jdXMgb24gdW5kZXJzdGFuZGluZyB0aGUgcHJvYmFiaWxpc3RpYyBmb3VuZGF0aW9ucw0KMi4gUHJhY3RpY2Ugd2l0aCByZWFsLXdvcmxkIGRhdGFzZXRzDQozLiBFeHBlcmltZW50IHdpdGggYm90aCBDUFUgYW5kIEdQVSBpbXBsZW1lbnRhdGlvbnMNCjQuIFBheSBhdHRlbnRpb24gdG8gcHJlcHJvY2Vzc2luZyBzdGVwcw0KNS4gVW5kZXJzdGFuZCB0aGUgbGltaXRhdGlvbnMgYW5kIGFzc3VtcHRpb25zDQoNCg0KDQpUaHJlZSB0aG91Z2h0LXByb3Zva2luZyBhbmQgbG9naWNhbGx5IGNvbXByZWhlbnNpdmUgcXVlc3Rpb25zIGFib3V0IE5haXZlIEJheWVzOg0KDQoxLiAqKiJXaHkgZG9lcyBOYWl2ZSBCYXllcyBvZnRlbiBwZXJmb3JtIHdlbGwgaW4gaGlnaC1kaW1lbnNpb25hbCB0ZXh0IGNsYXNzaWZpY2F0aW9uLCBkZXNwaXRlIHRoZSBuYWl2ZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbj8iKioNCiAgIC0gQWRkcmVzc2VzIHRoZSBmdW5kYW1lbnRhbCBwYXJhZG94IG9mIE5haXZlIEJheWVzDQogICAtIFJlcXVpcmVzIHVuZGVyc3RhbmRpbmcgb2YgYm90aCB0aGVvcnkgYW5kIHByYWN0aWNhbCBhcHBsaWNhdGlvbnMNCiAgIC0gQ2hhbGxlbmdlcyBzdHVkZW50cyB0byB0aGluayBhYm91dCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbW9kZWwgYXNzdW1wdGlvbnMgYW5kIHJlYWwtd29ybGQgcGVyZm9ybWFuY2UNCiAgIC0gQ29ubmVjdHMgdG8gcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMgaW4gdGV4dCBjbGFzc2lmaWNhdGlvbg0KDQoyLiAqKiJIb3cgZG9lcyB0aGUgaW5kZXBlbmRlbmNlIGFzc3VtcHRpb24gaW4gTmFpdmUgQmF5ZXMgYWZmZWN0IGl0cyBwZXJmb3JtYW5jZSBvbiByZWFsLXdvcmxkIGRhdGFzZXRzIHdoZXJlIGZlYXR1cmVzIGFyZSBvZnRlbiBjb3JyZWxhdGVkPyIqKg0KICAgLSBGb3JjZXMgY29uc2lkZXJhdGlvbiBvZiB0aGUgY29yZSBhc3N1bXB0aW9ucw0KICAgLSBSZXF1aXJlcyBhbmFseXNpcyBvZiByZWFsLXdvcmxkIGltcGxpY2F0aW9ucw0KICAgLSBFbmNvdXJhZ2VzIGNyaXRpY2FsIHRoaW5raW5nIGFib3V0IG1vZGVsIGxpbWl0YXRpb25zDQogICAtIENvbm5lY3RzIHRoZW9yZXRpY2FsIGNvbmNlcHRzIHRvIHByYWN0aWNhbCBhcHBsaWNhdGlvbnMNCg0KMy4gKioiSW4gd2hhdCBzY2VuYXJpb3Mgd291bGQgeW91IGNob29zZSBOYWl2ZSBCYXllcyBvdmVyIG90aGVyIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXM/IioqDQogICAtIFJlcXVpcmVzIGNvbXBhcmF0aXZlIGFuYWx5c2lzDQogICAtIERlbWFuZHMgdW5kZXJzdGFuZGluZyBvZiB2YXJpb3VzIGFsZ29yaXRobXMnIHN0cmVuZ3RocyBhbmQgd2Vha25lc3Nlcw0KICAgLSBGb2N1c2VzIG9uIHByYWN0aWNhbCBkZWNpc2lvbi1tYWtpbmcNCiAgIC0gRW5jb3VyYWdlcyBjb25zaWRlcmF0aW9uIG9mIHJlYWwtd29ybGQgY29uc3RyYWludHMgYW5kIHJlcXVpcmVtZW50cw0KDQoNClRha2Vhd2F5czogDQoxLiAqKlRoZSBJbmRlcGVuZGVuY2UgQXNzdW1wdGlvbidzIFBhcmFkb3gqKg0KICAgLSBEZXNwaXRlIGl0cyAibmFpdmUiIGFzc3VtcHRpb24gdGhhdCBmZWF0dXJlcyBhcmUgaW5kZXBlbmRlbnQgKHdoaWNoIGlzIHJhcmVseSB0cnVlIGluIHJlYWwtd29ybGQgZGF0YSksIE5haXZlIEJheWVzIG9mdGVuIHBlcmZvcm1zIHN1cnByaXNpbmdseSB3ZWxsIGluIHByYWN0aWNlDQogICAtIFRoaXMgaXMgcGFydGljdWxhcmx5IHRydWUgaW4gdGV4dCBjbGFzc2lmaWNhdGlvbiBhbmQgaGlnaC1kaW1lbnNpb25hbCBwcm9ibGVtcw0KICAgLSBVbmRlcnN0YW5kaW5nOiBUaGUgbW9kZWwncyBzaW1wbGljaXR5IGFuZCBlZmZpY2llbmN5IG9mdGVuIG91dHdlaWdoIHRoZSBsaW1pdGF0aW9ucyBvZiB0aGUgaW5kZXBlbmRlbmNlIGFzc3VtcHRpb24NCiAgIC0gTWF0aGVtYXRpY2FsIHJlcHJlc2VudGF0aW9uOiBQKHjigoEseOKCgnx5KSA9IFAoeOKCgXx5KSDDlyBQKHjigoJ8eSkNCg0KMi4gKipQcm9iYWJpbGlzdGljIEZvdW5kYXRpb24gYW5kIFNjYWxhYmlsaXR5KioNCiAgIC0gTmFpdmUgQmF5ZXMgaXMgZnVuZGFtZW50YWxseSBhIHByb2JhYmlsaXN0aWMgY2xhc3NpZmllciBiYXNlZCBvbiBCYXllcycgVGhlb3JlbQ0KICAgLSBJdCdzIGhpZ2hseSBzY2FsYWJsZSBhbmQgY2FuIGhhbmRsZSBsYXJnZSBkYXRhc2V0cyBlZmZpY2llbnRseQ0KICAgLSBUcmFpbmluZyBpcyBPKG4pIGNvbXBsZXhpdHksIG1ha2luZyBpdCBvbmUgb2YgdGhlIGZhc3Rlc3QgY2xhc3NpZmllcnMNCiAgIC0gS2V5IGVxdWF0aW9uOiBQKHl8eCkgPSBQKHh8eSlQKHkpL1AoeCkNCiAgIC0gVGhpcyBtYWtlcyBpdCBwYXJ0aWN1bGFybHkgdmFsdWFibGUgZm9yIHJlYWwtdGltZSBhcHBsaWNhdGlvbnMgYW5kIGxhcmdlLXNjYWxlIGNsYXNzaWZpY2F0aW9uIHRhc2tzDQoNCjMuICoqU3VzY2VwdGliaWxpdHkgdG8gWmVybyBQcm9iYWJpbGl0eSBQcm9ibGVtKioNCiAgIC0gV2hlbiBhIGZlYXR1cmUgdmFsdWUgaW4gdGhlIHRlc3QgZGF0YSBuZXZlciBhcHBlYXJzIGluIHRoZSB0cmFpbmluZyBkYXRhLCBpdCBsZWFkcyB0byB6ZXJvIHByb2JhYmlsaXR5DQogICAtIFRoaXMgaXMgc29sdmVkIHRocm91Z2ggc21vb3RoaW5nIHRlY2huaXF1ZXMgKGxpa2UgTGFwbGFjZS9BZGRpdGl2ZSBzbW9vdGhpbmcpDQogICAtIENyaXRpY2FsIGZvciBwcmFjdGljYWwgaW1wbGVtZW50YXRpb246IFAoeHx5KSA9IChjb3VudCh4LHkpICsgzrEpLyhjb3VudCh5KSArIM6xfFZ8KQ0KICAgLSBVbmRlcnN0YW5kaW5nIHRoaXMgbGltaXRhdGlvbiBhbmQgaXRzIHNvbHV0aW9ucyBpcyBjcnVjaWFsIGZvciBlZmZlY3RpdmUgaW1wbGVtZW50YXRpb24NCg0KVGhlc2UgdGFrZWF3YXlzIGFyZSBwYXJ0aWN1bGFybHkgaW1wb3J0YW50IGJlY2F1c2UgdGhleToNCi0gQ292ZXIgYm90aCB0aGVvcmV0aWNhbCBmb3VuZGF0aW9ucyBhbmQgcHJhY3RpY2FsIGltcGxpY2F0aW9ucw0KLSBBZGRyZXNzIGNvbW1vbiBjaGFsbGVuZ2VzIGFuZCBzb2x1dGlvbnMNCi0gRXhwbGFpbiB3aHkgYW5kIHdoZW4gdGhlIGFsZ29yaXRobSB3b3JrcyB3ZWxsDQotIFByb3ZpZGUgZXNzZW50aWFsIGtub3dsZWRnZSBmb3IgcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMNCi0gSGVscCBpbiBtYWtpbmcgaW5mb3JtZWQgZGVjaXNpb25zIGFib3V0IHdoZW4gdG8gdXNlIE5haXZlIEJheWVzDQoNCkVhY2ggb2YgdGhlc2UgcG9pbnRzIGFmZmVjdHM6DQotIE1vZGVsIHNlbGVjdGlvbiBkZWNpc2lvbnMNCi0gSW1wbGVtZW50YXRpb24gYXBwcm9hY2hlcw0KLSBQZXJmb3JtYW5jZSBvcHRpbWl6YXRpb24NCi0gUHJvYmxlbS1zb2x2aW5nIHN0cmF0ZWdpZXMNCi0gVW5kZXJzdGFuZGluZyBvZiByZXN1bHRzDQo=