hllinas2023

1 Prerequisites and software setup

The examples and exercises presented in this chapter rely on a small set of widely used Python libraries for text preprocessing, vectorization, and numerical computation. To ensure that all code runs correctly, the required packages and language resources should be installed before executing the examples in this document.

The commands below are provided for reference only and should be executed in a Python environment (for example, a terminal, Anaconda Prompt, or a Python-enabled R Markdown setup using reticulate).

# Core machine learning and NLP libraries
pip install scikit-learn
pip install nltk
pip install pandas
pip install numpy
pip install seaborn
pip install tabulate

# Download required NLTK resources
python -c "import nltk; nltk.download('wordnet')"
python -c "import nltk; nltk.download('omw-1.4')"
python -c "import nltk; nltk.download('stopwords')"

Once the packages are installed, the following Python modules are imported throughout this document:

import numpy as np
import pandas as pd
import re

from nltk.corpus import stopwords
from nltk.stem.wordnet import WordNetLemmatizer

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

Role of the libraries.

The purpose of each library used in this document is summarized below:

  • NLTK provides basic natural language preprocessing tools, including stopword removal and lemmatization. Only lightweight linguistic processing is used in this document.

  • scikit-learn (sklearn) supplies the vectorization and similarity machinery, including CountVectorizer, TfidfVectorizer, and cosine similarity computation.

  • pandasis used to manage text corpora as structured objects (e.g., Series) and to apply preprocessing functions consistently across documents.

  • numnpy supports numerical operations and vector-based computations required for similarity calculations.

  • seaborn is used for making statistical graphics.

  • tabulate is used to pretty-print tabular data in a human-readable format.

These tools are sufficient to illustrate the fundamental ideas behind frequency-based text representations, without introducing unnecessary dependencies.

2 Introduction

2.0.1 Preliminars

Textual data poses a distinctive challenge for computational analysis: unlike numerical or categorical variables, natural language does not come with an inherent mathematical representation. While computers operate exclusively on numbers, language is expressed through symbols, words, and structures whose meaning is not natively encoded in numeric form.

Transforming text into numbers is therefore unavoidable (but it is also an opportunity). The specific choices made during this transformation determine which aspects of language are preserved, which are simplified or ignored, and how effectively learning algorithms can operate on linguistic data. In this sense, representation choices are not neutral: they directly influence model behavior, interpretability, and performance.

In the previous document (see vocabulary construction), we focused on defining the symbolic units of language processing, including tokenization strategies, normalization procedures, and vocabulary design. These steps establish what constitutes a unit of analysis. In this chapter, we move to the next stage of the pipeline and examine how those symbolic units are transformed into numerical objects.

Our approach is deliberately incremental. We begin with simple and transparent representations that emphasize observable structure rather than deep semantic meaning. By relying on frequency counts and distributional information, we can construct representations that are easy to interpret and that provide a solid mathematical foundation for more advanced techniques.

Throughout this chapter, we introduce classical methods for numerical text representation, including Bag-of-Words and term frequency–inverse document frequency (TF-IDF). Although conceptually straightforward, these methods remain widely used in practice (for baseline models, exploratory analysis, and instructional settings).

Before introducing these techniques, it is useful to clarify a fundamental distinction that underlies all language modeling: syntax versus semantics. Syntax concerns the structural organization of words and their observable patterns of occurrence, whereas semantics relates to meaning and interpretation. A sentence may be syntactically well-formed without conveying meaningful information.

In this chapter, the emphasis is intentionally placed on the syntactic dimension of language. We focus on representations derived from word occurrence patterns (such as counts and relative frequencies) while postponing semantic representations (e.g., embeddings and neural encodings) to later chapters.

By the end of this chapter, you will be able to represent text using vectors and matrices, compute similarities between documents, and build simple language-based applications. These ideas also serve as a conceptual bridge toward the representation learning techniques employed in modern deep learning architectures, including Transformer-based models.

2.0.2 Motivation: From vectors to Transformer inputs

The numerical representations introduced in this chapter—such as Bag-of-Words and TF-IDF-illustrate a fundamental principle: language must be encoded as vectors before it can be processed by any computational model. Although these representations are relatively simple and primarily capture syntactic structure, they establish the mathematical foundation required for more advanced methods.

In modern architectures such as the Transformer (Vaswani et al., 2017), textual inputs are ultimately text can be mapped into vector spaces and processed through multiple layers of transformation. However, instead of relying on sparse, high-dimensional frequency-based vectors, Transformers employ dense vector representations (embeddings) that capture richer linguistic information.

Figure 2.1 shows that the model begins with an Input Embedding stage, where each token is mapped into a continuous vector space. The representations developed in this chapter can be interpreted as a conceptual precursor to that stage: they demonstrate how text can be embedded into vector spaces, even though they do not yet capture semantic relationships or contextual dependencies.

General architecture of the Transformer model. Source: [Vaswani et al. (2017)](https://arxiv.org/abs/1706.03762)

Figure 2.1: General architecture of the Transformer model. Source: Vaswani et al. (2017)

This highlights an important transition: while frequency-based methods focus on observable structure, modern neural models require representations that also capture meaning and context. This highlights an important transition: while frequency-based methods focus on observable structure, modern neural models require representations that also capture meaning and context. This transition (from syntactic representations to semantic vector spaces) is developed in the next document (see word embeddings).

Chapter roadmap.

The main topics covered in this chapter are:

  • Understanding vectors and matrices as mathematical data structures

  • Exploring the Bag-of-Words (BoW) representation

  • Constructing TF–IDF vectors

  • Measuring distance and similarity between document vectors

  • One-hot vectorization

  • Building a basic chatbot

3 Understanding vectors and matrices

A central challenge in NLP is expressing language in mathematical form. Two data structures play a fundamental role in this transformation: vectors and matrices. Together, they allow collections of text documents to be analyzed using the tools of linear algebra.

3.0.1 Vectors

Definition and notation.

A vector is a one-dimensional array of numerical values, where each position corresponds to a specific feature. Vectors are commonly represented as column arrays:

\[ \mathbf{x} = \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix}, \quad \mathbf{v} =\begin{bmatrix} v_1 \\ v_2 \\ v_3 \\ v_4 \end{bmatrix} \]

In this expression, the vector \(\mathbf{x}\) contains three components and belongs to \(\mathbb{R}^3\), while \(\mathbf{v}\) contains four components and belongs to \(\mathbb{R}^4\). Each coordinate represents the contribution of the vector along a particular axis. Once an object is represented as a vector, operations such as distance computation, similarity measurement, and projection become well defined.

Geometric intuition.

To develop geometric intuition, consider representing entities using measurable attributes. Suppose we describe two cities using their average annual temperature and annual rainfall:

\[ \begin{array}{c|cc} \text{City} & \text{Temperature (°C)} & \text{Rainfall (mm)} \\ \hline \text{A} & 18 & 500 \\ \text{B} & 25 & 1100 \end{array} \]

Each city can be interpreted as a point in a two-dimensional space, or equivalently, as a vector.

  • City A corresponds to the vector \(\mathbf{x}_A= (18, 500)\).

  • City B corresponds to the vector \(\mathbf{x}_B= (25, 1100)\).

From a mathematical perspective, both vectors belong to \(\mathbb{R}^2\). Here is the corresponding visualization:

Each vector originates at the coordinate system’s origin and points toward a location determined by the corresponding attributes. Adding a new attribute (such as altitude or population density) increases the dimensionality of the representation, moving the vectors from \(\mathbb{R}^2\) to \(\mathbb{R}^3\) or higher.

While such spaces quickly become difficult to visualize, the algebraic interpretation of vectors remains valid in any dimension.

From geometric vectors to text representation.

The same idea extends naturally to textual data.

After tokenization introduced in the previous document (see Vocabulary and text normalization), a document can be represented as a vector in which each dimension corresponds to a unique token in the vocabulary. The value along each dimension reflects how frequently that token appears in the document.

In this way, text is mapped into a high-dimensional vector space, where each document corresponds to a point. This representation enables the application of vector-based operations such as similarity measurement, distance computation, and clustering, forming the basis of many methods in natural language processing.

3.0.2 Matrices

Matrices extend vectors by organizing multiple vectors into rows and columns. A matrix can be written as:

\[ \mathbf{X} = \begin{bmatrix} x_{11} & x_{12} \\ x_{21} & x_{22} \\ x_{31} & x_{32} \end{bmatrix} \]

This matrix belongs to \(\mathbb{R}^{3 \times 2}\), indicating three rows and two columns. In text analysis, matrices are commonly used to represent collections of documents. Each row corresponds to a document, each column corresponds to a token in the vocabulary, and each entry stores the frequency of that token in the document.

3.0.3 Matrix representation: a simple example

From text to matrix representation.

To illustrate how text can be organized into matrix form, consider the following small collection of documents:

from sklearn.feature_extraction.text import CountVectorizer # 1

documents = (
    "Text analysis relies on numerical representations",
    "Vectors and matrices are core mathematical tools",
    "Large collections of text can be processed efficiently"
)

vectorizer = CountVectorizer(stop_words="english")  # 2
vectorizer

X = vectorizer.fit_transform(documents)             # 3
X

# Inspect the learned vocabulary and document-term matrix
print(vectorizer.vocabulary_)  # 4 --> first output
print(X.todense())             # 5a --> second output
print(X.toarray())             # 5b --> second output

Explanation of the code.

Code 1.

The code begins by importing CountVectorizer from sklearn.feature_extraction.text, a tool designed to transform a collection of text documents into a document–term matrix. Next, a small corpus of three short documents is defined.

Code 2.

The instruction CountVectorizer(stop_words="english") creates a vectorizer that automatically tokenizes the text, extracts a set of unique tokens (the vocabulary), and removes common English stopwords such as and, are, or of.

CountVectorizer(stop_words='english')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

The output displayed after evaluating vectorizer does not correspond to data, but to the internal representation of the CountVectorizer object. It simply confirms that the object has been successfully created with the specified parameters.

At this stage, the vectorizer has not yet been fitted to the data, meaning that no vocabulary has been learned and no matrix has been constructed. The expandable Parameters section reflects the configuration of the object (such as stopword removal), rather than any learned information.

In other words, we are not yet seeing the data (we are only defining the tool that will process it).

Only after applying fit_transform(documents) does the vectorizer learn the vocabulary and generate the document-term matrix.

Code 3.

The line fit_transform(documents) then performs two tasks simultaneously:

  • It first learns the vocabulary from the corpus (fit) and then

  • It converts the documents into a numerical matrix representation (transform).

The resulting object X is a sparse document-term matrix, where rows correspond to documents and columns correspond to vocabulary terms.

## <Compressed Sparse Row sparse matrix of dtype 'int64'
##  with 15 stored elements and shape (3, 14)>

The object X is not displayed as a full matrix because it is stored in a sparse format. Instead, Python displays a summary of its structure:

  • The shape (3, 14) indicates that the matrix has 3 rows (documents) and 14 columns (unique tokens in the vocabulary).

  • The expression “15 stored elements” means that only 15 entries in the matrix are nonzero. This reflects the sparsity of textual data, where most tokens do not appear in most documents.

  • The term Compressed Sparse Row (CSR) refers to the internal representation used to efficiently store and manipulate sparse matrices by keeping track only of nonzero entries.

This compact representation is essential for handling large text corpora, where the document–term matrix can have thousands or even millions of columns.

In addition to printing the matrix, several attributes can be used to better understand its structure:

  • X.shape returns the dimensions of the matrix (number of documents × vocabulary size).

  • X.nnz gives the number of nonzero entries, indicating how sparse the matrix is.

  • vectorizer.get_feature_names_out() returns the ordered list of tokens corresponding to the columns of the matrix.

X.shape
X.nnz
vectorizer.get_feature_names_out()
## (3, 14)
## 15
## array(['analysis', 'collections', 'core', 'efficiently', 'large',
##        'mathematical', 'matrices', 'numerical', 'processed', 'relies',
##        'representations', 'text', 'tools', 'vectors'], dtype=object)

These tools allow us to interpret the matrix more precisely without converting it into a dense representation. The sparse representation hides most of the matrix entries (which are zero), but it preserves all the information needed to reconstruct the full document–term matrix when required.

Codes 4 and 5.

Finally, the code produces two explicit outputs:

  • The learned vocabulary (vectorizer.vocabulary_), which maps each token to a column index.

  • The dense version of the matrix (X.todense() or X.toarray()), which makes the full structure easier to inspect in small examples.

Example: interpreting the first output (code 4).

The printed dictionary (vocabulary_) maps each unique token to a column index in the document-term matrix:

## {'text': 11, 'analysis': 0, 'relies': 9, 'numerical': 7, 'representations': 10, 'vectors': 13, 'matrices': 6, 'core': 2, 'mathematical': 5, 'tools': 12, 'large': 4, 'collections': 1, 'processed': 8, 'efficiently': 3}

Each key in this dictionary is a token extracted from the corpus after preprocessing (tokenization and stopword removal). The associated number is not a frequency and does not indicate importance or order of appearance in the text. Instead, it specifies the column position assigned to that token in the document-term matrix.

To make this concrete:

  • 'analysis': 0 means that the token analysis corresponds to column 0 of the matrix.

  • 'collections': 1 corresponds to column 1.

  • 'core': 2 corresponds to column 2.

  • 'text': 11 corresponds to column 11.

  • 'vectors': 13 corresponds to column 13.

In other words, the numbers 0, 1, 2, …, 13 are indices, not counts. They simply label the columns of the matrix, starting from zero, following Python’s indexing convention.

Once this mapping is defined, the document-term matrix uses it consistently. For example:

  • The value located at row \(i\) and column 0 represents the frequency of the token analysis in document \(i\).

  • Similarly, the value at column 11 represents the frequency of the token text in that same document.

More generally, the entry in row \(i\) and column \(j\) records how many times token \(j\) appears in document \(i\).

This separation of roles is crucial:

  • The vocabulary dictionary defines where each token lives in the matrix.

  • The matrix entries define how often each token appears in each document.

Understanding this distinction helps explain why a document vector has a fixed length equal to the size of the vocabulary, and why most entries are zero when a token does not appear in a document.

Example: interpreting the second output (code 5).

The second output (X.todense() or X.toarray()) is the document-term matrix itself:

## [[1 0 0 0 0 0 0 1 0 1 1 1 0 0]
##  [0 0 1 0 0 1 1 0 0 0 0 0 1 1]
##  [0 1 0 1 1 0 0 0 1 0 0 1 0 0]]

Mathematically:

\[ \mathbf{X} = \left( \begin{array}{c|cccccccccccccc} \text{Text} & \text{anal} & \text{coll} & \text{core} & \text{eff} & \text{lar} & \text{math} & \text{mat} & \text{num} & \text{proc} & \text{relies} & \text{repr} & \text{text} & \text{tools} & \text{vec} \\ \hline \text{#1} & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 1 & 1 & 1 & 0 & 0 \\ \text{#2} & 0 & 0 & 1 & 0 & 0 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\ \text{#3} & 0 & 1 & 0 & 1 & 1 & 0 & 0 & 0 & 1 & 0 & 0 & 1 & 0 & 0 \end{array} \right) \in \mathbb{R}^{3 \times 14} \]

Legend (tokens):

anal = analysis; coll = collections; core = core; eff = efficiently; large = large; math = mathematical; mat = matrices; num = numerical; proc = processed; relies = relies; repr = representations; text = text; tools = tools; vec = vectors.

This matrix should be interpreted as follows:

  • Rows of the matrix \(\mathbf{X}\) correspond to documents: Text 1, Text 2, Text 3 (in the same order as the input text).

  • Columns correspond to tokens in the vocabulary.

The entry \(x_{ij}\) of the matrix \(\mathbf{X}\),

\[ \mathbf{x}_i = (x_{i1}, x_{i2}, \dots, x_{i14}), \]

represents the number of times token \(j\) appears in document \(i\). For example:

  • \(x_{1,1} = 1\) indicates that the token analysis appears once in the first document.

  • \(x_{1,8} = 1\) indicates that the token numerical appears once in the first document.

  • \(x_{2,14} = 1\) indicates that the token vectors appears once in the second document.

  • Zeros indicate that the corresponding token does not appear in that document.

Because each document is short and most words appear at most once, the matrix mainly contains values of 0 and 1. A value of 1 indicates that the corresponding token appears once in that document, while 0 indicates that it does not appear at all.

The length of each row vector equals the size of the vocabulary. In this example, the vocabulary contains 14 unique tokens after stopword removal, which explains why each document vector has 14 components.

Once text data has been converted into matrix form, it becomes amenable to standard linear algebra operations such as similarity computation, projection, and matrix transformations, enabling quantitative analysis of documents.

This type of matrix-based encoding is commonly associated with the Bag-of-Words (BoW) model, in which each document is represented relative to a fixed vocabulary, typically by recording the frequency of its tokens while ignoring word order.

4 Bag-of-words (Bow)

4.0.1 Exploring the bag-of-words representation

Basic idea.

One of the simplest ways to represent text numerically is to count how often terms appear in a document. This idea forms the basis of the Bag-of-Words (BoW) representation.

The BoW model deliberately ignores word order and grammatical structure, focusing instead on which terms appear and how often they occur. Although this abstraction discards syntactic information such as word sequence, it provides a simple and effective baseline for many text analysis tasks.

In the previous chapter on vocabulary construction, we introduced the process of identifying and standardizing the basic units of text. That step is essential for BoW representations: before counting terms, we must first decide which terms belong to the vocabulary.

Vector interpretation.

Once the vocabulary is fixed, each document can be represented as a vector whose length equals the size of the vocabulary. Each position in the vector corresponds to a specific term, and the value stored in that position indicates how many times the term appears in the document.

If a term from the vocabulary does not appear in a given document, the corresponding entry in the vector is zero.

Sparsity and component values.

A natural question arises at this point:

What is the maximum possible value of a entry (or term count) in a Bag-of-Words vector?

Take a moment to think about it.

At first glance, one might expect a fixed upper bound. However, this is not the case.

In a Bag-of-Words representation, each component of the vector records the number of times a given term appears in a document. Therefore, the value of a component depends entirely on the frequency of that term within the document.

In principle, there is no fixed upper bound: a term could appear many times, especially in long documents or highly repetitive texts. As a result, some components may take relatively large values, while many others remain equal to zero.

This imbalance leads to a key property of Bag-of-Words representations: sparsity. Most entries in the vector are zero because most terms in the vocabulary do not appear in a given document.

4.0.2 From text to Bag-of-Words: a step-by-step construction

To make the idea concrete, we now construct a Bag-of-Words representation manually, starting from a small collection of sentences.

Before examining each step in detail, Figure 4.1 provides an overview of the main stages involved in building a Bag-of-Words representation. These stages will be explained progressively in the following sections.

Step-by-step construction of a Bag-of-Words representation. Source: Created by the author with ChatGPT (OpenAI)

Figure 4.1: Step-by-step construction of a Bag-of-Words representation. Source: Created by the author with ChatGPT (OpenAI)

Step 1: Define a small corpus.

We begin by defining a small collection of sentences, which will serve as our corpus. Each sentence is treated as a separate document:

sentences = [
    "Data science connects statistics and computation",
    "Statistical models learn patterns from data",
    "Modern data analysis relies on computational tools"
]

This corpus represents the raw textual input. At this stage, the data is still unstructured and cannot yet be processed mathematically.

Step 2: Store the corpus in a structured form.

The code.

The corpus is stored as a pandas.Series, where each element represents one document. This structured format facilitates systematic preprocessing and later vectorization steps.

import pandas as pd

corpus = pd.Series(sentences)
corpus

The code begins by importing the pandas library, which provides convenient data structures for handling and organizing data. The list of sentences defined in the previous step is then converted into a Series object.

A pandas.Series can be understood as a one-dimensional labeled array. In this context, each entry of the Series corresponds to a document, and the index (0, 1, 2, …) uniquely identifies each one.

The ouput.

In this case, the printed otuput is:

## 0     Data science connects statistics and computation
## 1          Statistical models learn patterns from data
## 2    Modern data analysis relies on computational t...
## dtype: object

This output shows:

  • The index on the left (0, 1, 2), which labels each document.

  • The text content of each document.

  • The data type (dtype: object), indicating that the entries are stored as text.

This representation does not yet transform the text into numbers, but it organizes the corpus into a structured format that can be easily processed in subsequent steps

Step 3: Apply basic preprocessing.

The code.

The preprocessing step standardizes the text by lowercasing, removing punctuation and stopwords, and reducing words to their lemma. These operations ensure that different surface forms of a word are treated consistently.

As discussed in the previous document on vocabulary construction (see Vocabulary and text normalization), preprocessing is a crucial step that directly influences how tokens are defined and how the vocabulary is built.

The following code implements a basic preprocessing pipeline using the nltk library:

# Step 3a: Required packages
import nltk
nltk.download('stopwords')
nltk.download('wordnet')

from nltk.corpus import stopwords
from nltk.stem.wordnet import WordNetLemmatizer
import re
import numpy as np

#Step 3b: Defining the preprocessing function
def clean_and_lemmatize(text):
    stop = set(stopwords.words("english"))
    lemmatizer = WordNetLemmatizer()
    
    tokens = re.sub(r"[^a-zA-Z]", " ", text).lower().split()
    tokens = [lemmatizer.lemmatize(tok) for tok in tokens if tok not in stop]
    
    return " ".join(tokens)

#Step 3c: Applying the preprocessing function to the corpus {.unlisted .unnumbered}
processed_corpus = corpus.apply(clean_and_lemmatize)
processed_corpus

This code defines a preprocessing function and applies it to each document in the corpus. For clarity, each component of this pipeline is explained in detail in the following subsections.

This preprocessing stage prepares the corpus for vocabulary construction, which is the next step in building the Bag-of-Words representation.

Step 3a: Required packages.

These packages provide tools for tokenization, stopword filtering, and lemmatization, which are standard preprocessing steps in natural language processing.

Step 3b: Defining the preprocessing function.

This function performs several preprocessing operations in sequence.

  • The re.sub(r"[^a-zA-Z]", " ", text) instruction removes punctuation and non-letter characters.

  • The .lower() method converts all text to lowercase to ensure consistency.

  • The .split() operation tokenizes the text into individual words.

  • Stopwords (common words such as the, and, is) are removed using the stopwords list from nltk.

  • The WordNetLemmatizer reduces each token to its base form (lemma), so that different grammatical forms are treated as the same term (e.g., models –> model).

  • The output of the function is a cleaned and normalized string, ready for vectorization.

This step is essential because the quality of the vocabulary and the resulting Bag-of-Words representation depend directly on how the text is preprocessed.

Step 3c: Applying the preprocessing function to the corpus.

The function clean_and_lemmatize is applied to each document in the corpus using the .apply() method from pandas. This method iterates over all elements of the Series and transforms each document individually.

## 0       data science connects statistic computation
## 1              statistical model learn pattern data
## 2    modern data analysis relies computational tool
## dtype: object

The output shows the preprocessed version of each document in the corpus, where stopwords have been removed and the remaining words have been lemmatized. Each row corresponds to one document, and the original document order is preserved.

len(processed_corpus)
## 3

This confirms that the corpus contains three documents, each of which has been transformed into its cleaned textual representation.

Step 4: Build the vocabulary.

The code.

This code constructs the vocabulary by extracting all unique tokens from the preprocessed corpus and sorting them alphabetically. Each token appears only once, regardless of how many times it occurs in the documents.

vocabulary = sorted(set(
    word for sentence in processed_corpus for word in sentence.split()
))
vocabulary

The expression inside the code performs three main operations:

  • The comprehension below iterates over each document and extracts all tokens.
word for sentence in processed_corpus for word in sentence.split()
  • The function set(...) removes duplicate tokens, ensuring that each term appears only once.

  • The function sorted(...) orders the vocabulary alphabetically, guaranteeing a consistent and reproducible structure.

The output.

The output is shown below:

## ['analysis', 'computation', 'computational', 'connects', 'data', 'learn', 'model', 'modern', 'pattern', 'relies', 'science', 'statistic', 'statistical', 'tool']

It is a list of 14 unique terms.

len(vocabulary)
## 14

Each term defines one dimension of the Bag-of-Words vector space. Therefore, every document will be represented as a vector of length 14, where each position corresponds to one vocabulary term.

In this way, the vocabulary defines the coordinate system of the vector space in which all documents will be represented.

Step 5: Assign indices to vocabulary terms.

The code.

This code creates a dictionary that maps each vocabulary term to a unique integer index. These indices define the column positions that each token will occupy in the Bag-of-Words matrix, ensuring a consistent numerical representation across all documents.

token_index = {token: idx for idx, token in enumerate(vocabulary)}
token_index

The function enumerate(vocabulary) pairs each term with an integer index:

(token₀, 0), (token₁, 1), (token₂, 2), ...

The dictionary comprehension then converts these pairs into a mapping of the form:

token → index

This mapping is essential because it determines the exact position of each term in the vector representation of every document.

In this way, the vocabulary is transformed into a coordinate system, where each dimension corresponds to a specific term.

The output.

In this case, the output is:

## {'analysis': 0, 'computation': 1, 'computational': 2, 'connects': 3, 'data': 4, 'learn': 5, 'model': 6, 'modern': 7, 'pattern': 8, 'relies': 9, 'science': 10, 'statistic': 11, 'statistical': 12, 'tool': 13}

Without this indexing step, it would not be possible to construct a consistent document–term matrix across multiple documents.

The next step is to initialize the document-term matrix using these indices.

Step 6: Initialize the Bag-of-Words matrix.

The code.

The next step is to initialize the document–term matrix, which will store the frequency of each vocabulary term in each document.

bow_matrix = np.zeros((len(processed_corpus), len(vocabulary)))
bow_matrix

The function np.zeros(...) creates a matrix filled with zeros. The shape of the matrix is determined by:

  • len(processed_corpus): the number of documents (rows).

  • len(vocabulary): the number of unique terms (columns).

Thus, the resulting matrix has:

number of documents × vocabulary size

This abstract description can be made more explicit by inspecting the matrix directly in Python.

The output (with python).

The size of the matrix can be verified as follows:

(len(processed_corpus), len(vocabulary))
## (3, 14)

The matrix itself is given by:

## array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
##        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
##        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

While this output shows the numerical structure, it is also useful to interpret the matrix from a mathematical perspective.

The output (matrix representation).

To better understand the structure of the matrix, it is useful to make explicit the correspondence between tokens and vocabulary terms:

\[ \text{Token map:}\qquad \begin{array}{llllllll} \texttt{ana}=\texttt{analysis}, & \texttt{cmp}=\texttt{computation}, & \texttt{cmpl}=\texttt{computational}, \\ \texttt{cnt}=\texttt{connects}, & \texttt{dat}=\texttt{data}, & \texttt{lear}=\texttt{learn}, \\ \texttt{mod}=\texttt{model}, & \texttt{mdrn}=\texttt{modern}, & \texttt{pat}=\texttt{pattern}, \\ \texttt{rel}=\texttt{relies}, & \texttt{sci}=\texttt{science}, & \texttt{st}=\texttt{statistic}, \\ \texttt{stl}=\texttt{statistical}, & \texttt{tool}=\texttt{tool}. \end{array} \]

Given this mapping, the matrix can be interpreted as the initial state:

\[ \mathbf{B}^{(0)} =\left( \begin{array}{c|cccccccccccccccc} \texttt{Text} & \texttt{ana} & \texttt{cmp} & \texttt{cmpl} & \texttt{cnt} & \texttt{dat} & \texttt{lear} & \texttt{mod} & \texttt{mdrn} & \texttt{pat} & \texttt{rel} & \texttt{sci} & \texttt{st} & \texttt{stl} & \texttt{tool} \\ \hline \text{#1} & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \text{#2} & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \text{#3} & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \end{array} \right) \]

This representation makes explicit the relationship between vocabulary terms (columns) and documents (rows).

The output (interpretation).

At this stage, all entries are zero because no word counts have been recorded yet. The matrix only defines the structure of the representation.

  • Each row corresponds to a document, and each column corresponds to a vocabulary term (as defined in Step 5).

  • The value at position \((i, j)\) will later store how many times term \(j\) appears in document \(i\).

This matrix defines the vector space in which documents will be represented, but it does not yet contain any information about word frequencies.

We are now ready to populate the matrix with actual word counts.

Step 7: Populate the matrix with word counts.

This code fills the Bag-of-Words matrix by counting word occurrences. For each document (i), every token in the preprocessed sentence is located in the vocabulary using token_index, and the corresponding matrix entry is increased by one.

for i, sentence in enumerate(processed_corpus):
    for token in sentence.split():
        bow_matrix[i, token_index[token]] += 1

The code operates as follows:

  • The loop enumerate(processed_corpus) iterates over each document, where i is the document index and sentence is the corresponding text.

  • Each document is split into tokens using .split().

  • For each token, the dictionary token_index provides the column index associated with that term.

  • The value in the matrix at position (i, j) is incremented by 1, where:

    • i = document index

    • j = token index

In this way, the matrix is gradually populated with term frequencies.

After this step, each row of bow_matrix represents a document as a vector of word counts. Nonzero values indicate that a term appears in the document, while zeros indicate absence.

This step transforms the empty matrix into a numerical representation of the corpus, where each document is encoded as a vector in the vocabulary space.

Step 8: Inspect the final representation.

The output (with python).

The resulting Bag-of-Words matrix is shown below:

bow_matrix
## array([[0., 1., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0.],
##        [0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 0., 1., 0.],
##        [1., 0., 1., 0., 1., 0., 0., 1., 0., 1., 0., 0., 0., 1.]])

This output provides the numerical representation of the corpus, where each row corresponds to a document and each column corresponds to a vocabulary term.

The output (matrix representation).

To better understand the structure of the final representation, we can express the matrix in mathematical form:

\[ \text{Token map:}\qquad \begin{array}{llllllll} \texttt{ana}=\texttt{analysis}, & \texttt{cmp}=\texttt{computation}, & \texttt{cmpl}=\texttt{computational}, \\ \texttt{cnt}=\texttt{connects}, & \texttt{dat}=\texttt{data}, & \texttt{lear}=\texttt{learn}, \\ \texttt{mod}=\texttt{model}, & \texttt{mdrn}=\texttt{modern}, & \texttt{pat}=\texttt{pattern}, \\ \texttt{rel}=\texttt{relies}, & \texttt{sci}=\texttt{science}, & \texttt{st}=\texttt{statistic}, \\ \texttt{stl}=\texttt{statistical}, & \texttt{tool}=\texttt{tool}. \end{array} \]

\[ \mathbf{B} =\left( \begin{array}{c|cccccccccccccccc} \texttt{Text} & \texttt{ana} & \texttt{cmp} & \texttt{cmpl} & \texttt{cnt} & \texttt{dat} & \texttt{lear} & \texttt{mod} & \texttt{mdrn} & \texttt{pat} & \texttt{rel} & \texttt{sci} & \texttt{st} & \texttt{stl} & \texttt{tool} \\ \hline \text{#1} & 0 & 1 & 0 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 0 & 0 \\ \text{#2} & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 1 & 0 & 0 & 0 & 1 & 0 \\ \text{#3} & 1 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 0 & 1 \end{array} \right) \] This matrix corresponds to the fully populated Bag-of-Words representation, where each entry reflects the frequency of a term in a given document.

The output (interpretation).

In this matrix:

  • Each row represents a document.

  • Each column a vocabulary term.

  • The entries indicate term frequencies, with many zeros reflecting the sparse nature of Bag-of-Words representations.

For example:

  • The entry in row #2 and column dat indicates how many times the word data appears in the second document.

  • More generally, each value \((i, j)\) captures the frequency of term \(j\) in document \(i\).

Final remark.

Most entries remain zero, illustrating the sparsity typical of Bag-of-Words representations. This sparsity arises because each document contains only a small subset of the full vocabulary.

This final matrix provides a complete numerical representation of the corpus, enabling the application of linear algebra operations and machine learning algorithms to textual data.

While this representation is simple and effective, it treats each word independently and ignores local context. We now explore extensions that capture richer structures in text.

4.0.3 Beyond unigrams

So far, we have considered only unigrams, meaning individual words. The same idea can be extended to:

  • Bigrams (pairs of consecutive words),

  • Trigrams, and

  • Higher-order n-grams.

Including n-grams allows the model to capture local contextual information, meaning that it can recognize short sequences of words rather than treating each word independently.

For example, the bigram data science carries a more specific meaning than the individual words data and science considered separately.

However, this comes at a cost: each additional n-gram increases the size of the vocabulary, leading to a higher-dimensional representation.

This raises a natural question:

Do we really need to implement all of this manually?
Fortunately, no.  

In practice, modern NLP libraries provide efficient and well-tested implementations of Bag-of-Words models.

In the next section, we introduce one such tool that automates this entire process.

5 Implementing Bag-of-Words with CountVectorizer

5.0.1 Understanding the BoW procedure with CountVectorizer

Manually building a Bag-of-Words (BoW) matrix helps develop intuition, but it is rarely necessary in practice. As discussed in the previous section, extending representations (e.g., to n-grams) quickly increases complexity.

In practice, Python provides efficient tools that automate this entire process. One of the most widely used is CountVectorizer from the scikit-learn library.

CountVectorizer transforms a collection of text documents into a document-term matrix, where:

  • Each row represents a document.

  • Each column corresponds to a token in the learned vocabulary.

  • Each cell contains the frequency of that token in the document.

This procedure mirrors the manual construction developed earlier, but in a fully automated and optimized way.

Figure 5.1 illustrates the transformation pipeline implemented by CountVectorizer, from raw text to the document-term matrix.

Bag-of-Words representation using `CountVectorizer`. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.1: Bag-of-Words representation using CountVectorizer. Source: Created by the author with ChatGPT (OpenAI)

We now illustrate this process with a simple, concrete example.

5.0.2 BoW with CountVectorizer: example

The code.

Let us illustrate this with a small, self-contained example.

First, a CountVectorizer object is initialized using its default settings. Then, the method fit_transform() is applied to the corpus. This method simultaneously learns the vocabulary from the input documents and constructs the corresponding Bag-of-Words matrix.

Before implementing the model, note that the Bag-of-Words representation ignores word order and focuses only on term frequencies.

from sklearn.feature_extraction.text import CountVectorizer  # 1

documents = [
    "Data science relies on numerical methods",
    "Text analysis uses vectors and matrices",
    "Mathematical representations support data modeling"
]

vectorizer = CountVectorizer()                    # 2
vectorizer

bow_matrix = vectorizer.fit_transform(documents) # 3
bow_matrix

# Inspect the learned vocabulary and document-term matrix
print(vectorizer.get_feature_names_out()) # 4 --> first output
print(bow_matrix.toarray())               # 5a --> second output
print(bow_matrix.todense())               # 5b --> second output

Explanation.

  1. The CountVectorizer class is imported from scikit-learn. It is used to convert a collection of text documents into a numerical representation based on word counts.

  2. A vectorizer object is created with default parameters. By default, it:

    • converts all text to lowercase,

    • tokenizes the text automatically,

    • builds the vocabulary from the corpus,

    • does not remove stopwords.

  3. The method fit_transform() performs two operations in a single step:

    • fit: learns the vocabulary from the documents,

    • transform: converts each document into a vector of term frequencies.

The result is a sparse matrix, where most entries are zero.

  1. The method get_feature_names_out() returns the learned vocabulary. The order of these terms defines the column ordering of the matrix.

  2. The method toarray() converts the sparse matrix into a dense numerical array for inspection

The resulting output contains the learned vocabulary and the associated document-term matrix, which corresponds directly to the conceptual BoW construction discussed earlier.

First output (code 4): learned vocabulary.

The first output displays the learned vocabulary, that is, the set of unique tokens extracted from the corpus.

Each term corresponds to a column in the document-term matrix, and the order shown determines the column positions in the representation.

By default, CountVectorizer sorts the terms alphabetically.

## ['analysis' 'and' 'data' 'mathematical' 'matrices' 'methods' 'modeling'
##  'numerical' 'on' 'relies' 'representations' 'science' 'support' 'text'
##  'uses' 'vectors']

Second output (code 5): Bag-of-Words matrix.

This corresponds directly to the document-term matrix introduced in the manual construction.

## [[0 0 1 0 0 1 0 1 1 1 0 1 0 0 0 0]
##  [1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1]
##  [0 0 1 1 0 0 1 0 0 0 1 0 1 0 0 0]]

The output shows the Bag-of-Words matrix in dense form.

  • Each row corresponds to one document.

  • Each column corresponds to one of the vocabulary terms listed above.

  • Each entry indicates how many times a term appears in a document.

A value of:

  • 0 means the term does not appear,

  • a positive integer indicates its frequency.

This representation ignores word order and syntactic structure, retaining only term frequencies.

Matrix representation of the BoW.

To keep the notation compact, we label each token with a short abbreviation and report the corresponding document-term matrix below.

\[ \text{Token map:}\qquad \begin{array}{llllllll} \texttt{ana}=\texttt{analysis}, & \texttt{and}=\texttt{and}, & \texttt{dat}=\texttt{data}, & \texttt{math}=\texttt{mathematical}, \\ \texttt{mtx}=\texttt{matrices}, & \texttt{meth}=\texttt{methods}, & \texttt{model}=\texttt{modeling}, & \texttt{num}=\texttt{numerical}, \\ \texttt{on}=\texttt{on}, & \texttt{rel}=\texttt{relies}, & \texttt{repr}=\texttt{representations}, & \texttt{sci}=\texttt{science}, \\ \texttt{sup}=\texttt{support}, & \texttt{txt}=\texttt{text}, & \texttt{use}=\texttt{uses}, & \texttt{vec}=\texttt{vectors}. \end{array} \]

To make the structure more explicit, we present the matrix in mathematical form.

Each entry \((i,j)\) represents the frequency of term \(j\) in document \(i\).

This type of representation is typically sparse, meaning that most entries are zero, especially as the vocabulary size increases.

\[ \mathbf{B}= \left( \begin{array}{c|cccccccccccccccc} \texttt{Doc} & \texttt{ana} & \texttt{and} & \texttt{dat} & \texttt{math} & \texttt{mtx} & \texttt{meth} & \texttt{model} & \texttt{num} & \texttt{on} & \texttt{rel} & \texttt{repr} & \texttt{sci} & \texttt{sup} & \texttt{txt} & \texttt{use} & \texttt{vec} \\ \hline \text{#1} & 0 & 0 & 1 & 0 & 0 & 1 & 0 & 1 & 1 & 1 & 0 & 1 & 0 & 0 & 0 & 0 \\ \text{#2} & 1 & 1 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 \\ \text{#3} & 0 & 0 & 1 & 1 & 0 & 0 & 1 & 0 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 0 \\ \end{array}\right) \]

For example, the token data appears once in the first and third documents, and does not appear in the second document. Similarly, the token analysis appears only in the second document, while numerical appears only in the first document. This sparsity pattern is typical of Bag-of-Words representations, especially as the vocabulary size grows.

More generally, each entry \((i,j)\) represents the frequency of term \(j\) in document \(i\).

Heatmap of the BoW.

The same matrix can be visualized as a heatmap, where darker cells indicate higher token counts.

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

terms = vectorizer.get_feature_names_out()
X = bow_matrix.toarray()
df_bow = pd.DataFrame(X, columns=terms)

plt.figure(figsize=(14,5));
ax = sns.heatmap(df_bow, cmap="Blues", cbar=True)

# --- Title and axis labels ---
ax.set_title("Bag-of-Words representation", fontsize=18, pad=10);
ax.set_xlabel("Vocabulary terms", fontsize=18);
ax.set_ylabel("Documents", fontsize=18);

# --- Tick labels ---
ax.tick_params(axis="x", labelsize=14, rotation=45)
ax.tick_params(axis="y", labelsize=14)

# --- Colorbar font size ---
cbar = ax.collections[0].colorbar
cbar.ax.tick_params(labelsize=14)

plt.tight_layout() # Prevents cropping
plt.show()

Each cell represents the frequency of a term in a document.

Since this is a small corpus, most values are either 0 or 1, so the heatmap primarily highlights the presence or absence of terms rather than strong frequency differences.

Note that common words such as and and on are included in the vocabulary because no stopword filtering was applied in this example.

Final remarks.

This example illustrates how CountVectorizer automates:

  • tokenization,

  • vocabulary construction,

  • and word counting.

The resulting Bag-of-Words representation provides a simple yet powerful way to transform text into numerical features suitable for machine learning models.

However, it is important to note that this representation ignores word order and context, which motivates more advanced approaches such as TF-IDF and word embeddings.

6 CountVectorizer: additional arguments

6.0.1 Understanding how CountVectorizer can be customized

Importantly, CountVectorizer provides several arguments that allow the basic Bag-of-Words representation to be refined and controlled, such as vocabulary size limits and document-frequency thresholds. These arguments, illustrated in Figure 6.1, will be introduced conceptually here and implemented in detail in the following sections.

Bag-of-words - `CountVectorizer` arguments. Source: Created by the author with ChatGPT (OpenAI)

Figure 6.1: Bag-of-words - CountVectorizer arguments. Source: Created by the author with ChatGPT (OpenAI)

Overview of customization options

To better organize these options, it is useful to distinguish between built-in processing features and explicit vocabulary control parameters.

The behavior of CountVectorizer can be adjusted through several arguments, which can be grouped into two main categories:

1. Built-in processing features (out-of-the-box behavior):

  • Automatic vocabulary learning.

  • Tokenization.

  • Support for n-grams (ngram_range).

  • Optional stopword removal (stop_words).

2. Vocabulary control parameters:

  • max_features: limits the vocabulary to the top N most frequent terms.

  • min_df: removes terms that appear in too few documents.

  • max_df: removes terms that appear in too many documents.

These options allow the user to balance expressiveness and dimensionality, tailoring the representation to the specific task and dataset.

6.0.2 CountVectorizer: out-of-the-box features

Beyond basic word counts, CountVectorizer includes several built-in options that make it flexible and practical for real-world applications.

We now explore some of the most commonly used features.

Automatic vocabulary learning and n-gram generation.

By default, CountVectorizer learns its vocabulary directly from the data. In addition, it can:

  • Apply tokenization internally,

  • Remove stopwords automatically, and

  • Generate n-grams without additional code.

Example.

In the following example, the argument ngram_range = (1, 3) instructs the vectorizer to include unigrams, bigrams, and trigrams, that is, single words, pairs of consecutive words, and sequences of three consecutive words.

First, a CountVectorizer object is created with the specified n-gram range. The method fit_transform() then learns the vocabulary from the corpus and constructs the corresponding Bag-of-Words matrix, where each column represents an n-gram and each row represents a document.

vectorizer_ngram = CountVectorizer(ngram_range=(1, 3))
bow_ngram = vectorizer_ngram.fit_transform(documents)

print(vectorizer_ngram.get_feature_names_out()) # Output 1
print(bow_ngram.toarray())                      # Output 2

First output.

The first output displays the learned n-gram vocabulary. As a result, terms such as analysis (unigram), text analysis (bigram), and text analysis uses (trigram) coexist as distinct features in the representation. The order shown here defines the column ordering of the Bag-of-Words matrix.

## ['analysis' 'analysis uses' 'analysis uses vectors' 'and' 'and matrices'
##  'data' 'data modeling' 'data science' 'data science relies'
##  'mathematical' 'mathematical representations'
##  'mathematical representations support' 'matrices' 'methods' 'modeling'
##  'numerical' 'numerical methods' 'on' 'on numerical'
##  'on numerical methods' 'relies' 'relies on' 'relies on numerical'
##  'representations' 'representations support'
##  'representations support data' 'science' 'science relies'
##  'science relies on' 'support' 'support data' 'support data modeling'
##  'text' 'text analysis' 'text analysis uses' 'uses' 'uses vectors'
##  'uses vectors and' 'vectors' 'vectors and' 'vectors and matrices']

To facilitate later reference and discussion, the learned vocabulary is listed below as an indexed sequence. This enumeration will be used in subsequent sections to illustrate how n-gram features are filtered, selected, or weighted when applying additional arguments of CountVectorizer.

library(dplyr)
library(stringr)
library(knitr)
library(kableExtra)

tokens <- c(
  "analysis",
  "analysis uses",
  "analysis uses vectors",
  "and",
  "and matrices",
  "data",
  "data modeling",
  "data science",
  "data science relies",
  "mathematical",
  "mathematical representations",
  "mathematical representations support",
  "matrices",
  "methods",
  "modeling",
  "numerical",
  "numerical methods",
  "on",
  "on numerical",
  "on numerical methods",
  "relies",
  "relies on",
  "relies on numerical",
  "representations",
  "representations support",
  "representations support data",
  "science",
  "science relies",
  "science relies on",
  "support",
  "support data",
  "support data modeling",
  "text",
  "text analysis",
  "text analysis uses",
  "uses",
  "uses vectors",
  "uses vectors and",
  "vectors",
  "vectors and",
  "vectors and matrices"
)

tok_tbl <- tibble(
  ID = seq_along(tokens),
  Token = tokens
) %>%
  mutate(
    n_words = str_count(Token, "\\S+") # cuenta "palabras" separadas por espacios
  ) %>%
  mutate(
    Unigram = ifelse(n_words == 1, "✓", ""),
    Bigram  = ifelse(n_words == 2, "✓", ""),
    Trigram = ifelse(n_words == 3, "✓", "")
  ) %>%
  select(ID, Token, Unigram, Bigram, Trigram)

# Mostrar en tabla con formato
kable(tok_tbl, align = "clccc",
      col.names = c("Token ID", "Token (as learned)", "Unigram", "Bigram", "Trigram"),
      caption = "Indexed n-gram vocabulary and token type (based on word count).",
       format = "html",
       booktabs = TRUE) %>%
kable_styling() %>%
kable_classic_2(full_width = FALSE)
Table 6.1: Indexed n-gram vocabulary and token type (based on word count).
Token ID Token (as learned) Unigram Bigram Trigram
1 analysis
2 analysis uses
3 analysis uses vectors
4 and
5 and matrices
6 data
7 data modeling
8 data science
9 data science relies
10 mathematical
11 mathematical representations
12 mathematical representations support
13 matrices
14 methods
15 modeling
16 numerical
17 numerical methods
18 on
19 on numerical
20 on numerical methods
21 relies
22 relies on
23 relies on numerical
24 representations
25 representations support
26 representations support data
27 science
28 science relies
29 science relies on
30 support
31 support data
32 support data modeling
33 text
34 text analysis
35 text analysis uses
36 uses
37 uses vectors
38 uses vectors and
39 vectors
40 vectors and
41 vectors and matrices

This example shows that the learned vocabulary contains tokens of different lengths. For instance:

  • Tokens 1, 6, 10, 16, and 33 correspond to unigrams.

  • Tokens 2, 7, 8, 17, and 34 correspond to bigrams.

  • Tokens 3, 11, 18, 35, and 38 correspond to trigrams.

These differences arise solely from the chosen ngram_range and do not change the underlying Bag-of-Words representation.

Second output.

The second output shows the Bag-of-Words matrix constructed using the n-gram vocabulary. Each row corresponds to a document, each column corresponds to a specific n-gram, and the value in each cell indicates how many times that n-gram appears in the document.

## [[0 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 0 0 0
##   0 0 0 0 0]
##  [1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1
##   1 1 1 1 1]
##  [0 0 0 0 0 1 1 0 0 1 1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0
##   0 0 0 0 0]]

For readability, the Bag-of-Words matrix is presented in two blocks, corresponding to tokens 1–21 and 22–40:

library(knitr)
library(kableExtra)

# --- Matriz original ---
B <- matrix(
  c(
    # Doc 1
    0,0,0,0,0,1,0,1,1,0, 0,0,0,1,0,1,1,1,1,1,
    1,1,1,0,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,0,0,0,
    # Doc 2
    1,1,1,1,1,0,0,0,0,0, 0,0,1,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0, 0,0,1,1,1,1,1,1,1,1,1,
    # Doc 3
    0,0,0,0,0,1,1,0,0,1, 1,1,0,0,1,0,0,0,0,0,
    0,0,0,1,1,1,0,0,0,1, 1,1,0,0,0,0,0,0,0,0,0
  ),
  nrow = 3,
  byrow = TRUE
)

colnames(B) <- paste0("T", 1:41)   # o solo 1:41 si prefieres
rownames(B) <- paste0("Doc.", 1:3)

# --- Subtabla 1: Tokens 1–21 ---
B_1_21 <- B[, 1:21]

kable(
  B_1_21,
  align = "c",
  caption = "(a) Bag-of-Words matrix (tokens T1 - T21)",
  format = "html",
  booktabs = TRUE
) %>%
  kable_styling(full_width = FALSE) %>%
  kable_classic_2()

# --- Subtabla 2: Tokens 22–41 ---
B_22_41 <- B[, 22:41]

kable(
  B_22_41,
  align = "c",
  caption = "(b) Bag-of-Words matrix (tokens T22 - T41)",
  format = "html",
  booktabs = TRUE
) %>%
  kable_styling(full_width = FALSE) %>%
  kable_classic_2()
Table 6.2: (a) Bag-of-Words matrix (tokens T1 - T21)
T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21
Doc.1 0 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 1 1 1 1
Doc.2 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
Doc.3 0 0 0 0 0 1 1 0 0 1 1 1 0 0 1 0 0 0 0 0 0
Table 6.2: (b) Bag-of-Words matrix (tokens T22 - T41)
T22 T23 T24 T25 T26 T27 T28 T29 T30 T31 T32 T33 T34 T35 T36 T37 T38 T39 T40 T41
Doc.1 1 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
Doc.2 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
Doc.3 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0

A value of 1 at position (Doc. i, Token Tj) indicates that token j appears once in document i; a value of 0 indicates it does not appear. For example,

Token 8 → "data science" → value 1 in document 1
Token 8 → "data science" → value 0 in document 2
Token 8 → "data science" → value 0 in document 3

In this case, a value of 1 in the column associated with data science means that this bigram appears once in the corresponding document, whereas a value of 0 indicates that it does not appear.

While these built-in features define the default behavior of the vectorizer, additional arguments allow further control over the vocabulary and document-term representation.

6.0.3 CountVectorizer: Controlling the vocabulary

Several arguments allow the vocabulary to be restricted or filtered:

  • max_features: limits vocabulary size.

  • min_df: removes rare terms.

  • max_df: removes overly frequent terms.

6.0.4 CountVectorizer: limiting vocabulary size with max_features

As the vocabulary grows, the dimensionality of document vectors increases accordingly. Very high-dimensional representations may reduce computational efficiency and harm generalization, a phenomenon commonly referred to as the curse of dimensionality.

To address this issue, CountVectorizer provides the max_features argument, which restricts the vocabulary to the most frequent tokens observed in the corpus.

Example.

In the following example, the vocabulary is limited to the five most frequent unigrams or bigrams in the corpus.

First, a CountVectorizer object is created with ngram_range = (1, 2) to extract both unigrams and bigrams. The argument max_features = 5 restricts the vocabulary to the five most frequent tokens (according to document frequency). The method fit_transform() then learns this reduced vocabulary and constructs the corresponding Bag-of-Words matrix.

vectorizer_limited = CountVectorizer(
    ngram_range=(1, 2),
    max_features=5
)

bow_limited = vectorizer_limited.fit_transform(documents)

print(vectorizer_limited.get_feature_names_out()) # Output 1
print(bow_limited.toarray())                      # Output 2

First output.

The first output displays the reduced vocabulary, consisting of the five most frequent unigrams or bigrams retained after applying the max_features constraint. The order shown here defines the column ordering of the Bag-of-Words matrix.

## ['analysis' 'analysis uses' 'and' 'and matrices' 'data']

Second output.

The resulting vocabulary (Output 1) defines the columns of the Bag-of-Words matrix (Output 2), in the exact order shown above.

## [[0 0 0 0 1]
##  [1 1 1 1 0]
##  [0 0 0 0 1]]

Each row corresponds to a document and each column corresponds to one of the selected n-grams. The entries represent term frequencies. Formally, the matrix can be written as \[ \mathbf{B} = (b_{ij}), \qquad b_{ij} = \text{frequency of n-gram } j \text{ in document } i, \] where the columns correspond to \[ (\texttt{analysis},\ \texttt{analysis uses},\ \texttt{and},\ \texttt{and matrices},\ \texttt{data}). \]

That is, the Bag-of-Words matrix can be written explicitly as \[ \mathbf{B} = \begin{array}{c|ccccc} & \texttt{analysis} & \texttt{analysis uses} & \texttt{and} & \texttt{and matrices} & \texttt{data} \\ \hline \text{Document 1} & 0 & 0 & 0 & 0 & 1 \\ \text{Document 2} & 1 & 1 & 1 & 1 & 0 \\ \text{Document 3} & 0 & 0 & 0 & 0 & 1 \end{array} \]

The value \(b_{25} = 0\) indicates that the token does not appear in the second document, while \(b_{15} = 1\) indicates that it appears once in the first document.

6.0.5 CountVectorizer: filtering tokens with min_df and max_df thresholds

Not all tokens contribute equally to the representation. Some appear in almost every document (low discrimination), while others appear only once (often too specific or noisy).

CountVectorizer supports filtering tokens using document frequency:

  • min_df keeps only terms that appear in at least min_df documents (as a count or proportion).

  • max_df keeps only terms that appear in at most max_df documents (as a count or proportion).

A useful workflow is:

  1. fit a vectorizer,

  2. compute df for each token,

  3. inspect which tokens would survive a chosen min_df/max_df rule.

6.0.6 CountVectorizer: example 1 (inspecting which tokens survive a min_df rule)

In this example, we let the vectorizer learn the full vocabulary with min_df = 1, and then compute each token’s document frequency (df) manually. Based on this information, we mark which tokens would be retained if a stricter rule such as min_df = 2 were applied.

This approach makes the effect of min_df explicit and easy to interpret.

First, a CountVectorizer object is created with ngram_range = (1, 3) to extract unigrams, bigrams, and trigrams. The argument min_df = 1 ensures that no tokens are filtered at this stage, allowing the full vocabulary to be inspected. The method fit_transform() then learns the vocabulary and constructs the corresponding Bag-of-Words matrix.

Specifically:

  • fit_transform(documents) scans the corpus, builds the vocabulary of observed tokens, and returns the document–term matrix in sparse format.

  • get_feature_names_out() extracts the list of tokens learned by the vectorizer, in the order in which they appear as columns in the matrix.

  • toarray() converts the sparse Bag-of-Words matrix into a dense numerical array, which facilitates inspection and manual computation of document frequencies.

Interpretation of the matrix structure.

The resulting Bag-of-Words matrix has:

  • rows corresponding to documents, and

  • columns corresponding to vocabulary tokens (unigrams, bigrams, or trigrams).

Each entry represents the number of times a given token appears in a given document.

Mathematical notation.

Let \(D\) denote the number of documents and \(V\) the size of the vocabulary. The Bag-of-Words representation can be written as a matrix

\[\mathbf{B} = (b_{ij}) \in \mathbb{N}^{D \times V},\]

where

\[b_{ij} = \text{number of occurrences of token } j \text{ in document } i.\]

This representation provides the basis for computing document frequencies and for applying frequency-based filtering rules such as min_df and max_df.

Application.

import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer

# Vectorizer (ya se tiene)
vectorizer_limited = CountVectorizer(
    ngram_range=(1, 3),
    min_df=1  # importante: df lo calculamos manualmente
)

bow = vectorizer_limited.fit_transform(documents)

# 1) Tokens aprendidos
tokens = vectorizer_limited.get_feature_names_out()

# 2) Bag-of-Words matrix
B = bow.toarray()

# 3) Document frequency (df)
df = (B > 0).sum(axis=0)

# 4) Número de palabras por token
n_words = np.array([len(t.split()) for t in tokens])

# 5) Construir tabla final
table_df = pd.DataFrame({
    "Token ID": np.arange(1, len(tokens) + 1),
    "Token (as learned)": tokens,
    "Unigram": (n_words == 1).astype(int),
    "Bigram":  (n_words == 2).astype(int),
    "Trigram": (n_words == 3).astype(int),
    "df": df,
    "Kept (min_df = 2)": np.where(df >= 2, "✓", "X")
})

# 6) Reemplazar 1/0 por ✓ / vacío (más legible)
for col in ["Unigram", "Bigram", "Trigram"]:
    table_df[col] = table_df[col].replace({1: "✓", 0: ""})

table_df
##     Token ID                    Token (as learned)  ... df Kept (min_df = 2)
## 0          1                              analysis  ...  1                 X
## 1          2                         analysis uses  ...  1                 X
## 2          3                 analysis uses vectors  ...  1                 X
## 3          4                                   and  ...  1                 X
## 4          5                          and matrices  ...  1                 X
## 5          6                                  data  ...  2                 ✓
## 6          7                         data modeling  ...  1                 X
## 7          8                          data science  ...  1                 X
## 8          9                   data science relies  ...  1                 X
## 9         10                          mathematical  ...  1                 X
## 10        11          mathematical representations  ...  1                 X
## 11        12  mathematical representations support  ...  1                 X
## 12        13                              matrices  ...  1                 X
## 13        14                               methods  ...  1                 X
## 14        15                              modeling  ...  1                 X
## 15        16                             numerical  ...  1                 X
## 16        17                     numerical methods  ...  1                 X
## 17        18                                    on  ...  1                 X
## 18        19                          on numerical  ...  1                 X
## 19        20                  on numerical methods  ...  1                 X
## 20        21                                relies  ...  1                 X
## 21        22                             relies on  ...  1                 X
## 22        23                   relies on numerical  ...  1                 X
## 23        24                       representations  ...  1                 X
## 24        25               representations support  ...  1                 X
## 25        26          representations support data  ...  1                 X
## 26        27                               science  ...  1                 X
## 27        28                        science relies  ...  1                 X
## 28        29                     science relies on  ...  1                 X
## 29        30                               support  ...  1                 X
## 30        31                          support data  ...  1                 X
## 31        32                 support data modeling  ...  1                 X
## 32        33                                  text  ...  1                 X
## 33        34                         text analysis  ...  1                 X
## 34        35                    text analysis uses  ...  1                 X
## 35        36                                  uses  ...  1                 X
## 36        37                          uses vectors  ...  1                 X
## 37        38                      uses vectors and  ...  1                 X
## 38        39                               vectors  ...  1                 X
## 39        40                           vectors and  ...  1                 X
## 40        41                  vectors and matrices  ...  1                 X
## 
## [41 rows x 7 columns]

Interpreting the output.

The resulting table contains 41 tokens, including unigrams, bigrams, and trigrams (up to length 3).
The most relevant columns are:

  • df: the number of documents in which a token appears at least once.

  • Kept (min_df = 2): indicates whether the token would be retained if we required it to appear in at least two documents.

From the output, only one token is retained:

  • data has df = 2, meaning it appears in two documents and therefore satisfies the condition min_df = 2 (✓).

All remaining tokens have:

  • df = 1, indicating that they appear in only one document. As a result, they would be removed (X) under the min_df = 2 rule.

Why does this happen?

This behavior is a direct consequence of the very small corpus size (approximately two documents). Most multi-word expressions (such as text analysis uses or vectors and matrices) occur in only one document.

By setting min_df = 2, we are effectively enforcing the rule:

Keep only the terms that appear across multiple documents.

When the corpus contains only two documents, this becomes a very strict filtering criterion, causing nearly all tokens to be discarded.

A more direct approach: applying min_df inside the vectorizer (optional).

In the previous example, token filtering was illustrated by manually computing document frequencies and marking which tokens would be retained under a given min_df rule.

An alternative (and more typical) approach is to apply the frequency constraint directly inside the vectorizer. In this case, tokens that do not satisfy the condition are never included in the learned vocabulary.

vectorizer_df = CountVectorizer(
    ngram_range=(1, 3),
    min_df=2
)

bow_df = vectorizer_df.fit_transform(documents)
print(vectorizer_df.get_feature_names_out())
## ['data']

Because the corpus is very small, only the token data appears in at least two documents and is therefore retained. All other unigrams, bigrams, and trigrams are discarded automatically during vocabulary construction.

This confirms the behavior observed earlier using the manual inspection table.

6.0.7 CountVectorizer: example 2 (joint filtering with min_df and max_df)

The previous example applied a lower bound on document frequency using min_df. We now extend this idea by introducing an upper bound through the parameter max_df.

In this example, only unigrams and bigrams that:

  • appear in at least two documents, and

  • appear in no more than 80% of the corpus

are retained.

First, a CountVectorizer object is created with ngram_range = (1, 2) to extract unigrams and bigrams only. The arguments min_df = 2 and max_df = 0.8 jointly filter tokens based on document frequency. As before, the method fit_transform() learns the filtered vocabulary and constructs the corresponding Bag-of-Words matrix.

vectorizer_df = CountVectorizer(
    ngram_range=(1, 2),
    min_df=2,
    max_df=0.8
)

bow_df = vectorizer_df.fit_transform(documents)

print(vectorizer_df.get_feature_names_out()) # Output 1
print(bow_df.toarray())                      # Output 2

First output.

The first output lists the tokens retained in the filtered vocabulary, that is, those satisfying both the min_df and max_df constraints.. In this case, only the token data satisfies both frequency constraints.

## ['data']

Second output.

The second output displays the resulting Bag-of-Words matrix, whose columns correspond to the retained tokens and whose rows represent documents, with entries indicating token counts. Since only one token is retained, the matrix has a single column. Each row corresponds to a document and indicates whether the token data appears in the corresponding document..

## [[1]
##  [0]
##  [1]]

Together, min_df and max_df provide a simple yet powerful mechanism to control which tokens enter the representation. They are especially useful for reducing noise and dimensionality in high-dimensional text data, while preserving terms that carry cross-document relevance.

6.0.8 Limitations of the Bag-of-Words representation

Despite its simplicity and interpretability, the Bag-of-Words model has important limitations. See Figure 6.2.

Limitations of the Bag-of-Words representation. Source: Created by the author with ChatGPT (OpenAI)

Figure 6.2: Limitations of the Bag-of-Words representation. Source: Created by the author with ChatGPT (OpenAI)

Limitations.

  • First, it relies exclusively on token counts, ignoring word order and syntactic structure. As a result, sentences with very different meanings may receive similar representations.

  • Second, BoW does not capture semantic relationships. Words with related meanings are treated as entirely independent dimensions.

  • Third, large vocabularies can lead to extremely high-dimensional vectors, which may degrade performance and increase computational cost.

These limitations motivate more refined representations that adjust token importance and incorporate contextual information. One such approach (TF-IDF weighting) is introduced in the next section.

7 TF-IDF vectors

In the previous section, documents were represented using raw word counts through the Bag-of-Words model. While this approach is intuitive, it treats all tokens equally and relies solely on their frequency within each document.

As a result, terms that appear very often across the corpus may dominate the representation, while less frequent but potentially informative terms receive little weight or are discarded altogether. This can lead to a loss of relevant patterns, especially when rare terms are crucial for distinguishing documents.

The Term Frequency-Inverse Document Frequency (TF-IDF) scheme addresses this limitation by re-weighting tokens according to both their local importance within a document and their global distribution across the corpus.

TF-IDF is widely used in information retrieval, search engines, and text mining applications. Like BoW, it is a frequency-based representation, but it incorporates an additional normalization mechanism that balances common and rare terms.

7.0.1 Term Frequency (TF)

The term frequency component measures how often a word appears in a specific document. However, since documents may vary in length, raw counts are typically normalized. A common normalized definition of term frequency is:

\[TF(w) = \frac{\text{Number of times the word w occurs in a document}}{\text{Total number of words in the document}}\]

This normalization prevents longer documents from automatically assigning higher importance to all their terms.

To build intuition, the next figure shows normalized TF for a few example tokens inside a single document (so longer documents do not automatically inflate importance).

import numpy as np
import matplotlib.pyplot as plt

# --- Simulated document term counts (Document d1) ---
terms = ["data", "analysis", "model", "the", "and", "python"]
counts_d1 = np.array([6, 3, 2, 10, 8, 1])  # raw counts in document d1
tf_d1 = counts_d1 / counts_d1.sum()        # normalized TF

plt.figure()
plt.bar(terms, tf_d1)
plt.title("Term Frequency (TF) in a single document")
plt.xlabel("Token")
plt.ylabel("TF (normalized frequency)")
plt.xticks(rotation=30, ha="right")
plt.tight_layout()
plt.show()
library(ggplot2)

tf_df <- data.frame(
  token = c("data", "analysis", "model", "the", "and", "python"),
  count = c(6, 3, 2, 10, 8, 1)
)

tf_df$TF <- tf_df$count / sum(tf_df$count)

ggplot(tf_df, aes(x = token, y = TF)) +
  geom_col(fill = "steelblue") +
  labs(
    title = "Term Frequency (TF) in a single document",
    x = "Token",
    y = "TF (normalized frequency)"
  ) +
  theme_minimal()

TF measures local importance: tokens that occur more often within the document receive larger TF values, but normalization keeps TF comparable across documents of different lengths.

7.0.2 Inverse Document Frequency (IDF)

While TF captures local relevance, it does not account for how informative a word is across the entire corpus. Words that appear in almost every document (such as general or domain-wide terms) may not be useful for discrimination.

The inverse document frequency component down-weights such ubiquitous terms and amplifies words that occur in fewer documents:

\[ IDF(w) = \log\left(\frac{\text{Total number of documents}}{\text{Number of documents containing word w}}\right) \]

where:

  • \(N\) is the total number of documents, and

  • \(df(w)\) is the number of documents containing word \(w\).

TF alone does not capture how informative a token is across the corpus. The next figure shows how IDF decreases as a token appears in more documents.

import numpy as np
import matplotlib.pyplot as plt

# --- Simulated corpus size and document frequencies ---
N = 10  # total documents
df = np.arange(1, N+1)                 # df(w) = 1..N
idf = np.log(N / df)                   # classic IDF definition (as in your notes)

plt.figure()
plt.plot(df, idf, marker="o")
plt.title("Inverse Document Frequency (IDF) vs. document frequency")
plt.xlabel("Document frequency  df(w)")
plt.ylabel("IDF(w) = log(N / df(w))")
plt.xticks(df)
plt.tight_layout()
plt.show()
idf_df <- data.frame(df = 1:10)

N <- 10
idf_df$IDF <- log(N / idf_df$df)

ggplot(idf_df, aes(x = df, y = IDF)) +
  geom_line(color = "steelblue", size=1) +
  geom_point(color = "steelblue", size=2.5) +
  scale_x_continuous(breaks = 1:10) +
  labs(
    title = "Inverse Document Frequency (IDF)",
    x = "Document frequency df(w)",
    y = "IDF(w) = log(N / df(w))"
  ) +
  theme_minimal()

Tokens that occur in many documents (high df) have low IDF, because they help less to distinguish documents. Tokens that occur in few documents have higher IDF.

7.0.3 TF-IDF weighting

The final TF-IDF weight of a word \(w\) in document \(d\) is obtained by combining two components:

\[ \text{weight}(w,d) = TF(w,d) \times IDF(w) \]

This formulation assigns higher weights to terms that are frequent within a document but relatively rare across the corpus.

Even when a term appears exactly once in every document, its TF-IDF weight is not necessarily identical across documents.
This occurs because the term frequency (TF) component is normalized by the total number of tokens in each document. Consequently, documents of different lengths assign different relative importance to the same term.

In addition, TF-IDF vectors are normalized by default using the \(L_2\) norm. This means that each document vector is rescaled to have unit length, further modifying the final weights. As a result, two documents may share the same vocabulary and identical raw term counts, yet still differ in their TF-IDF representations.

The next plot illustrates the combined effect: TF–IDF becomes large when a token is frequent in a document (high TF) and rare in the corpus (high IDF).

import numpy as np
import matplotlib.pyplot as plt

# --- Simulated TF (from a document) and IDF (from the corpus) for several tokens ---
tokens = ["data", "analysis", "model", "the", "and"]
tf = np.array([0.18, 0.12, 0.08, 0.30, 0.20])  # local frequencies (normalized)
idf = np.array([1.0, 1.4, 1.8, 0.1, 0.2])      # global rarity (higher = rarer)
tfidf = tf * idf

plt.figure()
plt.bar(tokens, tfidf)
plt.title("TF-IDF weights in a document (simulated)")
plt.xlabel("Token")
plt.ylabel("TF-IDF = TF × IDF")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.show()
tfidf_df <- data.frame(
  token = c("data", "analysis", "model", "the", "and"),
  TF = c(0.18, 0.12, 0.08, 0.30, 0.20),
  IDF = c(1.0, 1.4, 1.8, 0.1, 0.2)
)

tfidf_df$TFIDF <- tfidf_df$TF * tfidf_df$IDF

ggplot(tfidf_df, aes(x = token, y = TFIDF)) +
  geom_col(fill = "steelblue") +
  labs(
    title = "TF-IDF weights in a document (simulated)",
    x = "Token",
    y = "TF-IDF = TF × IDF"
  ) +
  theme_minimal()

A token can have a high TF but still receive a small TF–IDF weight if its IDF is low (e.g., very common words). TF–IDF emphasizes tokens that are both locally frequent and globally informative.

7.0.4 How TF, IDF, and TF-IDF relate (combined view)

Finally, the figure below visualizes TF and IDF jointly; TF-IDF is shown by the point size (larger = higher TF-IDF).

import numpy as np
import matplotlib.pyplot as plt

tokens = np.array(["data", "analysis", "model", "the", "and", "python", "science"])
tf = np.array([0.18, 0.12, 0.08, 0.30, 0.20, 0.05, 0.07])
idf = np.array([1.0, 1.4, 1.8, 0.1, 0.2, 2.0, 1.6])
tfidf = tf * idf

plt.figure()
plt.scatter(tf, idf, s=2500*tfidf)  # point size proportional to TF–IDF
for x, y, t in zip(tf, idf, tokens):
    plt.text(x, y, f"  {t}", va="center")

plt.title("TF–IDF as an interaction of TF and IDF (size = TF–IDF)")
plt.xlabel("TF (within-document frequency)")
plt.ylabel("IDF (corpus rarity)")
plt.tight_layout()
plt.show()
rel_df <- data.frame(
  token = c("data", "analysis", "model", "the", "and", "python", "science"),
  TF = c(0.18, 0.12, 0.08, 0.30, 0.20, 0.05, 0.07),
  IDF = c(1.0, 1.4, 1.8, 0.1, 0.2, 2.0, 1.6)
)

rel_df$TFIDF <- rel_df$TF * rel_df$IDF

ggplot(rel_df, aes(x = TF, y = IDF, size = TFIDF)) +
  geom_point(color = "steelblue", alpha = 0.7) +
  geom_text(aes(label = token), hjust = -0.1, vjust = 0.5) +
  labs(
    title = "TF–IDF as an interaction of TF and IDF",
    x = "TF (within-document frequency)",
    y = "IDF (corpus rarity)",
    size = "TF–IDF"
  ) +
  theme_minimal()

The largest points appear where TF and IDF are simultaneously high. This makes TF–IDF easy to interpret as an interaction: a token is most important when it is frequent in the document but uncommon in the corpus.

7.0.5 Building a basic TF–IDF vectorizer

In practice, TF–IDF representations are computed efficiently using the TfidfVectorizer class from scikit-learn, which combines term frequency normalization and inverse document frequency weighting in a single step.

Example.

To keep the example simple and self-contained, consider the following small collection of documents.

First, a TfidfVectorizer object is created using the default settings, which include \(L_2\) normalization. The method fit_transform() learns the vocabulary from the corpus and computes the TF–IDF matrix simultaneously, producing a numerical representation of the documents.

from sklearn.feature_extraction.text import TfidfVectorizer

documents = [
    "Statistical models rely on numerical features",
    "Text representations are built using vectors",
    "Feature weighting improves document comparison"
]

vectorizer = TfidfVectorizer()
tf_idf_matrix = vectorizer.fit_transform(documents)

The learned vocabulary and the resulting TF–IDF matrix can be inspected as follows:

print(vectorizer.get_feature_names_out())    # Output 1
print(tf_idf_matrix.toarray())               # Output 2
print("Matrix shape:", tf_idf_matrix.shape)  # Output 3

The three outputs correspond, respectively, to the learned vocabulary, the TF–IDF matrix expressed in dense form for inspection, and the dimensions of the resulting representation.

First output.

The first output displays the learned vocabulary, that is, the set of unique terms extracted from the corpus after preprocessing. Each element in this array corresponds to a column of the TF–IDF matrix, and the order shown here defines the column ordering used in the matrix representation.

## ['are' 'built' 'comparison' 'document' 'feature' 'features' 'improves'
##  'models' 'numerical' 'on' 'rely' 'representations' 'statistical' 'text'
##  'using' 'vectors' 'weighting']

Second output.

The second output shows the TF–IDF matrix itself, expressed in dense form for inspection. Each row corresponds to a document, each column corresponds to a term in the learned vocabulary, and each entry represents the TF–IDF weight assigned to that term in the corresponding document.

## [[0.         0.         0.         0.         0.         0.40824829
##   0.         0.40824829 0.40824829 0.40824829 0.40824829 0.
##   0.40824829 0.         0.         0.         0.        ]
##  [0.40824829 0.40824829 0.         0.         0.         0.
##   0.         0.         0.         0.         0.         0.40824829
##   0.         0.40824829 0.40824829 0.40824829 0.        ]
##  [0.         0.         0.4472136  0.4472136  0.4472136  0.
##   0.4472136  0.         0.         0.         0.         0.
##   0.         0.         0.         0.         0.4472136 ]]

Third output.

The final output reports the dimensions of the matrix. In this example, the matrix has three rows (one per document) and seventeen columns (one per vocabulary term), confirming the correspondence between the corpus size and the learned vocabulary.

## Matrix shape: (3, 17)

The vocabulary remains comparable to that of CountVectorizer, but the entries now represent TF-IDF weights rather than raw frequencies.

7.0.6 Normalization of TF–IDF vectors.

Normalization ensures that document vectors are comparable in magnitude, which is particularly important for similarity measures.

\(L_2\) norm.

By default, each TF-IDF document vector \(\mathbf{x} = (x_1, x_2, \dots, x_d)\) is normalized to have unit length using the \(L_2\) norm, defined as \[\|\mathbf{x}\|_2 \; =\; \sqrt{\sum_{j=1}^{d} x_j^2}\]

Under this normalization, the vector is rescaled so that \(\|\mathbf{x}\|_2 = 1\), emphasizing relative term contributions rather than document length.

\(L_1\) norm.

Alternatively, the \(L_1\) norm can be used, which is defined as

\[\|\mathbf{x}\|_1 \; =\; \sum_\limits{j=1}^{d} |x_j|\]

In this case, the vector is rescaled so that \(\|\mathbf{x}\|_1 = 1\), allowing the TF-IDF weights to be interpreted as relative proportions within each document.

Example.

The following example illustrates TF-IDF computation using \(L_1\) normalization.

First, a TfidfVectorizer object is created with the argument norm="l1", which specifies that each document vector will be normalized so that the sum of the absolute TF–IDF weights equals one. The method fit_transform() then learns the vocabulary from the corpus and computes the corresponding TF–IDF matrix in a single step.

The three outputs display, respectively, the learned vocabulary, the TF–IDF matrix with \(l_1\) normalization applied, and the dimensions of the resulting representation.

vectorizer_l1 = TfidfVectorizer(norm="l1")
tfidf_l1 = vectorizer_l1.fit_transform(documents)

print(vectorizer_l1.get_feature_names_out()) # Output 1
print(tfidf_l1.toarray())                    # Output 2
print("Matrix shape:", tfidf_l1.shape)       # Output 3

First output.

The first output displays the learned vocabulary. As before, each term corresponds to a column of the TF–IDF matrix, and the order shown here defines the column ordering used in the matrix representation. The vocabulary itself is unchanged by the choice of normalization.

## ['are' 'built' 'comparison' 'document' 'feature' 'features' 'improves'
##  'models' 'numerical' 'on' 'rely' 'representations' 'statistical' 'text'
##  'using' 'vectors' 'weighting']

Second output.

The second output shows the TF–IDF matrix with \(L_1\) normalization applied. Each row corresponds to a document and each column to a term in the vocabulary. Under \(L_1\) normalization, the values in each row sum to one, so the entries can be interpreted as relative weights of terms within the document. For example, in the first document, the nonzero entries are all equal, indicating that the retained terms contribute equally to the total TF–IDF weight of that document.

## [[0.         0.         0.         0.         0.         0.16666667
##   0.         0.16666667 0.16666667 0.16666667 0.16666667 0.
##   0.16666667 0.         0.         0.         0.        ]
##  [0.16666667 0.16666667 0.         0.         0.         0.
##   0.         0.         0.         0.         0.         0.16666667
##   0.         0.16666667 0.16666667 0.16666667 0.        ]
##  [0.         0.         0.2        0.2        0.2        0.
##   0.2        0.         0.         0.         0.         0.
##   0.         0.         0.         0.         0.2       ]]

Third output.

The final output reports the dimensions of the matrix. In this example, the matrix has three rows (one per document) and seventeen columns (one per vocabulary term), confirming that normalization affects the scale of the weights, but not the structure of the representation.

## Matrix shape: (3, 17)

7.0.7 N-grams and vocabulary size in TF–IDF

As with Bag-of-Words representations, the TF–IDF vectorizer supports the use of n-grams as well as constraints on vocabulary size. This allows short phrases to be incorporated into the representation while keeping dimensionality under control.

Example.

In the following example, the representation is restricted to the six most frequent features among unigrams, bigrams, and trigrams. The argument ngram_range = (1, 3) enables the extraction of n-grams up to length three, while max_features = 6 limits the vocabulary size. The default \(L_2\) normalization is applied.

vectorizer_ngram = TfidfVectorizer(
    ngram_range=(1, 3),
    max_features=6,
    norm="l2"
)

tfidf_ngram = vectorizer_ngram.fit_transform(documents)

print(vectorizer_ngram.get_feature_names_out())
print(tfidf_ngram.toarray())
print("Matrix shape:", tfidf_ngram.shape)

First output.

The first output displays the learned n-gram vocabulary, restricted to six features. In this case, all retained features correspond to unigrams, bigrams, and trigrams derived from the phrase are built using. Each element in this list defines a column of the TF-IDF matrix, and the order shown here determines the column ordering.

## ['are' 'are built' 'are built using' 'built' 'built using'
##  'built using vectors']

Second output.

The second output shows the TF–IDF matrix constructed using the restricted n-gram vocabulary. Each row corresponds to a document and each column corresponds to one of the selected n-grams.

## [[0.         0.         0.         0.         0.         0.        ]
##  [0.40824829 0.40824829 0.40824829 0.40824829 0.40824829 0.40824829]
##  [0.         0.         0.         0.         0.         0.        ]]

In this example, only the second document contains the retained n-grams, which explains why its row has nonzero TF–IDF values, while the first and third documents are represented by zero vectors.

Because \(L_2\) normalization is applied, the nonzero row has unit Euclidean norm, and the TF-IDF weights are evenly distributed across the six retained features.

Third output.

The final output reports the dimensions of the matrix. Here, the matrix has three rows (one per document) and six columns (one per retained n-gram), confirming that max_features directly controls the dimensionality of the TF-IDF representation.

## Matrix shape: (3, 6)

The parameters min_df and max_df are also available for TF-IDF vectorizers and behave identically to those in CountVectorizer, allowing extremely rare or overly common terms to be excluded based on document frequency.

7.0.8 Limitations of the TF-IDF representation

TF–IDF improves upon raw word counts by adjusting token importance using corpus-level statistics. It remains computationally efficient and highly interpretable.

However, TF–IDF still operates purely at the lexical level and therefore does not capture:

  • Semantic similarity between words,

  • Contextual meaning,

  • Word order or co-occurrence structure, or

  • Positional information within documents.

Figure 7.1 summarizes these four limitations with simple examples.

Limitations of the TF-IDF representation. Source: Created by the author with ChatGPT (OpenAI)

Figure 7.1: Limitations of the TF-IDF representation. Source: Created by the author with ChatGPT (OpenAI)

Like BoW, TF–IDF representations also scale with vocabulary size, which can become problematic for very large corpora.

These limitations motivate the use of similarity measures (such as cosine similarity) and more expressive representation learning techniques, which are explored in the following sections.

8 Distance/similarity calculation between document vectors

Once documents have been represented as vectors, a natural question arises:

How can we quantify how similar or dissimilar two text documents are?

If two documents use similar words with comparable distributions, it is reasonable to expect that they convey related information. In this section, we introduce cosine similarity, a geometric measure widely used to compare document vectors derived from Bag-of-Words and TF-IDF representations.

8.0.1 Cosine similarity

Cosine similarity measures the orientation of two vectors in a vector space by computing the cosine of the angle between them. Unlike distance-based measures, it is insensitive to vector magnitude and instead focuses on direction.

Two vectors are considered similar when they point in nearly the same direction, even if their lengths differ. This property is especially useful in text analysis, where vector magnitude is often influenced by document length.

For two vectors \(\mathbf{v}, \mathbf{v} \in \mathbb{R}^d\), cosine similarity is defined as:

\[\cos(\mathbf{u}, \mathbf{v}) \quad =\quad \frac{\mathbf{u} \cdot \mathbf{v}} {\|\mathbf{u}\|_2 \|\mathbf{v}\|_2} \quad = \quad \frac{\sum_\limits{i=1}^{d} u_{i} \, v_{i}} {\sqrt{\sum_\limits{i=1}^{d} u_{i}^2} \; \sqrt{\sum_\limits{i=1}^{d} v_{i}^2}} \quad \in \quad [-1, 1]\]

Here, \(\mathbf{u} \cdot \mathbf{v}\) denotes the Euclidean inner product, and the \(L_2\) norm (Euclidean norm) of a vector \(\mathbf{v} \in \mathbb{R}^d\) is defined as:

\[\|\mathbf{v}\|_2 \quad =\quad \sqrt{\sum_{i=1}^{d} v_i^2}.\]

Cosine similarity measures angular similarity, not Euclidean distance. It evaluates the angle between vectors rather than their magnitude. Its takes values in the continuous interval \([-1,1]\). The extreme cases correspond to:

  • Identical direction (maximum similarity): \(1\)

  • Orthogonal vectors (no linear association): \(0\)

  • Opposite direction: \(-1\)

Intermediate values (e.g., 0.82, 0.34, −0.15) reflect varying angular proximity between vectors. In embedding spaces trained on natural language data, cosine values are typically non-negative, since semantically unrelated words rarely exhibit strong opposite orientations.

8.0.2 Cosine similarity: Solving step by step

Consider two documents represented by count-based vectors:

\[\mathbf{u} = (4, 1, 2, 0, 3, 0, 1, 0) \quad \text{and} \quad \mathbf{v} = (2, 0, 1, 1, 2, 1, 0, 0)\]

The cosine similarity between them is:

\[\cos(\mathbf{u}, \mathbf{v}) \quad = \quad \frac{\mathbf{u} \cdot \mathbf{v}}{\|\mathbf{u}\|_2 \; \|\mathbf{v}\|_2}\]

First, compute the dot product:

\[\mathbf{u} \cdot \mathbf{v} \quad = \quad 4\cdot2 + 1\cdot0 + 2\cdot1 + 0\cdot1 + 3\cdot2 + 0\cdot1 + 1\cdot0 + 0\cdot0 \quad = \quad 16\]

Next, compute the vector norms:

\[\|\mathbf{u}\| \quad =\quad \sqrt{4^2 + 1^2 + 2^2 + 3^2 + 1^2} \quad = \quad \sqrt{31} \quad \approx \quad 5.57\]

\[\|\mathbf{v}\| \quad = \quad \sqrt{2^2 + 1^2 + 1^2 + 2^2 + 1^2} \quad = \quad \sqrt{11} \quad \approx \quad 3.32\]

Finally:

\[\cos(\mathbf{u}, \mathbf{v}) \quad = \quad \frac{16}{(5.57)(3.32)} \quad \approx \quad 0.87\]

A cosine similarity of \(0.87\) indicates a strong similarity between the two documents.

8.0.3 Cosine similarity: Implementing in Python

The following function computes cosine similarity between two numeric vectors:

import numpy as np

def cosine_similarity(vec1, vec2):
    v1 = np.array(vec1)
    v2 = np.array(vec2)
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

This function can be applied directly to document vectors produced by different vectorization techniques.

8.0.4 Cosine similarity using CountVectorizer outputs

CountVectorizer: the sparse and document-term matrices (code).

Recall that CountVectorizer builds a document–term matrix (Bag-of-Words). Each document is represented by a sparse vector of raw token counts, where:

  • Rows = documents.

  • Columns = vocabulary terms.

  • Entries = how many times each term appears in each document.

from sklearn.feature_extraction.text import CountVectorizer

documents = [
    "Data science relies on numerical methods",          # Document 1
    "Text analysis uses vectors and matrices",           # Document 2
    "Mathematical representations support data modeling" # Document 3
]

vectorizer = CountVectorizer()
bow_matrix = vectorizer.fit_transform(documents)

print(vectorizer.get_feature_names_out()) # Output 1
print(bow_matrix.toarray())               # Output 2

CountVectorizer: the sparse and document-term matrices (outputs).

Output 1 shows the learned vocabulary (feature names) extracted by CountVectorizer, which defines the columns of the document-term matrix. It lists the vocabulary learned from the corpus:

## ['analysis' 'and' 'data' 'mathematical' 'matrices' 'methods' 'modeling'
##  'numerical' 'on' 'relies' 'representations' 'science' 'support' 'text'
##  'uses' 'vectors']

Output 2 displays the corresponding document-term matrix in dense form, where rows represent documents, columns represent vocabulary terms (token), and entries indicate raw term frequencies.

## [[0 0 1 0 0 1 0 1 1 1 0 1 0 0 0 0]
##  [1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1]
##  [0 0 1 1 0 0 1 0 0 0 1 0 1 0 0 0]]

CountVectorizer: cosine similarity (code).

Using the document–term matrix bow_matrix, cosine similarity can be computed for every document pair:

for i in range(bow_matrix.shape[0]):
    for j in range(i + 1, bow_matrix.shape[0]):
        sim = cosine_similarity(
            bow_matrix.toarray()[i],
            bow_matrix.toarray()[j]
        )
        print(f"Cosine similarity between documents {i+1} and {j+1}: {sim:.3f}")

CountVectorizer: cosine similarity (outputs).

## Cosine similarity between documents 1 and 2: 0.000
## Cosine similarity between documents 1 and 3: 0.183
## Cosine similarity between documents 2 and 3: 0.000

CountVectorizer: cosine similarity (interpretation of the outputs).

  • A value of 0.000 means the two documents share no vocabulary terms after preprocessing, so their BoW vectors are orthogonal.

  • A small positive value (e.g., 0.183) typically indicates limited lexical overlap (for instance, a single shared token such as data), but not necessarily strong semantic similarity.

This highlights an important point: cosine similarity on Bag-of-Words is driven by shared tokens, not by meaning.

8.0.5 Cosine similarity using TF-IDF representations

TF-IDF representations: cosine similarity (code).

TF-IDF builds the same type of document vectors, but reweights terms:

  • Words that appear in many documents receive lower weight.

  • Words that are more document-specific receive higher weight.

for i in range(tf_idf_matrix.shape[0]):
    for j in range(i + 1, tf_idf_matrix.shape[0]):
        sim = cosine_similarity(
            tf_idf_matrix.toarray()[i],
            tf_idf_matrix.toarray()[j]
        )
        print(f"Cosine similarity between documents {i+1} and {j+1}: {sim:.3f}")

TF-IDF representations: cosine similarity (outputs).

## Cosine similarity between documents 1 and 2: 0.000
## Cosine similarity between documents 1 and 3: 0.000
## Cosine similarity between documents 2 and 3: 0.000

TF-IDF representations: cosine similarity (interpretation of outputs).

  • If similarities decrease (or become 0.000), it usually means that the documents share few or no important terms after TF-IDF reweighting.

  • TF-IDF can reduce the influence of very common tokens, so even when two documents share a word, the similarity may become smaller if that word is not informative.

8.0.6 Overall: BoW vs Cosine similarity vs TF-IDF

  • BoW + cosine measures overlap in raw counts.

  • TF-IDF + cosine measures overlap in weighted importance.

In the next section, we introduce one-hot vectorization, a foundational representation for neural and embedding-based models.

9 One-hot vectorization

One-hot encoding is a simple and widely used technique for representing categorical information in numerical form. In this representation, each possible category is associated with a unique coordinate in a vector. Exactly one entry takes the value 1, while all remaining entries are set to 0.

For a vocabulary of size \(|V|\), each one-hot vector lies in \(\mathbb{R}^{|V|}\) and contains exactly one non-zero component.

9.0.1 A simple intuition

Consider a categorical variable describing traffic conditions with three possible values:

  • Low.

  • Medium.

  • High.

A one-hot representation can be defined as:

\[\overrightarrow{\mathbf{\text{low}}} = (1,0,0),\quad \overrightarrow{\mathbf{\text{medium}}} = (0,1,0),\quad \overrightarrow{\mathbf{\text{high}}} = (0,0,1)\]

Each vector has length 3 because there are three possible categories, and exactly one position is active at a time. Geometrically, these vectors correspond to the canonical basis of \(\mathbb{R}^3\). They are mutually orthogonal and equidistant.

9.0.2 One-hot encoding in NLP

In natural language processing, the same idea applies to tokens. Once a vocabulary has been constructed, each word is treated as a category.

A token is represented by a vector in \(\mathbb{R}^{|V|}\), where only the coordinate corresponding to its position in the vocabulary equals 1.

Thus, one-hot encoding transforms discrete symbolic tokens into numerical vectors without introducing any semantic structure.

These representations serve as an intermediate step toward more advanced distributed representations such as word embeddings.

9.0.3 Constructing one-hot vectors step by step

To illustrate the process, we work with a short sentence.

Step 1: Define the input text.

sentence = ["Students study machine learning methods"]
corpus = pd.Series(sentence)
corpus
## 0    Students study machine learning methods
## dtype: object

The corpus contains a single document.

Step 2: Apply basic preprocessing.

We apply cleaning, stopword removal, and lemmatization:

def clean_and_lemmatize(text):
    stop = set(stopwords.words("english"))
    lemmatizer = WordNetLemmatizer()
    
    tokens = re.sub(r"[^a-zA-Z]", " ", text).lower().split()
    tokens = [lemmatizer.lemmatize(tok) for tok in tokens if tok not in stop]
    
    return " ".join(tokens)

preprocessed_corpus = corpus.apply(clean_and_lemmatize)
preprocessed_corpus

The output is:

## 0    student study machine learning method
## dtype: object

The sentence has been reduced to its core lexical components:

  • Studentsstudent.

  • methodsmethod.

  • Stopwords removed.

This ensures a clean and compact vocabulary.

Step 3: Build the vocabulary.

vocab = list(set(preprocessed_corpus[0].split()))
print(vocab)

The output is:

## ['machine', 'learning', 'student', 'study', 'method']

Each unique token now corresponds to one dimension in the vector space. Since there are 5 distinct tokens, the embedding space is \(\mathbb{R}^5\).

Step 4: Assign indices to vocabulary terms.

position = {token: idx for idx, token in enumerate(vocab)}
print(position)

The output is:

## {'machine': 0, 'learning': 1, 'student': 2, 'study': 3, 'method': 4}

This dictionary defines the coordinate system of the space: each token is assigned a fixed index. This mapping specifies which coordinate in the vector corresponds to each token.

Step 5: Initialize the one-hot matrix.

one_hot_matrix = np.zeros((len(preprocessed_corpus[0].split()), len(vocab)))
one_hot_matrix.shape

The output is:

## (5, 5)

In this case, the matrix has 5 rows (one row per token in the sentence) and 5 columns (one column per vocabulary term).

Step 6: Populate the one-hot vectors.

for i, token in enumerate(preprocessed_corpus[0].split()):
    one_hot_matrix[i][position[token]] = 1

For each token:

  • Identify its index in the vocabulary.

  • Set the corresponding column to 1.

Each row now becomes a canonical basis vector.

Step 7: Inspect the result.

one_hot_matrix

The output is:

## array([[0., 0., 1., 0., 0.],
##        [0., 0., 0., 1., 0.],
##        [1., 0., 0., 0., 0.],
##        [0., 1., 0., 0., 0.],
##        [0., 0., 0., 0., 1.]])

In this case:

  • Each row corresponds to one token.

  • Each row contains exactly one 1.

  • All vectors are orthogonal:

\[ \overrightarrow{w_i} \cdot \overrightarrow{w_j} = 0 \quad \text{for } i \neq j. \]

No semantic similarity is encoded and the representation only captures identity (no information about frequency, relative importance, or semantic similarity is captured).

Key observation.

While one-hot vectors provide a clear and unambiguous numerical encoding, they exhibit two major limitations:

  1. High dimensionality. The vector length grows linearly with vocabulary size.

  2. No semantic structure. All distinct words are equidistant:

    \[ \|\overrightarrow{w_i} \;-\; \overrightarrow{w_j}\|_2 \quad =\quad \sqrt{2} \quad \text{for}\, i\, \neq \,j. \]

These limitations motivate the transition to distributed representations, where meaning emerges from geometry rather than position alone.

10 Summary

In this chapter, we introduced the fundamental mathematical ideas behind representing text as numerical objects. Starting from simple heuristics, we explored how textual data can be mapped into vectors and matrices, enabling the use of linear algebra techniques for analysis.

We first examined the Bag-of-Words (BoW) representation and implemented it using the CountVectorizer API. While this approach provides an intuitive and effective way to encode text based on term frequencies, we also identified its main limitations—most notably, its tendency to overemphasize very frequent terms and ignore the relative importance of rarer but potentially informative words.

To address these issues, we introduced TF–IDF vectorization, which reweights term frequencies by incorporating global information about term distribution across the corpus. This adjustment helps balance local relevance within documents against global prevalence in the dataset. Despite this improvement, both BoW and TF–IDF remain fundamentally lexical methods: they rely on surface-level word occurrences and do not account for semantic meaning, word order, or contextual relationships.

Building on these vector representations, we then explored how document similarity can be quantified using cosine similarity, interpreting documents as points in a high-dimensional space and measuring the angles between their corresponding vectors. This provided a practical mechanism for comparing documents and served as the foundation for simple applications such as retrieval-based chatbots.

Finally, we discussed one-hot vectorization, a sparse encoding scheme commonly used to represent individual tokens as categorical variables. Although simple, this representation plays an important role as a conceptual building block for more advanced models.

Overall, the methods covered in this chapter are most effective in settings where the vocabulary size is moderate and lexical overlap between documents is meaningful. As vocabularies grow larger or semantic relationships become more important, these representations become less adequate.

With this syntactic foundation in place, the next chapter moves beyond word counts and lexical weighting. We will explore approaches that explicitly model semantic relationships between words, beginning with distributed representations such as Word2Vec.

11 Applied activity: from text to vector-based similarity

This activity is designed to integrate and apply the numerical text representation techniques introduced in this chapter.
The reader will transform a small text corpus into vector representations and analyze document similarity using linear algebra concepts.

11.0.1 Objective

To build a fully reproducible pipeline that converts raw text into numerical vectors using Bag-of-Words and TF–IDF representations, and to analyze document similarity using cosine similarity.

11.0.2 Instructions

  1. Select a small corpus of text, such as:

    • Short paragraphs from news articles,

    • Abstracts of scientific papers, or

    • Brief descriptions of products, movies, or books.

  2. The corpus must contain at least three documents, each consisting of one or two sentences.

  3. Create an R Markdown (.Rmd) document that compiles successfully to HTML (or PDF).

  4. The document must include both:

    • The code, and

    • The resulting output (printed matrices, tables, or numerical values).

11.0.3 Required Sections

1. Corpus Description.

Briefly describe the selected corpus and its context. List the documents explicitly and explain why this corpus is appropriate for similarity analysis.

2. Preprocessing.

Apply basic preprocessing steps, including:

  • Lowercasing,

  • Removal of punctuation,

  • Stopword removal, and

  • Lemmatization or stemming.

Show the processed version of each document.

3. Bag-of-Words Representation.

Construct a Bag-of-Words representation of the corpus using:

  • Either a manual implementation, or
  • A vectorization tool such as CountVectorizer.

Report:

  • The learned vocabulary, and

  • The document–term matrix.

Briefly interpret the sparsity and dimensionality of the resulting matrix.

4. TF–IDF Representation.

Using the same corpus, compute TF-IDF vectors.

Compare the TF–IDF matrix with the BoW matrix by discussing:

  • Differences in numerical values, and

  • How TF–IDF reweights frequent and rare terms.

5. Cosine Similarity Analysis.

Compute pairwise cosine similarity between all documents using:

  • BoW vectors, and

  • TF–IDF vectors.

Present the results clearly and identify:

  • The most similar document pair, and

  • The least similar document pair.

Explain any differences observed between the two representations.

6. One-hot Representation (Conceptual).

Select three tokens from the vocabulary and:

  • Construct their one-hot vectors, and - Explain why one-hot representations are unsuitable for measuring semantic similarity directly.

This section may be presented conceptually or with a small numerical example.

7. Summary and Reflection.

Write a concise reflection (6–10 lines) addressing:

  • How vectorization enables mathematical comparison of text,

  • The role of weighting schemes such as TF–IDF, and

  • The limitations of purely lexical representations.

11.0.4 Reproducibility Requirement

  • The R Markdown document must be fully reproducible.

  • All code chunks must execute without errors and regenerate the reported outputs when the document is compiled.

  • All random seeds (if applicable) must be set to ensure deterministic results.

  • All library versions used should be clearly reported.

References

 

 
If you found any ERRORS or have SUGGESTIONS, please report them to my email. Thanks.  
LS0tDQp0aXRsZTogIk1BVEhFTUFUSUNTIEJFSElORCBMQU5HVUFHRSBSRVBSRVNFTlRBVElPTiINCnN1YnRpdGxlOiA8aDE+KipUcmFuc2Zvcm1pbmcgVGV4dCBpbnRvIERhdGEgU3RydWN0dXJlcyoqPC9oMT4NCg0KYXV0aG9yOiANCiAgLSBuYW1lICAgICAgICAgIDogIkRyLiByZXIuIG5hdC4gSHVtYmVydG8gTExpbsOhcyBTb2xhbm8iDQogICAgYWZmaWxpYXRpb24gICA6ICJEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNzIGFuZCBTdGF0aXN0aWNzLCBVbml2ZXJzaWRhZCBkZWwgTm9ydGUsIEJhcnJhbnF1aWxsYSwgQ29sb21iaWEiDQogICAgICNjb3JyZXNwb25kaW5nIDogeWVzICAgICMgRGVmaW5lIG9ubHkgb25lIGNvcnJlc3BvbmRpbmcgYXV0aG9yDQogICAgICNhZGRyZXNzICAgICAgIDogIkRlcGFydGFtZW50byBkZSBNYXRlbcOhdGljYXMgeSBFc3RhZMOtc3RpY2EiDQogICAgZW1haWwgICAgICAgICA6IHwNCiAgICAgIGhsbGluYXNAdW5pbm9ydGUuZWR1LmNvDQogICAgICANCiAgICAgIFtCaW9ncmFwaGljYWwgc2tldGNoXShodHRwczovL3JwdWJzLmNvbS9obGxpbmFzL0Jpb19Ta2V0Y2gpDQogICAgICANCiAgICAgIGByIGZvcm1hdChTeXMudGltZSgpLCAiJWQvJW0vJXkiKWAgDQogICAgICANCiAgICAgI3JvbGU6ICAgICAgICAgIyBDb250cmlidXRvcnNoaXAgcm9sZXMgKGUuZy4sIENSZWRpVCwgaHR0cHM6Ly9jYXNyYWkub3JnL2NyZWRpdC8pDQogICMgICAgLSBDb25jZXB0dWFsaXphdGlvbg0KICAjICAgIC0gV3JpdGluZyAtIE9yaWdpbmFsIERyYWZ0IFByZXBhcmF0aW9uDQogICMgICAgLSBXcml0aW5nIC0gUmV2aWV3ICYgRWRpdGluZw0KICMgLSBuYW1lICAgICAgICAgIDogIkF1dG9yIG51bWVybyAyIg0KICMgICBhZmZpbGlhdGlvbiAgIDogIjEsMiINCiAjICAgcm9sZToNCiAjICAgICAtIFdyaXRpbmcgLSBSZXZpZXcgJiBFZGl0aW5nDQogICAgICNhZmZpbGlhdGlvbjoNCiAgIy0gaWQgICAgICAgICAgICA6ICIxIg0KICAjICBpbnN0aXR1dGlvbiAgIDogIlVuaXZlcnNpZGFkIGRlbCBOb3J0ZSAoQmFycmFucXVpbGxhLCBDb2xvbWJpYSkiDQogICMhW10oaGxsaW5hcy5qcGcpe3dpZHRoPTFpbn0gDQogIA0KI2RhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIiVkLyVtLyV5IilgJyAgIyB2ZXIgaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLWNvb2tib29rL3VwZGF0ZS1kYXRlLmh0bWwNCm91dHB1dDogDQogICAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOiANCiAgICAgICAgICAjT0pPIFNhbGVuIGNhcGl0dWxvcywgc2VjY2lvbmVzIHkgVGVvcmVtYXMNCiAgICAjYm9va2Rvd246Omh0bWxfYm9vazoNCiAgICAgICAgICAjT0pPIEVSUk9SIFNhbGVuIHRlb3JlbWFzLCBwZXJvIG5vIHNhbGVuIGxvcyBjYXBpdHVsb3MgDQogICAgI2h0bWxfZG9jdW1lbnQ6DQogICAgICAgICAgdG9jOiB0cnVlICAgICAgIyB0YWJsZSBvZiBjb250ZW50IHRydWUNCiAgICAgICAgICB0b2NfZGVwdGg6IDQgICAjIHVwdG8gdGhyZWUgZGVwdGhzIG9mIGhlYWRpbmdzIChzcGVjaWZpZWQgYnkgIywgIyMgYW5kICMjIykNCiAgICAgICAgICB0b2NfZmxvYXQ6IHRydWUgI0NvbiB0cnVlLCB0b2Mgc2FsZSBhbCBtYXJnZW4gaXpxdWllcmRvIGRlIGxhIHDDoWdpbmE7IGRlIGxvIGNvbnRyYXJpbywgYXJyaWJhDQogICAgICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlICAgIyBpZiB5b3Ugd2FudCBudW1iZXIgc2VjdGlvbnMgYXQgZWFjaCB0YWJsZSBoZWFkZXINCiAgICAgICAgICAjdGhlbWU6IHNhbmRzdG9uZQ0KICAgICAgICAgICN0aGVtZTogdW5pdGVkICAjIG1hbnkgb3B0aW9ucyBmb3IgdGhlbWUsIHRoaXMgb25lIGlzIG15IGZhdm9yaXRlLg0KICAgICAgICAgICN0aGVtZTogZmxhdGx5ICAjIA0KICAgICAgICAgICN0aGVtZTogY2VydWxlYW4gICMgDQogICAgICAgICAgI2hpZ2hsaWdodDogdGFuZ28gICMgc3BlY2lmaWVzIHRoZSBzeW50YXggaGlnaGxpZ2h0aW5nIHN0eWxlDQogICAgICAgICAgI2NzczogU2NyaXB0cyBhY2Nlc29yaW9zL2VzdGlsb2JvdG9uLmNzcw0KICAgICAgICAgICNjc3M6IG15LmNzcyAgICMgeW91IGNhbiBhZGQgeW91ciBjdXN0b20gY3NzLCBzaG91bGQgYmUgaW4gc2FtZSBmb2xkZXINCiAgICAgICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgICAgICAgI2hpZ2hsaWdodDogdGFuZ28gICMgY2FtYmlhciBjb2xvciBkZSBsaWJyYXJ5IGVuIGF6dWwNCiAgICAjIGJvb2tkb3duOjpnaXRib29rOg0KICAgICMgICAgICBpbmNsdWRlczoNCiAgICAjICAgICAgICBpbl9oZWFkZXI6IGhlYWRlci5odG1sDQogICAgIyBib29rZG93bjo6cGRmX2Jvb2s6DQogICAgIyAgICAgICBrZWVwX3RleDogeWVzDQogICAgIyBib29rZG93bjo6aHRtbF9ib29rOg0KICAgICMgICAgICAgY3NzOiB0b2MuY3NzDQogICAgIyBib29rZG93bjo6aHRtbF9ib29rOg0KICAgICMgICAgICAgICBpbmNsdWRlczoNCiAgICAjICAgICAgICAgICBpbl9oZWFkZXI6IHN0eWxlLmNzcw0KICAgICNib29rZG93bjo6aHRtbF9kb2N1bWVudDI6IGRlZmF1bHQNCiAgICAjIGJvb2tkb3duOjpwZGZfZG9jdW1lbnQyOg0KICAgICMgICAgICBrZWVwX3RleDogdHJ1ZQ0KICAgICNiaWJsaW9ncmFwaHk6IHJlZmVyZW5jZXMuYmliDQogICAgbWF0aGpheDogImh0dHA6Ly9leGFtcGxlLmNvbS9tYXRoamF4L01hdGhKYXguanM/Y29uZmlnPVRlWC1BTVMtTU1MX0hUTUxvck1NTCINCmhlYWRlci1pbmNsdWRlczoNCiAgICBcdXNlcGFja2FnZVt4MTFuYW1lc117eGNvbG9yfSANCiAgICBcdXNlcGFja2FnZXtncmFwaGljeH0NCiAgICBcdXNlcGFja2FnZXthcnJheX0NCiAgICANCmNzbDogc2NpZW5jZS5jc2wNCiNPam86IFNlIHV0aWxpemEgbGVuZ3VhamUgWUFNTA0KDQphYnN0cmFjdDogfA0KICoqT3RoZXIgcmVsYXRlZCBkb2N1bWVudHMgY2FuIGJlIGZvdW5kIGF0IFtScHViczo6IHRvY10oaHR0cHM6Ly9ycHVicy5jb20vaGxsaW5hcy90b2MpLioqDQogIA0KLS0tDQogIA0KIA0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHJldGljdWxhdGUpDQojIFNpIHF1aWVyZXMgZXNwZWNpZmljYXIgdW5hIHZlcnNpw7NuIGVzcGVjw61maWNhIGRlIFB5dGhvbjoNCiN1c2VfcHl0aG9uKCIvdXNyL2Jpbi9weXRob24iKQ0KIyBvIHVzYXIgdW4gZW50b3JubyB2aXJ0dWFsIG8gY29uZGE6DQojIHVzZV92aXJ0dWFsZW52KCJ+L21pbmljb25kYTMvZW52cy90b3JjaCIpIA0KIyANCiMgDQojIGNvbmRhIGNyZWF0ZSAtbiB0b3JjaCBweXRob249My4xMSAteQ0KI2NvbmRhIGFjdGl2YXRlIHRvcmNoDQojcGlwIGluc3RhbGwgbmx0aw0KDQp1c2VfY29uZGFlbnYoInRvcmNoIikNCg0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGZpZy5hbGlnbj0iY2VudGVyIiwgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UjLA0KICAgICAgICAgICAgICAgICAgICAjc3R5bGUgPSAiY29sb3I6ZGFya2JsdWUiDQogICAgICAgICAgICAgICAgICAgICMgY2xhc3Muc291cmNlPSJiZy1kYW5nZXIiLCBjbGFzcy5vdXRwdXQ9ImJnLXdhcm5pbmciICAgI0NvbG9yZXMgZGVudHJvIGRlbCBjaHVuaw0KICAgICAgICAgICAgICAgICAgICAgKQ0KbGlicmFyeShyZ2wpDQprbml0cjo6a25pdF9ob29rcyRzZXQod2ViZ2wgPSBob29rX3dlYmdsKQ0KYGBgDQoNCg0KDQo8IS0tIG1hcmtkb3dubGludC1kaXNhYmxlLW5leHQtbGluZSBNRDAzMyAtLT4NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCmJvZHl7IC8qIE5vcm1hbCAgKi8NCiAgICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgfQ0KDQovKiB0ZCB7IGZvbnQtc2l6ZTogOHB4OyB9ICBDb21lbnRhZG8gcGFyYSBubyBhZmVjdGFyIHRhbWHDsW8gZGUgdGFibGFzIGNvbiBrYWJsZUV4dHJhICovDQoNCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAzOHB4Ow0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMSAgeyAvKiBIZWFkZXIgMSAqLw0KICBmb250LXNpemU6IDIycHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOyAgLyogbyB1c2EgNzAwIHNpIHByZWZpZXJlcyAqLw0KICBjb2xvcjogQmxhY2s7DQp9DQpoMiB7IC8qIEhlYWRlciAyICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOyAgLyogbyB1c2EgNzAwIHNpIHByZWZpZXJlcyAqLw0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMyB7IC8qIEhlYWRlciAzICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgIGZvbnQtd2VpZ2h0OiBib2xkOyAgLyogbyB1c2EgNzAwIHNpIHByZWZpZXJlcyAqLw0KICAvKiBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsgKi8NCiAgY29sb3I6IERhcmtHcmVlbjsNCn0NCmg0IHsNCiAgZm9udC1zaXplOiAxOHB4Ow0KICBjb2xvcjogR3JlZW47DQogIGZvbnQtd2VpZ2h0OiA5MDA7ICAvKiBvIHVzYSA3MDAgc2kgcHJlZmllcmVzICovDQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KfQ0KDQpjb2RlLnJ7IC8qIENvZGUgYmxvY2sgKi8NCiAgICBmb250LXNpemU6IDEycHg7DQp9DQpwcmUgeyAvKiBDb2RlIGJsb2NrIC0gZGV0ZXJtaW5lcyBjb2RlIHNwYWNpbmcgYmV0d2VlbiBsaW5lcyAqLw0KICAgIGZvbnQtc2l6ZTogMTRweDsNCn0NCjwvc3R5bGU+DQoNCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZXZhbD1GQUxTRX0NCmh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi9sYW5ndWFnZS1lbmdpbmVzLmh0bWwNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvYm9va2Rvd24vbWFya2Rvd24tc3ludGF4Lmh0bWwNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvYm9va2Rvd24vYS1zaW5nbGUtZG9jdW1lbnQuaHRtbA0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ib29rZG93bi9tYXJrZG93bi1leHRlbnNpb25zLWJ5LWJvb2tkb3duLmh0bWwNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duL2Jvb2tkb3duLW1hcmtkb3duLmh0bWwgICMgVGVvcmVtcyBhbmQgcHJvb2ZzDQoNCmh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL2Jvb2tkb3duL21hcmtkb3duLWV4dGVuc2lvbnMtYnktYm9va2Rvd24uaHRtbCN0aGVvcmVtcw0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ib29rZG93bi9odG1sLmh0bWwNCg0KaHR0cHM6Ly93d3cuZGF0YS10by12aXouY29tLw0KICANCltScHVic10obGluaykNCiAgDQooXCNlcTplYy0pLCAgRWN1YWNpb24gXEByZWYoZXE6ZWMtKSwgRmlndXJhIFxAcmVmKGZpZzpGaWctKSwgVGFibGUgXEByZWYodGFiOm10Y2FycyksIFRoZW9yZW0gXEByZWYodGhtOmJvcmluZykNCg0KIyBUaXR1bG8geyNUaXR1bG9TZWNjaW9ufSAgIFxAcmVmKFRpdHVsb1NlY2Npb24pDQoNCiMgU2VlIEZpZ3VyZSBcQHJlZihmaWc6RmlnMS1UcmFuc2YpLiAgDQogICAgDQojIEZvciBIVE1MLCB3ZSBjYW4gc2V0IGNvbG9yIHdpdGggQ1NTLCBlLmcuLCA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPnRleHQ8L3NwYW4+DQogIA0KIyBodHRwczovL3JhZGlhbnQtcnN0YXRzLmdpdGh1Yi5pby9kb2NzL21vZGVsL2xvZ2lzdGljLmh0bWwgU2hpbm55IExvZ2l0ICANCiAgDQojIyMjIEVsIGPDs2RpZ28uICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCiMgY29uZGEgYWN0aXZhdGUgdG9yY2gNCmBgYA0KDQoNCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQ0KI0xhIGZvdG8gdGFtYcOxbyBjw6lkdWxhDQoNCmh0bWx0b29sczo6aW1nKHNyYyA9IGtuaXRyOjppbWFnZV91cmkoZmlsZS5wYXRoKFIuaG9tZSgiZG9jIiksICJodG1sIiwgImxvZ28uanBnIikpLCANCiAgICAgICAgICAgICAgIGFsdCA9ICdobGxpbmFzJywgDQogICAgICAgICAgICAgICBzdHlsZSA9ICdwb3NpdGlvbjphYnNvbHV0ZTsgdG9wOjA7IHJpZ2h0OjA7IHBhZGRpbmc6MTBweDsnLA0KICAgICAgICAgICAgICAgd2lkdGggPSAiMjAwcHgiKSAgIyBBcXXDrSBlc3BlY2lmaWNhcyBlbCBhbmNobyBkZXNlYWRvIGVuIHDDrXhlbGVzIG8gcG9yY2VudGFqZQ0KYGBgDQoNCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgfQ0KIyBMYSBmb3RvIGdyYW5kZQ0KDQpodG1sdG9vbHM6OmltZyhzcmMgPSBrbml0cjo6aW1hZ2VfdXJpKCJobGxpbmFzMjAyMy5qcGciKSwgDQogICAgICAgICAgICAgICBhbHQgPSAnaGxsaW5hczIwMjMnLCANCiAgICAgICAgICAgICAgIHN0eWxlID0gJ3Bvc2l0aW9uOmFic29sdXRlOyB0b3A6MDsgcmlnaHQ6MDsgcGFkZGluZzoxcHg7JywNCiAgICAgICAgICAgICAgIHdpZHRoPSIxNSUiKQ0KYGBgDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgIC0tPg0KDQpgYGB7Y3NzLCBlY2hvPUZBTFNFfQ0KLmNvbHVtbnMge2Rpc3BsYXk6IGZsZXg7fQ0KaDEge2NvbG9yOiBEYXJrQmx1ZTt9DQpoMyB7Y29sb3I6IERhcmtHcmVlbjt9DQpoNCB7Y29sb3I6IERhcmtHcmVlbjt9DQoNCg0KLmVycm9yLWJsb2NrIHsNCiAgbWFyZ2luLWxlZnQ6IDJlbTsNCn0NCg0KLmVycm9yLWJsb2NrIHN0cm9uZyB7DQogIG1hcmdpbi1sZWZ0OiAtMWVtOw0KfQ0KDQouc2FuZ3JpYTMgew0KICBtYXJnaW4tbGVmdDogM2VtOw0KfQ0KDQouc2FuZ3JpYTQgew0KICBtYXJnaW4tbGVmdDogNGVtOw0KfQ0KDQouc2FuZ3JpYTUgew0KICBtYXJnaW4tbGVmdDogNWVtOw0KfQ0KDQouc2FuZ3JpYTYgew0KICBtYXJnaW4tbGVmdDogNmVtOw0KfQ0KDQouc2FuZ3JpYTcgew0KICBtYXJnaW4tbGVmdDogN2VtOw0KfQ0KDQouc2FuZ3JpYTggew0KICBtYXJnaW4tbGVmdDogOGVtOw0KfQ0KDQouc2FuZ3JpYTkgew0KICBtYXJnaW4tbGVmdDogOWVtOw0KfQ0KDQpgYGANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIENhcMOtdHVsbyAxIC0tPg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZXZhbD1GQUxTRX0NCiNNdWx0aXBsZSBhdXRob3JzIGFuZCBzdWJ0aXRsZXMgaW4gUm1hcmtkb3duIHlhbWw6IA0KI2h0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzI2MDQzODA3L211bHRpcGxlLWF1dGhvcnMtYW5kLXN1YnRpdGxlcy1pbi1ybWFya2Rvd24teWFtbA0KDQojSW5zZXJ0IGEgbG9nbyBpbiB1cHBlciByaWdodCBjb3JuZXIgb2YgUiBtYXJrZG93biBodG1sIGRvY3VtZW50Og0KI2h0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQzMDA5Nzg4L2luc2VydC1hLWxvZ28taW4tdXBwZXItcmlnaHQtY29ybmVyLW9mLXItbWFya2Rvd24taHRtbC1kb2N1bWVudC80MzAxMDYzMg0KDQpgYGANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIFByZXJlcXVpc2l0ZXMgYW5kIHNvZnR3YXJlIHNldHVwIA0KDQpUaGUgZXhhbXBsZXMgYW5kIGV4ZXJjaXNlcyBwcmVzZW50ZWQgaW4gdGhpcyBjaGFwdGVyIHJlbHkgb24gYSBzbWFsbCBzZXQgb2Ygd2lkZWx5IHVzZWQgUHl0aG9uIGxpYnJhcmllcyBmb3IgdGV4dCBwcmVwcm9jZXNzaW5nLCB2ZWN0b3JpemF0aW9uLCBhbmQgbnVtZXJpY2FsIGNvbXB1dGF0aW9uLiBUbyBlbnN1cmUgdGhhdCBhbGwgY29kZSBydW5zIGNvcnJlY3RseSwgdGhlIHJlcXVpcmVkIHBhY2thZ2VzIGFuZCBsYW5ndWFnZSByZXNvdXJjZXMgc2hvdWxkIGJlIGluc3RhbGxlZCAqYmVmb3JlKiBleGVjdXRpbmcgdGhlIGV4YW1wbGVzIGluIHRoaXMgZG9jdW1lbnQuDQoNClRoZSBjb21tYW5kcyBiZWxvdyBhcmUgcHJvdmlkZWQgKmZvciByZWZlcmVuY2Ugb25seSogYW5kIHNob3VsZCBiZSBleGVjdXRlZCBpbiBhIFB5dGhvbiBlbnZpcm9ubWVudCAoZm9yIGV4YW1wbGUsIGEgdGVybWluYWwsIEFuYWNvbmRhIFByb21wdCwgb3IgYSBQeXRob24tZW5hYmxlZCBSIE1hcmtkb3duIHNldHVwIHVzaW5nIGByZXRpY3VsYXRlYCkuDQoNCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgQ29yZSBtYWNoaW5lIGxlYXJuaW5nIGFuZCBOTFAgbGlicmFyaWVzDQpwaXAgaW5zdGFsbCBzY2lraXQtbGVhcm4NCnBpcCBpbnN0YWxsIG5sdGsNCnBpcCBpbnN0YWxsIHBhbmRhcw0KcGlwIGluc3RhbGwgbnVtcHkNCnBpcCBpbnN0YWxsIHNlYWJvcm4NCnBpcCBpbnN0YWxsIHRhYnVsYXRlDQoNCiMgRG93bmxvYWQgcmVxdWlyZWQgTkxUSyByZXNvdXJjZXMNCnB5dGhvbiAtYyAiaW1wb3J0IG5sdGs7IG5sdGsuZG93bmxvYWQoJ3dvcmRuZXQnKSINCnB5dGhvbiAtYyAiaW1wb3J0IG5sdGs7IG5sdGsuZG93bmxvYWQoJ29tdy0xLjQnKSINCnB5dGhvbiAtYyAiaW1wb3J0IG5sdGs7IG5sdGsuZG93bmxvYWQoJ3N0b3B3b3JkcycpIg0KYGBgDQoNCk9uY2UgdGhlIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQsIHRoZSBmb2xsb3dpbmcgUHl0aG9uIG1vZHVsZXMgYXJlIGltcG9ydGVkIHRocm91Z2hvdXQgdGhpcyBkb2N1bWVudDoNCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgcGFuZGFzIGFzIHBkDQppbXBvcnQgcmUNCg0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQpmcm9tIG5sdGsuc3RlbS53b3JkbmV0IGltcG9ydCBXb3JkTmV0TGVtbWF0aXplcg0KDQpmcm9tIHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHQgaW1wb3J0IENvdW50VmVjdG9yaXplciwgVGZpZGZWZWN0b3JpemVyDQpmcm9tIHNrbGVhcm4ubWV0cmljcy5wYWlyd2lzZSBpbXBvcnQgY29zaW5lX3NpbWlsYXJpdHkNCmBgYA0KDQoNCiMjIyMgUm9sZSBvZiB0aGUgbGlicmFyaWVzLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpUaGUgcHVycG9zZSBvZiBlYWNoIGxpYnJhcnkgdXNlZCBpbiB0aGlzIGRvY3VtZW50IGlzIHN1bW1hcml6ZWQgYmVsb3c6DQoNCi0gYE5MVEtgIHByb3ZpZGVzIGJhc2ljIG5hdHVyYWwgbGFuZ3VhZ2UgcHJlcHJvY2Vzc2luZyB0b29scywgaW5jbHVkaW5nIHN0b3B3b3JkIHJlbW92YWwgYW5kIGxlbW1hdGl6YXRpb24uIE9ubHkgbGlnaHR3ZWlnaHQgbGluZ3Vpc3RpYyBwcm9jZXNzaW5nIGlzIHVzZWQgaW4gdGhpcyBkb2N1bWVudC4NCg0KLSBzY2lraXQtbGVhcm4gKGBza2xlYXJuYCkgc3VwcGxpZXMgdGhlIHZlY3Rvcml6YXRpb24gYW5kIHNpbWlsYXJpdHkgbWFjaGluZXJ5LCBpbmNsdWRpbmcgYENvdW50VmVjdG9yaXplcmAsIGBUZmlkZlZlY3Rvcml6ZXJgLCBhbmQgY29zaW5lIHNpbWlsYXJpdHkgY29tcHV0YXRpb24uDQoNCi0gYHBhbmRhc2BpcyB1c2VkIHRvIG1hbmFnZSB0ZXh0IGNvcnBvcmEgYXMgc3RydWN0dXJlZCBvYmplY3RzIChlLmcuLCBTZXJpZXMpIGFuZCB0byBhcHBseSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9ucyBjb25zaXN0ZW50bHkgYWNyb3NzIGRvY3VtZW50cy4NCg0KLSBgbnVtbnB5YCAgc3VwcG9ydHMgbnVtZXJpY2FsIG9wZXJhdGlvbnMgYW5kIHZlY3Rvci1iYXNlZCBjb21wdXRhdGlvbnMgcmVxdWlyZWQgZm9yIHNpbWlsYXJpdHkgY2FsY3VsYXRpb25zLg0KDQotIGBzZWFib3JuYCBpcyB1c2VkIGZvciBtYWtpbmcgc3RhdGlzdGljYWwgZ3JhcGhpY3MuDQoNCi0gYHRhYnVsYXRlYCBpcyB1c2VkIHRvIHByZXR0eS1wcmludCB0YWJ1bGFyIGRhdGEgaW4gYSBodW1hbi1yZWFkYWJsZSBmb3JtYXQuDQoNClRoZXNlIHRvb2xzIGFyZSBzdWZmaWNpZW50IHRvIGlsbHVzdHJhdGUgdGhlIGZ1bmRhbWVudGFsIGlkZWFzIGJlaGluZCBmcmVxdWVuY3ktYmFzZWQgdGV4dCByZXByZXNlbnRhdGlvbnMsIHdpdGhvdXQgaW50cm9kdWNpbmcgdW5uZWNlc3NhcnkgZGVwZW5kZW5jaWVzLg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIEludHJvZHVjdGlvbg0KDQojIyMgUHJlbGltaW5hcnMNCg0KVGV4dHVhbCBkYXRhIHBvc2VzIGEgZGlzdGluY3RpdmUgY2hhbGxlbmdlIGZvciBjb21wdXRhdGlvbmFsIGFuYWx5c2lzOiB1bmxpa2UgbnVtZXJpY2FsIG9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgbmF0dXJhbCBsYW5ndWFnZSBkb2VzIG5vdCBjb21lIHdpdGggYW4gaW5oZXJlbnQgbWF0aGVtYXRpY2FsIHJlcHJlc2VudGF0aW9uLiBXaGlsZSBjb21wdXRlcnMgb3BlcmF0ZSBleGNsdXNpdmVseSBvbiBudW1iZXJzLCBsYW5ndWFnZSBpcyBleHByZXNzZWQgdGhyb3VnaCBzeW1ib2xzLCB3b3JkcywgYW5kIHN0cnVjdHVyZXMgd2hvc2UgbWVhbmluZyBpcyBub3QgbmF0aXZlbHkgZW5jb2RlZCBpbiBudW1lcmljIGZvcm0uDQoNClRyYW5zZm9ybWluZyB0ZXh0IGludG8gbnVtYmVycyBpcyB0aGVyZWZvcmUgdW5hdm9pZGFibGUgKGJ1dCBpdCBpcyBhbHNvIGFuIG9wcG9ydHVuaXR5KS4gVGhlIHNwZWNpZmljIGNob2ljZXMgbWFkZSBkdXJpbmcgdGhpcyB0cmFuc2Zvcm1hdGlvbiBkZXRlcm1pbmUgd2hpY2ggYXNwZWN0cyBvZiBsYW5ndWFnZSBhcmUgcHJlc2VydmVkLCB3aGljaCBhcmUgc2ltcGxpZmllZCBvciBpZ25vcmVkLCBhbmQgaG93IGVmZmVjdGl2ZWx5IGxlYXJuaW5nIGFsZ29yaXRobXMgY2FuIG9wZXJhdGUgb24gbGluZ3Vpc3RpYyBkYXRhLiBJbiB0aGlzIHNlbnNlLCByZXByZXNlbnRhdGlvbiBjaG9pY2VzIGFyZSBub3QgbmV1dHJhbDogdGhleSBkaXJlY3RseSBpbmZsdWVuY2UgbW9kZWwgYmVoYXZpb3IsIGludGVycHJldGFiaWxpdHksIGFuZCBwZXJmb3JtYW5jZS4NCg0KSW4gdGhlIHByZXZpb3VzIGRvY3VtZW50IChbc2VlIHZvY2FidWxhcnkgY29uc3RydWN0aW9uXShodHRwczovL3JwdWJzLmNvbS9obGxpbmFzL1JfTkxQX3ZvY2FidWxhcnkpKSwgd2UgZm9jdXNlZCBvbiBkZWZpbmluZyB0aGUgKnN5bWJvbGljIHVuaXRzKiBvZiBsYW5ndWFnZSBwcm9jZXNzaW5nLCBpbmNsdWRpbmcgdG9rZW5pemF0aW9uIHN0cmF0ZWdpZXMsIG5vcm1hbGl6YXRpb24gcHJvY2VkdXJlcywgYW5kIHZvY2FidWxhcnkgZGVzaWduLiBUaGVzZSBzdGVwcyBlc3RhYmxpc2ggKndoYXQgY29uc3RpdHV0ZXMgYSB1bml0IG9mIGFuYWx5c2lzKi4gSW4gdGhpcyBjaGFwdGVyLCB3ZSBtb3ZlIHRvIHRoZSBuZXh0IHN0YWdlIG9mIHRoZSBwaXBlbGluZSBhbmQgZXhhbWluZSAqaG93IHRob3NlIHN5bWJvbGljIHVuaXRzIGFyZSB0cmFuc2Zvcm1lZCBpbnRvIG51bWVyaWNhbCBvYmplY3RzKi4NCg0KT3VyIGFwcHJvYWNoIGlzIGRlbGliZXJhdGVseSBpbmNyZW1lbnRhbC4gV2UgYmVnaW4gd2l0aCBzaW1wbGUgYW5kIHRyYW5zcGFyZW50IHJlcHJlc2VudGF0aW9ucyB0aGF0IGVtcGhhc2l6ZSAqb2JzZXJ2YWJsZSBzdHJ1Y3R1cmUqIHJhdGhlciB0aGFuIGRlZXAgc2VtYW50aWMgbWVhbmluZy4gQnkgcmVseWluZyBvbiBmcmVxdWVuY3kgY291bnRzIGFuZCBkaXN0cmlidXRpb25hbCBpbmZvcm1hdGlvbiwgd2UgY2FuIGNvbnN0cnVjdCByZXByZXNlbnRhdGlvbnMgdGhhdCBhcmUgZWFzeSB0byBpbnRlcnByZXQgYW5kIHRoYXQgcHJvdmlkZSBhIHNvbGlkIG1hdGhlbWF0aWNhbCBmb3VuZGF0aW9uIGZvciBtb3JlIGFkdmFuY2VkIHRlY2huaXF1ZXMuDQoNClRocm91Z2hvdXQgdGhpcyBjaGFwdGVyLCB3ZSBpbnRyb2R1Y2UgY2xhc3NpY2FsIG1ldGhvZHMgZm9yIG51bWVyaWNhbCB0ZXh0IHJlcHJlc2VudGF0aW9uLCBpbmNsdWRpbmcgKkJhZy1vZi1Xb3JkcyogYW5kICp0ZXJtIGZyZXF1ZW5jeeKAk2ludmVyc2UgZG9jdW1lbnQgZnJlcXVlbmN5IChURi1JREYpKi4gQWx0aG91Z2ggY29uY2VwdHVhbGx5IHN0cmFpZ2h0Zm9yd2FyZCwgdGhlc2UgbWV0aG9kcyByZW1haW4gd2lkZWx5IHVzZWQgaW4gcHJhY3RpY2UgKGZvciBiYXNlbGluZSBtb2RlbHMsIGV4cGxvcmF0b3J5IGFuYWx5c2lzLCBhbmQgaW5zdHJ1Y3Rpb25hbCBzZXR0aW5ncykuDQoNCkJlZm9yZSBpbnRyb2R1Y2luZyB0aGVzZSB0ZWNobmlxdWVzLCBpdCBpcyB1c2VmdWwgdG8gY2xhcmlmeSBhIGZ1bmRhbWVudGFsIGRpc3RpbmN0aW9uIHRoYXQgdW5kZXJsaWVzIGFsbCBsYW5ndWFnZSBtb2RlbGluZzogKnN5bnRheCB2ZXJzdXMgc2VtYW50aWNzKi4gU3ludGF4IGNvbmNlcm5zIHRoZSBzdHJ1Y3R1cmFsIG9yZ2FuaXphdGlvbiBvZiB3b3JkcyBhbmQgdGhlaXIgb2JzZXJ2YWJsZSBwYXR0ZXJucyBvZiBvY2N1cnJlbmNlLCB3aGVyZWFzIHNlbWFudGljcyByZWxhdGVzIHRvIG1lYW5pbmcgYW5kIGludGVycHJldGF0aW9uLiBBIHNlbnRlbmNlIG1heSBiZSBzeW50YWN0aWNhbGx5IHdlbGwtZm9ybWVkIHdpdGhvdXQgY29udmV5aW5nIG1lYW5pbmdmdWwgaW5mb3JtYXRpb24uDQoNCkluIHRoaXMgY2hhcHRlciwgdGhlIGVtcGhhc2lzIGlzIGludGVudGlvbmFsbHkgcGxhY2VkIG9uIHRoZSAqc3ludGFjdGljIGRpbWVuc2lvbiogb2YgbGFuZ3VhZ2UuIFdlIGZvY3VzIG9uIHJlcHJlc2VudGF0aW9ucyBkZXJpdmVkIGZyb20gd29yZCBvY2N1cnJlbmNlIHBhdHRlcm5zIChzdWNoIGFzIGNvdW50cyBhbmQgcmVsYXRpdmUgZnJlcXVlbmNpZXMpIHdoaWxlIHBvc3Rwb25pbmcgc2VtYW50aWMgcmVwcmVzZW50YXRpb25zIChlLmcuLCBlbWJlZGRpbmdzIGFuZCBuZXVyYWwgZW5jb2RpbmdzKSB0byBsYXRlciBjaGFwdGVycy4NCg0KQnkgdGhlIGVuZCBvZiB0aGlzIGNoYXB0ZXIsIHlvdSB3aWxsIGJlIGFibGUgdG8gcmVwcmVzZW50IHRleHQgdXNpbmcgdmVjdG9ycyBhbmQgbWF0cmljZXMsIGNvbXB1dGUgc2ltaWxhcml0aWVzIGJldHdlZW4gZG9jdW1lbnRzLCBhbmQgYnVpbGQgc2ltcGxlIGxhbmd1YWdlLWJhc2VkIGFwcGxpY2F0aW9ucy4gVGhlc2UgaWRlYXMgYWxzbyBzZXJ2ZSBhcyBhIGNvbmNlcHR1YWwgYnJpZGdlIHRvd2FyZCB0aGUgcmVwcmVzZW50YXRpb24gbGVhcm5pbmcgdGVjaG5pcXVlcyBlbXBsb3llZCBpbiBtb2Rlcm4gZGVlcCBsZWFybmluZyBhcmNoaXRlY3R1cmVzLCBpbmNsdWRpbmcgVHJhbnNmb3JtZXItYmFzZWQgbW9kZWxzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBNb3RpdmF0aW9uOiBGcm9tIHZlY3RvcnMgdG8gVHJhbnNmb3JtZXIgaW5wdXRzDQoNClRoZSBudW1lcmljYWwgcmVwcmVzZW50YXRpb25zIGludHJvZHVjZWQgaW4gdGhpcyBjaGFwdGVy4oCUc3VjaCBhcyBCYWctb2YtV29yZHMgYW5kIFRGLUlERi1pbGx1c3RyYXRlIGEgZnVuZGFtZW50YWwgcHJpbmNpcGxlOiBsYW5ndWFnZSBtdXN0IGJlIGVuY29kZWQgYXMgdmVjdG9ycyBiZWZvcmUgaXQgY2FuIGJlIHByb2Nlc3NlZCBieSBhbnkgY29tcHV0YXRpb25hbCBtb2RlbC4gQWx0aG91Z2ggdGhlc2UgcmVwcmVzZW50YXRpb25zIGFyZSByZWxhdGl2ZWx5IHNpbXBsZSBhbmQgcHJpbWFyaWx5IGNhcHR1cmUgKnN5bnRhY3RpYyBzdHJ1Y3R1cmUqLCB0aGV5IGVzdGFibGlzaCB0aGUgbWF0aGVtYXRpY2FsIGZvdW5kYXRpb24gcmVxdWlyZWQgZm9yIG1vcmUgYWR2YW5jZWQgbWV0aG9kcy4NCg0KSW4gbW9kZXJuIGFyY2hpdGVjdHVyZXMgc3VjaCBhcyB0aGUgVHJhbnNmb3JtZXIgWyhWYXN3YW5pIGV0IGFsLiwgMjAxNyldKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xNzA2LjAzNzYyKSwgdGV4dHVhbCBpbnB1dHMgYXJlIHVsdGltYXRlbHkgdGV4dCBjYW4gYmUgbWFwcGVkIGludG8gdmVjdG9yIHNwYWNlcyBhbmQgcHJvY2Vzc2VkIHRocm91Z2ggbXVsdGlwbGUgbGF5ZXJzIG9mIHRyYW5zZm9ybWF0aW9uLiBIb3dldmVyLCBpbnN0ZWFkIG9mIHJlbHlpbmcgb24gc3BhcnNlLCBoaWdoLWRpbWVuc2lvbmFsIGZyZXF1ZW5jeS1iYXNlZCB2ZWN0b3JzLCBUcmFuc2Zvcm1lcnMgZW1wbG95ICpkZW5zZSB2ZWN0b3IgcmVwcmVzZW50YXRpb25zKiAoZW1iZWRkaW5ncykgdGhhdCBjYXB0dXJlIHJpY2hlciBsaW5ndWlzdGljIGluZm9ybWF0aW9uLg0KDQpGaWd1cmUgXEByZWYoZmlnOkZpZy1UcmFuc2YpIHNob3dzIHRoYXQgdGhlIG1vZGVsIGJlZ2lucyB3aXRoIGFuICoqSW5wdXQgRW1iZWRkaW5nKiogc3RhZ2UsIHdoZXJlIGVhY2ggdG9rZW4gaXMgbWFwcGVkIGludG8gYSBjb250aW51b3VzIHZlY3RvciBzcGFjZS4gVGhlIHJlcHJlc2VudGF0aW9ucyBkZXZlbG9wZWQgaW4gdGhpcyBjaGFwdGVyIGNhbiBiZSBpbnRlcnByZXRlZCBhcyBhIGNvbmNlcHR1YWwgcHJlY3Vyc29yIHRvIHRoYXQgc3RhZ2U6IHRoZXkgZGVtb25zdHJhdGUgaG93IHRleHQgY2FuIGJlIGVtYmVkZGVkIGludG8gdmVjdG9yIHNwYWNlcywgZXZlbiB0aG91Z2ggdGhleSBkbyBub3QgeWV0IGNhcHR1cmUgc2VtYW50aWMgcmVsYXRpb25zaGlwcyBvciBjb250ZXh0dWFsIGRlcGVuZGVuY2llcy4NCg0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLVRyYW5zZiwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJHZW5lcmFsIGFyY2hpdGVjdHVyZSBvZiB0aGUgVHJhbnNmb3JtZXIgbW9kZWwuIFNvdXJjZTogW1Zhc3dhbmkgZXQgYWwuICgyMDE3KV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE3MDYuMDM3NjIpIiwgb3V0LndpZHRoID0gIjU1JSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiRmlnLVRyYW5zZi5wbmciKQ0KDQojT3RyYSBtYW5lcmEsIHBlcm8gbm8gc2FsZSBlbCBjYXB0aW9uOg0KIzxjZW50ZXI+DQojIVsoI2ZpZzpGaWctY2FwdGlvbikgTWkgZmlndXJhXShOb21icmUucG5nKXt3aWR0aD00MDBweH0NCiM8L2NlbnRlcj4NCmBgYA0KPC9jZW50ZXI+DQoNCg0KVGhpcyBoaWdobGlnaHRzIGFuIGltcG9ydGFudCB0cmFuc2l0aW9uOiB3aGlsZSBmcmVxdWVuY3ktYmFzZWQgbWV0aG9kcyBmb2N1cyBvbiBvYnNlcnZhYmxlIHN0cnVjdHVyZSwgbW9kZXJuIG5ldXJhbCBtb2RlbHMgcmVxdWlyZSByZXByZXNlbnRhdGlvbnMgdGhhdCBhbHNvIGNhcHR1cmUgbWVhbmluZyBhbmQgY29udGV4dC4gVGhpcyBoaWdobGlnaHRzIGFuIGltcG9ydGFudCB0cmFuc2l0aW9uOiB3aGlsZSBmcmVxdWVuY3ktYmFzZWQgbWV0aG9kcyBmb2N1cyBvbiBvYnNlcnZhYmxlIHN0cnVjdHVyZSwgbW9kZXJuIG5ldXJhbCBtb2RlbHMgcmVxdWlyZSByZXByZXNlbnRhdGlvbnMgdGhhdCBhbHNvIGNhcHR1cmUgbWVhbmluZyBhbmQgY29udGV4dC4gVGhpcyB0cmFuc2l0aW9uIChmcm9tIHN5bnRhY3RpYyByZXByZXNlbnRhdGlvbnMgdG8gc2VtYW50aWMgdmVjdG9yIHNwYWNlcykgaXMgZGV2ZWxvcGVkIGluIHRoZSBuZXh0IGRvY3VtZW50IChzZWUgW3dvcmQgZW1iZWRkaW5nc10oaHR0cHM6Ly9ycHVicy5jb20vaGxsaW5hcy9SX05MUF9lbWJlZGRpbmcxKSkuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBDaGFwdGVyIHJvYWRtYXAuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBtYWluIHRvcGljcyBjb3ZlcmVkIGluIHRoaXMgY2hhcHRlciBhcmU6DQoNCi0gVW5kZXJzdGFuZGluZyB2ZWN0b3JzIGFuZCBtYXRyaWNlcyBhcyBtYXRoZW1hdGljYWwgZGF0YSBzdHJ1Y3R1cmVzICANCg0KLSBFeHBsb3JpbmcgdGhlIEJhZy1vZi1Xb3JkcyAoQm9XKSByZXByZXNlbnRhdGlvbiAgDQoNCi0gQ29uc3RydWN0aW5nIFRG4oCTSURGIHZlY3RvcnMgIA0KDQotIE1lYXN1cmluZyBkaXN0YW5jZSBhbmQgc2ltaWxhcml0eSBiZXR3ZWVuIGRvY3VtZW50IHZlY3RvcnMgIA0KDQotIE9uZS1ob3QgdmVjdG9yaXphdGlvbiAgDQoNCi0gQnVpbGRpbmcgYSBiYXNpYyBjaGF0Ym90ICANCiAgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyBVbmRlcnN0YW5kaW5nIHZlY3RvcnMgYW5kIG1hdHJpY2VzDQoNCkEgY2VudHJhbCBjaGFsbGVuZ2UgaW4gTkxQIGlzIGV4cHJlc3NpbmcgbGFuZ3VhZ2UgaW4gbWF0aGVtYXRpY2FsIGZvcm0uIFR3byBkYXRhIHN0cnVjdHVyZXMgcGxheSBhIGZ1bmRhbWVudGFsIHJvbGUgaW4gdGhpcyB0cmFuc2Zvcm1hdGlvbjogKip2ZWN0b3JzKiogYW5kICoqbWF0cmljZXMqKi4gVG9nZXRoZXIsIHRoZXkgYWxsb3cgY29sbGVjdGlvbnMgb2YgdGV4dCBkb2N1bWVudHMgdG8gYmUgYW5hbHl6ZWQgdXNpbmcgdGhlIHRvb2xzIG9mIGxpbmVhciBhbGdlYnJhLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBWZWN0b3JzDQoNCiMjIyMgRGVmaW5pdGlvbiBhbmQgbm90YXRpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KQSB2ZWN0b3IgaXMgYSBvbmUtZGltZW5zaW9uYWwgYXJyYXkgb2YgbnVtZXJpY2FsIHZhbHVlcywgd2hlcmUgZWFjaCBwb3NpdGlvbiBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIGZlYXR1cmUuIFZlY3RvcnMgYXJlIGNvbW1vbmx5IHJlcHJlc2VudGVkIGFzIGNvbHVtbiBhcnJheXM6DQoNCiQkDQpcbWF0aGJme3h9ID0NClxiZWdpbntibWF0cml4fQ0KeF8xIFxcDQp4XzIgXFwNCnhfMyANClxlbmR7Ym1hdHJpeH0sIFxxdWFkIFxtYXRoYmZ7dn0gPVxiZWdpbntibWF0cml4fQ0Kdl8xIFxcDQp2XzIgXFwNCnZfMyBcXA0Kdl80DQpcZW5ke2JtYXRyaXh9DQokJA0KDQpJbiB0aGlzIGV4cHJlc3Npb24sIHRoZSB2ZWN0b3IgJFxtYXRoYmZ7eH0kIGNvbnRhaW5zIHRocmVlIGNvbXBvbmVudHMgYW5kIGJlbG9uZ3MgdG8gJFxtYXRoYmJ7Un1eMyQsIHdoaWxlICRcbWF0aGJme3Z9JCBjb250YWlucyBmb3VyIGNvbXBvbmVudHMgYW5kIGJlbG9uZ3MgdG8gJFxtYXRoYmJ7Un1eNCQuIEVhY2ggY29vcmRpbmF0ZSByZXByZXNlbnRzIHRoZSBjb250cmlidXRpb24gb2YgdGhlIHZlY3RvciBhbG9uZyBhIHBhcnRpY3VsYXIgYXhpcy4gT25jZSBhbiBvYmplY3QgaXMgcmVwcmVzZW50ZWQgYXMgYSB2ZWN0b3IsIG9wZXJhdGlvbnMgc3VjaCBhcyBkaXN0YW5jZSBjb21wdXRhdGlvbiwgc2ltaWxhcml0eSBtZWFzdXJlbWVudCwgYW5kIHByb2plY3Rpb24gYmVjb21lIHdlbGwgZGVmaW5lZC4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIEdlb21ldHJpYyBpbnR1aXRpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KVG8gZGV2ZWxvcCBnZW9tZXRyaWMgaW50dWl0aW9uLCBjb25zaWRlciByZXByZXNlbnRpbmcgZW50aXRpZXMgdXNpbmcgbWVhc3VyYWJsZSBhdHRyaWJ1dGVzLiBTdXBwb3NlIHdlIGRlc2NyaWJlIHR3byBjaXRpZXMgdXNpbmcgdGhlaXIgYXZlcmFnZSBhbm51YWwgdGVtcGVyYXR1cmUgYW5kIGFubnVhbCByYWluZmFsbDoNCg0KXFsNClxiZWdpbnthcnJheX17Y3xjY30NClx0ZXh0e0NpdHl9ICYgXHRleHR7VGVtcGVyYXR1cmUgKMKwQyl9ICYgXHRleHR7UmFpbmZhbGwgKG1tKX0gXFwNClxobGluZQ0KXHRleHR7QX0gJiAxOCAmIDUwMCBcXA0KXHRleHR7Qn0gJiAyNSAmIDExMDANClxlbmR7YXJyYXl9DQpcXQ0KDQoNCkVhY2ggY2l0eSBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgYSBwb2ludCBpbiBhIHR3by1kaW1lbnNpb25hbCBzcGFjZSwgb3IgZXF1aXZhbGVudGx5LCBhcyBhIHZlY3Rvci4NCg0KLSBDaXR5IEEgY29ycmVzcG9uZHMgdG8gdGhlIHZlY3RvciAkXG1hdGhiZnt4fV9BPSAoMTgsIDUwMCkkLg0KDQotIENpdHkgQiBjb3JyZXNwb25kcyB0byB0aGUgdmVjdG9yICRcbWF0aGJme3h9X0I9ICgyNSwgMTEwMCkkLg0KDQoNCg0KRnJvbSBhIG1hdGhlbWF0aWNhbCBwZXJzcGVjdGl2ZSwgYm90aCB2ZWN0b3JzIGJlbG9uZyB0byAkXG1hdGhiYntSfV4yJC4gSGVyZSBpcyB0aGUgY29ycmVzcG9uZGluZyB2aXN1YWxpemF0aW9uOiAgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIERhdGE6IGNpdGllcyBhcyB2ZWN0b3JzDQpkZiA8LSBkYXRhLmZyYW1lKA0KICBDaXR5ID0gYygiQ2l0eSBBIiwgIkNpdHkgQiIpLA0KICBUZW1wZXJhdHVyZSA9IGMoMTgsIDI1KSwNCiAgUmFpbmZhbGwgPSBjKDUwMCwgMTEwMCkNCikNCg0KZ2dwbG90KGRmLCBhZXMoeCA9IFRlbXBlcmF0dXJlLCB5ID0gUmFpbmZhbGwpKSArDQogIA0KICAjIFZlY3RvcnMgZnJvbSB0aGUgb3JpZ2luDQogIGdlb21fc2VnbWVudCgNCiAgICBhZXMoeCA9IDAsIHkgPSAwLCB4ZW5kID0gVGVtcGVyYXR1cmUsIHllbmQgPSBSYWluZmFsbCksDQogICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMjUsICJjbSIpKSwNCiAgICBsaW5ld2lkdGggPSAxLA0KICAgIGNvbG9yID0gInN0ZWVsYmx1ZSINCiAgKSArDQogIA0KICAjIFBvaW50cyBhdCB2ZWN0b3IgdGlwcw0KICBnZW9tX3BvaW50KHNpemUgPSAzLCBjb2xvciA9ICJzdGVlbGJsdWUiKSArDQogIA0KICAjIExhYmVscw0KICBnZW9tX3RleHQoDQogICAgYWVzKGxhYmVsID0gQ2l0eSksDQogICAgaGp1c3QgPSAtMC4xNSwNCiAgICB2anVzdCA9IC0wLjQsDQogICAgc2l6ZSA9IDQNCiAgKSArDQogIA0KICBsYWJzKA0KICAgIHggPSAiQXZlcmFnZSBUZW1wZXJhdHVyZSAowrBDKSIsDQogICAgeSA9ICJBbm51YWwgUmFpbmZhbGwgKG1tKSIsDQogICAgdGl0bGUgPSAiQ2l0aWVzIHJlcHJlc2VudGVkIGFzIHZlY3RvcnMgaW4gYSB0d28tZGltZW5zaW9uYWwgc3BhY2UiDQogICkgKw0KICANCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjIpKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMSkpKSArDQogIA0KICBleHBhbmRfbGltaXRzKHggPSAwLCB5ID0gMCkgKw0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDExKQ0KYGBgDQoNCg0KRWFjaCB2ZWN0b3Igb3JpZ2luYXRlcyBhdCB0aGUgY29vcmRpbmF0ZSBzeXN0ZW3igJlzIG9yaWdpbiBhbmQgcG9pbnRzIHRvd2FyZCBhIGxvY2F0aW9uIGRldGVybWluZWQgYnkgdGhlIGNvcnJlc3BvbmRpbmcgYXR0cmlidXRlcy4gQWRkaW5nIGEgbmV3IGF0dHJpYnV0ZSAoc3VjaCBhcyBhbHRpdHVkZSBvciBwb3B1bGF0aW9uIGRlbnNpdHkpIGluY3JlYXNlcyB0aGUgZGltZW5zaW9uYWxpdHkgb2YgdGhlIHJlcHJlc2VudGF0aW9uLCBtb3ZpbmcgdGhlIHZlY3RvcnMgZnJvbQ0KJFxtYXRoYmJ7Un1eMiQgdG8gJFxtYXRoYmJ7Un1eMyQgb3IgaGlnaGVyLg0KDQpXaGlsZSBzdWNoIHNwYWNlcyBxdWlja2x5IGJlY29tZSBkaWZmaWN1bHQgdG8gdmlzdWFsaXplLCB0aGUgYWxnZWJyYWljIGludGVycHJldGF0aW9uIG9mIHZlY3RvcnMgcmVtYWlucyB2YWxpZCBpbiBhbnkgZGltZW5zaW9uLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBGcm9tIGdlb21ldHJpYyB2ZWN0b3JzIHRvIHRleHQgcmVwcmVzZW50YXRpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KVGhlIHNhbWUgaWRlYSBleHRlbmRzIG5hdHVyYWxseSB0byB0ZXh0dWFsIGRhdGEuDQoNCkFmdGVyIHRva2VuaXphdGlvbiBpbnRyb2R1Y2VkIGluIHRoZSBwcmV2aW91cyBkb2N1bWVudCAoc2VlIFtWb2NhYnVsYXJ5IGFuZCB0ZXh0IG5vcm1hbGl6YXRpb25dKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvUl9OTFBfdm9jYWJ1bGFyeSkpLCBhIGRvY3VtZW50IGNhbiBiZSByZXByZXNlbnRlZCBhcyBhIHZlY3RvciBpbiB3aGljaCAqZWFjaCBkaW1lbnNpb24gY29ycmVzcG9uZHMgdG8gYSB1bmlxdWUgdG9rZW4qIGluIHRoZSB2b2NhYnVsYXJ5LiBUaGUgdmFsdWUgYWxvbmcgZWFjaCBkaW1lbnNpb24gcmVmbGVjdHMgaG93IGZyZXF1ZW50bHkgdGhhdCB0b2tlbiBhcHBlYXJzIGluIHRoZSBkb2N1bWVudC4NCg0KSW4gdGhpcyB3YXksIHRleHQgaXMgbWFwcGVkIGludG8gYSBoaWdoLWRpbWVuc2lvbmFsIHZlY3RvciBzcGFjZSwgd2hlcmUgZWFjaCBkb2N1bWVudCBjb3JyZXNwb25kcyB0byBhIHBvaW50LiBUaGlzIHJlcHJlc2VudGF0aW9uIGVuYWJsZXMgdGhlIGFwcGxpY2F0aW9uIG9mIHZlY3Rvci1iYXNlZCBvcGVyYXRpb25zIHN1Y2ggYXMgc2ltaWxhcml0eSBtZWFzdXJlbWVudCwgZGlzdGFuY2UgY29tcHV0YXRpb24sIGFuZCBjbHVzdGVyaW5nLCBmb3JtaW5nIHRoZSBiYXNpcyBvZiBtYW55IG1ldGhvZHMgaW4gbmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIE1hdHJpY2VzDQoNCk1hdHJpY2VzIGV4dGVuZCB2ZWN0b3JzIGJ5IG9yZ2FuaXppbmcgbXVsdGlwbGUgdmVjdG9ycyBpbnRvIHJvd3MgYW5kIGNvbHVtbnMuIEEgbWF0cml4IGNhbiBiZSB3cml0dGVuIGFzOg0KDQokJA0KXG1hdGhiZntYfSA9DQpcYmVnaW57Ym1hdHJpeH0NCnhfezExfSAmIHhfezEyfSBcXA0KeF97MjF9ICYgeF97MjJ9IFxcDQp4X3szMX0gJiB4X3szMn0NClxlbmR7Ym1hdHJpeH0NCiQkDQoNClRoaXMgbWF0cml4IGJlbG9uZ3MgdG8gJFxtYXRoYmJ7Un1eezMgXHRpbWVzIDJ9JCwgaW5kaWNhdGluZyB0aHJlZSByb3dzIGFuZCB0d28gY29sdW1ucy4gSW4gdGV4dCBhbmFseXNpcywgbWF0cmljZXMgYXJlIGNvbW1vbmx5IHVzZWQgdG8gcmVwcmVzZW50ICoqY29sbGVjdGlvbnMgb2YgZG9jdW1lbnRzKiouIEVhY2ggcm93IGNvcnJlc3BvbmRzIHRvIGEgZG9jdW1lbnQsIGVhY2ggY29sdW1uIGNvcnJlc3BvbmRzIHRvIGEgdG9rZW4gaW4gdGhlIHZvY2FidWxhcnksIGFuZCBlYWNoIGVudHJ5IHN0b3JlcyB0aGUgZnJlcXVlbmN5IG9mIHRoYXQgdG9rZW4gaW4gdGhlIGRvY3VtZW50Lg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIE1hdHJpeCByZXByZXNlbnRhdGlvbjogYSBzaW1wbGUgZXhhbXBsZQ0KDQoNCiMjIyMgRnJvbSB0ZXh0IHRvIG1hdHJpeCByZXByZXNlbnRhdGlvbi4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KDQpUbyBpbGx1c3RyYXRlIGhvdyB0ZXh0IGNhbiBiZSBvcmdhbml6ZWQgaW50byBtYXRyaXggZm9ybSwgY29uc2lkZXIgdGhlIGZvbGxvd2luZyBzbWFsbCBjb2xsZWN0aW9uIG9mIGRvY3VtZW50czoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyICMgMQ0KDQpkb2N1bWVudHMgPSAoDQogICAgIlRleHQgYW5hbHlzaXMgcmVsaWVzIG9uIG51bWVyaWNhbCByZXByZXNlbnRhdGlvbnMiLA0KICAgICJWZWN0b3JzIGFuZCBtYXRyaWNlcyBhcmUgY29yZSBtYXRoZW1hdGljYWwgdG9vbHMiLA0KICAgICJMYXJnZSBjb2xsZWN0aW9ucyBvZiB0ZXh0IGNhbiBiZSBwcm9jZXNzZWQgZWZmaWNpZW50bHkiDQopDQoNCnZlY3Rvcml6ZXIgPSBDb3VudFZlY3Rvcml6ZXIoc3RvcF93b3Jkcz0iZW5nbGlzaCIpICAjIDINCnZlY3Rvcml6ZXINCg0KWCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpICAgICAgICAgICAgICMgMw0KWA0KDQojIEluc3BlY3QgdGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSBhbmQgZG9jdW1lbnQtdGVybSBtYXRyaXgNCnByaW50KHZlY3Rvcml6ZXIudm9jYWJ1bGFyeV8pICAjIDQgLS0+IGZpcnN0IG91dHB1dA0KcHJpbnQoWC50b2RlbnNlKCkpICAgICAgICAgICAgICMgNWEgLS0+IHNlY29uZCBvdXRwdXQNCnByaW50KFgudG9hcnJheSgpKSAgICAgICAgICAgICAjIDViIC0tPiBzZWNvbmQgb3V0cHV0DQpgYGANCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhwbGFuYXRpb24gb2YgdGhlIGNvZGUuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KKipDb2RlIDEuKiogDQoNCjo6OnNhbmdyaWEzDQpUaGUgY29kZSBiZWdpbnMgYnkgaW1wb3J0aW5nIGBDb3VudFZlY3Rvcml6ZXJgIGZyb20gYHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHRgLCBhIHRvb2wgZGVzaWduZWQgdG8gdHJhbnNmb3JtIGEgY29sbGVjdGlvbiBvZiB0ZXh0IGRvY3VtZW50cyBpbnRvIGEgZG9jdW1lbnTigJN0ZXJtIG1hdHJpeC4gTmV4dCwgYSBzbWFsbCBjb3JwdXMgb2YgdGhyZWUgc2hvcnQgZG9jdW1lbnRzIGlzIGRlZmluZWQuIA0KOjo6DQoNCg0KKipDb2RlIDIuKiogDQoNCjo6OnNhbmdyaWEzDQpUaGUgaW5zdHJ1Y3Rpb24gYENvdW50VmVjdG9yaXplcihzdG9wX3dvcmRzPSJlbmdsaXNoIilgIGNyZWF0ZXMgYSB2ZWN0b3JpemVyIHRoYXQgYXV0b21hdGljYWxseSB0b2tlbml6ZXMgdGhlIHRleHQsIGV4dHJhY3RzIGEgc2V0IG9mIHVuaXF1ZSB0b2tlbnMgKHRoZSB2b2NhYnVsYXJ5KSwgYW5kIHJlbW92ZXMgY29tbW9uIEVuZ2xpc2ggc3RvcHdvcmRzIHN1Y2ggYXMgKmFuZCosICphcmUqLCBvciAqb2YqLiANCg0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBDb3VudFZlY3Rvcml6ZXINCg0KZG9jdW1lbnRzID0gKA0KICAgICJUZXh0IGFuYWx5c2lzIHJlbGllcyBvbiBudW1lcmljYWwgcmVwcmVzZW50YXRpb25zIiwNCiAgICAiVmVjdG9ycyBhbmQgbWF0cmljZXMgYXJlIGNvcmUgbWF0aGVtYXRpY2FsIHRvb2xzIiwNCiAgICAiTGFyZ2UgY29sbGVjdGlvbnMgb2YgdGV4dCBjYW4gYmUgcHJvY2Vzc2VkIGVmZmljaWVudGx5Ig0KKQ0KDQp2ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKHN0b3Bfd29yZHM9ImVuZ2xpc2giKQ0KdmVjdG9yaXplcg0KIyNYID0gdmVjdG9yaXplci5maXRfdHJhbnNmb3JtKGRvY3VtZW50cykgICAgICAgICAgICAgIyAzDQojWA0KDQoNCiMgSW5zcGVjdCB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5IGFuZCBkb2N1bWVudC10ZXJtIG1hdHJpeA0KIyBwcmludCh2ZWN0b3JpemVyLnZvY2FidWxhcnlfKSAgIyA0IC0tPiBmaXJzdCBvdXRwdXQNCiMgcHJpbnQoWC50b2RlbnNlKCkpICAgICAgICAgICAgICMgNSAtLT4gc2Vjb25kIG91dHB1dA0KYGBgDQoNCg0KVGhlIG91dHB1dCBkaXNwbGF5ZWQgYWZ0ZXIgZXZhbHVhdGluZyBgdmVjdG9yaXplcmAgZG9lcyBub3QgY29ycmVzcG9uZCB0byBkYXRhLCBidXQgdG8gdGhlIGludGVybmFsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBgQ291bnRWZWN0b3JpemVyYCBvYmplY3QuIEl0IHNpbXBseSBjb25maXJtcyB0aGF0IHRoZSBvYmplY3QgaGFzIGJlZW4gc3VjY2Vzc2Z1bGx5IGNyZWF0ZWQgd2l0aCB0aGUgc3BlY2lmaWVkIHBhcmFtZXRlcnMuDQoNCkF0IHRoaXMgc3RhZ2UsIHRoZSB2ZWN0b3JpemVyIGhhcyBub3QgeWV0IGJlZW4gZml0dGVkIHRvIHRoZSBkYXRhLCBtZWFuaW5nIHRoYXQgbm8gdm9jYWJ1bGFyeSBoYXMgYmVlbiBsZWFybmVkIGFuZCBubyBtYXRyaXggaGFzIGJlZW4gY29uc3RydWN0ZWQuIFRoZSBleHBhbmRhYmxlICpQYXJhbWV0ZXJzKiBzZWN0aW9uIHJlZmxlY3RzIHRoZSBjb25maWd1cmF0aW9uIG9mIHRoZSBvYmplY3QgKHN1Y2ggYXMgc3RvcHdvcmQgcmVtb3ZhbCksIHJhdGhlciB0aGFuIGFueSBsZWFybmVkIGluZm9ybWF0aW9uLg0KDQpJbiBvdGhlciB3b3Jkcywgd2UgYXJlIG5vdCB5ZXQgc2VlaW5nIHRoZSBkYXRhICh3ZSBhcmUgb25seSBkZWZpbmluZyB0aGUgdG9vbCB0aGF0IHdpbGwgcHJvY2VzcyBpdCkuDQoNCk9ubHkgYWZ0ZXIgYXBwbHlpbmcgYGZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKWAgZG9lcyB0aGUgdmVjdG9yaXplciBsZWFybiB0aGUgdm9jYWJ1bGFyeSBhbmQgZ2VuZXJhdGUgdGhlIGRvY3VtZW50LXRlcm0gbWF0cml4Lg0KOjo6DQoNCg0KKipDb2RlIDMuKiogDQoNCjo6OnNhbmdyaWEzDQpUaGUgbGluZSBgZml0X3RyYW5zZm9ybShkb2N1bWVudHMpYCB0aGVuIHBlcmZvcm1zIHR3byB0YXNrcyBzaW11bHRhbmVvdXNseTogDQoNCi0gSXQgZmlyc3QgbGVhcm5zIHRoZSB2b2NhYnVsYXJ5IGZyb20gdGhlIGNvcnB1cyAoYGZpdGApIGFuZCB0aGVuIA0KDQotIEl0IGNvbnZlcnRzIHRoZSBkb2N1bWVudHMgaW50byBhIG51bWVyaWNhbCBtYXRyaXggcmVwcmVzZW50YXRpb24gKGB0cmFuc2Zvcm1gKS4NCg0KVGhlIHJlc3VsdGluZyBvYmplY3QgYFhgIGlzIGEgc3BhcnNlIGRvY3VtZW50LXRlcm0gbWF0cml4LCB3aGVyZSByb3dzIGNvcnJlc3BvbmQgdG8gZG9jdW1lbnRzIGFuZCBjb2x1bW5zIGNvcnJlc3BvbmQgdG8gdm9jYWJ1bGFyeSB0ZXJtcy4NCg0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBDb3VudFZlY3Rvcml6ZXINCg0KZG9jdW1lbnRzID0gKA0KICAgICJUZXh0IGFuYWx5c2lzIHJlbGllcyBvbiBudW1lcmljYWwgcmVwcmVzZW50YXRpb25zIiwNCiAgICAiVmVjdG9ycyBhbmQgbWF0cmljZXMgYXJlIGNvcmUgbWF0aGVtYXRpY2FsIHRvb2xzIiwNCiAgICAiTGFyZ2UgY29sbGVjdGlvbnMgb2YgdGV4dCBjYW4gYmUgcHJvY2Vzc2VkIGVmZmljaWVudGx5Ig0KKQ0KDQp2ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKHN0b3Bfd29yZHM9ImVuZ2xpc2giKQ0KI3ZlY3Rvcml6ZXINClggPSB2ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKSAgICAgICAgICAgICAjIDMNClgNCg0KDQojIEluc3BlY3QgdGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSBhbmQgZG9jdW1lbnQtdGVybSBtYXRyaXgNCiMgcHJpbnQodmVjdG9yaXplci52b2NhYnVsYXJ5XykgICMgNCAtLT4gZmlyc3Qgb3V0cHV0DQojIHByaW50KFgudG9kZW5zZSgpKSAgICAgICAgICAgICAjIDUgLS0+IHNlY29uZCBvdXRwdXQNCmBgYA0KDQoNClRoZSBvYmplY3QgYFhgIGlzIG5vdCBkaXNwbGF5ZWQgYXMgYSBmdWxsIG1hdHJpeCBiZWNhdXNlIGl0IGlzIHN0b3JlZCBpbiBhICpzcGFyc2UgZm9ybWF0Ki4gSW5zdGVhZCwgUHl0aG9uIGRpc3BsYXlzIGEgc3VtbWFyeSBvZiBpdHMgc3RydWN0dXJlOg0KDQotIFRoZSBgc2hhcGUgKDMsIDE0KWAgaW5kaWNhdGVzIHRoYXQgdGhlIG1hdHJpeCBoYXMgMyByb3dzIChkb2N1bWVudHMpIGFuZCAxNCBjb2x1bW5zICh1bmlxdWUgdG9rZW5zIGluIHRoZSB2b2NhYnVsYXJ5KS4NCg0KLSBUaGUgZXhwcmVzc2lvbiBg4oCcMTUgc3RvcmVkIGVsZW1lbnRz4oCdYCBtZWFucyB0aGF0IG9ubHkgMTUgZW50cmllcyBpbiB0aGUgbWF0cml4IGFyZSBub256ZXJvLiBUaGlzIHJlZmxlY3RzIHRoZSBzcGFyc2l0eSBvZiB0ZXh0dWFsIGRhdGEsIHdoZXJlIG1vc3QgdG9rZW5zIGRvIG5vdCBhcHBlYXIgaW4gbW9zdCBkb2N1bWVudHMuDQoNCi0gVGhlIHRlcm0gYENvbXByZXNzZWQgU3BhcnNlIFJvdyAoQ1NSKWAgcmVmZXJzIHRvIHRoZSBpbnRlcm5hbCByZXByZXNlbnRhdGlvbiB1c2VkIHRvIGVmZmljaWVudGx5IHN0b3JlIGFuZCBtYW5pcHVsYXRlIHNwYXJzZSBtYXRyaWNlcyBieSBrZWVwaW5nIHRyYWNrIG9ubHkgb2Ygbm9uemVybyBlbnRyaWVzLg0KDQpUaGlzIGNvbXBhY3QgcmVwcmVzZW50YXRpb24gaXMgZXNzZW50aWFsIGZvciBoYW5kbGluZyBsYXJnZSB0ZXh0IGNvcnBvcmEsIHdoZXJlIHRoZSBkb2N1bWVudOKAk3Rlcm0gbWF0cml4IGNhbiBoYXZlIHRob3VzYW5kcyBvciBldmVuIG1pbGxpb25zIG9mIGNvbHVtbnMuDQoNCg0KSW4gYWRkaXRpb24gdG8gcHJpbnRpbmcgdGhlIG1hdHJpeCwgc2V2ZXJhbCBhdHRyaWJ1dGVzIGNhbiBiZSB1c2VkIHRvIGJldHRlciB1bmRlcnN0YW5kIGl0cyBzdHJ1Y3R1cmU6DQoNCi0gYFguc2hhcGVgIHJldHVybnMgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIG1hdHJpeCAobnVtYmVyIG9mIGRvY3VtZW50cyDDlyB2b2NhYnVsYXJ5IHNpemUpLg0KDQotIGBYLm5uemAgZ2l2ZXMgdGhlIG51bWJlciBvZiBub256ZXJvIGVudHJpZXMsIGluZGljYXRpbmcgaG93IHNwYXJzZSB0aGUgbWF0cml4IGlzLg0KDQotIGB2ZWN0b3JpemVyLmdldF9mZWF0dXJlX25hbWVzX291dCgpYCByZXR1cm5zIHRoZSBvcmRlcmVkIGxpc3Qgb2YgdG9rZW5zIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGNvbHVtbnMgb2YgdGhlIG1hdHJpeC4NCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0KWC5zaGFwZQ0KWC5ubnoNCnZlY3Rvcml6ZXIuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkNCmBgYA0KDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpYLnNoYXBlDQpYLm5ueg0KdmVjdG9yaXplci5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKQ0KYGBgDQoNClRoZXNlIHRvb2xzIGFsbG93IHVzIHRvIGludGVycHJldCB0aGUgbWF0cml4IG1vcmUgcHJlY2lzZWx5IHdpdGhvdXQgY29udmVydGluZyBpdCBpbnRvIGEgZGVuc2UgcmVwcmVzZW50YXRpb24uIFRoZSBzcGFyc2UgcmVwcmVzZW50YXRpb24gaGlkZXMgbW9zdCBvZiB0aGUgbWF0cml4IGVudHJpZXMgKHdoaWNoIGFyZSB6ZXJvKSwgYnV0IGl0IHByZXNlcnZlcyBhbGwgdGhlIGluZm9ybWF0aW9uIG5lZWRlZCB0byByZWNvbnN0cnVjdCB0aGUgZnVsbCBkb2N1bWVudOKAk3Rlcm0gbWF0cml4IHdoZW4gcmVxdWlyZWQuDQo6OjoNCg0KKipDb2RlcyA0IGFuZCA1KiouIA0KDQo6OjpzYW5ncmlhMw0KRmluYWxseSwgdGhlIGNvZGUgcHJvZHVjZXMgdHdvIGV4cGxpY2l0IG91dHB1dHM6DQoNCi0gVGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSAoYHZlY3Rvcml6ZXIudm9jYWJ1bGFyeV9gKSwgd2hpY2ggbWFwcyBlYWNoIHRva2VuIHRvIGEgY29sdW1uIGluZGV4LiANCg0KLSBUaGUgZGVuc2UgdmVyc2lvbiBvZiB0aGUgbWF0cml4IChgWC50b2RlbnNlKClgIG9yIGBYLnRvYXJyYXkoKWApLCB3aGljaCBtYWtlcyB0aGUgZnVsbCBzdHJ1Y3R1cmUgZWFzaWVyIHRvIGluc3BlY3QgaW4gc21hbGwgZXhhbXBsZXMuDQo6OjoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZTogaW50ZXJwcmV0aW5nIHRoZSBmaXJzdCBvdXRwdXQgKGNvZGUgNCkuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KVGhlIHByaW50ZWQgZGljdGlvbmFyeSAoYHZvY2FidWxhcnlfYCkgbWFwcyBlYWNoIHVuaXF1ZSB0b2tlbiB0byBhIGNvbHVtbiBpbmRleCBpbiB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXg6IA0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBDb3VudFZlY3Rvcml6ZXINCg0KZG9jdW1lbnRzID0gKA0KICAgICJUZXh0IGFuYWx5c2lzIHJlbGllcyBvbiBudW1lcmljYWwgcmVwcmVzZW50YXRpb25zIiwNCiAgICAiVmVjdG9ycyBhbmQgbWF0cmljZXMgYXJlIGNvcmUgbWF0aGVtYXRpY2FsIHRvb2xzIiwNCiAgICAiTGFyZ2UgY29sbGVjdGlvbnMgb2YgdGV4dCBjYW4gYmUgcHJvY2Vzc2VkIGVmZmljaWVudGx5Ig0KKQ0KDQp2ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKHN0b3Bfd29yZHM9ImVuZ2xpc2giKQ0KI3ZlY3Rvcml6ZXINClggPSB2ZWN0b3JpemVyLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKSAgICAgICAgICAgICAjIDMNCiNYDQoNCiMgSW5zcGVjdCB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5IGFuZCBkb2N1bWVudOKAk3Rlcm0gbWF0cml4DQpwcmludCh2ZWN0b3JpemVyLnZvY2FidWxhcnlfKQ0KYGBgDQoNCg0KDQpFYWNoIGtleSBpbiB0aGlzIGRpY3Rpb25hcnkgaXMgYSAqKnRva2VuKiogZXh0cmFjdGVkIGZyb20gdGhlIGNvcnB1cyBhZnRlciBwcmVwcm9jZXNzaW5nICh0b2tlbml6YXRpb24gYW5kIHN0b3B3b3JkIHJlbW92YWwpLiBUaGUgYXNzb2NpYXRlZCBudW1iZXIgaXMgKm5vdCBhIGZyZXF1ZW5jeSogYW5kIGRvZXMgKm5vdCogaW5kaWNhdGUgaW1wb3J0YW5jZSBvciBvcmRlciBvZiBhcHBlYXJhbmNlIGluIHRoZSB0ZXh0LiBJbnN0ZWFkLCBpdCBzcGVjaWZpZXMgdGhlICpjb2x1bW4gcG9zaXRpb24qIGFzc2lnbmVkIHRvIHRoYXQgdG9rZW4gaW4gdGhlIGRvY3VtZW50LXRlcm0gbWF0cml4Lg0KDQpUbyBtYWtlIHRoaXMgY29uY3JldGU6DQoNCi0gYCdhbmFseXNpcyc6IDBgIG1lYW5zIHRoYXQgdGhlIHRva2VuICphbmFseXNpcyogY29ycmVzcG9uZHMgdG8gKmNvbHVtbiAwKiBvZiB0aGUgbWF0cml4Lg0KDQotIGAnY29sbGVjdGlvbnMnOiAxYCBjb3JyZXNwb25kcyB0byAqY29sdW1uIDEqLg0KDQotIGAnY29yZSc6IDJgIGNvcnJlc3BvbmRzIHRvICpjb2x1bW4gMiouDQotIC4uLg0KLSBgJ3RleHQnOiAxMWAgY29ycmVzcG9uZHMgdG8gKmNvbHVtbiAxMSouDQoNCi0gYCd2ZWN0b3JzJzogMTNgIGNvcnJlc3BvbmRzIHRvICpjb2x1bW4gMTMqLg0KDQpJbiBvdGhlciB3b3JkcywgdGhlIG51bWJlcnMgYDBgLCBgMWAsIGAyYCwg4oCmLCBgMTNgIGFyZSAqaW5kaWNlcyosIG5vdCBjb3VudHMuIFRoZXkgc2ltcGx5IGxhYmVsIHRoZSBjb2x1bW5zIG9mIHRoZSBtYXRyaXgsIHN0YXJ0aW5nIGZyb20gemVybywgZm9sbG93aW5nIFB5dGhvbuKAmXMgaW5kZXhpbmcgY29udmVudGlvbi4NCg0KT25jZSB0aGlzIG1hcHBpbmcgaXMgZGVmaW5lZCwgdGhlIGRvY3VtZW50LXRlcm0gbWF0cml4IHVzZXMgaXQgY29uc2lzdGVudGx5LiBGb3IgZXhhbXBsZToNCg0KLSBUaGUgdmFsdWUgbG9jYXRlZCBhdCByb3cgJGkkIGFuZCBjb2x1bW4gYDBgIHJlcHJlc2VudHMgdGhlIGZyZXF1ZW5jeSBvZiB0aGUgdG9rZW4gKmFuYWx5c2lzKiBpbiBkb2N1bWVudCAkaSQuIA0KDQotIFNpbWlsYXJseSwgdGhlIHZhbHVlIGF0IGNvbHVtbiBgMTFgIHJlcHJlc2VudHMgdGhlIGZyZXF1ZW5jeSBvZiB0aGUgdG9rZW4gKnRleHQqIGluIHRoYXQgc2FtZSBkb2N1bWVudC4gIA0KDQoNCk1vcmUgZ2VuZXJhbGx5LCB0aGUgZW50cnkgaW4gcm93ICRpJCBhbmQgY29sdW1uICRqJCByZWNvcmRzIGhvdyBtYW55IHRpbWVzIHRva2VuICRqJCBhcHBlYXJzIGluIGRvY3VtZW50ICRpJC4gDQoNClRoaXMgc2VwYXJhdGlvbiBvZiByb2xlcyBpcyBjcnVjaWFsOg0KDQotIFRoZSAqdm9jYWJ1bGFyeSBkaWN0aW9uYXJ5KiBkZWZpbmVzICp3aGVyZSogZWFjaCB0b2tlbiBsaXZlcyBpbiB0aGUgbWF0cml4Lg0KDQotIFRoZSAqbWF0cml4IGVudHJpZXMqIGRlZmluZSAqaG93IG9mdGVuKiBlYWNoIHRva2VuIGFwcGVhcnMgaW4gZWFjaCBkb2N1bWVudC4NCg0KVW5kZXJzdGFuZGluZyB0aGlzIGRpc3RpbmN0aW9uIGhlbHBzIGV4cGxhaW4gd2h5IGEgZG9jdW1lbnQgdmVjdG9yIGhhcyBhIGZpeGVkIGxlbmd0aCBlcXVhbCB0byB0aGUgc2l6ZSBvZiB0aGUgdm9jYWJ1bGFyeSwgYW5kIHdoeSBtb3N0IGVudHJpZXMgYXJlIHplcm8gd2hlbiBhIHRva2VuIGRvZXMgbm90IGFwcGVhciBpbiBhIGRvY3VtZW50Lg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZTogaW50ZXJwcmV0aW5nIHRoZSBzZWNvbmQgb3V0cHV0IChjb2RlIDUpLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpUaGUgc2Vjb25kIG91dHB1dCAoYFgudG9kZW5zZSgpYCBvciBgWC50b2FycmF5KClgKSBpcyB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXggaXRzZWxmOiANCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnByaW50KFgudG9kZW5zZSgpKSAgDQpgYGANCg0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1GQUxTRX0NClxbDQpcbWF0aGJme1h9ID0NClxiZWdpbntibWF0cml4fQ0KMSAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDEgJiAwICYgMSAmIDEgJiAxICYgMCAmIDAgXFwNCjAgJiAwICYgMSAmIDAgJiAwICYgMSAmIDEgJiAwICYgMCAmIDAgJiAwICYgMCAmIDEgJiAxIFxcDQowICYgMSAmIDAgJiAxICYgMSAmIDAgJiAwICYgMCAmIDEgJiAwICYgMCAmIDEgJiAwICYgMA0KXGVuZHtibWF0cml4fQ0KXGluIFxtYXRoYmJ7Un1eezMgXHRpbWVzIDE0fQ0KXF0NCg0KXFsNClxtYXRoYmZ7WH0gPSBcbGVmdCgNClxiZWdpbnthcnJheX17Y2NjY2NjY2NjY2NjY2NjfQ0KICAgICAgICAmIFx0ZXh0e2FuYWx5c2lzfSAmIFx0ZXh0e2NvbGxlY3Rpb25zfSAmIFx0ZXh0e2NvcmV9ICYgXHRleHR7ZWZmaWNpZW50bHl9ICYgXHRleHR7bGFyZ2V9ICYgXHRleHR7bWF0aGVtYXRpY2FsfSAmIFx0ZXh0e21hdHJpY2VzfSAmIFx0ZXh0e251bWVyaWNhbH0gJiBcdGV4dHtwcm9jZXNzZWR9ICYgXHRleHR7cmVsaWVzfSAmIFx0ZXh0e3JlcHJlc2VudGF0aW9uc30gJiBcdGV4dHt0ZXh0fSAmIFx0ZXh0e3Rvb2xzfSAmIFx0ZXh0e3ZlY3RvcnN9IFxcDQpcdGV4dHtUZXh0IDF9ICYgMSAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDEgJiAwICYgMSAmIDEgJiAxICYgMCAmIDAgXFwNClx0ZXh0e1RleHQgMn0gJiAwICYgMCAmIDEgJiAwICYgMCAmIDEgJiAxICYgMCAmIDAgJiAwICYgMCAmIDAgJiAxICYgMSBcXA0KXHRleHR7VGV4dCAzfSAmIDAgJiAxICYgMCAmIDEgJiAxICYgMCAmIDAgJiAwICYgMSAmIDAgJiAwICYgMSAmIDAgJiAwDQpcZW5ke2FycmF5fSBccmlnaHQpDQpcaW4gXG1hdGhiYntSfV57MyBcdGltZXMgMTR9DQpcXQ0KDQpgYGANCg0KTWF0aGVtYXRpY2FsbHk6IA0KDQpcWw0KXG1hdGhiZntYfSA9IFxsZWZ0KA0KXGJlZ2lue2FycmF5fXtjfGNjY2NjY2NjY2NjY2NjfQ0KICAgXHRleHR7VGV4dH0gICAgICYgXHRleHR7YW5hbH0gJiBcdGV4dHtjb2xsfSAmIFx0ZXh0e2NvcmV9ICYgXHRleHR7ZWZmfSAmIFx0ZXh0e2xhcn0gJiBcdGV4dHttYXRofSAmIFx0ZXh0e21hdH0gJiBcdGV4dHtudW19ICYgXHRleHR7cHJvY30gJiBcdGV4dHtyZWxpZXN9ICYgXHRleHR7cmVwcn0gJiBcdGV4dHt0ZXh0fSAmIFx0ZXh0e3Rvb2xzfSAmIFx0ZXh0e3ZlY30gXFwNCiAgICAgICAgXGhsaW5lDQpcdGV4dHsjMX0gJiAxICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMSAmIDAgJiAxICYgMSAmIDEgJiAwICYgMCBcXA0KXHRleHR7IzJ9ICYgMCAmIDAgJiAxICYgMCAmIDAgJiAxICYgMSAmIDAgJiAwICYgMCAmIDAgJiAwICYgMSAmIDEgXFwNClx0ZXh0eyMzfSAmIDAgJiAxICYgMCAmIDEgJiAxICYgMCAmIDAgJiAwICYgMSAmIDAgJiAwICYgMSAmIDAgJiAwDQpcZW5ke2FycmF5fSBccmlnaHQpDQpcaW4gXG1hdGhiYntSfV57MyBcdGltZXMgMTR9DQpcXQ0KDQoqKkxlZ2VuZCAodG9rZW5zKToqKiAgDQoNCmBhbmFsYCA9IGFuYWx5c2lzOyBgY29sbGAgPSBjb2xsZWN0aW9uczsgYGNvcmVgID0gY29yZTsgYGVmZmAgPSBlZmZpY2llbnRseTsgYGxhcmdlYCA9IGxhcmdlOyAgYG1hdGhgID0gbWF0aGVtYXRpY2FsOyBgbWF0YCA9IG1hdHJpY2VzOyBgbnVtYCA9IG51bWVyaWNhbDsgYHByb2NgID0gcHJvY2Vzc2VkOyBgcmVsaWVzYCA9IHJlbGllczsgIGByZXByYCA9IHJlcHJlc2VudGF0aW9uczsgYHRleHRgID0gdGV4dDsgYHRvb2xzYCA9IHRvb2xzOyBgdmVjYCA9IHZlY3RvcnMuDQoNClRoaXMgbWF0cml4IHNob3VsZCBiZSBpbnRlcnByZXRlZCBhcyBmb2xsb3dzOg0KDQotIFJvd3Mgb2YgdGhlIG1hdHJpeCAkXG1hdGhiZntYfSQgIGNvcnJlc3BvbmQgdG8gZG9jdW1lbnRzOiBgVGV4dCAxYCwgYFRleHQgMmAsIGBUZXh0IDNgIChpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgaW5wdXQgdGV4dCkuDQoNCi0gQ29sdW1ucyBjb3JyZXNwb25kIHRvIHRva2VucyBpbiB0aGUgdm9jYWJ1bGFyeS4NCg0KVGhlIGVudHJ5ICR4X3tpan0kIG9mIHRoZSBtYXRyaXggJFxtYXRoYmZ7WH0kLA0KDQokJA0KXG1hdGhiZnt4fV9pID0gKHhfe2kxfSwgeF97aTJ9LCBcZG90cywgeF97aTE0fSksDQokJA0KDQogcmVwcmVzZW50cyB0aGUgbnVtYmVyIG9mIHRpbWVzIHRva2VuICRqJCBhcHBlYXJzIGluIGRvY3VtZW50ICRpJC4gRm9yIGV4YW1wbGU6DQoNCiAtICR4X3sxLDF9ID0gMSQgaW5kaWNhdGVzIHRoYXQgdGhlIHRva2VuICphbmFseXNpcyogYXBwZWFycyBvbmNlIGluIHRoZSBmaXJzdCBkb2N1bWVudC4NCiAgDQogLSAkeF97MSw4fSA9IDEkIGluZGljYXRlcyB0aGF0IHRoZSB0b2tlbiAqbnVtZXJpY2FsKiBhcHBlYXJzIG9uY2UgaW4gdGhlIGZpcnN0IGRvY3VtZW50Lg0KICANCiAtICR4X3syLDE0fSA9IDEkIGluZGljYXRlcyB0aGF0IHRoZSB0b2tlbiAqdmVjdG9ycyogYXBwZWFycyBvbmNlIGluIHRoZSBzZWNvbmQgZG9jdW1lbnQuDQoNCiAtIFplcm9zIGluZGljYXRlIHRoYXQgdGhlIGNvcnJlc3BvbmRpbmcgdG9rZW4gZG9lcyBub3QgYXBwZWFyIGluIHRoYXQgZG9jdW1lbnQuDQoNCkJlY2F1c2UgZWFjaCBkb2N1bWVudCBpcyBzaG9ydCBhbmQgbW9zdCB3b3JkcyBhcHBlYXIgYXQgbW9zdCBvbmNlLCB0aGUgbWF0cml4IG1haW5seSBjb250YWlucyB2YWx1ZXMgb2YgMCBhbmQgMS4gQSB2YWx1ZSBvZiAxIGluZGljYXRlcyB0aGF0IHRoZSBjb3JyZXNwb25kaW5nIHRva2VuIGFwcGVhcnMgb25jZSBpbiB0aGF0IGRvY3VtZW50LCB3aGlsZSAwIGluZGljYXRlcyB0aGF0IGl0IGRvZXMgbm90IGFwcGVhciBhdCBhbGwuDQoNClRoZSBsZW5ndGggb2YgZWFjaCByb3cgdmVjdG9yIGVxdWFscyB0aGUgc2l6ZSBvZiB0aGUgdm9jYWJ1bGFyeS4gSW4gdGhpcyBleGFtcGxlLCB0aGUgdm9jYWJ1bGFyeSBjb250YWlucyAxNCB1bmlxdWUgdG9rZW5zIGFmdGVyIHN0b3B3b3JkIHJlbW92YWwsIHdoaWNoIGV4cGxhaW5zIHdoeSBlYWNoIGRvY3VtZW50IHZlY3RvciBoYXMgMTQgY29tcG9uZW50cy4NCg0KT25jZSB0ZXh0IGRhdGEgaGFzIGJlZW4gY29udmVydGVkIGludG8gbWF0cml4IGZvcm0sIGl0IGJlY29tZXMgYW1lbmFibGUgdG8gc3RhbmRhcmQgbGluZWFyIGFsZ2VicmEgb3BlcmF0aW9ucyBzdWNoIGFzIHNpbWlsYXJpdHkgY29tcHV0YXRpb24sIHByb2plY3Rpb24sIGFuZCBtYXRyaXggdHJhbnNmb3JtYXRpb25zLCBlbmFibGluZyBxdWFudGl0YXRpdmUgYW5hbHlzaXMgb2YgZG9jdW1lbnRzLg0KDQpUaGlzIHR5cGUgb2YgbWF0cml4LWJhc2VkIGVuY29kaW5nIGlzIGNvbW1vbmx5IGFzc29jaWF0ZWQgd2l0aCB0aGUgKipCYWctb2YtV29yZHMgKEJvVykgbW9kZWwqKiwgaW4gd2hpY2ggZWFjaCBkb2N1bWVudCBpcyByZXByZXNlbnRlZCByZWxhdGl2ZSB0byBhIGZpeGVkIHZvY2FidWxhcnksIHR5cGljYWxseSBieSByZWNvcmRpbmcgdGhlIGZyZXF1ZW5jeSBvZiBpdHMgdG9rZW5zIHdoaWxlIGlnbm9yaW5nIHdvcmQgb3JkZXIuDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgQmFnLW9mLXdvcmRzIChCb3cpDQoNCiMjIyBFeHBsb3JpbmcgdGhlIGJhZy1vZi13b3JkcyByZXByZXNlbnRhdGlvbg0KDQojIyMjIEJhc2ljIGlkZWEuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KT25lIG9mIHRoZSBzaW1wbGVzdCB3YXlzIHRvIHJlcHJlc2VudCB0ZXh0IG51bWVyaWNhbGx5IGlzIHRvIGNvdW50IGhvdyBvZnRlbiB0ZXJtcyBhcHBlYXIgaW4gYSBkb2N1bWVudC4gVGhpcyBpZGVhIGZvcm1zIHRoZSBiYXNpcyBvZiB0aGUgKipCYWctb2YtV29yZHMgKEJvVykqKiByZXByZXNlbnRhdGlvbi4NCg0KVGhlIEJvVyBtb2RlbCBkZWxpYmVyYXRlbHkgaWdub3JlcyB3b3JkIG9yZGVyIGFuZCBncmFtbWF0aWNhbCBzdHJ1Y3R1cmUsIGZvY3VzaW5nIGluc3RlYWQgb24gKndoaWNoIHRlcm1zIGFwcGVhciogYW5kICpob3cgb2Z0ZW4gdGhleSBvY2N1ciouIEFsdGhvdWdoIHRoaXMgYWJzdHJhY3Rpb24gZGlzY2FyZHMgc3ludGFjdGljIGluZm9ybWF0aW9uIHN1Y2ggYXMgd29yZCBzZXF1ZW5jZSwgaXQgcHJvdmlkZXMgYSBzaW1wbGUgYW5kIGVmZmVjdGl2ZSBiYXNlbGluZSBmb3IgbWFueSB0ZXh0IGFuYWx5c2lzIHRhc2tzLg0KDQpJbiB0aGUgcHJldmlvdXMgY2hhcHRlciBvbiB2b2NhYnVsYXJ5IGNvbnN0cnVjdGlvbiwgd2UgaW50cm9kdWNlZCB0aGUgcHJvY2VzcyBvZiBpZGVudGlmeWluZyBhbmQgc3RhbmRhcmRpemluZyB0aGUgYmFzaWMgdW5pdHMgb2YgdGV4dC4gVGhhdCBzdGVwIGlzIGVzc2VudGlhbCBmb3IgQm9XIHJlcHJlc2VudGF0aW9uczogYmVmb3JlIGNvdW50aW5nIHRlcm1zLCB3ZSBtdXN0IGZpcnN0IGRlY2lkZSAqd2hpY2ggdGVybXMgYmVsb25nIHRvIHRoZSB2b2NhYnVsYXJ5Ki4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFZlY3RvciBpbnRlcnByZXRhdGlvbi4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQpPbmNlIHRoZSB2b2NhYnVsYXJ5IGlzIGZpeGVkLCBlYWNoIGRvY3VtZW50IGNhbiBiZSByZXByZXNlbnRlZCBhcyBhIHZlY3RvciB3aG9zZSBsZW5ndGggZXF1YWxzIHRoZSBzaXplIG9mIHRoZSB2b2NhYnVsYXJ5LiBFYWNoIHBvc2l0aW9uIGluIHRoZSB2ZWN0b3IgY29ycmVzcG9uZHMgdG8gYSBzcGVjaWZpYyB0ZXJtLCBhbmQgdGhlIHZhbHVlIHN0b3JlZCBpbiB0aGF0IHBvc2l0aW9uIGluZGljYXRlcyBob3cgbWFueSB0aW1lcyB0aGUgdGVybSBhcHBlYXJzIGluIHRoZSBkb2N1bWVudC4NCg0KSWYgYSB0ZXJtIGZyb20gdGhlIHZvY2FidWxhcnkgZG9lcyBub3QgYXBwZWFyIGluIGEgZ2l2ZW4gZG9jdW1lbnQsIHRoZSBjb3JyZXNwb25kaW5nIGVudHJ5IGluIHRoZSB2ZWN0b3IgaXMgemVyby4NCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFNwYXJzaXR5IGFuZCBjb21wb25lbnQgdmFsdWVzLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpBIG5hdHVyYWwgcXVlc3Rpb24gYXJpc2VzIGF0IHRoaXMgcG9pbnQ6DQoNCmBgYHRleHQNCldoYXQgaXMgdGhlIG1heGltdW0gcG9zc2libGUgdmFsdWUgb2YgYSBlbnRyeSAob3IgdGVybSBjb3VudCkgaW4gYSBCYWctb2YtV29yZHMgdmVjdG9yPw0KYGBgDQoNCg0KVGFrZSBhIG1vbWVudCB0byB0aGluayBhYm91dCBpdC4NCg0KQXQgZmlyc3QgZ2xhbmNlLCBvbmUgbWlnaHQgZXhwZWN0IGEgZml4ZWQgdXBwZXIgYm91bmQuIEhvd2V2ZXIsIHRoaXMgaXMgbm90IHRoZSBjYXNlLg0KDQpJbiBhIEJhZy1vZi1Xb3JkcyByZXByZXNlbnRhdGlvbiwgZWFjaCBjb21wb25lbnQgb2YgdGhlIHZlY3RvciByZWNvcmRzIHRoZSBudW1iZXIgb2YgdGltZXMgYSBnaXZlbiB0ZXJtIGFwcGVhcnMgaW4gYSBkb2N1bWVudC4gVGhlcmVmb3JlLCB0aGUgdmFsdWUgb2YgYSBjb21wb25lbnQgZGVwZW5kcyBlbnRpcmVseSBvbiB0aGUgZnJlcXVlbmN5IG9mIHRoYXQgdGVybSB3aXRoaW4gdGhlIGRvY3VtZW50Lg0KDQpJbiBwcmluY2lwbGUsIHRoZXJlIGlzIG5vIGZpeGVkIHVwcGVyIGJvdW5kOiBhIHRlcm0gY291bGQgYXBwZWFyIG1hbnkgdGltZXMsIGVzcGVjaWFsbHkgaW4gbG9uZyBkb2N1bWVudHMgb3IgaGlnaGx5IHJlcGV0aXRpdmUgdGV4dHMuIEFzIGEgcmVzdWx0LCBzb21lIGNvbXBvbmVudHMgbWF5IHRha2UgcmVsYXRpdmVseSBsYXJnZSB2YWx1ZXMsIHdoaWxlIG1hbnkgb3RoZXJzIHJlbWFpbiBlcXVhbCB0byB6ZXJvLg0KDQpUaGlzIGltYmFsYW5jZSBsZWFkcyB0byBhIGtleSBwcm9wZXJ0eSBvZiBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb25zOiBzcGFyc2l0eS4gTW9zdCBlbnRyaWVzIGluIHRoZSB2ZWN0b3IgYXJlIHplcm8gYmVjYXVzZSBtb3N0IHRlcm1zIGluIHRoZSB2b2NhYnVsYXJ5IGRvIG5vdCBhcHBlYXIgaW4gYSBnaXZlbiBkb2N1bWVudC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIEZyb20gdGV4dCB0byBCYWctb2YtV29yZHM6IGEgc3RlcC1ieS1zdGVwIGNvbnN0cnVjdGlvbg0KDQpUbyBtYWtlIHRoZSBpZGVhIGNvbmNyZXRlLCB3ZSBub3cgY29uc3RydWN0IGEgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9uIG1hbnVhbGx5LCBzdGFydGluZyBmcm9tIGEgc21hbGwgY29sbGVjdGlvbiBvZiBzZW50ZW5jZXMuDQoNCkJlZm9yZSBleGFtaW5pbmcgZWFjaCBzdGVwIGluIGRldGFpbCwgRmlndXJlIFxAcmVmKGZpZzpGaWctQm9XLXN0ZXBzKSBwcm92aWRlcyBhbiBvdmVydmlldyBvZiB0aGUgbWFpbiBzdGFnZXMgaW52b2x2ZWQgaW4gYnVpbGRpbmcgYSBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24uIFRoZXNlIHN0YWdlcyB3aWxsIGJlIGV4cGxhaW5lZCBwcm9ncmVzc2l2ZWx5IGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuDQoNCg0KDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctQm9XLXN0ZXBzLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIlN0ZXAtYnktc3RlcCBjb25zdHJ1Y3Rpb24gb2YgYSBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24uIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICIxNDAlIn0NCiMgZmlnLndpZHRoID0gMjAgIyBObyBmdW5jaW9uYSBlc3RhIG9wY2lvbiBlbiBlbCBjaHVuaw0KDQojaHR0cDovL3pldnJvc3MuY29tL2Jsb2cvMjAxNy8wNi8xOS90aXBzLWFuZC10cmlja3MtZm9yLXdvcmtpbmctd2l0aC1pbWFnZXMtYW5kLWZpZ3VyZXMtaW4tci1tYXJrZG93bi1kb2N1bWVudHMvDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJCb1dfc3RlcHMucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCAxOiBEZWZpbmUgYSBzbWFsbCBjb3JwdXMuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCldlIGJlZ2luIGJ5IGRlZmluaW5nIGEgc21hbGwgY29sbGVjdGlvbiBvZiBzZW50ZW5jZXMsIHdoaWNoIHdpbGwgc2VydmUgYXMgb3VyIGNvcnB1cy4gRWFjaCBzZW50ZW5jZSBpcyB0cmVhdGVkIGFzIGEgc2VwYXJhdGUgZG9jdW1lbnQ6DQoNCg0KDQpgYGB7cHl0aG9ufQ0Kc2VudGVuY2VzID0gWw0KICAgICJEYXRhIHNjaWVuY2UgY29ubmVjdHMgc3RhdGlzdGljcyBhbmQgY29tcHV0YXRpb24iLA0KICAgICJTdGF0aXN0aWNhbCBtb2RlbHMgbGVhcm4gcGF0dGVybnMgZnJvbSBkYXRhIiwNCiAgICAiTW9kZXJuIGRhdGEgYW5hbHlzaXMgcmVsaWVzIG9uIGNvbXB1dGF0aW9uYWwgdG9vbHMiDQpdDQpgYGANCg0KVGhpcyBjb3JwdXMgcmVwcmVzZW50cyB0aGUgcmF3IHRleHR1YWwgaW5wdXQuIEF0IHRoaXMgc3RhZ2UsIHRoZSBkYXRhIGlzIHN0aWxsIHVuc3RydWN0dXJlZCBhbmQgY2Fubm90IHlldCBiZSBwcm9jZXNzZWQgbWF0aGVtYXRpY2FsbHkuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFN0ZXAgMjogU3RvcmUgdGhlIGNvcnB1cyBpbiBhIHN0cnVjdHVyZWQgZm9ybS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KDQo6OjpzYW5ncmlhMw0KIyMjIyBUaGUgY29kZS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQoNClRoZSBjb3JwdXMgaXMgc3RvcmVkIGFzIGEgYHBhbmRhcy5TZXJpZXNgLCB3aGVyZSBlYWNoIGVsZW1lbnQgcmVwcmVzZW50cyBvbmUgZG9jdW1lbnQuIFRoaXMgc3RydWN0dXJlZCBmb3JtYXQgZmFjaWxpdGF0ZXMgc3lzdGVtYXRpYyBwcmVwcm9jZXNzaW5nIGFuZCBsYXRlciB2ZWN0b3JpemF0aW9uIHN0ZXBzLg0KDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQppbXBvcnQgcGFuZGFzIGFzIHBkDQoNCmNvcnB1cyA9IHBkLlNlcmllcyhzZW50ZW5jZXMpDQpjb3JwdXMNCmBgYA0KDQpUaGUgY29kZSBiZWdpbnMgYnkgaW1wb3J0aW5nIHRoZSBgcGFuZGFzYCBsaWJyYXJ5LCB3aGljaCBwcm92aWRlcyBjb252ZW5pZW50IGRhdGEgc3RydWN0dXJlcyBmb3IgaGFuZGxpbmcgYW5kIG9yZ2FuaXppbmcgZGF0YS4gVGhlIGxpc3Qgb2Ygc2VudGVuY2VzIGRlZmluZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXAgaXMgdGhlbiBjb252ZXJ0ZWQgaW50byBhIFNlcmllcyBvYmplY3QuDQoNCkEgYHBhbmRhcy5TZXJpZXNgIGNhbiBiZSB1bmRlcnN0b29kIGFzIGEgb25lLWRpbWVuc2lvbmFsIGxhYmVsZWQgYXJyYXkuIEluIHRoaXMgY29udGV4dCwgZWFjaCBlbnRyeSBvZiB0aGUgYFNlcmllc2AgY29ycmVzcG9uZHMgdG8gYSBkb2N1bWVudCwgYW5kIHRoZSBpbmRleCAoYDBgLCBgMWAsIGAyYCwgLi4uKSB1bmlxdWVseSBpZGVudGlmaWVzIGVhY2ggb25lLg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRoZSBvdXB1dC4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQoNCkluIHRoaXMgY2FzZSwgdGhlIHByaW50ZWQgb3R1cHV0IGlzOiANCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCmltcG9ydCBwYW5kYXMgYXMgcGQNCg0KY29ycHVzID0gcGQuU2VyaWVzKHNlbnRlbmNlcykNCmNvcnB1cw0KYGBgDQoNCg0KVGhpcyBvdXRwdXQgc2hvd3M6DQoNCi0gVGhlIGluZGV4IG9uIHRoZSBsZWZ0IChgMGAsIGAxYCwgYDJgKSwgd2hpY2ggbGFiZWxzIGVhY2ggZG9jdW1lbnQuDQoNCi0gVGhlIHRleHQgY29udGVudCBvZiBlYWNoIGRvY3VtZW50Lg0KDQotIFRoZSBkYXRhIHR5cGUgKGBkdHlwZTogb2JqZWN0YCksIGluZGljYXRpbmcgdGhhdCB0aGUgZW50cmllcyBhcmUgc3RvcmVkIGFzIHRleHQuDQoNClRoaXMgcmVwcmVzZW50YXRpb24gZG9lcyBub3QgeWV0IHRyYW5zZm9ybSB0aGUgdGV4dCBpbnRvIG51bWJlcnMsIGJ1dCBpdCBvcmdhbml6ZXMgdGhlIGNvcnB1cyBpbnRvIGEgc3RydWN0dXJlZCBmb3JtYXQgdGhhdCBjYW4gYmUgZWFzaWx5IHByb2Nlc3NlZCBpbiBzdWJzZXF1ZW50IHN0ZXBzDQo6OjoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCAzOiBBcHBseSBiYXNpYyBwcmVwcm9jZXNzaW5nLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQo6OjpzYW5ncmlhMw0KIyMjIyBUaGUgY29kZS4gey51bmxpc3RlZCAudW5udW1iZXJlZH0gDQoNClRoZSBwcmVwcm9jZXNzaW5nIHN0ZXAgc3RhbmRhcmRpemVzIHRoZSB0ZXh0IGJ5IGxvd2VyY2FzaW5nLCByZW1vdmluZyBwdW5jdHVhdGlvbiBhbmQgc3RvcHdvcmRzLCBhbmQgcmVkdWNpbmcgd29yZHMgdG8gdGhlaXIgbGVtbWEuIFRoZXNlIG9wZXJhdGlvbnMgZW5zdXJlIHRoYXQgZGlmZmVyZW50IHN1cmZhY2UgZm9ybXMgb2YgYSB3b3JkIGFyZSB0cmVhdGVkIGNvbnNpc3RlbnRseS4NCg0KQXMgZGlzY3Vzc2VkIGluIHRoZSBwcmV2aW91cyBkb2N1bWVudCBvbiB2b2NhYnVsYXJ5IGNvbnN0cnVjdGlvbiAoc2VlIFtWb2NhYnVsYXJ5IGFuZCB0ZXh0IG5vcm1hbGl6YXRpb25dKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvUl9OTFBfdm9jYWJ1bGFyeSkpLCBwcmVwcm9jZXNzaW5nIGlzIGEgY3J1Y2lhbCBzdGVwIHRoYXQgZGlyZWN0bHkgaW5mbHVlbmNlcyBob3cgdG9rZW5zIGFyZSBkZWZpbmVkIGFuZCBob3cgdGhlIHZvY2FidWxhcnkgaXMgYnVpbHQuDQoNClRoZSBmb2xsb3dpbmcgY29kZSBpbXBsZW1lbnRzIGEgYmFzaWMgcHJlcHJvY2Vzc2luZyBwaXBlbGluZSB1c2luZyB0aGUgYG5sdGtgIGxpYnJhcnk6DQoNCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0KIyBTdGVwIDNhOiBSZXF1aXJlZCBwYWNrYWdlcw0KaW1wb3J0IG5sdGsNCm5sdGsuZG93bmxvYWQoJ3N0b3B3b3JkcycpDQpubHRrLmRvd25sb2FkKCd3b3JkbmV0JykNCg0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQpmcm9tIG5sdGsuc3RlbS53b3JkbmV0IGltcG9ydCBXb3JkTmV0TGVtbWF0aXplcg0KaW1wb3J0IHJlDQppbXBvcnQgbnVtcHkgYXMgbnANCg0KI1N0ZXAgM2I6IERlZmluaW5nIHRoZSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9uDQpkZWYgY2xlYW5fYW5kX2xlbW1hdGl6ZSh0ZXh0KToNCiAgICBzdG9wID0gc2V0KHN0b3B3b3Jkcy53b3JkcygiZW5nbGlzaCIpKQ0KICAgIGxlbW1hdGl6ZXIgPSBXb3JkTmV0TGVtbWF0aXplcigpDQogICAgDQogICAgdG9rZW5zID0gcmUuc3ViKHIiW15hLXpBLVpdIiwgIiAiLCB0ZXh0KS5sb3dlcigpLnNwbGl0KCkNCiAgICB0b2tlbnMgPSBbbGVtbWF0aXplci5sZW1tYXRpemUodG9rKSBmb3IgdG9rIGluIHRva2VucyBpZiB0b2sgbm90IGluIHN0b3BdDQogICAgDQogICAgcmV0dXJuICIgIi5qb2luKHRva2VucykNCg0KI1N0ZXAgM2M6IEFwcGx5aW5nIHRoZSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9uIHRvIHRoZSBjb3JwdXMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCnByb2Nlc3NlZF9jb3JwdXMgPSBjb3JwdXMuYXBwbHkoY2xlYW5fYW5kX2xlbW1hdGl6ZSkNCnByb2Nlc3NlZF9jb3JwdXMNCmBgYA0KDQpUaGlzIGNvZGUgZGVmaW5lcyBhIHByZXByb2Nlc3NpbmcgZnVuY3Rpb24gYW5kIGFwcGxpZXMgaXQgdG8gZWFjaCBkb2N1bWVudCBpbiB0aGUgY29ycHVzLiBGb3IgY2xhcml0eSwgZWFjaCBjb21wb25lbnQgb2YgdGhpcyBwaXBlbGluZSBpcyBleHBsYWluZWQgaW4gZGV0YWlsIGluIHRoZSBmb2xsb3dpbmcgc3Vic2VjdGlvbnMuDQoNClRoaXMgcHJlcHJvY2Vzc2luZyBzdGFnZSBwcmVwYXJlcyB0aGUgY29ycHVzIGZvciB2b2NhYnVsYXJ5IGNvbnN0cnVjdGlvbiwgd2hpY2ggaXMgdGhlIG5leHQgc3RlcCBpbiBidWlsZGluZyB0aGUgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9uLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCAzYTogUmVxdWlyZWQgcGFja2FnZXMuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpUaGVzZSBwYWNrYWdlcyBwcm92aWRlIHRvb2xzIGZvciB0b2tlbml6YXRpb24sIHN0b3B3b3JkIGZpbHRlcmluZywgYW5kIGxlbW1hdGl6YXRpb24sIHdoaWNoIGFyZSBzdGFuZGFyZCBwcmVwcm9jZXNzaW5nIHN0ZXBzIGluIG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZy4NCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmltcG9ydCBubHRrDQppbXBvcnQgcmUNCmltcG9ydCBudW1weSBhcyBucA0KDQpmcm9tIG5sdGsuY29ycHVzIGltcG9ydCBzdG9wd29yZHMNCmZyb20gbmx0ay5zdGVtLndvcmRuZXQgaW1wb3J0IFdvcmROZXRMZW1tYXRpemVyDQoNCiMgRG93bmxvYWQgcmVxdWlyZWQgTkxUSyByZXNvdXJjZXMgKHNpbGVudCkNCl8gPW5sdGsuZG93bmxvYWQoJ3N0b3B3b3JkcycpDQpfID1ubHRrLmRvd25sb2FkKCd3b3JkbmV0JykNCmBgYA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCAzYjogRGVmaW5pbmcgdGhlIHByZXByb2Nlc3NpbmcgZnVuY3Rpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KVGhpcyBmdW5jdGlvbiBwZXJmb3JtcyBzZXZlcmFsIHByZXByb2Nlc3Npbmcgb3BlcmF0aW9ucyBpbiBzZXF1ZW5jZS4NCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCiMgTXkgZnVuY3Rpb24NCmRlZiBjbGVhbl9hbmRfbGVtbWF0aXplKHRleHQpOg0KICAgIHN0b3AgPSBzZXQoc3RvcHdvcmRzLndvcmRzKCJlbmdsaXNoIikpDQogICAgbGVtbWF0aXplciA9IFdvcmROZXRMZW1tYXRpemVyKCkNCiAgICANCiAgICB0b2tlbnMgPSByZS5zdWIociJbXmEtekEtWl0iLCAiICIsIHRleHQpLmxvd2VyKCkuc3BsaXQoKQ0KICAgIHRva2VucyA9IFtsZW1tYXRpemVyLmxlbW1hdGl6ZSh0b2spIGZvciB0b2sgaW4gdG9rZW5zIGlmIHRvayBub3QgaW4gc3RvcF0NCiAgICANCiAgICByZXR1cm4gIiAiLmpvaW4odG9rZW5zKQ0KYGBgDQoNCi0gVGhlIGByZS5zdWIociJbXmEtekEtWl0iLCAiICIsIHRleHQpYCBpbnN0cnVjdGlvbiByZW1vdmVzIHB1bmN0dWF0aW9uIGFuZCBub24tbGV0dGVyIGNoYXJhY3RlcnMuDQoNCi0gVGhlIGAubG93ZXIoKWAgbWV0aG9kIGNvbnZlcnRzIGFsbCB0ZXh0IHRvIGxvd2VyY2FzZSB0byBlbnN1cmUgY29uc2lzdGVuY3kuDQoNCi0gVGhlIGAuc3BsaXQoKWAgb3BlcmF0aW9uIHRva2VuaXplcyB0aGUgdGV4dCBpbnRvIGluZGl2aWR1YWwgd29yZHMuDQoNCi0gU3RvcHdvcmRzIChjb21tb24gd29yZHMgc3VjaCBhcyAqdGhlKiwgKmFuZCosICppcyopIGFyZSByZW1vdmVkIHVzaW5nIHRoZSBgc3RvcHdvcmRzYCBsaXN0IGZyb20gYG5sdGtgLg0KDQotIFRoZSBgV29yZE5ldExlbW1hdGl6ZXJgIHJlZHVjZXMgZWFjaCB0b2tlbiB0byBpdHMgYmFzZSBmb3JtIChsZW1tYSksIHNvIHRoYXQgZGlmZmVyZW50IGdyYW1tYXRpY2FsIGZvcm1zIGFyZSB0cmVhdGVkIGFzIHRoZSBzYW1lIHRlcm0gKGUuZy4sIGBtb2RlbHNgIC0tPiBgbW9kZWxgKS4NCg0KLSBUaGUgb3V0cHV0IG9mIHRoZSBmdW5jdGlvbiBpcyBhIGNsZWFuZWQgYW5kIG5vcm1hbGl6ZWQgc3RyaW5nLCByZWFkeSBmb3IgdmVjdG9yaXphdGlvbi4NCg0KVGhpcyBzdGVwIGlzIGVzc2VudGlhbCBiZWNhdXNlIHRoZSBxdWFsaXR5IG9mIHRoZSB2b2NhYnVsYXJ5IGFuZCB0aGUgcmVzdWx0aW5nIEJhZy1vZi1Xb3JkcyByZXByZXNlbnRhdGlvbiBkZXBlbmQgZGlyZWN0bHkgb24gaG93IHRoZSB0ZXh0IGlzIHByZXByb2Nlc3NlZC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCAzYzogQXBwbHlpbmcgdGhlIHByZXByb2Nlc3NpbmcgZnVuY3Rpb24gdG8gdGhlIGNvcnB1cy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpUaGUgZnVuY3Rpb24gYGNsZWFuX2FuZF9sZW1tYXRpemVgIGlzIGFwcGxpZWQgdG8gZWFjaCBkb2N1bWVudCBpbiB0aGUgY29ycHVzIHVzaW5nIHRoZSBgLmFwcGx5KClgIG1ldGhvZCBmcm9tIGBwYW5kYXNgLiBUaGlzIG1ldGhvZCBpdGVyYXRlcyBvdmVyIGFsbCBlbGVtZW50cyBvZiB0aGUgYFNlcmllc2AgYW5kIHRyYW5zZm9ybXMgZWFjaCBkb2N1bWVudCBpbmRpdmlkdWFsbHkuDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpwcm9jZXNzZWRfY29ycHVzID0gY29ycHVzLmFwcGx5KGNsZWFuX2FuZF9sZW1tYXRpemUpDQpwcm9jZXNzZWRfY29ycHVzDQpgYGANCg0KVGhlIG91dHB1dCBzaG93cyB0aGUgcHJlcHJvY2Vzc2VkIHZlcnNpb24gb2YgZWFjaCBkb2N1bWVudCBpbiB0aGUgY29ycHVzLCB3aGVyZSBzdG9wd29yZHMgaGF2ZSBiZWVuIHJlbW92ZWQgYW5kIHRoZSByZW1haW5pbmcgd29yZHMgaGF2ZSBiZWVuIGxlbW1hdGl6ZWQuIEVhY2ggcm93IGNvcnJlc3BvbmRzIHRvIG9uZSBkb2N1bWVudCwgYW5kIHRoZSBvcmlnaW5hbCBkb2N1bWVudCBvcmRlciBpcyBwcmVzZXJ2ZWQuDQoNCmBgYHtweXRob259DQpsZW4ocHJvY2Vzc2VkX2NvcnB1cykNCmBgYA0KDQoNClRoaXMgY29uZmlybXMgdGhhdCB0aGUgY29ycHVzIGNvbnRhaW5zIHRocmVlIGRvY3VtZW50cywgZWFjaCBvZiB3aGljaCBoYXMgYmVlbiB0cmFuc2Zvcm1lZCBpbnRvIGl0cyBjbGVhbmVkIHRleHR1YWwgcmVwcmVzZW50YXRpb24uDQo6OjoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFN0ZXAgNDogQnVpbGQgdGhlIHZvY2FidWxhcnkuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KOjo6c2FuZ3JpYTMNCiMjIyMgVGhlIGNvZGUuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KDQpUaGlzIGNvZGUgY29uc3RydWN0cyB0aGUgdm9jYWJ1bGFyeSBieSBleHRyYWN0aW5nIGFsbCB1bmlxdWUgdG9rZW5zIGZyb20gdGhlIHByZXByb2Nlc3NlZCBjb3JwdXMgYW5kIHNvcnRpbmcgdGhlbSBhbHBoYWJldGljYWxseS4gRWFjaCB0b2tlbiBhcHBlYXJzIG9ubHkgb25jZSwgcmVnYXJkbGVzcyBvZiBob3cgbWFueSB0aW1lcyBpdCBvY2N1cnMgaW4gdGhlIGRvY3VtZW50cy4gDQoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCnZvY2FidWxhcnkgPSBzb3J0ZWQoc2V0KA0KICAgIHdvcmQgZm9yIHNlbnRlbmNlIGluIHByb2Nlc3NlZF9jb3JwdXMgZm9yIHdvcmQgaW4gc2VudGVuY2Uuc3BsaXQoKQ0KKSkNCnZvY2FidWxhcnkNCmBgYA0KDQpUaGUgZXhwcmVzc2lvbiBpbnNpZGUgdGhlIGNvZGUgcGVyZm9ybXMgdGhyZWUgbWFpbiBvcGVyYXRpb25zOg0KDQotIFRoZSBjb21wcmVoZW5zaW9uIGJlbG93IGl0ZXJhdGVzIG92ZXIgZWFjaCBkb2N1bWVudCBhbmQgZXh0cmFjdHMgYWxsIHRva2Vucy4NCg0KOjo6c2FuZ3JpYTMNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQp3b3JkIGZvciBzZW50ZW5jZSBpbiBwcm9jZXNzZWRfY29ycHVzIGZvciB3b3JkIGluIHNlbnRlbmNlLnNwbGl0KCkNCmBgYA0KOjo6DQoNCi0gVGhlIGZ1bmN0aW9uIGBzZXQoLi4uKWAgcmVtb3ZlcyBkdXBsaWNhdGUgdG9rZW5zLCBlbnN1cmluZyB0aGF0IGVhY2ggdGVybSBhcHBlYXJzIG9ubHkgb25jZS4NCg0KLSBUaGUgZnVuY3Rpb24gYHNvcnRlZCguLi4pYCBvcmRlcnMgdGhlIHZvY2FidWxhcnkgYWxwaGFiZXRpY2FsbHksIGd1YXJhbnRlZWluZyBhIGNvbnNpc3RlbnQgYW5kIHJlcHJvZHVjaWJsZSBzdHJ1Y3R1cmUuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMjIyMgVGhlIG91dHB1dC4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpUaGUgb3V0cHV0IGlzIHNob3duIGJlbG93Og0KDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQp2b2NhYnVsYXJ5ID0gc29ydGVkKHNldCgNCiAgICB3b3JkIGZvciBzZW50ZW5jZSBpbiBwcm9jZXNzZWRfY29ycHVzIGZvciB3b3JkIGluIHNlbnRlbmNlLnNwbGl0KCkNCikpDQp2b2NhYnVsYXJ5DQpgYGANCg0KDQpJdCBpcyBhIGxpc3Qgb2YgMTQgdW5pcXVlIHRlcm1zLiANCiANCmBgYHtweXRob259DQpsZW4odm9jYWJ1bGFyeSkNCmBgYA0KDQoNCg0KRWFjaCB0ZXJtIGRlZmluZXMgb25lIGRpbWVuc2lvbiBvZiB0aGUgQmFnLW9mLVdvcmRzIHZlY3RvciBzcGFjZS4gVGhlcmVmb3JlLCBldmVyeSBkb2N1bWVudCB3aWxsIGJlIHJlcHJlc2VudGVkIGFzIGEgdmVjdG9yIG9mIGxlbmd0aCAxNCwgd2hlcmUgZWFjaCBwb3NpdGlvbiBjb3JyZXNwb25kcyB0byBvbmUgdm9jYWJ1bGFyeSB0ZXJtLg0KDQpJbiB0aGlzIHdheSwgdGhlIHZvY2FidWxhcnkgZGVmaW5lcyB0aGUgY29vcmRpbmF0ZSBzeXN0ZW0gb2YgdGhlIHZlY3RvciBzcGFjZSBpbiB3aGljaCBhbGwgZG9jdW1lbnRzIHdpbGwgYmUgcmVwcmVzZW50ZWQuDQo6OjoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFN0ZXAgNTogQXNzaWduIGluZGljZXMgdG8gdm9jYWJ1bGFyeSB0ZXJtcy4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KOjo6c2FuZ3JpYTMNCiMjIyMgVGhlIGNvZGUuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KDQpUaGlzIGNvZGUgY3JlYXRlcyBhIGRpY3Rpb25hcnkgdGhhdCBtYXBzIGVhY2ggdm9jYWJ1bGFyeSB0ZXJtIHRvIGEgdW5pcXVlIGludGVnZXIgaW5kZXguIFRoZXNlIGluZGljZXMgZGVmaW5lIHRoZSBjb2x1bW4gcG9zaXRpb25zIHRoYXQgZWFjaCB0b2tlbiB3aWxsIG9jY3VweSBpbiB0aGUgQmFnLW9mLVdvcmRzIG1hdHJpeCwgZW5zdXJpbmcgYSBjb25zaXN0ZW50IG51bWVyaWNhbCByZXByZXNlbnRhdGlvbiBhY3Jvc3MgYWxsIGRvY3VtZW50cy4NCg0KDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQp0b2tlbl9pbmRleCA9IHt0b2tlbjogaWR4IGZvciBpZHgsIHRva2VuIGluIGVudW1lcmF0ZSh2b2NhYnVsYXJ5KX0NCnRva2VuX2luZGV4DQpgYGANCg0KVGhlIGZ1bmN0aW9uIGBlbnVtZXJhdGUodm9jYWJ1bGFyeSlgIHBhaXJzIGVhY2ggdGVybSB3aXRoIGFuIGludGVnZXIgaW5kZXg6DQoNCmBgYHRleHQNCih0b2tlbuKCgCwgMCksICh0b2tlbuKCgSwgMSksICh0b2tlbuKCgiwgMiksIC4uLg0KYGBgDQoNClRoZSBkaWN0aW9uYXJ5IGNvbXByZWhlbnNpb24gdGhlbiBjb252ZXJ0cyB0aGVzZSBwYWlycyBpbnRvIGEgbWFwcGluZyBvZiB0aGUgZm9ybToNCg0KYGBgdGV4dA0KdG9rZW4g4oaSIGluZGV4DQpgYGANCg0KVGhpcyBtYXBwaW5nIGlzIGVzc2VudGlhbCBiZWNhdXNlIGl0IGRldGVybWluZXMgdGhlIGV4YWN0IHBvc2l0aW9uIG9mIGVhY2ggdGVybSBpbiB0aGUgdmVjdG9yIHJlcHJlc2VudGF0aW9uIG9mIGV2ZXJ5IGRvY3VtZW50Lg0KDQpJbiB0aGlzIHdheSwgdGhlIHZvY2FidWxhcnkgaXMgdHJhbnNmb3JtZWQgaW50byBhIGNvb3JkaW5hdGUgc3lzdGVtLCB3aGVyZSBlYWNoIGRpbWVuc2lvbiBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIHRlcm0uDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIFRoZSBvdXRwdXQuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KSW4gdGhpcyBjYXNlLCB0aGUgb3V0cHV0IGlzOiANCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnRva2VuX2luZGV4ID0ge3Rva2VuOiBpZHggZm9yIGlkeCwgdG9rZW4gaW4gZW51bWVyYXRlKHZvY2FidWxhcnkpfQ0KdG9rZW5faW5kZXgNCmBgYA0KDQpXaXRob3V0IHRoaXMgaW5kZXhpbmcgc3RlcCwgaXQgd291bGQgbm90IGJlIHBvc3NpYmxlIHRvIGNvbnN0cnVjdCBhIGNvbnNpc3RlbnQgZG9jdW1lbnTigJN0ZXJtIG1hdHJpeCBhY3Jvc3MgbXVsdGlwbGUgZG9jdW1lbnRzLg0KDQpUaGUgbmV4dCBzdGVwIGlzIHRvIGluaXRpYWxpemUgdGhlIGRvY3VtZW50LXRlcm0gbWF0cml4IHVzaW5nIHRoZXNlIGluZGljZXMuDQo6OjoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFN0ZXAgNjogSW5pdGlhbGl6ZSB0aGUgQmFnLW9mLVdvcmRzIG1hdHJpeC4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KDQo6OjpzYW5ncmlhMw0KIyMjIyBUaGUgY29kZS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQoNClRoZSBuZXh0IHN0ZXAgaXMgdG8gaW5pdGlhbGl6ZSB0aGUgZG9jdW1lbnTigJN0ZXJtIG1hdHJpeCwgd2hpY2ggd2lsbCBzdG9yZSB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggdm9jYWJ1bGFyeSB0ZXJtIGluIGVhY2ggZG9jdW1lbnQuDQoNCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0KYm93X21hdHJpeCA9IG5wLnplcm9zKChsZW4ocHJvY2Vzc2VkX2NvcnB1cyksIGxlbih2b2NhYnVsYXJ5KSkpDQpib3dfbWF0cml4DQpgYGANCg0KVGhlIGZ1bmN0aW9uIGBucC56ZXJvcyguLi4pYCBjcmVhdGVzIGEgbWF0cml4IGZpbGxlZCB3aXRoIHplcm9zLiBUaGUgc2hhcGUgb2YgdGhlIG1hdHJpeCBpcyBkZXRlcm1pbmVkIGJ5Og0KDQotIGBsZW4ocHJvY2Vzc2VkX2NvcnB1cylgOiB0aGUgbnVtYmVyIG9mIGRvY3VtZW50cyAocm93cykuDQoNCi0gYGxlbih2b2NhYnVsYXJ5KWA6IHRoZSBudW1iZXIgb2YgdW5pcXVlIHRlcm1zIChjb2x1bW5zKS4NCg0KDQoNClRodXMsIHRoZSByZXN1bHRpbmcgbWF0cml4IGhhczoNCg0KYGBgdGV4dA0KbnVtYmVyIG9mIGRvY3VtZW50cyDDlyB2b2NhYnVsYXJ5IHNpemUNCmBgYA0KVGhpcyBhYnN0cmFjdCBkZXNjcmlwdGlvbiBjYW4gYmUgbWFkZSBtb3JlIGV4cGxpY2l0IGJ5IGluc3BlY3RpbmcgdGhlIG1hdHJpeCBkaXJlY3RseSBpbiBQeXRob24uDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIFRoZSBvdXRwdXQgKHdpdGggcHl0aG9uKS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQoNCg0KVGhlIHNpemUgb2YgdGhlIG1hdHJpeCBjYW4gYmUgdmVyaWZpZWQgYXMgZm9sbG93czoNCg0KYGBge3B5dGhvbn0NCihsZW4ocHJvY2Vzc2VkX2NvcnB1cyksIGxlbih2b2NhYnVsYXJ5KSkNCmBgYA0KDQpUaGUgbWF0cml4IGl0c2VsZiBpcyBnaXZlbiBieToNCg0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KYm93X21hdHJpeCA9IG5wLnplcm9zKChsZW4ocHJvY2Vzc2VkX2NvcnB1cyksIGxlbih2b2NhYnVsYXJ5KSkpDQpib3dfbWF0cml4DQpgYGANCg0KV2hpbGUgdGhpcyBvdXRwdXQgc2hvd3MgdGhlIG51bWVyaWNhbCBzdHJ1Y3R1cmUsIGl0IGlzIGFsc28gdXNlZnVsIHRvIGludGVycHJldCB0aGUgbWF0cml4IGZyb20gYSBtYXRoZW1hdGljYWwgcGVyc3BlY3RpdmUuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIFRoZSBvdXRwdXQgKG1hdHJpeCByZXByZXNlbnRhdGlvbikuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KVG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgbWF0cml4LCBpdCBpcyB1c2VmdWwgdG8gbWFrZSBleHBsaWNpdCB0aGUgY29ycmVzcG9uZGVuY2UgYmV0d2VlbiB0b2tlbnMgYW5kIHZvY2FidWxhcnkgdGVybXM6DQoNCiQkDQpcdGV4dHtUb2tlbiBtYXA6fVxxcXVhZA0KXGJlZ2lue2FycmF5fXtsbGxsbGxsbH0NClx0ZXh0dHR7YW5hfT1cdGV4dHR0e2FuYWx5c2lzfSwgJg0KXHRleHR0dHtjbXB9PVx0ZXh0dHR7Y29tcHV0YXRpb259LCAmDQpcdGV4dHR0e2NtcGx9PVx0ZXh0dHR7Y29tcHV0YXRpb25hbH0sIFxcDQpcdGV4dHR0e2NudH09XHRleHR0dHtjb25uZWN0c30sICYNClx0ZXh0dHR7ZGF0fT1cdGV4dHR0e2RhdGF9LCAmDQpcdGV4dHR0e2xlYXJ9PVx0ZXh0dHR7bGVhcm59LCBcXA0KXHRleHR0dHttb2R9PVx0ZXh0dHR7bW9kZWx9LCAmDQpcdGV4dHR0e21kcm59PVx0ZXh0dHR7bW9kZXJufSwgJg0KXHRleHR0dHtwYXR9PVx0ZXh0dHR7cGF0dGVybn0sIFxcDQpcdGV4dHR0e3JlbH09XHRleHR0dHtyZWxpZXN9LCAmDQpcdGV4dHR0e3NjaX09XHRleHR0dHtzY2llbmNlfSwgJg0KXHRleHR0dHtzdH09XHRleHR0dHtzdGF0aXN0aWN9LCBcXA0KXHRleHR0dHtzdGx9PVx0ZXh0dHR7c3RhdGlzdGljYWx9LCAmDQpcdGV4dHR0e3Rvb2x9PVx0ZXh0dHR7dG9vbH0uDQpcZW5ke2FycmF5fQ0KJCQNCg0KR2l2ZW4gdGhpcyBtYXBwaW5nLCB0aGUgbWF0cml4IGNhbiBiZSBpbnRlcnByZXRlZCBhcyB0aGUgaW5pdGlhbCBzdGF0ZToNCg0KJCQNClxtYXRoYmZ7Qn1eeygwKX0gPVxsZWZ0KA0KXGJlZ2lue2FycmF5fXtjfGNjY2NjY2NjY2NjY2NjY2N9DQpcdGV4dHR0e1RleHR9ICYgXHRleHR0dHthbmF9ICYgXHRleHR0dHtjbXB9ICYgXHRleHR0dHtjbXBsfSAmIFx0ZXh0dHR7Y250fSAmIFx0ZXh0dHR7ZGF0fSAmIFx0ZXh0dHR7bGVhcn0gJiBcdGV4dHR0e21vZH0gJiBcdGV4dHR0e21kcm59ICYgXHRleHR0dHtwYXR9ICYgXHRleHR0dHtyZWx9ICYgXHRleHR0dHtzY2l9ICYgXHRleHR0dHtzdH0gJiBcdGV4dHR0e3N0bH0gJiBcdGV4dHR0e3Rvb2x9IFxcDQpcaGxpbmUNClx0ZXh0eyMxfSAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwIFxcDQpcdGV4dHsjMn0gJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCBcXA0KXHRleHR7IzN9ICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDANClxlbmR7YXJyYXl9IFxyaWdodCkNCiQkDQoNClRoaXMgcmVwcmVzZW50YXRpb24gbWFrZXMgZXhwbGljaXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHZvY2FidWxhcnkgdGVybXMgKGNvbHVtbnMpIGFuZCBkb2N1bWVudHMgKHJvd3MpLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIyBUaGUgb3V0cHV0IChpbnRlcnByZXRhdGlvbikuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KQXQgdGhpcyBzdGFnZSwgYWxsIGVudHJpZXMgYXJlIHplcm8gYmVjYXVzZSBubyB3b3JkIGNvdW50cyBoYXZlIGJlZW4gcmVjb3JkZWQgeWV0LiBUaGUgbWF0cml4IG9ubHkgZGVmaW5lcyB0aGUgc3RydWN0dXJlIG9mIHRoZSByZXByZXNlbnRhdGlvbi4NCg0KLSBFYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIGRvY3VtZW50LCBhbmQgZWFjaCBjb2x1bW4gY29ycmVzcG9uZHMgdG8gYSB2b2NhYnVsYXJ5IHRlcm0gKGFzIGRlZmluZWQgaW4gU3RlcCA1KS4gDQoNCi0gVGhlIHZhbHVlIGF0IHBvc2l0aW9uICQoaSwgaikkIHdpbGwgbGF0ZXIgc3RvcmUgaG93IG1hbnkgdGltZXMgdGVybSAkaiQgYXBwZWFycyBpbiBkb2N1bWVudCAkaSQuDQoNClRoaXMgbWF0cml4IGRlZmluZXMgdGhlIHZlY3RvciBzcGFjZSBpbiB3aGljaCBkb2N1bWVudHMgd2lsbCBiZSByZXByZXNlbnRlZCwgYnV0IGl0IGRvZXMgbm90IHlldCBjb250YWluIGFueSBpbmZvcm1hdGlvbiBhYm91dCB3b3JkIGZyZXF1ZW5jaWVzLg0KDQpXZSBhcmUgbm93IHJlYWR5IHRvIHBvcHVsYXRlIHRoZSBtYXRyaXggd2l0aCBhY3R1YWwgd29yZCBjb3VudHMuDQo6OjoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFN0ZXAgNzogUG9wdWxhdGUgdGhlIG1hdHJpeCB3aXRoIHdvcmQgY291bnRzLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gDQoNCg0KOjo6c2FuZ3JpYTMNClRoaXMgY29kZSBmaWxscyB0aGUgQmFnLW9mLVdvcmRzIG1hdHJpeCBieSBjb3VudGluZyB3b3JkIG9jY3VycmVuY2VzLiBGb3IgZWFjaCBkb2N1bWVudCAoYGlgKSwgZXZlcnkgdG9rZW4gaW4gdGhlIHByZXByb2Nlc3NlZCBzZW50ZW5jZSBpcyBsb2NhdGVkIGluIHRoZSB2b2NhYnVsYXJ5IHVzaW5nIGB0b2tlbl9pbmRleGAsIGFuZCB0aGUgY29ycmVzcG9uZGluZyBtYXRyaXggZW50cnkgaXMgaW5jcmVhc2VkIGJ5IG9uZS4NCg0KDQpgYGB7cHl0aG9ufQ0KZm9yIGksIHNlbnRlbmNlIGluIGVudW1lcmF0ZShwcm9jZXNzZWRfY29ycHVzKToNCiAgICBmb3IgdG9rZW4gaW4gc2VudGVuY2Uuc3BsaXQoKToNCiAgICAgICAgYm93X21hdHJpeFtpLCB0b2tlbl9pbmRleFt0b2tlbl1dICs9IDENCmBgYA0KDQpUaGUgY29kZSBvcGVyYXRlcyBhcyBmb2xsb3dzOg0KDQotIFRoZSBsb29wIGBlbnVtZXJhdGUocHJvY2Vzc2VkX2NvcnB1cylgIGl0ZXJhdGVzIG92ZXIgZWFjaCBkb2N1bWVudCwgd2hlcmUgYGlgIGlzIHRoZSBkb2N1bWVudCBpbmRleCBhbmQgYHNlbnRlbmNlYCBpcyB0aGUgY29ycmVzcG9uZGluZyB0ZXh0Lg0KDQotIEVhY2ggZG9jdW1lbnQgaXMgc3BsaXQgaW50byB0b2tlbnMgdXNpbmcgYC5zcGxpdCgpYC4NCg0KLSBGb3IgZWFjaCB0b2tlbiwgdGhlIGRpY3Rpb25hcnkgYHRva2VuX2luZGV4YCBwcm92aWRlcyB0aGUgY29sdW1uIGluZGV4IGFzc29jaWF0ZWQgd2l0aCB0aGF0IHRlcm0uDQoNCi0gVGhlIHZhbHVlIGluIHRoZSBtYXRyaXggYXQgcG9zaXRpb24gYChpLCBqKWAgaXMgaW5jcmVtZW50ZWQgYnkgMSwgd2hlcmU6DQoNCiAgLSBgaWAgPSBkb2N1bWVudCBpbmRleA0KICANCiAgLSBgamAgPSB0b2tlbiBpbmRleA0KDQpJbiB0aGlzIHdheSwgdGhlIG1hdHJpeCBpcyBncmFkdWFsbHkgcG9wdWxhdGVkIHdpdGggdGVybSBmcmVxdWVuY2llcy4NCg0KQWZ0ZXIgdGhpcyBzdGVwLCBlYWNoIHJvdyBvZiBgYm93X21hdHJpeGAgcmVwcmVzZW50cyBhIGRvY3VtZW50IGFzIGEgdmVjdG9yIG9mIHdvcmQgY291bnRzLiBOb256ZXJvIHZhbHVlcyBpbmRpY2F0ZSB0aGF0IGEgdGVybSBhcHBlYXJzIGluIHRoZSBkb2N1bWVudCwgd2hpbGUgemVyb3MgaW5kaWNhdGUgYWJzZW5jZS4NCg0KVGhpcyBzdGVwIHRyYW5zZm9ybXMgdGhlIGVtcHR5IG1hdHJpeCBpbnRvIGEgbnVtZXJpY2FsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBjb3JwdXMsIHdoZXJlIGVhY2ggZG9jdW1lbnQgaXMgZW5jb2RlZCBhcyBhIHZlY3RvciBpbiB0aGUgdm9jYWJ1bGFyeSBzcGFjZS4NCjo6Og0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCA4OiBJbnNwZWN0IHRoZSBmaW5hbCByZXByZXNlbnRhdGlvbi4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KOjo6c2FuZ3JpYTMNCiMjIyMgVGhlIG91dHB1dCAod2l0aCBweXRob24pLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gDQoNClRoZSByZXN1bHRpbmcgQmFnLW9mLVdvcmRzIG1hdHJpeCBpcyBzaG93biBiZWxvdzoNCg0KYGBge3B5dGhvbn0NCmJvd19tYXRyaXgNCmBgYA0KDQoNClRoaXMgb3V0cHV0IHByb3ZpZGVzIHRoZSBudW1lcmljYWwgcmVwcmVzZW50YXRpb24gb2YgdGhlIGNvcnB1cywgd2hlcmUgZWFjaCByb3cgY29ycmVzcG9uZHMgdG8gYSBkb2N1bWVudCBhbmQgZWFjaCBjb2x1bW4gY29ycmVzcG9uZHMgdG8gYSB2b2NhYnVsYXJ5IHRlcm0uDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMjIyMgVGhlIG91dHB1dCAobWF0cml4IHJlcHJlc2VudGF0aW9uKS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpUbyBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgc3RydWN0dXJlIG9mIHRoZSBmaW5hbCByZXByZXNlbnRhdGlvbiwgd2UgY2FuIGV4cHJlc3MgdGhlIG1hdHJpeCBpbiBtYXRoZW1hdGljYWwgZm9ybToNCg0KDQokJA0KXHRleHR7VG9rZW4gbWFwOn1ccXF1YWQNClxiZWdpbnthcnJheX17bGxsbGxsbGx9DQpcdGV4dHR0e2FuYX09XHRleHR0dHthbmFseXNpc30sICYNClx0ZXh0dHR7Y21wfT1cdGV4dHR0e2NvbXB1dGF0aW9ufSwgJg0KXHRleHR0dHtjbXBsfT1cdGV4dHR0e2NvbXB1dGF0aW9uYWx9LCBcXA0KXHRleHR0dHtjbnR9PVx0ZXh0dHR7Y29ubmVjdHN9LCAmDQpcdGV4dHR0e2RhdH09XHRleHR0dHtkYXRhfSwgJg0KXHRleHR0dHtsZWFyfT1cdGV4dHR0e2xlYXJufSwgXFwNClx0ZXh0dHR7bW9kfT1cdGV4dHR0e21vZGVsfSwgJg0KXHRleHR0dHttZHJufT1cdGV4dHR0e21vZGVybn0sICYNClx0ZXh0dHR7cGF0fT1cdGV4dHR0e3BhdHRlcm59LCBcXA0KXHRleHR0dHtyZWx9PVx0ZXh0dHR7cmVsaWVzfSwgJg0KXHRleHR0dHtzY2l9PVx0ZXh0dHR7c2NpZW5jZX0sICYNClx0ZXh0dHR7c3R9PVx0ZXh0dHR7c3RhdGlzdGljfSwgXFwNClx0ZXh0dHR7c3RsfT1cdGV4dHR0e3N0YXRpc3RpY2FsfSwgJg0KXHRleHR0dHt0b29sfT1cdGV4dHR0e3Rvb2x9Lg0KXGVuZHthcnJheX0NCiQkDQoNCiQkDQpcbWF0aGJme0J9ID1cbGVmdCgNClxiZWdpbnthcnJheX17Y3xjY2NjY2NjY2NjY2NjY2NjfQ0KXHRleHR0dHtUZXh0fSAmIFx0ZXh0dHR7YW5hfSAmIFx0ZXh0dHR7Y21wfSAmIFx0ZXh0dHR7Y21wbH0gJiBcdGV4dHR0e2NudH0gJiBcdGV4dHR0e2RhdH0gJiBcdGV4dHR0e2xlYXJ9ICYgXHRleHR0dHttb2R9ICYgXHRleHR0dHttZHJufSAmIFx0ZXh0dHR7cGF0fSAmIFx0ZXh0dHR7cmVsfSAmIFx0ZXh0dHR7c2NpfSAmIFx0ZXh0dHR7c3R9ICYgXHRleHR0dHtzdGx9ICYgXHRleHR0dHt0b29sfSBcXA0KXGhsaW5lDQpcdGV4dHsjMX0gJiAwICYgMSAmIDAgJiAxICYgMSAmIDAgJiAwICYgMCAmIDAgJiAwICYgMSAmIDEgJiAwICYgMCBcXA0KXHRleHR7IzJ9ICYgMCAmIDAgJiAwICYgMCAmIDEgJiAxICYgMSAmIDAgJiAxICYgMCAmIDAgJiAwICYgMSAmIDAgXFwNClx0ZXh0eyMzfSAmIDEgJiAwICYgMSAmIDAgJiAxICYgMCAmIDAgJiAxICYgMCAmIDEgJiAwICYgMCAmIDAgJiAxDQpcZW5ke2FycmF5fSBccmlnaHQpDQokJA0KVGhpcyBtYXRyaXggY29ycmVzcG9uZHMgdG8gdGhlIGZ1bGx5IHBvcHVsYXRlZCBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24sIHdoZXJlIGVhY2ggZW50cnkgcmVmbGVjdHMgdGhlIGZyZXF1ZW5jeSBvZiBhIHRlcm0gaW4gYSBnaXZlbiBkb2N1bWVudC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIyBUaGUgb3V0cHV0IChpbnRlcnByZXRhdGlvbikuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KSW4gdGhpcyBtYXRyaXg6IA0KDQotIEVhY2ggcm93IHJlcHJlc2VudHMgYSBkb2N1bWVudC4NCg0KLSBFYWNoIGNvbHVtbiBhIHZvY2FidWxhcnkgdGVybS4gDQoNCi0gVGhlIGVudHJpZXMgaW5kaWNhdGUgdGVybSBmcmVxdWVuY2llcywgd2l0aCBtYW55IHplcm9zIHJlZmxlY3RpbmcgdGhlIHNwYXJzZSBuYXR1cmUgb2YgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9ucy4NCg0KRm9yIGV4YW1wbGU6DQoNCi0gVGhlIGVudHJ5IGluIHJvdyAjMiBhbmQgY29sdW1uIGRhdCBpbmRpY2F0ZXMgaG93IG1hbnkgdGltZXMgdGhlIHdvcmQgZGF0YSBhcHBlYXJzIGluIHRoZSBzZWNvbmQgZG9jdW1lbnQuIA0KDQotIE1vcmUgZ2VuZXJhbGx5LCBlYWNoIHZhbHVlICQoaSwgaikkIGNhcHR1cmVzIHRoZSBmcmVxdWVuY3kgb2YgdGVybSAkaiQgaW4gZG9jdW1lbnQgJGkkLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIyBGaW5hbCByZW1hcmsuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KTW9zdCBlbnRyaWVzIHJlbWFpbiB6ZXJvLCBpbGx1c3RyYXRpbmcgdGhlIHNwYXJzaXR5IHR5cGljYWwgb2YgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9ucy4gVGhpcyBzcGFyc2l0eSBhcmlzZXMgYmVjYXVzZSBlYWNoIGRvY3VtZW50IGNvbnRhaW5zIG9ubHkgYSBzbWFsbCBzdWJzZXQgb2YgdGhlIGZ1bGwgdm9jYWJ1bGFyeS4NCg0KDQpUaGlzIGZpbmFsIG1hdHJpeCBwcm92aWRlcyBhIGNvbXBsZXRlIG51bWVyaWNhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgY29ycHVzLCBlbmFibGluZyB0aGUgYXBwbGljYXRpb24gb2YgbGluZWFyIGFsZ2VicmEgb3BlcmF0aW9ucyBhbmQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIHRvIHRleHR1YWwgZGF0YS4NCg0KV2hpbGUgdGhpcyByZXByZXNlbnRhdGlvbiBpcyBzaW1wbGUgYW5kIGVmZmVjdGl2ZSwgaXQgdHJlYXRzIGVhY2ggd29yZCBpbmRlcGVuZGVudGx5IGFuZCBpZ25vcmVzIGxvY2FsIGNvbnRleHQuIFdlIG5vdyBleHBsb3JlIGV4dGVuc2lvbnMgdGhhdCBjYXB0dXJlIHJpY2hlciBzdHJ1Y3R1cmVzIGluIHRleHQuDQo6OjoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgQmV5b25kIHVuaWdyYW1zDQoNClNvIGZhciwgd2UgaGF2ZSBjb25zaWRlcmVkIG9ubHkgKnVuaWdyYW1zKiwgbWVhbmluZyBpbmRpdmlkdWFsIHdvcmRzLiBUaGUgc2FtZSBpZGVhIGNhbiBiZSBleHRlbmRlZCB0bzoNCg0KLSBCaWdyYW1zIChwYWlycyBvZiBjb25zZWN1dGl2ZSB3b3JkcyksDQoNCi0gVHJpZ3JhbXMsIGFuZA0KDQotIEhpZ2hlci1vcmRlciBuLWdyYW1zLg0KDQpJbmNsdWRpbmcgbi1ncmFtcyBhbGxvd3MgdGhlIG1vZGVsIHRvIGNhcHR1cmUgKmxvY2FsIGNvbnRleHR1YWwgaW5mb3JtYXRpb24qLCBtZWFuaW5nIHRoYXQgaXQgY2FuIHJlY29nbml6ZSBzaG9ydCBzZXF1ZW5jZXMgb2Ygd29yZHMgcmF0aGVyIHRoYW4gdHJlYXRpbmcgZWFjaCB3b3JkIGluZGVwZW5kZW50bHkuDQoNCkZvciBleGFtcGxlLCB0aGUgYmlncmFtIGBkYXRhIHNjaWVuY2VgIGNhcnJpZXMgYSBtb3JlIHNwZWNpZmljIG1lYW5pbmcgdGhhbiB0aGUgaW5kaXZpZHVhbCB3b3JkcyBgZGF0YWAgYW5kIGBzY2llbmNlYCBjb25zaWRlcmVkIHNlcGFyYXRlbHkuDQoNCkhvd2V2ZXIsIHRoaXMgY29tZXMgYXQgYSBjb3N0OiBlYWNoIGFkZGl0aW9uYWwgbi1ncmFtIGluY3JlYXNlcyB0aGUgc2l6ZSBvZiB0aGUgdm9jYWJ1bGFyeSwgbGVhZGluZyB0byBhIGhpZ2hlci1kaW1lbnNpb25hbCByZXByZXNlbnRhdGlvbi4NCg0KDQpUaGlzIHJhaXNlcyBhIG5hdHVyYWwgcXVlc3Rpb246DQoNCmBgYHRleHQNCkRvIHdlIHJlYWxseSBuZWVkIHRvIGltcGxlbWVudCBhbGwgb2YgdGhpcyBtYW51YWxseT8NCkZvcnR1bmF0ZWx5LCBuby4gIA0KYGBgDQoNCg0KSW4gcHJhY3RpY2UsIG1vZGVybiBOTFAgbGlicmFyaWVzIHByb3ZpZGUgZWZmaWNpZW50IGFuZCB3ZWxsLXRlc3RlZCBpbXBsZW1lbnRhdGlvbnMgb2YgQmFnLW9mLVdvcmRzIG1vZGVscy4NCg0KSW4gdGhlIG5leHQgc2VjdGlvbiwgd2UgaW50cm9kdWNlIG9uZSBzdWNoIHRvb2wgdGhhdCBhdXRvbWF0ZXMgdGhpcyBlbnRpcmUgcHJvY2Vzcy4NCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIEltcGxlbWVudGluZyBCYWctb2YtV29yZHMgd2l0aCBgQ291bnRWZWN0b3JpemVyYA0KDQojIyMgVW5kZXJzdGFuZGluZyB0aGUgQm9XIHByb2NlZHVyZSB3aXRoIGBDb3VudFZlY3Rvcml6ZXJgDQoNCk1hbnVhbGx5IGJ1aWxkaW5nIGEgQmFnLW9mLVdvcmRzIChCb1cpIG1hdHJpeCBoZWxwcyBkZXZlbG9wIGludHVpdGlvbiwgYnV0IGl0IGlzIHJhcmVseSBuZWNlc3NhcnkgaW4gcHJhY3RpY2UuIEFzIGRpc2N1c3NlZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbiwgZXh0ZW5kaW5nIHJlcHJlc2VudGF0aW9ucyAoZS5nLiwgdG8gbi1ncmFtcykgcXVpY2tseSBpbmNyZWFzZXMgY29tcGxleGl0eS4NCg0KSW4gcHJhY3RpY2UsIFB5dGhvbiBwcm92aWRlcyBlZmZpY2llbnQgdG9vbHMgdGhhdCBhdXRvbWF0ZSB0aGlzIGVudGlyZSBwcm9jZXNzLiBPbmUgb2YgdGhlIG1vc3Qgd2lkZWx5IHVzZWQgaXMgYENvdW50VmVjdG9yaXplcmAgZnJvbSB0aGUgYHNjaWtpdC1sZWFybmAgbGlicmFyeS4NCg0KYENvdW50VmVjdG9yaXplcmAgdHJhbnNmb3JtcyBhIGNvbGxlY3Rpb24gb2YgdGV4dCBkb2N1bWVudHMgaW50byBhICpkb2N1bWVudC10ZXJtIG1hdHJpeCosIHdoZXJlOg0KDQotIEVhY2ggcm93IHJlcHJlc2VudHMgYSBkb2N1bWVudC4NCg0KLSBFYWNoIGNvbHVtbiBjb3JyZXNwb25kcyB0byBhIHRva2VuIGluIHRoZSBsZWFybmVkIHZvY2FidWxhcnkuDQoNCi0gRWFjaCBjZWxsIGNvbnRhaW5zIHRoZSBmcmVxdWVuY3kgb2YgdGhhdCB0b2tlbiBpbiB0aGUgZG9jdW1lbnQuICANCg0KDQpUaGlzIHByb2NlZHVyZSBtaXJyb3JzIHRoZSBtYW51YWwgY29uc3RydWN0aW9uIGRldmVsb3BlZCBlYXJsaWVyLCBidXQgaW4gYSBmdWxseSBhdXRvbWF0ZWQgYW5kIG9wdGltaXplZCB3YXkuDQoNCkZpZ3VyZSBcQHJlZihmaWc6RmlnLUNvdW50VmVjdG9yaXplcjEpIGlsbHVzdHJhdGVzIHRoZSB0cmFuc2Zvcm1hdGlvbiBwaXBlbGluZSBpbXBsZW1lbnRlZCBieSBgQ291bnRWZWN0b3JpemVyYCwgZnJvbSByYXcgdGV4dCB0byB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXguDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctQ291bnRWZWN0b3JpemVyMSwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24gdXNpbmcgYENvdW50VmVjdG9yaXplcmAuIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI5MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkNvdW50VmVjdG9yaXplcjEucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQpXZSBub3cgaWxsdXN0cmF0ZSB0aGlzIHByb2Nlc3Mgd2l0aCBhIHNpbXBsZSwgY29uY3JldGUgZXhhbXBsZS4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBCb1cgd2l0aCBgQ291bnRWZWN0b3JpemVyYDogZXhhbXBsZQ0KDQojIyMjIFRoZSBjb2RlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoNCkxldCB1cyBpbGx1c3RyYXRlIHRoaXMgd2l0aCBhIHNtYWxsLCBzZWxmLWNvbnRhaW5lZCBleGFtcGxlLg0KDQpGaXJzdCwgYSBgQ291bnRWZWN0b3JpemVyYCBvYmplY3QgaXMgaW5pdGlhbGl6ZWQgdXNpbmcgaXRzIGRlZmF1bHQgc2V0dGluZ3MuIFRoZW4sIHRoZSBtZXRob2QgYGZpdF90cmFuc2Zvcm0oKWAgaXMgYXBwbGllZCB0byB0aGUgY29ycHVzLiBUaGlzIG1ldGhvZCBzaW11bHRhbmVvdXNseSBsZWFybnMgdGhlIHZvY2FidWxhcnkgZnJvbSB0aGUgaW5wdXQgZG9jdW1lbnRzIGFuZCBjb25zdHJ1Y3RzIHRoZSBjb3JyZXNwb25kaW5nIEJhZy1vZi1Xb3JkcyBtYXRyaXguDQoNCkJlZm9yZSBpbXBsZW1lbnRpbmcgdGhlIG1vZGVsLCBub3RlIHRoYXQgdGhlIEJhZy1vZi1Xb3JkcyByZXByZXNlbnRhdGlvbiBpZ25vcmVzIHdvcmQgb3JkZXIgYW5kIGZvY3VzZXMgb25seSBvbiB0ZXJtIGZyZXF1ZW5jaWVzLg0KDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQpmcm9tIHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHQgaW1wb3J0IENvdW50VmVjdG9yaXplciAgIyAxDQoNCmRvY3VtZW50cyA9IFsNCiAgICAiRGF0YSBzY2llbmNlIHJlbGllcyBvbiBudW1lcmljYWwgbWV0aG9kcyIsDQogICAgIlRleHQgYW5hbHlzaXMgdXNlcyB2ZWN0b3JzIGFuZCBtYXRyaWNlcyIsDQogICAgIk1hdGhlbWF0aWNhbCByZXByZXNlbnRhdGlvbnMgc3VwcG9ydCBkYXRhIG1vZGVsaW5nIg0KXQ0KDQp2ZWN0b3JpemVyID0gQ291bnRWZWN0b3JpemVyKCkgICAgICAgICAgICAgICAgICAgICMgMg0KdmVjdG9yaXplcg0KDQpib3dfbWF0cml4ID0gdmVjdG9yaXplci5maXRfdHJhbnNmb3JtKGRvY3VtZW50cykgIyAzDQpib3dfbWF0cml4DQoNCiMgSW5zcGVjdCB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5IGFuZCBkb2N1bWVudC10ZXJtIG1hdHJpeA0KcHJpbnQodmVjdG9yaXplci5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkgIyA0IC0tPiBmaXJzdCBvdXRwdXQNCnByaW50KGJvd19tYXRyaXgudG9hcnJheSgpKSAgICAgICAgICAgICAgICMgNWEgLS0+IHNlY29uZCBvdXRwdXQNCnByaW50KGJvd19tYXRyaXgudG9kZW5zZSgpKSAgICAgICAgICAgICAgICMgNWIgLS0+IHNlY29uZCBvdXRwdXQNCmBgYA0KDQoqKkV4cGxhbmF0aW9uLioqDQoNCjEuIFRoZSBgQ291bnRWZWN0b3JpemVyYCBjbGFzcyBpcyBpbXBvcnRlZCBmcm9tIGBzY2lraXQtbGVhcm5gLiBJdCBpcyB1c2VkIHRvIGNvbnZlcnQgYSBjb2xsZWN0aW9uIG9mIHRleHQgZG9jdW1lbnRzIGludG8gYSBudW1lcmljYWwgcmVwcmVzZW50YXRpb24gYmFzZWQgb24gd29yZCBjb3VudHMuDQoNCjIuIEEgdmVjdG9yaXplciBvYmplY3QgaXMgY3JlYXRlZCB3aXRoIGRlZmF1bHQgcGFyYW1ldGVycy4gQnkgZGVmYXVsdCwgaXQ6DQogICAgDQogICAtIGNvbnZlcnRzIGFsbCB0ZXh0IHRvIGxvd2VyY2FzZSwNCiAgIA0KICAgLSB0b2tlbml6ZXMgdGhlIHRleHQgYXV0b21hdGljYWxseSwNCiAgIA0KICAgLSBidWlsZHMgdGhlIHZvY2FidWxhcnkgZnJvbSB0aGUgY29ycHVzLA0KICAgDQogICAtIGRvZXMgbm90IHJlbW92ZSBzdG9wd29yZHMuDQoNCjMuIFRoZSBtZXRob2QgYGZpdF90cmFuc2Zvcm0oKWAgcGVyZm9ybXMgdHdvIG9wZXJhdGlvbnMgaW4gYSBzaW5nbGUgc3RlcDoNCiAgICANCiAgIC0gKmZpdCo6IGxlYXJucyB0aGUgdm9jYWJ1bGFyeSBmcm9tIHRoZSBkb2N1bWVudHMsDQogICAgDQogICAtICp0cmFuc2Zvcm0qOiBjb252ZXJ0cyBlYWNoIGRvY3VtZW50IGludG8gYSB2ZWN0b3Igb2YgdGVybSBmcmVxdWVuY2llcy4NCg0KOjo6c2FuZ3JpYTMNClRoZSByZXN1bHQgaXMgYSBzcGFyc2UgbWF0cml4LCB3aGVyZSBtb3N0IGVudHJpZXMgYXJlIHplcm8uDQo6OjoNCg0KNC4gVGhlIG1ldGhvZCBgZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KClgIHJldHVybnMgdGhlIGxlYXJuZWQgdm9jYWJ1bGFyeS4gVGhlIG9yZGVyIG9mIHRoZXNlIHRlcm1zIGRlZmluZXMgdGhlIGNvbHVtbiBvcmRlcmluZyBvZiB0aGUgbWF0cml4Lg0KDQo1LiBUaGUgbWV0aG9kIGB0b2FycmF5KClgIGNvbnZlcnRzIHRoZSBzcGFyc2UgbWF0cml4IGludG8gYSBkZW5zZSBudW1lcmljYWwgYXJyYXkgZm9yIGluc3BlY3Rpb24NCg0KDQpUaGUgcmVzdWx0aW5nIG91dHB1dCBjb250YWlucyB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5IGFuZCB0aGUgYXNzb2NpYXRlZCBkb2N1bWVudC10ZXJtIG1hdHJpeCwgd2hpY2ggY29ycmVzcG9uZHMgZGlyZWN0bHkgdG8gdGhlIGNvbmNlcHR1YWwgQm9XIGNvbnN0cnVjdGlvbiBkaXNjdXNzZWQgZWFybGllci4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRmlyc3Qgb3V0cHV0IChjb2RlIDQpOiBsZWFybmVkIHZvY2FidWxhcnkuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmaXJzdCBvdXRwdXQgZGlzcGxheXMgdGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSwgdGhhdCBpcywgdGhlIHNldCBvZiB1bmlxdWUgdG9rZW5zIGV4dHJhY3RlZCBmcm9tIHRoZSBjb3JwdXMuDQoNCkVhY2ggdGVybSBjb3JyZXNwb25kcyB0byBhIGNvbHVtbiBpbiB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXgsIGFuZCB0aGUgb3JkZXIgc2hvd24gZGV0ZXJtaW5lcyB0aGUgY29sdW1uIHBvc2l0aW9ucyBpbiB0aGUgcmVwcmVzZW50YXRpb24uDQoNCkJ5IGRlZmF1bHQsIGBDb3VudFZlY3Rvcml6ZXJgIHNvcnRzIHRoZSB0ZXJtcyBhbHBoYWJldGljYWxseS4NCg0KYGBge3B5dGhvbixlY2hvPUZBTFNFfQ0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBDb3VudFZlY3Rvcml6ZXINCg0KZG9jdW1lbnRzID0gWw0KICAgICJEYXRhIHNjaWVuY2UgcmVsaWVzIG9uIG51bWVyaWNhbCBtZXRob2RzIiwNCiAgICAiVGV4dCBhbmFseXNpcyB1c2VzIHZlY3RvcnMgYW5kIG1hdHJpY2VzIiwNCiAgICAiTWF0aGVtYXRpY2FsIHJlcHJlc2VudGF0aW9ucyBzdXBwb3J0IGRhdGEgbW9kZWxpbmciDQpdDQoNCnZlY3Rvcml6ZXIgPSBDb3VudFZlY3Rvcml6ZXIoKQ0KYm93X21hdHJpeCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXIuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpICMgT3V0cHV0IDENCmBgYA0KDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU2Vjb25kIG91dHB1dCAoY29kZSA1KTogQmFnLW9mLVdvcmRzIG1hdHJpeC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhpcyBjb3JyZXNwb25kcyBkaXJlY3RseSB0byB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXggaW50cm9kdWNlZCBpbiB0aGUgbWFudWFsIGNvbnN0cnVjdGlvbi4NCg0KDQpgYGB7cHl0aG9uLGVjaG89RkFMU0V9DQpwcmludChib3dfbWF0cml4LnRvYXJyYXkoKSkgICAgICAgICAgICAgICAjIE91dHB1dCAyDQpgYGANCg0KVGhlIG91dHB1dCBzaG93cyB0aGUgKkJhZy1vZi1Xb3JkcyBtYXRyaXgqIGluIGRlbnNlIGZvcm0uDQoNCi0gRWFjaCByb3cgY29ycmVzcG9uZHMgdG8gb25lIGRvY3VtZW50Lg0KDQotIEVhY2ggY29sdW1uIGNvcnJlc3BvbmRzIHRvIG9uZSBvZiB0aGUgdm9jYWJ1bGFyeSB0ZXJtcyBsaXN0ZWQgYWJvdmUuIA0KDQotIEVhY2ggZW50cnkgaW5kaWNhdGVzIGhvdyBtYW55IHRpbWVzIGEgdGVybSBhcHBlYXJzIGluIGEgZG9jdW1lbnQuDQoNCkEgdmFsdWUgb2Y6DQoNCi0gMCBtZWFucyB0aGUgdGVybSBkb2VzIG5vdCBhcHBlYXIsDQoNCi0gYSBwb3NpdGl2ZSBpbnRlZ2VyIGluZGljYXRlcyBpdHMgZnJlcXVlbmN5Lg0KDQpUaGlzIHJlcHJlc2VudGF0aW9uIGlnbm9yZXMgd29yZCBvcmRlciBhbmQgc3ludGFjdGljIHN0cnVjdHVyZSwgcmV0YWluaW5nIG9ubHkgdGVybSBmcmVxdWVuY2llcy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIE1hdHJpeCByZXByZXNlbnRhdGlvbiBvZiB0aGUgQm9XLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUbyBrZWVwIHRoZSBub3RhdGlvbiBjb21wYWN0LCB3ZSBsYWJlbCBlYWNoIHRva2VuIHdpdGggYSBzaG9ydCBhYmJyZXZpYXRpb24gYW5kIHJlcG9ydCB0aGUgY29ycmVzcG9uZGluZyBkb2N1bWVudC10ZXJtIG1hdHJpeCBiZWxvdy4gDQoNClxbDQpcdGV4dHtUb2tlbiBtYXA6fVxxcXVhZA0KXGJlZ2lue2FycmF5fXtsbGxsbGxsbH0NClx0ZXh0dHR7YW5hfT1cdGV4dHR0e2FuYWx5c2lzfSwgJg0KXHRleHR0dHthbmR9PVx0ZXh0dHR7YW5kfSwgJg0KXHRleHR0dHtkYXR9PVx0ZXh0dHR7ZGF0YX0sICYNClx0ZXh0dHR7bWF0aH09XHRleHR0dHttYXRoZW1hdGljYWx9LCBcXA0KXHRleHR0dHttdHh9PVx0ZXh0dHR7bWF0cmljZXN9LCAmDQpcdGV4dHR0e21ldGh9PVx0ZXh0dHR7bWV0aG9kc30sICYNClx0ZXh0dHR7bW9kZWx9PVx0ZXh0dHR7bW9kZWxpbmd9LCAmDQpcdGV4dHR0e251bX09XHRleHR0dHtudW1lcmljYWx9LCBcXA0KXHRleHR0dHtvbn09XHRleHR0dHtvbn0sICYNClx0ZXh0dHR7cmVsfT1cdGV4dHR0e3JlbGllc30sICYNClx0ZXh0dHR7cmVwcn09XHRleHR0dHtyZXByZXNlbnRhdGlvbnN9LCAmDQpcdGV4dHR0e3NjaX09XHRleHR0dHtzY2llbmNlfSwgXFwNClx0ZXh0dHR7c3VwfT1cdGV4dHR0e3N1cHBvcnR9LCAmDQpcdGV4dHR0e3R4dH09XHRleHR0dHt0ZXh0fSwgJg0KXHRleHR0dHt1c2V9PVx0ZXh0dHR7dXNlc30sICYNClx0ZXh0dHR7dmVjfT1cdGV4dHR0e3ZlY3RvcnN9Lg0KXGVuZHthcnJheX0NClxdDQoNClRvIG1ha2UgdGhlIHN0cnVjdHVyZSBtb3JlIGV4cGxpY2l0LCB3ZSBwcmVzZW50IHRoZSBtYXRyaXggaW4gbWF0aGVtYXRpY2FsIGZvcm0uDQoNCkVhY2ggZW50cnkgJChpLGopJCByZXByZXNlbnRzIHRoZSBmcmVxdWVuY3kgb2YgdGVybSAkaiQgaW4gZG9jdW1lbnQgJGkkLg0KDQpUaGlzIHR5cGUgb2YgcmVwcmVzZW50YXRpb24gaXMgdHlwaWNhbGx5IHNwYXJzZSwgbWVhbmluZyB0aGF0IG1vc3QgZW50cmllcyBhcmUgemVybywgZXNwZWNpYWxseSBhcyB0aGUgdm9jYWJ1bGFyeSBzaXplIGluY3JlYXNlcy4NCg0KXFsNClxtYXRoYmZ7Qn09IFxsZWZ0KA0KXGJlZ2lue2FycmF5fXtjfGNjY2NjY2NjY2NjY2NjY2N9DQpcdGV4dHR0e0RvY30gJiBcdGV4dHR0e2FuYX0gJiBcdGV4dHR0e2FuZH0gJiBcdGV4dHR0e2RhdH0gJiBcdGV4dHR0e21hdGh9ICYgXHRleHR0dHttdHh9ICYgXHRleHR0dHttZXRofSAmIFx0ZXh0dHR7bW9kZWx9ICYgXHRleHR0dHtudW19ICYgXHRleHR0dHtvbn0gJiBcdGV4dHR0e3JlbH0gJiBcdGV4dHR0e3JlcHJ9ICYgXHRleHR0dHtzY2l9ICYgXHRleHR0dHtzdXB9ICYgXHRleHR0dHt0eHR9ICYgXHRleHR0dHt1c2V9ICYgXHRleHR0dHt2ZWN9IFxcDQpcaGxpbmUNClx0ZXh0eyMxfSAmIDAgJiAwICYgMSAmIDAgJiAwICYgMSAmIDAgJiAxICYgMSAmIDEgJiAwICYgMSAmIDAgJiAwICYgMCAmIDAgXFwNClx0ZXh0eyMyfSAmIDEgJiAxICYgMCAmIDAgJiAxICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAxICYgMSAmIDEgXFwNClx0ZXh0eyMzfSAmIDAgJiAwICYgMSAmIDEgJiAwICYgMCAmIDEgJiAwICYgMCAmIDAgJiAxICYgMCAmIDEgJiAwICYgMCAmIDAgXFwNClxlbmR7YXJyYXl9XHJpZ2h0KQ0KXF0NCg0KYGBge3IsIGV2YWw9RkFMU0UsIGVjaG89RkFMU0V9DQpcWw0KXG1hdGhiZntCfT0NClxiZWdpbnthcnJheX17bHxjY2NjY2NjY2NjY2NjY2NjfQ0KICYgXHRleHR0dHthbmFseXNpc30gJiBcdGV4dHR0e2FuZH0gJiBcdGV4dHR0e2RhdGF9ICYgXHRleHR0dHttYXRoZW1hdGljYWx9ICYgXHRleHR0dHttYXRyaWNlc30gJiBcdGV4dHR0e21ldGhvZHN9ICYgXHRleHR0dHttb2RlbGluZ30gJiBcdGV4dHR0e251bWVyaWNhbH0gJiBcdGV4dHR0e29ufSAmIFx0ZXh0dHR7cmVsaWVzfSAmIFx0ZXh0dHR7cmVwcmVzZW50YXRpb25zfSAmIFx0ZXh0dHR7c2NpZW5jZX0gJiBcdGV4dHR0e3N1cHBvcnR9ICYgXHRleHR0dHt0ZXh0fSAmIFx0ZXh0dHR7dXNlc30gJiBcdGV4dHR0e3ZlY3RvcnN9IFxcDQpcaGxpbmUNClx0ZXh0e0RvY3VtZW50IDF9ICYgMCAmIDAgJiAxICYgMCAmIDAgJiAxICYgMCAmIDEgJiAxICYgMSAmIDAgJiAxICYgMCAmIDAgJiAwICYgMCBcXA0KXHRleHR7RG9jdW1lbnQgMn0gJiAxICYgMSAmIDAgJiAwICYgMSAmIDAgJiAwICYgMCAmIDAgJiAwICYgMCAmIDAgJiAwICYgMSAmIDEgJiAxIFxcDQpcdGV4dHtEb2N1bWVudCAzfSAmIDAgJiAwICYgMSAmIDEgJiAwICYgMCAmIDEgJiAwICYgMCAmIDAgJiAxICYgMCAmIDEgJiAwICYgMCAmIDAgXFwNClxlbmR7YXJyYXl9DQpcXQ0KYGBgDQoNCg0KDQpGb3IgZXhhbXBsZSwgdGhlIHRva2VuIGBkYXRhYCBhcHBlYXJzIG9uY2UgaW4gdGhlIGZpcnN0IGFuZCB0aGlyZCBkb2N1bWVudHMsIGFuZCBkb2VzIG5vdCBhcHBlYXIgaW4gdGhlIHNlY29uZCBkb2N1bWVudC4gU2ltaWxhcmx5LCB0aGUgdG9rZW4gYGFuYWx5c2lzYCBhcHBlYXJzIG9ubHkgaW4gdGhlIHNlY29uZCBkb2N1bWVudCwgd2hpbGUgYG51bWVyaWNhbGAgYXBwZWFycyBvbmx5IGluIHRoZSBmaXJzdCBkb2N1bWVudC4gVGhpcyBzcGFyc2l0eSBwYXR0ZXJuIGlzIHR5cGljYWwgb2YgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9ucywgZXNwZWNpYWxseSBhcyB0aGUgdm9jYWJ1bGFyeSBzaXplIGdyb3dzLg0KDQpNb3JlIGdlbmVyYWxseSwgZWFjaCBlbnRyeSAkKGksaikkIHJlcHJlc2VudHMgdGhlIGZyZXF1ZW5jeSBvZiB0ZXJtICRqJCBpbiBkb2N1bWVudCAkaSQuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIEhlYXRtYXAgb2YgdGhlIEJvVy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIHNhbWUgbWF0cml4IGNhbiBiZSB2aXN1YWxpemVkIGFzIGEgaGVhdG1hcCwgd2hlcmUgZGFya2VyIGNlbGxzIGluZGljYXRlIGhpZ2hlciB0b2tlbiBjb3VudHMuDQoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZ3M9RkFMU0V9DQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgc2VhYm9ybiBhcyBzbnMNCmltcG9ydCBwYW5kYXMgYXMgcGQNCg0KdGVybXMgPSB2ZWN0b3JpemVyLmdldF9mZWF0dXJlX25hbWVzX291dCgpDQpYID0gYm93X21hdHJpeC50b2FycmF5KCkNCmRmX2JvdyA9IHBkLkRhdGFGcmFtZShYLCBjb2x1bW5zPXRlcm1zKQ0KDQpwbHQuZmlndXJlKGZpZ3NpemU9KDE0LDUpKTsNCmF4ID0gc25zLmhlYXRtYXAoZGZfYm93LCBjbWFwPSJCbHVlcyIsIGNiYXI9VHJ1ZSkNCg0KIyAtLS0gVGl0bGUgYW5kIGF4aXMgbGFiZWxzIC0tLQ0KYXguc2V0X3RpdGxlKCJCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24iLCBmb250c2l6ZT0xOCwgcGFkPTEwKTsNCmF4LnNldF94bGFiZWwoIlZvY2FidWxhcnkgdGVybXMiLCBmb250c2l6ZT0xOCk7DQpheC5zZXRfeWxhYmVsKCJEb2N1bWVudHMiLCBmb250c2l6ZT0xOCk7DQoNCiMgLS0tIFRpY2sgbGFiZWxzIC0tLQ0KYXgudGlja19wYXJhbXMoYXhpcz0ieCIsIGxhYmVsc2l6ZT0xNCwgcm90YXRpb249NDUpDQpheC50aWNrX3BhcmFtcyhheGlzPSJ5IiwgbGFiZWxzaXplPTE0KQ0KDQojIC0tLSBDb2xvcmJhciBmb250IHNpemUgLS0tDQpjYmFyID0gYXguY29sbGVjdGlvbnNbMF0uY29sb3JiYXINCmNiYXIuYXgudGlja19wYXJhbXMobGFiZWxzaXplPTE0KQ0KDQpwbHQudGlnaHRfbGF5b3V0KCkgIyBQcmV2ZW50cyBjcm9wcGluZw0KcGx0LnNob3coKQ0KYGBgDQoNCg0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5ncz1GQUxTRX0NCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmltcG9ydCBzZWFib3JuIGFzIHNucw0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KDQp0ZXJtcyA9IHZlY3Rvcml6ZXIuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkNClggPSBib3dfbWF0cml4LnRvYXJyYXkoKSBpZiBoYXNhdHRyKGJvd19tYXRyaXgsICJ0b2FycmF5IikgZWxzZSBib3dfbWF0cml4DQpkZl9ib3cgPSBwZC5EYXRhRnJhbWUoWCwgY29sdW1ucz10ZXJtcykNCg0KcGx0LmZpZ3VyZShmaWdzaXplPSgxNCw1KSk7DQpheCA9IHNucy5oZWF0bWFwKGRmX2JvdywgY21hcD0iQmx1ZXMiLCBjYmFyPVRydWUpDQoNCiMgLS0tIFRpdGxlIGFuZCBheGlzIGxhYmVscyAtLS0NCmF4LnNldF90aXRsZSgiQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9uIiwgZm9udHNpemU9MTgsIHBhZD0xMCk7DQpheC5zZXRfeGxhYmVsKCJWb2NhYnVsYXJ5IHRlcm1zIiwgZm9udHNpemU9MTgpOw0KYXguc2V0X3lsYWJlbCgiRG9jdW1lbnRzIiwgZm9udHNpemU9MTgpOw0KDQojIC0tLSBUaWNrIGxhYmVscyAtLS0NCmF4LnRpY2tfcGFyYW1zKGF4aXM9IngiLCBsYWJlbHNpemU9MTQsIHJvdGF0aW9uPTQ1KQ0KYXgudGlja19wYXJhbXMoYXhpcz0ieSIsIGxhYmVsc2l6ZT0xNCkNCg0KIyAtLS0gQ29sb3JiYXIgZm9udCBzaXplIC0tLQ0KY2JhciA9IGF4LmNvbGxlY3Rpb25zWzBdLmNvbG9yYmFyDQpjYmFyLmF4LnRpY2tfcGFyYW1zKGxhYmVsc2l6ZT0xNCkNCg0KcGx0LnRpZ2h0X2xheW91dCgpICMgUHJldmVudHMgY3JvcHBpbmcNCnBsdC5zaG93KCkNCmBgYA0KDQoNCkVhY2ggY2VsbCByZXByZXNlbnRzIHRoZSBmcmVxdWVuY3kgb2YgYSB0ZXJtIGluIGEgZG9jdW1lbnQuDQoNClNpbmNlIHRoaXMgaXMgYSBzbWFsbCBjb3JwdXMsIG1vc3QgdmFsdWVzIGFyZSBlaXRoZXIgMCBvciAxLCBzbyB0aGUgaGVhdG1hcCBwcmltYXJpbHkgaGlnaGxpZ2h0cyB0aGUgcHJlc2VuY2Ugb3IgYWJzZW5jZSBvZiB0ZXJtcyByYXRoZXIgdGhhbiBzdHJvbmcgZnJlcXVlbmN5IGRpZmZlcmVuY2VzLg0KDQpOb3RlIHRoYXQgY29tbW9uIHdvcmRzIHN1Y2ggYXMgKmFuZCogYW5kICpvbiogYXJlIGluY2x1ZGVkIGluIHRoZSB2b2NhYnVsYXJ5IGJlY2F1c2Ugbm8gc3RvcHdvcmQgZmlsdGVyaW5nIHdhcyBhcHBsaWVkIGluIHRoaXMgZXhhbXBsZS4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRmluYWwgcmVtYXJrcy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhpcyBleGFtcGxlIGlsbHVzdHJhdGVzIGhvdyBgQ291bnRWZWN0b3JpemVyYCBhdXRvbWF0ZXM6DQoNCi0gdG9rZW5pemF0aW9uLA0KDQotIHZvY2FidWxhcnkgY29uc3RydWN0aW9uLA0KDQotIGFuZCB3b3JkIGNvdW50aW5nLg0KDQpUaGUgcmVzdWx0aW5nIEJhZy1vZi1Xb3JkcyByZXByZXNlbnRhdGlvbiBwcm92aWRlcyBhIHNpbXBsZSB5ZXQgcG93ZXJmdWwgd2F5IHRvIHRyYW5zZm9ybSB0ZXh0IGludG8gbnVtZXJpY2FsIGZlYXR1cmVzIHN1aXRhYmxlIGZvciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4NCg0KSG93ZXZlciwgaXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCB0aGlzIHJlcHJlc2VudGF0aW9uIGlnbm9yZXMgd29yZCBvcmRlciBhbmQgY29udGV4dCwgd2hpY2ggbW90aXZhdGVzIG1vcmUgYWR2YW5jZWQgYXBwcm9hY2hlcyBzdWNoIGFzIFRGLUlERiBhbmQgd29yZCBlbWJlZGRpbmdzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgYENvdW50VmVjdG9yaXplcmA6IGFkZGl0aW9uYWwgYXJndW1lbnRzDQoNCiMjIyBVbmRlcnN0YW5kaW5nIGhvdyBgQ291bnRWZWN0b3JpemVyYCBjYW4gYmUgY3VzdG9taXplZA0KDQpJbXBvcnRhbnRseSwgYENvdW50VmVjdG9yaXplcmAgcHJvdmlkZXMgc2V2ZXJhbCBhcmd1bWVudHMgdGhhdCBhbGxvdyB0aGUgYmFzaWMgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9uIHRvIGJlIHJlZmluZWQgYW5kIGNvbnRyb2xsZWQsIHN1Y2ggYXMgdm9jYWJ1bGFyeSBzaXplIGxpbWl0cyBhbmQgZG9jdW1lbnQtZnJlcXVlbmN5IHRocmVzaG9sZHMuIFRoZXNlIGFyZ3VtZW50cywgaWxsdXN0cmF0ZWQgaW4gRmlndXJlIFxAcmVmKGZpZzpGaWctQ291bnRWZWN0b3JpemVyMiksIHdpbGwgYmUgaW50cm9kdWNlZCBjb25jZXB0dWFsbHkgaGVyZSBhbmQgaW1wbGVtZW50ZWQgaW4gZGV0YWlsIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuIA0KDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctQ291bnRWZWN0b3JpemVyMiwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJCYWctb2Ytd29yZHMgLSBgQ291bnRWZWN0b3JpemVyYCBhcmd1bWVudHMuIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI5MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkNvdW50VmVjdG9yaXplcjIucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBPdmVydmlldyBvZiBjdXN0b21pemF0aW9uIG9wdGlvbnMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVG8gYmV0dGVyIG9yZ2FuaXplIHRoZXNlIG9wdGlvbnMsIGl0IGlzIHVzZWZ1bCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIGJ1aWx0LWluIHByb2Nlc3NpbmcgZmVhdHVyZXMgYW5kIGV4cGxpY2l0IHZvY2FidWxhcnkgY29udHJvbCBwYXJhbWV0ZXJzLg0KDQpUaGUgYmVoYXZpb3Igb2YgYENvdW50VmVjdG9yaXplcmAgY2FuIGJlIGFkanVzdGVkIHRocm91Z2ggc2V2ZXJhbCBhcmd1bWVudHMsIHdoaWNoIGNhbiBiZSBncm91cGVkIGludG8gdHdvIG1haW4gY2F0ZWdvcmllczoNCg0KDQoqKjEuIEJ1aWx0LWluIHByb2Nlc3NpbmcgZmVhdHVyZXMgKG91dC1vZi10aGUtYm94IGJlaGF2aW9yKToqKg0KDQotIEF1dG9tYXRpYyB2b2NhYnVsYXJ5IGxlYXJuaW5nLg0KDQotIFRva2VuaXphdGlvbi4NCg0KLSBTdXBwb3J0IGZvciBuLWdyYW1zIChgbmdyYW1fcmFuZ2VgKS4NCg0KLSBPcHRpb25hbCBzdG9wd29yZCByZW1vdmFsIChgc3RvcF93b3Jkc2ApLiAgDQoNCioqMi4gVm9jYWJ1bGFyeSBjb250cm9sIHBhcmFtZXRlcnM6KioNCg0KLSBgbWF4X2ZlYXR1cmVzYDogbGltaXRzIHRoZSB2b2NhYnVsYXJ5IHRvIHRoZSB0b3AgTiBtb3N0IGZyZXF1ZW50IHRlcm1zLg0KDQotIGBtaW5fZGZgOiByZW1vdmVzIHRlcm1zIHRoYXQgYXBwZWFyIGluIHRvbyBmZXcgZG9jdW1lbnRzLg0KDQotIGBtYXhfZGZgOiByZW1vdmVzIHRlcm1zIHRoYXQgYXBwZWFyIGluIHRvbyBtYW55IGRvY3VtZW50cy4gIA0KDQpUaGVzZSBvcHRpb25zIGFsbG93IHRoZSB1c2VyIHRvIGJhbGFuY2UgZXhwcmVzc2l2ZW5lc3MgYW5kIGRpbWVuc2lvbmFsaXR5LCB0YWlsb3JpbmcgdGhlIHJlcHJlc2VudGF0aW9uIHRvIHRoZSBzcGVjaWZpYyB0YXNrIGFuZCBkYXRhc2V0Lg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIGBDb3VudFZlY3Rvcml6ZXJgOiBvdXQtb2YtdGhlLWJveCBmZWF0dXJlcyANCg0KQmV5b25kIGJhc2ljIHdvcmQgY291bnRzLCBgQ291bnRWZWN0b3JpemVyYCBpbmNsdWRlcyBzZXZlcmFsIGJ1aWx0LWluIG9wdGlvbnMgdGhhdCBtYWtlIGl0IGZsZXhpYmxlIGFuZCBwcmFjdGljYWwgZm9yIHJlYWwtd29ybGQgYXBwbGljYXRpb25zLg0KDQpXZSBub3cgZXhwbG9yZSBzb21lIG9mIHRoZSBtb3N0IGNvbW1vbmx5IHVzZWQgZmVhdHVyZXMuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBBdXRvbWF0aWMgdm9jYWJ1bGFyeSBsZWFybmluZyBhbmQgbi1ncmFtIGdlbmVyYXRpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCkJ5IGRlZmF1bHQsIGBDb3VudFZlY3Rvcml6ZXJgIGxlYXJucyBpdHMgdm9jYWJ1bGFyeSBkaXJlY3RseSBmcm9tIHRoZSBkYXRhLiBJbiBhZGRpdGlvbiwgaXQgY2FuOg0KDQotIEFwcGx5IHRva2VuaXphdGlvbiBpbnRlcm5hbGx5LA0KDQotIFJlbW92ZSBzdG9wd29yZHMgYXV0b21hdGljYWxseSwgYW5kDQoNCi0gR2VuZXJhdGUgbi1ncmFtcyB3aXRob3V0IGFkZGl0aW9uYWwgY29kZS4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQo6OjpzYW5ncmlhMw0KIyMjIyBFeGFtcGxlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUsIHRoZSBhcmd1bWVudCBgbmdyYW1fcmFuZ2UgPSAoMSwgMylgIGluc3RydWN0cyB0aGUgdmVjdG9yaXplciB0byBpbmNsdWRlICp1bmlncmFtcyosICpiaWdyYW1zKiwgYW5kICp0cmlncmFtcyosIHRoYXQgaXMsIHNpbmdsZSB3b3JkcywgcGFpcnMgb2YgY29uc2VjdXRpdmUgd29yZHMsIGFuZCBzZXF1ZW5jZXMgb2YgdGhyZWUgY29uc2VjdXRpdmUgd29yZHMuDQoNCkZpcnN0LCBhIGBDb3VudFZlY3Rvcml6ZXJgIG9iamVjdCBpcyBjcmVhdGVkIHdpdGggdGhlIHNwZWNpZmllZCBuLWdyYW0gcmFuZ2UuIFRoZSBtZXRob2QgYGZpdF90cmFuc2Zvcm0oKWAgdGhlbiBsZWFybnMgdGhlIHZvY2FidWxhcnkgZnJvbSB0aGUgY29ycHVzIGFuZCBjb25zdHJ1Y3RzIHRoZSBjb3JyZXNwb25kaW5nIEJhZy1vZi1Xb3JkcyBtYXRyaXgsIHdoZXJlIGVhY2ggY29sdW1uIHJlcHJlc2VudHMgYW4gbi1ncmFtIGFuZCBlYWNoIHJvdyByZXByZXNlbnRzIGEgZG9jdW1lbnQuDQoNCg0KDQpgYGB7cHl0aG9uLCBldmFsID1GQUxTRX0NCnZlY3Rvcml6ZXJfbmdyYW0gPSBDb3VudFZlY3Rvcml6ZXIobmdyYW1fcmFuZ2U9KDEsIDMpKQ0KYm93X25ncmFtID0gdmVjdG9yaXplcl9uZ3JhbS5maXRfdHJhbnNmb3JtKGRvY3VtZW50cykNCg0KcHJpbnQodmVjdG9yaXplcl9uZ3JhbS5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkgIyBPdXRwdXQgMQ0KcHJpbnQoYm93X25ncmFtLnRvYXJyYXkoKSkgICAgICAgICAgICAgICAgICAgICAgIyBPdXRwdXQgMg0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBGaXJzdCBvdXRwdXQuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmaXJzdCBvdXRwdXQgZGlzcGxheXMgdGhlIGxlYXJuZWQgbi1ncmFtIHZvY2FidWxhcnkuIEFzIGEgcmVzdWx0LCB0ZXJtcyBzdWNoIGFzIGBhbmFseXNpc2AgKHVuaWdyYW0pLCBgdGV4dCBhbmFseXNpc2AgKGJpZ3JhbSksIGFuZCBgdGV4dCBhbmFseXNpcyB1c2VzYCAodHJpZ3JhbSkgY29leGlzdCBhcyBkaXN0aW5jdCBmZWF0dXJlcyBpbiB0aGUgcmVwcmVzZW50YXRpb24uIFRoZSBvcmRlciBzaG93biBoZXJlIGRlZmluZXMgdGhlIGNvbHVtbiBvcmRlcmluZyBvZiB0aGUgQmFnLW9mLVdvcmRzIG1hdHJpeC4NCg0KYGBge3B5dGhvbiwgZWNobyA9RkFMU0V9DQp2ZWN0b3JpemVyX25ncmFtID0gQ291bnRWZWN0b3JpemVyKG5ncmFtX3JhbmdlPSgxLCAzKSkNCmJvd19uZ3JhbSA9IHZlY3Rvcml6ZXJfbmdyYW0uZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXJfbmdyYW0uZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpDQpgYGANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQpUbyBmYWNpbGl0YXRlIGxhdGVyIHJlZmVyZW5jZSBhbmQgZGlzY3Vzc2lvbiwgdGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSBpcyBsaXN0ZWQgYmVsb3cgYXMgYW4gaW5kZXhlZCBzZXF1ZW5jZS4gVGhpcyBlbnVtZXJhdGlvbiB3aWxsIGJlIHVzZWQgaW4gc3Vic2VxdWVudCBzZWN0aW9ucyB0byBpbGx1c3RyYXRlIGhvdyBuLWdyYW0gZmVhdHVyZXMgYXJlIGZpbHRlcmVkLCBzZWxlY3RlZCwgb3Igd2VpZ2h0ZWQgd2hlbiBhcHBseWluZyBhZGRpdGlvbmFsIGFyZ3VtZW50cyBvZiBgQ291bnRWZWN0b3JpemVyYC4NCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZXZhbD1GQUxTRX0NCjEuIGFuYWx5c2lzICANCjIuIGFuYWx5c2lzIHVzZXMgIA0KMy4gYW5hbHlzaXMgdXNlcyB2ZWN0b3JzICANCjQuIGFuZCAgDQo1LiBhbmQgbWF0cmljZXMgIA0KNi4gZGF0YSAgDQo3LiBkYXRhIG1vZGVsaW5nICANCjguIGRhdGEgc2NpZW5jZSAgDQo5LiBkYXRhIHNjaWVuY2UgcmVsaWVzICANCjEwLiBtYXRoZW1hdGljYWwgIA0KMTEuIG1hdGhlbWF0aWNhbCByZXByZXNlbnRhdGlvbnMgIA0KMTIuIG1hdGhlbWF0aWNhbCByZXByZXNlbnRhdGlvbnMgc3VwcG9ydCAgDQoxMy4gbWF0cmljZXMgIA0KMTQuIG1ldGhvZHMgIA0KMTUuIG1vZGVsaW5nICANCjE2LiBudW1lcmljYWwgIA0KMTcuIG51bWVyaWNhbCBtZXRob2RzICANCjE4LiBvbiAgDQoxOS4gb24gbnVtZXJpY2FsICANCjIwLiBvbiBudW1lcmljYWwgbWV0aG9kcyAgDQoyMS4gcmVsaWVzICANCjIyLiByZWxpZXMgb24gIA0KMjMuIHJlbGllcyBvbiBudW1lcmljYWwgIA0KMjQuIHJlcHJlc2VudGF0aW9ucyAgDQoyNS4gcmVwcmVzZW50YXRpb25zIHN1cHBvcnQgIA0KMjYuIHJlcHJlc2VudGF0aW9ucyBzdXBwb3J0IGRhdGEgIA0KMjcuIHNjaWVuY2UgIA0KMjguIHNjaWVuY2UgcmVsaWVzICANCjI5LiBzY2llbmNlIHJlbGllcyBvbiAgDQozMC4gc3VwcG9ydCAgDQozMS4gc3VwcG9ydCBkYXRhICANCjMyLiBzdXBwb3J0IGRhdGEgbW9kZWxpbmcgIA0KMzMuIHRleHQgIA0KMzQuIHRleHQgYW5hbHlzaXMgIA0KMzUuIHRleHQgYW5hbHlzaXMgdXNlcyAgDQozNi4gdXNlcyAgDQozNy4gdXNlcyB2ZWN0b3JzICANCjM4LiB1c2VzIHZlY3RvcnMgYW5kICANCjM5LiB2ZWN0b3JzICANCjQwLiB2ZWN0b3JzIGFuZCAgDQo0MS4gdmVjdG9ycyBhbmQgbWF0cmljZXMNCmBgYA0KDQoNCmBgYHtyLCAgcmVzdWx0cz0nYXNpcyd9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCg0KdG9rZW5zIDwtIGMoDQogICJhbmFseXNpcyIsDQogICJhbmFseXNpcyB1c2VzIiwNCiAgImFuYWx5c2lzIHVzZXMgdmVjdG9ycyIsDQogICJhbmQiLA0KICAiYW5kIG1hdHJpY2VzIiwNCiAgImRhdGEiLA0KICAiZGF0YSBtb2RlbGluZyIsDQogICJkYXRhIHNjaWVuY2UiLA0KICAiZGF0YSBzY2llbmNlIHJlbGllcyIsDQogICJtYXRoZW1hdGljYWwiLA0KICAibWF0aGVtYXRpY2FsIHJlcHJlc2VudGF0aW9ucyIsDQogICJtYXRoZW1hdGljYWwgcmVwcmVzZW50YXRpb25zIHN1cHBvcnQiLA0KICAibWF0cmljZXMiLA0KICAibWV0aG9kcyIsDQogICJtb2RlbGluZyIsDQogICJudW1lcmljYWwiLA0KICAibnVtZXJpY2FsIG1ldGhvZHMiLA0KICAib24iLA0KICAib24gbnVtZXJpY2FsIiwNCiAgIm9uIG51bWVyaWNhbCBtZXRob2RzIiwNCiAgInJlbGllcyIsDQogICJyZWxpZXMgb24iLA0KICAicmVsaWVzIG9uIG51bWVyaWNhbCIsDQogICJyZXByZXNlbnRhdGlvbnMiLA0KICAicmVwcmVzZW50YXRpb25zIHN1cHBvcnQiLA0KICAicmVwcmVzZW50YXRpb25zIHN1cHBvcnQgZGF0YSIsDQogICJzY2llbmNlIiwNCiAgInNjaWVuY2UgcmVsaWVzIiwNCiAgInNjaWVuY2UgcmVsaWVzIG9uIiwNCiAgInN1cHBvcnQiLA0KICAic3VwcG9ydCBkYXRhIiwNCiAgInN1cHBvcnQgZGF0YSBtb2RlbGluZyIsDQogICJ0ZXh0IiwNCiAgInRleHQgYW5hbHlzaXMiLA0KICAidGV4dCBhbmFseXNpcyB1c2VzIiwNCiAgInVzZXMiLA0KICAidXNlcyB2ZWN0b3JzIiwNCiAgInVzZXMgdmVjdG9ycyBhbmQiLA0KICAidmVjdG9ycyIsDQogICJ2ZWN0b3JzIGFuZCIsDQogICJ2ZWN0b3JzIGFuZCBtYXRyaWNlcyINCikNCg0KdG9rX3RibCA8LSB0aWJibGUoDQogIElEID0gc2VxX2Fsb25nKHRva2VucyksDQogIFRva2VuID0gdG9rZW5zDQopICU+JQ0KICBtdXRhdGUoDQogICAgbl93b3JkcyA9IHN0cl9jb3VudChUb2tlbiwgIlxcUysiKSAjIGN1ZW50YSAicGFsYWJyYXMiIHNlcGFyYWRhcyBwb3IgZXNwYWNpb3MNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgIFVuaWdyYW0gPSBpZmVsc2Uobl93b3JkcyA9PSAxLCAi4pyTIiwgIiIpLA0KICAgIEJpZ3JhbSAgPSBpZmVsc2Uobl93b3JkcyA9PSAyLCAi4pyTIiwgIiIpLA0KICAgIFRyaWdyYW0gPSBpZmVsc2Uobl93b3JkcyA9PSAzLCAi4pyTIiwgIiIpDQogICkgJT4lDQogIHNlbGVjdChJRCwgVG9rZW4sIFVuaWdyYW0sIEJpZ3JhbSwgVHJpZ3JhbSkNCg0KIyBNb3N0cmFyIGVuIHRhYmxhIGNvbiBmb3JtYXRvDQprYWJsZSh0b2tfdGJsLCBhbGlnbiA9ICJjbGNjYyIsDQogICAgICBjb2wubmFtZXMgPSBjKCJUb2tlbiBJRCIsICJUb2tlbiAoYXMgbGVhcm5lZCkiLCAiVW5pZ3JhbSIsICJCaWdyYW0iLCAiVHJpZ3JhbSIpLA0KICAgICAgY2FwdGlvbiA9ICJJbmRleGVkIG4tZ3JhbSB2b2NhYnVsYXJ5IGFuZCB0b2tlbiB0eXBlIChiYXNlZCBvbiB3b3JkIGNvdW50KS4iLA0KICAgICAgIGZvcm1hdCA9ICJodG1sIiwNCiAgICAgICBib29rdGFicyA9IFRSVUUpICU+JQ0Ka2FibGVfc3R5bGluZygpICU+JQ0Ka2FibGVfY2xhc3NpY18yKGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQpUaGlzIGV4YW1wbGUgc2hvd3MgdGhhdCB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5IGNvbnRhaW5zIHRva2VucyBvZiBkaWZmZXJlbnQgbGVuZ3Rocy4gRm9yIGluc3RhbmNlOg0KDQotIFRva2VucyAxLCA2LCAxMCwgMTYsIGFuZCAzMyBjb3JyZXNwb25kIHRvICp1bmlncmFtcyouDQoNCi0gVG9rZW5zIDIsIDcsIDgsIDE3LCBhbmQgMzQgY29ycmVzcG9uZCB0byAqYmlncmFtcyouDQoNCi0gVG9rZW5zIDMsIDExLCAxOCwgMzUsIGFuZCAzOCBjb3JyZXNwb25kIHRvICp0cmlncmFtcyouDQoNClRoZXNlIGRpZmZlcmVuY2VzIGFyaXNlIHNvbGVseSBmcm9tIHRoZSBjaG9zZW4gYG5ncmFtX3JhbmdlYCBhbmQgZG8gbm90IGNoYW5nZSB0aGUgdW5kZXJseWluZyBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24uDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU2Vjb25kIG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIHNlY29uZCBvdXRwdXQgc2hvd3MgdGhlIEJhZy1vZi1Xb3JkcyBtYXRyaXggY29uc3RydWN0ZWQgdXNpbmcgdGhlIG4tZ3JhbSB2b2NhYnVsYXJ5LiBFYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIGRvY3VtZW50LCBlYWNoIGNvbHVtbiBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIG4tZ3JhbSwgYW5kIHRoZSB2YWx1ZSBpbiBlYWNoIGNlbGwgaW5kaWNhdGVzIGhvdyBtYW55IHRpbWVzIHRoYXQgbi1ncmFtIGFwcGVhcnMgaW4gdGhlIGRvY3VtZW50Lg0KDQpgYGB7cHl0aG9uLCBlY2hvID1GQUxTRX0NCnByaW50KGJvd19uZ3JhbS50b2FycmF5KCkpDQpgYGANCg0KDQpGb3IgcmVhZGFiaWxpdHksIHRoZSBCYWctb2YtV29yZHMgbWF0cml4IGlzIHByZXNlbnRlZCBpbiB0d28gYmxvY2tzLCBjb3JyZXNwb25kaW5nIHRvIHRva2VucyAx4oCTMjEgYW5kIDIy4oCTNDA6DQoNCg0KYGBge3IsIGVjaG89RkFMU0UsIGV2YWw9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KDQpCIDwtIG1hdHJpeCgNCiAgYygNCiAgICAjIERvYyAxICg0MSB2YWxvcmVzKQ0KICAgIDAsMCwwLDAsMCwxLDAsMSwxLDAsIDAsMCwwLDEsMCwxLDEsMSwxLDEsIDEsMSwxLDAsMCwwLDEsMSwxLDAsIDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwNCiAgICAjIERvYyAyICg0MSB2YWxvcmVzKQ0KICAgIDEsMSwxLDEsMSwwLDAsMCwwLDAsIDAsMCwxLDAsMCwwLDAsMCwwLDAsIDAsMCwwLDAsMCwwLDAsMCwwLDAsIDAsMCwxLDEsMSwxLDEsMSwxLDEsMSwNCiAgICAjIERvYyAzICg0MSB2YWxvcmVzKQ0KICAgIDAsMCwwLDAsMCwxLDEsMCwwLDEsIDEsMSwwLDAsMSwwLDAsMCwwLDAsIDAsMCwwLDEsMSwxLDAsMCwwLDEsIDEsMSwwLDAsMCwwLDAsMCwwLDAsMA0KICApLA0KICBucm93ID0gMywNCiAgYnlyb3cgPSBUUlVFDQopDQoNCmNvbG5hbWVzKEIpIDwtIDE6NDEjIHBhc3RlMCgiVCIsIDE6NDEpICAgIyBvIHNvbG8gMTo0MSBzaSBwcmVmaWVyZXMNCnJvd25hbWVzKEIpIDwtIHBhc3RlMCgiRCIsIDE6MykNCg0Ka2FibGUoQiwNCiAgICAgIGFsaWduID0gImMiLA0KICAgICAgY2FwdGlvbiA9ICJCYWctb2YtV29yZHMgbWF0cml4IChpbmRleGVkIG4tZ3JhbSBmZWF0dXJlcykuIENvbHVtbnMgY29ycmVzcG9uZCB0byB0b2tlbnMgMS00MS4iKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpICU+JQ0KICBrYWJsZV9jbGFzc2ljXzIoKQ0KDQpgYGANCg0KDQpgYGB7ciwgZXZhbD0gRkFMU0UsIHJlc3VsdHM9J2FzaXMnfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCg0KIyAtLS0gTWF0cml6IG9yaWdpbmFsIC0tLQ0KQiA8LSBtYXRyaXgoDQogIGMoDQogICAgIyBEb2MgMQ0KICAgIDAsMCwwLDAsMCwxLDAsMSwxLDAsIDAsMCwwLDEsMCwxLDEsMSwxLDEsDQogICAgMSwxLDEsMCwwLDAsMSwxLDEsMCwgMCwwLDAsMCwwLDAsMCwwLDAsMCwwLA0KICAgICMgRG9jIDINCiAgICAxLDEsMSwxLDEsMCwwLDAsMCwwLCAwLDAsMSwwLDAsMCwwLDAsMCwwLA0KICAgIDAsMCwwLDAsMCwwLDAsMCwwLDAsIDAsMCwxLDEsMSwxLDEsMSwxLDEsMSwNCiAgICAjIERvYyAzDQogICAgMCwwLDAsMCwwLDEsMSwwLDAsMSwgMSwxLDAsMCwxLDAsMCwwLDAsMCwNCiAgICAwLDAsMCwxLDEsMSwwLDAsMCwxLCAxLDEsMCwwLDAsMCwwLDAsMCwwLDANCiAgKSwNCiAgbnJvdyA9IDMsDQogIGJ5cm93ID0gVFJVRQ0KKQ0KDQpjb2xuYW1lcyhCKSA8LSBwYXN0ZTAoIlQiLCAxOjQxKSAgICMgbyBzb2xvIDE6NDEgc2kgcHJlZmllcmVzDQpyb3duYW1lcyhCKSA8LSBwYXN0ZTAoIkRvYy4iLCAxOjMpDQoNCiMgLS0tIFN1YnRhYmxhIDE6IFRva2VucyAx4oCTMjEgLS0tDQpCXzFfMjEgPC0gQlssIDE6MjFdDQoNCmthYmxlKA0KICBCXzFfMjEsDQogIGFsaWduID0gImMiLA0KICBjYXB0aW9uID0gIihhKSBCYWctb2YtV29yZHMgbWF0cml4ICh0b2tlbnMgVDEgLSBUMjEpIiwNCiAgZm9ybWF0ID0gImh0bWwiLA0KICBib29rdGFicyA9IFRSVUUNCikgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKSAlPiUNCiAga2FibGVfY2xhc3NpY18yKCkNCg0KIyAtLS0gU3VidGFibGEgMjogVG9rZW5zIDIy4oCTNDEgLS0tDQpCXzIyXzQxIDwtIEJbLCAyMjo0MV0NCg0Ka2FibGUoDQogIEJfMjJfNDEsDQogIGFsaWduID0gImMiLA0KICBjYXB0aW9uID0gIihiKSBCYWctb2YtV29yZHMgbWF0cml4ICh0b2tlbnMgVDIyIC0gVDQxKSIsDQogIGZvcm1hdCA9ICJodG1sIiwNCiAgYm9va3RhYnMgPSBUUlVFDQopICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkgJT4lDQogIGthYmxlX2NsYXNzaWNfMigpDQoNCmBgYA0KDQoNCmBgYHtyLCBlY2hvPSBGQUxTRSwgcmVzdWx0cz0nYXNpcyd9DQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KDQojIC0tLSBNYXRyaXogb3JpZ2luYWwgLS0tDQpCIDwtIG1hdHJpeCgNCiAgYygNCiAgICAjIERvYyAxDQogICAgMCwwLDAsMCwwLDEsMCwxLDEsMCwgMCwwLDAsMSwwLDEsMSwxLDEsMSwNCiAgICAxLDEsMSwwLDAsMCwxLDEsMSwwLCAwLDAsMCwwLDAsMCwwLDAsMCwwLDAsDQogICAgIyBEb2MgMg0KICAgIDEsMSwxLDEsMSwwLDAsMCwwLDAsIDAsMCwxLDAsMCwwLDAsMCwwLDAsDQogICAgMCwwLDAsMCwwLDAsMCwwLDAsMCwgMCwwLDEsMSwxLDEsMSwxLDEsMSwxLA0KICAgICMgRG9jIDMNCiAgICAwLDAsMCwwLDAsMSwxLDAsMCwxLCAxLDEsMCwwLDEsMCwwLDAsMCwwLA0KICAgIDAsMCwwLDEsMSwxLDAsMCwwLDEsIDEsMSwwLDAsMCwwLDAsMCwwLDAsMA0KICApLA0KICBucm93ID0gMywNCiAgYnlyb3cgPSBUUlVFDQopDQoNCmNvbG5hbWVzKEIpIDwtIHBhc3RlMCgiVCIsIDE6NDEpICAgIyBvIHNvbG8gMTo0MSBzaSBwcmVmaWVyZXMNCnJvd25hbWVzKEIpIDwtIHBhc3RlMCgiRG9jLiIsIDE6MykNCg0KIyAtLS0gU3VidGFibGEgMTogVG9rZW5zIDHigJMyMSAtLS0NCkJfMV8yMSA8LSBCWywgMToyMV0NCg0Ka2FibGUoDQogIEJfMV8yMSwNCiAgYWxpZ24gPSAiYyIsDQogIGNhcHRpb24gPSAiKGEpIEJhZy1vZi1Xb3JkcyBtYXRyaXggKHRva2VucyBUMSAtIFQyMSkiLA0KICBmb3JtYXQgPSAiaHRtbCIsDQogIGJvb2t0YWJzID0gVFJVRQ0KKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpICU+JQ0KICBrYWJsZV9jbGFzc2ljXzIoKQ0KDQojIC0tLSBTdWJ0YWJsYSAyOiBUb2tlbnMgMjLigJM0MSAtLS0NCkJfMjJfNDEgPC0gQlssIDIyOjQxXQ0KDQprYWJsZSgNCiAgQl8yMl80MSwNCiAgYWxpZ24gPSAiYyIsDQogIGNhcHRpb24gPSAiKGIpIEJhZy1vZi1Xb3JkcyBtYXRyaXggKHRva2VucyBUMjIgLSBUNDEpIiwNCiAgZm9ybWF0ID0gImh0bWwiLA0KICBib29rdGFicyA9IFRSVUUNCikgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKSAlPiUNCiAga2FibGVfY2xhc3NpY18yKCkNCg0KYGBgDQoNCg0KQSB2YWx1ZSBvZiAxIGF0IHBvc2l0aW9uIChEb2MuIGBpYCwgVG9rZW4gYFRqYCkgaW5kaWNhdGVzIHRoYXQgdG9rZW4gYGpgIGFwcGVhcnMgb25jZSBpbiBkb2N1bWVudCBgaWA7IGEgdmFsdWUgb2YgMCBpbmRpY2F0ZXMgaXQgZG9lcyBub3QgYXBwZWFyLiBGb3IgZXhhbXBsZSwgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KVG9rZW4gOCDihpIgImRhdGEgc2NpZW5jZSIg4oaSIHZhbHVlIDEgaW4gZG9jdW1lbnQgMQ0KVG9rZW4gOCDihpIgImRhdGEgc2NpZW5jZSIg4oaSIHZhbHVlIDAgaW4gZG9jdW1lbnQgMg0KVG9rZW4gOCDihpIgImRhdGEgc2NpZW5jZSIg4oaSIHZhbHVlIDAgaW4gZG9jdW1lbnQgMw0KYGBgDQoNCg0KSW4gdGhpcyBjYXNlLCBhIHZhbHVlIG9mIDEgaW4gdGhlIGNvbHVtbiBhc3NvY2lhdGVkIHdpdGggYGRhdGEgc2NpZW5jZWAgbWVhbnMgdGhhdCB0aGlzIGJpZ3JhbSBhcHBlYXJzIG9uY2UgaW4gdGhlIGNvcnJlc3BvbmRpbmcgZG9jdW1lbnQsIHdoZXJlYXMgYSB2YWx1ZSBvZiAwIGluZGljYXRlcyB0aGF0IGl0IGRvZXMgbm90IGFwcGVhci4NCg0KV2hpbGUgdGhlc2UgYnVpbHQtaW4gZmVhdHVyZXMgZGVmaW5lIHRoZSBkZWZhdWx0IGJlaGF2aW9yIG9mIHRoZSB2ZWN0b3JpemVyLCBhZGRpdGlvbmFsIGFyZ3VtZW50cyBhbGxvdyBmdXJ0aGVyIGNvbnRyb2wgb3ZlciB0aGUgdm9jYWJ1bGFyeSBhbmQgZG9jdW1lbnQtdGVybSByZXByZXNlbnRhdGlvbi4NCjo6Og0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIGBDb3VudFZlY3Rvcml6ZXJgOiBDb250cm9sbGluZyB0aGUgdm9jYWJ1bGFyeQ0KDQpTZXZlcmFsIGFyZ3VtZW50cyBhbGxvdyB0aGUgdm9jYWJ1bGFyeSB0byBiZSByZXN0cmljdGVkIG9yIGZpbHRlcmVkOg0KDQotIGBtYXhfZmVhdHVyZXNgOiBsaW1pdHMgdm9jYWJ1bGFyeSBzaXplLg0KDQotIGBtaW5fZGZgOiByZW1vdmVzIHJhcmUgdGVybXMuDQoNCi0gYG1heF9kZmA6IHJlbW92ZXMgb3Zlcmx5IGZyZXF1ZW50IHRlcm1zLiANCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBgQ291bnRWZWN0b3JpemVyYDogbGltaXRpbmcgdm9jYWJ1bGFyeSBzaXplIHdpdGggYG1heF9mZWF0dXJlc2AgIA0KDQpBcyB0aGUgdm9jYWJ1bGFyeSBncm93cywgdGhlIGRpbWVuc2lvbmFsaXR5IG9mIGRvY3VtZW50IHZlY3RvcnMgaW5jcmVhc2VzIGFjY29yZGluZ2x5LiBWZXJ5IGhpZ2gtZGltZW5zaW9uYWwgcmVwcmVzZW50YXRpb25zIG1heSByZWR1Y2UgY29tcHV0YXRpb25hbCBlZmZpY2llbmN5IGFuZCBoYXJtIGdlbmVyYWxpemF0aW9uLCBhIHBoZW5vbWVub24gY29tbW9ubHkgcmVmZXJyZWQgdG8gYXMgdGhlICpjdXJzZSBvZiBkaW1lbnNpb25hbGl0eSouDQoNClRvIGFkZHJlc3MgdGhpcyBpc3N1ZSwgYENvdW50VmVjdG9yaXplcmAgcHJvdmlkZXMgdGhlIGBtYXhfZmVhdHVyZXNgIGFyZ3VtZW50LCB3aGljaCByZXN0cmljdHMgdGhlIHZvY2FidWxhcnkgdG8gdGhlIG1vc3QgZnJlcXVlbnQgdG9rZW5zIG9ic2VydmVkIGluIHRoZSBjb3JwdXMuIA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBFeGFtcGxlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUsIHRoZSB2b2NhYnVsYXJ5IGlzIGxpbWl0ZWQgdG8gdGhlIGZpdmUgbW9zdCBmcmVxdWVudCB1bmlncmFtcyBvciBiaWdyYW1zIGluIHRoZSBjb3JwdXMuDQoNCkZpcnN0LCBhIGBDb3VudFZlY3Rvcml6ZXJgIG9iamVjdCBpcyBjcmVhdGVkIHdpdGggYG5ncmFtX3JhbmdlID0gKDEsIDIpYCB0byBleHRyYWN0IGJvdGggdW5pZ3JhbXMgYW5kIGJpZ3JhbXMuIFRoZSBhcmd1bWVudCBgbWF4X2ZlYXR1cmVzID0gNWAgcmVzdHJpY3RzIHRoZSB2b2NhYnVsYXJ5IHRvIHRoZSBmaXZlIG1vc3QgZnJlcXVlbnQgdG9rZW5zIChhY2NvcmRpbmcgdG8gZG9jdW1lbnQgZnJlcXVlbmN5KS4gVGhlIG1ldGhvZCBgZml0X3RyYW5zZm9ybSgpYCB0aGVuIGxlYXJucyB0aGlzIHJlZHVjZWQgdm9jYWJ1bGFyeSBhbmQgY29uc3RydWN0cyB0aGUgY29ycmVzcG9uZGluZyBCYWctb2YtV29yZHMgbWF0cml4Lg0KDQoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCnZlY3Rvcml6ZXJfbGltaXRlZCA9IENvdW50VmVjdG9yaXplcigNCiAgICBuZ3JhbV9yYW5nZT0oMSwgMiksDQogICAgbWF4X2ZlYXR1cmVzPTUNCikNCg0KYm93X2xpbWl0ZWQgPSB2ZWN0b3JpemVyX2xpbWl0ZWQuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXJfbGltaXRlZC5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkgIyBPdXRwdXQgMQ0KcHJpbnQoYm93X2xpbWl0ZWQudG9hcnJheSgpKSAgICAgICAgICAgICAgICAgICAgICAjIE91dHB1dCAyDQpgYGANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIEZpcnN0IG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIGZpcnN0IG91dHB1dCBkaXNwbGF5cyB0aGUgcmVkdWNlZCB2b2NhYnVsYXJ5LCBjb25zaXN0aW5nIG9mIHRoZSBmaXZlIG1vc3QgZnJlcXVlbnQgdW5pZ3JhbXMgb3IgYmlncmFtcyByZXRhaW5lZCBhZnRlciBhcHBseWluZyB0aGUgbWF4X2ZlYXR1cmVzIGNvbnN0cmFpbnQuIFRoZSBvcmRlciBzaG93biBoZXJlIGRlZmluZXMgdGhlIGNvbHVtbiBvcmRlcmluZyBvZiB0aGUgQmFnLW9mLVdvcmRzIG1hdHJpeC4NCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnZlY3Rvcml6ZXJfbGltaXRlZCA9IENvdW50VmVjdG9yaXplcigNCiAgICBuZ3JhbV9yYW5nZT0oMSwgMiksDQogICAgbWF4X2ZlYXR1cmVzPTUNCikNCg0KYm93X2xpbWl0ZWQgPSB2ZWN0b3JpemVyX2xpbWl0ZWQuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXJfbGltaXRlZC5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkNCmBgYA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU2Vjb25kIG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIHJlc3VsdGluZyB2b2NhYnVsYXJ5IChPdXRwdXQgMSkgZGVmaW5lcyB0aGUgY29sdW1ucyBvZiB0aGUgQmFnLW9mLVdvcmRzIG1hdHJpeCAoT3V0cHV0IDIpLCBpbiB0aGUgZXhhY3Qgb3JkZXIgc2hvd24gYWJvdmUuDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpwcmludChib3dfbGltaXRlZC50b2FycmF5KCkpDQpgYGANCg0KRWFjaCByb3cgY29ycmVzcG9uZHMgdG8gYSBkb2N1bWVudCBhbmQgZWFjaCBjb2x1bW4gY29ycmVzcG9uZHMgdG8gb25lIG9mIHRoZSBzZWxlY3RlZCBuLWdyYW1zLiBUaGUgZW50cmllcyByZXByZXNlbnQgdGVybSBmcmVxdWVuY2llcy4gRm9ybWFsbHksIHRoZSBtYXRyaXggY2FuIGJlIHdyaXR0ZW4gYXMNClxbDQpcbWF0aGJme0J9ID0gKGJfe2lqfSksIFxxcXVhZCANCmJfe2lqfSA9IFx0ZXh0e2ZyZXF1ZW5jeSBvZiBuLWdyYW0gfSBqIFx0ZXh0eyBpbiBkb2N1bWVudCB9IGksDQpcXQ0Kd2hlcmUgdGhlIGNvbHVtbnMgY29ycmVzcG9uZCB0bw0KXFsNCihcdGV4dHR0e2FuYWx5c2lzfSxcIFx0ZXh0dHR7YW5hbHlzaXMgdXNlc30sXCBcdGV4dHR0e2FuZH0sXCBcdGV4dHR0e2FuZCBtYXRyaWNlc30sXCBcdGV4dHR0e2RhdGF9KS4NClxdDQoNClRoYXQgaXMsIHRoZSBCYWctb2YtV29yZHMgbWF0cml4IGNhbiBiZSB3cml0dGVuIGV4cGxpY2l0bHkgYXMNClxbDQpcbWF0aGJme0J9ID0NClxiZWdpbnthcnJheX17Y3xjY2NjY30NCiAgICAgICAgJiBcdGV4dHR0e2FuYWx5c2lzfSAmIFx0ZXh0dHR7YW5hbHlzaXMgdXNlc30gJiBcdGV4dHR0e2FuZH0gJiBcdGV4dHR0e2FuZCBtYXRyaWNlc30gJiBcdGV4dHR0e2RhdGF9IFxcIFxobGluZQ0KXHRleHR7RG9jdW1lbnQgMX0gJiAwICYgMCAmIDAgJiAwICYgMSBcXA0KXHRleHR7RG9jdW1lbnQgMn0gJiAxICYgMSAmIDEgJiAxICYgMCBcXA0KXHRleHR7RG9jdW1lbnQgM30gJiAwICYgMCAmIDAgJiAwICYgMQ0KXGVuZHthcnJheX0NClxdDQoNClRoZSB2YWx1ZSBcKCBiX3syNX0gPSAwIFwpIGluZGljYXRlcyB0aGF0IHRoZSB0b2tlbiBcdGV4dHR0e2RhdGF9IGRvZXMgbm90IGFwcGVhciBpbiB0aGUgc2Vjb25kIGRvY3VtZW50LCB3aGlsZSBcKCBiX3sxNX0gPSAxIFwpIGluZGljYXRlcyB0aGF0IGl0IGFwcGVhcnMgb25jZSBpbiB0aGUgZmlyc3QgZG9jdW1lbnQuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgYENvdW50VmVjdG9yaXplcmA6IGZpbHRlcmluZyB0b2tlbnMgd2l0aCBgbWluX2RmYCBhbmQgYG1heF9kZmAgdGhyZXNob2xkcw0KDQpOb3QgYWxsIHRva2VucyBjb250cmlidXRlIGVxdWFsbHkgdG8gdGhlIHJlcHJlc2VudGF0aW9uLiBTb21lIGFwcGVhciBpbiBhbG1vc3QgZXZlcnkgZG9jdW1lbnQgKGxvdyBkaXNjcmltaW5hdGlvbiksIHdoaWxlIG90aGVycyBhcHBlYXIgb25seSBvbmNlIChvZnRlbiB0b28gc3BlY2lmaWMgb3Igbm9pc3kpLg0KDQpgQ291bnRWZWN0b3JpemVyYCBzdXBwb3J0cyBmaWx0ZXJpbmcgdG9rZW5zIHVzaW5nICpkb2N1bWVudCBmcmVxdWVuY3kqOg0KDQotIGBtaW5fZGZgIGtlZXBzIG9ubHkgdGVybXMgdGhhdCBhcHBlYXIgaW4gYXQgbGVhc3QgYG1pbl9kZmAgZG9jdW1lbnRzIChhcyBhIGNvdW50IG9yIHByb3BvcnRpb24pLg0KDQotIGBtYXhfZGZgIGtlZXBzIG9ubHkgdGVybXMgdGhhdCBhcHBlYXIgaW4gYXQgbW9zdCBgbWF4X2RmYCBkb2N1bWVudHMgKGFzIGEgY291bnQgb3IgcHJvcG9ydGlvbikuDQoNCkEgdXNlZnVsIHdvcmtmbG93IGlzOg0KDQoxLiBmaXQgYSB2ZWN0b3JpemVyLA0KDQoyLiBjb21wdXRlIGRmIGZvciBlYWNoIHRva2VuLA0KDQozLiBpbnNwZWN0IHdoaWNoIHRva2VucyB3b3VsZCBzdXJ2aXZlIGEgY2hvc2VuIGBtaW5fZGZgL2BtYXhfZGZgIHJ1bGUuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIGBDb3VudFZlY3Rvcml6ZXJgOiAgZXhhbXBsZSAxIChpbnNwZWN0aW5nIHdoaWNoIHRva2VucyBzdXJ2aXZlIGEgYG1pbl9kZmAgcnVsZSkNCg0KSW4gdGhpcyBleGFtcGxlLCB3ZSBsZXQgdGhlIHZlY3Rvcml6ZXIgbGVhcm4gdGhlICpmdWxsIHZvY2FidWxhcnkqIHdpdGggYG1pbl9kZiA9IDFgLCBhbmQgdGhlbiBjb21wdXRlIGVhY2ggdG9rZW7igJlzIGRvY3VtZW50IGZyZXF1ZW5jeSAoYGRmYCkgbWFudWFsbHkuICBCYXNlZCBvbiB0aGlzIGluZm9ybWF0aW9uLCB3ZSBtYXJrIHdoaWNoIHRva2VucyB3b3VsZCBiZSByZXRhaW5lZCBpZiBhIHN0cmljdGVyIHJ1bGUgc3VjaCBhcyBgbWluX2RmID0gMmAgd2VyZSBhcHBsaWVkLg0KDQpUaGlzIGFwcHJvYWNoIG1ha2VzIHRoZSBlZmZlY3Qgb2YgYG1pbl9kZmAgZXhwbGljaXQgYW5kIGVhc3kgdG8gaW50ZXJwcmV0LiANCg0KRmlyc3QsIGEgYENvdW50VmVjdG9yaXplcmAgb2JqZWN0IGlzIGNyZWF0ZWQgd2l0aCBgbmdyYW1fcmFuZ2UgPSAoMSwgMylgIHRvIGV4dHJhY3QgKnVuaWdyYW1zLCBiaWdyYW1zLCBhbmQgdHJpZ3JhbXMqLiAgVGhlIGFyZ3VtZW50IGBtaW5fZGYgPSAxYCBlbnN1cmVzIHRoYXQgKm5vIHRva2VucyBhcmUgZmlsdGVyZWQgYXQgdGhpcyBzdGFnZSosIGFsbG93aW5nIHRoZSBmdWxsIHZvY2FidWxhcnkgdG8gYmUgaW5zcGVjdGVkLiBUaGUgbWV0aG9kIGBmaXRfdHJhbnNmb3JtKClgIHRoZW4gbGVhcm5zIHRoZSB2b2NhYnVsYXJ5IGFuZCBjb25zdHJ1Y3RzIHRoZSBjb3JyZXNwb25kaW5nIEJhZy1vZi1Xb3JkcyBtYXRyaXguDQoNCg0KU3BlY2lmaWNhbGx5Og0KDQotIGBmaXRfdHJhbnNmb3JtKGRvY3VtZW50cylgIHNjYW5zIHRoZSBjb3JwdXMsIGJ1aWxkcyB0aGUgdm9jYWJ1bGFyeSBvZiBvYnNlcnZlZCB0b2tlbnMsIGFuZCByZXR1cm5zIHRoZSAqZG9jdW1lbnTigJN0ZXJtIG1hdHJpeCogaW4gc3BhcnNlIGZvcm1hdC4NCg0KLSBgZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KClgIGV4dHJhY3RzIHRoZSBsaXN0IG9mIHRva2VucyBsZWFybmVkIGJ5IHRoZSB2ZWN0b3JpemVyLCBpbiB0aGUgb3JkZXIgaW4gd2hpY2ggdGhleSBhcHBlYXIgYXMgY29sdW1ucyBpbiB0aGUgbWF0cml4Lg0KDQotIGB0b2FycmF5KClgIGNvbnZlcnRzIHRoZSBzcGFyc2UgQmFnLW9mLVdvcmRzIG1hdHJpeCBpbnRvIGEgZGVuc2UgbnVtZXJpY2FsIGFycmF5LCB3aGljaCBmYWNpbGl0YXRlcyBpbnNwZWN0aW9uIGFuZCBtYW51YWwgY29tcHV0YXRpb24gb2YgZG9jdW1lbnQgZnJlcXVlbmNpZXMuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIEludGVycHJldGF0aW9uIG9mIHRoZSBtYXRyaXggc3RydWN0dXJlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoNClRoZSByZXN1bHRpbmcgQmFnLW9mLVdvcmRzIG1hdHJpeCBoYXM6DQoNCi0gKnJvd3MqIGNvcnJlc3BvbmRpbmcgdG8gZG9jdW1lbnRzLCBhbmQgIA0KDQotICpjb2x1bW5zKiBjb3JyZXNwb25kaW5nIHRvIHZvY2FidWxhcnkgdG9rZW5zICh1bmlncmFtcywgYmlncmFtcywgb3IgdHJpZ3JhbXMpLg0KDQpFYWNoIGVudHJ5IHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiB0aW1lcyBhIGdpdmVuIHRva2VuIGFwcGVhcnMgaW4gYSBnaXZlbiBkb2N1bWVudC4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIE1hdGhlbWF0aWNhbCBub3RhdGlvbi4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQpMZXQgXCggRCBcKSBkZW5vdGUgdGhlIG51bWJlciBvZiBkb2N1bWVudHMgYW5kIFwoIFYgXCkgdGhlIHNpemUgb2YgdGhlIHZvY2FidWxhcnkuICBUaGUgQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9uIGNhbiBiZSB3cml0dGVuIGFzIGEgbWF0cml4DQoNCiQkXG1hdGhiZntCfSA9IChiX3tpan0pIFxpbiBcbWF0aGJie059XntEIFx0aW1lcyBWfSwkJA0KDQp3aGVyZQ0KDQokJGJfe2lqfSA9IFx0ZXh0e251bWJlciBvZiBvY2N1cnJlbmNlcyBvZiB0b2tlbiB9IGogXHRleHR7IGluIGRvY3VtZW50IH0gaS4kJA0KDQpUaGlzIHJlcHJlc2VudGF0aW9uIHByb3ZpZGVzIHRoZSBiYXNpcyBmb3IgY29tcHV0aW5nIGRvY3VtZW50IGZyZXF1ZW5jaWVzIGFuZCBmb3IgYXBwbHlpbmcgZnJlcXVlbmN5LWJhc2VkIGZpbHRlcmluZyBydWxlcyBzdWNoIGFzIGBtaW5fZGZgIGFuZCBgbWF4X2RmYC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgQXBwbGljYXRpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCmBgYHtweXRob259DQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyDQoNCiMgVmVjdG9yaXplciAoeWEgc2UgdGllbmUpDQp2ZWN0b3JpemVyX2xpbWl0ZWQgPSBDb3VudFZlY3Rvcml6ZXIoDQogICAgbmdyYW1fcmFuZ2U9KDEsIDMpLA0KICAgIG1pbl9kZj0xICAjIGltcG9ydGFudGU6IGRmIGxvIGNhbGN1bGFtb3MgbWFudWFsbWVudGUNCikNCg0KYm93ID0gdmVjdG9yaXplcl9saW1pdGVkLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKQ0KDQojIDEpIFRva2VucyBhcHJlbmRpZG9zDQp0b2tlbnMgPSB2ZWN0b3JpemVyX2xpbWl0ZWQuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkNCg0KIyAyKSBCYWctb2YtV29yZHMgbWF0cml4DQpCID0gYm93LnRvYXJyYXkoKQ0KDQojIDMpIERvY3VtZW50IGZyZXF1ZW5jeSAoZGYpDQpkZiA9IChCID4gMCkuc3VtKGF4aXM9MCkNCg0KIyA0KSBOw7ptZXJvIGRlIHBhbGFicmFzIHBvciB0b2tlbg0Kbl93b3JkcyA9IG5wLmFycmF5KFtsZW4odC5zcGxpdCgpKSBmb3IgdCBpbiB0b2tlbnNdKQ0KDQojIDUpIENvbnN0cnVpciB0YWJsYSBmaW5hbA0KdGFibGVfZGYgPSBwZC5EYXRhRnJhbWUoew0KICAgICJUb2tlbiBJRCI6IG5wLmFyYW5nZSgxLCBsZW4odG9rZW5zKSArIDEpLA0KICAgICJUb2tlbiAoYXMgbGVhcm5lZCkiOiB0b2tlbnMsDQogICAgIlVuaWdyYW0iOiAobl93b3JkcyA9PSAxKS5hc3R5cGUoaW50KSwNCiAgICAiQmlncmFtIjogIChuX3dvcmRzID09IDIpLmFzdHlwZShpbnQpLA0KICAgICJUcmlncmFtIjogKG5fd29yZHMgPT0gMykuYXN0eXBlKGludCksDQogICAgImRmIjogZGYsDQogICAgIktlcHQgKG1pbl9kZiA9IDIpIjogbnAud2hlcmUoZGYgPj0gMiwgIuKckyIsICJYIikNCn0pDQoNCiMgNikgUmVlbXBsYXphciAxLzAgcG9yIOKckyAvIHZhY8OtbyAobcOhcyBsZWdpYmxlKQ0KZm9yIGNvbCBpbiBbIlVuaWdyYW0iLCAiQmlncmFtIiwgIlRyaWdyYW0iXToNCiAgICB0YWJsZV9kZltjb2xdID0gdGFibGVfZGZbY29sXS5yZXBsYWNlKHsxOiAi4pyTIiwgMDogIiJ9KQ0KDQp0YWJsZV9kZg0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBJbnRlcnByZXRpbmcgdGhlIG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQoNClRoZSByZXN1bHRpbmcgdGFibGUgY29udGFpbnMgKjQxIHRva2VucyosIGluY2x1ZGluZyAqdW5pZ3JhbXMsIGJpZ3JhbXMsIGFuZCB0cmlncmFtcyogKHVwIHRvIGxlbmd0aCAzKS4gIA0KVGhlIG1vc3QgcmVsZXZhbnQgY29sdW1ucyBhcmU6DQoNCi0gKmBkZmAqOiB0aGUgbnVtYmVyIG9mIGRvY3VtZW50cyBpbiB3aGljaCBhIHRva2VuIGFwcGVhcnMgYXQgbGVhc3Qgb25jZS4NCg0KLSAqYEtlcHQgKG1pbl9kZiA9IDIpYCo6IGluZGljYXRlcyB3aGV0aGVyIHRoZSB0b2tlbiB3b3VsZCBiZSByZXRhaW5lZCBpZiB3ZSByZXF1aXJlZCBpdCB0byBhcHBlYXIgaW4gKmF0IGxlYXN0IHR3byBkb2N1bWVudHMqLg0KDQpGcm9tIHRoZSBvdXRwdXQsIG9ubHkgb25lIHRva2VuIGlzIHJldGFpbmVkOg0KDQotICpgZGF0YWAqIGhhcyBgZGYgPSAyYCwgbWVhbmluZyBpdCBhcHBlYXJzIGluIHR3byBkb2N1bWVudHMgYW5kIHRoZXJlZm9yZSBzYXRpc2ZpZXMgdGhlIGNvbmRpdGlvbiBgbWluX2RmID0gMmAgKOKckykuDQoNCkFsbCByZW1haW5pbmcgdG9rZW5zIGhhdmU6DQoNCi0gYGRmID0gMWAsIGluZGljYXRpbmcgdGhhdCB0aGV5IGFwcGVhciBpbiBvbmx5IG9uZSBkb2N1bWVudC4gQXMgYSByZXN1bHQsIHRoZXkgd291bGQgYmUgcmVtb3ZlZCAoWCkgdW5kZXIgdGhlIGBtaW5fZGYgPSAyYCBydWxlLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgIFdoeSBkb2VzIHRoaXMgaGFwcGVuPyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGlzIGJlaGF2aW9yIGlzIGEgZGlyZWN0IGNvbnNlcXVlbmNlIG9mIHRoZSAqdmVyeSBzbWFsbCBjb3JwdXMgc2l6ZSogKGFwcHJveGltYXRlbHkgdHdvIGRvY3VtZW50cykuIE1vc3QgbXVsdGktd29yZCBleHByZXNzaW9ucyAoc3VjaCBhcyBgdGV4dCBhbmFseXNpcyB1c2VzYCBvciBgdmVjdG9ycyBhbmQgbWF0cmljZXNgKSBvY2N1ciBpbiBvbmx5IG9uZSBkb2N1bWVudC4NCg0KQnkgc2V0dGluZyBgbWluX2RmID0gMmAsIHdlIGFyZSBlZmZlY3RpdmVseSBlbmZvcmNpbmcgdGhlIHJ1bGU6DQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpLZWVwIG9ubHkgdGhlIHRlcm1zIHRoYXQgYXBwZWFyIGFjcm9zcyBtdWx0aXBsZSBkb2N1bWVudHMuDQpgYGANCg0KDQoNCldoZW4gdGhlIGNvcnB1cyBjb250YWlucyBvbmx5IHR3byBkb2N1bWVudHMsIHRoaXMgYmVjb21lcyBhICp2ZXJ5IHN0cmljdCBmaWx0ZXJpbmcgY3JpdGVyaW9uKiwgY2F1c2luZyBuZWFybHkgYWxsIHRva2VucyB0byBiZSBkaXNjYXJkZWQuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBBIG1vcmUgZGlyZWN0IGFwcHJvYWNoOiBhcHBseWluZyBgbWluX2RmYCBpbnNpZGUgdGhlIHZlY3Rvcml6ZXIgKG9wdGlvbmFsKS4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCiANCg0KSW4gdGhlIHByZXZpb3VzIGV4YW1wbGUsIHRva2VuIGZpbHRlcmluZyB3YXMgaWxsdXN0cmF0ZWQgYnkgbWFudWFsbHkgY29tcHV0aW5nIGRvY3VtZW50IGZyZXF1ZW5jaWVzIGFuZCBtYXJraW5nIHdoaWNoIHRva2VucyB3b3VsZCBiZSByZXRhaW5lZCB1bmRlciBhIGdpdmVuIGBtaW5fZGZgIHJ1bGUuICANCg0KQW4gYWx0ZXJuYXRpdmUgKGFuZCBtb3JlIHR5cGljYWwpIGFwcHJvYWNoIGlzIHRvIGFwcGx5IHRoZSBmcmVxdWVuY3kgY29uc3RyYWludCAqZGlyZWN0bHkgaW5zaWRlIHRoZSB2ZWN0b3JpemVyKi4gSW4gdGhpcyBjYXNlLCB0b2tlbnMgdGhhdCBkbyBub3Qgc2F0aXNmeSB0aGUgY29uZGl0aW9uIGFyZSBuZXZlciBpbmNsdWRlZCBpbiB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5Lg0KDQoNCmBgYHtweXRob259DQp2ZWN0b3JpemVyX2RmID0gQ291bnRWZWN0b3JpemVyKA0KICAgIG5ncmFtX3JhbmdlPSgxLCAzKSwNCiAgICBtaW5fZGY9Mg0KKQ0KDQpib3dfZGYgPSB2ZWN0b3JpemVyX2RmLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKQ0KcHJpbnQodmVjdG9yaXplcl9kZi5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkNCmBgYA0KDQpCZWNhdXNlIHRoZSBjb3JwdXMgaXMgdmVyeSBzbWFsbCwgb25seSB0aGUgdG9rZW4gZGF0YSBhcHBlYXJzIGluIGF0IGxlYXN0IHR3byBkb2N1bWVudHMgYW5kIGlzIHRoZXJlZm9yZSByZXRhaW5lZC4gQWxsIG90aGVyIHVuaWdyYW1zLCBiaWdyYW1zLCBhbmQgdHJpZ3JhbXMgYXJlIGRpc2NhcmRlZCBhdXRvbWF0aWNhbGx5IGR1cmluZyB2b2NhYnVsYXJ5IGNvbnN0cnVjdGlvbi4NCg0KVGhpcyBjb25maXJtcyB0aGUgYmVoYXZpb3Igb2JzZXJ2ZWQgZWFybGllciB1c2luZyB0aGUgbWFudWFsIGluc3BlY3Rpb24gdGFibGUuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgYENvdW50VmVjdG9yaXplcmA6IGV4YW1wbGUgMiAoam9pbnQgZmlsdGVyaW5nIHdpdGggYG1pbl9kZmAgYW5kIGBtYXhfZGZgKQ0KDQpUaGUgcHJldmlvdXMgZXhhbXBsZSBhcHBsaWVkIGEgbG93ZXIgYm91bmQgb24gZG9jdW1lbnQgZnJlcXVlbmN5IHVzaW5nIGBtaW5fZGZgLiAgV2Ugbm93IGV4dGVuZCB0aGlzIGlkZWEgYnkgaW50cm9kdWNpbmcgYW4gdXBwZXIgYm91bmQgdGhyb3VnaCB0aGUgcGFyYW1ldGVyIGBtYXhfZGZgLg0KDQpJbiB0aGlzIGV4YW1wbGUsIG9ubHkgKnVuaWdyYW1zIGFuZCBiaWdyYW1zKiB0aGF0Og0KDQotIGFwcGVhciBpbiAqYXQgbGVhc3QgdHdvIGRvY3VtZW50cyosIGFuZCAgDQoNCi0gYXBwZWFyIGluICpubyBtb3JlIHRoYW4gODAlIG9mIHRoZSBjb3JwdXMqDQoNCmFyZSByZXRhaW5lZC4NCg0KRmlyc3QsIGEgYENvdW50VmVjdG9yaXplcmAgb2JqZWN0IGlzIGNyZWF0ZWQgd2l0aCBgbmdyYW1fcmFuZ2UgPSAoMSwgMilgIHRvIGV4dHJhY3QgdW5pZ3JhbXMgYW5kIGJpZ3JhbXMgb25seS4gVGhlIGFyZ3VtZW50cyBgbWluX2RmID0gMmAgYW5kIGBtYXhfZGYgPSAwLjhgIGpvaW50bHkgZmlsdGVyIHRva2VucyBiYXNlZCBvbiBkb2N1bWVudCBmcmVxdWVuY3kuICBBcyBiZWZvcmUsIHRoZSBtZXRob2QgYGZpdF90cmFuc2Zvcm0oKWAgbGVhcm5zIHRoZSBmaWx0ZXJlZCB2b2NhYnVsYXJ5IGFuZCBjb25zdHJ1Y3RzIHRoZSBjb3JyZXNwb25kaW5nIEJhZy1vZi1Xb3JkcyBtYXRyaXguDQoNCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0KdmVjdG9yaXplcl9kZiA9IENvdW50VmVjdG9yaXplcigNCiAgICBuZ3JhbV9yYW5nZT0oMSwgMiksDQogICAgbWluX2RmPTIsDQogICAgbWF4X2RmPTAuOA0KKQ0KDQpib3dfZGYgPSB2ZWN0b3JpemVyX2RmLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKQ0KDQpwcmludCh2ZWN0b3JpemVyX2RmLmdldF9mZWF0dXJlX25hbWVzX291dCgpKSAjIE91dHB1dCAxDQpwcmludChib3dfZGYudG9hcnJheSgpKSAgICAgICAgICAgICAgICAgICAgICAjIE91dHB1dCAyDQpgYGANCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBGaXJzdCBvdXRwdXQuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmaXJzdCBvdXRwdXQgbGlzdHMgdGhlIHRva2VucyByZXRhaW5lZCBpbiB0aGUgZmlsdGVyZWQgdm9jYWJ1bGFyeSwgdGhhdCBpcywgdGhvc2Ugc2F0aXNmeWluZyBib3RoIHRoZSBgbWluX2RmYCBhbmQgYG1heF9kZmAgY29uc3RyYWludHMuLiBJbiB0aGlzIGNhc2UsIG9ubHkgdGhlIHRva2VuIGBkYXRhYCBzYXRpc2ZpZXMgYm90aCBmcmVxdWVuY3kgY29uc3RyYWludHMuDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQp2ZWN0b3JpemVyX2RmID0gQ291bnRWZWN0b3JpemVyKA0KICAgIG5ncmFtX3JhbmdlPSgxLCAyKSwNCiAgICBtaW5fZGY9MiwNCiAgICBtYXhfZGY9MC44DQopDQoNCmJvd19kZiA9IHZlY3Rvcml6ZXJfZGYuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXJfZGYuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpDQpgYGANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFNlY29uZCBvdXRwdXQuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBzZWNvbmQgb3V0cHV0IGRpc3BsYXlzIHRoZSByZXN1bHRpbmcgQmFnLW9mLVdvcmRzIG1hdHJpeCwgd2hvc2UgY29sdW1ucyBjb3JyZXNwb25kIHRvIHRoZSByZXRhaW5lZCB0b2tlbnMgYW5kIHdob3NlIHJvd3MgcmVwcmVzZW50IGRvY3VtZW50cywgd2l0aCBlbnRyaWVzIGluZGljYXRpbmcgdG9rZW4gY291bnRzLiBTaW5jZSBvbmx5IG9uZSB0b2tlbiBpcyByZXRhaW5lZCwgdGhlIG1hdHJpeCBoYXMgYSBzaW5nbGUgY29sdW1uLiBFYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIGRvY3VtZW50IGFuZCBpbmRpY2F0ZXMgd2hldGhlciB0aGUgdG9rZW4gYGRhdGFgIGFwcGVhcnMgaW4gdGhlIGNvcnJlc3BvbmRpbmcgZG9jdW1lbnQuLg0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KcHJpbnQoYm93X2RmLnRvYXJyYXkoKSkNCmBgYA0KDQpUb2dldGhlciwgYG1pbl9kZmAgYW5kIGBtYXhfZGZgIHByb3ZpZGUgYSBzaW1wbGUgeWV0IHBvd2VyZnVsIG1lY2hhbmlzbSB0byBjb250cm9sIHdoaWNoIHRva2VucyBlbnRlciB0aGUgcmVwcmVzZW50YXRpb24uIFRoZXkgYXJlIGVzcGVjaWFsbHkgdXNlZnVsIGZvciByZWR1Y2luZyBub2lzZSBhbmQgZGltZW5zaW9uYWxpdHkgaW4gaGlnaC1kaW1lbnNpb25hbCB0ZXh0IGRhdGEsIHdoaWxlIHByZXNlcnZpbmcgdGVybXMgdGhhdCBjYXJyeSBjcm9zcy1kb2N1bWVudCByZWxldmFuY2UuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIExpbWl0YXRpb25zIG9mIHRoZSBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24NCg0KRGVzcGl0ZSBpdHMgc2ltcGxpY2l0eSBhbmQgaW50ZXJwcmV0YWJpbGl0eSwgdGhlIEJhZy1vZi1Xb3JkcyBtb2RlbCBoYXMgaW1wb3J0YW50IGxpbWl0YXRpb25zLiBTZWUgRmlndXJlIFxAcmVmKGZpZzpGaWctQm9XLXN0ZXBzLWxpbWl0YSkuDQoNCg0KPGNlbnRlcj4NCmBgYHtyIEZpZy1Cb1ctc3RlcHMtbGltaXRhLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIkxpbWl0YXRpb25zIG9mIHRoZSBCYWctb2YtV29yZHMgcmVwcmVzZW50YXRpb24uIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI4MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkJvV19zdGVwc19saW1pdGEucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgTGltaXRhdGlvbnMuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KLSBGaXJzdCwgaXQgcmVsaWVzIGV4Y2x1c2l2ZWx5IG9uIHRva2VuIGNvdW50cywgaWdub3Jpbmcgd29yZCBvcmRlciBhbmQgc3ludGFjdGljIHN0cnVjdHVyZS4gQXMgYSByZXN1bHQsIHNlbnRlbmNlcyB3aXRoIHZlcnkgZGlmZmVyZW50IG1lYW5pbmdzIG1heSByZWNlaXZlIHNpbWlsYXIgcmVwcmVzZW50YXRpb25zLg0KDQotIFNlY29uZCwgQm9XIGRvZXMgbm90IGNhcHR1cmUgc2VtYW50aWMgcmVsYXRpb25zaGlwcy4gV29yZHMgd2l0aCByZWxhdGVkIG1lYW5pbmdzIGFyZSB0cmVhdGVkIGFzIGVudGlyZWx5IGluZGVwZW5kZW50IGRpbWVuc2lvbnMuDQoNCi0gVGhpcmQsIGxhcmdlIHZvY2FidWxhcmllcyBjYW4gbGVhZCB0byBleHRyZW1lbHkgaGlnaC1kaW1lbnNpb25hbCB2ZWN0b3JzLCB3aGljaCBtYXkgZGVncmFkZSBwZXJmb3JtYW5jZSBhbmQgaW5jcmVhc2UgY29tcHV0YXRpb25hbCBjb3N0Lg0KDQpUaGVzZSBsaW1pdGF0aW9ucyBtb3RpdmF0ZSBtb3JlIHJlZmluZWQgcmVwcmVzZW50YXRpb25zIHRoYXQgYWRqdXN0IHRva2VuIGltcG9ydGFuY2UgYW5kIGluY29ycG9yYXRlIGNvbnRleHR1YWwgaW5mb3JtYXRpb24uIE9uZSBzdWNoIGFwcHJvYWNoIChURi1JREYgd2VpZ2h0aW5nKSBpcyBpbnRyb2R1Y2VkIGluIHRoZSBuZXh0IHNlY3Rpb24uDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgVEYtSURGIHZlY3RvcnMNCg0KSW4gdGhlIHByZXZpb3VzIHNlY3Rpb24sIGRvY3VtZW50cyB3ZXJlIHJlcHJlc2VudGVkIHVzaW5nIHJhdyB3b3JkIGNvdW50cyB0aHJvdWdoIHRoZSBCYWctb2YtV29yZHMgbW9kZWwuIFdoaWxlIHRoaXMgYXBwcm9hY2ggaXMgaW50dWl0aXZlLCBpdCB0cmVhdHMgYWxsIHRva2VucyBlcXVhbGx5IGFuZCByZWxpZXMgc29sZWx5IG9uIHRoZWlyIGZyZXF1ZW5jeSB3aXRoaW4gZWFjaCBkb2N1bWVudC4NCg0KQXMgYSByZXN1bHQsIHRlcm1zIHRoYXQgYXBwZWFyIHZlcnkgb2Z0ZW4gYWNyb3NzIHRoZSBjb3JwdXMgbWF5IGRvbWluYXRlIHRoZSByZXByZXNlbnRhdGlvbiwgd2hpbGUgbGVzcyBmcmVxdWVudCBidXQgcG90ZW50aWFsbHkgaW5mb3JtYXRpdmUgdGVybXMgcmVjZWl2ZSBsaXR0bGUgd2VpZ2h0IG9yIGFyZSBkaXNjYXJkZWQgYWx0b2dldGhlci4gVGhpcyBjYW4gbGVhZCB0byBhIGxvc3Mgb2YgcmVsZXZhbnQgcGF0dGVybnMsIGVzcGVjaWFsbHkgd2hlbiByYXJlIHRlcm1zIGFyZSBjcnVjaWFsIGZvciBkaXN0aW5ndWlzaGluZyBkb2N1bWVudHMuDQoNClRoZSBUZXJtIEZyZXF1ZW5jeS1JbnZlcnNlIERvY3VtZW50IEZyZXF1ZW5jeSAoVEYtSURGKSBzY2hlbWUgYWRkcmVzc2VzIHRoaXMgbGltaXRhdGlvbiBieSByZS13ZWlnaHRpbmcgdG9rZW5zIGFjY29yZGluZyB0byBib3RoIHRoZWlyIGxvY2FsIGltcG9ydGFuY2Ugd2l0aGluIGEgZG9jdW1lbnQgYW5kIHRoZWlyIGdsb2JhbCBkaXN0cmlidXRpb24gYWNyb3NzIHRoZSBjb3JwdXMuDQoNClRGLUlERiBpcyB3aWRlbHkgdXNlZCBpbiBpbmZvcm1hdGlvbiByZXRyaWV2YWwsIHNlYXJjaCBlbmdpbmVzLCBhbmQgdGV4dCBtaW5pbmcgYXBwbGljYXRpb25zLiBMaWtlIEJvVywgaXQgaXMgYSBmcmVxdWVuY3ktYmFzZWQgcmVwcmVzZW50YXRpb24sIGJ1dCBpdCBpbmNvcnBvcmF0ZXMgYW4gYWRkaXRpb25hbCBub3JtYWxpemF0aW9uIG1lY2hhbmlzbSB0aGF0IGJhbGFuY2VzIGNvbW1vbiBhbmQgcmFyZSB0ZXJtcy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgVGVybSBGcmVxdWVuY3kgKFRGKQ0KDQpUaGUgdGVybSBmcmVxdWVuY3kgY29tcG9uZW50IG1lYXN1cmVzIGhvdyBvZnRlbiBhIHdvcmQgYXBwZWFycyBpbiBhIHNwZWNpZmljIGRvY3VtZW50LiBIb3dldmVyLCBzaW5jZSBkb2N1bWVudHMgbWF5IHZhcnkgaW4gbGVuZ3RoLCByYXcgY291bnRzIGFyZSB0eXBpY2FsbHkgbm9ybWFsaXplZC4gQSBjb21tb24gbm9ybWFsaXplZCBkZWZpbml0aW9uIG9mIHRlcm0gZnJlcXVlbmN5IGlzOg0KDQoNCiQkVEYodykgPSBcZnJhY3tcdGV4dHtOdW1iZXIgb2YgdGltZXMgdGhlIHdvcmQgdyBvY2N1cnMgaW4gYSBkb2N1bWVudH19e1x0ZXh0e1RvdGFsIG51bWJlciBvZiB3b3JkcyBpbiB0aGUgZG9jdW1lbnR9fSQkDQoNClRoaXMgbm9ybWFsaXphdGlvbiBwcmV2ZW50cyBsb25nZXIgZG9jdW1lbnRzIGZyb20gYXV0b21hdGljYWxseSBhc3NpZ25pbmcgaGlnaGVyIGltcG9ydGFuY2UgdG8gYWxsIHRoZWlyIHRlcm1zLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQoNClRvIGJ1aWxkIGludHVpdGlvbiwgdGhlIG5leHQgZmlndXJlIHNob3dzICoqbm9ybWFsaXplZCBURioqIGZvciBhIGZldyBleGFtcGxlIHRva2VucyBpbnNpZGUgYSBzaW5nbGUgZG9jdW1lbnQgKHNvIGxvbmdlciBkb2N1bWVudHMgZG8gbm90IGF1dG9tYXRpY2FsbHkgaW5mbGF0ZSBpbXBvcnRhbmNlKS4NCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIC0tLSBTaW11bGF0ZWQgZG9jdW1lbnQgdGVybSBjb3VudHMgKERvY3VtZW50IGQxKSAtLS0NCnRlcm1zID0gWyJkYXRhIiwgImFuYWx5c2lzIiwgIm1vZGVsIiwgInRoZSIsICJhbmQiLCAicHl0aG9uIl0NCmNvdW50c19kMSA9IG5wLmFycmF5KFs2LCAzLCAyLCAxMCwgOCwgMV0pICAjIHJhdyBjb3VudHMgaW4gZG9jdW1lbnQgZDENCnRmX2QxID0gY291bnRzX2QxIC8gY291bnRzX2QxLnN1bSgpICAgICAgICAjIG5vcm1hbGl6ZWQgVEYNCg0KcGx0LmZpZ3VyZSgpDQpwbHQuYmFyKHRlcm1zLCB0Zl9kMSkNCnBsdC50aXRsZSgiVGVybSBGcmVxdWVuY3kgKFRGKSBpbiBhIHNpbmdsZSBkb2N1bWVudCIpDQpwbHQueGxhYmVsKCJUb2tlbiIpDQpwbHQueWxhYmVsKCJURiAobm9ybWFsaXplZCBmcmVxdWVuY3kpIikNCnBsdC54dGlja3Mocm90YXRpb249MzAsIGhhPSJyaWdodCIpDQpwbHQudGlnaHRfbGF5b3V0KCkNCnBsdC5zaG93KCkNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCg0KdGZfZGYgPC0gZGF0YS5mcmFtZSgNCiAgdG9rZW4gPSBjKCJkYXRhIiwgImFuYWx5c2lzIiwgIm1vZGVsIiwgInRoZSIsICJhbmQiLCAicHl0aG9uIiksDQogIGNvdW50ID0gYyg2LCAzLCAyLCAxMCwgOCwgMSkNCikNCg0KdGZfZGYkVEYgPC0gdGZfZGYkY291bnQgLyBzdW0odGZfZGYkY291bnQpDQoNCmdncGxvdCh0Zl9kZiwgYWVzKHggPSB0b2tlbiwgeSA9IFRGKSkgKw0KICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJUZXJtIEZyZXF1ZW5jeSAoVEYpIGluIGEgc2luZ2xlIGRvY3VtZW50IiwNCiAgICB4ID0gIlRva2VuIiwNCiAgICB5ID0gIlRGIChub3JtYWxpemVkIGZyZXF1ZW5jeSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNClRGIG1lYXN1cmVzIGxvY2FsIGltcG9ydGFuY2U6IHRva2VucyB0aGF0IG9jY3VyIG1vcmUgb2Z0ZW4gd2l0aGluIHRoZSBkb2N1bWVudCByZWNlaXZlIGxhcmdlciBURiB2YWx1ZXMsIGJ1dCBub3JtYWxpemF0aW9uIGtlZXBzIFRGIGNvbXBhcmFibGUgYWNyb3NzIGRvY3VtZW50cyBvZiBkaWZmZXJlbnQgbGVuZ3Rocy4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIEludmVyc2UgRG9jdW1lbnQgRnJlcXVlbmN5IChJREYpDQoNCldoaWxlIFRGIGNhcHR1cmVzIGxvY2FsIHJlbGV2YW5jZSwgaXQgZG9lcyBub3QgYWNjb3VudCBmb3IgaG93IGluZm9ybWF0aXZlIGEgd29yZCBpcyBhY3Jvc3MgdGhlIGVudGlyZSBjb3JwdXMuIFdvcmRzIHRoYXQgYXBwZWFyIGluIGFsbW9zdCBldmVyeSBkb2N1bWVudCAoc3VjaCBhcyBnZW5lcmFsIG9yIGRvbWFpbi13aWRlIHRlcm1zKSBtYXkgbm90IGJlIHVzZWZ1bCBmb3IgZGlzY3JpbWluYXRpb24uDQoNClRoZSBpbnZlcnNlIGRvY3VtZW50IGZyZXF1ZW5jeSBjb21wb25lbnQgZG93bi13ZWlnaHRzIHN1Y2ggdWJpcXVpdG91cyB0ZXJtcyBhbmQgYW1wbGlmaWVzIHdvcmRzIHRoYXQgb2NjdXIgaW4gZmV3ZXIgZG9jdW1lbnRzOg0KDQokJCBJREYodykgPSBcbG9nXGxlZnQoXGZyYWN7XHRleHR7VG90YWwgbnVtYmVyIG9mIGRvY3VtZW50c319e1x0ZXh0e051bWJlciBvZiBkb2N1bWVudHMgY29udGFpbmluZyB3b3JkIHd9fVxyaWdodCkgJCQNCg0Kd2hlcmU6DQoNCi0gJE4kIGlzIHRoZSB0b3RhbCBudW1iZXIgb2YgZG9jdW1lbnRzLCBhbmQNCg0KLSAkZGYodykkIGlzIHRoZSBudW1iZXIgb2YgZG9jdW1lbnRzIGNvbnRhaW5pbmcgd29yZCAkdyQuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNClRGIGFsb25lIGRvZXMgbm90IGNhcHR1cmUgaG93IGluZm9ybWF0aXZlIGEgdG9rZW4gaXMgYWNyb3NzIHRoZSBjb3JwdXMuIFRoZSBuZXh0IGZpZ3VyZSBzaG93cyBob3cgSURGIGRlY3JlYXNlcyBhcyBhIHRva2VuIGFwcGVhcnMgaW4gbW9yZSBkb2N1bWVudHMuDQoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIC0tLSBTaW11bGF0ZWQgY29ycHVzIHNpemUgYW5kIGRvY3VtZW50IGZyZXF1ZW5jaWVzIC0tLQ0KTiA9IDEwICAjIHRvdGFsIGRvY3VtZW50cw0KZGYgPSBucC5hcmFuZ2UoMSwgTisxKSAgICAgICAgICAgICAgICAgIyBkZih3KSA9IDEuLk4NCmlkZiA9IG5wLmxvZyhOIC8gZGYpICAgICAgICAgICAgICAgICAgICMgY2xhc3NpYyBJREYgZGVmaW5pdGlvbiAoYXMgaW4geW91ciBub3RlcykNCg0KcGx0LmZpZ3VyZSgpDQpwbHQucGxvdChkZiwgaWRmLCBtYXJrZXI9Im8iKQ0KcGx0LnRpdGxlKCJJbnZlcnNlIERvY3VtZW50IEZyZXF1ZW5jeSAoSURGKSB2cy4gZG9jdW1lbnQgZnJlcXVlbmN5IikNCnBsdC54bGFiZWwoIkRvY3VtZW50IGZyZXF1ZW5jeSAgZGYodykiKQ0KcGx0LnlsYWJlbCgiSURGKHcpID0gbG9nKE4gLyBkZih3KSkiKQ0KcGx0Lnh0aWNrcyhkZikNCnBsdC50aWdodF9sYXlvdXQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCg0KYGBge3J9DQppZGZfZGYgPC0gZGF0YS5mcmFtZShkZiA9IDE6MTApDQoNCk4gPC0gMTANCmlkZl9kZiRJREYgPC0gbG9nKE4gLyBpZGZfZGYkZGYpDQoNCmdncGxvdChpZGZfZGYsIGFlcyh4ID0gZGYsIHkgPSBJREYpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJzdGVlbGJsdWUiLCBzaXplPTEpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJzdGVlbGJsdWUiLCBzaXplPTIuNSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMToxMCkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkludmVyc2UgRG9jdW1lbnQgRnJlcXVlbmN5IChJREYpIiwNCiAgICB4ID0gIkRvY3VtZW50IGZyZXF1ZW5jeSBkZih3KSIsDQogICAgeSA9ICJJREYodykgPSBsb2coTiAvIGRmKHcpKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KDQpUb2tlbnMgdGhhdCBvY2N1ciBpbiBtYW55IGRvY3VtZW50cyAoaGlnaCBkZikgaGF2ZSBsb3cgSURGLCBiZWNhdXNlIHRoZXkgaGVscCBsZXNzIHRvIGRpc3Rpbmd1aXNoIGRvY3VtZW50cy4gVG9rZW5zIHRoYXQgb2NjdXIgaW4gZmV3IGRvY3VtZW50cyBoYXZlIGhpZ2hlciBJREYuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgVEYtSURGIHdlaWdodGluZw0KDQpUaGUgZmluYWwgVEYtSURGIHdlaWdodCBvZiBhIHdvcmQgXCggdyBcKSBpbiBkb2N1bWVudCBcKCBkIFwpIGlzIG9idGFpbmVkIGJ5IGNvbWJpbmluZyB0d28gY29tcG9uZW50czoNCg0KXFsNClx0ZXh0e3dlaWdodH0odyxkKSA9IFRGKHcsZCkgXHRpbWVzIElERih3KQ0KXF0NCg0KVGhpcyBmb3JtdWxhdGlvbiBhc3NpZ25zIGhpZ2hlciB3ZWlnaHRzIHRvIHRlcm1zIHRoYXQgYXJlIGZyZXF1ZW50IHdpdGhpbiBhIGRvY3VtZW50IGJ1dCByZWxhdGl2ZWx5IHJhcmUgYWNyb3NzIHRoZSBjb3JwdXMuDQoNCkV2ZW4gd2hlbiBhIHRlcm0gYXBwZWFycyBleGFjdGx5IG9uY2UgaW4gZXZlcnkgZG9jdW1lbnQsIGl0cyBURi1JREYgd2VpZ2h0IGlzIG5vdCBuZWNlc3NhcmlseSBpZGVudGljYWwgYWNyb3NzIGRvY3VtZW50cy4gIA0KVGhpcyBvY2N1cnMgYmVjYXVzZSB0aGUgKnRlcm0gZnJlcXVlbmN5IChURikqIGNvbXBvbmVudCBpcyBub3JtYWxpemVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgdG9rZW5zIGluIGVhY2ggZG9jdW1lbnQuIENvbnNlcXVlbnRseSwgZG9jdW1lbnRzIG9mIGRpZmZlcmVudCBsZW5ndGhzIGFzc2lnbiBkaWZmZXJlbnQgcmVsYXRpdmUgaW1wb3J0YW5jZSB0byB0aGUgc2FtZSB0ZXJtLg0KDQpJbiBhZGRpdGlvbiwgVEYtSURGIHZlY3RvcnMgYXJlICpub3JtYWxpemVkIGJ5IGRlZmF1bHQqIHVzaW5nIHRoZSBcKCBMXzIgXCkgbm9ybS4gVGhpcyBtZWFucyB0aGF0IGVhY2ggZG9jdW1lbnQgdmVjdG9yIGlzIHJlc2NhbGVkIHRvIGhhdmUgdW5pdCBsZW5ndGgsIGZ1cnRoZXIgbW9kaWZ5aW5nIHRoZSBmaW5hbCB3ZWlnaHRzLiBBcyBhIHJlc3VsdCwgdHdvIGRvY3VtZW50cyBtYXkgc2hhcmUgdGhlIHNhbWUgdm9jYWJ1bGFyeSBhbmQgaWRlbnRpY2FsIHJhdyB0ZXJtIGNvdW50cywgeWV0IHN0aWxsIGRpZmZlciBpbiB0aGVpciBURi1JREYgcmVwcmVzZW50YXRpb25zLg0KDQpUaGUgbmV4dCBwbG90IGlsbHVzdHJhdGVzIHRoZSBjb21iaW5lZCBlZmZlY3Q6IFRG4oCTSURGIGJlY29tZXMgbGFyZ2Ugd2hlbiBhIHRva2VuIGlzIGZyZXF1ZW50IGluIGEgZG9jdW1lbnQgKGhpZ2ggVEYpIGFuZCByYXJlIGluIHRoZSBjb3JwdXMgKGhpZ2ggSURGKS4NCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIC0tLSBTaW11bGF0ZWQgVEYgKGZyb20gYSBkb2N1bWVudCkgYW5kIElERiAoZnJvbSB0aGUgY29ycHVzKSBmb3Igc2V2ZXJhbCB0b2tlbnMgLS0tDQp0b2tlbnMgPSBbImRhdGEiLCAiYW5hbHlzaXMiLCAibW9kZWwiLCAidGhlIiwgImFuZCJdDQp0ZiA9IG5wLmFycmF5KFswLjE4LCAwLjEyLCAwLjA4LCAwLjMwLCAwLjIwXSkgICMgbG9jYWwgZnJlcXVlbmNpZXMgKG5vcm1hbGl6ZWQpDQppZGYgPSBucC5hcnJheShbMS4wLCAxLjQsIDEuOCwgMC4xLCAwLjJdKSAgICAgICMgZ2xvYmFsIHJhcml0eSAoaGlnaGVyID0gcmFyZXIpDQp0ZmlkZiA9IHRmICogaWRmDQoNCnBsdC5maWd1cmUoKQ0KcGx0LmJhcih0b2tlbnMsIHRmaWRmKQ0KcGx0LnRpdGxlKCJURi1JREYgd2VpZ2h0cyBpbiBhIGRvY3VtZW50IChzaW11bGF0ZWQpIikNCnBsdC54bGFiZWwoIlRva2VuIikNCnBsdC55bGFiZWwoIlRGLUlERiA9IFRGIMOXIElERiIpDQpwbHQueHRpY2tzKHJvdGF0aW9uPTIwLCBoYT0icmlnaHQiKQ0KcGx0LnRpZ2h0X2xheW91dCgpDQpwbHQuc2hvdygpDQpgYGANCg0KYGBge3J9DQp0ZmlkZl9kZiA8LSBkYXRhLmZyYW1lKA0KICB0b2tlbiA9IGMoImRhdGEiLCAiYW5hbHlzaXMiLCAibW9kZWwiLCAidGhlIiwgImFuZCIpLA0KICBURiA9IGMoMC4xOCwgMC4xMiwgMC4wOCwgMC4zMCwgMC4yMCksDQogIElERiA9IGMoMS4wLCAxLjQsIDEuOCwgMC4xLCAwLjIpDQopDQoNCnRmaWRmX2RmJFRGSURGIDwtIHRmaWRmX2RmJFRGICogdGZpZGZfZGYkSURGDQoNCmdncGxvdCh0ZmlkZl9kZiwgYWVzKHggPSB0b2tlbiwgeSA9IFRGSURGKSkgKw0KICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJURi1JREYgd2VpZ2h0cyBpbiBhIGRvY3VtZW50IChzaW11bGF0ZWQpIiwNCiAgICB4ID0gIlRva2VuIiwNCiAgICB5ID0gIlRGLUlERiA9IFRGIMOXIElERiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KDQpBIHRva2VuIGNhbiBoYXZlIGEgaGlnaCBURiBidXQgc3RpbGwgcmVjZWl2ZSBhIHNtYWxsIFRG4oCTSURGIHdlaWdodCBpZiBpdHMgSURGIGlzIGxvdyAoZS5nLiwgdmVyeSBjb21tb24gd29yZHMpLiBURuKAk0lERiBlbXBoYXNpemVzIHRva2VucyB0aGF0IGFyZSBib3RoIGxvY2FsbHkgZnJlcXVlbnQgYW5kIGdsb2JhbGx5IGluZm9ybWF0aXZlLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBIb3cgVEYsIElERiwgYW5kIFRGLUlERiByZWxhdGUgKGNvbWJpbmVkIHZpZXcpDQoNCkZpbmFsbHksIHRoZSBmaWd1cmUgYmVsb3cgdmlzdWFsaXplcyBURiBhbmQgSURGIGpvaW50bHk7IFRGLUlERiBpcyBzaG93biBieSB0aGUgcG9pbnQgc2l6ZSAobGFyZ2VyID0gaGlnaGVyIFRGLUlERikuDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KdG9rZW5zID0gbnAuYXJyYXkoWyJkYXRhIiwgImFuYWx5c2lzIiwgIm1vZGVsIiwgInRoZSIsICJhbmQiLCAicHl0aG9uIiwgInNjaWVuY2UiXSkNCnRmID0gbnAuYXJyYXkoWzAuMTgsIDAuMTIsIDAuMDgsIDAuMzAsIDAuMjAsIDAuMDUsIDAuMDddKQ0KaWRmID0gbnAuYXJyYXkoWzEuMCwgMS40LCAxLjgsIDAuMSwgMC4yLCAyLjAsIDEuNl0pDQp0ZmlkZiA9IHRmICogaWRmDQoNCnBsdC5maWd1cmUoKQ0KcGx0LnNjYXR0ZXIodGYsIGlkZiwgcz0yNTAwKnRmaWRmKSAgIyBwb2ludCBzaXplIHByb3BvcnRpb25hbCB0byBURuKAk0lERg0KZm9yIHgsIHksIHQgaW4gemlwKHRmLCBpZGYsIHRva2Vucyk6DQogICAgcGx0LnRleHQoeCwgeSwgZiIgIHt0fSIsIHZhPSJjZW50ZXIiKQ0KDQpwbHQudGl0bGUoIlRG4oCTSURGIGFzIGFuIGludGVyYWN0aW9uIG9mIFRGIGFuZCBJREYgKHNpemUgPSBURuKAk0lERikiKQ0KcGx0LnhsYWJlbCgiVEYgKHdpdGhpbi1kb2N1bWVudCBmcmVxdWVuY3kpIikNCnBsdC55bGFiZWwoIklERiAoY29ycHVzIHJhcml0eSkiKQ0KcGx0LnRpZ2h0X2xheW91dCgpDQpwbHQuc2hvdygpDQpgYGANCg0KDQpgYGB7cn0NCnJlbF9kZiA8LSBkYXRhLmZyYW1lKA0KICB0b2tlbiA9IGMoImRhdGEiLCAiYW5hbHlzaXMiLCAibW9kZWwiLCAidGhlIiwgImFuZCIsICJweXRob24iLCAic2NpZW5jZSIpLA0KICBURiA9IGMoMC4xOCwgMC4xMiwgMC4wOCwgMC4zMCwgMC4yMCwgMC4wNSwgMC4wNyksDQogIElERiA9IGMoMS4wLCAxLjQsIDEuOCwgMC4xLCAwLjIsIDIuMCwgMS42KQ0KKQ0KDQpyZWxfZGYkVEZJREYgPC0gcmVsX2RmJFRGICogcmVsX2RmJElERg0KDQpnZ3Bsb3QocmVsX2RmLCBhZXMoeCA9IFRGLCB5ID0gSURGLCBzaXplID0gVEZJREYpKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAic3RlZWxibHVlIiwgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHRva2VuKSwgaGp1c3QgPSAtMC4xLCB2anVzdCA9IDAuNSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlRG4oCTSURGIGFzIGFuIGludGVyYWN0aW9uIG9mIFRGIGFuZCBJREYiLA0KICAgIHggPSAiVEYgKHdpdGhpbi1kb2N1bWVudCBmcmVxdWVuY3kpIiwNCiAgICB5ID0gIklERiAoY29ycHVzIHJhcml0eSkiLA0KICAgIHNpemUgPSAiVEbigJNJREYiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNClRoZSBsYXJnZXN0IHBvaW50cyBhcHBlYXIgd2hlcmUgVEYgYW5kIElERiBhcmUgc2ltdWx0YW5lb3VzbHkgaGlnaC4gVGhpcyBtYWtlcyBURuKAk0lERiBlYXN5IHRvIGludGVycHJldCBhcyBhbiBpbnRlcmFjdGlvbjogYSB0b2tlbiBpcyBtb3N0IGltcG9ydGFudCB3aGVuIGl0IGlzIGZyZXF1ZW50IGluIHRoZSBkb2N1bWVudCBidXQgdW5jb21tb24gaW4gdGhlIGNvcnB1cy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgQnVpbGRpbmcgYSBiYXNpYyBURuKAk0lERiB2ZWN0b3JpemVyDQoNCkluIHByYWN0aWNlLCBURuKAk0lERiByZXByZXNlbnRhdGlvbnMgYXJlIGNvbXB1dGVkIGVmZmljaWVudGx5IHVzaW5nIHRoZSBgVGZpZGZWZWN0b3JpemVyYCBjbGFzcyBmcm9tIGBzY2lraXQtbGVhcm5gLCB3aGljaCBjb21iaW5lcyB0ZXJtIGZyZXF1ZW5jeSBub3JtYWxpemF0aW9uIGFuZCBpbnZlcnNlIGRvY3VtZW50IGZyZXF1ZW5jeSB3ZWlnaHRpbmcgaW4gYSBzaW5nbGUgc3RlcC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZS4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVG8ga2VlcCB0aGUgZXhhbXBsZSBzaW1wbGUgYW5kIHNlbGYtY29udGFpbmVkLCBjb25zaWRlciB0aGUgZm9sbG93aW5nIHNtYWxsIGNvbGxlY3Rpb24gb2YgZG9jdW1lbnRzLg0KDQpGaXJzdCwgYSBgVGZpZGZWZWN0b3JpemVyYCBvYmplY3QgaXMgY3JlYXRlZCB1c2luZyB0aGUgZGVmYXVsdCBzZXR0aW5ncywgd2hpY2ggaW5jbHVkZSBcKExfMlwpIG5vcm1hbGl6YXRpb24uIFRoZSBtZXRob2QgYGZpdF90cmFuc2Zvcm0oKWAgbGVhcm5zIHRoZSB2b2NhYnVsYXJ5IGZyb20gdGhlIGNvcnB1cyBhbmQgY29tcHV0ZXMgdGhlIFRG4oCTSURGIG1hdHJpeCBzaW11bHRhbmVvdXNseSwgcHJvZHVjaW5nIGEgbnVtZXJpY2FsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkb2N1bWVudHMuDQoNCg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBza2xlYXJuLmZlYXR1cmVfZXh0cmFjdGlvbi50ZXh0IGltcG9ydCBUZmlkZlZlY3Rvcml6ZXINCg0KZG9jdW1lbnRzID0gWw0KICAgICJTdGF0aXN0aWNhbCBtb2RlbHMgcmVseSBvbiBudW1lcmljYWwgZmVhdHVyZXMiLA0KICAgICJUZXh0IHJlcHJlc2VudGF0aW9ucyBhcmUgYnVpbHQgdXNpbmcgdmVjdG9ycyIsDQogICAgIkZlYXR1cmUgd2VpZ2h0aW5nIGltcHJvdmVzIGRvY3VtZW50IGNvbXBhcmlzb24iDQpdDQoNCnZlY3Rvcml6ZXIgPSBUZmlkZlZlY3Rvcml6ZXIoKQ0KdGZfaWRmX21hdHJpeCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQpgYGANCg0KVGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSBhbmQgdGhlIHJlc3VsdGluZyBURuKAk0lERiBtYXRyaXggY2FuIGJlIGluc3BlY3RlZCBhcyBmb2xsb3dzOg0KDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQpwcmludCh2ZWN0b3JpemVyLmdldF9mZWF0dXJlX25hbWVzX291dCgpKSAgICAjIE91dHB1dCAxDQpwcmludCh0Zl9pZGZfbWF0cml4LnRvYXJyYXkoKSkgICAgICAgICAgICAgICAjIE91dHB1dCAyDQpwcmludCgiTWF0cml4IHNoYXBlOiIsIHRmX2lkZl9tYXRyaXguc2hhcGUpICAjIE91dHB1dCAzDQpgYGANCg0KVGhlIHRocmVlIG91dHB1dHMgY29ycmVzcG9uZCwgcmVzcGVjdGl2ZWx5LCB0byB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5LCB0aGUgVEbigJNJREYgbWF0cml4IGV4cHJlc3NlZCBpbiBkZW5zZSBmb3JtIGZvciBpbnNwZWN0aW9uLCBhbmQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIHJlc3VsdGluZyByZXByZXNlbnRhdGlvbi4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRmlyc3Qgb3V0cHV0LiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoNClRoZSBmaXJzdCBvdXRwdXQgZGlzcGxheXMgdGhlICpsZWFybmVkIHZvY2FidWxhcnkqLCB0aGF0IGlzLCB0aGUgc2V0IG9mIHVuaXF1ZSB0ZXJtcyBleHRyYWN0ZWQgZnJvbSB0aGUgY29ycHVzIGFmdGVyIHByZXByb2Nlc3NpbmcuIEVhY2ggZWxlbWVudCBpbiB0aGlzIGFycmF5IGNvcnJlc3BvbmRzIHRvIGEgY29sdW1uIG9mIHRoZSBURuKAk0lERiBtYXRyaXgsIGFuZCB0aGUgb3JkZXIgc2hvd24gaGVyZSBkZWZpbmVzIHRoZSBjb2x1bW4gb3JkZXJpbmcgdXNlZCBpbiB0aGUgbWF0cml4IHJlcHJlc2VudGF0aW9uLg0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KcHJpbnQodmVjdG9yaXplci5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkNCmBgYA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU2Vjb25kIG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIHNlY29uZCBvdXRwdXQgc2hvd3MgdGhlIFRG4oCTSURGIG1hdHJpeCBpdHNlbGYsIGV4cHJlc3NlZCBpbiBkZW5zZSBmb3JtIGZvciBpbnNwZWN0aW9uLiBFYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIGRvY3VtZW50LCBlYWNoIGNvbHVtbiBjb3JyZXNwb25kcyB0byBhIHRlcm0gaW4gdGhlIGxlYXJuZWQgdm9jYWJ1bGFyeSwgYW5kIGVhY2ggZW50cnkgcmVwcmVzZW50cyB0aGUgVEbigJNJREYgd2VpZ2h0IGFzc2lnbmVkIHRvIHRoYXQgdGVybSBpbiB0aGUgY29ycmVzcG9uZGluZyBkb2N1bWVudC4NCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnByaW50KHRmX2lkZl9tYXRyaXgudG9hcnJheSgpKQ0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRoaXJkIG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIGZpbmFsIG91dHB1dCByZXBvcnRzIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBtYXRyaXguIEluIHRoaXMgZXhhbXBsZSwgdGhlIG1hdHJpeCBoYXMgdGhyZWUgcm93cyAob25lIHBlciBkb2N1bWVudCkgYW5kIHNldmVudGVlbiBjb2x1bW5zIChvbmUgcGVyIHZvY2FidWxhcnkgdGVybSksIGNvbmZpcm1pbmcgdGhlIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gdGhlIGNvcnB1cyBzaXplIGFuZCB0aGUgbGVhcm5lZCB2b2NhYnVsYXJ5Lg0KDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpwcmludCgiTWF0cml4IHNoYXBlOiIsIHRmX2lkZl9tYXRyaXguc2hhcGUpDQpgYGANCg0KVGhlIHZvY2FidWxhcnkgcmVtYWlucyBjb21wYXJhYmxlIHRvIHRoYXQgb2YgYENvdW50VmVjdG9yaXplcmAsIGJ1dCB0aGUgZW50cmllcyBub3cgcmVwcmVzZW50IFRGLUlERiB3ZWlnaHRzIHJhdGhlciB0aGFuIHJhdyBmcmVxdWVuY2llcy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgTm9ybWFsaXphdGlvbiBvZiBURuKAk0lERiB2ZWN0b3JzLg0KDQpOb3JtYWxpemF0aW9uIGVuc3VyZXMgdGhhdCBkb2N1bWVudCB2ZWN0b3JzIGFyZSBjb21wYXJhYmxlIGluIG1hZ25pdHVkZSwgd2hpY2ggaXMgcGFydGljdWxhcmx5IGltcG9ydGFudCBmb3Igc2ltaWxhcml0eSBtZWFzdXJlcy4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgXChMXzJcKSBub3JtLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoNCg0KQnkgZGVmYXVsdCwgZWFjaCBURi1JREYgZG9jdW1lbnQgdmVjdG9yIFwoXG1hdGhiZnt4fSA9ICh4XzEsIHhfMiwgXGRvdHMsIHhfZClcKSBpcyBub3JtYWxpemVkIHRvIGhhdmUgdW5pdCBsZW5ndGggdXNpbmcgdGhlIFwoTF8yXCkgbm9ybSwgZGVmaW5lZCBhcw0KJCRcfFxtYXRoYmZ7eH1cfF8yICBcOyA9XDsgICBcc3FydHtcc3VtX3tqPTF9XntkfSB4X2peMn0kJA0KDQpVbmRlciB0aGlzIG5vcm1hbGl6YXRpb24sIHRoZSB2ZWN0b3IgaXMgcmVzY2FsZWQgc28gdGhhdCBcKFx8XG1hdGhiZnt4fVx8XzIgPSAxXCksIGVtcGhhc2l6aW5nIHJlbGF0aXZlIHRlcm0gY29udHJpYnV0aW9ucyByYXRoZXIgdGhhbiBkb2N1bWVudCBsZW5ndGguDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBcKExfMVwpIG5vcm0uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KDQpBbHRlcm5hdGl2ZWx5LCB0aGUgXChMXzFcKSBub3JtIGNhbiBiZSB1c2VkLCB3aGljaCBpcyBkZWZpbmVkIGFzDQoNCiQkXHxcbWF0aGJme3h9XHxfMSBcOyA9XDsgIFxzdW1fXGxpbWl0c3tqPTF9XntkfSB8eF9qfCQkDQoNCkluIHRoaXMgY2FzZSwgdGhlIHZlY3RvciBpcyByZXNjYWxlZCBzbyB0aGF0IFwoXHxcbWF0aGJme3h9XHxfMSA9IDFcKSwgYWxsb3dpbmcgdGhlIFRGLUlERiB3ZWlnaHRzIHRvIGJlIGludGVycHJldGVkIGFzIHJlbGF0aXZlIHByb3BvcnRpb25zIHdpdGhpbiBlYWNoIGRvY3VtZW50Lg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIEV4YW1wbGUuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmb2xsb3dpbmcgZXhhbXBsZSBpbGx1c3RyYXRlcyBURi1JREYgY29tcHV0YXRpb24gdXNpbmcgXChMXzFcKSBub3JtYWxpemF0aW9uLiANCg0KRmlyc3QsIGEgYFRmaWRmVmVjdG9yaXplcmAgb2JqZWN0IGlzIGNyZWF0ZWQgd2l0aCB0aGUgYXJndW1lbnQgYG5vcm09ImwxImAsIHdoaWNoIHNwZWNpZmllcyB0aGF0IGVhY2ggZG9jdW1lbnQgdmVjdG9yIHdpbGwgYmUgbm9ybWFsaXplZCBzbyB0aGF0IHRoZSBzdW0gb2YgdGhlIGFic29sdXRlIFRG4oCTSURGIHdlaWdodHMgZXF1YWxzIG9uZS4gVGhlIG1ldGhvZCBgZml0X3RyYW5zZm9ybSgpYCB0aGVuIGxlYXJucyB0aGUgdm9jYWJ1bGFyeSBmcm9tIHRoZSBjb3JwdXMgYW5kIGNvbXB1dGVzIHRoZSBjb3JyZXNwb25kaW5nIFRG4oCTSURGIG1hdHJpeCBpbiBhIHNpbmdsZSBzdGVwLg0KDQpUaGUgdGhyZWUgb3V0cHV0cyBkaXNwbGF5LCByZXNwZWN0aXZlbHksIHRoZSBsZWFybmVkIHZvY2FidWxhcnksIHRoZSBURuKAk0lERiBtYXRyaXggd2l0aCAkbF8xJCBub3JtYWxpemF0aW9uIGFwcGxpZWQsIGFuZCB0aGUgZGltZW5zaW9ucyBvZiB0aGUgcmVzdWx0aW5nIHJlcHJlc2VudGF0aW9uLg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0KdmVjdG9yaXplcl9sMSA9IFRmaWRmVmVjdG9yaXplcihub3JtPSJsMSIpDQp0ZmlkZl9sMSA9IHZlY3Rvcml6ZXJfbDEuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXJfbDEuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpICMgT3V0cHV0IDENCnByaW50KHRmaWRmX2wxLnRvYXJyYXkoKSkgICAgICAgICAgICAgICAgICAgICMgT3V0cHV0IDINCnByaW50KCJNYXRyaXggc2hhcGU6IiwgdGZpZGZfbDEuc2hhcGUpICAgICAgICMgT3V0cHV0IDMNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBGaXJzdCBvdXRwdXQuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KVGhlIGZpcnN0IG91dHB1dCBkaXNwbGF5cyB0aGUgKmxlYXJuZWQgdm9jYWJ1bGFyeSouIEFzIGJlZm9yZSwgZWFjaCB0ZXJtIGNvcnJlc3BvbmRzIHRvIGEgY29sdW1uIG9mIHRoZSBURuKAk0lERiBtYXRyaXgsIGFuZCB0aGUgb3JkZXIgc2hvd24gaGVyZSBkZWZpbmVzIHRoZSBjb2x1bW4gb3JkZXJpbmcgdXNlZCBpbiB0aGUgbWF0cml4IHJlcHJlc2VudGF0aW9uLiBUaGUgdm9jYWJ1bGFyeSBpdHNlbGYgaXMgdW5jaGFuZ2VkIGJ5IHRoZSBjaG9pY2Ugb2Ygbm9ybWFsaXphdGlvbi4NCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnZlY3Rvcml6ZXJfbDEgPSBUZmlkZlZlY3Rvcml6ZXIobm9ybT0ibDEiKQ0KdGZpZGZfbDEgPSB2ZWN0b3JpemVyX2wxLmZpdF90cmFuc2Zvcm0oZG9jdW1lbnRzKQ0KDQpwcmludCh2ZWN0b3JpemVyX2wxLmdldF9mZWF0dXJlX25hbWVzX291dCgpKQ0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBTZWNvbmQgb3V0cHV0LiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgc2Vjb25kIG91dHB1dCBzaG93cyB0aGUgVEbigJNJREYgbWF0cml4IHdpdGggJExfMSQgbm9ybWFsaXphdGlvbiBhcHBsaWVkLiBFYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIGRvY3VtZW50IGFuZCBlYWNoIGNvbHVtbiB0byBhIHRlcm0gaW4gdGhlIHZvY2FidWxhcnkuIFVuZGVyICRMXzEkIG5vcm1hbGl6YXRpb24sIHRoZSB2YWx1ZXMgaW4gZWFjaCByb3cgc3VtIHRvIG9uZSwgc28gdGhlIGVudHJpZXMgY2FuIGJlIGludGVycHJldGVkIGFzIHJlbGF0aXZlIHdlaWdodHMgb2YgdGVybXMgd2l0aGluIHRoZSBkb2N1bWVudC4gRm9yIGV4YW1wbGUsIGluIHRoZSBmaXJzdCBkb2N1bWVudCwgdGhlIG5vbnplcm8gZW50cmllcyBhcmUgYWxsIGVxdWFsLCBpbmRpY2F0aW5nIHRoYXQgdGhlIHJldGFpbmVkIHRlcm1zIGNvbnRyaWJ1dGUgZXF1YWxseSB0byB0aGUgdG90YWwgVEbigJNJREYgd2VpZ2h0IG9mIHRoYXQgZG9jdW1lbnQuDQogDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KcHJpbnQodGZpZGZfbDEudG9hcnJheSgpKQ0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRoaXJkIG91dHB1dC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIGZpbmFsIG91dHB1dCByZXBvcnRzIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBtYXRyaXguIEluIHRoaXMgZXhhbXBsZSwgdGhlIG1hdHJpeCBoYXMgdGhyZWUgcm93cyAob25lIHBlciBkb2N1bWVudCkgYW5kIHNldmVudGVlbiBjb2x1bW5zIChvbmUgcGVyIHZvY2FidWxhcnkgdGVybSksIGNvbmZpcm1pbmcgdGhhdCBub3JtYWxpemF0aW9uIGFmZmVjdHMgdGhlIHNjYWxlIG9mIHRoZSB3ZWlnaHRzLCBidXQgbm90IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIHJlcHJlc2VudGF0aW9uLg0KDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpwcmludCgiTWF0cml4IHNoYXBlOiIsIHRmaWRmX2wxLnNoYXBlKQ0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgTi1ncmFtcyBhbmQgdm9jYWJ1bGFyeSBzaXplIGluIFRG4oCTSURGDQoNCkFzIHdpdGggQmFnLW9mLVdvcmRzIHJlcHJlc2VudGF0aW9ucywgdGhlIFRG4oCTSURGIHZlY3Rvcml6ZXIgc3VwcG9ydHMgdGhlIHVzZSBvZiBuLWdyYW1zIGFzIHdlbGwgYXMgY29uc3RyYWludHMgb24gdm9jYWJ1bGFyeSBzaXplLiBUaGlzIGFsbG93cyBzaG9ydCBwaHJhc2VzIHRvIGJlIGluY29ycG9yYXRlZCBpbnRvIHRoZSByZXByZXNlbnRhdGlvbiB3aGlsZSBrZWVwaW5nIGRpbWVuc2lvbmFsaXR5IHVuZGVyIGNvbnRyb2wuDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZS4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KSW4gdGhlIGZvbGxvd2luZyBleGFtcGxlLCB0aGUgcmVwcmVzZW50YXRpb24gaXMgcmVzdHJpY3RlZCB0byB0aGUgc2l4IG1vc3QgZnJlcXVlbnQgZmVhdHVyZXMgYW1vbmcgdW5pZ3JhbXMsIGJpZ3JhbXMsIGFuZCB0cmlncmFtcy4gVGhlIGFyZ3VtZW50IGBuZ3JhbV9yYW5nZSA9ICgxLCAzKWAgZW5hYmxlcyB0aGUgZXh0cmFjdGlvbiBvZiBuLWdyYW1zIHVwIHRvIGxlbmd0aCB0aHJlZSwgd2hpbGUgYG1heF9mZWF0dXJlcyA9IDZgIGxpbWl0cyB0aGUgdm9jYWJ1bGFyeSBzaXplLiBUaGUgZGVmYXVsdCBcKExfMlwpIG5vcm1hbGl6YXRpb24gaXMgYXBwbGllZC4NCg0KDQpgYGB7cHl0aG9uLCBldmFsPSBGQUxTRX0NCnZlY3Rvcml6ZXJfbmdyYW0gPSBUZmlkZlZlY3Rvcml6ZXIoDQogICAgbmdyYW1fcmFuZ2U9KDEsIDMpLA0KICAgIG1heF9mZWF0dXJlcz02LA0KICAgIG5vcm09ImwyIg0KKQ0KDQp0ZmlkZl9uZ3JhbSA9IHZlY3Rvcml6ZXJfbmdyYW0uZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXJfbmdyYW0uZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpDQpwcmludCh0ZmlkZl9uZ3JhbS50b2FycmF5KCkpDQpwcmludCgiTWF0cml4IHNoYXBlOiIsIHRmaWRmX25ncmFtLnNoYXBlKQ0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBGaXJzdCBvdXRwdXQuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmaXJzdCBvdXRwdXQgZGlzcGxheXMgdGhlICpsZWFybmVkIG4tZ3JhbSB2b2NhYnVsYXJ5KiwgcmVzdHJpY3RlZCB0byBzaXggZmVhdHVyZXMuIEluIHRoaXMgY2FzZSwgYWxsIHJldGFpbmVkIGZlYXR1cmVzIGNvcnJlc3BvbmQgdG8gdW5pZ3JhbXMsIGJpZ3JhbXMsIGFuZCB0cmlncmFtcyBkZXJpdmVkIGZyb20gdGhlIHBocmFzZSBgYXJlIGJ1aWx0IHVzaW5nYC4gRWFjaCBlbGVtZW50IGluIHRoaXMgbGlzdCBkZWZpbmVzIGEgY29sdW1uIG9mIHRoZSBURi1JREYgbWF0cml4LCBhbmQgdGhlIG9yZGVyIHNob3duIGhlcmUgZGV0ZXJtaW5lcyB0aGUgY29sdW1uIG9yZGVyaW5nLg0KDQoNCmBgYHtweXRob24sIGVjaG89IEZBTFNFfQ0KdmVjdG9yaXplcl9uZ3JhbSA9IFRmaWRmVmVjdG9yaXplcigNCiAgICBuZ3JhbV9yYW5nZT0oMSwgMyksDQogICAgbWF4X2ZlYXR1cmVzPTYsDQogICAgbm9ybT0ibDIiDQopDQoNCnRmaWRmX25ncmFtID0gdmVjdG9yaXplcl9uZ3JhbS5maXRfdHJhbnNmb3JtKGRvY3VtZW50cykNCg0KcHJpbnQodmVjdG9yaXplcl9uZ3JhbS5nZXRfZmVhdHVyZV9uYW1lc19vdXQoKSkNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBTZWNvbmQgb3V0cHV0LiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgc2Vjb25kIG91dHB1dCBzaG93cyB0aGUgVEbigJNJREYgbWF0cml4IGNvbnN0cnVjdGVkIHVzaW5nIHRoZSByZXN0cmljdGVkIG4tZ3JhbSB2b2NhYnVsYXJ5LiBFYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIGRvY3VtZW50IGFuZCBlYWNoIGNvbHVtbiBjb3JyZXNwb25kcyB0byBvbmUgb2YgdGhlIHNlbGVjdGVkIG4tZ3JhbXMuDQoNCg0KYGBge3B5dGhvbiwgZWNobz0gRkFMU0V9DQpwcmludCh0ZmlkZl9uZ3JhbS50b2FycmF5KCkpDQpgYGANCg0KSW4gdGhpcyBleGFtcGxlLCBvbmx5IHRoZSBzZWNvbmQgZG9jdW1lbnQgY29udGFpbnMgdGhlIHJldGFpbmVkIG4tZ3JhbXMsIHdoaWNoIGV4cGxhaW5zIHdoeSBpdHMgcm93IGhhcyBub256ZXJvIFRG4oCTSURGIHZhbHVlcywgd2hpbGUgdGhlIGZpcnN0IGFuZCB0aGlyZCBkb2N1bWVudHMgYXJlIHJlcHJlc2VudGVkIGJ5IHplcm8gdmVjdG9ycy4NCg0KQmVjYXVzZSAkTF8yJCBub3JtYWxpemF0aW9uIGlzIGFwcGxpZWQsIHRoZSBub256ZXJvIHJvdyBoYXMgdW5pdCBFdWNsaWRlYW4gbm9ybSwgYW5kIHRoZSBURi1JREYgd2VpZ2h0cyBhcmUgZXZlbmx5IGRpc3RyaWJ1dGVkIGFjcm9zcyB0aGUgc2l4IHJldGFpbmVkIGZlYXR1cmVzLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBUaGlyZCBvdXRwdXQuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmaW5hbCBvdXRwdXQgcmVwb3J0cyB0aGUgZGltZW5zaW9ucyBvZiB0aGUgbWF0cml4LiBIZXJlLCB0aGUgbWF0cml4IGhhcyB0aHJlZSByb3dzIChvbmUgcGVyIGRvY3VtZW50KSBhbmQgc2l4IGNvbHVtbnMgKG9uZSBwZXIgcmV0YWluZWQgbi1ncmFtKSwgY29uZmlybWluZyB0aGF0IGBtYXhfZmVhdHVyZXNgIGRpcmVjdGx5IGNvbnRyb2xzIHRoZSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgVEYtSURGIHJlcHJlc2VudGF0aW9uLg0KDQpgYGB7cHl0aG9uLCBlY2hvPSBGQUxTRX0NCnByaW50KCJNYXRyaXggc2hhcGU6IiwgdGZpZGZfbmdyYW0uc2hhcGUpDQpgYGANCg0KVGhlIHBhcmFtZXRlcnMgYG1pbl9kZmAgYW5kIGBtYXhfZGZgIGFyZSBhbHNvIGF2YWlsYWJsZSBmb3IgVEYtSURGIHZlY3Rvcml6ZXJzIGFuZCBiZWhhdmUgaWRlbnRpY2FsbHkgdG8gdGhvc2UgaW4gYENvdW50VmVjdG9yaXplcmAsIGFsbG93aW5nIGV4dHJlbWVseSByYXJlIG9yIG92ZXJseSBjb21tb24gdGVybXMgdG8gYmUgZXhjbHVkZWQgYmFzZWQgb24gZG9jdW1lbnQgZnJlcXVlbmN5Lg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPiANCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMjIyBMaW1pdGF0aW9ucyBvZiB0aGUgVEYtSURGIHJlcHJlc2VudGF0aW9uDQoNClRG4oCTSURGIGltcHJvdmVzIHVwb24gcmF3IHdvcmQgY291bnRzIGJ5IGFkanVzdGluZyB0b2tlbiBpbXBvcnRhbmNlIHVzaW5nIGNvcnB1cy1sZXZlbCBzdGF0aXN0aWNzLiBJdCByZW1haW5zIGNvbXB1dGF0aW9uYWxseSBlZmZpY2llbnQgYW5kIGhpZ2hseSBpbnRlcnByZXRhYmxlLg0KDQpIb3dldmVyLCBURuKAk0lERiBzdGlsbCBvcGVyYXRlcyBwdXJlbHkgYXQgdGhlIGxleGljYWwgbGV2ZWwgYW5kIHRoZXJlZm9yZSBkb2VzIG5vdCBjYXB0dXJlOg0KDQotIFNlbWFudGljIHNpbWlsYXJpdHkgYmV0d2VlbiB3b3JkcywNCg0KLSBDb250ZXh0dWFsIG1lYW5pbmcsDQoNCi0gV29yZCBvcmRlciBvciBjby1vY2N1cnJlbmNlIHN0cnVjdHVyZSwgb3INCg0KLSBQb3NpdGlvbmFsIGluZm9ybWF0aW9uIHdpdGhpbiBkb2N1bWVudHMuDQoNCkZpZ3VyZSBcQHJlZihmaWc6RmlnLUxpbWl0YTEpIHN1bW1hcml6ZXMgdGhlc2UgZm91ciBsaW1pdGF0aW9ucyB3aXRoIHNpbXBsZSBleGFtcGxlcy4NCg0KPGNlbnRlcj4NCmBgYHtyIEZpZy1MaW1pdGExLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIkxpbWl0YXRpb25zIG9mIHRoZSBURi1JREYgcmVwcmVzZW50YXRpb24uIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI4MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkxpbWl0YTEucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQoNCkxpa2UgQm9XLCBURuKAk0lERiByZXByZXNlbnRhdGlvbnMgYWxzbyBzY2FsZSB3aXRoIHZvY2FidWxhcnkgc2l6ZSwgd2hpY2ggY2FuIGJlY29tZSBwcm9ibGVtYXRpYyBmb3IgdmVyeSBsYXJnZSBjb3Jwb3JhLg0KDQpUaGVzZSBsaW1pdGF0aW9ucyBtb3RpdmF0ZSB0aGUgdXNlIG9mIHNpbWlsYXJpdHkgbWVhc3VyZXMgKHN1Y2ggYXMgY29zaW5lIHNpbWlsYXJpdHkpIGFuZCBtb3JlIGV4cHJlc3NpdmUgcmVwcmVzZW50YXRpb24gbGVhcm5pbmcgdGVjaG5pcXVlcywgd2hpY2ggYXJlIGV4cGxvcmVkIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgRGlzdGFuY2Uvc2ltaWxhcml0eSBjYWxjdWxhdGlvbiBiZXR3ZWVuIGRvY3VtZW50IHZlY3RvcnMNCg0KT25jZSBkb2N1bWVudHMgaGF2ZSBiZWVuIHJlcHJlc2VudGVkIGFzIHZlY3RvcnMsIGEgbmF0dXJhbCBxdWVzdGlvbiBhcmlzZXM6DQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KSG93IGNhbiB3ZSBxdWFudGlmeSBob3cgc2ltaWxhciBvciBkaXNzaW1pbGFyIHR3byB0ZXh0IGRvY3VtZW50cyBhcmU/DQpgYGANCg0KDQpJZiB0d28gZG9jdW1lbnRzIHVzZSBzaW1pbGFyIHdvcmRzIHdpdGggY29tcGFyYWJsZSBkaXN0cmlidXRpb25zLCBpdCBpcyByZWFzb25hYmxlIHRvIGV4cGVjdCB0aGF0IHRoZXkgY29udmV5IHJlbGF0ZWQgaW5mb3JtYXRpb24uIEluIHRoaXMgc2VjdGlvbiwgd2UgaW50cm9kdWNlICoqY29zaW5lIHNpbWlsYXJpdHkqKiwgYSBnZW9tZXRyaWMgbWVhc3VyZSB3aWRlbHkgdXNlZCB0byBjb21wYXJlIGRvY3VtZW50IHZlY3RvcnMgZGVyaXZlZCBmcm9tIEJhZy1vZi1Xb3JkcyBhbmQgVEYtSURGIHJlcHJlc2VudGF0aW9ucy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgQ29zaW5lIHNpbWlsYXJpdHkNCg0KQ29zaW5lIHNpbWlsYXJpdHkgbWVhc3VyZXMgdGhlICpvcmllbnRhdGlvbiogb2YgdHdvIHZlY3RvcnMgaW4gYSB2ZWN0b3Igc3BhY2UgYnkgY29tcHV0aW5nIHRoZSBjb3NpbmUgb2YgdGhlIGFuZ2xlIGJldHdlZW4gdGhlbS4gVW5saWtlIGRpc3RhbmNlLWJhc2VkIG1lYXN1cmVzLCBpdCBpcyBpbnNlbnNpdGl2ZSB0byB2ZWN0b3IgbWFnbml0dWRlIGFuZCBpbnN0ZWFkIGZvY3VzZXMgb24gZGlyZWN0aW9uLg0KDQpUd28gdmVjdG9ycyBhcmUgY29uc2lkZXJlZCBzaW1pbGFyIHdoZW4gdGhleSBwb2ludCBpbiBuZWFybHkgdGhlIHNhbWUgZGlyZWN0aW9uLCBldmVuIGlmIHRoZWlyIGxlbmd0aHMgZGlmZmVyLiBUaGlzIHByb3BlcnR5IGlzIGVzcGVjaWFsbHkgdXNlZnVsIGluIHRleHQgYW5hbHlzaXMsIHdoZXJlIHZlY3RvciBtYWduaXR1ZGUgaXMgb2Z0ZW4gaW5mbHVlbmNlZCBieSBkb2N1bWVudCBsZW5ndGguDQoNCkZvciB0d28gdmVjdG9ycyBcKFxtYXRoYmZ7dn0sIFxtYXRoYmZ7dn0gXGluIFxtYXRoYmJ7Un1eZCBcKSwgY29zaW5lIHNpbWlsYXJpdHkgaXMgZGVmaW5lZCBhczoNCg0KJCRcY29zKFxtYXRoYmZ7dX0sIFxtYXRoYmZ7dn0pIFxxdWFkID1ccXVhZCBcZnJhY3tcbWF0aGJme3V9IFxjZG90IFxtYXRoYmZ7dn19IHtcfFxtYXRoYmZ7dX1cfF8yIFx8XG1hdGhiZnt2fVx8XzJ9IFxxdWFkID0gXHF1YWQgDQpcZnJhY3tcc3VtX1xsaW1pdHN7aT0xfV57ZH0gdV97aX0gXCwgdl97aX19ICB7XHNxcnR7XHN1bV9cbGltaXRze2k9MX1ee2R9IHVfe2l9XjJ9IFw7ICBcc3FydHtcc3VtX1xsaW1pdHN7aT0xfV57ZH0gdl97aX1eMn19IFxxdWFkIFxpbiBccXVhZCBbLTEsIDFdJCQNCg0KDQpIZXJlLCBcKCBcbWF0aGJme3V9IFxjZG90IFxtYXRoYmZ7dn0gXCkgZGVub3RlcyB0aGUgRXVjbGlkZWFuIGlubmVyIHByb2R1Y3QsIGFuZCB0aGUgJExfMiQgbm9ybSAoRXVjbGlkZWFuIG5vcm0pIG9mIGEgdmVjdG9yIFwoIFxtYXRoYmZ7dn0gXGluIFxtYXRoYmJ7Un1eZCBcKSBpcyBkZWZpbmVkIGFzOg0KDQoNCiQkXHxcbWF0aGJme3Z9XHxfMiBccXVhZCA9XHF1YWQgIFxzcXJ0e1xzdW1fe2k9MX1ee2R9IHZfaV4yfS4kJA0KDQpDb3NpbmUgc2ltaWxhcml0eSBtZWFzdXJlcyAqKmFuZ3VsYXIgc2ltaWxhcml0eSoqLCBub3QgRXVjbGlkZWFuIGRpc3RhbmNlLiBJdCBldmFsdWF0ZXMgdGhlIGFuZ2xlIGJldHdlZW4gdmVjdG9ycyByYXRoZXIgdGhhbiB0aGVpciBtYWduaXR1ZGUuIEl0cyB0YWtlcyB2YWx1ZXMgaW4gdGhlIGNvbnRpbnVvdXMgaW50ZXJ2YWwgXChbLTEsMV1cKS4gVGhlIGV4dHJlbWUgY2FzZXMgY29ycmVzcG9uZCB0bzoNCg0KLSBJZGVudGljYWwgZGlyZWN0aW9uIChtYXhpbXVtIHNpbWlsYXJpdHkpOiBcKCAxIFwpIA0KDQotIE9ydGhvZ29uYWwgdmVjdG9ycyAobm8gbGluZWFyIGFzc29jaWF0aW9uKTogXCggMCBcKSANCg0KLSBPcHBvc2l0ZSBkaXJlY3Rpb246IFwoIC0xIFwpIA0KDQpJbnRlcm1lZGlhdGUgdmFsdWVzIChlLmcuLCAwLjgyLCAwLjM0LCDiiJIwLjE1KSByZWZsZWN0IHZhcnlpbmcgYW5ndWxhciBwcm94aW1pdHkgYmV0d2VlbiB2ZWN0b3JzLiBJbiBlbWJlZGRpbmcgc3BhY2VzIHRyYWluZWQgb24gbmF0dXJhbCBsYW5ndWFnZSBkYXRhLCBjb3NpbmUgdmFsdWVzIGFyZSB0eXBpY2FsbHkgbm9uLW5lZ2F0aXZlLCBzaW5jZSBzZW1hbnRpY2FsbHkgdW5yZWxhdGVkIHdvcmRzIHJhcmVseSBleGhpYml0IHN0cm9uZyBvcHBvc2l0ZSBvcmllbnRhdGlvbnMuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgQ29zaW5lIHNpbWlsYXJpdHk6IFNvbHZpbmcgc3RlcCBieSBzdGVwIA0KDQpDb25zaWRlciB0d28gZG9jdW1lbnRzIHJlcHJlc2VudGVkIGJ5IGNvdW50LWJhc2VkIHZlY3RvcnM6DQoNCiQkXG1hdGhiZnt1fSA9ICg0LCAxLCAyLCAwLCAzLCAwLCAxLCAwKSBccXVhZCBcdGV4dHthbmR9IFxxdWFkIFxtYXRoYmZ7dn0gPSAoMiwgMCwgMSwgMSwgMiwgMSwgMCwgMCkkJA0KDQpUaGUgY29zaW5lIHNpbWlsYXJpdHkgYmV0d2VlbiB0aGVtIGlzOg0KDQokJFxjb3MoXG1hdGhiZnt1fSwgXG1hdGhiZnt2fSkgXHF1YWQgPSBccXVhZCAgXGZyYWN7XG1hdGhiZnt1fSBcY2RvdCBcbWF0aGJme3Z9fXtcfFxtYXRoYmZ7dX1cfF8yIFw7IFx8XG1hdGhiZnt2fVx8XzJ9JCQNCg0KRmlyc3QsIGNvbXB1dGUgdGhlIGRvdCBwcm9kdWN0Og0KDQokJFxtYXRoYmZ7dX0gXGNkb3QgXG1hdGhiZnt2fSBccXVhZCA9IFxxdWFkICA0XGNkb3QyICsgMVxjZG90MCArIDJcY2RvdDEgKyAwXGNkb3QxICsgM1xjZG90MiArIDBcY2RvdDEgKyAxXGNkb3QwICsgMFxjZG90MCBccXVhZCA9IFxxdWFkIDE2JCQNCg0KTmV4dCwgY29tcHV0ZSB0aGUgdmVjdG9yIG5vcm1zOg0KDQokJFx8XG1hdGhiZnt1fVx8IFxxdWFkID1ccXVhZCAgXHNxcnR7NF4yICsgMV4yICsgMl4yICsgM14yICsgMV4yfSBccXVhZCA9IFxxdWFkICBcc3FydHszMX0gXHF1YWQgXGFwcHJveCBccXVhZCA1LjU3JCQNCg0KJCRcfFxtYXRoYmZ7dn1cfCBccXVhZCA9IFxxdWFkIFxzcXJ0ezJeMiArIDFeMiArIDFeMiArIDJeMiArIDFeMn0gXHF1YWQgPSBccXVhZCAgXHNxcnR7MTF9IFxxdWFkIFxhcHByb3ggXHF1YWQgMy4zMiQkDQoNCkZpbmFsbHk6DQoNCiQkXGNvcyhcbWF0aGJme3V9LCBcbWF0aGJme3Z9KSBccXVhZCA9IFxxdWFkICBcZnJhY3sxNn17KDUuNTcpKDMuMzIpfSBccXVhZCBcYXBwcm94IFxxdWFkIDAuODckJA0KDQpBIGNvc2luZSBzaW1pbGFyaXR5IG9mIFwoMC44N1wpIGluZGljYXRlcyBhIHN0cm9uZyBzaW1pbGFyaXR5IGJldHdlZW4gdGhlIHR3byBkb2N1bWVudHMuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIENvc2luZSBzaW1pbGFyaXR5OiBJbXBsZW1lbnRpbmcgaW4gUHl0aG9uICANCg0KVGhlIGZvbGxvd2luZyBmdW5jdGlvbiBjb21wdXRlcyBjb3NpbmUgc2ltaWxhcml0eSBiZXR3ZWVuIHR3byBudW1lcmljIHZlY3RvcnM6DQoNCmBgYHtweXRob259DQppbXBvcnQgbnVtcHkgYXMgbnANCg0KZGVmIGNvc2luZV9zaW1pbGFyaXR5KHZlYzEsIHZlYzIpOg0KICAgIHYxID0gbnAuYXJyYXkodmVjMSkNCiAgICB2MiA9IG5wLmFycmF5KHZlYzIpDQogICAgcmV0dXJuIG5wLmRvdCh2MSwgdjIpIC8gKG5wLmxpbmFsZy5ub3JtKHYxKSAqIG5wLmxpbmFsZy5ub3JtKHYyKSkNCmBgYA0KDQpUaGlzIGZ1bmN0aW9uIGNhbiBiZSBhcHBsaWVkIGRpcmVjdGx5IHRvIGRvY3VtZW50IHZlY3RvcnMgcHJvZHVjZWQgYnkgZGlmZmVyZW50IHZlY3Rvcml6YXRpb24gdGVjaG5pcXVlcy4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBDb3NpbmUgc2ltaWxhcml0eSB1c2luZyBgQ291bnRWZWN0b3JpemVyYCBvdXRwdXRzIA0KDQojIyMjIGBDb3VudFZlY3Rvcml6ZXJgOiB0aGUgc3BhcnNlIGFuZCBkb2N1bWVudC10ZXJtIG1hdHJpY2VzIChjb2RlKS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KDQpSZWNhbGwgdGhhdCBgQ291bnRWZWN0b3JpemVyYCBidWlsZHMgYSAqZG9jdW1lbnTigJN0ZXJtIG1hdHJpeCogKEJhZy1vZi1Xb3JkcykuIEVhY2ggZG9jdW1lbnQgaXMgcmVwcmVzZW50ZWQgYnkgYSBzcGFyc2UgdmVjdG9yIG9mICpyYXcgdG9rZW4gY291bnRzKiwgd2hlcmU6DQoNCi0gKlJvd3MqID0gZG9jdW1lbnRzLiAgDQoNCi0gKkNvbHVtbnMqID0gdm9jYWJ1bGFyeSB0ZXJtcy4gIA0KDQotICpFbnRyaWVzKiA9IGhvdyBtYW55IHRpbWVzIGVhY2ggdGVybSBhcHBlYXJzIGluIGVhY2ggZG9jdW1lbnQuDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQpmcm9tIHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHQgaW1wb3J0IENvdW50VmVjdG9yaXplcg0KDQpkb2N1bWVudHMgPSBbDQogICAgIkRhdGEgc2NpZW5jZSByZWxpZXMgb24gbnVtZXJpY2FsIG1ldGhvZHMiLCAgICAgICAgICAjIERvY3VtZW50IDENCiAgICAiVGV4dCBhbmFseXNpcyB1c2VzIHZlY3RvcnMgYW5kIG1hdHJpY2VzIiwgICAgICAgICAgICMgRG9jdW1lbnQgMg0KICAgICJNYXRoZW1hdGljYWwgcmVwcmVzZW50YXRpb25zIHN1cHBvcnQgZGF0YSBtb2RlbGluZyIgIyBEb2N1bWVudCAzDQpdDQoNCnZlY3Rvcml6ZXIgPSBDb3VudFZlY3Rvcml6ZXIoKQ0KYm93X21hdHJpeCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShkb2N1bWVudHMpDQoNCnByaW50KHZlY3Rvcml6ZXIuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpICMgT3V0cHV0IDENCnByaW50KGJvd19tYXRyaXgudG9hcnJheSgpKSAgICAgICAgICAgICAgICMgT3V0cHV0IDINCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBgQ291bnRWZWN0b3JpemVyYDogdGhlIHNwYXJzZSBhbmQgZG9jdW1lbnQtdGVybSBtYXRyaWNlcyAob3V0cHV0cykuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgQ291bnRWZWN0b3JpemVyDQoNCmRvY3VtZW50cyA9IFsNCiAgICAiRGF0YSBzY2llbmNlIHJlbGllcyBvbiBudW1lcmljYWwgbWV0aG9kcyIsICAgICAgICAgICMgRG9jdW1lbnQgMQ0KICAgICJUZXh0IGFuYWx5c2lzIHVzZXMgdmVjdG9ycyBhbmQgbWF0cmljZXMiLCAgICAgICAgICAgIyBEb2N1bWVudCAyDQogICAgIk1hdGhlbWF0aWNhbCByZXByZXNlbnRhdGlvbnMgc3VwcG9ydCBkYXRhIG1vZGVsaW5nIiAjIERvY3VtZW50IDMNCl0NCg0KdmVjdG9yaXplciA9IENvdW50VmVjdG9yaXplcigpDQpib3dfbWF0cml4ID0gdmVjdG9yaXplci5maXRfdHJhbnNmb3JtKGRvY3VtZW50cykNCmBgYA0KDQoNCk91dHB1dCAxIHNob3dzIHRoZSBsZWFybmVkIHZvY2FidWxhcnkgKGZlYXR1cmUgbmFtZXMpIGV4dHJhY3RlZCBieSBgQ291bnRWZWN0b3JpemVyYCwgd2hpY2ggZGVmaW5lcyB0aGUgY29sdW1ucyBvZiB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXguIEl0IGxpc3RzIHRoZSB2b2NhYnVsYXJ5IGxlYXJuZWQgZnJvbSB0aGUgY29ycHVzOg0KDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpwcmludCh2ZWN0b3JpemVyLmdldF9mZWF0dXJlX25hbWVzX291dCgpKQ0KYGBgDQoNCk91dHB1dCAyIGRpc3BsYXlzIHRoZSBjb3JyZXNwb25kaW5nIGRvY3VtZW50LXRlcm0gbWF0cml4IGluIGRlbnNlIGZvcm0sIHdoZXJlIHJvd3MgcmVwcmVzZW50IGRvY3VtZW50cywgY29sdW1ucyByZXByZXNlbnQgdm9jYWJ1bGFyeSB0ZXJtcyAodG9rZW4pLCBhbmQgZW50cmllcyBpbmRpY2F0ZSByYXcgdGVybSBmcmVxdWVuY2llcy4gDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpwcmludChib3dfbWF0cml4LnRvYXJyYXkoKSkgICAgICAgICAgICAgICANCmBgYA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgYENvdW50VmVjdG9yaXplcmA6IGNvc2luZSBzaW1pbGFyaXR5IChjb2RlKS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KVXNpbmcgdGhlIGRvY3VtZW504oCTdGVybSBtYXRyaXggYGJvd19tYXRyaXhgLCBjb3NpbmUgc2ltaWxhcml0eSBjYW4gYmUgY29tcHV0ZWQgZm9yIGV2ZXJ5IGRvY3VtZW50IHBhaXI6DQoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCmZvciBpIGluIHJhbmdlKGJvd19tYXRyaXguc2hhcGVbMF0pOg0KICAgIGZvciBqIGluIHJhbmdlKGkgKyAxLCBib3dfbWF0cml4LnNoYXBlWzBdKToNCiAgICAgICAgc2ltID0gY29zaW5lX3NpbWlsYXJpdHkoDQogICAgICAgICAgICBib3dfbWF0cml4LnRvYXJyYXkoKVtpXSwNCiAgICAgICAgICAgIGJvd19tYXRyaXgudG9hcnJheSgpW2pdDQogICAgICAgICkNCiAgICAgICAgcHJpbnQoZiJDb3NpbmUgc2ltaWxhcml0eSBiZXR3ZWVuIGRvY3VtZW50cyB7aSsxfSBhbmQge2orMX06IHtzaW06LjNmfSIpDQpgYGANCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgYENvdW50VmVjdG9yaXplcmA6IGNvc2luZSBzaW1pbGFyaXR5IChvdXRwdXRzKS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KZm9yIGkgaW4gcmFuZ2UoYm93X21hdHJpeC5zaGFwZVswXSk6DQogICAgZm9yIGogaW4gcmFuZ2UoaSArIDEsIGJvd19tYXRyaXguc2hhcGVbMF0pOg0KICAgICAgICBzaW0gPSBjb3NpbmVfc2ltaWxhcml0eSgNCiAgICAgICAgICAgIGJvd19tYXRyaXgudG9hcnJheSgpW2ldLA0KICAgICAgICAgICAgYm93X21hdHJpeC50b2FycmF5KClbal0NCiAgICAgICAgKQ0KICAgICAgICBwcmludChmIkNvc2luZSBzaW1pbGFyaXR5IGJldHdlZW4gZG9jdW1lbnRzIHtpKzF9IGFuZCB7aisxfToge3NpbTouM2Z9IikNCmBgYA0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIGBDb3VudFZlY3Rvcml6ZXJgOiBjb3NpbmUgc2ltaWxhcml0eSAoaW50ZXJwcmV0YXRpb24gb2YgdGhlIG91dHB1dHMpLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQoNCi0gQSB2YWx1ZSBvZiAqMC4wMDAqIG1lYW5zIHRoZSB0d28gZG9jdW1lbnRzIHNoYXJlICpubyB2b2NhYnVsYXJ5IHRlcm1zKiBhZnRlciBwcmVwcm9jZXNzaW5nLCBzbyB0aGVpciBCb1cgdmVjdG9ycyBhcmUgb3J0aG9nb25hbC4NCg0KLSBBIHNtYWxsIHBvc2l0aXZlIHZhbHVlIChlLmcuLCAqMC4xODMqKSB0eXBpY2FsbHkgaW5kaWNhdGVzICpsaW1pdGVkIGxleGljYWwgb3ZlcmxhcCogKGZvciBpbnN0YW5jZSwgYSBzaW5nbGUgc2hhcmVkIHRva2VuIHN1Y2ggYXMgKmRhdGEqKSwgYnV0IG5vdCBuZWNlc3NhcmlseSBzdHJvbmcgc2VtYW50aWMgc2ltaWxhcml0eS4NCg0KVGhpcyBoaWdobGlnaHRzIGFuIGltcG9ydGFudCBwb2ludDogKmNvc2luZSBzaW1pbGFyaXR5IG9uIEJhZy1vZi1Xb3JkcyBpcyBkcml2ZW4gYnkgc2hhcmVkIHRva2VucyosIG5vdCBieSBtZWFuaW5nLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIENvc2luZSBzaW1pbGFyaXR5IHVzaW5nIFRGLUlERiByZXByZXNlbnRhdGlvbnMgDQoNCg0KIyMjIyBURi1JREYgcmVwcmVzZW50YXRpb25zOiBjb3NpbmUgc2ltaWxhcml0eSAoY29kZSkuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KVEYtSURGIGJ1aWxkcyB0aGUgc2FtZSB0eXBlIG9mIGRvY3VtZW50IHZlY3RvcnMsIGJ1dCAqcmV3ZWlnaHRzKiB0ZXJtczoNCg0KLSBXb3JkcyB0aGF0IGFwcGVhciBpbiBtYW55IGRvY3VtZW50cyByZWNlaXZlICpsb3dlciB3ZWlnaHQqLg0KDQotIFdvcmRzIHRoYXQgYXJlIG1vcmUgZG9jdW1lbnQtc3BlY2lmaWMgcmVjZWl2ZSAqaGlnaGVyIHdlaWdodCouDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQpmb3IgaSBpbiByYW5nZSh0Zl9pZGZfbWF0cml4LnNoYXBlWzBdKToNCiAgICBmb3IgaiBpbiByYW5nZShpICsgMSwgdGZfaWRmX21hdHJpeC5zaGFwZVswXSk6DQogICAgICAgIHNpbSA9IGNvc2luZV9zaW1pbGFyaXR5KA0KICAgICAgICAgICAgdGZfaWRmX21hdHJpeC50b2FycmF5KClbaV0sDQogICAgICAgICAgICB0Zl9pZGZfbWF0cml4LnRvYXJyYXkoKVtqXQ0KICAgICAgICApDQogICAgICAgIHByaW50KGYiQ29zaW5lIHNpbWlsYXJpdHkgYmV0d2VlbiBkb2N1bWVudHMge2krMX0gYW5kIHtqKzF9OiB7c2ltOi4zZn0iKQ0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRGLUlERiByZXByZXNlbnRhdGlvbnM6IGNvc2luZSBzaW1pbGFyaXR5IChvdXRwdXRzKS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0KZm9yIGkgaW4gcmFuZ2UodGZfaWRmX21hdHJpeC5zaGFwZVswXSk6DQogICAgZm9yIGogaW4gcmFuZ2UoaSArIDEsIHRmX2lkZl9tYXRyaXguc2hhcGVbMF0pOg0KICAgICAgICBzaW0gPSBjb3NpbmVfc2ltaWxhcml0eSgNCiAgICAgICAgICAgIHRmX2lkZl9tYXRyaXgudG9hcnJheSgpW2ldLA0KICAgICAgICAgICAgdGZfaWRmX21hdHJpeC50b2FycmF5KClbal0NCiAgICAgICAgKQ0KICAgICAgICBwcmludChmIkNvc2luZSBzaW1pbGFyaXR5IGJldHdlZW4gZG9jdW1lbnRzIHtpKzF9IGFuZCB7aisxfToge3NpbTouM2Z9IikNCmBgYA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIyBURi1JREYgcmVwcmVzZW50YXRpb25zOiBjb3NpbmUgc2ltaWxhcml0eSAoaW50ZXJwcmV0YXRpb24gb2Ygb3V0cHV0cykuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KLSBJZiBzaW1pbGFyaXRpZXMgZGVjcmVhc2UgKG9yIGJlY29tZSAqMC4wMDAqKSwgaXQgdXN1YWxseSBtZWFucyB0aGF0IHRoZSBkb2N1bWVudHMgc2hhcmUgKmZldyBvciBubyBpbXBvcnRhbnQgdGVybXMqIGFmdGVyIFRGLUlERiByZXdlaWdodGluZy4NCg0KLSBURi1JREYgY2FuIHJlZHVjZSB0aGUgaW5mbHVlbmNlIG9mIHZlcnkgY29tbW9uIHRva2Vucywgc28gZXZlbiB3aGVuIHR3byBkb2N1bWVudHMgc2hhcmUgYSB3b3JkLCB0aGUgc2ltaWxhcml0eSBtYXkgYmVjb21lIHNtYWxsZXIgaWYgdGhhdCB3b3JkIGlzIG5vdCBpbmZvcm1hdGl2ZS4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIE92ZXJhbGw6IEJvVyB2cyBDb3NpbmUgc2ltaWxhcml0eSB2cyBURi1JREYNCg0KDQotICpCb1cgKyBjb3NpbmUqIG1lYXN1cmVzIG92ZXJsYXAgaW4gKnJhdyBjb3VudHMqLg0KDQotICpURi1JREYgKyBjb3NpbmUqIG1lYXN1cmVzIG92ZXJsYXAgaW4gKndlaWdodGVkIGltcG9ydGFuY2UqLg0KDQpJbiB0aGUgbmV4dCBzZWN0aW9uLCB3ZSBpbnRyb2R1Y2UgKipvbmUtaG90IHZlY3Rvcml6YXRpb24qKiwgYSBmb3VuZGF0aW9uYWwgcmVwcmVzZW50YXRpb24gZm9yIG5ldXJhbCBhbmQgZW1iZWRkaW5nLWJhc2VkIG1vZGVscy4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgT25lLWhvdCB2ZWN0b3JpemF0aW9uDQoNCk9uZS1ob3QgZW5jb2RpbmcgaXMgYSBzaW1wbGUgYW5kIHdpZGVseSB1c2VkIHRlY2huaXF1ZSBmb3IgcmVwcmVzZW50aW5nIGNhdGVnb3JpY2FsIGluZm9ybWF0aW9uIGluIG51bWVyaWNhbCBmb3JtLiBJbiB0aGlzIHJlcHJlc2VudGF0aW9uLCBlYWNoIHBvc3NpYmxlIGNhdGVnb3J5IGlzIGFzc29jaWF0ZWQgd2l0aCBhIHVuaXF1ZSBjb29yZGluYXRlIGluIGEgdmVjdG9yLiBFeGFjdGx5IG9uZSBlbnRyeSB0YWtlcyB0aGUgdmFsdWUgMSwgd2hpbGUgYWxsIHJlbWFpbmluZyBlbnRyaWVzIGFyZSBzZXQgdG8gMC4gIA0KDQpGb3IgYSB2b2NhYnVsYXJ5IG9mIHNpemUgXCggfFZ8IFwpLCBlYWNoIG9uZS1ob3QgdmVjdG9yIGxpZXMgaW4gXCggXG1hdGhiYntSfV57fFZ8fSBcKSBhbmQgY29udGFpbnMgZXhhY3RseSBvbmUgbm9uLXplcm8gY29tcG9uZW50Lg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMgQSBzaW1wbGUgaW50dWl0aW9uDQoNCkNvbnNpZGVyIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgZGVzY3JpYmluZyB0cmFmZmljIGNvbmRpdGlvbnMgd2l0aCB0aHJlZSBwb3NzaWJsZSB2YWx1ZXM6DQoNCi0gTG93LiAgDQoNCi0gTWVkaXVtLiAgDQoNCi0gSGlnaC4gIA0KDQpBIG9uZS1ob3QgcmVwcmVzZW50YXRpb24gY2FuIGJlIGRlZmluZWQgYXM6DQoNCg0KDQokJFxvdmVycmlnaHRhcnJvd3tcbWF0aGJme1x0ZXh0e2xvd319fSA9ICgxLDAsMCksXHF1YWQgXG92ZXJyaWdodGFycm93e1xtYXRoYmZ7XHRleHR7bWVkaXVtfX19ID0gKDAsMSwwKSxccXVhZCBcb3ZlcnJpZ2h0YXJyb3d7XG1hdGhiZntcdGV4dHtoaWdofX19ID0gKDAsMCwxKSQkDQoNCg0KRWFjaCB2ZWN0b3IgaGFzIGxlbmd0aCAzIGJlY2F1c2UgdGhlcmUgYXJlIHRocmVlIHBvc3NpYmxlIGNhdGVnb3JpZXMsIGFuZCBleGFjdGx5IG9uZSBwb3NpdGlvbiBpcyBhY3RpdmUgYXQgYSB0aW1lLiBHZW9tZXRyaWNhbGx5LCB0aGVzZSB2ZWN0b3JzIGNvcnJlc3BvbmQgdG8gdGhlIGNhbm9uaWNhbCBiYXNpcyBvZiBcKCBcbWF0aGJie1J9XjMgXCkuIFRoZXkgYXJlIG11dHVhbGx5IG9ydGhvZ29uYWwgYW5kIGVxdWlkaXN0YW50Lg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIE9uZS1ob3QgZW5jb2RpbmcgaW4gTkxQDQoNCkluIG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZywgdGhlIHNhbWUgaWRlYSBhcHBsaWVzIHRvIHRva2Vucy4gT25jZSBhIHZvY2FidWxhcnkgaGFzIGJlZW4gY29uc3RydWN0ZWQsIGVhY2ggd29yZCBpcyB0cmVhdGVkIGFzIGEgY2F0ZWdvcnkuICANCg0KQSB0b2tlbiBpcyByZXByZXNlbnRlZCBieSBhIHZlY3RvciBpbiBcKCBcbWF0aGJie1J9Xnt8Vnx9IFwpLCB3aGVyZSBvbmx5IHRoZSBjb29yZGluYXRlIGNvcnJlc3BvbmRpbmcgdG8gaXRzIHBvc2l0aW9uIGluIHRoZSB2b2NhYnVsYXJ5IGVxdWFscyAxLg0KDQpUaHVzLCBvbmUtaG90IGVuY29kaW5nIHRyYW5zZm9ybXMgZGlzY3JldGUgc3ltYm9saWMgdG9rZW5zIGludG8gbnVtZXJpY2FsIHZlY3RvcnMgd2l0aG91dCBpbnRyb2R1Y2luZyBhbnkgc2VtYW50aWMgc3RydWN0dXJlLg0KDQpUaGVzZSByZXByZXNlbnRhdGlvbnMgc2VydmUgYXMgYW4gaW50ZXJtZWRpYXRlIHN0ZXAgdG93YXJkIG1vcmUgYWR2YW5jZWQgZGlzdHJpYnV0ZWQgcmVwcmVzZW50YXRpb25zIHN1Y2ggYXMgd29yZCBlbWJlZGRpbmdzLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIENvbnN0cnVjdGluZyBvbmUtaG90IHZlY3RvcnMgc3RlcCBieSBzdGVwDQoNClRvIGlsbHVzdHJhdGUgdGhlIHByb2Nlc3MsIHdlIHdvcmsgd2l0aCBhIHNob3J0IHNlbnRlbmNlLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCAxOiBEZWZpbmUgdGhlIGlucHV0IHRleHQuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCmBgYHtweXRob259DQpzZW50ZW5jZSA9IFsiU3R1ZGVudHMgc3R1ZHkgbWFjaGluZSBsZWFybmluZyBtZXRob2RzIl0NCmNvcnB1cyA9IHBkLlNlcmllcyhzZW50ZW5jZSkNCmNvcnB1cw0KYGBgDQoNClRoZSBjb3JwdXMgY29udGFpbnMgYSBzaW5nbGUgZG9jdW1lbnQuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBTdGVwIDI6IEFwcGx5IGJhc2ljIHByZXByb2Nlc3NpbmcuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCldlIGFwcGx5IGNsZWFuaW5nLCBzdG9wd29yZCByZW1vdmFsLCBhbmQgbGVtbWF0aXphdGlvbjoNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRX0NCmRlZiBjbGVhbl9hbmRfbGVtbWF0aXplKHRleHQpOg0KICAgIHN0b3AgPSBzZXQoc3RvcHdvcmRzLndvcmRzKCJlbmdsaXNoIikpDQogICAgbGVtbWF0aXplciA9IFdvcmROZXRMZW1tYXRpemVyKCkNCiAgICANCiAgICB0b2tlbnMgPSByZS5zdWIociJbXmEtekEtWl0iLCAiICIsIHRleHQpLmxvd2VyKCkuc3BsaXQoKQ0KICAgIHRva2VucyA9IFtsZW1tYXRpemVyLmxlbW1hdGl6ZSh0b2spIGZvciB0b2sgaW4gdG9rZW5zIGlmIHRvayBub3QgaW4gc3RvcF0NCiAgICANCiAgICByZXR1cm4gIiAiLmpvaW4odG9rZW5zKQ0KDQpwcmVwcm9jZXNzZWRfY29ycHVzID0gY29ycHVzLmFwcGx5KGNsZWFuX2FuZF9sZW1tYXRpemUpDQpwcmVwcm9jZXNzZWRfY29ycHVzDQpgYGANCg0KVGhlIG91dHB1dCBpczogDQoNCmBgYHtweXRob24sIGVjaG89RkFMU0V9DQpkZWYgY2xlYW5fYW5kX2xlbW1hdGl6ZSh0ZXh0KToNCiAgICBzdG9wID0gc2V0KHN0b3B3b3Jkcy53b3JkcygiZW5nbGlzaCIpKQ0KICAgIGxlbW1hdGl6ZXIgPSBXb3JkTmV0TGVtbWF0aXplcigpDQogICAgDQogICAgdG9rZW5zID0gcmUuc3ViKHIiW15hLXpBLVpdIiwgIiAiLCB0ZXh0KS5sb3dlcigpLnNwbGl0KCkNCiAgICB0b2tlbnMgPSBbbGVtbWF0aXplci5sZW1tYXRpemUodG9rKSBmb3IgdG9rIGluIHRva2VucyBpZiB0b2sgbm90IGluIHN0b3BdDQogICAgDQogICAgcmV0dXJuICIgIi5qb2luKHRva2VucykNCg0KcHJlcHJvY2Vzc2VkX2NvcnB1cyA9IGNvcnB1cy5hcHBseShjbGVhbl9hbmRfbGVtbWF0aXplKQ0KcHJlcHJvY2Vzc2VkX2NvcnB1cw0KYGBgDQoNCg0KDQpUaGUgc2VudGVuY2UgaGFzIGJlZW4gcmVkdWNlZCB0byBpdHMgY29yZSBsZXhpY2FsIGNvbXBvbmVudHM6DQoNCi0gYFN0dWRlbnRzYCDihpIgYHN0dWRlbnRgLg0KDQotIGBtZXRob2RzYCDihpIgYG1ldGhvZGAuIA0KDQotIFN0b3B3b3JkcyByZW1vdmVkLiAgDQoNClRoaXMgZW5zdXJlcyBhIGNsZWFuIGFuZCBjb21wYWN0IHZvY2FidWxhcnkuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBTdGVwIDM6IEJ1aWxkIHRoZSB2b2NhYnVsYXJ5LiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQp2b2NhYiA9IGxpc3Qoc2V0KHByZXByb2Nlc3NlZF9jb3JwdXNbMF0uc3BsaXQoKSkpDQpwcmludCh2b2NhYikNCmBgYA0KDQpUaGUgb3V0cHV0IGlzOiANCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnZvY2FiID0gbGlzdChzZXQocHJlcHJvY2Vzc2VkX2NvcnB1c1swXS5zcGxpdCgpKSkNCnByaW50KHZvY2FiKQ0KYGBgDQoNCg0KRWFjaCB1bmlxdWUgdG9rZW4gbm93IGNvcnJlc3BvbmRzIHRvIG9uZSBkaW1lbnNpb24gaW4gdGhlIHZlY3RvciBzcGFjZS4gIFNpbmNlIHRoZXJlIGFyZSA1IGRpc3RpbmN0IHRva2VucywgdGhlIGVtYmVkZGluZyBzcGFjZSBpcyBcKCBcbWF0aGJie1J9XjUgXCkuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBTdGVwIDQ6IEFzc2lnbiBpbmRpY2VzIHRvIHZvY2FidWxhcnkgdGVybXMuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQpwb3NpdGlvbiA9IHt0b2tlbjogaWR4IGZvciBpZHgsIHRva2VuIGluIGVudW1lcmF0ZSh2b2NhYil9DQpwcmludChwb3NpdGlvbikNCmBgYA0KDQpUaGUgb3V0cHV0IGlzOiANCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnBvc2l0aW9uID0ge3Rva2VuOiBpZHggZm9yIGlkeCwgdG9rZW4gaW4gZW51bWVyYXRlKHZvY2FiKX0NCnByaW50KHBvc2l0aW9uKQ0KYGBgDQoNCg0KVGhpcyBkaWN0aW9uYXJ5IGRlZmluZXMgdGhlIGNvb3JkaW5hdGUgc3lzdGVtIG9mIHRoZSBzcGFjZTogKmVhY2ggdG9rZW4gaXMgYXNzaWduZWQgYSBmaXhlZCBpbmRleCouIFRoaXMgbWFwcGluZyBzcGVjaWZpZXMgd2hpY2ggY29vcmRpbmF0ZSBpbiB0aGUgdmVjdG9yIGNvcnJlc3BvbmRzIHRvIGVhY2ggdG9rZW4uDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBTdGVwIDU6IEluaXRpYWxpemUgdGhlIG9uZS1ob3QgbWF0cml4LiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQpvbmVfaG90X21hdHJpeCA9IG5wLnplcm9zKChsZW4ocHJlcHJvY2Vzc2VkX2NvcnB1c1swXS5zcGxpdCgpKSwgbGVuKHZvY2FiKSkpDQpvbmVfaG90X21hdHJpeC5zaGFwZQ0KYGBgDQoNClRoZSBvdXRwdXQgaXM6IA0KDQoNCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCm9uZV9ob3RfbWF0cml4ID0gbnAuemVyb3MoKGxlbihwcmVwcm9jZXNzZWRfY29ycHVzWzBdLnNwbGl0KCkpLCBsZW4odm9jYWIpKSkNCm9uZV9ob3RfbWF0cml4LnNoYXBlDQpgYGANCg0KDQpJbiB0aGlzIGNhc2UsIHRoZSBtYXRyaXggaGFzICA1IHJvd3MgKG9uZSByb3cgcGVyIHRva2VuIGluIHRoZSBzZW50ZW5jZSkgYW5kIDUgY29sdW1ucyAob25lIGNvbHVtbiBwZXIgdm9jYWJ1bGFyeSB0ZXJtKS4gDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFN0ZXAgNjogUG9wdWxhdGUgdGhlIG9uZS1ob3QgdmVjdG9ycy4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KDQpgYGB7cHl0aG9ufQ0KZm9yIGksIHRva2VuIGluIGVudW1lcmF0ZShwcmVwcm9jZXNzZWRfY29ycHVzWzBdLnNwbGl0KCkpOg0KICAgIG9uZV9ob3RfbWF0cml4W2ldW3Bvc2l0aW9uW3Rva2VuXV0gPSAxDQpgYGANCg0KDQpGb3IgZWFjaCB0b2tlbjoNCg0KLSBJZGVudGlmeSBpdHMgaW5kZXggaW4gdGhlIHZvY2FidWxhcnkuICANCg0KLSBTZXQgdGhlIGNvcnJlc3BvbmRpbmcgY29sdW1uIHRvIDEuICANCg0KRWFjaCByb3cgbm93IGJlY29tZXMgYSBjYW5vbmljYWwgYmFzaXMgdmVjdG9yLg0KDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlcCA3OiBJbnNwZWN0IHRoZSByZXN1bHQuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFfQ0Kb25lX2hvdF9tYXRyaXgNCmBgYA0KDQoNClRoZSBvdXRwdXQgaXM6IA0KDQpgYGB7cHl0aG9uLCBlY2hvPUZBTFNFfQ0Kb25lX2hvdF9tYXRyaXgNCmBgYA0KDQpJbiB0aGlzIGNhc2U6IA0KDQotIEVhY2ggcm93IGNvcnJlc3BvbmRzIHRvIG9uZSB0b2tlbi4NCg0KLSBFYWNoIHJvdyBjb250YWlucyBleGFjdGx5IG9uZSAxLg0KDQotIEFsbCB2ZWN0b3JzIGFyZSBvcnRob2dvbmFsOg0KDQokJA0KXG92ZXJyaWdodGFycm93e3dfaX0gXGNkb3QgXG92ZXJyaWdodGFycm93e3dfan0gPSAwIFxxdWFkIFx0ZXh0e2ZvciB9IGkgXG5lcSBqLg0KJCQNCg0KTm8gc2VtYW50aWMgc2ltaWxhcml0eSBpcyBlbmNvZGVkIGFuZCB0aGUgcmVwcmVzZW50YXRpb24gb25seSBjYXB0dXJlcyBpZGVudGl0eSAobm8gaW5mb3JtYXRpb24gYWJvdXQgKmZyZXF1ZW5jeSosICpyZWxhdGl2ZSBpbXBvcnRhbmNlKiwgb3IgKnNlbWFudGljIHNpbWlsYXJpdHkqIGlzIGNhcHR1cmVkKS4NCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBLZXkgb2JzZXJ2YXRpb24uICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCg0KV2hpbGUgb25lLWhvdCB2ZWN0b3JzIHByb3ZpZGUgYSBjbGVhciBhbmQgdW5hbWJpZ3VvdXMgbnVtZXJpY2FsIGVuY29kaW5nLCB0aGV5IGV4aGliaXQgdHdvIG1ham9yIGxpbWl0YXRpb25zOg0KDQoxLiAqKkhpZ2ggZGltZW5zaW9uYWxpdHkqKi4gICAgVGhlIHZlY3RvciBsZW5ndGggZ3Jvd3MgbGluZWFybHkgd2l0aCB2b2NhYnVsYXJ5IHNpemUuDQoNCjIuICoqTm8gc2VtYW50aWMgc3RydWN0dXJlKiouICAgICBBbGwgZGlzdGluY3Qgd29yZHMgYXJlIGVxdWlkaXN0YW50Og0KICAgDQogICAkJCAgIFx8XG92ZXJyaWdodGFycm93e3dfaX0gXDstXDsgXG92ZXJyaWdodGFycm93e3dfan1cfF8yIFxxdWFkID1ccXVhZCBcc3FydHsyfSAgIFxxdWFkIFx0ZXh0e2Zvcn1cLCBpXCwgXG5lcSBcLGouICAgJCQNCg0KVGhlc2UgbGltaXRhdGlvbnMgbW90aXZhdGUgdGhlIHRyYW5zaXRpb24gdG8gZGlzdHJpYnV0ZWQgcmVwcmVzZW50YXRpb25zLCB3aGVyZSBtZWFuaW5nIGVtZXJnZXMgZnJvbSBnZW9tZXRyeSByYXRoZXIgdGhhbiBwb3NpdGlvbiBhbG9uZS4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KDQojIFN1bW1hcnkNCg0KSW4gdGhpcyBjaGFwdGVyLCB3ZSBpbnRyb2R1Y2VkIHRoZSBmdW5kYW1lbnRhbCBtYXRoZW1hdGljYWwgaWRlYXMgYmVoaW5kIHJlcHJlc2VudGluZyB0ZXh0IGFzIG51bWVyaWNhbCBvYmplY3RzLiBTdGFydGluZyBmcm9tIHNpbXBsZSBoZXVyaXN0aWNzLCB3ZSBleHBsb3JlZCBob3cgdGV4dHVhbCBkYXRhIGNhbiBiZSBtYXBwZWQgaW50byB2ZWN0b3JzIGFuZCBtYXRyaWNlcywgZW5hYmxpbmcgdGhlIHVzZSBvZiBsaW5lYXIgYWxnZWJyYSB0ZWNobmlxdWVzIGZvciBhbmFseXNpcy4NCg0KV2UgZmlyc3QgZXhhbWluZWQgdGhlICpCYWctb2YtV29yZHMgKEJvVykqIHJlcHJlc2VudGF0aW9uIGFuZCBpbXBsZW1lbnRlZCBpdCB1c2luZyB0aGUgYENvdW50VmVjdG9yaXplcmAgQVBJLiBXaGlsZSB0aGlzIGFwcHJvYWNoIHByb3ZpZGVzIGFuIGludHVpdGl2ZSBhbmQgZWZmZWN0aXZlIHdheSB0byBlbmNvZGUgdGV4dCBiYXNlZCBvbiB0ZXJtIGZyZXF1ZW5jaWVzLCB3ZSBhbHNvIGlkZW50aWZpZWQgaXRzIG1haW4gbGltaXRhdGlvbnPigJRtb3N0IG5vdGFibHksIGl0cyB0ZW5kZW5jeSB0byBvdmVyZW1waGFzaXplIHZlcnkgZnJlcXVlbnQgdGVybXMgYW5kIGlnbm9yZSB0aGUgcmVsYXRpdmUgaW1wb3J0YW5jZSBvZiByYXJlciBidXQgcG90ZW50aWFsbHkgaW5mb3JtYXRpdmUgd29yZHMuDQoNClRvIGFkZHJlc3MgdGhlc2UgaXNzdWVzLCB3ZSBpbnRyb2R1Y2VkICpURuKAk0lERiB2ZWN0b3JpemF0aW9uKiwgd2hpY2ggcmV3ZWlnaHRzIHRlcm0gZnJlcXVlbmNpZXMgYnkgaW5jb3Jwb3JhdGluZyBnbG9iYWwgaW5mb3JtYXRpb24gYWJvdXQgdGVybSBkaXN0cmlidXRpb24gYWNyb3NzIHRoZSBjb3JwdXMuIFRoaXMgYWRqdXN0bWVudCBoZWxwcyBiYWxhbmNlIGxvY2FsIHJlbGV2YW5jZSB3aXRoaW4gZG9jdW1lbnRzIGFnYWluc3QgZ2xvYmFsIHByZXZhbGVuY2UgaW4gdGhlIGRhdGFzZXQuIERlc3BpdGUgdGhpcyBpbXByb3ZlbWVudCwgYm90aCBCb1cgYW5kIFRG4oCTSURGIHJlbWFpbiBmdW5kYW1lbnRhbGx5ICpsZXhpY2FsKiBtZXRob2RzOiB0aGV5IHJlbHkgb24gc3VyZmFjZS1sZXZlbCB3b3JkIG9jY3VycmVuY2VzIGFuZCBkbyBub3QgYWNjb3VudCBmb3Igc2VtYW50aWMgbWVhbmluZywgd29yZCBvcmRlciwgb3IgY29udGV4dHVhbCByZWxhdGlvbnNoaXBzLg0KDQpCdWlsZGluZyBvbiB0aGVzZSB2ZWN0b3IgcmVwcmVzZW50YXRpb25zLCB3ZSB0aGVuIGV4cGxvcmVkIGhvdyBkb2N1bWVudCBzaW1pbGFyaXR5IGNhbiBiZSBxdWFudGlmaWVkIHVzaW5nICpjb3NpbmUgc2ltaWxhcml0eSosIGludGVycHJldGluZyBkb2N1bWVudHMgYXMgcG9pbnRzIGluIGEgaGlnaC1kaW1lbnNpb25hbCBzcGFjZSBhbmQgbWVhc3VyaW5nIHRoZSBhbmdsZXMgYmV0d2VlbiB0aGVpciBjb3JyZXNwb25kaW5nIHZlY3RvcnMuIFRoaXMgcHJvdmlkZWQgYSBwcmFjdGljYWwgbWVjaGFuaXNtIGZvciBjb21wYXJpbmcgZG9jdW1lbnRzIGFuZCBzZXJ2ZWQgYXMgdGhlIGZvdW5kYXRpb24gZm9yIHNpbXBsZSBhcHBsaWNhdGlvbnMgc3VjaCBhcyByZXRyaWV2YWwtYmFzZWQgY2hhdGJvdHMuDQoNCkZpbmFsbHksIHdlIGRpc2N1c3NlZCAqb25lLWhvdCB2ZWN0b3JpemF0aW9uKiwgYSBzcGFyc2UgZW5jb2Rpbmcgc2NoZW1lIGNvbW1vbmx5IHVzZWQgdG8gcmVwcmVzZW50IGluZGl2aWR1YWwgdG9rZW5zIGFzIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gQWx0aG91Z2ggc2ltcGxlLCB0aGlzIHJlcHJlc2VudGF0aW9uIHBsYXlzIGFuIGltcG9ydGFudCByb2xlIGFzIGEgY29uY2VwdHVhbCBidWlsZGluZyBibG9jayBmb3IgbW9yZSBhZHZhbmNlZCBtb2RlbHMuDQoNCk92ZXJhbGwsIHRoZSBtZXRob2RzIGNvdmVyZWQgaW4gdGhpcyBjaGFwdGVyIGFyZSBtb3N0IGVmZmVjdGl2ZSBpbiBzZXR0aW5ncyB3aGVyZSB0aGUgdm9jYWJ1bGFyeSBzaXplIGlzIG1vZGVyYXRlIGFuZCBsZXhpY2FsIG92ZXJsYXAgYmV0d2VlbiBkb2N1bWVudHMgaXMgbWVhbmluZ2Z1bC4gQXMgdm9jYWJ1bGFyaWVzIGdyb3cgbGFyZ2VyIG9yIHNlbWFudGljIHJlbGF0aW9uc2hpcHMgYmVjb21lIG1vcmUgaW1wb3J0YW50LCB0aGVzZSByZXByZXNlbnRhdGlvbnMgYmVjb21lIGxlc3MgYWRlcXVhdGUuDQoNCldpdGggdGhpcyBzeW50YWN0aWMgZm91bmRhdGlvbiBpbiBwbGFjZSwgdGhlIG5leHQgY2hhcHRlciBtb3ZlcyBiZXlvbmQgd29yZCBjb3VudHMgYW5kIGxleGljYWwgd2VpZ2h0aW5nLiBXZSB3aWxsIGV4cGxvcmUgYXBwcm9hY2hlcyB0aGF0IGV4cGxpY2l0bHkgbW9kZWwgKnNlbWFudGljIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB3b3JkcyosIGJlZ2lubmluZyB3aXRoIGRpc3RyaWJ1dGVkIHJlcHJlc2VudGF0aW9ucyBzdWNoIGFzICpXb3JkMlZlYyouDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIEFwcGxpZWQgYWN0aXZpdHk6IGZyb20gdGV4dCB0byB2ZWN0b3ItYmFzZWQgc2ltaWxhcml0eQ0KDQpUaGlzIGFjdGl2aXR5IGlzIGRlc2lnbmVkIHRvIGludGVncmF0ZSBhbmQgYXBwbHkgdGhlIG51bWVyaWNhbCB0ZXh0IHJlcHJlc2VudGF0aW9uIHRlY2huaXF1ZXMgaW50cm9kdWNlZCBpbiB0aGlzIGNoYXB0ZXIuICANClRoZSByZWFkZXIgd2lsbCB0cmFuc2Zvcm0gYSBzbWFsbCB0ZXh0IGNvcnB1cyBpbnRvIHZlY3RvciByZXByZXNlbnRhdGlvbnMgYW5kIGFuYWx5emUgZG9jdW1lbnQgc2ltaWxhcml0eSB1c2luZyBsaW5lYXIgYWxnZWJyYSBjb25jZXB0cy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgT2JqZWN0aXZlDQoNClRvIGJ1aWxkIGEgZnVsbHkgcmVwcm9kdWNpYmxlIHBpcGVsaW5lIHRoYXQgY29udmVydHMgcmF3IHRleHQgaW50byBudW1lcmljYWwgdmVjdG9ycyB1c2luZyBCYWctb2YtV29yZHMgYW5kIFRG4oCTSURGIHJlcHJlc2VudGF0aW9ucywgYW5kIHRvIGFuYWx5emUgZG9jdW1lbnQgc2ltaWxhcml0eSB1c2luZyBjb3NpbmUgc2ltaWxhcml0eS4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuIFNlbGVjdCBhICpzbWFsbCBjb3JwdXMgb2YgdGV4dCosIHN1Y2ggYXM6DQoNCiAgIC0gU2hvcnQgcGFyYWdyYXBocyBmcm9tIG5ld3MgYXJ0aWNsZXMsDQogICANCiAgIC0gQWJzdHJhY3RzIG9mIHNjaWVudGlmaWMgcGFwZXJzLCBvciAgDQogICANCiAgIC0gQnJpZWYgZGVzY3JpcHRpb25zIG9mIHByb2R1Y3RzLCBtb3ZpZXMsIG9yIGJvb2tzLg0KDQozLiBUaGUgY29ycHVzIG11c3QgY29udGFpbiAqYXQgbGVhc3QgdGhyZWUgZG9jdW1lbnRzKiwgZWFjaCBjb25zaXN0aW5nIG9mIG9uZSBvciB0d28gc2VudGVuY2VzLg0KDQo0LiBDcmVhdGUgYW4gKlIgTWFya2Rvd24gKGAuUm1kYCkqIGRvY3VtZW50IHRoYXQgY29tcGlsZXMgc3VjY2Vzc2Z1bGx5IHRvICpIVE1MKiAob3IgUERGKS4NCg0KNS4gVGhlIGRvY3VtZW50IG11c3QgaW5jbHVkZSBib3RoOg0KDQogICAtIFRoZSAqY29kZSosIGFuZCAgDQogICANCiAgIC0gVGhlICpyZXN1bHRpbmcgb3V0cHV0KiAocHJpbnRlZCBtYXRyaWNlcywgdGFibGVzLCBvciBudW1lcmljYWwgdmFsdWVzKS4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgUmVxdWlyZWQgU2VjdGlvbnMNCg0KOjo6c2FuZ3JpYTMNCiMjIyMgMS4gQ29ycHVzIERlc2NyaXB0aW9uLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpCcmllZmx5IGRlc2NyaWJlIHRoZSBzZWxlY3RlZCBjb3JwdXMgYW5kIGl0cyBjb250ZXh0LiAgTGlzdCB0aGUgZG9jdW1lbnRzIGV4cGxpY2l0bHkgYW5kIGV4cGxhaW4gd2h5IHRoaXMgY29ycHVzIGlzIGFwcHJvcHJpYXRlIGZvciBzaW1pbGFyaXR5IGFuYWx5c2lzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgMi4gUHJlcHJvY2Vzc2luZy4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KQXBwbHkgYmFzaWMgcHJlcHJvY2Vzc2luZyBzdGVwcywgaW5jbHVkaW5nOg0KDQotIExvd2VyY2FzaW5nLCANCg0KLSBSZW1vdmFsIG9mIHB1bmN0dWF0aW9uLA0KDQotIFN0b3B3b3JkIHJlbW92YWwsIGFuZCANCg0KLSBMZW1tYXRpemF0aW9uICpvciogc3RlbW1pbmcuDQoNClNob3cgdGhlIHByb2Nlc3NlZCB2ZXJzaW9uIG9mIGVhY2ggZG9jdW1lbnQuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyAzLiBCYWctb2YtV29yZHMgUmVwcmVzZW50YXRpb24uICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCkNvbnN0cnVjdCBhIEJhZy1vZi1Xb3JkcyByZXByZXNlbnRhdGlvbiBvZiB0aGUgY29ycHVzIHVzaW5nOg0KDQotIEVpdGhlciBhIG1hbnVhbCBpbXBsZW1lbnRhdGlvbiwgKm9yKiAgDQotIEEgdmVjdG9yaXphdGlvbiB0b29sIHN1Y2ggYXMgYENvdW50VmVjdG9yaXplcmAuDQoNClJlcG9ydDoNCg0KLSBUaGUgbGVhcm5lZCB2b2NhYnVsYXJ5LCBhbmQgIA0KDQotIFRoZSBkb2N1bWVudOKAk3Rlcm0gbWF0cml4Lg0KDQpCcmllZmx5IGludGVycHJldCB0aGUgc3BhcnNpdHkgYW5kIGRpbWVuc2lvbmFsaXR5IG9mIHRoZSByZXN1bHRpbmcgbWF0cml4Lg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgNC4gVEbigJNJREYgUmVwcmVzZW50YXRpb24uICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNClVzaW5nIHRoZSBzYW1lIGNvcnB1cywgY29tcHV0ZSBURi1JREYgdmVjdG9ycy4NCg0KQ29tcGFyZSB0aGUgVEbigJNJREYgbWF0cml4IHdpdGggdGhlIEJvVyBtYXRyaXggYnkgZGlzY3Vzc2luZzoNCg0KLSBEaWZmZXJlbmNlcyBpbiBudW1lcmljYWwgdmFsdWVzLCBhbmQgIA0KDQotIEhvdyBURuKAk0lERiByZXdlaWdodHMgZnJlcXVlbnQgYW5kIHJhcmUgdGVybXMuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyA1LiBDb3NpbmUgU2ltaWxhcml0eSBBbmFseXNpcy4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KQ29tcHV0ZSBwYWlyd2lzZSBjb3NpbmUgc2ltaWxhcml0eSBiZXR3ZWVuIGFsbCBkb2N1bWVudHMgdXNpbmc6DQoNCi0gQm9XIHZlY3RvcnMsIGFuZCANCg0KLSBURuKAk0lERiB2ZWN0b3JzLg0KDQpQcmVzZW50IHRoZSByZXN1bHRzIGNsZWFybHkgYW5kIGlkZW50aWZ5Og0KDQotIFRoZSBtb3N0IHNpbWlsYXIgZG9jdW1lbnQgcGFpciwgYW5kICANCg0KLSBUaGUgbGVhc3Qgc2ltaWxhciBkb2N1bWVudCBwYWlyLg0KDQpFeHBsYWluIGFueSBkaWZmZXJlbmNlcyBvYnNlcnZlZCBiZXR3ZWVuIHRoZSB0d28gcmVwcmVzZW50YXRpb25zLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgNi4gT25lLWhvdCBSZXByZXNlbnRhdGlvbiAoQ29uY2VwdHVhbCkuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNClNlbGVjdCAqKnRocmVlIHRva2VucyoqIGZyb20gdGhlIHZvY2FidWxhcnkgYW5kOg0KDQotIENvbnN0cnVjdCB0aGVpciBvbmUtaG90IHZlY3RvcnMsIGFuZCAtIEV4cGxhaW4gd2h5IG9uZS1ob3QgcmVwcmVzZW50YXRpb25zIGFyZSB1bnN1aXRhYmxlIGZvciBtZWFzdXJpbmcgc2VtYW50aWMgc2ltaWxhcml0eSBkaXJlY3RseS4NCg0KVGhpcyBzZWN0aW9uIG1heSBiZSBwcmVzZW50ZWQgY29uY2VwdHVhbGx5IG9yIHdpdGggYSBzbWFsbCBudW1lcmljYWwgZXhhbXBsZS4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIDcuIFN1bW1hcnkgYW5kIFJlZmxlY3Rpb24uICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCldyaXRlIGEgY29uY2lzZSByZWZsZWN0aW9uICg24oCTMTAgbGluZXMpIGFkZHJlc3Npbmc6DQoNCi0gSG93IHZlY3Rvcml6YXRpb24gZW5hYmxlcyBtYXRoZW1hdGljYWwgY29tcGFyaXNvbiBvZiB0ZXh0LCAgDQoNCi0gVGhlIHJvbGUgb2Ygd2VpZ2h0aW5nIHNjaGVtZXMgc3VjaCBhcyBURuKAk0lERiwgYW5kIA0KDQotIFRoZSBsaW1pdGF0aW9ucyBvZiBwdXJlbHkgbGV4aWNhbCByZXByZXNlbnRhdGlvbnMuDQo6OjoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMgUmVwcm9kdWNpYmlsaXR5IFJlcXVpcmVtZW50DQoNCi0gVGhlIFIgTWFya2Rvd24gZG9jdW1lbnQgbXVzdCBiZSBmdWxseSByZXByb2R1Y2libGUuIA0KDQotIEFsbCBjb2RlIGNodW5rcyBtdXN0IGV4ZWN1dGUgd2l0aG91dCBlcnJvcnMgYW5kIHJlZ2VuZXJhdGUgdGhlIHJlcG9ydGVkIG91dHB1dHMgd2hlbiB0aGUgZG9jdW1lbnQgaXMgY29tcGlsZWQuIA0KDQotIEFsbCByYW5kb20gc2VlZHMgKGlmIGFwcGxpY2FibGUpIG11c3QgYmUgc2V0IHRvIGVuc3VyZSBkZXRlcm1pbmlzdGljIHJlc3VsdHMuDQoNCi0gQWxsIGxpYnJhcnkgdmVyc2lvbnMgdXNlZCBzaG91bGQgYmUgY2xlYXJseSByZXBvcnRlZC4NCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQoNCjwhLS0gQ2Fww610dWxvIEJpYmxpb2dyYWbDrWEtLT4NCg0KDQojIFJlZmVyZW5jZXMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCiAgDQoNCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KJm5ic3A7DQoNCg0KJm5ic3A7DQo8Y2VudGVyPg0Kfn5+DQpJZiB5b3UgZm91bmQgYW55IEVSUk9SUyBvciBoYXZlIFNVR0dFU1RJT05TLCBwbGVhc2UgcmVwb3J0IHRoZW0gdG8gbXkgZW1haWwuIFRoYW5rcy4gIA0Kfn5+DQo8L2NlbnRlcj4NCg0KDQo=