Study Guide: Support Vector Machines (SVMs) – Module 9

Part 1


1. Introduction to Support Vector Machines

SVM is a powerful supervised learning algorithm primarily used for classification tasks. The main goal of SVM is to find an optimal separating hyperplane that best divides different classes of data points.

Key Concepts:

  • Linear Separability: When a dataset can be perfectly divided using a straight line (in 2D) or a hyperplane (in higher dimensions).
  • Hyperplane: A decision boundary separating different classes.
  • Margin: The distance between the closest data points (support vectors) and the hyperplane.
  • Support Vectors: Data points that lie closest to the hyperplane and influence its orientation.
  • Maximal Margin Classifier: The hyperplane that maximizes the margin.
  • Soft Margin: Introduces slack variables to allow misclassification in cases where perfect separation is impossible.

2. Vector Algebra and Hyperplanes

Before understanding SVM, it is essential to understand vector algebra and the equation of a hyperplane.

Hyperplane Equation:

A hyperplane in \(n\)-dimensional space is defined as: \[ \beta_0 + \beta^T x = 0 \] Where: - \(\beta\) is the normal vector (perpendicular to the hyperplane). - \(x\) represents the feature vectors. - \(\beta_0\) is the bias term.

If we have two classes labeled \(y_i \in \{-1, 1\}\), the SVM decision boundary follows: \[ y_i (\beta^T x_i + \beta_0) \geq M \] where \(M\) is the margin.

Finding the Optimal Hyperplane

The objective is to maximize the margin, which translates to minimizing \(||\beta||\) subject to the constraint:

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

This is a convex optimization problem that can be solved using Lagrange multipliers.


3. Handling Non-Separable Data

In many real-world cases, data is not perfectly separable. SVM introduces slack variables \(\xi_i\) to allow some misclassification:

\[ y_i (\beta^T x_i + \beta_0) \geq 1 - \xi_i \]

where \(\xi_i \geq 0\) represents the amount by which data points violate the margin constraint.

The new optimization objective becomes:

\[ \min_{\beta, \beta_0, \xi} \frac{1}{2} ||\beta||^2 + C \sum \xi_i \]

where \(C\) is a regularization parameter controlling the trade-off between maximizing margin and minimizing classification errors.


4. Kernel Trick for Non-Linear Data

If a dataset is not linearly separable, we map it to a higher-dimensional space using kernels:

\[ K(x_i, x_j) = \phi(x_i)^T \phi(x_j) \]

Common kernels: - Linear Kernel: \(K(x_i, x_j) = x_i^T x_j\) - Polynomial Kernel: \(K(x_i, x_j) = (x_i^T x_j + c)^d\) - Radial Basis Function (RBF) Kernel: \(K(x_i, x_j) = \exp(-\gamma ||x_i - x_j||^2)\)

The kernel trick enables SVM to operate in higher-dimensional spaces without explicitly computing transformations.


5. Mathematical Formulation of SVM

Hard Margin SVM (Linearly Separable)

\[ \min_{\beta, \beta_0} \frac{1}{2} ||\beta||^2 \] subject to: \[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

Soft Margin SVM (Allowing Misclassification)

\[ \min_{\beta, \beta_0, \xi} \frac{1}{2} ||\beta||^2 + C \sum \xi_i \] subject to: \[ y_i (\beta^T x_i + \beta_0) \geq 1 - \xi_i, \quad \xi_i \geq 0 \]


6. Implementing SVM in Python

Here’s how you can implement SVM using Scikit-Learn:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Generate synthetic dataset
X, y = make_classification(n_samples=100, n_features=2, n_classes=2, n_redundant=0, random_state=42)
y = np.where(y == 0, -1, 1)  # Convert labels to {-1, 1}

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

# Train SVM classifier
svm = SVC(kernel='linear', C=1.0)
svm.fit(X_train, y_train)

# Predictions
y_pred = svm.predict(X_test)
print(f'Accuracy: {accuracy_score(y_test, y_pred):.2f}')

# Plot decision boundary
def plot_svm_decision_boundary(X, y, model):
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
    
    ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    
    xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
                         np.linspace(ylim[0], ylim[1], 50))
    
    Z = model.decision_function(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    ax.contour(xx, yy, Z, colors='k', levels=[-1, 0, 1], linestyles=['--', '-', '--'])
    
    plt.title("SVM Decision Boundary")
    plt.show()

plot_svm_decision_boundary(X_train, y_train, svm)

7. Key Takeaways

  • SVM finds the optimal hyperplane that maximizes margin.
  • Slack variables \(\xi\) allow for soft-margin classification.
  • Kernels enable non-linear classification by transforming data into higher dimensions.
  • SVM is powerful for both linear and non-linear classification problems.
  • The regularization parameter \(C\) controls the trade-off between margin maximization and misclassification tolerance.

8. Practical Considerations

  • Choose an appropriate kernel based on dataset complexity.
  • Hyperparameter tuning (C, γ for RBF kernel) is critical and can be optimized using grid search and cross-validation.
  • SVMs scale poorly with very large datasets (due to quadratic complexity), so consider alternative classifiers like Random Forests or Deep Learning for big data.

Here is an expanded Study Guide on Support Vector Machines (SVMs) incorporating concepts from The Elements of Statistical Learning (ESLII) and The Statistical Sleuth.


Study Guide: Support Vector Machines (SVMs)

Module 9: Support Vector Machines

1. Introduction to SVMs

Support Vector Machines (SVMs) are supervised learning algorithms used primarily for classification and regression tasks. The key idea is to find an optimal hyperplane that best separates different classes.

Why Use SVMs?

  • Effective in high-dimensional spaces.
  • Works well with small datasets but may scale poorly for very large datasets.
  • Can model non-linear relationships using kernel tricks.

Key Terms

  • Hyperplane: A decision boundary separating different classes.
  • Margin: The distance between the closest points (support vectors) and the hyperplane.
  • Support Vectors: Data points that define the margin and influence the position of the hyperplane.
  • Slack Variables (ξ): Allow misclassification in non-separable cases.
  • Kernel Trick: Maps data to a higher-dimensional space where it becomes separable.

2. Mathematical Formulation of SVM

A hyperplane in an \(n\)-dimensional space is represented as:

\[ \beta_0 + \beta^T x = 0 \]

For classification, we assign labels \(y_i \in \{-1,1\}\) and enforce the constraint:

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

Optimization Problem

The objective is to maximize the margin \(M\), which is equivalent to minimizing:

\[ \frac{1}{2} ||\beta||^2 \]

subject to:

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

For non-separable data, we introduce slack variables \(\xi_i\):

\[ y_i (\beta^T x_i + \beta_0) \geq 1 - \xi_i, \quad \xi_i \geq 0 \]

which leads to the soft-margin SVM:

\[ \min_{\beta, \beta_0, \xi} \frac{1}{2} ||\beta||^2 + C \sum \xi_i \]

where \(C\) is a regularization parameter.

Dual Formulation

Using Lagrange multipliers \(\alpha_i\), we rewrite the optimization as:

\[ \max_{\alpha} \sum_{i=1}^{N} \alpha_i - \frac{1}{2} \sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_i \alpha_j y_i y_j K(x_i, x_j) \]

subject to:

\[ \sum_{i=1}^{N} \alpha_i y_i = 0, \quad 0 \leq \alpha_i \leq C \]

where \(K(x_i, x_j)\) is the kernel function.


3. Handling Non-Separable Data with Kernels

When data is not linearly separable, we transform it into a higher-dimensional space using kernel functions:

\[ K(x_i, x_j) = \phi(x_i)^T \phi(x_j) \]

Common Kernel Functions

  1. Linear Kernel: \(K(x_i, x_j) = x_i^T x_j\)
  2. Polynomial Kernel: \(K(x_i, x_j) = (x_i^T x_j + c)^d\)
  3. Radial Basis Function (RBF) Kernel: \(K(x_i, x_j) = \exp(-\gamma ||x_i - x_j||^2)\)
  4. Sigmoid Kernel: \(K(x_i, x_j) = \tanh(\kappa x_i^T x_j + c)\)

The kernel trick allows non-linear classification without explicitly transforming data.


4. Computational Aspects & SVM in High Dimensions

From ESLII, SVM’s ability to work in high-dimensional spaces is discussed in Chapter 12. However, it warns about the Curse of Dimensionality: - If most features are irrelevant, SVM may perform poorly. - SVM may need feature selection to improve generalization.

Regularization Parameter \(C\)

  • Higher \(C\) → Penalizes misclassification more (narrower margin, overfitting risk).
  • Lower \(C\) → Allows more margin violations (wider margin, better generalization).

Choosing Kernel Parameters

  • For RBF Kernel, the choice of \(\gamma\) impacts model complexity:
    • Large \(\gamma\) → Higher flexibility (risk of overfitting).
    • Small \(\gamma\) → Simpler model (may underfit).

5. Implementing SVM in Python

Here’s how to implement an SVM classifier with Scikit-Learn:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Generate synthetic dataset
X, y = make_classification(n_samples=100, n_features=2, n_classes=2, n_redundant=0, random_state=42)
y = np.where(y == 0, -1, 1)  # Convert labels to {-1, 1}

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

# Train SVM classifier with RBF kernel
svm = SVC(kernel='rbf', C=1.0, gamma=0.5)
svm.fit(X_train, y_train)

# Predictions
y_pred = svm.predict(X_test)
print(f'Accuracy: {accuracy_score(y_test, y_pred):.2f}')

# Plot decision boundary
def plot_svm_decision_boundary(X, y, model):
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
    
    ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    
    xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
                         np.linspace(ylim[0], ylim[1], 50))
    
    Z = model.decision_function(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    ax.contour(xx, yy, Z, colors='k', levels=[-1, 0, 1], linestyles=['--', '-', '--'])
    
    plt.title("SVM Decision Boundary")
    plt.show()

plot_svm_decision_boundary(X_train, y_train, svm)

6. Key Takeaways

  1. SVM finds the optimal hyperplane that maximizes the margin.
  2. Soft margin SVMs allow some misclassification for better generalization.
  3. Kernels enable non-linear classification without explicit transformation.
  4. Regularization parameter \(C\) and kernel choice significantly impact model performance.
  5. SVMs are computationally expensive for large datasets but are effective in high-dimensional spaces.

7. Additional Insights from ESLII

  • Table 12.2 in ESLII compares SVM, polynomial, and radial basis function classifiers, showing that RBF SVM often performs best.
  • Regularization and kernel selection are key in optimizing SVM models.

—### Study Guide: Margins and Loss in Support Vector Machines (SVMs) #### Module 9, Section 2: Margins and Loss


Part 2


1. Introduction to Margins and Loss in SVM

Support Vector Machines (SVMs) operate by maximizing the margin between different classes. However, real-world datasets are often not perfectly separable, which introduces classification errors. To handle this, we use loss functions, specifically the hinge loss, to penalize misclassified points and ensure robust model performance.


2. Hinge Loss in SVM

What is Hinge Loss?

Hinge loss is a function designed to penalize misclassified points while allowing correctly classified points far from the decision boundary to contribute no loss. It is mathematically defined as:

\[ L(y, f(x)) = \max(0, 1 - y f(x)) \]

where: - \(y \in \{-1,1\}\) is the true class label. - \(f(x)\) is the decision function (e.g., \(f(x) = \beta^T x + \beta_0\)).

Interpretation of Hinge Loss

  • If a point is correctly classified and far from the margin (i.e., \(y f(x) > 1\)), the loss is zero.
  • If a point is near the margin (i.e., \(0 < y f(x) \leq 1\)), it contributes to the loss.
  • If a point is misclassified (i.e., \(y f(x) < 0\)), the loss increases linearly.

Quadratic Hinge Loss

An alternative quadratic version of hinge loss is:

\[ L(y, f(x)) = ( \max(0, 1 - y f(x)) )^2 \]

This smooths out the loss function, making it more stable during optimization.

Comparison with Other Loss Functions

Hinge loss differs from logistic regression’s log-likelihood loss and squared error loss: - Log-likelihood loss (used in logistic regression) penalizes misclassifications more smoothly. - Squared error loss assigns a quadratic penalty to errors, making it less robust to outliers. - Hinge loss is a compromise—it penalizes misclassifications linearly, making it more robust.


3. The Role of Margins in SVM

SVMs aim to maximize the margin, defined as:

\[ M = \frac{1}{\|\beta\|} \]

where \(\beta\) is the vector normal to the hyperplane. A larger margin provides better generalization.

Margin-Based Decision Rule

For a hard-margin SVM (linearly separable data):

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

For a soft-margin SVM (allowing some misclassification):

\[ y_i (\beta^T x_i + \beta_0) \geq 1 - \xi_i, \quad \xi_i \geq 0 \]

where \(\xi_i\) are slack variables controlling misclassification.


4. Confidence in SVM

What is Confidence in SVM?

Confidence in SVMs refers to the distance of a point from the decision boundary (not the margin!). It is a measure of how confidently a point is classified.

  • Positive confidence: The point is correctly classified.
  • Negative confidence: The point is misclassified.

Mathematically, confidence is given by:

\[ \text{Confidence} = |f(x)| \]

where \(f(x) = \beta^T x + \beta_0\) is the decision function.


5. Implementing Hinge Loss in Python

Here’s how to implement hinge loss and SVM training using Scikit-Learn.

Hinge Loss Computation

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.metrics import hinge_loss

# Generate synthetic dataset
X, y = make_classification(n_samples=100, n_features=2, n_classes=2, n_redundant=0, random_state=42)
y = np.where(y == 0, -1, 1)  # Convert labels to {-1, 1}

# Train SVM
svm = SVC(kernel='linear', C=1.0)
svm.fit(X, y)

# Compute decision function
decision_values = svm.decision_function(X)

# Compute hinge loss
loss = hinge_loss(y, decision_values)
print(f'Hinge Loss: {loss:.4f}')

Plotting Decision Boundary with Margins

def plot_svm_decision_boundary(X, y, model):
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')

    ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
                         np.linspace(ylim[0], ylim[1], 50))

    Z = model.decision_function(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    ax.contour(xx, yy, Z, colors='k', levels=[-1, 0, 1], linestyles=['--', '-', '--'])

    plt.title("SVM Decision Boundary with Margins")
    plt.show()

plot_svm_decision_boundary(X, y, svm)

6. Key Takeaways

  • Hinge loss is used in SVM to penalize misclassified points while ignoring correctly classified points far from the decision boundary.
  • Margin maximization ensures better generalization.
  • Slack variables \(\xi_i\) allow some misclassification in soft-margin SVMs.
  • Confidence in SVM is not probability but the distance to the decision boundary.
  • Quadratic hinge loss smooths out the loss function, making optimization more stable.

7. Additional Insights from ESLII

  • Figure 12.4 in ESLII compares hinge loss, squared error loss, and logistic regression loss.
  • Table 12.1 in ESLII characterizes different loss functions, showing that hinge loss estimates the mode of class probabilities.
  • Regularization and kernel selection play a key role in optimizing SVM models.

Part 2 Continued: Margins and Loss in Support Vector Machines (SVMs)


1. Introduction to Margins and Loss in SVM

Support Vector Machines (SVMs) operate by maximizing the margin between different classes. However, real-world datasets are often not perfectly separable, which introduces classification errors. To handle this, we use loss functions, specifically the hinge loss, to penalize misclassified points and ensure robust model performance.


2. Hinge Loss in SVM

What is Hinge Loss?

Hinge loss is a function designed to penalize misclassified points while allowing correctly classified points far from the decision boundary to contribute no loss. It is mathematically defined as:

\[ L(y, f(x)) = \max(0, 1 - y f(x)) \]

where: - \(y \in \{-1,1\}\) is the true class label. - \(f(x)\) is the decision function (e.g., \(f(x) = \beta^T x + \beta_0\)).

Interpretation of Hinge Loss

  • If a point is correctly classified and far from the margin (i.e., \(y f(x) > 1\)), the loss is zero.
  • If a point is near the margin (i.e., \(0 < y f(x) \leq 1\)), it contributes to the loss.
  • If a point is misclassified (i.e., \(y f(x) < 0\)), the loss increases linearly.

Quadratic Hinge Loss

An alternative quadratic version of hinge loss is:

\[ L(y, f(x)) = ( \max(0, 1 - y f(x)) )^2 \]

This smooths out the loss function, making it more stable during optimization.

Comparison with Other Loss Functions

Hinge loss differs from logistic regression’s log-likelihood loss and squared error loss: - Log-likelihood loss (used in logistic regression) penalizes misclassifications more smoothly. - Squared error loss assigns a quadratic penalty to errors, making it less robust to outliers. - Hinge loss is a compromise—it penalizes misclassifications linearly, making it more robust.


3. The Role of Margins in SVM

SVMs aim to maximize the margin, defined as:

\[ M = \frac{1}{\|\beta\|} \]

where \(\beta\) is the vector normal to the hyperplane. A larger margin provides better generalization.

Margin-Based Decision Rule

For a hard-margin SVM (linearly separable data):

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

For a soft-margin SVM (allowing some misclassification):

\[ y_i (\beta^T x_i + \beta_0) \geq 1 - \xi_i, \quad \xi_i \geq 0 \]

where \(\xi_i\) are slack variables controlling misclassification.


4. Confidence in SVM

What is Confidence in SVM?

Confidence in SVMs refers to the distance of a point from the decision boundary (not the margin!). It is a measure of how confidently a point is classified.

  • Positive confidence: The point is correctly classified.
  • Negative confidence: The point is misclassified.

Mathematically, confidence is given by:

\[ \text{Confidence} = |f(x)| \]

where \(f(x) = \beta^T x + \beta_0\) is the decision function.


5. Implementing Hinge Loss in Python

Here’s how to implement hinge loss and SVM training using Scikit-Learn.

Hinge Loss Computation

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.metrics import hinge_loss

# Generate synthetic dataset
X, y = make_classification(n_samples=100, n_features=2, n_classes=2, n_redundant=0, random_state=42)
y = np.where(y == 0, -1, 1)  # Convert labels to {-1, 1}

# Train SVM
svm = SVC(kernel='linear', C=1.0)
svm.fit(X, y)

# Compute decision function
decision_values = svm.decision_function(X)

# Compute hinge loss
loss = hinge_loss(y, decision_values)
print(f'Hinge Loss: {loss:.4f}')

Plotting Decision Boundary with Margins

def plot_svm_decision_boundary(X, y, model):
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')

    ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
                         np.linspace(ylim[0], ylim[1], 50))

    Z = model.decision_function(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    ax.contour(xx, yy, Z, colors='k', levels=[-1, 0, 1], linestyles=['--', '-', '--'])

    plt.title("SVM Decision Boundary with Margins")
    plt.show()

plot_svm_decision_boundary(X, y, svm)

6. Key Takeaways

  • Hinge loss is used in SVM to penalize misclassified points while ignoring correctly classified points far from the decision boundary.
  • Margin maximization ensures better generalization.
  • Slack variables \(\xi_i\) allow some misclassification in soft-margin SVMs.
  • Confidence in SVM is not probability but the distance to the decision boundary.
  • Quadratic hinge loss smooths out the loss function, making optimization more stable.

7. Additional Insights from ESLII

  • Figure 12.4 in ESLII compares hinge loss, squared error loss, and logistic regression loss.
  • Table 12.1 in ESLII characterizes different loss functions, showing that hinge loss estimates the mode of class probabilities.
  • Regularization and kernel selection play a key role in optimizing SVM models.


Part 3 - The Kernel Trick


Concept Overview

Support Vector Machines (SVMs) are supervised learning models used for classification and regression analysis. The goal of an SVM is to find the optimal hyperplane that separates different classes in a dataset with the maximum margin.

Key Concepts: - Hyperplane: A decision boundary that separates classes. - Margin: The distance between the hyperplane and the nearest data points from either class (support vectors). - Support Vectors: The data points that are closest to the hyperplane and influence its position.

Mathematical Formulation

For a binary classification problem with input features \(x_i\) and labels \(y_i \in \{-1,1\}\), the decision boundary is defined by:

\[ f(x) = \beta^T x + \beta_0 \]

The goal is to maximize the margin \(M\) while ensuring correct classification:

\[ y_i (\beta^T x_i + \beta_0) \geq M \]

For a perfectly separable dataset, the optimal separating hyperplane satisfies:

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

We minimize \(\|\beta\|\) to maximize \(M = \frac{1}{\|\beta\|}\), leading to the optimization problem:

\[ \min_{\beta, \beta_0} \frac{1}{2} \|\beta\|^2 \]

subject to:

\[ y_i (\beta^T x_i + \beta_0) \geq 1 \]

This is a quadratic optimization problem solved using Lagrange multipliers.


Part 2: Margins and Loss Functions in SVMs

SVMs use Hinge Loss to handle misclassified points.

Hinge Loss Function

\[ L(y, f(x)) = \max(0, 1 - y f(x)) \]

  • If the point is correctly classified and far from the hyperplane, \(L(y, f(x)) = 0\).
  • If the point is misclassified or within the margin, the loss increases linearly.

Mathematical Representation

\[ \min_{\beta, \beta_0} \frac{1}{2} \|\beta\|^2 + C \sum_{i=1}^{N} \xi_i \]

where \(\xi_i\) represents slack variables allowing some misclassifications.

Types of Hinge Loss: - Linear Hinge Loss: Penalizes misclassified points linearly. - Quadratic Hinge Loss: Penalizes with a squared function for better robustness.

Python Code for Hinge Loss

import numpy as np
import matplotlib.pyplot as plt

# Hinge Loss Function
def hinge_loss(y, f_x):
    return np.maximum(0, 1 - y * f_x)

x = np.linspace(-2, 2, 100)
y = np.ones_like(x)  # Assume all points belong to class 1
loss = hinge_loss(y, x)

plt.plot(x, loss, label="Hinge Loss")
plt.xlabel("Distance from Margin")
plt.ylabel("Loss")
plt.legend()
plt.title("Hinge Loss Function")
plt.show()

Part 3: The Kernel Trick Continued

SVMs perform nonlinear classification using the kernel trick, which implicitly maps data to a higher-dimensional space where it is linearly separable.

Common Kernels

Kernel Type Mathematical Form
Linear \(x \cdot y\)
Polynomial \((x \cdot y + r)^d\)
Radial Basis Function (RBF) \(\exp(-\gamma \| x - y \|^2)\)
Sigmoid \(\tanh(r x \cdot y + \eta)\)

Mathematical Representation
Instead of computing a transformation \(\phi(x)\) explicitly, we compute the kernel function directly:

\[ K(x, y) = \phi(x) \cdot \phi(y) \]

Python Code for Kernel SVM

from sklearn.svm import SVC
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt
import numpy as np

# Generate nonlinear data
X, y = make_moons(n_samples=100, noise=0.1, random_state=42)
y = np.where(y == 0, -1, 1)  # Convert to {-1,1} labels

# Train SVM with RBF Kernel
svm_model = SVC(kernel='rbf', C=1.0, gamma=0.5)
svm_model.fit(X, y)

# Plot Decision Boundary
xx, yy = np.meshgrid(np.linspace(-2, 2, 100), np.linspace(-1.5, 1.5, 100))
Z = svm_model.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, levels=[-1, 0, 1], alpha=0.3, colors=['blue', 'black', 'red'])
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Paired)
plt.title("SVM with RBF Kernel")
plt.show()

Why the Kernel Trick Works

  • Avoids explicitly mapping data to higher dimensions, which would be computationally expensive.
  • Computes dot products in transformed space efficiently using a kernel function.

The XOR Problem

The XOR problem is a classic example where linear classifiers fail. The kernel trick allows SVMs to separate such datasets.

  • 2D space: XOR cannot be separated by a single line.
  • 3D space: Using \(z = x^2 + y^2\), the data becomes linearly separable.

Key Takeaways

  1. SVMs optimize for the maximum margin between classes.
  2. Hinge loss ensures a robust classification boundary.
  3. The kernel trick allows nonlinear classification by mapping data to higher dimensions.
  4. Different kernels can be used based on dataset characteristics.

Further Reading

  • Elements of Statistical Learning by Hastie, Tibshirani, and Friedman.
  • The Statistical Sleuth for advanced mathematical details.

Study Guide: The Kernel Trick

Introduction

The kernel trick is a fundamental concept in machine learning, particularly in support vector machines (SVMs) and other kernelized learning algorithms. It allows nonlinear problems to be transformed into higher-dimensional space where a linear solution can be applied without explicitly computing the transformation.

This guide will cover: - The XOR problem and why linear classifiers fail - The concept of mapping into higher dimensions - Mathematical formulations of kernel functions - Practical coding implementation with Python (using scikit-learn)


1. The XOR Problem

The XOR (Exclusive OR) problem is a classic example where a simple linear classifier fails. Given two input features \(x_1\) and \(x_2\), the XOR function outputs: - 1 if only one of \(x_1\) or \(x_2\) is 1 - 0 otherwise

In a two-dimensional space, there is no single straight line that can separate the classes.

Visualizing the XOR Problem

Mathematically, XOR follows:

\[ \text{XOR}(x_1, x_2) = x_1 \oplus x_2 \]

The decision boundary is nonlinear, making it impossible to classify using traditional linear SVMs.


2. Mapping to a Higher Dimension

To address this, we introduce a transformation function \(\phi(x)\) that maps the data to a higher-dimensional space:

\[ z = x_1^2 + x_2^2 \]

This transformation allows a linear classifier to work effectively in the new space.

Mathematical Representation

Instead of explicitly transforming data, the kernel trick enables us to compute inner products in the higher-dimensional space directly.

For a transformation function \(\phi(x)\), the kernel function is:

\[ K(x, y) = \langle \phi(x), \phi(y) \rangle \]

Common kernels: 1. Linear Kernel: \(K(x, y) = x^T y\) 2. Polynomial Kernel: \(K(x, y) = (x^T y + c)^d\) 3. Radial Basis Function (RBF) Kernel: \(K(x, y) = \exp(-\gamma \|x - y\|^2)\) 4. Sigmoid Kernel: \(K(x, y) = \tanh(\alpha x^T y + c)\)


3. The Kernel Trick

The kernel trick allows us to compute the inner product in the transformed space without explicitly performing the transformation. Instead of computing \(\phi(x)\), we directly use:

\[ K(x, y) = \phi(x) \cdot \phi(y) \]

This reduces computational cost and avoids explicitly computing high-dimensional features.


4. Coding Implementation in Python

Let’s see how to implement SVM with different kernels using scikit-learn:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles

# Generate a nonlinear dataset
X, y = make_circles(n_samples=300, noise=0.05, factor=0.5)

# Train an SVM with different kernels
kernels = ['linear', 'poly', 'rbf', 'sigmoid']

plt.figure(figsize=(12, 8))
for i, kernel in enumerate(kernels, 1):
    model = SVC(kernel=kernel, gamma=1)
    model.fit(X, y)
    
    # Plot decision boundary
    plt.subplot(2, 2, i)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
    
    # Create a mesh grid
    x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
    y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100), np.linspace(y_min, y_max, 100))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    # Plot contour
    plt.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
    plt.title(f'SVM with {kernel} kernel')

plt.tight_layout()
plt.show()

Explanation:

  • Dataset: We use make_circles() to create a nonlinear dataset.
  • SVM Kernels: We test four different kernels (linear, poly, rbf, sigmoid).
  • Visualization: We plot the dataset and decision boundary.

Results: - The linear kernel fails. - The polynomial kernel performs better. - The RBF kernel performs best for nonlinear data. - The sigmoid kernel behaves like a neural network.


5. Summary & Key Takeaways

  • The XOR problem demonstrates why linear classifiers fail on nonlinear data.
  • Higher-dimensional mapping makes it possible to separate data using linear classifiers.
  • The kernel trick allows SVMs to work efficiently by computing dot products in high-dimensional space without explicitly transforming data.
  • Radial Basis Function (RBF) and Polynomial Kernels are commonly used to handle nonlinearity.

Further Reading & References

  • [Elements of Statistical Learning, Section 12.3: Support Vector Machines and Kernels]
  • [Radial Basis Function Networks & Kernel Methods]

Here’s an in-depth study guide on Support Vector Machines (SVMs) expanding upon Part 4 of the lecture series, incorporating mathematical concepts, coding examples, and theoretical explanations.


Study Guide: Support Vector Machines (SVMs)

1. Introduction to Support Vector Machines

Support Vector Machines (SVMs) are supervised learning models used for classification and regression tasks. The key idea behind SVM is to find an optimal hyperplane that maximizes the margin between classes while minimizing classification errors.

SVM can be divided into: - Hard Margin SVM (for perfectly separable data) - Soft Margin SVM (for overlapping classes, using slack variables) - Kernelized SVM (for nonlinear classification)


2. The Optimization Problem

SVM aims to find a hyperplane that best separates two classes. Given a dataset with feature vectors \(x_i\) and labels \(y_i \in \{-1, 1\}\), the decision boundary is defined by:

\[ f(x) = w^T x + b \]

where \(w\) is the weight vector and \(b\) is the bias.

Hard Margin SVM (Linearly Separable Case)

For perfectly separable classes, we find the hyperplane that maximizes the margin:

\[ \min_{w, b} \frac{1}{2} ||w||^2 \]

subject to:

\[ y_i (w^T x_i + b) \geq 1, \quad \forall i \]

where \(||w||^{-1}\) represents the margin width.

Soft Margin SVM (Non-Separable Case)

For cases where the data is not perfectly separable, we introduce slack variables \(\xi_i\) to allow misclassifications:

\[ \min_{w, b, \xi} \frac{1}{2} ||w||^2 + C \sum_{i=1}^{N} \xi_i \]

subject to:

\[ y_i (w^T x_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0, \quad \forall i \]

where \(C\) controls the trade-off between maximizing the margin and minimizing classification error.


3. The Kernel Trick

SVMs can be extended to handle nonlinear classification problems using the Kernel Trick, which maps data into a higher-dimensional space where a linear boundary can be applied.

The kernel function \(K(x_i, x_j)\) computes the dot product in this high-dimensional space:

\[ K(x_i, x_j) = \phi(x_i) \cdot \phi(x_j) \]

Common kernel functions: 1. Linear Kernel: \(K(x, y) = x \cdot y\) 2. Polynomial Kernel: \(K(x, y) = (x \cdot y + c)^d\) 3. Radial Basis Function (RBF) Kernel: \(K(x, y) = e^{-\gamma ||x - y||^2}\) 4. Sigmoid Kernel: \(K(x, y) = \tanh( \alpha x \cdot y + c)\)

These kernels allow SVM to classify nonlinear data without explicitly transforming features into higher dimensions.


4. Mathematical Representation of the Kernelized SVM

Using the Lagrange dual formulation, the SVM optimization problem can be rewritten in terms of Lagrange multipliers \(\alpha_i\):

\[ \max_{\alpha} \sum_{i=1}^{N} \alpha_i - \frac{1}{2} \sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_i \alpha_j y_i y_j K(x_i, x_j) \]

subject to:

\[ 0 \leq \alpha_i \leq C, \quad \sum_{i=1}^{N} \alpha_i y_i = 0 \]

where only support vectors contribute to the decision function.

The final decision function is:

\[ f(x) = \sum_{i=1}^{N} \alpha_i y_i K(x_i, x) + b \]


5. Support Vector Regression (SVR)

SVMs can also be applied to regression problems by introducing epsilon-insensitive loss:

\[ \min_{w, b, \xi, \xi^*} \frac{1}{2} ||w||^2 + C \sum_{i=1}^{N} (\xi_i + \xi_i^*) \]

subject to:

\[ y_i - (w^T x_i + b) \leq \epsilon + \xi_i \]

\[ (w^T x_i + b) - y_i \leq \epsilon + \xi_i^* \]

where \(\epsilon\) defines a margin within which no penalty is given for errors.


6. Python Code Examples

Linear SVM Classification

from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Generate dataset
X, y = make_classification(n_samples=100, n_features=2, n_classes=2, random_state=42)

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

# Train SVM classifier
svm = SVC(kernel='linear', C=1.0)
svm.fit(X_train, y_train)

# Predict and evaluate
y_pred = svm.predict(X_test)
print("Accuracy:", accuracy_score(y_test, y_pred))

Kernelized SVM (RBF Kernel)

svm_rbf = SVC(kernel='rbf', C=1.0, gamma=0.5)
svm_rbf.fit(X_train, y_train)

y_pred_rbf = svm_rbf.predict(X_test)
print("RBF Kernel Accuracy:", accuracy_score(y_test, y_pred_rbf))

Support Vector Regression (SVR)

from sklearn.svm import SVR
import numpy as np

# Generate data
X = np.linspace(-3, 3, 100).reshape(-1, 1)
y = np.sinc(X).ravel()

# Train SVR model
svr = SVR(kernel='rbf', C=1.0, epsilon=0.1)
svr.fit(X, y)

# Predict
y_pred = svr.predict(X)

7. Applications of SVM

  • Image Classification: SVM is widely used in handwritten digit recognition (e.g., MNIST dataset).
  • Bioinformatics: SVMs are used for protein structure prediction and gene classification.
  • Text Classification: Spam detection and sentiment analysis.
  • Anomaly Detection: Fraud detection and outlier detection in cybersecurity.

8. Summary

  • SVMs find the optimal hyperplane that maximizes the margin.
  • Soft Margin SVM allows misclassification to handle overlapping classes.
  • Kernel trick allows nonlinear classification without explicit feature transformation.
  • Support Vector Regression (SVR) extends SVMs to continuous data.
  • SVMs perform well in high-dimensional spaces but can be slow for large datasets.

Study Guide: Support Vector Machines (SVMs) and the Kernel Trick


1. Introduction to the Kernel Trick

What is the Kernel Trick?

  • The kernel trick is a technique used in Support Vector Machines (SVMs) to handle non-linearly separable data.
  • Instead of explicitly mapping data to a higher-dimensional space, the kernel trick computes the dot product in that space directly, avoiding the computational burden of transformation.

Why Use the Kernel Trick?

  • Many datasets, like the XOR problem, are not linearly separable in their original feature space.
  • By using the kernel trick, we can map the data into a higher-dimensional space where it becomes linearly separable.
  • This transformation is done implicitly, meaning we never have to compute the new coordinates explicitly.

Types of Kernels

Common kernels used in SVMs: | Kernel | Mathematical Form | |——–|——————| | Linear | \(K(x, y) = x \cdot y\) | | Polynomial | \(K(x, y) = (r \cdot x \cdot y + c)^d\) | | Radial Basis Function (RBF) | \(K(x, y) = \exp(-\gamma ||x - y||^2)\) | | Sigmoid | \(K(x, y) = \tanh(r \cdot x \cdot y + c)\) |

Mathematical Explanation

  • Suppose we map data from 2D space to a 3D space using: \[ z = x^2 + y^2 \]
  • Instead of computing all new feature values, the kernel trick allows us to use a function \(K(x, y)\) to compute dot products in this space directly.

2. The XOR Problem and Projection into Higher Dimensions

Understanding the XOR Problem

  • The XOR problem cannot be solved using a simple linear decision boundary.
  • A hyperplane cannot separate the points as they are distributed in an interleaved manner.
  • The kernel trick helps by projecting the data into a higher-dimensional space where separation becomes possible.

Visualization

  • Data in original space: The XOR problem appears inseparable.
  • Data in transformed space: A new feature (e.g., \(z = x^2 + y^2\)) allows a linear separator to be used.

Python Example: Transforming XOR Data

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC

# Generate XOR dataset
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([0, 1, 1, 0])  # XOR labels

# Fit an SVM with RBF Kernel
svm_model = SVC(kernel='rbf', C=1, gamma='auto')
svm_model.fit(X, y)

# Plot decision boundary
xx, yy = np.meshgrid(np.linspace(-1, 2, 100), np.linspace(-1, 2, 100))
Z = svm_model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, alpha=0.3)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k')
plt.title("SVM Decision Boundary using RBF Kernel for XOR Problem")
plt.show()

3. Support Vector Machines (SVM)

What is an SVM?

  • SVM is a supervised learning algorithm used for classification and regression.
  • It finds the best hyperplane that maximizes the margin between two classes.

Key Concepts

  • Support Vectors: Data points that are closest to the decision boundary.
  • Margin: The distance between the decision boundary and the closest support vectors.
  • Hyperplane: A decision boundary in an N-dimensional space.

Mathematical Formulation

SVM solves the following optimization problem: \[ \min_{\mathbf{w}, b} \frac{1}{2} ||\mathbf{w}||^2 \] subject to: \[ y_i (\mathbf{w} \cdot x_i + b) \geq 1, \quad \forall i \]

Regularization Parameter (C)

  • Controls the trade-off between maximizing the margin and minimizing classification error.
  • A high C leads to less regularization (smaller margin, better fit to training data).
  • A low C leads to more regularization (larger margin, potentially better generalization).

Python Example: Linear SVM

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# Load dataset
iris = datasets.load_iris()
X = iris.data[:, :2]  # Using only two features
y = iris.target

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

# Train SVM with Linear Kernel
svm = SVC(kernel='linear', C=1)
svm.fit(X_train, y_train)

# Predictions
y_pred = svm.predict(X_test)

# Accuracy
print("Accuracy:", accuracy_score(y_test, y_pred))

4. Tuning SVM with Cross-Validation

Why Use Cross-Validation?

  • Helps determine the optimal hyperparameters (C, kernel type, gamma, etc.).
  • Prevents overfitting by evaluating model performance on unseen data.

Cross-Validation in SVM

from sklearn.model_selection import cross_val_score

# Perform cross-validation
scores = cross_val_score(svm, X, y, cv=5, scoring='accuracy')

print("Cross-Validation Accuracy Scores:", scores)
print("Mean Accuracy:", scores.mean())

5. Comparing Different Kernels in SVM

Effect of Different Kernels

  • Linear Kernel: Best for linearly separable data.
  • Polynomial Kernel: Captures complex decision boundaries.
  • RBF Kernel: Handles non-linearity efficiently.
  • Sigmoid Kernel: Similar to neural networks.

Python Example: Comparing Kernels

kernels = ['linear', 'poly', 'rbf', 'sigmoid']

for kernel in kernels:
    svm = SVC(kernel=kernel, C=1, gamma='scale')
    scores = cross_val_score(svm, X, y, cv=5, scoring='accuracy')
    print(f"Kernel: {kernel}, Mean Accuracy: {scores.mean():.4f}")

6. Summary

Key Takeaways

  • The Kernel Trick allows SVMs to classify nonlinear data without explicitly mapping it into a higher-dimensional space.
  • Support Vector Machines find the best hyperplane by maximizing the margin between classes.
  • Regularization Parameter (C) controls the trade-off between margin size and classification accuracy.
  • Cross-Validation is crucial for tuning hyperparameters and ensuring good generalization.

Study Questions

  1. What is the kernel trick, and why is it useful?
  2. How does SVM determine the optimal decision boundary?
  3. What is the effect of increasing or decreasing the regularization parameter \(C\)?
  4. How do different kernels affect the performance of SVM?
  5. What are the advantages and disadvantages of using an RBF kernel over a linear kernel?

Further Reading


Key Takeaways on Support Vector Machines (SVM) Scaling and Limitations:

  1. SVMs Scale Poorly to Large Datasets
    • SVMs suffer from O(n²) complexity, which means they require quadratic memory and computational time as the dataset grows.
    • The limitation arises from the need to store and compute dot products for all data points in memory.
    • Generally, SVMs become impractical beyond 50,000 samples, especially if the dataset is dense.
  2. Memory Constraints
    • A 16 GB memory system can handle approximately 128,000 samples under ideal conditions.
    • Since the kernel matrix is symmetric, practical limits place SVM usability at around 50,000 samples before performance degradation becomes severe.
  3. Why Other Algorithms Scale Better
    • Regression requires only slope calculations (O(n) or less).
    • Decision Trees need only the data to build the tree, significantly reducing overhead.
    • Gaussian models require only data distributions, making them more efficient for large datasets.
    • Clustering suffers from similar n² distance computations as SVM, but adaptations exist.
  4. Overfitting in High Dimensions
    • SVMs work well in high-dimensional spaces, but this increases the risk of overfitting.
    • Regularization (C parameter) helps control model complexity, but improper tuning can lead to overfitting or underfitting.
  5. Regression and Probability Estimation in SVMs Are Limited
    • SVMs naturally support classification, not regression.
    • While adaptations for regression (SVR) exist, better alternatives such as linear regression, decision trees, or neural networks provide superior performance.
    • SVMs do not inherently provide probabilities; instead, they output confidence scores based on distance from the decision boundary.
  6. Kernel Trick and Nonlinearity
    • The kernel trick allows SVMs to learn non-linear decision boundaries.
    • Common kernels: Linear, Polynomial, Radial Basis Function (RBF), Sigmoid.
    • While kernelized SVMs are powerful, they require additional computation, further affecting scalability.

Conclusion:

  • SVMs are powerful for small to medium datasets (<50,000 samples) and high-dimensional classification problems.
  • They are not ideal for big data due to memory and computational constraints.
  • Other algorithms like decision trees, regression, and neural networks scale better for large datasets.
  • Regularization is crucial to prevent overfitting in high-dimensional applications.

Here is a detailed Study Guide on Scaling of Support Vector Machines (SVMs) based on the transcript, images, and references from the textbooks.


Study Guide: Scaling of Support Vector Machines (SVMs)

1. Introduction to SVM Scaling

Support Vector Machines (SVMs) are powerful supervised learning models used for classification and regression tasks. However, they do not scale well for large datasets due to their computational complexity. The main challenge arises from the need to store and compute a large matrix of dot products for every pair of data points.

Key Concept: Order of \(O(n^2)\) Scaling

  • SVMs have quadratic complexity, meaning they require \(O(n^2)\) memory and \(O(n^3)\) computation to find the optimal separating hyperplane.
  • This makes them infeasible for datasets exceeding 50,000 rows in practice.

2. Why SVMs Do Not Scale to Big Data

The Problem: Storing the Dot Product Matrix

  • The kernel trick in SVMs relies on dot products between all data points.
  • If a dataset has n samples, the model must store an \(n \times n\) Gram matrix.
  • The memory and computational requirements grow quadratically.

Example Calculation

  • Given 16 GB of RAM, the maximum number of storable samples is approximately 128,000.
  • However, since the matrix is symmetric, we only need to store \(n(n-1)/2\) values, reducing this to 64,000 samples.
  • Despite this reduction, SVMs become impractically slow beyond 50,000 samples.

3. Why Other Algorithms Scale Better

Comparison with Other Machine Learning Models

Algorithm Complexity Memory Usage Why It Scales Better?
SVM \(O(n^2)\) \(O(n^2)\) Must store all dot products
Linear Regression \(O(n)\) \(O(n)\) Only stores slopes
Decision Trees \(O(n \log n)\) \(O(n)\) Stores only tree structure
Gaussian Methods \(O(n)\) \(O(n)\) Only stores distribution parameters
K-Means Clustering \(O(nk)\) \(O(n)\) Computes distances efficiently
Deep Learning (Neural Networks) \(O(n)\) \(O(n)\) Stochastic Gradient Descent (SGD) allows batch processing
  • Trees require only the structure of the tree, which scales logarithmically.
  • Regression only needs coefficients rather than storing all pairwise relationships.
  • Neural networks can batch process and update weights efficiently without needing all pairwise interactions.

4. Addressing SVM Scaling Issues

Strategies to Improve SVM Performance on Large Datasets

  1. Using Approximate Methods
    • Linear SVM (by setting the kernel to 'linear') avoids storing the full matrix.
    • Stochastic Gradient Descent (SGD-SVM) updates model parameters in smaller steps, reducing computation.
  2. Feature Selection & Dimensionality Reduction
    • Principal Component Analysis (PCA) reduces the number of features before training an SVM.
    • Feature hashing converts high-dimensional data into lower-dimensional space.
  3. Using Subset Sampling
    • Train SVM on a representative subset rather than the full dataset.
    • Combine results using ensemble methods.
  4. Kernel Approximations
    • Nyström Method: Approximates the kernel matrix to reduce computational cost.
    • Random Fourier Features: Approximates kernels with a lower-dimensional projection.

5. SVMs and Regularization

  • SVMs naturally overfit in high-dimensional spaces.
  • Regularization parameter (C) controls the trade-off between margin width and misclassification rate.
    • Large C → More complex model, smaller margin (high variance).
    • Small C → Simpler model, larger margin (high bias).
  • Kernel-based SVMs require tuning hyperparameters (e.g., RBF kernel width \(\gamma\)) to avoid overfitting.

6. Mathematical Representation of Scaling in SVM

Support Vector Machine Objective Function

For a binary classification task, SVM minimizes: \[ \frac{1}{2} ||w||^2 + C \sum_{i=1}^{n} \max(0, 1 - y_i (w^T x_i + b)) \] where: - \(||w||^2\) ensures maximizing the margin. - \(C\) is the regularization parameter. - \(\max(0, 1 - y_i (w^T x_i + b))\) is the hinge loss function.

Scaling Bottleneck

  • Training involves solving a Quadratic Programming (QP) problem with constraints: \[ \alpha_i (y_i (w^T x_i + b) - 1) = 0 \] where \(\alpha_i\) are Lagrange multipliers.
  • The dual form requires computing: \[ K(x_i, x_j) = \phi(x_i) \cdot \phi(x_j) \] for all pairs, leading to \(O(n^2)\) memory consumption.

7. Python Code for SVM with Scaling Considerations

Basic SVM with Hyperparameter Tuning

from sklearn.svm import SVC
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.datasets import make_classification
import numpy as np

# Generate synthetic dataset
X, y = make_classification(n_samples=5000, n_features=20, random_state=42)

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

# Define SVM with RBF kernel and tune C
param_grid = {'C': [0.1, 1, 10], 'gamma': ['scale', 'auto']}
svm = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=5)
svm.fit(X_train, y_train)

# Evaluate performance
print("Best Parameters:", svm.best_params_)
print("Test Accuracy:", svm.score(X_test, y_test))

Using Linear SVM for Large Datasets

from sklearn.linear_model import SGDClassifier

# Using SGD to scale SVM
sgd_svm = SGDClassifier(loss="hinge", max_iter=1000, tol=1e-3)
sgd_svm.fit(X_train, y_train)

print("SGD SVM Test Accuracy:", sgd_svm.score(X_test, y_test))

Why Use SGD? - No need to store dot products → reduces \(O(n^2)\) memory usage. - Efficient for large datasets → works on mini-batches.


8. Summary

Aspect SVM (Kernel) SVM (Linear) SGD SVM
Complexity \(O(n^2)\) \(O(n)\) \(O(n)\)
Memory \(O(n^2)\) \(O(n)\) \(O(1)\)
Large Datasets? ❌ No ✅ Yes ✅ Yes
Best Use Case Small datasets (< 50K) Large datasets Massive datasets

Key Takeaways

  1. SVMs do not scale well due to quadratic complexity.
  2. SGD-based SVMs are an alternative for large datasets.
  3. Feature selection and kernel approximations help reduce memory constraints.
  4. Other algorithms (trees, regression, neural networks) scale much better.

AGAIN


Study Guide: Support Vector Machines (SVMs)

1. Introduction to Support Vector Machines (SVMs)

Support Vector Machines (SVMs) are supervised learning models used for classification and regression tasks. They work by identifying the optimal hyperplane that best separates classes in a given dataset.

Mathematical Representation:

Given a dataset (xi,yi)(x_i, y_i) where xix_i represents the feature vectors and yiy_i the class labels (±1\pm1), the decision boundary is defined as:

w⋅x+b=0 w \cdot x + b = 0

where ww is the weight vector and bb is the bias term.

The margin is given by:

2∥w∥ \frac{2}{\|w\|}

which should be maximized while ensuring correct classification.

Python Implementation:

from sklearn.svm import SVC from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split  X, y = make_classification(n_samples=100, n_features=2, n_classes=2, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  model = SVC(kernel='linear', C=1.0) model.fit(X_train, y_train) y_pred = model.predict(X_test) 

2. Margins and Hinge Loss

The hinge loss function is defined as:

L(y,f(x))=max⁡(0,1−yf(x)) L(y, f(x)) = \max(0, 1 - y f(x))

This loss penalizes misclassified points and points close to the margin, ensuring better generalization.

Comparison with Other Loss Functions:

  • Logistic Loss (Binomial Deviance): log⁡(1+e−yf(x))\log(1 + e^{-yf(x)})

  • Squared Error Loss: (y−f(x))2(y - f(x))^2

  • Huberized Square Hinge Loss: Hybrid between hinge loss and squared loss【104:0†ESLII_print12_toc.pdf】.

3. Kernel Trick

For non-linearly separable data, SVMs use the kernel trick to transform input space into a higher-dimensional feature space where a linear decision boundary can be applied.

Common Kernels:

  • Linear Kernel: K(x,x′)=x⋅x′K(x, x’) = x \cdot x’

  • Polynomial Kernel: K(x,x′)=(x⋅x′+c)dK(x, x’) = (x \cdot x’ + c)^d

  • Radial Basis Function (RBF) Kernel: K(x,x′)=exp⁡(−γ∥x−x′∥2)K(x, x’) = \exp(-\gamma \|x - x’\|^2)

  • Sigmoid Kernel: K(x,x′)=tanh⁡(αx⋅x′+c)K(x, x’) = \tanh(\alpha x \cdot x’ + c)

# Example of using an RBF kernel model = SVC(kernel='rbf', gamma=0.1, C=1.0) model.fit(X_train, y_train) 

4. Scaling and Performance

SVMs have an O(n^2) complexity, making them inefficient for large datasets. Performance optimization techniques include:

  • Using LinearSVC (which implements SVM as an optimization problem)

  • Reducing features via PCA

  • Using approximate methods such as SGDClassifier

from sklearn.svm import LinearSVC model = LinearSVC() model.fit(X_train, y_train) 

5. Comparison with Naive Bayes

While SVMs optimize a margin, Naive Bayes estimates class probabilities.

  • SVMs perform better in high-dimensional spaces.

  • Naive Bayes is computationally faster and works well when feature independence assumptions hold.

Key Takeaways:

  1. SVMs find the optimal hyperplane by maximizing the margin.

  2. The kernel trick allows SVMs to handle non-linearly separable data.

  3. Hinge loss is used for classification to penalize misclassified points.

  4. Scaling issues limit SVMs on large datasets due to quadratic complexity.

  5. SVMs outperform Naive Bayes in high-dimensional spaces, but are computationally expensive.

Discussion Questions:

  1. How does the choice of the kernel affect the performance of an SVM model?

  2. In what scenarios is SVM preferable over logistic regression?

  3. How can we handle imbalanced data while using SVMs?

  4. Why does increasing the regularization parameter C reduce the margin?

  5. What strategies can improve SVM performance on large datasets?

Study Guide: Support Vector Machines (SVMs)

1. Introduction to Support Vector Machines (SVMs)

Support Vector Machines (SVMs) are supervised learning models used for classification and regression tasks. They work by identifying the optimal hyperplane that best separates classes in a given dataset.

Mathematical Representation:

Given a dataset \((x_i, y_i)\) where \(x_i\) represents the feature vectors and \(y_i\) the class labels (\(\pm1\)), the decision boundary is defined as: \[ w \cdot x + b = 0 \] where \(w\) is the weight vector and \(b\) is the bias term.

The margin is given by: \[ \frac{2}{\|w\|} \] which should be maximized while ensuring correct classification.

Python Implementation:

from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(n_samples=100, n_features=2, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = SVC(kernel='linear', C=1.0)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

2. Margins and Hinge Loss

The hinge loss function is defined as: \[ L(y, f(x)) = \max(0, 1 - y f(x)) \] This loss penalizes misclassified points and points close to the margin, ensuring better generalization.

Comparison with Other Loss Functions:

  • Logistic Loss (Binomial Deviance): \(\log(1 + e^{-yf(x)})\)
  • Squared Error Loss: \((y - f(x))^2\)
  • Huberized Square Hinge Loss: Hybrid between hinge loss and squared loss【104:0†ESLII_print12_toc.pdf】.

3. Kernel Trick

For non-linearly separable data, SVMs use the kernel trick to transform input space into a higher-dimensional feature space where a linear decision boundary can be applied.

Common Kernels:

  • Linear Kernel: \(K(x, x') = x \cdot x'\)
  • Polynomial Kernel: \(K(x, x') = (x \cdot x' + c)^d\)
  • Radial Basis Function (RBF) Kernel: \(K(x, x') = \exp(-\gamma \|x - x'\|^2)\)
  • Sigmoid Kernel: \(K(x, x') = \tanh(\alpha x \cdot x' + c)\)
# Example of using an RBF kernel
model = SVC(kernel='rbf', gamma=0.1, C=1.0)
model.fit(X_train, y_train)

4. Scaling and Performance

SVMs have an O(n^2) complexity, making them inefficient for large datasets. Performance optimization techniques include: - Using LinearSVC (which implements SVM as an optimization problem) - Reducing features via PCA - Using approximate methods such as SGDClassifier

from sklearn.svm import LinearSVC
model = LinearSVC()
model.fit(X_train, y_train)

5. Comparison with Naive Bayes

While SVMs optimize a margin, Naive Bayes estimates class probabilities. - SVMs perform better in high-dimensional spaces. - Naive Bayes is computationally faster and works well when feature independence assumptions hold.

Key Takeaways:

  1. SVMs find the optimal hyperplane by maximizing the margin.
  2. The kernel trick allows SVMs to handle non-linearly separable data.
  3. Hinge loss is used for classification to penalize misclassified points.
  4. Scaling issues limit SVMs on large datasets due to quadratic complexity.
  5. SVMs outperform Naive Bayes in high-dimensional spaces, but are computationally expensive.

Discussion Questions:

  1. How does the choice of the kernel affect the performance of an SVM model?
  2. In what scenarios is SVM preferable over logistic regression?
  3. How can we handle imbalanced data while using SVMs?
  4. Why does increasing the regularization parameter C reduce the margin?
  5. What strategies can improve SVM performance on large datasets?
LS0tDQp0aXRsZTogIjczMzMgTU9kdWxlIDkgVmVjdG9yIE1hY2hpbmVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQplZGl0b3Jfb3B0aW9uczogDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiA3Mg0KLS0tDQoNCiMjICoqU3R1ZHkgR3VpZGU6IFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSDigJMgTW9kdWxlIDkqKg0KDQojIFBhcnQgMQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqMS4gSW50cm9kdWN0aW9uIHRvIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzKioNCg0KU1ZNIGlzIGEgcG93ZXJmdWwgc3VwZXJ2aXNlZCBsZWFybmluZyBhbGdvcml0aG0gcHJpbWFyaWx5IHVzZWQgZm9yDQpjbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIG1haW4gZ29hbCBvZiBTVk0gaXMgdG8gZmluZCBhbiBvcHRpbWFsDQpzZXBhcmF0aW5nICoqaHlwZXJwbGFuZSoqIHRoYXQgYmVzdCBkaXZpZGVzIGRpZmZlcmVudCBjbGFzc2VzIG9mIGRhdGENCnBvaW50cy4NCg0KIyMjIyAqKktleSBDb25jZXB0czoqKg0KDQotICAgKipMaW5lYXIgU2VwYXJhYmlsaXR5Kio6IFdoZW4gYSBkYXRhc2V0IGNhbiBiZSBwZXJmZWN0bHkgZGl2aWRlZA0KICAgIHVzaW5nIGEgc3RyYWlnaHQgbGluZSAoaW4gMkQpIG9yIGEgaHlwZXJwbGFuZSAoaW4gaGlnaGVyDQogICAgZGltZW5zaW9ucykuDQotICAgKipIeXBlcnBsYW5lKio6IEEgZGVjaXNpb24gYm91bmRhcnkgc2VwYXJhdGluZyBkaWZmZXJlbnQgY2xhc3Nlcy4NCi0gICAqKk1hcmdpbioqOiBUaGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgY2xvc2VzdCBkYXRhIHBvaW50cyAoc3VwcG9ydA0KICAgIHZlY3RvcnMpIGFuZCB0aGUgaHlwZXJwbGFuZS4NCi0gICAqKlN1cHBvcnQgVmVjdG9ycyoqOiBEYXRhIHBvaW50cyB0aGF0IGxpZSBjbG9zZXN0IHRvIHRoZSBoeXBlcnBsYW5lDQogICAgYW5kIGluZmx1ZW5jZSBpdHMgb3JpZW50YXRpb24uDQotICAgKipNYXhpbWFsIE1hcmdpbiBDbGFzc2lmaWVyKio6IFRoZSBoeXBlcnBsYW5lIHRoYXQgbWF4aW1pemVzIHRoZQ0KICAgIG1hcmdpbi4NCi0gICAqKlNvZnQgTWFyZ2luKio6IEludHJvZHVjZXMgc2xhY2sgdmFyaWFibGVzIHRvIGFsbG93DQogICAgbWlzY2xhc3NpZmljYXRpb24gaW4gY2FzZXMgd2hlcmUgcGVyZmVjdCBzZXBhcmF0aW9uIGlzIGltcG9zc2libGUuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKioyLiBWZWN0b3IgQWxnZWJyYSBhbmQgSHlwZXJwbGFuZXMqKg0KDQpCZWZvcmUgdW5kZXJzdGFuZGluZyBTVk0sIGl0IGlzIGVzc2VudGlhbCB0byB1bmRlcnN0YW5kICoqdmVjdG9yDQphbGdlYnJhKiogYW5kIHRoZSBlcXVhdGlvbiBvZiBhIGh5cGVycGxhbmUuDQoNCiMjIyMgKipIeXBlcnBsYW5lIEVxdWF0aW9uOioqDQoNCkEgaHlwZXJwbGFuZSBpbiAkbiQtZGltZW5zaW9uYWwgc3BhY2UgaXMgZGVmaW5lZCBhczogJCQNClxiZXRhXzAgKyBcYmV0YV5UIHggPSAwDQokJCBXaGVyZTogLSAkXGJldGEkIGlzIHRoZSBub3JtYWwgdmVjdG9yIChwZXJwZW5kaWN1bGFyIHRvIHRoZQ0KaHlwZXJwbGFuZSkuIC0gJHgkIHJlcHJlc2VudHMgdGhlIGZlYXR1cmUgdmVjdG9ycy4gLSAkXGJldGFfMCQgaXMgdGhlDQpiaWFzIHRlcm0uDQoNCklmIHdlIGhhdmUgdHdvIGNsYXNzZXMgbGFiZWxlZCAkeV9pIFxpbiBcey0xLCAxXH0kLCB0aGUgU1ZNIGRlY2lzaW9uDQpib3VuZGFyeSBmb2xsb3dzOiAkJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgTQ0KJCQgd2hlcmUgJE0kIGlzIHRoZSBtYXJnaW4uDQoNCiMjIyMgKipGaW5kaW5nIHRoZSBPcHRpbWFsIEh5cGVycGxhbmUqKg0KDQpUaGUgb2JqZWN0aXZlIGlzIHRvICoqbWF4aW1pemUgdGhlIG1hcmdpbioqLCB3aGljaCB0cmFuc2xhdGVzIHRvDQptaW5pbWl6aW5nICR8fFxiZXRhfHwkIHN1YmplY3QgdG8gdGhlIGNvbnN0cmFpbnQ6DQoNCiQkDQp5X2kgKFxiZXRhXlQgeF9pICsgXGJldGFfMCkgXGdlcSAxDQokJA0KDQpUaGlzIGlzIGEgKipjb252ZXggb3B0aW1pemF0aW9uKiogcHJvYmxlbSB0aGF0IGNhbiBiZSBzb2x2ZWQgdXNpbmcNCioqTGFncmFuZ2UgbXVsdGlwbGllcnMqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjMuIEhhbmRsaW5nIE5vbi1TZXBhcmFibGUgRGF0YSoqDQoNCkluIG1hbnkgcmVhbC13b3JsZCBjYXNlcywgZGF0YSBpcyBub3QgcGVyZmVjdGx5IHNlcGFyYWJsZS4gU1ZNDQppbnRyb2R1Y2VzICoqc2xhY2sgdmFyaWFibGVzKiogJFx4aV9pJCB0byBhbGxvdyBzb21lIG1pc2NsYXNzaWZpY2F0aW9uOg0KDQokJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMSAtIFx4aV9pDQokJA0KDQp3aGVyZSAkXHhpX2kgXGdlcSAwJCByZXByZXNlbnRzIHRoZSBhbW91bnQgYnkgd2hpY2ggZGF0YSBwb2ludHMgdmlvbGF0ZQ0KdGhlIG1hcmdpbiBjb25zdHJhaW50Lg0KDQpUaGUgbmV3IG9wdGltaXphdGlvbiBvYmplY3RpdmUgYmVjb21lczoNCg0KJCQNClxtaW5fe1xiZXRhLCBcYmV0YV8wLCBceGl9IFxmcmFjezF9ezJ9IHx8XGJldGF8fF4yICsgQyBcc3VtIFx4aV9pDQokJA0KDQp3aGVyZSAkQyQgaXMgYSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIgY29udHJvbGxpbmcgdGhlIHRyYWRlLW9mZg0KYmV0d2VlbiBtYXhpbWl6aW5nIG1hcmdpbiBhbmQgbWluaW1pemluZyBjbGFzc2lmaWNhdGlvbiBlcnJvcnMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKio0LiBLZXJuZWwgVHJpY2sgZm9yIE5vbi1MaW5lYXIgRGF0YSoqDQoNCklmIGEgZGF0YXNldCBpcyAqKm5vdCBsaW5lYXJseSBzZXBhcmFibGUqKiwgd2UgbWFwIGl0IHRvIGENCmhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZSB1c2luZyAqKmtlcm5lbHMqKjoNCg0KJCQNCksoeF9pLCB4X2opID0gXHBoaSh4X2kpXlQgXHBoaSh4X2opDQokJA0KDQpDb21tb24ga2VybmVsczogLSAqKkxpbmVhciBLZXJuZWwqKjogJEsoeF9pLCB4X2opID0geF9pXlQgeF9qJCAtDQoqKlBvbHlub21pYWwgS2VybmVsKio6ICRLKHhfaSwgeF9qKSA9ICh4X2leVCB4X2ogKyBjKV5kJCAtICoqUmFkaWFsDQpCYXNpcyBGdW5jdGlvbiAoUkJGKSBLZXJuZWwqKjoNCiRLKHhfaSwgeF9qKSA9IFxleHAoLVxnYW1tYSB8fHhfaSAtIHhfanx8XjIpJA0KDQpUaGUga2VybmVsIHRyaWNrIGVuYWJsZXMgU1ZNIHRvIG9wZXJhdGUgaW4gKipoaWdoZXItZGltZW5zaW9uYWwgc3BhY2VzKioNCndpdGhvdXQgZXhwbGljaXRseSBjb21wdXRpbmcgdHJhbnNmb3JtYXRpb25zLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqNS4gTWF0aGVtYXRpY2FsIEZvcm11bGF0aW9uIG9mIFNWTSoqDQoNCiMjIyMgKipIYXJkIE1hcmdpbiBTVk0gKExpbmVhcmx5IFNlcGFyYWJsZSkqKg0KDQokJA0KXG1pbl97XGJldGEsIFxiZXRhXzB9IFxmcmFjezF9ezJ9IHx8XGJldGF8fF4yDQokJCBzdWJqZWN0IHRvOiAkJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMQ0KJCQNCg0KIyMjIyAqKlNvZnQgTWFyZ2luIFNWTSAoQWxsb3dpbmcgTWlzY2xhc3NpZmljYXRpb24pKioNCg0KJCQNClxtaW5fe1xiZXRhLCBcYmV0YV8wLCBceGl9IFxmcmFjezF9ezJ9IHx8XGJldGF8fF4yICsgQyBcc3VtIFx4aV9pDQokJCBzdWJqZWN0IHRvOiAkJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMSAtIFx4aV9pLCBccXVhZCBceGlfaSBcZ2VxIDANCiQkDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKio2LiBJbXBsZW1lbnRpbmcgU1ZNIGluIFB5dGhvbioqDQoNCkhlcmXigJlzIGhvdyB5b3UgY2FuIGltcGxlbWVudCAqKlNWTSB1c2luZyBTY2lraXQtTGVhcm4qKjoNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQpmcm9tIHNrbGVhcm4uc3ZtIGltcG9ydCBTVkMNCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbWFrZV9jbGFzc2lmaWNhdGlvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KZnJvbSBza2xlYXJuLm1ldHJpY3MgaW1wb3J0IGFjY3VyYWN5X3Njb3JlDQoNCiMgR2VuZXJhdGUgc3ludGhldGljIGRhdGFzZXQNClgsIHkgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKG5fc2FtcGxlcz0xMDAsIG5fZmVhdHVyZXM9Miwgbl9jbGFzc2VzPTIsIG5fcmVkdW5kYW50PTAsIHJhbmRvbV9zdGF0ZT00MikNCnkgPSBucC53aGVyZSh5ID09IDAsIC0xLCAxKSAgIyBDb252ZXJ0IGxhYmVscyB0byB7LTEsIDF9DQoNCiMgU3BsaXQgaW50byB0cmFpbi10ZXN0IHNldHMNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4zLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgVHJhaW4gU1ZNIGNsYXNzaWZpZXINCnN2bSA9IFNWQyhrZXJuZWw9J2xpbmVhcicsIEM9MS4wKQ0Kc3ZtLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KDQojIFByZWRpY3Rpb25zDQp5X3ByZWQgPSBzdm0ucHJlZGljdChYX3Rlc3QpDQpwcmludChmJ0FjY3VyYWN5OiB7YWNjdXJhY3lfc2NvcmUoeV90ZXN0LCB5X3ByZWQpOi4yZn0nKQ0KDQojIFBsb3QgZGVjaXNpb24gYm91bmRhcnkNCmRlZiBwbG90X3N2bV9kZWNpc2lvbl9ib3VuZGFyeShYLCB5LCBtb2RlbCk6DQogICAgcGx0LnNjYXR0ZXIoWFs6LCAwXSwgWFs6LCAxXSwgYz15LCBjbWFwPSdjb29sd2FybScsIGVkZ2Vjb2xvcnM9J2snKQ0KICAgIA0KICAgIGF4ID0gcGx0LmdjYSgpDQogICAgeGxpbSA9IGF4LmdldF94bGltKCkNCiAgICB5bGltID0gYXguZ2V0X3lsaW0oKQ0KICAgIA0KICAgIHh4LCB5eSA9IG5wLm1lc2hncmlkKG5wLmxpbnNwYWNlKHhsaW1bMF0sIHhsaW1bMV0sIDUwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBucC5saW5zcGFjZSh5bGltWzBdLCB5bGltWzFdLCA1MCkpDQogICAgDQogICAgWiA9IG1vZGVsLmRlY2lzaW9uX2Z1bmN0aW9uKG5wLmNfW3h4LnJhdmVsKCksIHl5LnJhdmVsKCldKQ0KICAgIFogPSBaLnJlc2hhcGUoeHguc2hhcGUpDQogICAgDQogICAgYXguY29udG91cih4eCwgeXksIFosIGNvbG9ycz0naycsIGxldmVscz1bLTEsIDAsIDFdLCBsaW5lc3R5bGVzPVsnLS0nLCAnLScsICctLSddKQ0KICAgIA0KICAgIHBsdC50aXRsZSgiU1ZNIERlY2lzaW9uIEJvdW5kYXJ5IikNCiAgICBwbHQuc2hvdygpDQoNCnBsb3Rfc3ZtX2RlY2lzaW9uX2JvdW5kYXJ5KFhfdHJhaW4sIHlfdHJhaW4sIHN2bSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqNy4gS2V5IFRha2Vhd2F5cyoqDQoNCi0gICAqKlNWTSBmaW5kcyB0aGUgb3B0aW1hbCBoeXBlcnBsYW5lIHRoYXQgbWF4aW1pemVzIG1hcmdpbi4qKg0KLSAgICoqU2xhY2sgdmFyaWFibGVzKiogJFx4aSQgYWxsb3cgZm9yIHNvZnQtbWFyZ2luIGNsYXNzaWZpY2F0aW9uLg0KLSAgICoqS2VybmVscyBlbmFibGUgbm9uLWxpbmVhciBjbGFzc2lmaWNhdGlvbiBieSB0cmFuc2Zvcm1pbmcgZGF0YSBpbnRvDQogICAgaGlnaGVyIGRpbWVuc2lvbnMuKioNCi0gICAqKlNWTSBpcyBwb3dlcmZ1bCBmb3IgYm90aCBsaW5lYXIgYW5kIG5vbi1saW5lYXIgY2xhc3NpZmljYXRpb24NCiAgICBwcm9ibGVtcy4qKg0KLSAgICoqVGhlIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlcioqICRDJCBjb250cm9scyB0aGUgdHJhZGUtb2ZmIGJldHdlZW4NCiAgICBtYXJnaW4gbWF4aW1pemF0aW9uIGFuZCBtaXNjbGFzc2lmaWNhdGlvbiB0b2xlcmFuY2UuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKio4LiBQcmFjdGljYWwgQ29uc2lkZXJhdGlvbnMqKg0KDQotICAgKipDaG9vc2UgYW4gYXBwcm9wcmlhdGUga2VybmVsKiogYmFzZWQgb24gZGF0YXNldCBjb21wbGV4aXR5Lg0KLSAgICoqSHlwZXJwYXJhbWV0ZXIgdHVuaW5nIChDLCDOsyBmb3IgUkJGIGtlcm5lbCkgaXMgY3JpdGljYWwqKiBhbmQgY2FuDQogICAgYmUgb3B0aW1pemVkIHVzaW5nIGdyaWQgc2VhcmNoIGFuZCBjcm9zcy12YWxpZGF0aW9uLg0KLSAgICoqU1ZNcyBzY2FsZSBwb29ybHkgd2l0aCB2ZXJ5IGxhcmdlIGRhdGFzZXRzKiogKGR1ZSB0byBxdWFkcmF0aWMNCiAgICBjb21wbGV4aXR5KSwgc28gY29uc2lkZXIgYWx0ZXJuYXRpdmUgY2xhc3NpZmllcnMgbGlrZSAqKlJhbmRvbQ0KICAgIEZvcmVzdHMgb3IgRGVlcCBMZWFybmluZyoqIGZvciBiaWcgZGF0YS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCkhlcmUgaXMgYW4gZXhwYW5kZWQgKipTdHVkeSBHdWlkZSBvbiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykqKg0KaW5jb3Jwb3JhdGluZyBjb25jZXB0cyBmcm9tICoqVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nDQooRVNMSUkpKiogYW5kICoqVGhlIFN0YXRpc3RpY2FsIFNsZXV0aCoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKlN0dWR5IEd1aWRlOiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykqKg0KDQojIyMgKipNb2R1bGUgOTogU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMqKg0KDQojIyMgKioxLiBJbnRyb2R1Y3Rpb24gdG8gU1ZNcyoqDQoNClN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSBhcmUgc3VwZXJ2aXNlZCBsZWFybmluZyBhbGdvcml0aG1zIHVzZWQNCnByaW1hcmlseSBmb3IgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gdGFza3MuIFRoZSBrZXkgaWRlYSBpcyB0bw0KKipmaW5kIGFuIG9wdGltYWwgaHlwZXJwbGFuZSoqIHRoYXQgYmVzdCBzZXBhcmF0ZXMgZGlmZmVyZW50IGNsYXNzZXMuDQoNCiMjIyMgKipXaHkgVXNlIFNWTXM/KioNCg0KLSAgIEVmZmVjdGl2ZSBpbiAqKmhpZ2gtZGltZW5zaW9uYWwgc3BhY2VzKiouDQotICAgV29ya3Mgd2VsbCB3aXRoICoqc21hbGwgZGF0YXNldHMqKiBidXQgbWF5IHNjYWxlIHBvb3JseSBmb3IgdmVyeQ0KICAgIGxhcmdlIGRhdGFzZXRzLg0KLSAgIENhbiBtb2RlbCAqKm5vbi1saW5lYXIgcmVsYXRpb25zaGlwcyoqIHVzaW5nICoqa2VybmVsIHRyaWNrcyoqLg0KDQojIyMjICoqS2V5IFRlcm1zKioNCg0KLSAgICoqSHlwZXJwbGFuZToqKiBBIGRlY2lzaW9uIGJvdW5kYXJ5IHNlcGFyYXRpbmcgZGlmZmVyZW50IGNsYXNzZXMuDQotICAgKipNYXJnaW46KiogVGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIGNsb3Nlc3QgcG9pbnRzIChzdXBwb3J0DQogICAgdmVjdG9ycykgYW5kIHRoZSBoeXBlcnBsYW5lLg0KLSAgICoqU3VwcG9ydCBWZWN0b3JzOioqIERhdGEgcG9pbnRzIHRoYXQgZGVmaW5lIHRoZSBtYXJnaW4gYW5kDQogICAgaW5mbHVlbmNlIHRoZSBwb3NpdGlvbiBvZiB0aGUgaHlwZXJwbGFuZS4NCi0gICAqKlNsYWNrIFZhcmlhYmxlcyAozr4pOioqIEFsbG93IG1pc2NsYXNzaWZpY2F0aW9uIGluIG5vbi1zZXBhcmFibGUNCiAgICBjYXNlcy4NCi0gICAqKktlcm5lbCBUcmljazoqKiBNYXBzIGRhdGEgdG8gYSBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2Ugd2hlcmUgaXQNCiAgICBiZWNvbWVzIHNlcGFyYWJsZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjIuIE1hdGhlbWF0aWNhbCBGb3JtdWxhdGlvbiBvZiBTVk0qKg0KDQpBIGh5cGVycGxhbmUgaW4gYW4gJG4kLWRpbWVuc2lvbmFsIHNwYWNlIGlzIHJlcHJlc2VudGVkIGFzOg0KDQokJA0KXGJldGFfMCArIFxiZXRhXlQgeCA9IDANCiQkDQoNCkZvciBjbGFzc2lmaWNhdGlvbiwgd2UgYXNzaWduIGxhYmVscyAkeV9pIFxpbiBcey0xLDFcfSQgYW5kIGVuZm9yY2UgdGhlDQpjb25zdHJhaW50Og0KDQokJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMQ0KJCQNCg0KIyMjIyAqKk9wdGltaXphdGlvbiBQcm9ibGVtKioNCg0KVGhlIG9iamVjdGl2ZSBpcyB0byBtYXhpbWl6ZSB0aGUgbWFyZ2luICRNJCwgd2hpY2ggaXMgZXF1aXZhbGVudCB0bw0KbWluaW1pemluZzoNCg0KJCQNClxmcmFjezF9ezJ9IHx8XGJldGF8fF4yDQokJA0KDQpzdWJqZWN0IHRvOg0KDQokJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMQ0KJCQNCg0KRm9yICoqbm9uLXNlcGFyYWJsZSoqIGRhdGEsIHdlIGludHJvZHVjZSAqKnNsYWNrIHZhcmlhYmxlcyoqICRceGlfaSQ6DQoNCiQkDQp5X2kgKFxiZXRhXlQgeF9pICsgXGJldGFfMCkgXGdlcSAxIC0gXHhpX2ksIFxxdWFkIFx4aV9pIFxnZXEgMA0KJCQNCg0Kd2hpY2ggbGVhZHMgdG8gdGhlIHNvZnQtbWFyZ2luIFNWTToNCg0KJCQNClxtaW5fe1xiZXRhLCBcYmV0YV8wLCBceGl9IFxmcmFjezF9ezJ9IHx8XGJldGF8fF4yICsgQyBcc3VtIFx4aV9pDQokJA0KDQp3aGVyZSAkQyQgaXMgYSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIuDQoNCiMjIyMjICoqRHVhbCBGb3JtdWxhdGlvbioqDQoNClVzaW5nIExhZ3JhbmdlIG11bHRpcGxpZXJzICRcYWxwaGFfaSQsIHdlIHJld3JpdGUgdGhlIG9wdGltaXphdGlvbiBhczoNCg0KJCQNClxtYXhfe1xhbHBoYX0gXHN1bV97aT0xfV57Tn0gXGFscGhhX2kgLSBcZnJhY3sxfXsyfSBcc3VtX3tpPTF9XntOfSBcc3VtX3tqPTF9XntOfSBcYWxwaGFfaSBcYWxwaGFfaiB5X2kgeV9qIEsoeF9pLCB4X2opDQokJA0KDQpzdWJqZWN0IHRvOg0KDQokJA0KXHN1bV97aT0xfV57Tn0gXGFscGhhX2kgeV9pID0gMCwgXHF1YWQgMCBcbGVxIFxhbHBoYV9pIFxsZXEgQw0KJCQNCg0Kd2hlcmUgJEsoeF9pLCB4X2opJCBpcyB0aGUgKiprZXJuZWwgZnVuY3Rpb24qKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjMuIEhhbmRsaW5nIE5vbi1TZXBhcmFibGUgRGF0YSB3aXRoIEtlcm5lbHMqKg0KDQpXaGVuIGRhdGEgaXMgKipub3QgbGluZWFybHkgc2VwYXJhYmxlKiosIHdlIHRyYW5zZm9ybSBpdCBpbnRvIGENCmhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZSB1c2luZyAqKmtlcm5lbCBmdW5jdGlvbnMqKjoNCg0KJCQNCksoeF9pLCB4X2opID0gXHBoaSh4X2kpXlQgXHBoaSh4X2opDQokJA0KDQojIyMjICoqQ29tbW9uIEtlcm5lbCBGdW5jdGlvbnMqKg0KDQoxLiAgKipMaW5lYXIgS2VybmVsKio6ICRLKHhfaSwgeF9qKSA9IHhfaV5UIHhfaiQNCjIuICAqKlBvbHlub21pYWwgS2VybmVsKio6ICRLKHhfaSwgeF9qKSA9ICh4X2leVCB4X2ogKyBjKV5kJA0KMy4gICoqUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIChSQkYpIEtlcm5lbCoqOg0KICAgICRLKHhfaSwgeF9qKSA9IFxleHAoLVxnYW1tYSB8fHhfaSAtIHhfanx8XjIpJA0KNC4gICoqU2lnbW9pZCBLZXJuZWwqKjogJEsoeF9pLCB4X2opID0gXHRhbmgoXGthcHBhIHhfaV5UIHhfaiArIGMpJA0KDQpUaGUga2VybmVsIHRyaWNrIGFsbG93cyAqKm5vbi1saW5lYXIgY2xhc3NpZmljYXRpb24gd2l0aG91dCBleHBsaWNpdGx5DQp0cmFuc2Zvcm1pbmcgZGF0YSoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqNC4gQ29tcHV0YXRpb25hbCBBc3BlY3RzICYgU1ZNIGluIEhpZ2ggRGltZW5zaW9ucyoqDQoNCkZyb20gKipFU0xJSSoqLCBTVk3igJlzIGFiaWxpdHkgdG8gd29yayBpbiBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlcyBpcw0KZGlzY3Vzc2VkIGluICoqQ2hhcHRlciAxMioqLiBIb3dldmVyLCBpdCB3YXJucyBhYm91dCB0aGUgKipDdXJzZSBvZg0KRGltZW5zaW9uYWxpdHkqKjogLSBJZiBtb3N0IGZlYXR1cmVzIGFyZSAqKmlycmVsZXZhbnQqKiwgU1ZNIG1heSBwZXJmb3JtDQpwb29ybHkuIC0gU1ZNIG1heSBuZWVkICoqZmVhdHVyZSBzZWxlY3Rpb24qKiB0byBpbXByb3ZlIGdlbmVyYWxpemF0aW9uLg0KDQojIyMjICoqUmVndWxhcml6YXRpb24gUGFyYW1ldGVyKiogJEMkDQoNCi0gICAqKkhpZ2hlcioqICRDJCDihpIgUGVuYWxpemVzIG1pc2NsYXNzaWZpY2F0aW9uIG1vcmUgKG5hcnJvd2VyIG1hcmdpbiwNCiAgICBvdmVyZml0dGluZyByaXNrKS4NCi0gICAqKkxvd2VyKiogJEMkIOKGkiBBbGxvd3MgbW9yZSBtYXJnaW4gdmlvbGF0aW9ucyAod2lkZXIgbWFyZ2luLCBiZXR0ZXINCiAgICBnZW5lcmFsaXphdGlvbikuDQoNCiMjIyMgKipDaG9vc2luZyBLZXJuZWwgUGFyYW1ldGVycyoqDQoNCi0gICAqKkZvciBSQkYgS2VybmVsKiosIHRoZSBjaG9pY2Ugb2YgJFxnYW1tYSQgaW1wYWN0cyBtb2RlbCBjb21wbGV4aXR5Og0KICAgIC0gICAqKkxhcmdlKiogJFxnYW1tYSQg4oaSIEhpZ2hlciBmbGV4aWJpbGl0eSAocmlzayBvZiBvdmVyZml0dGluZykuDQogICAgLSAgICoqU21hbGwqKiAkXGdhbW1hJCDihpIgU2ltcGxlciBtb2RlbCAobWF5IHVuZGVyZml0KS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjUuIEltcGxlbWVudGluZyBTVk0gaW4gUHl0aG9uKioNCg0KSGVyZeKAmXMgaG93IHRvIGltcGxlbWVudCBhbiAqKlNWTSBjbGFzc2lmaWVyIHdpdGggU2Npa2l0LUxlYXJuKio6DQoNCmBgYCBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KZnJvbSBza2xlYXJuLnN2bSBpbXBvcnQgU1ZDDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IG1ha2VfY2xhc3NpZmljYXRpb24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KDQojIEdlbmVyYXRlIHN5bnRoZXRpYyBkYXRhc2V0DQpYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX3NhbXBsZXM9MTAwLCBuX2ZlYXR1cmVzPTIsIG5fY2xhc3Nlcz0yLCBuX3JlZHVuZGFudD0wLCByYW5kb21fc3RhdGU9NDIpDQp5ID0gbnAud2hlcmUoeSA9PSAwLCAtMSwgMSkgICMgQ29udmVydCBsYWJlbHMgdG8gey0xLCAxfQ0KDQojIFNwbGl0IGludG8gdHJhaW4tdGVzdCBzZXRzDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMywgcmFuZG9tX3N0YXRlPTQyKQ0KDQojIFRyYWluIFNWTSBjbGFzc2lmaWVyIHdpdGggUkJGIGtlcm5lbA0Kc3ZtID0gU1ZDKGtlcm5lbD0ncmJmJywgQz0xLjAsIGdhbW1hPTAuNSkNCnN2bS5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBQcmVkaWN0aW9ucw0KeV9wcmVkID0gc3ZtLnByZWRpY3QoWF90ZXN0KQ0KcHJpbnQoZidBY2N1cmFjeToge2FjY3VyYWN5X3Njb3JlKHlfdGVzdCwgeV9wcmVkKTouMmZ9JykNCg0KIyBQbG90IGRlY2lzaW9uIGJvdW5kYXJ5DQpkZWYgcGxvdF9zdm1fZGVjaXNpb25fYm91bmRhcnkoWCwgeSwgbW9kZWwpOg0KICAgIHBsdC5zY2F0dGVyKFhbOiwgMF0sIFhbOiwgMV0sIGM9eSwgY21hcD0nY29vbHdhcm0nLCBlZGdlY29sb3JzPSdrJykNCiAgICANCiAgICBheCA9IHBsdC5nY2EoKQ0KICAgIHhsaW0gPSBheC5nZXRfeGxpbSgpDQogICAgeWxpbSA9IGF4LmdldF95bGltKCkNCiAgICANCiAgICB4eCwgeXkgPSBucC5tZXNoZ3JpZChucC5saW5zcGFjZSh4bGltWzBdLCB4bGltWzFdLCA1MCksDQogICAgICAgICAgICAgICAgICAgICAgICAgbnAubGluc3BhY2UoeWxpbVswXSwgeWxpbVsxXSwgNTApKQ0KICAgIA0KICAgIFogPSBtb2RlbC5kZWNpc2lvbl9mdW5jdGlvbihucC5jX1t4eC5yYXZlbCgpLCB5eS5yYXZlbCgpXSkNCiAgICBaID0gWi5yZXNoYXBlKHh4LnNoYXBlKQ0KICAgIA0KICAgIGF4LmNvbnRvdXIoeHgsIHl5LCBaLCBjb2xvcnM9J2snLCBsZXZlbHM9Wy0xLCAwLCAxXSwgbGluZXN0eWxlcz1bJy0tJywgJy0nLCAnLS0nXSkNCiAgICANCiAgICBwbHQudGl0bGUoIlNWTSBEZWNpc2lvbiBCb3VuZGFyeSIpDQogICAgcGx0LnNob3coKQ0KDQpwbG90X3N2bV9kZWNpc2lvbl9ib3VuZGFyeShYX3RyYWluLCB5X3RyYWluLCBzdm0pDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjYuIEtleSBUYWtlYXdheXMqKg0KDQoxLiAgKipTVk0gZmluZHMgdGhlIG9wdGltYWwgaHlwZXJwbGFuZSoqIHRoYXQgbWF4aW1pemVzIHRoZSBtYXJnaW4uDQoyLiAgKipTb2Z0IG1hcmdpbiBTVk1zKiogYWxsb3cgc29tZSBtaXNjbGFzc2lmaWNhdGlvbiBmb3IgYmV0dGVyDQogICAgZ2VuZXJhbGl6YXRpb24uDQozLiAgKipLZXJuZWxzIGVuYWJsZSBub24tbGluZWFyIGNsYXNzaWZpY2F0aW9uKiogd2l0aG91dCBleHBsaWNpdA0KICAgIHRyYW5zZm9ybWF0aW9uLg0KNC4gICoqUmVndWxhcml6YXRpb24gcGFyYW1ldGVyKiogJEMkIGFuZCBrZXJuZWwgY2hvaWNlIHNpZ25pZmljYW50bHkNCiAgICBpbXBhY3QgbW9kZWwgcGVyZm9ybWFuY2UuDQo1LiAgKipTVk1zIGFyZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlKiogZm9yIGxhcmdlIGRhdGFzZXRzIGJ1dCBhcmUNCiAgICBlZmZlY3RpdmUgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKio3LiBBZGRpdGlvbmFsIEluc2lnaHRzIGZyb20gRVNMSUkqKg0KDQotICAgKipUYWJsZSAxMi4yIGluIEVTTElJKiogY29tcGFyZXMgKipTVk0sIHBvbHlub21pYWwsIGFuZCByYWRpYWwgYmFzaXMNCiAgICBmdW5jdGlvbiBjbGFzc2lmaWVycyoqLCBzaG93aW5nIHRoYXQgUkJGIFNWTSBvZnRlbiAqKnBlcmZvcm1zDQogICAgYmVzdCoqLg0KLSAgICoqUmVndWxhcml6YXRpb24gYW5kIGtlcm5lbCBzZWxlY3Rpb24qKiBhcmUga2V5IGluIG9wdGltaXppbmcgU1ZNDQogICAgbW9kZWxzLg0KDQotLS0jIyMgKipTdHVkeSBHdWlkZTogTWFyZ2lucyBhbmQgTG9zcyBpbiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcw0KKFNWTXMpKiogXCMjIyMgKipNb2R1bGUgOSwgU2VjdGlvbiAyOiBNYXJnaW5zIGFuZCBMb3NzKioNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgIFBhcnQgMg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKioxLiBJbnRyb2R1Y3Rpb24gdG8gTWFyZ2lucyBhbmQgTG9zcyBpbiBTVk0qKg0KDQpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykgb3BlcmF0ZSBieSAqKm1heGltaXppbmcgdGhlIG1hcmdpbioqDQpiZXR3ZWVuIGRpZmZlcmVudCBjbGFzc2VzLiBIb3dldmVyLCByZWFsLXdvcmxkIGRhdGFzZXRzIGFyZSBvZnRlbiAqKm5vdA0KcGVyZmVjdGx5IHNlcGFyYWJsZSoqLCB3aGljaCBpbnRyb2R1Y2VzICoqY2xhc3NpZmljYXRpb24gZXJyb3JzKiouIFRvDQpoYW5kbGUgdGhpcywgd2UgdXNlICoqbG9zcyBmdW5jdGlvbnMqKiwgc3BlY2lmaWNhbGx5IHRoZSAqKmhpbmdlIGxvc3MqKiwNCnRvIHBlbmFsaXplIG1pc2NsYXNzaWZpZWQgcG9pbnRzIGFuZCBlbnN1cmUgcm9idXN0IG1vZGVsIHBlcmZvcm1hbmNlLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKioyLiBIaW5nZSBMb3NzIGluIFNWTSoqDQoNCiMjIyAqKldoYXQgaXMgSGluZ2UgTG9zcz8qKg0KDQpIaW5nZSBsb3NzIGlzIGEgZnVuY3Rpb24gZGVzaWduZWQgdG8gKipwZW5hbGl6ZSBtaXNjbGFzc2lmaWVkIHBvaW50cyoqDQp3aGlsZSBhbGxvd2luZyAqKmNvcnJlY3RseSBjbGFzc2lmaWVkIHBvaW50cyBmYXIgZnJvbSB0aGUgZGVjaXNpb24NCmJvdW5kYXJ5IHRvIGNvbnRyaWJ1dGUgbm8gbG9zcyoqLiBJdCBpcyBtYXRoZW1hdGljYWxseSBkZWZpbmVkIGFzOg0KDQokJA0KTCh5LCBmKHgpKSA9IFxtYXgoMCwgMSAtIHkgZih4KSkNCiQkDQoNCndoZXJlOiAtICR5IFxpbiBcey0xLDFcfSQgaXMgdGhlIHRydWUgY2xhc3MgbGFiZWwuIC0gJGYoeCkkIGlzIHRoZQ0KZGVjaXNpb24gZnVuY3Rpb24gKGUuZy4sICRmKHgpID0gXGJldGFeVCB4ICsgXGJldGFfMCQpLg0KDQojIyMgKipJbnRlcnByZXRhdGlvbiBvZiBIaW5nZSBMb3NzKioNCg0KLSAgIElmIGEgcG9pbnQgaXMgKipjb3JyZWN0bHkgY2xhc3NpZmllZCoqIGFuZCBmYXIgZnJvbSB0aGUgbWFyZ2luDQogICAgKGkuZS4sICR5IGYoeCkgPiAxJCksIHRoZSBsb3NzIGlzICoqemVybyoqLg0KLSAgIElmIGEgcG9pbnQgaXMgKipuZWFyIHRoZSBtYXJnaW4qKiAoaS5lLiwgJDAgPCB5IGYoeCkgXGxlcSAxJCksIGl0DQogICAgY29udHJpYnV0ZXMgdG8gdGhlIGxvc3MuDQotICAgSWYgYSBwb2ludCBpcyAqKm1pc2NsYXNzaWZpZWQqKiAoaS5lLiwgJHkgZih4KSA8IDAkKSwgdGhlIGxvc3MNCiAgICBpbmNyZWFzZXMgKipsaW5lYXJseSoqLg0KDQojIyMgKipRdWFkcmF0aWMgSGluZ2UgTG9zcyoqDQoNCkFuIGFsdGVybmF0aXZlICoqcXVhZHJhdGljIHZlcnNpb24qKiBvZiBoaW5nZSBsb3NzIGlzOg0KDQokJA0KTCh5LCBmKHgpKSA9ICggXG1heCgwLCAxIC0geSBmKHgpKSApXjINCiQkDQoNClRoaXMgKipzbW9vdGhzIG91dCoqIHRoZSBsb3NzIGZ1bmN0aW9uLCBtYWtpbmcgaXQgbW9yZSBzdGFibGUgZHVyaW5nDQpvcHRpbWl6YXRpb24uDQoNCiMjIyAqKkNvbXBhcmlzb24gd2l0aCBPdGhlciBMb3NzIEZ1bmN0aW9ucyoqDQoNCkhpbmdlIGxvc3MgZGlmZmVycyBmcm9tICoqbG9naXN0aWMgcmVncmVzc2lvbuKAmXMgbG9nLWxpa2VsaWhvb2QgbG9zcyoqDQphbmQgKipzcXVhcmVkIGVycm9yIGxvc3MqKjogLSAqKkxvZy1saWtlbGlob29kIGxvc3MqKiAodXNlZCBpbiBsb2dpc3RpYw0KcmVncmVzc2lvbikgcGVuYWxpemVzIG1pc2NsYXNzaWZpY2F0aW9ucyBtb3JlIHNtb290aGx5LiAtICoqU3F1YXJlZA0KZXJyb3IgbG9zcyoqIGFzc2lnbnMgYSAqKnF1YWRyYXRpYyBwZW5hbHR5KiogdG8gZXJyb3JzLCBtYWtpbmcgaXQgbGVzcw0Kcm9idXN0IHRvIG91dGxpZXJzLiAtICoqSGluZ2UgbG9zcyoqIGlzIGEgY29tcHJvbWlzZeKAlGl0ICoqcGVuYWxpemVzDQptaXNjbGFzc2lmaWNhdGlvbnMgbGluZWFybHkqKiwgbWFraW5nIGl0IG1vcmUgKipyb2J1c3QqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqMy4gVGhlIFJvbGUgb2YgTWFyZ2lucyBpbiBTVk0qKg0KDQpTVk1zIGFpbSB0byAqKm1heGltaXplIHRoZSBtYXJnaW4qKiwgZGVmaW5lZCBhczoNCg0KJCQNCk0gPSBcZnJhY3sxfXtcfFxiZXRhXHx9DQokJA0KDQp3aGVyZSAkXGJldGEkIGlzIHRoZSB2ZWN0b3Igbm9ybWFsIHRvIHRoZSBoeXBlcnBsYW5lLiBBIGxhcmdlciBtYXJnaW4NCnByb3ZpZGVzICoqYmV0dGVyIGdlbmVyYWxpemF0aW9uKiouDQoNCiMjIyAqKk1hcmdpbi1CYXNlZCBEZWNpc2lvbiBSdWxlKioNCg0KRm9yIGEgKipoYXJkLW1hcmdpbiBTVk0qKiAobGluZWFybHkgc2VwYXJhYmxlIGRhdGEpOg0KDQokJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMQ0KJCQNCg0KRm9yIGEgKipzb2Z0LW1hcmdpbiBTVk0qKiAoYWxsb3dpbmcgc29tZSBtaXNjbGFzc2lmaWNhdGlvbik6DQoNCiQkDQp5X2kgKFxiZXRhXlQgeF9pICsgXGJldGFfMCkgXGdlcSAxIC0gXHhpX2ksIFxxdWFkIFx4aV9pIFxnZXEgMA0KJCQNCg0Kd2hlcmUgJFx4aV9pJCBhcmUgKipzbGFjayB2YXJpYWJsZXMqKiBjb250cm9sbGluZyBtaXNjbGFzc2lmaWNhdGlvbi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNC4gQ29uZmlkZW5jZSBpbiBTVk0qKg0KDQojIyMgKipXaGF0IGlzIENvbmZpZGVuY2UgaW4gU1ZNPyoqDQoNCkNvbmZpZGVuY2UgaW4gU1ZNcyByZWZlcnMgdG8gdGhlICoqZGlzdGFuY2Ugb2YgYSBwb2ludCBmcm9tIHRoZSBkZWNpc2lvbg0KYm91bmRhcnkqKiAobm90IHRoZSBtYXJnaW4hKS4gSXQgaXMgYSBtZWFzdXJlIG9mIGhvdyBjb25maWRlbnRseSBhIHBvaW50DQppcyBjbGFzc2lmaWVkLg0KDQotICAgKipQb3NpdGl2ZSBjb25maWRlbmNlOioqIFRoZSBwb2ludCBpcyBjb3JyZWN0bHkgY2xhc3NpZmllZC4NCi0gICAqKk5lZ2F0aXZlIGNvbmZpZGVuY2U6KiogVGhlIHBvaW50IGlzIG1pc2NsYXNzaWZpZWQuDQoNCk1hdGhlbWF0aWNhbGx5LCBjb25maWRlbmNlIGlzIGdpdmVuIGJ5Og0KDQokJA0KXHRleHR7Q29uZmlkZW5jZX0gPSB8Zih4KXwNCiQkDQoNCndoZXJlICRmKHgpID0gXGJldGFeVCB4ICsgXGJldGFfMCQgaXMgdGhlIGRlY2lzaW9uIGZ1bmN0aW9uLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio1LiBJbXBsZW1lbnRpbmcgSGluZ2UgTG9zcyBpbiBQeXRob24qKg0KDQpIZXJl4oCZcyBob3cgdG8gaW1wbGVtZW50ICoqaGluZ2UgbG9zcyBhbmQgU1ZNIHRyYWluaW5nKiogdXNpbmcNCioqU2Npa2l0LUxlYXJuKiouDQoNCiMjIyAqKkhpbmdlIExvc3MgQ29tcHV0YXRpb24qKg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gc2tsZWFybi5zdm0gaW1wb3J0IFNWQw0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgaGluZ2VfbG9zcw0KDQojIEdlbmVyYXRlIHN5bnRoZXRpYyBkYXRhc2V0DQpYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX3NhbXBsZXM9MTAwLCBuX2ZlYXR1cmVzPTIsIG5fY2xhc3Nlcz0yLCBuX3JlZHVuZGFudD0wLCByYW5kb21fc3RhdGU9NDIpDQp5ID0gbnAud2hlcmUoeSA9PSAwLCAtMSwgMSkgICMgQ29udmVydCBsYWJlbHMgdG8gey0xLCAxfQ0KDQojIFRyYWluIFNWTQ0Kc3ZtID0gU1ZDKGtlcm5lbD0nbGluZWFyJywgQz0xLjApDQpzdm0uZml0KFgsIHkpDQoNCiMgQ29tcHV0ZSBkZWNpc2lvbiBmdW5jdGlvbg0KZGVjaXNpb25fdmFsdWVzID0gc3ZtLmRlY2lzaW9uX2Z1bmN0aW9uKFgpDQoNCiMgQ29tcHV0ZSBoaW5nZSBsb3NzDQpsb3NzID0gaGluZ2VfbG9zcyh5LCBkZWNpc2lvbl92YWx1ZXMpDQpwcmludChmJ0hpbmdlIExvc3M6IHtsb3NzOi40Zn0nKQ0KYGBgDQoNCiMjIyAqKlBsb3R0aW5nIERlY2lzaW9uIEJvdW5kYXJ5IHdpdGggTWFyZ2lucyoqDQoNCmBgYCBweXRob24NCmRlZiBwbG90X3N2bV9kZWNpc2lvbl9ib3VuZGFyeShYLCB5LCBtb2RlbCk6DQogICAgcGx0LnNjYXR0ZXIoWFs6LCAwXSwgWFs6LCAxXSwgYz15LCBjbWFwPSdjb29sd2FybScsIGVkZ2Vjb2xvcnM9J2snKQ0KDQogICAgYXggPSBwbHQuZ2NhKCkNCiAgICB4bGltID0gYXguZ2V0X3hsaW0oKQ0KICAgIHlsaW0gPSBheC5nZXRfeWxpbSgpDQoNCiAgICB4eCwgeXkgPSBucC5tZXNoZ3JpZChucC5saW5zcGFjZSh4bGltWzBdLCB4bGltWzFdLCA1MCksDQogICAgICAgICAgICAgICAgICAgICAgICAgbnAubGluc3BhY2UoeWxpbVswXSwgeWxpbVsxXSwgNTApKQ0KDQogICAgWiA9IG1vZGVsLmRlY2lzaW9uX2Z1bmN0aW9uKG5wLmNfW3h4LnJhdmVsKCksIHl5LnJhdmVsKCldKQ0KICAgIFogPSBaLnJlc2hhcGUoeHguc2hhcGUpDQoNCiAgICBheC5jb250b3VyKHh4LCB5eSwgWiwgY29sb3JzPSdrJywgbGV2ZWxzPVstMSwgMCwgMV0sIGxpbmVzdHlsZXM9WyctLScsICctJywgJy0tJ10pDQoNCiAgICBwbHQudGl0bGUoIlNWTSBEZWNpc2lvbiBCb3VuZGFyeSB3aXRoIE1hcmdpbnMiKQ0KICAgIHBsdC5zaG93KCkNCg0KcGxvdF9zdm1fZGVjaXNpb25fYm91bmRhcnkoWCwgeSwgc3ZtKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjYuIEtleSBUYWtlYXdheXMqKg0KDQotICAgKipIaW5nZSBsb3NzKiogaXMgdXNlZCBpbiBTVk0gdG8gKipwZW5hbGl6ZSBtaXNjbGFzc2lmaWVkIHBvaW50cyoqDQogICAgd2hpbGUgaWdub3JpbmcgY29ycmVjdGx5IGNsYXNzaWZpZWQgcG9pbnRzIGZhciBmcm9tIHRoZSBkZWNpc2lvbg0KICAgIGJvdW5kYXJ5Lg0KLSAgICoqTWFyZ2luIG1heGltaXphdGlvbioqIGVuc3VyZXMgYmV0dGVyIGdlbmVyYWxpemF0aW9uLg0KLSAgICoqU2xhY2sgdmFyaWFibGVzKiogJFx4aV9pJCBhbGxvdyBzb21lIG1pc2NsYXNzaWZpY2F0aW9uIGluDQogICAgKipzb2Z0LW1hcmdpbiBTVk1zKiouDQotICAgKipDb25maWRlbmNlKiogaW4gU1ZNIGlzICoqbm90IHByb2JhYmlsaXR5KiogYnV0IHRoZSAqKmRpc3RhbmNlIHRvDQogICAgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5KiouDQotICAgKipRdWFkcmF0aWMgaGluZ2UgbG9zcyoqIHNtb290aHMgb3V0IHRoZSBsb3NzIGZ1bmN0aW9uLCBtYWtpbmcNCiAgICBvcHRpbWl6YXRpb24gbW9yZSBzdGFibGUuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjcuIEFkZGl0aW9uYWwgSW5zaWdodHMgZnJvbSBFU0xJSSoqDQoNCi0gICAqKkZpZ3VyZSAxMi40IGluIEVTTElJKiogY29tcGFyZXMgKipoaW5nZSBsb3NzLCBzcXVhcmVkIGVycm9yIGxvc3MsDQogICAgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbG9zcyoqLg0KLSAgICoqVGFibGUgMTIuMSBpbiBFU0xJSSoqIGNoYXJhY3Rlcml6ZXMgKipkaWZmZXJlbnQgbG9zcyBmdW5jdGlvbnMqKiwNCiAgICBzaG93aW5nIHRoYXQgaGluZ2UgbG9zcyBlc3RpbWF0ZXMgdGhlICoqbW9kZSoqIG9mIGNsYXNzDQogICAgcHJvYmFiaWxpdGllcy4NCi0gICAqKlJlZ3VsYXJpemF0aW9uIGFuZCBrZXJuZWwgc2VsZWN0aW9uKiogcGxheSBhIGtleSByb2xlIGluDQogICAgb3B0aW1pemluZyBTVk0gbW9kZWxzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipQYXJ0IDIgQ29udGludWVkOiBNYXJnaW5zIGFuZCBMb3NzIGluIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSoqDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjEuIEludHJvZHVjdGlvbiB0byBNYXJnaW5zIGFuZCBMb3NzIGluIFNWTSoqDQoNClN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSBvcGVyYXRlIGJ5ICoqbWF4aW1pemluZyB0aGUgbWFyZ2luKioNCmJldHdlZW4gZGlmZmVyZW50IGNsYXNzZXMuIEhvd2V2ZXIsIHJlYWwtd29ybGQgZGF0YXNldHMgYXJlIG9mdGVuICoqbm90DQpwZXJmZWN0bHkgc2VwYXJhYmxlKiosIHdoaWNoIGludHJvZHVjZXMgKipjbGFzc2lmaWNhdGlvbiBlcnJvcnMqKi4gVG8NCmhhbmRsZSB0aGlzLCB3ZSB1c2UgKipsb3NzIGZ1bmN0aW9ucyoqLCBzcGVjaWZpY2FsbHkgdGhlICoqaGluZ2UgbG9zcyoqLA0KdG8gcGVuYWxpemUgbWlzY2xhc3NpZmllZCBwb2ludHMgYW5kIGVuc3VyZSByb2J1c3QgbW9kZWwgcGVyZm9ybWFuY2UuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjIuIEhpbmdlIExvc3MgaW4gU1ZNKioNCg0KIyMjICoqV2hhdCBpcyBIaW5nZSBMb3NzPyoqDQoNCkhpbmdlIGxvc3MgaXMgYSBmdW5jdGlvbiBkZXNpZ25lZCB0byAqKnBlbmFsaXplIG1pc2NsYXNzaWZpZWQgcG9pbnRzKioNCndoaWxlIGFsbG93aW5nICoqY29ycmVjdGx5IGNsYXNzaWZpZWQgcG9pbnRzIGZhciBmcm9tIHRoZSBkZWNpc2lvbg0KYm91bmRhcnkgdG8gY29udHJpYnV0ZSBubyBsb3NzKiouIEl0IGlzIG1hdGhlbWF0aWNhbGx5IGRlZmluZWQgYXM6DQoNCiQkDQpMKHksIGYoeCkpID0gXG1heCgwLCAxIC0geSBmKHgpKQ0KJCQNCg0Kd2hlcmU6IC0gJHkgXGluIFx7LTEsMVx9JCBpcyB0aGUgdHJ1ZSBjbGFzcyBsYWJlbC4gLSAkZih4KSQgaXMgdGhlDQpkZWNpc2lvbiBmdW5jdGlvbiAoZS5nLiwgJGYoeCkgPSBcYmV0YV5UIHggKyBcYmV0YV8wJCkuDQoNCiMjIyAqKkludGVycHJldGF0aW9uIG9mIEhpbmdlIExvc3MqKg0KDQotICAgSWYgYSBwb2ludCBpcyAqKmNvcnJlY3RseSBjbGFzc2lmaWVkKiogYW5kIGZhciBmcm9tIHRoZSBtYXJnaW4NCiAgICAoaS5lLiwgJHkgZih4KSA+IDEkKSwgdGhlIGxvc3MgaXMgKip6ZXJvKiouDQotICAgSWYgYSBwb2ludCBpcyAqKm5lYXIgdGhlIG1hcmdpbioqIChpLmUuLCAkMCA8IHkgZih4KSBcbGVxIDEkKSwgaXQNCiAgICBjb250cmlidXRlcyB0byB0aGUgbG9zcy4NCi0gICBJZiBhIHBvaW50IGlzICoqbWlzY2xhc3NpZmllZCoqIChpLmUuLCAkeSBmKHgpIDwgMCQpLCB0aGUgbG9zcw0KICAgIGluY3JlYXNlcyAqKmxpbmVhcmx5KiouDQoNCiMjIyAqKlF1YWRyYXRpYyBIaW5nZSBMb3NzKioNCg0KQW4gYWx0ZXJuYXRpdmUgKipxdWFkcmF0aWMgdmVyc2lvbioqIG9mIGhpbmdlIGxvc3MgaXM6DQoNCiQkDQpMKHksIGYoeCkpID0gKCBcbWF4KDAsIDEgLSB5IGYoeCkpICleMg0KJCQNCg0KVGhpcyAqKnNtb290aHMgb3V0KiogdGhlIGxvc3MgZnVuY3Rpb24sIG1ha2luZyBpdCBtb3JlIHN0YWJsZSBkdXJpbmcNCm9wdGltaXphdGlvbi4NCg0KIyMjICoqQ29tcGFyaXNvbiB3aXRoIE90aGVyIExvc3MgRnVuY3Rpb25zKioNCg0KSGluZ2UgbG9zcyBkaWZmZXJzIGZyb20gKipsb2dpc3RpYyByZWdyZXNzaW9u4oCZcyBsb2ctbGlrZWxpaG9vZCBsb3NzKioNCmFuZCAqKnNxdWFyZWQgZXJyb3IgbG9zcyoqOiAtICoqTG9nLWxpa2VsaWhvb2QgbG9zcyoqICh1c2VkIGluIGxvZ2lzdGljDQpyZWdyZXNzaW9uKSBwZW5hbGl6ZXMgbWlzY2xhc3NpZmljYXRpb25zIG1vcmUgc21vb3RobHkuIC0gKipTcXVhcmVkDQplcnJvciBsb3NzKiogYXNzaWducyBhICoqcXVhZHJhdGljIHBlbmFsdHkqKiB0byBlcnJvcnMsIG1ha2luZyBpdCBsZXNzDQpyb2J1c3QgdG8gb3V0bGllcnMuIC0gKipIaW5nZSBsb3NzKiogaXMgYSBjb21wcm9taXNl4oCUaXQgKipwZW5hbGl6ZXMNCm1pc2NsYXNzaWZpY2F0aW9ucyBsaW5lYXJseSoqLCBtYWtpbmcgaXQgbW9yZSAqKnJvYnVzdCoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKiozLiBUaGUgUm9sZSBvZiBNYXJnaW5zIGluIFNWTSoqDQoNClNWTXMgYWltIHRvICoqbWF4aW1pemUgdGhlIG1hcmdpbioqLCBkZWZpbmVkIGFzOg0KDQokJA0KTSA9IFxmcmFjezF9e1x8XGJldGFcfH0NCiQkDQoNCndoZXJlICRcYmV0YSQgaXMgdGhlIHZlY3RvciBub3JtYWwgdG8gdGhlIGh5cGVycGxhbmUuIEEgbGFyZ2VyIG1hcmdpbg0KcHJvdmlkZXMgKipiZXR0ZXIgZ2VuZXJhbGl6YXRpb24qKi4NCg0KIyMjICoqTWFyZ2luLUJhc2VkIERlY2lzaW9uIFJ1bGUqKg0KDQpGb3IgYSAqKmhhcmQtbWFyZ2luIFNWTSoqIChsaW5lYXJseSBzZXBhcmFibGUgZGF0YSk6DQoNCiQkDQp5X2kgKFxiZXRhXlQgeF9pICsgXGJldGFfMCkgXGdlcSAxDQokJA0KDQpGb3IgYSAqKnNvZnQtbWFyZ2luIFNWTSoqIChhbGxvd2luZyBzb21lIG1pc2NsYXNzaWZpY2F0aW9uKToNCg0KJCQNCnlfaSAoXGJldGFeVCB4X2kgKyBcYmV0YV8wKSBcZ2VxIDEgLSBceGlfaSwgXHF1YWQgXHhpX2kgXGdlcSAwDQokJA0KDQp3aGVyZSAkXHhpX2kkIGFyZSAqKnNsYWNrIHZhcmlhYmxlcyoqIGNvbnRyb2xsaW5nIG1pc2NsYXNzaWZpY2F0aW9uLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio0LiBDb25maWRlbmNlIGluIFNWTSoqDQoNCiMjIyAqKldoYXQgaXMgQ29uZmlkZW5jZSBpbiBTVk0/KioNCg0KQ29uZmlkZW5jZSBpbiBTVk1zIHJlZmVycyB0byB0aGUgKipkaXN0YW5jZSBvZiBhIHBvaW50IGZyb20gdGhlIGRlY2lzaW9uDQpib3VuZGFyeSoqIChub3QgdGhlIG1hcmdpbiEpLiBJdCBpcyBhIG1lYXN1cmUgb2YgaG93IGNvbmZpZGVudGx5IGEgcG9pbnQNCmlzIGNsYXNzaWZpZWQuDQoNCi0gICAqKlBvc2l0aXZlIGNvbmZpZGVuY2U6KiogVGhlIHBvaW50IGlzIGNvcnJlY3RseSBjbGFzc2lmaWVkLg0KLSAgICoqTmVnYXRpdmUgY29uZmlkZW5jZToqKiBUaGUgcG9pbnQgaXMgbWlzY2xhc3NpZmllZC4NCg0KTWF0aGVtYXRpY2FsbHksIGNvbmZpZGVuY2UgaXMgZ2l2ZW4gYnk6DQoNCiQkDQpcdGV4dHtDb25maWRlbmNlfSA9IHxmKHgpfA0KJCQNCg0Kd2hlcmUgJGYoeCkgPSBcYmV0YV5UIHggKyBcYmV0YV8wJCBpcyB0aGUgZGVjaXNpb24gZnVuY3Rpb24uDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjUuIEltcGxlbWVudGluZyBIaW5nZSBMb3NzIGluIFB5dGhvbioqDQoNCkhlcmXigJlzIGhvdyB0byBpbXBsZW1lbnQgKipoaW5nZSBsb3NzIGFuZCBTVk0gdHJhaW5pbmcqKiB1c2luZw0KKipTY2lraXQtTGVhcm4qKi4NCg0KIyMjICoqSGluZ2UgTG9zcyBDb21wdXRhdGlvbioqDQoNCmBgYCBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KZnJvbSBza2xlYXJuLnN2bSBpbXBvcnQgU1ZDDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IG1ha2VfY2xhc3NpZmljYXRpb24NCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBoaW5nZV9sb3NzDQoNCiMgR2VuZXJhdGUgc3ludGhldGljIGRhdGFzZXQNClgsIHkgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKG5fc2FtcGxlcz0xMDAsIG5fZmVhdHVyZXM9Miwgbl9jbGFzc2VzPTIsIG5fcmVkdW5kYW50PTAsIHJhbmRvbV9zdGF0ZT00MikNCnkgPSBucC53aGVyZSh5ID09IDAsIC0xLCAxKSAgIyBDb252ZXJ0IGxhYmVscyB0byB7LTEsIDF9DQoNCiMgVHJhaW4gU1ZNDQpzdm0gPSBTVkMoa2VybmVsPSdsaW5lYXInLCBDPTEuMCkNCnN2bS5maXQoWCwgeSkNCg0KIyBDb21wdXRlIGRlY2lzaW9uIGZ1bmN0aW9uDQpkZWNpc2lvbl92YWx1ZXMgPSBzdm0uZGVjaXNpb25fZnVuY3Rpb24oWCkNCg0KIyBDb21wdXRlIGhpbmdlIGxvc3MNCmxvc3MgPSBoaW5nZV9sb3NzKHksIGRlY2lzaW9uX3ZhbHVlcykNCnByaW50KGYnSGluZ2UgTG9zczoge2xvc3M6LjRmfScpDQpgYGANCg0KIyMjICoqUGxvdHRpbmcgRGVjaXNpb24gQm91bmRhcnkgd2l0aCBNYXJnaW5zKioNCg0KYGBgIHB5dGhvbg0KZGVmIHBsb3Rfc3ZtX2RlY2lzaW9uX2JvdW5kYXJ5KFgsIHksIG1vZGVsKToNCiAgICBwbHQuc2NhdHRlcihYWzosIDBdLCBYWzosIDFdLCBjPXksIGNtYXA9J2Nvb2x3YXJtJywgZWRnZWNvbG9ycz0naycpDQoNCiAgICBheCA9IHBsdC5nY2EoKQ0KICAgIHhsaW0gPSBheC5nZXRfeGxpbSgpDQogICAgeWxpbSA9IGF4LmdldF95bGltKCkNCg0KICAgIHh4LCB5eSA9IG5wLm1lc2hncmlkKG5wLmxpbnNwYWNlKHhsaW1bMF0sIHhsaW1bMV0sIDUwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBucC5saW5zcGFjZSh5bGltWzBdLCB5bGltWzFdLCA1MCkpDQoNCiAgICBaID0gbW9kZWwuZGVjaXNpb25fZnVuY3Rpb24obnAuY19beHgucmF2ZWwoKSwgeXkucmF2ZWwoKV0pDQogICAgWiA9IFoucmVzaGFwZSh4eC5zaGFwZSkNCg0KICAgIGF4LmNvbnRvdXIoeHgsIHl5LCBaLCBjb2xvcnM9J2snLCBsZXZlbHM9Wy0xLCAwLCAxXSwgbGluZXN0eWxlcz1bJy0tJywgJy0nLCAnLS0nXSkNCg0KICAgIHBsdC50aXRsZSgiU1ZNIERlY2lzaW9uIEJvdW5kYXJ5IHdpdGggTWFyZ2lucyIpDQogICAgcGx0LnNob3coKQ0KDQpwbG90X3N2bV9kZWNpc2lvbl9ib3VuZGFyeShYLCB5LCBzdm0pDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNi4gS2V5IFRha2Vhd2F5cyoqDQoNCi0gICAqKkhpbmdlIGxvc3MqKiBpcyB1c2VkIGluIFNWTSB0byAqKnBlbmFsaXplIG1pc2NsYXNzaWZpZWQgcG9pbnRzKioNCiAgICB3aGlsZSBpZ25vcmluZyBjb3JyZWN0bHkgY2xhc3NpZmllZCBwb2ludHMgZmFyIGZyb20gdGhlIGRlY2lzaW9uDQogICAgYm91bmRhcnkuDQotICAgKipNYXJnaW4gbWF4aW1pemF0aW9uKiogZW5zdXJlcyBiZXR0ZXIgZ2VuZXJhbGl6YXRpb24uDQotICAgKipTbGFjayB2YXJpYWJsZXMqKiAkXHhpX2kkIGFsbG93IHNvbWUgbWlzY2xhc3NpZmljYXRpb24gaW4NCiAgICAqKnNvZnQtbWFyZ2luIFNWTXMqKi4NCi0gICAqKkNvbmZpZGVuY2UqKiBpbiBTVk0gaXMgKipub3QgcHJvYmFiaWxpdHkqKiBidXQgdGhlICoqZGlzdGFuY2UgdG8NCiAgICB0aGUgZGVjaXNpb24gYm91bmRhcnkqKi4NCi0gICAqKlF1YWRyYXRpYyBoaW5nZSBsb3NzKiogc21vb3RocyBvdXQgdGhlIGxvc3MgZnVuY3Rpb24sIG1ha2luZw0KICAgIG9wdGltaXphdGlvbiBtb3JlIHN0YWJsZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNy4gQWRkaXRpb25hbCBJbnNpZ2h0cyBmcm9tIEVTTElJKioNCg0KLSAgICoqRmlndXJlIDEyLjQgaW4gRVNMSUkqKiBjb21wYXJlcyAqKmhpbmdlIGxvc3MsIHNxdWFyZWQgZXJyb3IgbG9zcywNCiAgICBhbmQgbG9naXN0aWMgcmVncmVzc2lvbiBsb3NzKiouDQotICAgKipUYWJsZSAxMi4xIGluIEVTTElJKiogY2hhcmFjdGVyaXplcyAqKmRpZmZlcmVudCBsb3NzIGZ1bmN0aW9ucyoqLA0KICAgIHNob3dpbmcgdGhhdCBoaW5nZSBsb3NzIGVzdGltYXRlcyB0aGUgKiptb2RlKiogb2YgY2xhc3MNCiAgICBwcm9iYWJpbGl0aWVzLg0KLSAgICoqUmVndWxhcml6YXRpb24gYW5kIGtlcm5lbCBzZWxlY3Rpb24qKiBwbGF5IGEga2V5IHJvbGUgaW4NCiAgICBvcHRpbWl6aW5nIFNWTSBtb2RlbHMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKlBhcnQgMyAtIFRoZSBLZXJuZWwgVHJpY2sqKg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqQ29uY2VwdCBPdmVydmlldyoqDQoNClN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSBhcmUgc3VwZXJ2aXNlZCBsZWFybmluZyBtb2RlbHMgdXNlZCBmb3INCmNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIGFuYWx5c2lzLiBUaGUgZ29hbCBvZiBhbiBTVk0gaXMgdG8gZmluZA0KdGhlICoqb3B0aW1hbCBoeXBlcnBsYW5lKiogdGhhdCBzZXBhcmF0ZXMgZGlmZmVyZW50IGNsYXNzZXMgaW4gYSBkYXRhc2V0DQp3aXRoIHRoZSAqKm1heGltdW0gbWFyZ2luKiouDQoNCioqS2V5IENvbmNlcHRzOioqIC0gKipIeXBlcnBsYW5lKio6IEEgZGVjaXNpb24gYm91bmRhcnkgdGhhdCBzZXBhcmF0ZXMNCmNsYXNzZXMuIC0gKipNYXJnaW4qKjogVGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIGh5cGVycGxhbmUgYW5kIHRoZQ0KbmVhcmVzdCBkYXRhIHBvaW50cyBmcm9tIGVpdGhlciBjbGFzcyAoc3VwcG9ydCB2ZWN0b3JzKS4gLSAqKlN1cHBvcnQNClZlY3RvcnMqKjogVGhlIGRhdGEgcG9pbnRzIHRoYXQgYXJlIGNsb3Nlc3QgdG8gdGhlIGh5cGVycGxhbmUgYW5kDQppbmZsdWVuY2UgaXRzIHBvc2l0aW9uLg0KDQojIyMgKipNYXRoZW1hdGljYWwgRm9ybXVsYXRpb24qKg0KDQpGb3IgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbSB3aXRoIGlucHV0IGZlYXR1cmVzICR4X2kkIGFuZCBsYWJlbHMNCiR5X2kgXGluIFx7LTEsMVx9JCwgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IGlzIGRlZmluZWQgYnk6DQoNCiQkDQpmKHgpID0gXGJldGFeVCB4ICsgXGJldGFfMA0KJCQNCg0KVGhlIGdvYWwgaXMgdG8gbWF4aW1pemUgdGhlIG1hcmdpbiAkTSQgd2hpbGUgZW5zdXJpbmcgY29ycmVjdA0KY2xhc3NpZmljYXRpb246DQoNCiQkDQp5X2kgKFxiZXRhXlQgeF9pICsgXGJldGFfMCkgXGdlcSBNDQokJA0KDQpGb3IgYSBwZXJmZWN0bHkgc2VwYXJhYmxlIGRhdGFzZXQsIHRoZSBvcHRpbWFsIHNlcGFyYXRpbmcgaHlwZXJwbGFuZQ0Kc2F0aXNmaWVzOg0KDQokJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMQ0KJCQNCg0KV2UgbWluaW1pemUgJFx8XGJldGFcfCQgdG8gbWF4aW1pemUgJE0gPSBcZnJhY3sxfXtcfFxiZXRhXHx9JCwgbGVhZGluZw0KdG8gdGhlIG9wdGltaXphdGlvbiBwcm9ibGVtOg0KDQokJA0KXG1pbl97XGJldGEsIFxiZXRhXzB9IFxmcmFjezF9ezJ9IFx8XGJldGFcfF4yDQokJA0KDQpzdWJqZWN0IHRvOg0KDQokJA0KeV9pIChcYmV0YV5UIHhfaSArIFxiZXRhXzApIFxnZXEgMQ0KJCQNCg0KVGhpcyBpcyBhICoqcXVhZHJhdGljIG9wdGltaXphdGlvbiBwcm9ibGVtKiogc29sdmVkIHVzaW5nICoqTGFncmFuZ2UNCm11bHRpcGxpZXJzKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKlBhcnQgMjogTWFyZ2lucyBhbmQgTG9zcyBGdW5jdGlvbnMgaW4gU1ZNcyoqDQoNClNWTXMgdXNlICoqSGluZ2UgTG9zcyoqIHRvIGhhbmRsZSBtaXNjbGFzc2lmaWVkIHBvaW50cy4NCg0KIyMjICoqSGluZ2UgTG9zcyBGdW5jdGlvbioqDQoNCiQkDQpMKHksIGYoeCkpID0gXG1heCgwLCAxIC0geSBmKHgpKQ0KJCQNCg0KLSAgIElmIHRoZSBwb2ludCBpcyBjb3JyZWN0bHkgY2xhc3NpZmllZCBhbmQgZmFyIGZyb20gdGhlIGh5cGVycGxhbmUsDQogICAgJEwoeSwgZih4KSkgPSAwJC4NCi0gICBJZiB0aGUgcG9pbnQgaXMgbWlzY2xhc3NpZmllZCBvciB3aXRoaW4gdGhlIG1hcmdpbiwgdGhlIGxvc3MNCiAgICBpbmNyZWFzZXMgbGluZWFybHkuDQoNCiMjIyAqKk1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbioqDQoNCiQkDQpcbWluX3tcYmV0YSwgXGJldGFfMH0gXGZyYWN7MX17Mn0gXHxcYmV0YVx8XjIgKyBDIFxzdW1fe2k9MX1ee059IFx4aV9pDQokJA0KDQp3aGVyZSAkXHhpX2kkIHJlcHJlc2VudHMgc2xhY2sgdmFyaWFibGVzIGFsbG93aW5nIHNvbWUNCm1pc2NsYXNzaWZpY2F0aW9ucy4NCg0KKipUeXBlcyBvZiBIaW5nZSBMb3NzOioqIC0gKipMaW5lYXIgSGluZ2UgTG9zcyoqOiBQZW5hbGl6ZXMNCm1pc2NsYXNzaWZpZWQgcG9pbnRzIGxpbmVhcmx5LiAtICoqUXVhZHJhdGljIEhpbmdlIExvc3MqKjogUGVuYWxpemVzDQp3aXRoIGEgc3F1YXJlZCBmdW5jdGlvbiBmb3IgYmV0dGVyIHJvYnVzdG5lc3MuDQoNCiMjIyAqKlB5dGhvbiBDb2RlIGZvciBIaW5nZSBMb3NzKioNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQoNCiMgSGluZ2UgTG9zcyBGdW5jdGlvbg0KZGVmIGhpbmdlX2xvc3MoeSwgZl94KToNCiAgICByZXR1cm4gbnAubWF4aW11bSgwLCAxIC0geSAqIGZfeCkNCg0KeCA9IG5wLmxpbnNwYWNlKC0yLCAyLCAxMDApDQp5ID0gbnAub25lc19saWtlKHgpICAjIEFzc3VtZSBhbGwgcG9pbnRzIGJlbG9uZyB0byBjbGFzcyAxDQpsb3NzID0gaGluZ2VfbG9zcyh5LCB4KQ0KDQpwbHQucGxvdCh4LCBsb3NzLCBsYWJlbD0iSGluZ2UgTG9zcyIpDQpwbHQueGxhYmVsKCJEaXN0YW5jZSBmcm9tIE1hcmdpbiIpDQpwbHQueWxhYmVsKCJMb3NzIikNCnBsdC5sZWdlbmQoKQ0KcGx0LnRpdGxlKCJIaW5nZSBMb3NzIEZ1bmN0aW9uIikNCnBsdC5zaG93KCkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipQYXJ0IDM6IFRoZSBLZXJuZWwgVHJpY2sgQ29udGludWVkKiogDQoNClNWTXMgcGVyZm9ybSAqKm5vbmxpbmVhciBjbGFzc2lmaWNhdGlvbioqIHVzaW5nIHRoZSAqKmtlcm5lbCB0cmljayoqLA0Kd2hpY2ggaW1wbGljaXRseSBtYXBzIGRhdGEgdG8gYSBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2Ugd2hlcmUgaXQgaXMNCmxpbmVhcmx5IHNlcGFyYWJsZS4NCg0KIyMjICoqQ29tbW9uIEtlcm5lbHMqKg0KDQp8IEtlcm5lbCBUeXBlICAgICAgICAgICAgICAgICAgICAgfCBNYXRoZW1hdGljYWwgRm9ybSAgICAgICAgICAgICB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18DQp8ICoqTGluZWFyKiogICAgICAgICAgICAgICAgICAgICAgfCAkeCBcY2RvdCB5JCAgICAgICAgICAgICAgICAgICB8DQp8ICoqUG9seW5vbWlhbCoqICAgICAgICAgICAgICAgICAgfCAkKHggXGNkb3QgeSArIHIpXmQkICAgICAgICAgICB8DQp8ICoqUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIChSQkYpKiogfCAkXGV4cCgtXGdhbW1hIFx8IHggLSB5IFx8XjIpJCB8DQp8ICoqU2lnbW9pZCoqICAgICAgICAgICAgICAgICAgICAgfCAkXHRhbmgociB4IFxjZG90IHkgKyBcZXRhKSQgICB8DQoNCioqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uKipcDQpJbnN0ZWFkIG9mIGNvbXB1dGluZyBhIHRyYW5zZm9ybWF0aW9uICRccGhpKHgpJCBleHBsaWNpdGx5LCB3ZSBjb21wdXRlDQp0aGUga2VybmVsIGZ1bmN0aW9uIGRpcmVjdGx5Og0KDQokJA0KSyh4LCB5KSA9IFxwaGkoeCkgXGNkb3QgXHBoaSh5KQ0KJCQNCg0KIyMjICoqUHl0aG9uIENvZGUgZm9yIEtlcm5lbCBTVk0qKg0KDQpgYGAgcHl0aG9uDQpmcm9tIHNrbGVhcm4uc3ZtIGltcG9ydCBTVkMNCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbWFrZV9tb29ucw0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KaW1wb3J0IG51bXB5IGFzIG5wDQoNCiMgR2VuZXJhdGUgbm9ubGluZWFyIGRhdGENClgsIHkgPSBtYWtlX21vb25zKG5fc2FtcGxlcz0xMDAsIG5vaXNlPTAuMSwgcmFuZG9tX3N0YXRlPTQyKQ0KeSA9IG5wLndoZXJlKHkgPT0gMCwgLTEsIDEpICAjIENvbnZlcnQgdG8gey0xLDF9IGxhYmVscw0KDQojIFRyYWluIFNWTSB3aXRoIFJCRiBLZXJuZWwNCnN2bV9tb2RlbCA9IFNWQyhrZXJuZWw9J3JiZicsIEM9MS4wLCBnYW1tYT0wLjUpDQpzdm1fbW9kZWwuZml0KFgsIHkpDQoNCiMgUGxvdCBEZWNpc2lvbiBCb3VuZGFyeQ0KeHgsIHl5ID0gbnAubWVzaGdyaWQobnAubGluc3BhY2UoLTIsIDIsIDEwMCksIG5wLmxpbnNwYWNlKC0xLjUsIDEuNSwgMTAwKSkNClogPSBzdm1fbW9kZWwuZGVjaXNpb25fZnVuY3Rpb24obnAuY19beHgucmF2ZWwoKSwgeXkucmF2ZWwoKV0pDQpaID0gWi5yZXNoYXBlKHh4LnNoYXBlKQ0KDQpwbHQuY29udG91cmYoeHgsIHl5LCBaLCBsZXZlbHM9Wy0xLCAwLCAxXSwgYWxwaGE9MC4zLCBjb2xvcnM9WydibHVlJywgJ2JsYWNrJywgJ3JlZCddKQ0KcGx0LnNjYXR0ZXIoWFs6LCAwXSwgWFs6LCAxXSwgYz15LCBjbWFwPXBsdC5jbS5QYWlyZWQpDQpwbHQudGl0bGUoIlNWTSB3aXRoIFJCRiBLZXJuZWwiKQ0KcGx0LnNob3coKQ0KYGBgDQoNCiMjIyAqKldoeSB0aGUgS2VybmVsIFRyaWNrIFdvcmtzKioNCg0KLSAgICoqQXZvaWRzIGV4cGxpY2l0bHkgbWFwcGluZyBkYXRhIHRvIGhpZ2hlciBkaW1lbnNpb25zKiosIHdoaWNoIHdvdWxkDQogICAgYmUgY29tcHV0YXRpb25hbGx5IGV4cGVuc2l2ZS4NCi0gICAqKkNvbXB1dGVzIGRvdCBwcm9kdWN0cyBpbiB0cmFuc2Zvcm1lZCBzcGFjZSBlZmZpY2llbnRseSoqIHVzaW5nIGENCiAgICBrZXJuZWwgZnVuY3Rpb24uDQoNCiMjIyAqKlRoZSBYT1IgUHJvYmxlbSoqDQoNClRoZSAqKlhPUiBwcm9ibGVtKiogaXMgYSBjbGFzc2ljIGV4YW1wbGUgd2hlcmUgbGluZWFyIGNsYXNzaWZpZXJzIGZhaWwuDQpUaGUgKiprZXJuZWwgdHJpY2sqKiBhbGxvd3MgU1ZNcyB0byBzZXBhcmF0ZSBzdWNoIGRhdGFzZXRzLg0KDQotICAgKioyRCBzcGFjZSoqOiBYT1IgY2Fubm90IGJlIHNlcGFyYXRlZCBieSBhIHNpbmdsZSBsaW5lLg0KLSAgICoqM0Qgc3BhY2UqKjogVXNpbmcgJHogPSB4XjIgKyB5XjIkLCB0aGUgZGF0YSBiZWNvbWVzIGxpbmVhcmx5DQogICAgc2VwYXJhYmxlLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipLZXkgVGFrZWF3YXlzKioNCg0KMS4gICoqU1ZNcyBvcHRpbWl6ZSBmb3IgdGhlIG1heGltdW0gbWFyZ2luKiogYmV0d2VlbiBjbGFzc2VzLg0KMi4gICoqSGluZ2UgbG9zcyBlbnN1cmVzIGEgcm9idXN0IGNsYXNzaWZpY2F0aW9uIGJvdW5kYXJ5LioqDQozLiAgKipUaGUga2VybmVsIHRyaWNrIGFsbG93cyBub25saW5lYXIgY2xhc3NpZmljYXRpb24qKiBieSBtYXBwaW5nIGRhdGENCiAgICB0byBoaWdoZXIgZGltZW5zaW9ucy4NCjQuICAqKkRpZmZlcmVudCBrZXJuZWxzIGNhbiBiZSB1c2VkKiogYmFzZWQgb24gZGF0YXNldCBjaGFyYWN0ZXJpc3RpY3MuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKkZ1cnRoZXIgUmVhZGluZyoqDQoNCi0gICAqKkVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nKiogYnkgSGFzdGllLCBUaWJzaGlyYW5pLCBhbmQNCiAgICBGcmllZG1hbi4NCi0gICAqKlRoZSBTdGF0aXN0aWNhbCBTbGV1dGgqKiBmb3IgYWR2YW5jZWQgbWF0aGVtYXRpY2FsIGRldGFpbHMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojICoqU3R1ZHkgR3VpZGU6IFRoZSBLZXJuZWwgVHJpY2sqKg0KDQojIyAqKkludHJvZHVjdGlvbioqDQoNClRoZSBrZXJuZWwgdHJpY2sgaXMgYSBmdW5kYW1lbnRhbCBjb25jZXB0IGluIG1hY2hpbmUgbGVhcm5pbmcsDQpwYXJ0aWN1bGFybHkgaW4gc3VwcG9ydCB2ZWN0b3IgbWFjaGluZXMgKFNWTXMpIGFuZCBvdGhlciBrZXJuZWxpemVkDQpsZWFybmluZyBhbGdvcml0aG1zLiBJdCBhbGxvd3Mgbm9ubGluZWFyIHByb2JsZW1zIHRvIGJlIHRyYW5zZm9ybWVkIGludG8NCmhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZSB3aGVyZSBhIGxpbmVhciBzb2x1dGlvbiBjYW4gYmUgYXBwbGllZCB3aXRob3V0DQpleHBsaWNpdGx5IGNvbXB1dGluZyB0aGUgdHJhbnNmb3JtYXRpb24uDQoNClRoaXMgZ3VpZGUgd2lsbCBjb3ZlcjogLSBUaGUgWE9SIHByb2JsZW0gYW5kIHdoeSBsaW5lYXIgY2xhc3NpZmllcnMNCmZhaWwgLSBUaGUgY29uY2VwdCBvZiBtYXBwaW5nIGludG8gaGlnaGVyIGRpbWVuc2lvbnMgLSBNYXRoZW1hdGljYWwNCmZvcm11bGF0aW9ucyBvZiBrZXJuZWwgZnVuY3Rpb25zIC0gUHJhY3RpY2FsIGNvZGluZyBpbXBsZW1lbnRhdGlvbiB3aXRoDQpQeXRob24gKHVzaW5nIGBzY2lraXQtbGVhcm5gKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKioxLiBUaGUgWE9SIFByb2JsZW0qKg0KDQpUaGUgWE9SIChFeGNsdXNpdmUgT1IpIHByb2JsZW0gaXMgYSBjbGFzc2ljIGV4YW1wbGUgd2hlcmUgYSBzaW1wbGUNCmxpbmVhciBjbGFzc2lmaWVyIGZhaWxzLiBHaXZlbiB0d28gaW5wdXQgZmVhdHVyZXMgJHhfMSQgYW5kICR4XzIkLCB0aGUNClhPUiBmdW5jdGlvbiBvdXRwdXRzOiAtIDEgaWYgb25seSBvbmUgb2YgJHhfMSQgb3IgJHhfMiQgaXMgMSAtIDANCm90aGVyd2lzZQ0KDQpJbiBhIHR3by1kaW1lbnNpb25hbCBzcGFjZSwgdGhlcmUgaXMgbm8gc2luZ2xlIHN0cmFpZ2h0IGxpbmUgdGhhdCBjYW4NCnNlcGFyYXRlIHRoZSBjbGFzc2VzLg0KDQojIyMgKipWaXN1YWxpemluZyB0aGUgWE9SIFByb2JsZW0qKg0KDQpNYXRoZW1hdGljYWxseSwgWE9SIGZvbGxvd3M6DQoNCiQkDQpcdGV4dHtYT1J9KHhfMSwgeF8yKSA9IHhfMSBcb3BsdXMgeF8yDQokJA0KDQpUaGUgZGVjaXNpb24gYm91bmRhcnkgaXMgbm9ubGluZWFyLCBtYWtpbmcgaXQgaW1wb3NzaWJsZSB0byBjbGFzc2lmeQ0KdXNpbmcgdHJhZGl0aW9uYWwgbGluZWFyIFNWTXMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjIuIE1hcHBpbmcgdG8gYSBIaWdoZXIgRGltZW5zaW9uKioNCg0KVG8gYWRkcmVzcyB0aGlzLCB3ZSBpbnRyb2R1Y2UgYSB0cmFuc2Zvcm1hdGlvbiBmdW5jdGlvbiAkXHBoaSh4KSQgdGhhdA0KbWFwcyB0aGUgZGF0YSB0byBhIGhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZToNCg0KJCQNCnogPSB4XzFeMiArIHhfMl4yDQokJA0KDQpUaGlzIHRyYW5zZm9ybWF0aW9uIGFsbG93cyBhIGxpbmVhciBjbGFzc2lmaWVyIHRvIHdvcmsgZWZmZWN0aXZlbHkgaW4NCnRoZSBuZXcgc3BhY2UuDQoNCiMjIyAqKk1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbioqDQoNCkluc3RlYWQgb2YgZXhwbGljaXRseSB0cmFuc2Zvcm1pbmcgZGF0YSwgdGhlIGtlcm5lbCB0cmljayBlbmFibGVzIHVzIHRvDQpjb21wdXRlIGlubmVyIHByb2R1Y3RzIGluIHRoZSBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2UgZGlyZWN0bHkuDQoNCkZvciBhIHRyYW5zZm9ybWF0aW9uIGZ1bmN0aW9uICRccGhpKHgpJCwgdGhlIGtlcm5lbCBmdW5jdGlvbiBpczoNCg0KJCQNCksoeCwgeSkgPSBcbGFuZ2xlIFxwaGkoeCksIFxwaGkoeSkgXHJhbmdsZQ0KJCQNCg0KQ29tbW9uIGtlcm5lbHM6IDEuICoqTGluZWFyIEtlcm5lbDoqKiAkSyh4LCB5KSA9IHheVCB5JCAyLiAqKlBvbHlub21pYWwNCktlcm5lbDoqKiAkSyh4LCB5KSA9ICh4XlQgeSArIGMpXmQkIDMuICoqUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIChSQkYpDQpLZXJuZWw6KiogJEsoeCwgeSkgPSBcZXhwKC1cZ2FtbWEgXHx4IC0geVx8XjIpJCA0LiAqKlNpZ21vaWQgS2VybmVsOioqDQokSyh4LCB5KSA9IFx0YW5oKFxhbHBoYSB4XlQgeSArIGMpJA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKiozLiBUaGUgS2VybmVsIFRyaWNrKioNCg0KVGhlIGtlcm5lbCB0cmljayBhbGxvd3MgdXMgdG8gY29tcHV0ZSB0aGUgaW5uZXIgcHJvZHVjdCBpbiB0aGUNCnRyYW5zZm9ybWVkIHNwYWNlIHdpdGhvdXQgZXhwbGljaXRseSBwZXJmb3JtaW5nIHRoZSB0cmFuc2Zvcm1hdGlvbi4NCkluc3RlYWQgb2YgY29tcHV0aW5nICRccGhpKHgpJCwgd2UgZGlyZWN0bHkgdXNlOg0KDQokJA0KSyh4LCB5KSA9IFxwaGkoeCkgXGNkb3QgXHBoaSh5KQ0KJCQNCg0KVGhpcyByZWR1Y2VzIGNvbXB1dGF0aW9uYWwgY29zdCBhbmQgYXZvaWRzIGV4cGxpY2l0bHkgY29tcHV0aW5nDQpoaWdoLWRpbWVuc2lvbmFsIGZlYXR1cmVzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio0LiBDb2RpbmcgSW1wbGVtZW50YXRpb24gaW4gUHl0aG9uKioNCg0KTGV0J3Mgc2VlIGhvdyB0byBpbXBsZW1lbnQgU1ZNIHdpdGggZGlmZmVyZW50IGtlcm5lbHMgdXNpbmcNCmBzY2lraXQtbGVhcm5gOg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gc2tsZWFybi5zdm0gaW1wb3J0IFNWQw0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX21vb25zLCBtYWtlX2NpcmNsZXMNCg0KIyBHZW5lcmF0ZSBhIG5vbmxpbmVhciBkYXRhc2V0DQpYLCB5ID0gbWFrZV9jaXJjbGVzKG5fc2FtcGxlcz0zMDAsIG5vaXNlPTAuMDUsIGZhY3Rvcj0wLjUpDQoNCiMgVHJhaW4gYW4gU1ZNIHdpdGggZGlmZmVyZW50IGtlcm5lbHMNCmtlcm5lbHMgPSBbJ2xpbmVhcicsICdwb2x5JywgJ3JiZicsICdzaWdtb2lkJ10NCg0KcGx0LmZpZ3VyZShmaWdzaXplPSgxMiwgOCkpDQpmb3IgaSwga2VybmVsIGluIGVudW1lcmF0ZShrZXJuZWxzLCAxKToNCiAgICBtb2RlbCA9IFNWQyhrZXJuZWw9a2VybmVsLCBnYW1tYT0xKQ0KICAgIG1vZGVsLmZpdChYLCB5KQ0KICAgIA0KICAgICMgUGxvdCBkZWNpc2lvbiBib3VuZGFyeQ0KICAgIHBsdC5zdWJwbG90KDIsIDIsIGkpDQogICAgcGx0LnNjYXR0ZXIoWFs6LCAwXSwgWFs6LCAxXSwgYz15LCBjbWFwPSdjb29sd2FybScsIGVkZ2Vjb2xvcnM9J2snKQ0KICAgIA0KICAgICMgQ3JlYXRlIGEgbWVzaCBncmlkDQogICAgeF9taW4sIHhfbWF4ID0gWFs6LCAwXS5taW4oKSAtIDAuNSwgWFs6LCAwXS5tYXgoKSArIDAuNQ0KICAgIHlfbWluLCB5X21heCA9IFhbOiwgMV0ubWluKCkgLSAwLjUsIFhbOiwgMV0ubWF4KCkgKyAwLjUNCiAgICB4eCwgeXkgPSBucC5tZXNoZ3JpZChucC5saW5zcGFjZSh4X21pbiwgeF9tYXgsIDEwMCksIG5wLmxpbnNwYWNlKHlfbWluLCB5X21heCwgMTAwKSkNCiAgICBaID0gbW9kZWwucHJlZGljdChucC5jX1t4eC5yYXZlbCgpLCB5eS5yYXZlbCgpXSkNCiAgICBaID0gWi5yZXNoYXBlKHh4LnNoYXBlKQ0KICAgIA0KICAgICMgUGxvdCBjb250b3VyDQogICAgcGx0LmNvbnRvdXJmKHh4LCB5eSwgWiwgYWxwaGE9MC4zLCBjbWFwPSdjb29sd2FybScpDQogICAgcGx0LnRpdGxlKGYnU1ZNIHdpdGgge2tlcm5lbH0ga2VybmVsJykNCg0KcGx0LnRpZ2h0X2xheW91dCgpDQpwbHQuc2hvdygpDQpgYGANCg0KIyMjICoqRXhwbGFuYXRpb246KioNCg0KLSAgICoqRGF0YXNldDoqKiBXZSB1c2UgYG1ha2VfY2lyY2xlcygpYCB0byBjcmVhdGUgYSBub25saW5lYXIgZGF0YXNldC4NCi0gICAqKlNWTSBLZXJuZWxzOioqIFdlIHRlc3QgZm91ciBkaWZmZXJlbnQga2VybmVscyAoYGxpbmVhcmAsIGBwb2x5YCwNCiAgICBgcmJmYCwgYHNpZ21vaWRgKS4NCi0gICAqKlZpc3VhbGl6YXRpb246KiogV2UgcGxvdCB0aGUgZGF0YXNldCBhbmQgZGVjaXNpb24gYm91bmRhcnkuDQoNCioqUmVzdWx0czoqKiAtIFRoZSAqKmxpbmVhciBrZXJuZWwqKiBmYWlscy4gLSBUaGUgKipwb2x5bm9taWFsIGtlcm5lbCoqDQpwZXJmb3JtcyBiZXR0ZXIuIC0gVGhlICoqUkJGIGtlcm5lbCoqIHBlcmZvcm1zIGJlc3QgZm9yIG5vbmxpbmVhcg0KZGF0YS4gLSBUaGUgKipzaWdtb2lkIGtlcm5lbCoqIGJlaGF2ZXMgbGlrZSBhIG5ldXJhbCBuZXR3b3JrLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio1LiBTdW1tYXJ5ICYgS2V5IFRha2Vhd2F5cyoqDQoNCi0gICAqKlRoZSBYT1IgcHJvYmxlbSoqIGRlbW9uc3RyYXRlcyB3aHkgbGluZWFyIGNsYXNzaWZpZXJzIGZhaWwgb24NCiAgICBub25saW5lYXIgZGF0YS4NCi0gICAqKkhpZ2hlci1kaW1lbnNpb25hbCBtYXBwaW5nKiogbWFrZXMgaXQgcG9zc2libGUgdG8gc2VwYXJhdGUgZGF0YQ0KICAgIHVzaW5nIGxpbmVhciBjbGFzc2lmaWVycy4NCi0gICAqKlRoZSBrZXJuZWwgdHJpY2sqKiBhbGxvd3MgU1ZNcyB0byB3b3JrIGVmZmljaWVudGx5IGJ5IGNvbXB1dGluZw0KICAgIGRvdCBwcm9kdWN0cyBpbiBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlIHdpdGhvdXQgZXhwbGljaXRseQ0KICAgIHRyYW5zZm9ybWluZyBkYXRhLg0KLSAgICoqUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIChSQkYpIGFuZCBQb2x5bm9taWFsIEtlcm5lbHMqKiBhcmUgY29tbW9ubHkNCiAgICB1c2VkIHRvIGhhbmRsZSBub25saW5lYXJpdHkuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKkZ1cnRoZXIgUmVhZGluZyAmIFJlZmVyZW5jZXMqKg0KDQotICAgW0VsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nLCBTZWN0aW9uIDEyLjM6IFN1cHBvcnQgVmVjdG9yDQogICAgTWFjaGluZXMgYW5kIEtlcm5lbHNdDQotICAgW1JhZGlhbCBCYXNpcyBGdW5jdGlvbiBOZXR3b3JrcyAmIEtlcm5lbCBNZXRob2RzXQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KSGVyZeKAmXMgYW4gaW4tZGVwdGggKipzdHVkeSBndWlkZSoqIG9uICoqU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTXMpKioNCmV4cGFuZGluZyB1cG9uICoqUGFydCA0Kiogb2YgdGhlIGxlY3R1cmUgc2VyaWVzLCBpbmNvcnBvcmF0aW5nDQoqKm1hdGhlbWF0aWNhbCBjb25jZXB0cywgY29kaW5nIGV4YW1wbGVzLCBhbmQgdGhlb3JldGljYWwNCmV4cGxhbmF0aW9ucyoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKlN0dWR5IEd1aWRlOiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykqKg0KDQojIyMgKioxLiBJbnRyb2R1Y3Rpb24gdG8gU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMqKg0KDQpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykgYXJlIHN1cGVydmlzZWQgbGVhcm5pbmcgbW9kZWxzIHVzZWQgZm9yDQoqKmNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRhc2tzKiouIFRoZSBrZXkgaWRlYSBiZWhpbmQgU1ZNIGlzIHRvDQpmaW5kIGFuIG9wdGltYWwgaHlwZXJwbGFuZSB0aGF0IG1heGltaXplcyB0aGUgKiptYXJnaW4qKiBiZXR3ZWVuIGNsYXNzZXMNCndoaWxlIG1pbmltaXppbmcgY2xhc3NpZmljYXRpb24gZXJyb3JzLg0KDQpTVk0gY2FuIGJlIGRpdmlkZWQgaW50bzogLSAqKkhhcmQgTWFyZ2luIFNWTSoqIChmb3IgcGVyZmVjdGx5IHNlcGFyYWJsZQ0KZGF0YSkgLSAqKlNvZnQgTWFyZ2luIFNWTSoqIChmb3Igb3ZlcmxhcHBpbmcgY2xhc3NlcywgdXNpbmcgc2xhY2sNCnZhcmlhYmxlcykgLSAqKktlcm5lbGl6ZWQgU1ZNKiogKGZvciBub25saW5lYXIgY2xhc3NpZmljYXRpb24pDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKioyLiBUaGUgT3B0aW1pemF0aW9uIFByb2JsZW0qKg0KDQpTVk0gYWltcyB0byBmaW5kIGEgaHlwZXJwbGFuZSB0aGF0IGJlc3Qgc2VwYXJhdGVzIHR3byBjbGFzc2VzLiBHaXZlbiBhDQpkYXRhc2V0IHdpdGggZmVhdHVyZSB2ZWN0b3JzICR4X2kkIGFuZCBsYWJlbHMgJHlfaSBcaW4gXHstMSwgMVx9JCwgdGhlDQpkZWNpc2lvbiBib3VuZGFyeSBpcyBkZWZpbmVkIGJ5Og0KDQokJA0KZih4KSA9IHdeVCB4ICsgYg0KJCQNCg0Kd2hlcmUgJHckIGlzIHRoZSB3ZWlnaHQgdmVjdG9yIGFuZCAkYiQgaXMgdGhlIGJpYXMuDQoNCiMjIyMgKipIYXJkIE1hcmdpbiBTVk0gKExpbmVhcmx5IFNlcGFyYWJsZSBDYXNlKSoqDQoNCkZvciBwZXJmZWN0bHkgc2VwYXJhYmxlIGNsYXNzZXMsIHdlIGZpbmQgdGhlIGh5cGVycGxhbmUgdGhhdCBtYXhpbWl6ZXMNCnRoZSBtYXJnaW46DQoNCiQkDQpcbWluX3t3LCBifSBcZnJhY3sxfXsyfSB8fHd8fF4yDQokJA0KDQpzdWJqZWN0IHRvOg0KDQokJA0KeV9pICh3XlQgeF9pICsgYikgXGdlcSAxLCBccXVhZCBcZm9yYWxsIGkNCiQkDQoNCndoZXJlICR8fHd8fF57LTF9JCByZXByZXNlbnRzIHRoZSBtYXJnaW4gd2lkdGguDQoNCiMjIyMgKipTb2Z0IE1hcmdpbiBTVk0gKE5vbi1TZXBhcmFibGUgQ2FzZSkqKg0KDQpGb3IgY2FzZXMgd2hlcmUgdGhlIGRhdGEgaXMgbm90IHBlcmZlY3RseSBzZXBhcmFibGUsIHdlIGludHJvZHVjZQ0KKipzbGFjayB2YXJpYWJsZXMqKiAkXHhpX2kkIHRvIGFsbG93IG1pc2NsYXNzaWZpY2F0aW9uczoNCg0KJCQNClxtaW5fe3csIGIsIFx4aX0gXGZyYWN7MX17Mn0gfHx3fHxeMiArIEMgXHN1bV97aT0xfV57Tn0gXHhpX2kNCiQkDQoNCnN1YmplY3QgdG86DQoNCiQkDQp5X2kgKHdeVCB4X2kgKyBiKSBcZ2VxIDEgLSBceGlfaSwgXHF1YWQgXHhpX2kgXGdlcSAwLCBccXVhZCBcZm9yYWxsIGkNCiQkDQoNCndoZXJlICRDJCBjb250cm9scyB0aGUgdHJhZGUtb2ZmIGJldHdlZW4gbWF4aW1pemluZyB0aGUgbWFyZ2luIGFuZA0KbWluaW1pemluZyBjbGFzc2lmaWNhdGlvbiBlcnJvci4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjMuIFRoZSBLZXJuZWwgVHJpY2sqKg0KDQpTVk1zIGNhbiBiZSBleHRlbmRlZCB0byBoYW5kbGUgKipub25saW5lYXIgY2xhc3NpZmljYXRpb24gcHJvYmxlbXMqKg0KdXNpbmcgdGhlICoqS2VybmVsIFRyaWNrKiosIHdoaWNoIG1hcHMgZGF0YSBpbnRvIGEgKipoaWdoZXItZGltZW5zaW9uYWwNCnNwYWNlKiogd2hlcmUgYSBsaW5lYXIgYm91bmRhcnkgY2FuIGJlIGFwcGxpZWQuDQoNClRoZSBrZXJuZWwgZnVuY3Rpb24gJEsoeF9pLCB4X2opJCBjb21wdXRlcyB0aGUgZG90IHByb2R1Y3QgaW4gdGhpcw0KaGlnaC1kaW1lbnNpb25hbCBzcGFjZToNCg0KJCQNCksoeF9pLCB4X2opID0gXHBoaSh4X2kpIFxjZG90IFxwaGkoeF9qKQ0KJCQNCg0KQ29tbW9uIGtlcm5lbCBmdW5jdGlvbnM6IDEuICoqTGluZWFyIEtlcm5lbDoqKiAkSyh4LCB5KSA9IHggXGNkb3QgeSQgMi4NCioqUG9seW5vbWlhbCBLZXJuZWw6KiogJEsoeCwgeSkgPSAoeCBcY2RvdCB5ICsgYyleZCQgMy4gKipSYWRpYWwgQmFzaXMNCkZ1bmN0aW9uIChSQkYpIEtlcm5lbDoqKiAkSyh4LCB5KSA9IGVeey1cZ2FtbWEgfHx4IC0geXx8XjJ9JCA0Lg0KKipTaWdtb2lkIEtlcm5lbDoqKiAkSyh4LCB5KSA9IFx0YW5oKCBcYWxwaGEgeCBcY2RvdCB5ICsgYykkDQoNClRoZXNlIGtlcm5lbHMgYWxsb3cgU1ZNIHRvICoqY2xhc3NpZnkgbm9ubGluZWFyIGRhdGEqKiB3aXRob3V0DQpleHBsaWNpdGx5IHRyYW5zZm9ybWluZyBmZWF0dXJlcyBpbnRvIGhpZ2hlciBkaW1lbnNpb25zLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqNC4gTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uIG9mIHRoZSBLZXJuZWxpemVkIFNWTSoqDQoNClVzaW5nIHRoZSAqKkxhZ3JhbmdlIGR1YWwgZm9ybXVsYXRpb24qKiwgdGhlIFNWTSBvcHRpbWl6YXRpb24gcHJvYmxlbQ0KY2FuIGJlIHJld3JpdHRlbiBpbiB0ZXJtcyBvZiBMYWdyYW5nZSBtdWx0aXBsaWVycyAkXGFscGhhX2kkOg0KDQokJA0KXG1heF97XGFscGhhfSBcc3VtX3tpPTF9XntOfSBcYWxwaGFfaSAtIFxmcmFjezF9ezJ9IFxzdW1fe2k9MX1ee059IFxzdW1fe2o9MX1ee059IFxhbHBoYV9pIFxhbHBoYV9qIHlfaSB5X2ogSyh4X2ksIHhfaikNCiQkDQoNCnN1YmplY3QgdG86DQoNCiQkDQowIFxsZXEgXGFscGhhX2kgXGxlcSBDLCBccXVhZCBcc3VtX3tpPTF9XntOfSBcYWxwaGFfaSB5X2kgPSAwDQokJA0KDQp3aGVyZSBvbmx5ICoqc3VwcG9ydCB2ZWN0b3JzKiogY29udHJpYnV0ZSB0byB0aGUgZGVjaXNpb24gZnVuY3Rpb24uDQoNClRoZSBmaW5hbCBkZWNpc2lvbiBmdW5jdGlvbiBpczoNCg0KJCQNCmYoeCkgPSBcc3VtX3tpPTF9XntOfSBcYWxwaGFfaSB5X2kgSyh4X2ksIHgpICsgYg0KJCQNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjUuIFN1cHBvcnQgVmVjdG9yIFJlZ3Jlc3Npb24gKFNWUikqKg0KDQpTVk1zIGNhbiBhbHNvIGJlIGFwcGxpZWQgdG8gKipyZWdyZXNzaW9uIHByb2JsZW1zKiogYnkgaW50cm9kdWNpbmcNCioqZXBzaWxvbi1pbnNlbnNpdGl2ZSBsb3NzKio6DQoNCiQkDQpcbWluX3t3LCBiLCBceGksIFx4aV4qfSBcZnJhY3sxfXsyfSB8fHd8fF4yICsgQyBcc3VtX3tpPTF9XntOfSAoXHhpX2kgKyBceGlfaV4qKQ0KJCQNCg0Kc3ViamVjdCB0bzoNCg0KJCQNCnlfaSAtICh3XlQgeF9pICsgYikgXGxlcSBcZXBzaWxvbiArIFx4aV9pDQokJA0KDQokJA0KKHdeVCB4X2kgKyBiKSAtIHlfaSBcbGVxIFxlcHNpbG9uICsgXHhpX2leKg0KJCQNCg0Kd2hlcmUgJFxlcHNpbG9uJCBkZWZpbmVzIGEgbWFyZ2luIHdpdGhpbiB3aGljaCBubyBwZW5hbHR5IGlzIGdpdmVuIGZvcg0KZXJyb3JzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqNi4gUHl0aG9uIENvZGUgRXhhbXBsZXMqKg0KDQojIyMjICoqTGluZWFyIFNWTSBDbGFzc2lmaWNhdGlvbioqDQoNCmBgYCBweXRob24NCmZyb20gc2tsZWFybi5zdm0gaW1wb3J0IFNWQw0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCg0KIyBHZW5lcmF0ZSBkYXRhc2V0DQpYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX3NhbXBsZXM9MTAwLCBuX2ZlYXR1cmVzPTIsIG5fY2xhc3Nlcz0yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgU3BsaXQgZGF0YXNldA0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjMsIHJhbmRvbV9zdGF0ZT00MikNCg0KIyBUcmFpbiBTVk0gY2xhc3NpZmllcg0Kc3ZtID0gU1ZDKGtlcm5lbD0nbGluZWFyJywgQz0xLjApDQpzdm0uZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgUHJlZGljdCBhbmQgZXZhbHVhdGUNCnlfcHJlZCA9IHN2bS5wcmVkaWN0KFhfdGVzdCkNCnByaW50KCJBY2N1cmFjeToiLCBhY2N1cmFjeV9zY29yZSh5X3Rlc3QsIHlfcHJlZCkpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyMgKipLZXJuZWxpemVkIFNWTSAoUkJGIEtlcm5lbCkqKg0KDQpgYGAgcHl0aG9uDQpzdm1fcmJmID0gU1ZDKGtlcm5lbD0ncmJmJywgQz0xLjAsIGdhbW1hPTAuNSkNCnN2bV9yYmYuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCnlfcHJlZF9yYmYgPSBzdm1fcmJmLnByZWRpY3QoWF90ZXN0KQ0KcHJpbnQoIlJCRiBLZXJuZWwgQWNjdXJhY3k6IiwgYWNjdXJhY3lfc2NvcmUoeV90ZXN0LCB5X3ByZWRfcmJmKSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIyAqKlN1cHBvcnQgVmVjdG9yIFJlZ3Jlc3Npb24gKFNWUikqKg0KDQpgYGAgcHl0aG9uDQpmcm9tIHNrbGVhcm4uc3ZtIGltcG9ydCBTVlINCmltcG9ydCBudW1weSBhcyBucA0KDQojIEdlbmVyYXRlIGRhdGENClggPSBucC5saW5zcGFjZSgtMywgMywgMTAwKS5yZXNoYXBlKC0xLCAxKQ0KeSA9IG5wLnNpbmMoWCkucmF2ZWwoKQ0KDQojIFRyYWluIFNWUiBtb2RlbA0Kc3ZyID0gU1ZSKGtlcm5lbD0ncmJmJywgQz0xLjAsIGVwc2lsb249MC4xKQ0Kc3ZyLmZpdChYLCB5KQ0KDQojIFByZWRpY3QNCnlfcHJlZCA9IHN2ci5wcmVkaWN0KFgpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjcuIEFwcGxpY2F0aW9ucyBvZiBTVk0qKg0KDQotICAgKipJbWFnZSBDbGFzc2lmaWNhdGlvbioqOiBTVk0gaXMgd2lkZWx5IHVzZWQgaW4gKipoYW5kd3JpdHRlbiBkaWdpdA0KICAgIHJlY29nbml0aW9uKiogKGUuZy4sIE1OSVNUIGRhdGFzZXQpLg0KLSAgICoqQmlvaW5mb3JtYXRpY3MqKjogU1ZNcyBhcmUgdXNlZCBmb3IgKipwcm90ZWluIHN0cnVjdHVyZQ0KICAgIHByZWRpY3Rpb24qKiBhbmQgKipnZW5lIGNsYXNzaWZpY2F0aW9uKiouDQotICAgKipUZXh0IENsYXNzaWZpY2F0aW9uKio6IFNwYW0gZGV0ZWN0aW9uIGFuZCBzZW50aW1lbnQgYW5hbHlzaXMuDQotICAgKipBbm9tYWx5IERldGVjdGlvbioqOiBGcmF1ZCBkZXRlY3Rpb24gYW5kIG91dGxpZXIgZGV0ZWN0aW9uIGluDQogICAgY3liZXJzZWN1cml0eS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKjguIFN1bW1hcnkqKg0KDQotICAgKipTVk1zIGZpbmQgdGhlIG9wdGltYWwgaHlwZXJwbGFuZSoqIHRoYXQgbWF4aW1pemVzIHRoZSBtYXJnaW4uDQotICAgKipTb2Z0IE1hcmdpbiBTVk0qKiBhbGxvd3MgbWlzY2xhc3NpZmljYXRpb24gdG8gaGFuZGxlICoqb3ZlcmxhcHBpbmcNCiAgICBjbGFzc2VzKiouDQotICAgKipLZXJuZWwgdHJpY2sqKiBhbGxvd3Mgbm9ubGluZWFyIGNsYXNzaWZpY2F0aW9uICoqd2l0aG91dCBleHBsaWNpdA0KICAgIGZlYXR1cmUgdHJhbnNmb3JtYXRpb24qKi4NCi0gICAqKlN1cHBvcnQgVmVjdG9yIFJlZ3Jlc3Npb24gKFNWUikqKiBleHRlbmRzIFNWTXMgdG8gKipjb250aW51b3VzDQogICAgZGF0YSoqLg0KLSAgICoqU1ZNcyBwZXJmb3JtIHdlbGwgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMqKiBidXQgY2FuIGJlIHNsb3cgZm9yDQogICAgbGFyZ2UgZGF0YXNldHMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdHVkeSBHdWlkZTogU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTXMpIGFuZCB0aGUgS2VybmVsIFRyaWNrKioNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqMS4gSW50cm9kdWN0aW9uIHRvIHRoZSBLZXJuZWwgVHJpY2sqKg0KDQojIyMgKipXaGF0IGlzIHRoZSBLZXJuZWwgVHJpY2s/KioNCg0KLSAgIFRoZSBrZXJuZWwgdHJpY2sgaXMgYSB0ZWNobmlxdWUgdXNlZCBpbiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcw0KICAgIChTVk1zKSB0byBoYW5kbGUgbm9uLWxpbmVhcmx5IHNlcGFyYWJsZSBkYXRhLg0KLSAgIEluc3RlYWQgb2YgZXhwbGljaXRseSBtYXBwaW5nIGRhdGEgdG8gYSBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2UsDQogICAgdGhlIGtlcm5lbCB0cmljayBjb21wdXRlcyB0aGUgZG90IHByb2R1Y3QgaW4gdGhhdCBzcGFjZSBkaXJlY3RseSwNCiAgICBhdm9pZGluZyB0aGUgY29tcHV0YXRpb25hbCBidXJkZW4gb2YgdHJhbnNmb3JtYXRpb24uDQoNCiMjIyAqKldoeSBVc2UgdGhlIEtlcm5lbCBUcmljaz8qKg0KDQotICAgTWFueSBkYXRhc2V0cywgbGlrZSB0aGUgWE9SIHByb2JsZW0sIGFyZSBub3QgbGluZWFybHkgc2VwYXJhYmxlIGluDQogICAgdGhlaXIgb3JpZ2luYWwgZmVhdHVyZSBzcGFjZS4NCi0gICBCeSB1c2luZyB0aGUga2VybmVsIHRyaWNrLCB3ZSBjYW4gbWFwIHRoZSBkYXRhIGludG8gYQ0KICAgIGhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZSB3aGVyZSBpdCBiZWNvbWVzIGxpbmVhcmx5IHNlcGFyYWJsZS4NCi0gICBUaGlzIHRyYW5zZm9ybWF0aW9uIGlzIGRvbmUgaW1wbGljaXRseSwgbWVhbmluZyB3ZSBuZXZlciBoYXZlIHRvDQogICAgY29tcHV0ZSB0aGUgbmV3IGNvb3JkaW5hdGVzIGV4cGxpY2l0bHkuDQoNCiMjIyAqKlR5cGVzIG9mIEtlcm5lbHMqKg0KDQpDb21tb24ga2VybmVscyB1c2VkIGluIFNWTXM6IFx8IEtlcm5lbCBcfCBNYXRoZW1hdGljYWwgRm9ybSBcfA0KXHwtLS0tLS0tLVx8LS0tLS0tLS0tLS0tLS0tLS0tXHwgXHwgTGluZWFyIFx8ICRLKHgsIHkpID0geCBcY2RvdCB5JCBcfA0KXHwgUG9seW5vbWlhbCBcfCAkSyh4LCB5KSA9IChyIFxjZG90IHggXGNkb3QgeSArIGMpXmQkIFx8IFx8IFJhZGlhbA0KQmFzaXMgRnVuY3Rpb24gKFJCRikgXHwgJEsoeCwgeSkgPSBcZXhwKC1cZ2FtbWEgfHx4IC0geXx8XjIpJCBcfCBcfA0KU2lnbW9pZCBcfCAkSyh4LCB5KSA9IFx0YW5oKHIgXGNkb3QgeCBcY2RvdCB5ICsgYykkIFx8DQoNCiMjIyAqKk1hdGhlbWF0aWNhbCBFeHBsYW5hdGlvbioqDQoNCi0gICBTdXBwb3NlIHdlIG1hcCBkYXRhIGZyb20gMkQgc3BhY2UgdG8gYSAzRCBzcGFjZSB1c2luZzogJCQNCiAgICB6ID0geF4yICsgeV4yDQogICAgJCQNCi0gICBJbnN0ZWFkIG9mIGNvbXB1dGluZyBhbGwgbmV3IGZlYXR1cmUgdmFsdWVzLCB0aGUga2VybmVsIHRyaWNrIGFsbG93cw0KICAgIHVzIHRvIHVzZSBhIGZ1bmN0aW9uICRLKHgsIHkpJCB0byBjb21wdXRlIGRvdCBwcm9kdWN0cyBpbiB0aGlzIHNwYWNlDQogICAgZGlyZWN0bHkuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjIuIFRoZSBYT1IgUHJvYmxlbSBhbmQgUHJvamVjdGlvbiBpbnRvIEhpZ2hlciBEaW1lbnNpb25zKioNCg0KIyMjICoqVW5kZXJzdGFuZGluZyB0aGUgWE9SIFByb2JsZW0qKg0KDQotICAgVGhlIFhPUiBwcm9ibGVtIGNhbm5vdCBiZSBzb2x2ZWQgdXNpbmcgYSBzaW1wbGUgbGluZWFyIGRlY2lzaW9uDQogICAgYm91bmRhcnkuDQotICAgQSBoeXBlcnBsYW5lIGNhbm5vdCBzZXBhcmF0ZSB0aGUgcG9pbnRzIGFzIHRoZXkgYXJlIGRpc3RyaWJ1dGVkIGluDQogICAgYW4gaW50ZXJsZWF2ZWQgbWFubmVyLg0KLSAgIFRoZSBrZXJuZWwgdHJpY2sgaGVscHMgYnkgcHJvamVjdGluZyB0aGUgZGF0YSBpbnRvIGENCiAgICBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2Ugd2hlcmUgc2VwYXJhdGlvbiBiZWNvbWVzIHBvc3NpYmxlLg0KDQojIyMgKipWaXN1YWxpemF0aW9uKioNCg0KLSAgIERhdGEgaW4gb3JpZ2luYWwgc3BhY2U6IFRoZSBYT1IgcHJvYmxlbSBhcHBlYXJzIGluc2VwYXJhYmxlLg0KLSAgIERhdGEgaW4gdHJhbnNmb3JtZWQgc3BhY2U6IEEgbmV3IGZlYXR1cmUgKGUuZy4sICR6ID0geF4yICsgeV4yJCkNCiAgICBhbGxvd3MgYSBsaW5lYXIgc2VwYXJhdG9yIHRvIGJlIHVzZWQuDQoNCiMjIyAqKlB5dGhvbiBFeGFtcGxlOiBUcmFuc2Zvcm1pbmcgWE9SIERhdGEqKg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gc2tsZWFybi5zdm0gaW1wb3J0IFNWQw0KDQojIEdlbmVyYXRlIFhPUiBkYXRhc2V0DQpYID0gbnAuYXJyYXkoW1swLDBdLCBbMCwxXSwgWzEsMF0sIFsxLDFdXSkNCnkgPSBucC5hcnJheShbMCwgMSwgMSwgMF0pICAjIFhPUiBsYWJlbHMNCg0KIyBGaXQgYW4gU1ZNIHdpdGggUkJGIEtlcm5lbA0Kc3ZtX21vZGVsID0gU1ZDKGtlcm5lbD0ncmJmJywgQz0xLCBnYW1tYT0nYXV0bycpDQpzdm1fbW9kZWwuZml0KFgsIHkpDQoNCiMgUGxvdCBkZWNpc2lvbiBib3VuZGFyeQ0KeHgsIHl5ID0gbnAubWVzaGdyaWQobnAubGluc3BhY2UoLTEsIDIsIDEwMCksIG5wLmxpbnNwYWNlKC0xLCAyLCAxMDApKQ0KWiA9IHN2bV9tb2RlbC5wcmVkaWN0KG5wLmNfW3h4LnJhdmVsKCksIHl5LnJhdmVsKCldKQ0KWiA9IFoucmVzaGFwZSh4eC5zaGFwZSkNCg0KcGx0LmNvbnRvdXJmKHh4LCB5eSwgWiwgYWxwaGE9MC4zKQ0KcGx0LnNjYXR0ZXIoWFs6LCAwXSwgWFs6LCAxXSwgYz15LCBlZGdlY29sb3JzPSdrJykNCnBsdC50aXRsZSgiU1ZNIERlY2lzaW9uIEJvdW5kYXJ5IHVzaW5nIFJCRiBLZXJuZWwgZm9yIFhPUiBQcm9ibGVtIikNCnBsdC5zaG93KCkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKiozLiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNKSoqDQoNCiMjIyAqKldoYXQgaXMgYW4gU1ZNPyoqDQoNCi0gICBTVk0gaXMgYSBzdXBlcnZpc2VkIGxlYXJuaW5nIGFsZ29yaXRobSB1c2VkIGZvciBjbGFzc2lmaWNhdGlvbiBhbmQNCiAgICByZWdyZXNzaW9uLg0KLSAgIEl0IGZpbmRzIHRoZSBiZXN0IGh5cGVycGxhbmUgdGhhdCBtYXhpbWl6ZXMgdGhlIG1hcmdpbiBiZXR3ZWVuIHR3bw0KICAgIGNsYXNzZXMuDQoNCiMjIyAqKktleSBDb25jZXB0cyoqDQoNCi0gICAqKlN1cHBvcnQgVmVjdG9ycyoqOiBEYXRhIHBvaW50cyB0aGF0IGFyZSBjbG9zZXN0IHRvIHRoZSBkZWNpc2lvbg0KICAgIGJvdW5kYXJ5Lg0KLSAgICoqTWFyZ2luKio6IFRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBkZWNpc2lvbiBib3VuZGFyeSBhbmQgdGhlDQogICAgY2xvc2VzdCBzdXBwb3J0IHZlY3RvcnMuDQotICAgKipIeXBlcnBsYW5lKio6IEEgZGVjaXNpb24gYm91bmRhcnkgaW4gYW4gTi1kaW1lbnNpb25hbCBzcGFjZS4NCg0KIyMjICoqTWF0aGVtYXRpY2FsIEZvcm11bGF0aW9uKioNCg0KU1ZNIHNvbHZlcyB0aGUgZm9sbG93aW5nIG9wdGltaXphdGlvbiBwcm9ibGVtOiAkJA0KXG1pbl97XG1hdGhiZnt3fSwgYn0gXGZyYWN7MX17Mn0gfHxcbWF0aGJme3d9fHxeMg0KJCQgc3ViamVjdCB0bzogJCQNCnlfaSAoXG1hdGhiZnt3fSBcY2RvdCB4X2kgKyBiKSBcZ2VxIDEsIFxxdWFkIFxmb3JhbGwgaQ0KJCQNCg0KIyMjICoqUmVndWxhcml6YXRpb24gUGFyYW1ldGVyIChDKSoqDQoNCi0gICBDb250cm9scyB0aGUgdHJhZGUtb2ZmIGJldHdlZW4gbWF4aW1pemluZyB0aGUgbWFyZ2luIGFuZCBtaW5pbWl6aW5nDQogICAgY2xhc3NpZmljYXRpb24gZXJyb3IuDQotICAgQSAqKmhpZ2ggQyoqIGxlYWRzIHRvICoqbGVzcyByZWd1bGFyaXphdGlvbioqIChzbWFsbGVyIG1hcmdpbiwNCiAgICBiZXR0ZXIgZml0IHRvIHRyYWluaW5nIGRhdGEpLg0KLSAgIEEgKipsb3cgQyoqIGxlYWRzIHRvICoqbW9yZSByZWd1bGFyaXphdGlvbioqIChsYXJnZXIgbWFyZ2luLA0KICAgIHBvdGVudGlhbGx5IGJldHRlciBnZW5lcmFsaXphdGlvbikuDQoNCiMjIyAqKlB5dGhvbiBFeGFtcGxlOiBMaW5lYXIgU1ZNKioNCg0KYGBgIHB5dGhvbg0KZnJvbSBza2xlYXJuIGltcG9ydCBkYXRhc2V0cw0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KZnJvbSBza2xlYXJuLnN2bSBpbXBvcnQgU1ZDDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCg0KIyBMb2FkIGRhdGFzZXQNCmlyaXMgPSBkYXRhc2V0cy5sb2FkX2lyaXMoKQ0KWCA9IGlyaXMuZGF0YVs6LCA6Ml0gICMgVXNpbmcgb25seSB0d28gZmVhdHVyZXMNCnkgPSBpcmlzLnRhcmdldA0KDQojIFNwbGl0IGRhdGFzZXQNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgVHJhaW4gU1ZNIHdpdGggTGluZWFyIEtlcm5lbA0Kc3ZtID0gU1ZDKGtlcm5lbD0nbGluZWFyJywgQz0xKQ0Kc3ZtLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KDQojIFByZWRpY3Rpb25zDQp5X3ByZWQgPSBzdm0ucHJlZGljdChYX3Rlc3QpDQoNCiMgQWNjdXJhY3kNCnByaW50KCJBY2N1cmFjeToiLCBhY2N1cmFjeV9zY29yZSh5X3Rlc3QsIHlfcHJlZCkpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNC4gVHVuaW5nIFNWTSB3aXRoIENyb3NzLVZhbGlkYXRpb24qKg0KDQojIyMgKipXaHkgVXNlIENyb3NzLVZhbGlkYXRpb24/KioNCg0KLSAgIEhlbHBzIGRldGVybWluZSB0aGUgb3B0aW1hbCBoeXBlcnBhcmFtZXRlcnMgKEMsIGtlcm5lbCB0eXBlLCBnYW1tYSwNCiAgICBldGMuKS4NCi0gICBQcmV2ZW50cyBvdmVyZml0dGluZyBieSBldmFsdWF0aW5nIG1vZGVsIHBlcmZvcm1hbmNlIG9uIHVuc2VlbiBkYXRhLg0KDQojIyMgKipDcm9zcy1WYWxpZGF0aW9uIGluIFNWTSoqDQoNCmBgYCBweXRob24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IGNyb3NzX3ZhbF9zY29yZQ0KDQojIFBlcmZvcm0gY3Jvc3MtdmFsaWRhdGlvbg0Kc2NvcmVzID0gY3Jvc3NfdmFsX3Njb3JlKHN2bSwgWCwgeSwgY3Y9NSwgc2NvcmluZz0nYWNjdXJhY3knKQ0KDQpwcmludCgiQ3Jvc3MtVmFsaWRhdGlvbiBBY2N1cmFjeSBTY29yZXM6Iiwgc2NvcmVzKQ0KcHJpbnQoIk1lYW4gQWNjdXJhY3k6Iiwgc2NvcmVzLm1lYW4oKSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio1LiBDb21wYXJpbmcgRGlmZmVyZW50IEtlcm5lbHMgaW4gU1ZNKioNCg0KIyMjICoqRWZmZWN0IG9mIERpZmZlcmVudCBLZXJuZWxzKioNCg0KLSAgICoqTGluZWFyIEtlcm5lbCoqOiBCZXN0IGZvciBsaW5lYXJseSBzZXBhcmFibGUgZGF0YS4NCi0gICAqKlBvbHlub21pYWwgS2VybmVsKio6IENhcHR1cmVzIGNvbXBsZXggZGVjaXNpb24gYm91bmRhcmllcy4NCi0gICAqKlJCRiBLZXJuZWwqKjogSGFuZGxlcyBub24tbGluZWFyaXR5IGVmZmljaWVudGx5Lg0KLSAgICoqU2lnbW9pZCBLZXJuZWwqKjogU2ltaWxhciB0byBuZXVyYWwgbmV0d29ya3MuDQoNCiMjIyAqKlB5dGhvbiBFeGFtcGxlOiBDb21wYXJpbmcgS2VybmVscyoqDQoNCmBgYCBweXRob24NCmtlcm5lbHMgPSBbJ2xpbmVhcicsICdwb2x5JywgJ3JiZicsICdzaWdtb2lkJ10NCg0KZm9yIGtlcm5lbCBpbiBrZXJuZWxzOg0KICAgIHN2bSA9IFNWQyhrZXJuZWw9a2VybmVsLCBDPTEsIGdhbW1hPSdzY2FsZScpDQogICAgc2NvcmVzID0gY3Jvc3NfdmFsX3Njb3JlKHN2bSwgWCwgeSwgY3Y9NSwgc2NvcmluZz0nYWNjdXJhY3knKQ0KICAgIHByaW50KGYiS2VybmVsOiB7a2VybmVsfSwgTWVhbiBBY2N1cmFjeToge3Njb3Jlcy5tZWFuKCk6LjRmfSIpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNi4gU3VtbWFyeSoqDQoNCiMjIyAqKktleSBUYWtlYXdheXMqKg0KDQotICAgKipUaGUgS2VybmVsIFRyaWNrKiogYWxsb3dzIFNWTXMgdG8gY2xhc3NpZnkgbm9ubGluZWFyIGRhdGEgd2l0aG91dA0KICAgIGV4cGxpY2l0bHkgbWFwcGluZyBpdCBpbnRvIGEgaGlnaGVyLWRpbWVuc2lvbmFsIHNwYWNlLg0KLSAgICoqU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMqKiBmaW5kIHRoZSBiZXN0IGh5cGVycGxhbmUgYnkgbWF4aW1pemluZw0KICAgIHRoZSBtYXJnaW4gYmV0d2VlbiBjbGFzc2VzLg0KLSAgICoqUmVndWxhcml6YXRpb24gUGFyYW1ldGVyIChDKSoqIGNvbnRyb2xzIHRoZSB0cmFkZS1vZmYgYmV0d2Vlbg0KICAgIG1hcmdpbiBzaXplIGFuZCBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeS4NCi0gICAqKkNyb3NzLVZhbGlkYXRpb24qKiBpcyBjcnVjaWFsIGZvciB0dW5pbmcgaHlwZXJwYXJhbWV0ZXJzIGFuZA0KICAgIGVuc3VyaW5nIGdvb2QgZ2VuZXJhbGl6YXRpb24uDQoNCiMjIyAqKlN0dWR5IFF1ZXN0aW9ucyoqDQoNCjEuICBXaGF0IGlzIHRoZSBrZXJuZWwgdHJpY2ssIGFuZCB3aHkgaXMgaXQgdXNlZnVsPw0KMi4gIEhvdyBkb2VzIFNWTSBkZXRlcm1pbmUgdGhlIG9wdGltYWwgZGVjaXNpb24gYm91bmRhcnk/DQozLiAgV2hhdCBpcyB0aGUgZWZmZWN0IG9mIGluY3JlYXNpbmcgb3IgZGVjcmVhc2luZyB0aGUgcmVndWxhcml6YXRpb24NCiAgICBwYXJhbWV0ZXIgJEMkPw0KNC4gIEhvdyBkbyBkaWZmZXJlbnQga2VybmVscyBhZmZlY3QgdGhlIHBlcmZvcm1hbmNlIG9mIFNWTT8NCjUuICBXaGF0IGFyZSB0aGUgYWR2YW50YWdlcyBhbmQgZGlzYWR2YW50YWdlcyBvZiB1c2luZyBhbiBSQkYga2VybmVsDQogICAgb3ZlciBhIGxpbmVhciBrZXJuZWw/DQoNCiMjIyAqKkZ1cnRoZXIgUmVhZGluZyoqDQoNCi0gICAqKiJUaGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmciKiogYnkgSGFzdGllLCBUaWJzaGlyYW5pLA0KICAgIGFuZCBGcmllZG1hbi4NCi0gICAqKlNjaWtpdC1sZWFybiBkb2N1bWVudGF0aW9uIG9uIFNWTXMqKjoNCiAgICA8aHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9tb2R1bGVzL3N2bS5odG1sPg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIEtleSBUYWtlYXdheXMgb24gU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTSkgU2NhbGluZyBhbmQgTGltaXRhdGlvbnM6DQoNCjEuICAqKlNWTXMgU2NhbGUgUG9vcmx5IHRvIExhcmdlIERhdGFzZXRzKioNCiAgICAtICAgU1ZNcyBzdWZmZXIgZnJvbSAqKk8obsKyKSBjb21wbGV4aXR5KiosIHdoaWNoIG1lYW5zIHRoZXkgcmVxdWlyZQ0KICAgICAgICBxdWFkcmF0aWMgbWVtb3J5IGFuZCBjb21wdXRhdGlvbmFsIHRpbWUgYXMgdGhlIGRhdGFzZXQgZ3Jvd3MuDQogICAgLSAgIFRoZSBsaW1pdGF0aW9uIGFyaXNlcyBmcm9tIHRoZSBuZWVkIHRvIHN0b3JlIGFuZCBjb21wdXRlIGRvdA0KICAgICAgICBwcm9kdWN0cyBmb3IgKiphbGwqKiBkYXRhIHBvaW50cyBpbiBtZW1vcnkuDQogICAgLSAgIEdlbmVyYWxseSwgKipTVk1zIGJlY29tZSBpbXByYWN0aWNhbCBiZXlvbmQgNTAsMDAwIHNhbXBsZXMqKiwNCiAgICAgICAgZXNwZWNpYWxseSBpZiB0aGUgZGF0YXNldCBpcyBkZW5zZS4NCjIuICAqKk1lbW9yeSBDb25zdHJhaW50cyoqDQogICAgLSAgIEEgKioxNiBHQiBtZW1vcnkgc3lzdGVtKiogY2FuIGhhbmRsZSAqKmFwcHJveGltYXRlbHkgMTI4LDAwMA0KICAgICAgICBzYW1wbGVzKiogdW5kZXIgaWRlYWwgY29uZGl0aW9ucy4NCiAgICAtICAgU2luY2UgdGhlIGtlcm5lbCBtYXRyaXggaXMgc3ltbWV0cmljLCBwcmFjdGljYWwgbGltaXRzIHBsYWNlDQogICAgICAgICoqU1ZNIHVzYWJpbGl0eSBhdCBhcm91bmQgNTAsMDAwIHNhbXBsZXMqKiBiZWZvcmUgcGVyZm9ybWFuY2UNCiAgICAgICAgZGVncmFkYXRpb24gYmVjb21lcyBzZXZlcmUuDQozLiAgKipXaHkgT3RoZXIgQWxnb3JpdGhtcyBTY2FsZSBCZXR0ZXIqKg0KICAgIC0gICAqKlJlZ3Jlc3Npb24qKiByZXF1aXJlcyBvbmx5IHNsb3BlIGNhbGN1bGF0aW9ucyAoTyhuKSBvciBsZXNzKS4NCiAgICAtICAgKipEZWNpc2lvbiBUcmVlcyoqIG5lZWQgb25seSB0aGUgZGF0YSB0byBidWlsZCB0aGUgdHJlZSwNCiAgICAgICAgc2lnbmlmaWNhbnRseSByZWR1Y2luZyBvdmVyaGVhZC4NCiAgICAtICAgKipHYXVzc2lhbiBtb2RlbHMqKiByZXF1aXJlIG9ubHkgZGF0YSBkaXN0cmlidXRpb25zLCBtYWtpbmcgdGhlbQ0KICAgICAgICBtb3JlIGVmZmljaWVudCBmb3IgbGFyZ2UgZGF0YXNldHMuDQogICAgLSAgICoqQ2x1c3RlcmluZyoqIHN1ZmZlcnMgZnJvbSBzaW1pbGFyIG7CsiBkaXN0YW5jZSBjb21wdXRhdGlvbnMgYXMNCiAgICAgICAgU1ZNLCBidXQgYWRhcHRhdGlvbnMgZXhpc3QuDQo0LiAgKipPdmVyZml0dGluZyBpbiBIaWdoIERpbWVuc2lvbnMqKg0KICAgIC0gICBTVk1zIHdvcmsgd2VsbCBpbiAqKmhpZ2gtZGltZW5zaW9uYWwgc3BhY2VzKiosIGJ1dCB0aGlzDQogICAgICAgIGluY3JlYXNlcyB0aGUgcmlzayBvZiAqKm92ZXJmaXR0aW5nKiouDQogICAgLSAgICoqUmVndWxhcml6YXRpb24gKEMgcGFyYW1ldGVyKSoqIGhlbHBzIGNvbnRyb2wgbW9kZWwgY29tcGxleGl0eSwNCiAgICAgICAgYnV0IGltcHJvcGVyIHR1bmluZyBjYW4gbGVhZCB0byBvdmVyZml0dGluZyBvciB1bmRlcmZpdHRpbmcuDQo1LiAgKipSZWdyZXNzaW9uIGFuZCBQcm9iYWJpbGl0eSBFc3RpbWF0aW9uIGluIFNWTXMgQXJlIExpbWl0ZWQqKg0KICAgIC0gICAqKlNWTXMgbmF0dXJhbGx5IHN1cHBvcnQgY2xhc3NpZmljYXRpb24sIG5vdCByZWdyZXNzaW9uKiouDQogICAgLSAgIFdoaWxlIGFkYXB0YXRpb25zIGZvciByZWdyZXNzaW9uIChTVlIpIGV4aXN0LCAqKmJldHRlcg0KICAgICAgICBhbHRlcm5hdGl2ZXMqKiBzdWNoIGFzIGxpbmVhciByZWdyZXNzaW9uLCBkZWNpc2lvbiB0cmVlcywgb3INCiAgICAgICAgbmV1cmFsIG5ldHdvcmtzIHByb3ZpZGUgc3VwZXJpb3IgcGVyZm9ybWFuY2UuDQogICAgLSAgIFNWTXMgZG8gKipub3QgaW5oZXJlbnRseSBwcm92aWRlIHByb2JhYmlsaXRpZXMqKjsgaW5zdGVhZCwgdGhleQ0KICAgICAgICBvdXRwdXQgY29uZmlkZW5jZSBzY29yZXMgYmFzZWQgb24gZGlzdGFuY2UgZnJvbSB0aGUgZGVjaXNpb24NCiAgICAgICAgYm91bmRhcnkuDQo2LiAgKipLZXJuZWwgVHJpY2sgYW5kIE5vbmxpbmVhcml0eSoqDQogICAgLSAgIFRoZSAqKmtlcm5lbCB0cmljayoqIGFsbG93cyBTVk1zIHRvIGxlYXJuIG5vbi1saW5lYXIgZGVjaXNpb24NCiAgICAgICAgYm91bmRhcmllcy4NCiAgICAtICAgKipDb21tb24ga2VybmVscyoqOiBMaW5lYXIsIFBvbHlub21pYWwsIFJhZGlhbCBCYXNpcyBGdW5jdGlvbg0KICAgICAgICAoUkJGKSwgU2lnbW9pZC4NCiAgICAtICAgV2hpbGUga2VybmVsaXplZCBTVk1zIGFyZSBwb3dlcmZ1bCwgdGhleSByZXF1aXJlIGFkZGl0aW9uYWwNCiAgICAgICAgY29tcHV0YXRpb24sIGZ1cnRoZXIgYWZmZWN0aW5nIHNjYWxhYmlsaXR5Lg0KDQojIyMgQ29uY2x1c2lvbjoNCg0KLSAgICoqU1ZNcyBhcmUgcG93ZXJmdWwgZm9yIHNtYWxsIHRvIG1lZGl1bSBkYXRhc2V0cyAoXDw1MCwwMDANCiAgICBzYW1wbGVzKSoqIGFuZCBoaWdoLWRpbWVuc2lvbmFsIGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zLg0KLSAgICoqVGhleSBhcmUgbm90IGlkZWFsIGZvciBiaWcgZGF0YSoqIGR1ZSB0byBtZW1vcnkgYW5kIGNvbXB1dGF0aW9uYWwNCiAgICBjb25zdHJhaW50cy4NCi0gICAqKk90aGVyIGFsZ29yaXRobXMgbGlrZSBkZWNpc2lvbiB0cmVlcywgcmVncmVzc2lvbiwgYW5kIG5ldXJhbA0KICAgIG5ldHdvcmtzIHNjYWxlIGJldHRlcioqIGZvciBsYXJnZSBkYXRhc2V0cy4NCi0gICAqKlJlZ3VsYXJpemF0aW9uIGlzIGNydWNpYWwqKiB0byBwcmV2ZW50IG92ZXJmaXR0aW5nIGluDQogICAgaGlnaC1kaW1lbnNpb25hbCBhcHBsaWNhdGlvbnMuDQoNCkhlcmUgaXMgYSBkZXRhaWxlZCAqKlN0dWR5IEd1aWRlIG9uIFNjYWxpbmcgb2YgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMNCihTVk1zKSoqIGJhc2VkIG9uIHRoZSB0cmFuc2NyaXB0LCBpbWFnZXMsIGFuZCByZWZlcmVuY2VzIGZyb20gdGhlDQp0ZXh0Ym9va3MuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojICoqU3R1ZHkgR3VpZGU6IFNjYWxpbmcgb2YgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTXMpKioNCg0KIyMgKioxLiBJbnRyb2R1Y3Rpb24gdG8gU1ZNIFNjYWxpbmcqKg0KDQpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykgYXJlIHBvd2VyZnVsIHN1cGVydmlzZWQgbGVhcm5pbmcgbW9kZWxzDQp1c2VkIGZvciBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiB0YXNrcy4gSG93ZXZlciwgdGhleSBkbyBub3Qgc2NhbGUNCndlbGwgZm9yIGxhcmdlIGRhdGFzZXRzIGR1ZSB0byB0aGVpciBjb21wdXRhdGlvbmFsIGNvbXBsZXhpdHkuIFRoZSBtYWluDQpjaGFsbGVuZ2UgYXJpc2VzIGZyb20gdGhlIG5lZWQgdG8gc3RvcmUgYW5kIGNvbXB1dGUgYSBsYXJnZSBtYXRyaXggb2YNCmRvdCBwcm9kdWN0cyBmb3IgZXZlcnkgcGFpciBvZiBkYXRhIHBvaW50cy4NCg0KIyMjICoqS2V5IENvbmNlcHQ6IE9yZGVyIG9mKiogJE8obl4yKSQgU2NhbGluZw0KDQotICAgU1ZNcyBoYXZlICoqcXVhZHJhdGljIGNvbXBsZXhpdHkqKiwgbWVhbmluZyB0aGV5IHJlcXVpcmUgJE8obl4yKSQNCiAgICBtZW1vcnkgYW5kICRPKG5eMykkIGNvbXB1dGF0aW9uIHRvIGZpbmQgdGhlIG9wdGltYWwgc2VwYXJhdGluZw0KICAgIGh5cGVycGxhbmUuDQotICAgVGhpcyBtYWtlcyB0aGVtIGluZmVhc2libGUgZm9yIGRhdGFzZXRzIGV4Y2VlZGluZyAqKjUwLDAwMCoqIHJvd3MgaW4NCiAgICBwcmFjdGljZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqMi4gV2h5IFNWTXMgRG8gTm90IFNjYWxlIHRvIEJpZyBEYXRhKioNCg0KIyMjICoqVGhlIFByb2JsZW06IFN0b3JpbmcgdGhlIERvdCBQcm9kdWN0IE1hdHJpeCoqDQoNCi0gICBUaGUga2VybmVsIHRyaWNrIGluIFNWTXMgcmVsaWVzIG9uIGRvdCBwcm9kdWN0cyBiZXR3ZWVuIGFsbCBkYXRhDQogICAgcG9pbnRzLg0KLSAgIElmIGEgZGF0YXNldCBoYXMgKipuKiogc2FtcGxlcywgdGhlIG1vZGVsIG11c3Qgc3RvcmUgYW4gJG4gXHRpbWVzIG4kDQogICAgR3JhbSBtYXRyaXguDQotICAgVGhlIG1lbW9yeSBhbmQgY29tcHV0YXRpb25hbCByZXF1aXJlbWVudHMgZ3JvdyBxdWFkcmF0aWNhbGx5Lg0KDQojIyMgKipFeGFtcGxlIENhbGN1bGF0aW9uKioNCg0KLSAgIEdpdmVuICoqMTYgR0Igb2YgUkFNKiosIHRoZSBtYXhpbXVtIG51bWJlciBvZiBzdG9yYWJsZSBzYW1wbGVzIGlzDQogICAgYXBwcm94aW1hdGVseSAqKjEyOCwwMDAqKi4NCi0gICBIb3dldmVyLCBzaW5jZSB0aGUgbWF0cml4IGlzIHN5bW1ldHJpYywgd2Ugb25seSBuZWVkIHRvIHN0b3JlDQogICAgJG4obi0xKS8yJCB2YWx1ZXMsIHJlZHVjaW5nIHRoaXMgdG8gKio2NCwwMDAqKiBzYW1wbGVzLg0KLSAgIERlc3BpdGUgdGhpcyByZWR1Y3Rpb24sICoqU1ZNcyBiZWNvbWUgaW1wcmFjdGljYWxseSBzbG93IGJleW9uZA0KICAgIDUwLDAwMCBzYW1wbGVzKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKjMuIFdoeSBPdGhlciBBbGdvcml0aG1zIFNjYWxlIEJldHRlcioqDQoNCiMjIyAqKkNvbXBhcmlzb24gd2l0aCBPdGhlciBNYWNoaW5lIExlYXJuaW5nIE1vZGVscyoqDQoNCnwgQWxnb3JpdGhtIHwgQ29tcGxleGl0eSB8IE1lbW9yeSBVc2FnZSB8IFdoeSBJdCBTY2FsZXMgQmV0dGVyPyB8DQp8LS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18DQp8ICoqU1ZNKiogfCAkTyhuXjIpJCB8ICRPKG5eMikkIHwgTXVzdCBzdG9yZSBhbGwgZG90IHByb2R1Y3RzIHwNCnwgKipMaW5lYXIgUmVncmVzc2lvbioqIHwgJE8obikkIHwgJE8obikkIHwgT25seSBzdG9yZXMgc2xvcGVzIHwNCnwgKipEZWNpc2lvbiBUcmVlcyoqIHwgJE8obiBcbG9nIG4pJCB8ICRPKG4pJCB8IFN0b3JlcyBvbmx5IHRyZWUgc3RydWN0dXJlIHwNCnwgKipHYXVzc2lhbiBNZXRob2RzKiogfCAkTyhuKSQgfCAkTyhuKSQgfCBPbmx5IHN0b3JlcyBkaXN0cmlidXRpb24gcGFyYW1ldGVycyB8DQp8ICoqSy1NZWFucyBDbHVzdGVyaW5nKiogfCAkTyhuaykkIHwgJE8obikkIHwgQ29tcHV0ZXMgZGlzdGFuY2VzIGVmZmljaWVudGx5IHwNCnwgKipEZWVwIExlYXJuaW5nIChOZXVyYWwgTmV0d29ya3MpKiogfCAkTyhuKSQgfCAkTyhuKSQgfCBTdG9jaGFzdGljIEdyYWRpZW50IERlc2NlbnQgKFNHRCkgYWxsb3dzIGJhdGNoIHByb2Nlc3NpbmcgfA0KDQotICAgKipUcmVlcyoqIHJlcXVpcmUgb25seSB0aGUgc3RydWN0dXJlIG9mIHRoZSB0cmVlLCB3aGljaCBzY2FsZXMNCiAgICBsb2dhcml0aG1pY2FsbHkuDQotICAgKipSZWdyZXNzaW9uKiogb25seSBuZWVkcyBjb2VmZmljaWVudHMgcmF0aGVyIHRoYW4gc3RvcmluZyBhbGwNCiAgICBwYWlyd2lzZSByZWxhdGlvbnNoaXBzLg0KLSAgICoqTmV1cmFsIG5ldHdvcmtzKiogY2FuICoqYmF0Y2ggcHJvY2VzcyoqIGFuZCB1cGRhdGUgd2VpZ2h0cw0KICAgIGVmZmljaWVudGx5IHdpdGhvdXQgbmVlZGluZyBhbGwgcGFpcndpc2UgaW50ZXJhY3Rpb25zLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio0LiBBZGRyZXNzaW5nIFNWTSBTY2FsaW5nIElzc3VlcyoqDQoNCiMjIyAqKlN0cmF0ZWdpZXMgdG8gSW1wcm92ZSBTVk0gUGVyZm9ybWFuY2Ugb24gTGFyZ2UgRGF0YXNldHMqKg0KDQoxLiAgKipVc2luZyBBcHByb3hpbWF0ZSBNZXRob2RzKioNCiAgICAtICAgKipMaW5lYXIgU1ZNKiogKGJ5IHNldHRpbmcgdGhlIGtlcm5lbCB0byBgJ2xpbmVhcidgKSBhdm9pZHMNCiAgICAgICAgc3RvcmluZyB0aGUgZnVsbCBtYXRyaXguDQogICAgLSAgICoqU3RvY2hhc3RpYyBHcmFkaWVudCBEZXNjZW50IChTR0QtU1ZNKSoqIHVwZGF0ZXMgbW9kZWwNCiAgICAgICAgcGFyYW1ldGVycyBpbiBzbWFsbGVyIHN0ZXBzLCByZWR1Y2luZyBjb21wdXRhdGlvbi4NCjIuICAqKkZlYXR1cmUgU2VsZWN0aW9uICYgRGltZW5zaW9uYWxpdHkgUmVkdWN0aW9uKioNCiAgICAtICAgKipQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpKiogcmVkdWNlcyB0aGUgbnVtYmVyIG9mDQogICAgICAgIGZlYXR1cmVzIGJlZm9yZSB0cmFpbmluZyBhbiBTVk0uDQogICAgLSAgICoqRmVhdHVyZSBoYXNoaW5nKiogY29udmVydHMgaGlnaC1kaW1lbnNpb25hbCBkYXRhIGludG8NCiAgICAgICAgbG93ZXItZGltZW5zaW9uYWwgc3BhY2UuDQozLiAgKipVc2luZyBTdWJzZXQgU2FtcGxpbmcqKg0KICAgIC0gICBUcmFpbiBTVk0gb24gYSAqKnJlcHJlc2VudGF0aXZlIHN1YnNldCoqIHJhdGhlciB0aGFuIHRoZSBmdWxsDQogICAgICAgIGRhdGFzZXQuDQogICAgLSAgIENvbWJpbmUgcmVzdWx0cyB1c2luZyAqKmVuc2VtYmxlIG1ldGhvZHMqKi4NCjQuICAqKktlcm5lbCBBcHByb3hpbWF0aW9ucyoqDQogICAgLSAgICoqTnlzdHLDtm0gTWV0aG9kKio6IEFwcHJveGltYXRlcyB0aGUga2VybmVsIG1hdHJpeCB0byByZWR1Y2UNCiAgICAgICAgY29tcHV0YXRpb25hbCBjb3N0Lg0KICAgIC0gICAqKlJhbmRvbSBGb3VyaWVyIEZlYXR1cmVzKio6IEFwcHJveGltYXRlcyBrZXJuZWxzIHdpdGggYQ0KICAgICAgICBsb3dlci1kaW1lbnNpb25hbCBwcm9qZWN0aW9uLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio1LiBTVk1zIGFuZCBSZWd1bGFyaXphdGlvbioqDQoNCi0gICBTVk1zIG5hdHVyYWxseSAqKm92ZXJmaXQgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMqKi4NCi0gICAqKlJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlciAoQykqKiBjb250cm9scyB0aGUgdHJhZGUtb2ZmIGJldHdlZW4NCiAgICBtYXJnaW4gd2lkdGggYW5kIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUuDQogICAgLSAgICoqTGFyZ2UgQyoqIOKGkiBNb3JlIGNvbXBsZXggbW9kZWwsIHNtYWxsZXIgbWFyZ2luIChoaWdoDQogICAgICAgIHZhcmlhbmNlKS4NCiAgICAtICAgKipTbWFsbCBDKiog4oaSIFNpbXBsZXIgbW9kZWwsIGxhcmdlciBtYXJnaW4gKGhpZ2ggYmlhcykuDQotICAgS2VybmVsLWJhc2VkIFNWTXMgcmVxdWlyZSB0dW5pbmcgKipoeXBlcnBhcmFtZXRlcnMqKiAoZS5nLiwgUkJGDQogICAga2VybmVsIHdpZHRoICRcZ2FtbWEkKSB0byBhdm9pZCBvdmVyZml0dGluZy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNi4gTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uIG9mIFNjYWxpbmcgaW4gU1ZNKioNCg0KIyMjICoqU3VwcG9ydCBWZWN0b3IgTWFjaGluZSBPYmplY3RpdmUgRnVuY3Rpb24qKg0KDQpGb3IgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gdGFzaywgU1ZNIG1pbmltaXplczogJCQNClxmcmFjezF9ezJ9IHx8d3x8XjIgKyBDIFxzdW1fe2k9MX1ee259IFxtYXgoMCwgMSAtIHlfaSAod15UIHhfaSArIGIpKQ0KJCQgd2hlcmU6IC0gJHx8d3x8XjIkIGVuc3VyZXMgKiptYXhpbWl6aW5nIHRoZSBtYXJnaW4qKi4gLSAkQyQgaXMgdGhlDQoqKnJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlcioqLiAtICRcbWF4KDAsIDEgLSB5X2kgKHdeVCB4X2kgKyBiKSkkIGlzIHRoZQ0KKipoaW5nZSBsb3NzIGZ1bmN0aW9uKiouDQoNCiMjIyAqKlNjYWxpbmcgQm90dGxlbmVjayoqDQoNCi0gICBUcmFpbmluZyBpbnZvbHZlcyBzb2x2aW5nIGEgKipRdWFkcmF0aWMgUHJvZ3JhbW1pbmcgKFFQKSBwcm9ibGVtKioNCiAgICB3aXRoIGNvbnN0cmFpbnRzOiAkJA0KICAgIFxhbHBoYV9pICh5X2kgKHdeVCB4X2kgKyBiKSAtIDEpID0gMA0KICAgICQkIHdoZXJlICRcYWxwaGFfaSQgYXJlIExhZ3JhbmdlIG11bHRpcGxpZXJzLg0KLSAgIFRoZSAqKmR1YWwgZm9ybSoqIHJlcXVpcmVzIGNvbXB1dGluZzogJCQNCiAgICBLKHhfaSwgeF9qKSA9IFxwaGkoeF9pKSBcY2RvdCBccGhpKHhfaikNCiAgICAkJCBmb3IgYWxsIHBhaXJzLCBsZWFkaW5nIHRvICRPKG5eMikkIG1lbW9yeSBjb25zdW1wdGlvbi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjICoqNy4gUHl0aG9uIENvZGUgZm9yIFNWTSB3aXRoIFNjYWxpbmcgQ29uc2lkZXJhdGlvbnMqKg0KDQojIyMgKipCYXNpYyBTVk0gd2l0aCBIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0KDQpgYGAgcHl0aG9uDQpmcm9tIHNrbGVhcm4uc3ZtIGltcG9ydCBTVkMNCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQsIEdyaWRTZWFyY2hDVg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uDQppbXBvcnQgbnVtcHkgYXMgbnANCg0KIyBHZW5lcmF0ZSBzeW50aGV0aWMgZGF0YXNldA0KWCwgeSA9IG1ha2VfY2xhc3NpZmljYXRpb24obl9zYW1wbGVzPTUwMDAsIG5fZmVhdHVyZXM9MjAsIHJhbmRvbV9zdGF0ZT00MikNCg0KIyBTcGxpdCBpbnRvIHRyYWluIGFuZCB0ZXN0IHNldHMNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgRGVmaW5lIFNWTSB3aXRoIFJCRiBrZXJuZWwgYW5kIHR1bmUgQw0KcGFyYW1fZ3JpZCA9IHsnQyc6IFswLjEsIDEsIDEwXSwgJ2dhbW1hJzogWydzY2FsZScsICdhdXRvJ119DQpzdm0gPSBHcmlkU2VhcmNoQ1YoU1ZDKGtlcm5lbD0ncmJmJyksIHBhcmFtX2dyaWQsIGN2PTUpDQpzdm0uZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgRXZhbHVhdGUgcGVyZm9ybWFuY2UNCnByaW50KCJCZXN0IFBhcmFtZXRlcnM6Iiwgc3ZtLmJlc3RfcGFyYW1zXykNCnByaW50KCJUZXN0IEFjY3VyYWN5OiIsIHN2bS5zY29yZShYX3Rlc3QsIHlfdGVzdCkpDQpgYGANCg0KIyMjICoqVXNpbmcgTGluZWFyIFNWTSBmb3IgTGFyZ2UgRGF0YXNldHMqKg0KDQpgYGAgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBTR0RDbGFzc2lmaWVyDQoNCiMgVXNpbmcgU0dEIHRvIHNjYWxlIFNWTQ0Kc2dkX3N2bSA9IFNHRENsYXNzaWZpZXIobG9zcz0iaGluZ2UiLCBtYXhfaXRlcj0xMDAwLCB0b2w9MWUtMykNCnNnZF9zdm0uZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCnByaW50KCJTR0QgU1ZNIFRlc3QgQWNjdXJhY3k6Iiwgc2dkX3N2bS5zY29yZShYX3Rlc3QsIHlfdGVzdCkpDQpgYGANCg0KKipXaHkgVXNlIFNHRD8qKiAtICoqTm8gbmVlZCB0byBzdG9yZSBkb3QgcHJvZHVjdHMqKiDihpIgcmVkdWNlcyAkTyhuXjIpJA0KbWVtb3J5IHVzYWdlLiAtICoqRWZmaWNpZW50IGZvciBsYXJnZSBkYXRhc2V0cyoqIOKGkiB3b3JrcyBvbg0KbWluaS1iYXRjaGVzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKio4LiBTdW1tYXJ5KioNCg0KfCAqKkFzcGVjdCoqIHwgKipTVk0gKEtlcm5lbCkqKiB8ICoqU1ZNIChMaW5lYXIpKiogfCAqKlNHRCBTVk0qKiB8DQp8LS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLXwNCnwgKipDb21wbGV4aXR5KiogfCAkTyhuXjIpJCB8ICRPKG4pJCB8ICRPKG4pJCB8DQp8ICoqTWVtb3J5KiogfCAkTyhuXjIpJCB8ICRPKG4pJCB8ICRPKDEpJCB8DQp8ICoqTGFyZ2UgRGF0YXNldHM/KiogfCDinYwgTm8gfCDinIUgWWVzIHwg4pyFIFllcyB8DQp8ICoqQmVzdCBVc2UgQ2FzZSoqIHwgU21hbGwgZGF0YXNldHMgKFw8IDUwSykgfCBMYXJnZSBkYXRhc2V0cyB8IE1hc3NpdmUgZGF0YXNldHMgfA0KDQojIyMgKipLZXkgVGFrZWF3YXlzKioNCg0KMS4gICoqU1ZNcyBkbyBub3Qgc2NhbGUgd2VsbCoqIGR1ZSB0byAqKnF1YWRyYXRpYyBjb21wbGV4aXR5KiouDQoyLiAgKipTR0QtYmFzZWQgU1ZNcyoqIGFyZSBhbiBhbHRlcm5hdGl2ZSBmb3IgKipsYXJnZSBkYXRhc2V0cyoqLg0KMy4gICoqRmVhdHVyZSBzZWxlY3Rpb24gYW5kIGtlcm5lbCBhcHByb3hpbWF0aW9ucyoqIGhlbHAgcmVkdWNlIG1lbW9yeQ0KICAgIGNvbnN0cmFpbnRzLg0KNC4gICoqT3RoZXIgYWxnb3JpdGhtcyoqICh0cmVlcywgcmVncmVzc2lvbiwgbmV1cmFsIG5ldHdvcmtzKSBzY2FsZQ0KICAgICoqbXVjaCBiZXR0ZXIqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgQUdBSU4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCioqU3R1ZHkgR3VpZGU6IFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSoqDQoNCiMjIyAqKjEuIEludHJvZHVjdGlvbiB0byBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykqKg0KDQpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykgYXJlIHN1cGVydmlzZWQgbGVhcm5pbmcgbW9kZWxzIHVzZWQgZm9yDQpjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiB0YXNrcy4gVGhleSB3b3JrIGJ5IGlkZW50aWZ5aW5nIHRoZQ0Kb3B0aW1hbCBoeXBlcnBsYW5lIHRoYXQgYmVzdCBzZXBhcmF0ZXMgY2xhc3NlcyBpbiBhIGdpdmVuIGRhdGFzZXQuDQoNCiMjIyMgKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb246KioNCg0KR2l2ZW4gYSBkYXRhc2V0ICh4aSx5aSkoeF9pLCB5X2kpIHdoZXJlIHhpeF9pIHJlcHJlc2VudHMgdGhlIGZlYXR1cmUNCnZlY3RvcnMgYW5kIHlpeV9pIHRoZSBjbGFzcyBsYWJlbHMgKMKxMVxccG0xKSwgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IGlzDQpkZWZpbmVkIGFzOg0KDQp34ouFeCtiPTAgdyBcXGNkb3QgeCArIGIgPSAwDQoNCndoZXJlIHd3IGlzIHRoZSB3ZWlnaHQgdmVjdG9yIGFuZCBiYiBpcyB0aGUgYmlhcyB0ZXJtLg0KDQpUaGUgbWFyZ2luIGlzIGdpdmVuIGJ5Og0KDQoy4oild+KIpSBcXGZyYWN7Mn17XFxcfHdcXFx8fQ0KDQp3aGljaCBzaG91bGQgYmUgbWF4aW1pemVkIHdoaWxlIGVuc3VyaW5nIGNvcnJlY3QgY2xhc3NpZmljYXRpb24uDQoNCiMjIyMgKipQeXRob24gSW1wbGVtZW50YXRpb246KioNCg0KYGBgICAgICAgICAgDQpmcm9tIHNrbGVhcm4uc3ZtIGltcG9ydCBTVkMgZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uIGZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQgIFgsIHkgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKG5fc2FtcGxlcz0xMDAsIG5fZmVhdHVyZXM9Miwgbl9jbGFzc2VzPTIsIHJhbmRvbV9zdGF0ZT00MikgWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT00MikgIG1vZGVsID0gU1ZDKGtlcm5lbD0nbGluZWFyJywgQz0xLjApIG1vZGVsLmZpdChYX3RyYWluLCB5X3RyYWluKSB5X3ByZWQgPSBtb2RlbC5wcmVkaWN0KFhfdGVzdCkgDQpgYGANCg0KIyMjICoqMi4gTWFyZ2lucyBhbmQgSGluZ2UgTG9zcyoqDQoNClRoZSBoaW5nZSBsb3NzIGZ1bmN0aW9uIGlzIGRlZmluZWQgYXM6DQoNCkwoeSxmKHgpKT1tYXjigaEoMCwx4oiSeWYoeCkpIEwoeSwgZih4KSkgPSBcXG1heCgwLCAxIC0geSBmKHgpKQ0KDQpUaGlzIGxvc3MgcGVuYWxpemVzIG1pc2NsYXNzaWZpZWQgcG9pbnRzIGFuZCBwb2ludHMgY2xvc2UgdG8gdGhlIG1hcmdpbiwNCmVuc3VyaW5nIGJldHRlciBnZW5lcmFsaXphdGlvbi4NCg0KIyMjIyAqKkNvbXBhcmlzb24gd2l0aCBPdGhlciBMb3NzIEZ1bmN0aW9uczoqKg0KDQotICAgTG9naXN0aWMgTG9zcyAoQmlub21pYWwgRGV2aWFuY2UpOiBsb2figaEoMStl4oiSeWYoeCkpXFxsb2coMSArDQogICAgZVxeey15Zih4KX0pDQoNCi0gICBTcXVhcmVkIEVycm9yIExvc3M6ICh54oiSZih4KSkyKHkgLSBmKHgpKVxeMg0KDQotICAgSHViZXJpemVkIFNxdWFyZSBIaW5nZSBMb3NzOiBIeWJyaWQgYmV0d2VlbiBoaW5nZSBsb3NzIGFuZCBzcXVhcmVkDQogICAgbG9zc+OAkDEwNDow4oCgRVNMSUlfcHJpbnQxMl90b2MucGRm44CRLg0KDQojIyMgKiozLiBLZXJuZWwgVHJpY2sqKg0KDQpGb3Igbm9uLWxpbmVhcmx5IHNlcGFyYWJsZSBkYXRhLCBTVk1zIHVzZSB0aGUgKiprZXJuZWwgdHJpY2sqKiB0bw0KdHJhbnNmb3JtIGlucHV0IHNwYWNlIGludG8gYSBoaWdoZXItZGltZW5zaW9uYWwgZmVhdHVyZSBzcGFjZSB3aGVyZSBhDQpsaW5lYXIgZGVjaXNpb24gYm91bmRhcnkgY2FuIGJlIGFwcGxpZWQuDQoNCiMjIyMgKipDb21tb24gS2VybmVsczoqKg0KDQotICAgKipMaW5lYXIgS2VybmVsOioqIEsoeCx44oCyKT144ouFeOKAsksoeCwgeCcpID0geCBcXGNkb3QgeCcNCg0KLSAgICoqUG9seW5vbWlhbCBLZXJuZWw6KiogSyh4LHjigLIpPSh44ouFeOKAsitjKWRLKHgsIHgnKSA9ICh4IFxcY2RvdCB4JyArDQogICAgYylcXmQNCg0KLSAgICoqUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIChSQkYpIEtlcm5lbDoqKiBLKHgseOKAsik9ZXhw4oGhKOKIks6z4oileOKIknjigLLiiKUyKUsoeCwNCiAgICB4JykgPSBcXGV4cCgtXFxnYW1tYSBcXFx8eCAtIHgnXFxcfFxeMikNCg0KLSAgICoqU2lnbW9pZCBLZXJuZWw6KiogSyh4LHjigLIpPXRhbmjigaEozrF44ouFeOKAsitjKUsoeCwgeCcpID0gXFx0YW5oKFxcYWxwaGEgeA0KICAgIFxcY2RvdCB4JyArIGMpDQoNCmBgYCAgICAgICAgIA0KIyBFeGFtcGxlIG9mIHVzaW5nIGFuIFJCRiBrZXJuZWwgbW9kZWwgPSBTVkMoa2VybmVsPSdyYmYnLCBnYW1tYT0wLjEsIEM9MS4wKSBtb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikgDQpgYGANCg0KIyMjICoqNC4gU2NhbGluZyBhbmQgUGVyZm9ybWFuY2UqKg0KDQpTVk1zIGhhdmUgYW4gKipPKG5cXjIpIGNvbXBsZXhpdHkqKiwgbWFraW5nIHRoZW0gaW5lZmZpY2llbnQgZm9yIGxhcmdlDQpkYXRhc2V0cy4gUGVyZm9ybWFuY2Ugb3B0aW1pemF0aW9uIHRlY2huaXF1ZXMgaW5jbHVkZToNCg0KLSAgIFVzaW5nICoqTGluZWFyU1ZDKiogKHdoaWNoIGltcGxlbWVudHMgU1ZNIGFzIGFuIG9wdGltaXphdGlvbg0KICAgIHByb2JsZW0pDQoNCi0gICBSZWR1Y2luZyBmZWF0dXJlcyB2aWEgUENBDQoNCi0gICBVc2luZyBhcHByb3hpbWF0ZSBtZXRob2RzIHN1Y2ggYXMgU0dEQ2xhc3NpZmllcg0KDQpgYGAgICAgICAgICANCmZyb20gc2tsZWFybi5zdm0gaW1wb3J0IExpbmVhclNWQyBtb2RlbCA9IExpbmVhclNWQygpIG1vZGVsLmZpdChYX3RyYWluLCB5X3RyYWluKSANCmBgYA0KDQojIyMgKio1LiBDb21wYXJpc29uIHdpdGggTmFpdmUgQmF5ZXMqKg0KDQpXaGlsZSBTVk1zIG9wdGltaXplIGEgbWFyZ2luLCBOYWl2ZSBCYXllcyBlc3RpbWF0ZXMgY2xhc3MgcHJvYmFiaWxpdGllcy4NCg0KLSAgICoqU1ZNcyoqIHBlcmZvcm0gYmV0dGVyIGluICoqaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMqKi4NCg0KLSAgICoqTmFpdmUgQmF5ZXMqKiBpcyBjb21wdXRhdGlvbmFsbHkgKipmYXN0ZXIqKiBhbmQgd29ya3Mgd2VsbCB3aGVuDQogICAgZmVhdHVyZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbnMgaG9sZC4NCg0KIyMjICoqS2V5IFRha2Vhd2F5czoqKg0KDQoxLiAgIFNWTXMgZmluZCB0aGUgb3B0aW1hbCBoeXBlcnBsYW5lIGJ5IG1heGltaXppbmcgdGhlIG1hcmdpbi4NCg0KMi4gIFRoZSAqKmtlcm5lbCB0cmljayoqIGFsbG93cyBTVk1zIHRvIGhhbmRsZSBub24tbGluZWFybHkgc2VwYXJhYmxlDQogICAgZGF0YS4NCg0KMy4gICoqSGluZ2UgbG9zcyoqIGlzIHVzZWQgZm9yIGNsYXNzaWZpY2F0aW9uIHRvIHBlbmFsaXplIG1pc2NsYXNzaWZpZWQNCiAgICBwb2ludHMuDQoNCjQuICAqKlNjYWxpbmcgaXNzdWVzKiogbGltaXQgU1ZNcyBvbiBsYXJnZSBkYXRhc2V0cyBkdWUgdG8gcXVhZHJhdGljDQogICAgY29tcGxleGl0eS4NCg0KNS4gIFNWTXMgb3V0cGVyZm9ybSBOYWl2ZSBCYXllcyBpbiAqKmhpZ2gtZGltZW5zaW9uYWwgc3BhY2VzKiosIGJ1dCBhcmUNCiAgICBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlLg0KDQojIyMgKipEaXNjdXNzaW9uIFF1ZXN0aW9uczoqKg0KDQoxLiAgIEhvdyBkb2VzIHRoZSBjaG9pY2Ugb2YgdGhlIGtlcm5lbCBhZmZlY3QgdGhlIHBlcmZvcm1hbmNlIG9mIGFuIFNWTQ0KICAgIG1vZGVsPw0KDQoyLiAgIEluIHdoYXQgc2NlbmFyaW9zIGlzIFNWTSBwcmVmZXJhYmxlIG92ZXIgbG9naXN0aWMgcmVncmVzc2lvbj8NCg0KMy4gICBIb3cgY2FuIHdlIGhhbmRsZSBpbWJhbGFuY2VkIGRhdGEgd2hpbGUgdXNpbmcgU1ZNcz8NCg0KNC4gICBXaHkgZG9lcyBpbmNyZWFzaW5nIHRoZSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIgQyByZWR1Y2UgdGhlDQogICAgbWFyZ2luPw0KDQo1LiAgIFdoYXQgc3RyYXRlZ2llcyBjYW4gaW1wcm92ZSBTVk0gcGVyZm9ybWFuY2Ugb24gbGFyZ2UgZGF0YXNldHM/DQoNCg0KDQoNCg0KKipTdHVkeSBHdWlkZTogU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTXMpKioNCg0KIyMjICoqMS4gSW50cm9kdWN0aW9uIHRvIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSoqDQpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykgYXJlIHN1cGVydmlzZWQgbGVhcm5pbmcgbW9kZWxzIHVzZWQgZm9yIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRhc2tzLiBUaGV5IHdvcmsgYnkgaWRlbnRpZnlpbmcgdGhlIG9wdGltYWwgaHlwZXJwbGFuZSB0aGF0IGJlc3Qgc2VwYXJhdGVzIGNsYXNzZXMgaW4gYSBnaXZlbiBkYXRhc2V0Lg0KDQojIyMjICoqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uOioqDQpHaXZlbiBhIGRhdGFzZXQgXCgoeF9pLCB5X2kpXCkgd2hlcmUgXCh4X2lcKSByZXByZXNlbnRzIHRoZSBmZWF0dXJlIHZlY3RvcnMgYW5kIFwoeV9pXCkgdGhlIGNsYXNzIGxhYmVscyAoXChccG0xXCkpLCB0aGUgZGVjaXNpb24gYm91bmRhcnkgaXMgZGVmaW5lZCBhczoNClxbDQogICAgdyBcY2RvdCB4ICsgYiA9IDANClxdDQp3aGVyZSBcKHdcKSBpcyB0aGUgd2VpZ2h0IHZlY3RvciBhbmQgXChiXCkgaXMgdGhlIGJpYXMgdGVybS4NCg0KVGhlIG1hcmdpbiBpcyBnaXZlbiBieToNClxbDQogICAgXGZyYWN7Mn17XHx3XHx9DQpcXQ0Kd2hpY2ggc2hvdWxkIGJlIG1heGltaXplZCB3aGlsZSBlbnN1cmluZyBjb3JyZWN0IGNsYXNzaWZpY2F0aW9uLg0KDQojIyMjICoqUHl0aG9uIEltcGxlbWVudGF0aW9uOioqDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5zdm0gaW1wb3J0IFNWQw0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQoNClgsIHkgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKG5fc2FtcGxlcz0xMDAsIG5fZmVhdHVyZXM9Miwgbl9jbGFzc2VzPTIsIHJhbmRvbV9zdGF0ZT00MikNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCm1vZGVsID0gU1ZDKGtlcm5lbD0nbGluZWFyJywgQz0xLjApDQptb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikNCnlfcHJlZCA9IG1vZGVsLnByZWRpY3QoWF90ZXN0KQ0KYGBgDQoNCiMjIyAqKjIuIE1hcmdpbnMgYW5kIEhpbmdlIExvc3MqKg0KVGhlIGhpbmdlIGxvc3MgZnVuY3Rpb24gaXMgZGVmaW5lZCBhczoNClxbDQogICAgTCh5LCBmKHgpKSA9IFxtYXgoMCwgMSAtIHkgZih4KSkNClxdDQpUaGlzIGxvc3MgcGVuYWxpemVzIG1pc2NsYXNzaWZpZWQgcG9pbnRzIGFuZCBwb2ludHMgY2xvc2UgdG8gdGhlIG1hcmdpbiwgZW5zdXJpbmcgYmV0dGVyIGdlbmVyYWxpemF0aW9uLg0KDQojIyMjICoqQ29tcGFyaXNvbiB3aXRoIE90aGVyIExvc3MgRnVuY3Rpb25zOioqDQotIExvZ2lzdGljIExvc3MgKEJpbm9taWFsIERldmlhbmNlKTogXChcbG9nKDEgKyBlXnsteWYoeCl9KVwpDQotIFNxdWFyZWQgRXJyb3IgTG9zczogXCgoeSAtIGYoeCkpXjJcKQ0KLSBIdWJlcml6ZWQgU3F1YXJlIEhpbmdlIExvc3M6IEh5YnJpZCBiZXR3ZWVuIGhpbmdlIGxvc3MgYW5kIHNxdWFyZWQgbG9zc+OAkDEwNDow4oCgRVNMSUlfcHJpbnQxMl90b2MucGRm44CRLg0KDQojIyMgKiozLiBLZXJuZWwgVHJpY2sqKg0KRm9yIG5vbi1saW5lYXJseSBzZXBhcmFibGUgZGF0YSwgU1ZNcyB1c2UgdGhlICoqa2VybmVsIHRyaWNrKiogdG8gdHJhbnNmb3JtIGlucHV0IHNwYWNlIGludG8gYSBoaWdoZXItZGltZW5zaW9uYWwgZmVhdHVyZSBzcGFjZSB3aGVyZSBhIGxpbmVhciBkZWNpc2lvbiBib3VuZGFyeSBjYW4gYmUgYXBwbGllZC4NCg0KIyMjIyAqKkNvbW1vbiBLZXJuZWxzOioqDQotICoqTGluZWFyIEtlcm5lbDoqKiBcKCBLKHgsIHgnKSA9IHggXGNkb3QgeCcgXCkNCi0gKipQb2x5bm9taWFsIEtlcm5lbDoqKiBcKCBLKHgsIHgnKSA9ICh4IFxjZG90IHgnICsgYyleZCBcKQ0KLSAqKlJhZGlhbCBCYXNpcyBGdW5jdGlvbiAoUkJGKSBLZXJuZWw6KiogXCggSyh4LCB4JykgPSBcZXhwKC1cZ2FtbWEgXHx4IC0geCdcfF4yKSBcKQ0KLSAqKlNpZ21vaWQgS2VybmVsOioqIFwoIEsoeCwgeCcpID0gXHRhbmgoXGFscGhhIHggXGNkb3QgeCcgKyBjKSBcKQ0KDQpgYGBweXRob24NCiMgRXhhbXBsZSBvZiB1c2luZyBhbiBSQkYga2VybmVsDQptb2RlbCA9IFNWQyhrZXJuZWw9J3JiZicsIGdhbW1hPTAuMSwgQz0xLjApDQptb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikNCmBgYA0KDQojIyMgKio0LiBTY2FsaW5nIGFuZCBQZXJmb3JtYW5jZSoqDQpTVk1zIGhhdmUgYW4gKipPKG5eMikgY29tcGxleGl0eSoqLCBtYWtpbmcgdGhlbSBpbmVmZmljaWVudCBmb3IgbGFyZ2UgZGF0YXNldHMuIFBlcmZvcm1hbmNlIG9wdGltaXphdGlvbiB0ZWNobmlxdWVzIGluY2x1ZGU6DQotIFVzaW5nICoqTGluZWFyU1ZDKiogKHdoaWNoIGltcGxlbWVudHMgU1ZNIGFzIGFuIG9wdGltaXphdGlvbiBwcm9ibGVtKQ0KLSBSZWR1Y2luZyBmZWF0dXJlcyB2aWEgUENBDQotIFVzaW5nIGFwcHJveGltYXRlIG1ldGhvZHMgc3VjaCBhcyBTR0RDbGFzc2lmaWVyDQoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLnN2bSBpbXBvcnQgTGluZWFyU1ZDDQptb2RlbCA9IExpbmVhclNWQygpDQptb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikNCmBgYA0KDQojIyMgKio1LiBDb21wYXJpc29uIHdpdGggTmFpdmUgQmF5ZXMqKg0KV2hpbGUgU1ZNcyBvcHRpbWl6ZSBhIG1hcmdpbiwgTmFpdmUgQmF5ZXMgZXN0aW1hdGVzIGNsYXNzIHByb2JhYmlsaXRpZXMuDQotICoqU1ZNcyoqIHBlcmZvcm0gYmV0dGVyIGluICoqaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMqKi4NCi0gKipOYWl2ZSBCYXllcyoqIGlzIGNvbXB1dGF0aW9uYWxseSAqKmZhc3RlcioqIGFuZCB3b3JrcyB3ZWxsIHdoZW4gZmVhdHVyZSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbnMgaG9sZC4NCg0KIyMjICoqS2V5IFRha2Vhd2F5czoqKg0KMS4gU1ZNcyBmaW5kIHRoZSBvcHRpbWFsIGh5cGVycGxhbmUgYnkgbWF4aW1pemluZyB0aGUgbWFyZ2luLg0KMi4gVGhlICoqa2VybmVsIHRyaWNrKiogYWxsb3dzIFNWTXMgdG8gaGFuZGxlIG5vbi1saW5lYXJseSBzZXBhcmFibGUgZGF0YS4NCjMuICoqSGluZ2UgbG9zcyoqIGlzIHVzZWQgZm9yIGNsYXNzaWZpY2F0aW9uIHRvIHBlbmFsaXplIG1pc2NsYXNzaWZpZWQgcG9pbnRzLg0KNC4gKipTY2FsaW5nIGlzc3VlcyoqIGxpbWl0IFNWTXMgb24gbGFyZ2UgZGF0YXNldHMgZHVlIHRvIHF1YWRyYXRpYyBjb21wbGV4aXR5Lg0KNS4gU1ZNcyBvdXRwZXJmb3JtIE5haXZlIEJheWVzIGluICoqaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMqKiwgYnV0IGFyZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlLg0KDQojIyMgKipEaXNjdXNzaW9uIFF1ZXN0aW9uczoqKg0KMS4gSG93IGRvZXMgdGhlIGNob2ljZSBvZiB0aGUga2VybmVsIGFmZmVjdCB0aGUgcGVyZm9ybWFuY2Ugb2YgYW4gU1ZNIG1vZGVsPw0KMi4gSW4gd2hhdCBzY2VuYXJpb3MgaXMgU1ZNIHByZWZlcmFibGUgb3ZlciBsb2dpc3RpYyByZWdyZXNzaW9uPw0KMy4gSG93IGNhbiB3ZSBoYW5kbGUgaW1iYWxhbmNlZCBkYXRhIHdoaWxlIHVzaW5nIFNWTXM/DQo0LiBXaHkgZG9lcyBpbmNyZWFzaW5nIHRoZSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIgQyByZWR1Y2UgdGhlIG1hcmdpbj8NCjUuIFdoYXQgc3RyYXRlZ2llcyBjYW4gaW1wcm92ZSBTVk0gcGVyZm9ybWFuY2Ugb24gbGFyZ2UgZGF0YXNldHM/DQoNCg==