hllinas2023

1 Introduction

1.0.1 Preliminars

To understand that initial step, we’ll study how vocabularies are built and how language is represented symbolically. This includes lexicons, phonemes, graphemes, morphemes, tokenization strategies, and word normalization techniques.

Before applying any model to a text dataset, it is essential to convert language into a format that machines can understand. In this chapter, we’ll explore the core elements of that transformation (from raw text to structured tokens).

The Figure 1.1 illustrates the progression from raw text to structured tokens, highlighting key linguistic units such as lexicons, phonemes, graphemes, morphemes, and tokenization strategies used in natural language processing.

Core Components of Linguistic Representation. Source: Created by the author with ChatGPT (OpenAI)

Figure 1.1: Core Components of Linguistic Representation. Source: Created by the author with ChatGPT (OpenAI)

1.0.2 Motivation: Transformers architecture

As a motivating example, consider the Transformer architecture (Vaswani et al., 2017), widely used in modern NLP. See Figure 1.2.

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

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

The process begins with tokenization (Inputs, shown at the lower left of the figure), where input sentences are broken down into basic units (usually subwords or word-pieces). These units then flow through the entire model. To understand that initial step, we’ll study how vocabularies are built and how language is represented symbolically.

Understanding the first stage.

To understand this initial stage, it is necessary to examine how vocabularies are constructed and how language is represented in symbolic form. This leads us to fundamental concepts such as lexicons, phonemes, graphemes, morphemes, tokenization methods, and normalization procedures, which together define how raw text is transformed into model-ready inputs.

1.0.3 Technical requirements

All code examples used in this section are available in the following GitHub repository:

https://github.com/PacktPublishing/Hands-On-Python-Natural-Language-Processing/tree/master/Chapter03.

2 Lexicons

Definition.

  • A lexicon is the set of words used in a language or within a specific domain.

  • In practice, it functions like a dictionary that defines which terms are meaningful in a given context.

Examples.

The following examples illustrate how lexicons vary depending on the context or domain:

  • General language: house, run, happy

  • Medicine: diagnosis, dosage, symptom

  • Technology: algorithm, dataset, model

Note.

  • In natural language processing, the lexicon defines the vocabulary a model can recognize and process.

  • Words not included in the lexicon are typically ignored, replaced, or decomposed into smaller units.

  • For this reason, building or learning a lexicon is a foundational step before tokenization and modeling.

  • The Figure 2.1 clarifies the conceptual differences between closely related terms that are often used interchangeably in NLP and linguistics. It distinguishes between lexicon, vocabulary, and word, highlighting their scope, usage, and role in language representation.

Lexicon vs vocabulary vs word. Source: Created by the author with ChatGPT (OpenAI)

Figure 2.1: Lexicon vs vocabulary vs word. Source: Created by the author with ChatGPT (OpenAI)

3 Phonomes, graphemes, and morphemes

Before building a vocabulary, it is useful to understand three basic linguistic units: phonemes, graphemes, and morphemes. These concepts provide the linguistic foundation for the tokenization and representation methods used in NLP systems.

Phonemes.

They are the smallest sound units that distinguish meaning in spoken language. Examples:

  1. English: the sounds /k/, /æ/, and /t/ form the word cat.

  2. English: the sounds /f/, /uː/, and /d/ form the word food.

  3. French: the sounds /ʃ/, /a/, and /t/ form the word chat.

  4. Spanish: the sounds /g/, /a/, and /t/, /o/ form the word gato.

Phonemes. Source: [Charge Mommy Books](https://chargemommybooks.com/reading/phonemic-awareness)

Figure 3.1: Phonemes. Source: Charge Mommy Books

Graphemes.

They are letters or letter groups that represent phonemes in written language. Examples:

  1. In spoon, the graphemes s, p, oo, and n represent four phonemes.

  2. In sheet, the graphemes sh, ee, and t represent three phonemes.

  3. In ship, the graphemes sh, i, and p represent three phonemes.

  4. In food, the grapheme oo represents a single phoneme (within a word that contains multiple phonemes).

  5. In chat, the grapheme ch represents a single phoneme.

  6. In Spanish, the word gato is composed of the following graphemes: g, a, t, and o. Each grapheme corresponds to a written unit that represents a phoneme in the word.

Graphemes. Source: [ReadingDoctor](https://www.readingdoctor.com.au/phonemes-graphemes-letters-word-burger)

Figure 3.2: Graphemes. Source: ReadingDoctor

Morphemes.

They are the smallest units that carry meaning. Examples:

  1. The word unhappiness can be decomposed into three morphemes:

    • un- (a bound morpheme signifying not).

    • happy (the root morpheme).

    • -ness (a free morpheme signifying state or quality).

  2. The word teacher consists of two morphemes:

    • teach (root).

    • -er (a person who performs an action).

  3. The word reusable can be decomposed into:

    • re- (again).

    • use (root).

    • -able (can be done).

  4. The word imported can be decomposed into:

    • im- (prefix).

    • port (root).

    • -ed (sufix).

Morphemes. Source: [Literacy Learn](https://literacylearn.com/about-phonemes-graphemes-morphemes/)

Figure 3.3: Morphemes. Source: Literacy Learn

4 Tokenization

4.0.1 Token and tokenization

To construct a vocabulary, text must first be divided into smaller units called tokens.

This process, known as tokenization, consists of segmenting sentences or documents into meaningful elements that can be processed by a model.

In most cases, tokens correspond to words or numbers, although punctuation symbols and other textual elements may also be treated as tokens depending on the application. The Figure 4.1 is a visual representation of tokens generated by gpt-4o on Tiktokenizer:

Visual representation of tokens generated by gpt-4o on [Tiktokenizer](https://tiktokenizer.vercel.app/)

Figure 4.1: Visual representation of tokens generated by gpt-4o on Tiktokenizer

Example.

A simple example illustrates this idea:

sentence = "Machine learning improves decision making"
sentence.split()
## ['Machine', 'learning', 'improves', 'decision', 'making']

This basic splitting operation separates the sentence into individual word tokens. While this approach is intuitive, real-world tokenization is often more complex and requires more advanced strategies.

4.0.2 Issues with tokenization

Real-world text presents multiple challenges that cannot be adequately handled by simple tokenization rules such as whitespace splitting.
Figure 4.2 provides a high-level overview of some common tokenization issues encountered in natural language processing, including apostrophes, contractions, multi-word expressions, punctuation, non-lexical tokens, and social media artifacts.

These issues are introduced here for conceptual orientation. Each case will be examined in detail in the following subsections, together with illustrative examples and discussion of why more sophisticated tokenization strategies are required.

IssueS with tokenization.  Source: Created by the author with ChatGPT (OpenAI)

Figure 4.2: IssueS with tokenization. Source: Created by the author with ChatGPT (OpenAI)

Apostrophes and possessives

Simple tokenization methods often struggle with common language patterns. Consider the following sentence:

sentence = "Machine learning's impact is growing"
sentence.split()
## ['Machine', "learning's", 'impact', 'is', 'growing']

Here, the tokenizer cannot determine whether the correct token should be learning, learnings, or learning's. Apostrophes introduce ambiguity that basic splitting rules cannot resolve.

Contractions

Contractions present a similar challenge. For example:

sentence = "We'll apply machine learning tomorrow"
sentence.split()
## ["We'll", 'apply', 'machine', 'learning', 'tomorrow']

The contraction we'll actually represents we will, but a simple split does not capture this distinction. Ideally, a tokenizer should convert it into two tokens: we and will.

A related case appears with pronoun contractions:

sentence = "I'm studying machine learning"
sentence.split()
## ["I'm", 'studying', 'machine', 'learning']

Here, I'm should be interpreted as I am, which again requires linguistic awareness beyond simple string splitting.

Multi-word expressions

Multi-word expressions also raise important questions. Consider:

sentence = "Deep learning is a branch of machine learning"
sentence.split()
## ['Deep', 'learning', 'is', 'a', 'branch', 'of', 'machine', 'learning']

Should machine learning be treated as two separate tokens or as a single meaningful unit? In many contexts, it functions as one concept rather than two independent words.

Punctuation and abbreviations

Punctuation introduces additional complexity. In the following example, the period does not mark the end of a sentence:

sentence = "She holds a Ph.D. in machine learning"
sentence.split()
## ['She', 'holds', 'a', 'Ph.D.', 'in', 'machine', 'learning']

Non-lexical tokens

Finally, not all tokens are standard words. Some elements may appear meaningless but still carry contextual value:

sentence = "I was umm thinking about this problem"
sentence.split()
## ['I', 'was', 'umm', 'thinking', 'about', 'this', 'problem']

Although umm is not part of formal vocabulary, it may be relevant in applications such as speech analysis.

Tokenization in social media text

Text from social media introduces additional challenges for tokenization. It often contains emoticons, emojis, hashtags, repeated punctuation, and informal expressions that do not follow standard grammatical rules.

Consider the following example:

sentence = "Learning NLP is fun!! >10 😄 🚀 #NLP #AI"
sentence.split()
## ['Learning', 'NLP', 'is', 'fun!!', '>10', '😄', '🚀', '#NLP', '#AI']

A simple split fails to correctly handle elements such as emoticons (😄, 🚀), symbols (>10), hashtags (#NLP, #AI), and repeated punctuation (!!). Although these elements are not standard words, they often convey emotional tone, emphasis, or topical information that can be relevant in NLP tasks. These limitations motivate the use of specialized tokenizers, such as TweetTokenizer from the nltk package, which we introduce next.

These examples highlight why real-world NLP systems rely on more sophisticated tokenization strategies than simple string splitting.

5 Different types of tokenizers

So far, we have seen that simple splitting rules are often insufficient for real text. To address different linguistic patterns and use cases, several types of tokenizers have been developed. In this section, we introduce:

  • Rule-based tokenizers (such as regular expression–based tokenizers),

  • Linguistically motivated tokenizers (such as the Treebank tokenizer), and

  • Tokenizers designed for informal and social media text (such as TweetTokenizer).

Each type is presented in the following subsections, along with simple examples that illustrate when and why it should be used in NLP applications.

The Figure 5.1 illustrates the main categories of tokenizers, highlighting their underlying principles and typical use cases, including rule-based, linguistically motivated, and social media–oriented approaches..

Different types of tokenizers. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.1: Different types of tokenizers. Source: Created by the author with ChatGPT (OpenAI)

5.0.1 Regular expresions

Regular expressions (regex) are formal patterns used to identify, match, and extract specific structures in text. They constitute one of the earliest and most widely used tools for text processing and remain fundamental in many NLP pipelines.

Figure 5.2 provides a visual overview of several commonly used regular expression patterns and illustrates how they operate on concrete text examples.

Regular expressions. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.2: Regular expressions. Source: Created by the author with ChatGPT (OpenAI)

Regex-based approaches are especially useful when the target patterns are well defined and follow recognizable formats, such as dates, prices, email addresses, numerical values, or identifiers. In such cases, rule-based methods are often simpler, more transparent, and computationally more efficient than machine learning alternatives.

Because these elements exhibit fixed structural regularities, regular expressions are particularly well suited for rule-based extraction during tokenization and text preprocessing.

For further reading and interactive practice, see: MDN Web Docs - Regular Expressions and regex101. MDN Web Docs is an authoritative, developer-oriented documentation resource that provides clear and practical explanations of programming concepts, including regular expressions. In contrast, regex101 is an interactive platform designed for testing, visualizing, and debugging regular expression patterns in real time..

Common regex metacharacters (quick reference).

The table below provides a quick reference to some commonly used regular expression metacharacters. It is not exhaustive (many additional symbols and constructs exist depending on the regex engine) but it covers the most frequently encountered elements in introductory NLP tasks.

Table 5.1: Table 5.2: Common regex metacharacters.
Metacharacter Name Meaning
[ ] Square brackets Character class: matches one character from a specified set/range (e.g., [abc], [0-9]).
\ Backslash (escape) Escapes a metacharacter (or introduces special sequences depending on the regex engine).
(concatenation) Sequence (AND) Logical AND expressed by sequence: patterns must appear consecutively (e.g., catdog means cat AND dog).
+ Plus Quantifier: repeats the previous token one or more times.
^ Caret Anchors the match at the start of the string/line (depending on flags).
* Asterisk Quantifier: repeats the previous token zero or more times.
. Dot Wildcard: matches (almost) any single character except newline (by default).
$ Dollar sign Anchors the match at the end of the string/line (depending on flags).
? Question mark Quantifier: makes the previous token optional (zero or one time).
{ } Curly braces Quantifier: repeats the previous token a specified number of times (e.g., {3}, {2,5}).
( ) Parentheses Grouping: groups tokens; also used to capture subpatterns for later reference.
! Exclamation mark Negation (engine-dependent): often used for “NOT”/negative constructs (not universal across all regex flavors).

Simple regular expression examples

The following examples illustrate how some of the metacharacters above behave in practice. Each example focuses on pattern intuition, not on exhaustive matching rules.

  • Alternation (|) and repetition (*): matches one option or repeated occurrences of another. Example a|b* matches either the symbol a or zero or more repetitions of b: , a, b, bb, bbb (but not ab, ba, aa).

  • Grouping with repetition: generates strings using only the specified symbols. Example (a|b)* matches any sequence formed by the symbols a and b, including the empty string: , a, b, ab, ba, aab (but not c, abc, aabx).

  • Optional element (?): allows a symbol to appear zero or one time. Example ab*(c)? matches strings that start with a, followed by zero or more b’s, and optionally end with c: a, ab, abb, ac, abc (but not b, bc, abbcde).

  • Wildcard (.): matches any single character in a fixed position. Example .ing matches any sequence where one character is followed by the substring ing. The match does not need to start at the beginning of the word: sing->sing, flying->ying, going->oing. Does not match: ing, thing, bring, because .ing requires exactly one character before ing).

  • Character class ([]): matches one character from a defined set. Example [mh]ouse matches one character from the specified set, followed by a fixed suffix: mouse, house. Does not match: louse, Mouse, because only m or h are allowed, and matching is case-sensitive.

  • Negated character class ([^ ]): matches any character not listed. Example [^h]ouse matches any single character except those listed inside the brackets (h): mouse, cheese and housemouse (but not house, and, and cheese, because h is explicitly excluded).

  • Anchors (^, $): restrict matches to the start and end of a string. Example ^[mh]ouse$ matches only complete strings that start and end exactly with the specified pattern: mouse, house (but not match: warehouse, mousepad, my house).

  • Greedy matching (.*): matches a symbol followed by any number of characters. Example f.* matches the character f followed by zero or more characters of any kind: f, fly, foot data, fast text processing.

  • One or more repetitions (+): requires at least one occurrence. Example [mp]+ouse matches: mouse, pmouse, mmouse, ppouse (but not ouse, house). Requires at least one occurrence of the specified characters before the remaining pattern.

5.0.2 Regular expresions-based tokenizers: RegexpTokenizer

The nltk library provides a tokenizer based on regular expressions, known as RegexpTokenizer. This tokenizer allows us to define explicit rules that control how text is split into tokens.

Instead of relying on spaces or punctuation alone, RegexpTokenizer uses a regex pattern to specify which character sequences should be treated as tokens.

Figure 5.3 illustrates the main variants built on top of RegexpTokenizer. These tokenizers are introduced here for orientation purposes and will be examined in detail in subsequent sections.

`RegexpTokenizer` and its variants. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.3: RegexpTokenizer and its variants. Source: Created by the author with ChatGPT (OpenAI)

Example (RegexpTokenizer).

Consider the following sentence:

sentence = "The price ranges from $120.50 to $350.00 today."
sentence
## 'The price ranges from $120.50 to $350.00 today.'

We define a tokenizer that recognizes words and prices:

from nltk.tokenize import RegexpTokenizer

tokenizer = RegexpTokenizer(r"\w+|$[\d.]+|\S+")
tokenizer.tokenize(sentence)
## ['The', 'price', 'ranges', 'from', '$120.50', 'to', '$350.00', 'today', '.']

In this pattern:

  • \w+ matches words and numbers (equal to [a-zA-Z0-9_]).

  • \$ in \$[\d\.]+matches prices starting with $; \d matches a digit between 0 and 9, \. matches the character . (period), and + again acts as a quantifier matching between one and unlimited times.

  • \S accepts any non-whitespace character and + again acts the same way as in the preceding two alternatives.

This approach is useful when we want precise control over which elements are preserved as tokens.

5.0.3 Regular expresions-based tokenizers: More RegexpTokenizer (RegexpTokenizer Family)

Several tokenizers in NLTK are implemented as wrappers or variants of RegexpTokenizer include:

  • WordPunctTokenizer, which separates alphabetic and non-alphabetic characters,

  • BlanklineTokenizer, which uses empty lines as delimiters.

Variants of `RegexpTokenizer`. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.4: Variants of RegexpTokenizer. Source: Created by the author with ChatGPT (OpenAI)

Example (WordPunctTokenizer).

This tokenizer separates alphabetic tokens from punctuation and symbols, making each punctuation mark an individual token.

from nltk.tokenize import WordPunctTokenizer

sentence = "Price: $120.50, available today!"
tokenizer = WordPunctTokenizer()
tokenizer.tokenize(sentence)
## ['Price', ':', '$', '120', '.', '50', ',', 'available', 'today', '!']

Example (BlanklineTokenizer).

This tokenizer splits text into chunks based on blank lines, which is useful when processing documents structured into paragraphs.

from nltk.tokenize import BlanklineTokenizer

sentence = "This is the first paragraph.\n\nThis is the second paragraph."
tokenizer = BlanklineTokenizer()
tokenizer.tokenize(sentence)
## ['This is the first paragraph.', 'This is the second paragraph.']

Try it out:

Write a regular expression to extract email addresses from a text and test it at regex101.

5.0.4 Treebank tokenizer

The Treebank tokenizer applies a set of linguistically motivated rules inspired by the Penn Treebank annotation guidelines.
Although it relies internally on regular expressions, its main goal is not purely pattern matching, but rather linguistically informed tokenization.

In particular, this tokenizer is designed to handle contractions, punctuation, and other common syntactic phenomena in a way that better reflects the structure of natural language. As a result, it is especially effective for preprocessing English text in downstream NLP tasks.

Treebank tokenizer`. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.5: Treebank tokenizer`. Source: Created by the author with ChatGPT (OpenAI)

As illustrated in Figure 5.5, the Treebank tokenizer systematically splits contractions (e.g., we'llwe + will) and separates punctuation marks from words, producing tokens that are more suitable for syntactic and semantic analysis. These design choices make it a standard baseline tokenizer in many NLP pipelines.

Example (Treebank).

Consider the following example:

## "I'm sure this model doesn't perform perfectly."

Tokenizing with the Treebank tokenizer:

from nltk.tokenize import TreebankWordTokenizer

tokenizer = TreebankWordTokenizer()
tokenizer.tokenize(sentence)
## ['I', "'m", 'sure', 'this', 'model', 'does', "n't", 'perform', 'perfectly', '.']

Here, contractions such as I'm and doesn't are split into meaningful components:

  • I'mI and 'm

  • doesn'tdoes and n't

This decomposition helps isolate grammatical and semantic elements such as negation, which would be harder to analyze if treated as a single token.

5.0.5 TweetTokenizer

Text from social media often differs substantially from standard written language. It typically includes user mentions, hashtags, emojis, URLs, elongated words, repeated characters, all of which pose challenges for simple whitespace- or punctuation-based tokenizers.

To address these characteristics, the nltk library provides the TweetTokenizer, a tokenizer specifically designed to handle the informal and highly variable nature of social media text. Rather than discarding these elements, TweetTokenizer preserves them as meaningful tokens, allowing downstream NLP models to capture emotional cues, emphasis, and topical information.

Tweet tokenizer`. Source: Created by the author with ChatGPT (OpenAI)

Figure 5.6: Tweet tokenizer`. Source: Created by the author with ChatGPT (OpenAI)

As illustrated in the Figure 5.6, TweetTokenizer is able to correctly identify and separate mentions (e.g., @user), hashtags (e.g., #AI), emojis, URLs, and expressive punctuation, producing a token sequence that better reflects the structure and semantics of social media communication.

Example (TweetTokenizer).

Consider the following example:

from nltk.tokenize import TweetTokenizer

sentence = "@datafan NLP is sooo exciting!!! 😄🚀 #TextMining #AI"
sentence
## '@datafan NLP is sooo exciting!!! 😄🚀 #TextMining #AI'

Using TweetTokenizer:

from nltk.tokenize import TweetTokenizer

tokenizer = TweetTokenizer()
tokenizer.tokenize(sentence)
## ['@datafan', 'NLP', 'is', 'sooo', 'exciting', '!', '!', '!', '😄', '🚀', '#TextMining', '#AI']

This tokenizer preserves important elements such as:

  • User mentions (@datafan),

  • Emojis (😄, 🚀),

  • Hashtags (#TextMining, #AI), and

  • Repeated punctuation or characters.

5.0.6 TweetTokenizer (example with other configurations)

The TweetTokenizer also offers useful configuration options:

tokenizer = TweetTokenizer(strip_handles=True, reduce_len=True, preserve_case=False)
tokenizer.tokenize(sentence)
## ['nlp', 'is', 'sooo', 'exciting', '!', '!', '!', '😄', '🚀', '#textmining', '#ai']

In this example, each parameter modifies the tokenization behavior as follows:

  • The parameter strip_handles=True (when set to True) removes user mentions in a post/tweet. For example, The user mention (e.g., @datafan) is removed from the output. This is useful when user identifiers are not relevant for the analysis.

  • reduce_len=True shortens exaggerated repetitions, but not completely removed. For instance, sooosoo (here, sooo is preserved in a reduced form rather than being fully normalized).

  • preserve_case=False (when set to False) converts text to lowercase for vocabulary normalization (NLPnlp, #AI#ai). This helps reduce vocabulary size during normalization. The default value for this parameter is True.

Together, these options allow TweetTokenizer to retain meaningful social media elements (emojis, hashtags, emphasis) while reducing noise and variability in informal text.

6 Understanding word normalization

In many NLP tasks, keeping every possible word form in the vocabulary is unnecessary (and often undesirable). A common solution is word normalization, where different surface forms are mapped to a more consistent representation.

For instance, verb forms such as am, are, and is can be mapped to the same base concept be. Likewise, variants such as car, cars, and car's may be treated as the same underlying word, depending on the goal of the analysis.

Normalization is mainly used to control vocabulary size and reduce noise in text data. However, the choice of technique is task-dependent: words that are often removed in general NLP pipelines (e.g., when, why, where) may be uninformative for some classification tasks, but essential for applications such as question answering.

Figure 6.1 summarizes the main normalization steps covered in this section and how they typically fit into a preprocessing workflow.

Word normalization`. Source: Created by the author with ChatGPT (OpenAI)

Figure 6.1: Word normalization`. Source: Created by the author with ChatGPT (OpenAI)

6.0.1 Stemming

Stemming is a technique used to reduce words to a simplified base form, known as the stem, by removing prefixes or suffixes.

For example, words such as compute, computer, and computing may all be reduced to the stem comput.

compute, computer, computing → comput

Importantly, the resulting stem is not guaranteed to be a valid dictionary word.

Stemming algorithms.

Stemming relies on heuristic rules rather than linguistic analysis, which makes it fast but sometimes imprecise. Two widely used stemming algorithms are:

  • Porter stemmer (PorterStemmer), designed for English.

  • Snowball stemmer (SnowballStemmer), an extension of the PorterStemmer that supports multiple languages.

The SnowballStemmer supports the following languages:

from nltk.stem.snowball import SnowballStemmer
SnowballStemmer.languages
## ('arabic', 'danish', 'dutch', 'english', 'finnish', 'french', 'german', 'hungarian', 'italian', 'norwegian', 'porter', 'portuguese', 'romanian', 'russian', 'spanish', 'swedish')

PorterStemmer.

Let us first apply the PorterStemmer to a small set of words:

from nltk.stem.porter import PorterStemmer

words = ["running", "runs", "runner", "easily", "fairly"]
stemmer = PorterStemmer()
[stemmer.stem(word) for word in words]
## ['run', 'run', 'runner', 'easili', 'fairli']

The output shows that the PorterStemmer aggressively removes suffixes:

  • running and runs are correctly reduced to run.

  • runner remains unchanged, since the algorithm does not treat it as an inflected form.

  • easily and fairly are reduced to easili and fairli, which are not valid English words.

This illustrates a key property of stemming: the resulting stem is not required to be linguistically correct, only consistent.

SnowballStemmer.

Now, applying the SnowballStemmer to the same words:

from nltk.stem.snowball import SnowballStemmer

stemmer = SnowballStemmer(language="english")
[stemmer.stem(word) for word in words]
## ['run', 'run', 'runner', 'easili', 'fair']

The Snowballstemmer produces results that are very similar to those of the PorterStemmer, but with small refinements:

  • As before, running and runs are reduced to run.

  • runner remains unchanged.

  • easily is still reduced to easili.

  • However, fairly is reduced to fair, which is a more readable and meaningful stem than fairli.

This difference illustrates how Snowball refines some of the original Porter rules, leading to slightly more interpretable stems in certain cases.

Overall, both stemmers behave similarly, but Snowball often provides modest improvements while preserving the speed and simplicity of rule-based stemming.

Over-stemming and under-stemming.

Stemming can introduce two common types of errors:

  • Over-stemming occurs when different words are reduced to the same stem even though they have different meanings. Example: university and universe may be incorrectly mapped to a similar stem.

  • Under-stemming occurs when related words are not reduced to the same stem. Example: analysis and analyst may remain separate despite being conceptually related.

These limitations highlight that stemming is a crude normalization technique and should be applied with care, depending on the NLP task.

Note

For a detailed discussion on stemming algorithms, see this paper: A Comparative Study of Stemming Algorithms (Jivani et al., 2011)

6.0.2 Lemmatization

Unlike stemming, which removes characters using heuristic rules, lemmatization aims to convert a word into its meaningful base form, known as the lemma. The lemma usually corresponds to a valid dictionary word.

Lemmatization groups together different word forms that share the same base meaning. For example, am, are, and is can all be mapped to the lemma be.

Linguistic information

This process relies on linguistic information such as:

  • The part of speech (POS) of a word,

  • Its contextual usage, and,

  • In some cases, semantic knowledge.

Because the same word can have different lemmas depending on context, lemmatization is generally more accurate (but also more computationally expensive) than stemming.

In this section, we illustrate lemmatization using the WordNet lemmatizer (WordNetLemmatizer) and the spaCy lemmatizer (spacy).

6.0.3 Lemmatization (wordNet lemmatizer)

WordNet is a large lexical database of English in which words are grouped into sets of synonyms (called synsets) that represent distinct concepts. The nltk library provides an interface to WordNet that can be used for lemmatization.

Consider the following sentence:

sentence = "We are putting in efforts to improve our understanding of lemmatization"
sentence
## 'We are putting in efforts to improve our understanding of lemmatization'

Applying the WordNet lemmatizer (WordNetLemmatizer) without additional information:

import nltk
from nltk.stem import WordNetLemmatizer

lemmatizer = WordNetLemmatizer()
tokens = sentence.split()
lemmatized = [lemmatizer.lemmatize(token) for token in tokens]
lemmatized
## ['We', 'are', 'putting', 'in', 'effort', 'to', 'improve', 'our', 'understanding', 'of', 'lemmatization']

Most words remain unchanged. This occurs because, by default, the WordNet lemmatizer assumes that all words are nouns. As a result, verbs such as are or putting are not reduced to their correct base forms.

The role of Part-of-Speech (POS) information.

Lemmatization is inherently context-dependent: the correct lemma of a word depends on its grammatical role in the sentence. For this reason, lemmatizers typically rely on part-of-speech (POS) tags.

The nltk library provides a pretrained POS tagger that assigns grammatical labels to tokens:

pos_tags = nltk.pos_tag(tokens)
pos_tags
## [('We', 'PRP'), ('are', 'VBP'), ('putting', 'VBG'), ('in', 'IN'), ('efforts', 'NNS'), ('to', 'TO'), ('improve', 'VB'), ('our', 'PRP$'), ('understanding', 'NN'), ('of', 'IN'), ('lemmatization', 'NN')]

Some common POS tags appearing in this example are:

  • PRP: personal pronoun
  • PRP$: possessive pronoun
  • VB: verb (base form)
  • VBP: verb (present tense)
  • VBG: verb (gerund or present participle)
  • NN: noun (singular)
  • NNS: noun (plural)
  • IN: preposition or subordinating conjunction
  • TO: infinitive marker

A complete description of the Penn Treebank POS tagset can be found at: Alphabetical list of part-of-speech tags used in the Penn Treebank Project.

Mapping POS tags to WordNet categories.

Internally, WordNet uses a simplified set of grammatical categories (noun, verb, adjective, adverb). Therefore, POS tags produced by the tagger must be mapped to WordNet-compatible labels.

The following helper function performs this mapping:

from nltk.corpus import wordnet
import nltk

def get_wordnet_pos(token):
    tag = nltk.pos_tag([token])[0][1][0].upper()
    tag_map = {
        "J": wordnet.ADJ,
        "N": wordnet.NOUN,
        "V": wordnet.VERB,
        "R": wordnet.ADV
    }
    return tag_map.get(tag, wordnet.NOUN)

Lemmatization with POS information.

When POS information is incorporated, lemmatization becomes more accurate:

lemmatized_with_pos = [
lemmatizer.lemmatize(token, get_wordnet_pos(token))
for token in tokens
]
" ".join(lemmatized_with_pos)
## 'We be put in effort to improve our understand of lemmatization'

This time, the lemmatizer correctly identifies meaningful base forms, for example:

  • arebe

  • puttingput

  • understandingunderstand

This example illustrates that lemmatization is linguistically informed and context-aware, in contrast to stemming, which applies purely rule-based truncation without regard to grammatical role or semantic validity.

Note.

While modern Transformer-based models do not explicitly perform lemmatization, understanding these normalization steps helps clarify how linguistic structure is simplified before tokenization and embedding.

Comparison with stemming.

For comparison, consider the output of a stemmer applied to the same tokens:

from nltk.stem.snowball import SnowballStemmer

stemmer = SnowballStemmer(language="english")
stemmed = [stemmer.stem(token) for token in tokens]
" ".join(stemmed)
## 'we are put in effort to improv our understand of lemmat'

Several words are truncated to forms that do not correspond to valid dictionary entries, for example:

  • improveimprov

  • understandingunderstand

  • lemmatizationlemmat

Unlike lemmatization, stemming applies purely rule-based suffix removal without considering grammatical role or meaning. As a result, it may generate incomplete or non-standard word forms. Lemmatization, by contrast, aims to preserve linguistic validity and interpretability.

6.0.4 Lemmatization (spaCy Lemmatizer)

The spaCy lemmatizer relies on pretrained language models that perform tokenization, POS tagging, and lemmatization as part of a single integrated pipeline.

After installing spaCy and downloading a language model, lemmatization can be applied directly:

import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("We are putting in efforts to improve our understanding of lemmatization")
[token.lemma_ for token in doc]
## ['we', 'be', 'put', 'in', 'effort', 'to', 'improve', 'our', 'understanding', 'of', 'lemmatization']

In this output, spaCy automatically infers the grammatical role of each token and assigns an appropriate lemma:

  • arebe (verb normalization).

  • puttingput (verb base form).

  • effortseffort (singular noun).

  • Function words such as in, to, and of remain unchanged.

  • Content words like understanding and lemmatization already appear in their base form and therefore do not change.

Note.

  • Unlike the WordNet-based approach, spaCy does not require POS tags to be supplied explicitly, as grammatical information is inferred internally by the model.

  • In some spaCy language models, pronouns are represented using the placeholder -PRON-. This is a design choice intended to abstract away surface forms of pronouns rather than a lemmatization error. Depending on the application, this behavior may be useful (e.g., for normalization) or undesirable (e.g., for interpretability).

  • spaCy supports multiple languages. A list of available language models can be found at: SpaCY: Models & Languages.

Examples of spaCy language models.

spaCy supports multiple languages through pretrained language models. These models are not installed by default and must be downloaded separately from the command line before use. For example:

 python -m spacy download en_core_web_sm
 python -m spacy download es_core_news_sm
 python -m spacy download fr_core_news_sm

In this course, the following examples are presented for illustrative purposes only, in order to highlight spaCy’s multilingual capabilities. The code below shows how different language models would be loaded if they were installed.

import spacy

# English
nlp_en = spacy.load("en_core_web_sm")

# Spanish
nlp_es = spacy.load("es_core_news_sm")

# French
nlp_fr = spacy.load("fr_core_news_sm")

The code above is provided for illustrative purposes only and is not intended to be executed in this course. This avoids installation issues while preserving the conceptual understanding of spaCy’s multilingual model structure.

6.0.5 Stopword removal

In previous sections, we briefly mentioned stopword removal as a common preprocessing step in NLP. We now examine this technique in more detail.

What are stopwords?

Stopwords are words such as a, an, the, in, at, and to that occur very frequently in text corpora but usually carry limited semantic information on their own. Although these words are essential for grammatical correctness, they often contribute little to tasks focused on content or meaning.

As a result, stopword removal is commonly used to:

  • Reduce vocabulary size,

  • Simplify text representations, and

  • Improve efficiency in certain NLP tasks.

It is important to note that there is no universal stopword list. Stopwords depend on the:

  • The language,

  • The application, and

  • The specific task being addressed.

Stopwords in practice (nltk example).

The nltk library provides predefined stopword lists for several languages. The following example illustrates how stopwords can be retrieved for English:

#nltk.download('stopwords')
from nltk.corpus import stopwords

stop = set(stopwords.words('english'))
", ".join(stop)
## "aren't, wouldn, there, other, our, with, wouldn't, them, o, is, he's, few, aren, off, that'll, shan't, didn't, are, over, wasn't, needn, had, of, then, i've, their, ve, don, you, we, we'd, haven't, from, hasn, while, me, where, ours, each, itself, shouldn't, m, mustn't, t, theirs, you'd, just, such, about, down, all, shouldn, under, between, couldn't, he, up, before, hers, it'd, having, it, for, a, through, as, here, he'll, weren't, what, no, doesn, an, they've, i'll, i, we've, do, during, she'll, was, it's, hadn't, until, needn't, that, how, ma, now, she'd, re, myself, they'll, be, mightn't, on, does, by, s, were, any, his, they're, because, have, only, shan, hasn't, i'm, been, most, both, i'd, doesn't, won, but, her, did, couldn, am, so, being, these, or, more, below, we're, which, wasn, some, him, your, weren, again, above, themselves, mightn, when, haven, they'd, y, himself, in, isn't, yourself, you've, don't, to, very, if, who, further, why, hadn, herself, the, than, can, nor, he'd, those, she, didn, should've, ain, we'll, and, isn, will, ourselves, yourselves, yours, mustn, you'll, should, once, into, own, whom, out, it'll, this, my, won't, ll, they, has, not, she's, you're, same, at, against, its, doing, d, after, too"

The Italian stopword list includes frequent function words such as di, e, il, la, che, per, which may be removed depending on the goals of the analysis.

from nltk.corpus import stopwords

stop_it = set(stopwords.words("italian"))
", ".join(stop_it)
## 'stemmo, ho, facessimo, farò, sulle, quello, ebbe, o, starebbero, sugli, se, dagl, avevamo, avuto, sta, avresti, da, si, essendo, c, avessi, stando, l, agli, facevo, ero, starei, nei, nel, foste, stia, noi, abbiate, abbiano, dai, faccia, avrei, fareste, ne, sullo, facendo, fosse, quella, sull, loro, fui, stanno, alle, quale, e, farai, uno, faceva, facevate, fummo, abbiamo, avete, faceste, tra, sono, stettero, ti, dalla, facesse, nostro, del, cui, stiate, negl, saresti, facciano, della, starete, saremmo, all, il, avevano, miei, quelle, stava, fossimo, staresti, stai, vostre, anche, stette, quante, gli, avrò, stavate, stesti, sareste, quanti, stetti, agl, mio, per, tutto, faccio, faremmo, lei, facessero, dello, a, stessero, avemmo, di, sarebbe, sarei, fu, ebbero, quanto, nello, queste, contro, avevi, con, avrebbe, stavo, fai, una, dallo, tue, ad, dov, i, avendo, le, faresti, facevano, stiamo, lo, sto, starà, stavamo, coi, avesti, farebbero, un, ma, avrete, sarò, avrebbero, avuta, non, nostra, dagli, avuti, fosti, stesse, stessimo, siate, staremo, avrai, vostri, starai, col, eravate, sarebbero, questi, vostra, come, abbia, avremo, dei, perché, faremo, negli, saremo, facevi, siamo, era, eravamo, aveva, farei, eri, dal, avrà, avremmo, stavi, nostre, alla, degli, chi, facciate, farà, farete, saranno, tuo, tua, al, vostro, avreste, mie, sugl, avevate, in, steste, quanta, hanno, su, voi, hai, sarete, furono, facciamo, staranno, stavano, stessi, ha, fossero, erano, avessimo, sei, io, quelli, degl, stareste, delle, staremmo, dell, avevo, dalle, ed, starebbe, è, nell, allo, questa, avute, fece, sue, tu, sarà, ai, dall, più, tutti, sul, fossi, nella, facemmo, faranno, starò, nelle, lui, vi, che, facesti, ebbi, sulla, sua, farebbe, suoi, tuoi, sarai, avessero, avranno, facevamo, stiano, fanno, questo, ci, li, sui, suo, mi, aveste, mia, siete, siano, dove, feci, facessi, avesse, nostri, fecero, la, sia'

The French stopword list contains common grammatical words such as le, la, les, de, et, à, which are frequently removed in preprocessing steps depending on the task.

from nltk.corpus import stopwords

stop_fr = set(stopwords.words("french"))
", ".join(stop_fr)
## 'qu, étées, ou, auras, eurent, eussiez, moi, étantes, aux, se, étiez, eues, c, leur, eusses, ce, sommes, eu, en, qui, aura, une, ton, ayons, ne, au, avions, la, étant, me, serais, été, aie, m, sa, je, aurait, t, fussent, votre, les, fûmes, pas, il, ta, aies, elle, aient, ayants, eût, as, soyons, mes, es, à, n, avons, étaient, aviez, serons, fussiez, tes, le, mais, que, ayantes, avait, des, ma, un, étée, êtes, sont, son, aurions, on, s, même, eûmes, te, fus, auriez, seraient, ils, étés, avec, vos, aurez, eut, serai, sur, serait, soyez, avaient, serions, fut, étais, fût, eûtes, seras, ait, eus, soit, eue, mon, nos, eux, eussent, vous, avais, y, furent, suis, notre, étions, sera, toi, fussions, fusses, fusse, aurons, aurais, ont, ces, tu, ayez, et, pour, auront, ai, de, est, seront, auraient, lui, nous, fûtes, aurai, eusse, ses, eussions, dans, du, étante, j, avez, sois, était, soient, ayant, par, d, seriez, étants, ayante, l, serez'

Although stopword lists are language-specific, the underlying principle remains the same: stopword removal is a task-dependent preprocessing decision rather than a universal rule. These lists typically consist of highly frequent function words; however, applying them blindly can lead to the removal of linguistically or semantically relevant terms.

Task-dependent stopword selection.

As discussed earlier, stopword lists should not be applied blindly. In particular, wh- words such as who, what, when, why, how, which, where, and whom often play a crucial role in tasks involving questions or information-seeking behavior.

While removing these words may be acceptable in some contexts, it can be harmful in applications such as:

  • Question answering,

  • Question classification,

  • Information retrieval.

The following example illustrates how a stopword list can be adapted to preserve wh- words when they are relevant for interpretation.

from nltk.corpus import stopwords

wh_words = ["who", "what", "when", "why", "how", "which", "where", "whom"]

stop = set(stopwords.words("english"))
for word in wh_words:
    stop.remove(word)

sentence = "how do students analyze text data in applied statistics courses"
filtered_sentence = [token for token in sentence.split() if token not in stop]
" ".join(filtered_sentence)
## 'how students analyze text data applied statistics courses'

The original sentence:

how do students analyze text data in applied statistics courses

is transformed into:

how students analyze text data applied statistics courses

In this process, common function words such as do and in are removed, while the wh- word how is preserved due to its importance for interpretation. This example highlights that stopword removal must be adapted to the specific goals of the analysis rather than applied mechanically.

6.0.6 Case Folding

Another common normalization strategy in NLP is case folding, which consists of converting all characters in a text corpus to lowercase. Under case folding, tokens such as The and the are treated as identical, whereas they would be considered distinct in a case-sensitive representation.

Case folding is particularly useful in applications such as information retrieval and text matching, where differences in capitalization are usually not meaningful. For example, whether a user types Statistics or statistics should not affect the retrieval of relevant documents.

However, case folding can introduce limitations in certain contexts. Proper nouns may lose important distinctions when converted to lowercase. For instance, acronyms such as NASA or UN may be transformed into common nouns. Similarly, named entities composed of common words can become ambiguous after case folding.

Although more sophisticated approaches attempt to preserve capitalization selectively using contextual information, such methods are not always reliable—especially when users predominantly write in lowercase. As a result, fully lowercasing text remains a widely used and practical solution.

It is also important to note that the relevance of capitalization varies across languages. In languages such as English, capitalization often conveys syntactic or semantic information, whereas in other languages it may play a less significant role.

The following example illustrates a simple case-folding operation in Python using the lower() method:

sentence = "Graduate Students Apply Statistical Models to Text Analysis"
sentence = sentence.lower()
sentence
## 'graduate students apply statistical models to text analysis'

In this output, all uppercase letters are converted to lowercase. As a result, words such as Graduate, Students, and Statistical lose their capitalization and become indistinguishable from their lowercase counterparts. This transformation reduces variability in the text representation, which can be beneficial for tasks such as text matching and information retrieval, but may also remove useful signals when capitalization carries semantic or syntactic meaning.

Note: Case folding and modern embeddings

In traditional NLP pipelines, case folding is often applied explicitly as a preprocessing step. In contrast, modern neural language models may handle capitalization differently depending on their architecture and training data.

For example, uncased models rely on fully lowercased text, whereas cased models preserve capitalization and may use it as a signal for meaning or named-entity recognition. Consequently, the decision to apply case folding should be aligned with the representation model being used.

Key takeaways.

  • Stopword removal and case folding are common text normalization techniques, but neither should be applied blindly.

  • Both techniques involve modeling decisions that depend on the task, language, and downstream application.

  • Removing stopwords may simplify representations, but can be harmful in tasks such as question answering or information retrieval.

  • Case folding reduces sparsity but may eliminate meaningful distinctions, particularly for proper nouns and acronyms.

  • In modern NLP systems, including Transformer-based models, some normalization steps may be handled implicitly rather than explicitly.

This concludes the discussion on lexical normalization. We now turn to tokenization strategies that capture not only individual words, but also short sequences of words that convey meaning jointly.

N-grams.

So far, we have implicitly worked with unigrams, that is, individual words treated as independent tokens. Unigrams represent the simplest level of text representation and are often used to model word frequency and basic lexical information. However, many expressions in natural language convey meaning only when multiple words are considered together. Examples include compound terms, named entities, and fixed expressions. To capture such local context, NLP relies on n-grams, which are contiguous sequences of n tokens.

  • Unigrams (n = 1): single words.

  • Bigrams (n = 2): pairs of words.

  • Trigrams (n = 3): sequences of three words.

In practice, most NLP applications use unigrams, bigrams, and trigrams, as larger n-grams tend to be sparse and less informative.

Consider the following sentence:

sentence = "Applied statistics supports data-driven decision making. Applied statistics supports better decision making in practice,  and  applied statistics supports all decisions"
sentence
## 'Applied statistics supports data-driven decision making. Applied statistics supports better decision making in practice,  and  applied statistics supports all decisions'

The phrase data-driven decision making carries a specific meaning that would be partially lost if each word were analyzed independently. N-grams allow us to preserve such local context.

Unigrams.

In this case, unigrams correspond to the individual words in the sentence. They capture basic lexical information but ignore word order and local dependencies.

tokens = sentence.lower().split()
tokens
## ['applied', 'statistics', 'supports', 'data-driven', 'decision', 'making.', 'applied', 'statistics', 'supports', 'better', 'decision', 'making', 'in', 'practice,', 'and', 'applied', 'statistics', 'supports', 'all', 'decisions']

Unigram frequency table.

To summarize the distribution of individual words in the text, unigram frequencies are computed and organized into a table. Presenting frequencies in tabular form facilitates inspection and comparison, making it easier to identify which terms dominate the text.

from collections import Counter
unigram_freq = Counter(tokens)
unigram_freq
## Counter({'applied': 3, 'statistics': 3, 'supports': 3, 'decision': 2, 'data-driven': 1, 'making.': 1, 'better': 1, 'making': 1, 'in': 1, 'practice,': 1, 'and': 1, 'all': 1, 'decisions': 1})

For improved readability, the frequency information is displayed as a structured table with one row per unigram:

#pip install matplotlib
import pandas as pd

unigram_table = (
pd.DataFrame(unigram_freq.items(), columns=["Unigram", "Frequency"])
.sort_values("Frequency", ascending=False)
.reset_index(drop=True)
)

unigram_table
##         Unigram  Frequency
## 0       applied          3
## 1    statistics          3
## 2      supports          3
## 3      decision          2
## 4   data-driven          1
## 5       making.          1
## 6        better          1
## 7        making          1
## 8            in          1
## 9     practice,          1
## 10          and          1
## 11          all          1
## 12    decisions          1

The unigram frequency table shows how often each individual word appears in the text. Words such as applied, statistics, supports, and decision occur multiple times, indicating their central role in the sentence. However, because unigrams treat words independently, this representation does not preserve word order or capture multi-word expressions, limiting the contextual information available.

Bigrams.

While unigrams focus on individual words, bigrams capture pairs of adjacent words. This allows the representation to preserve short-range dependencies and common two-word expressions.

from nltk.util import ngrams

tokens = sentence.split()
bigrams = list(ngrams(tokens, 2))
[" ".join(token) for token in bigrams]
## ['Applied statistics', 'statistics supports', 'supports data-driven', 'data-driven decision', 'decision making.', 'making. Applied', 'Applied statistics', 'statistics supports', 'supports better', 'better decision', 'decision making', 'making in', 'in practice,', 'practice, and', 'and applied', 'applied statistics', 'statistics supports', 'supports all', 'all decisions']

Bigrams capture pairs of adjacent words. This allows the model to preserve short-range dependencies and common phrases such as data-driven decision or decision making, which would lose meaning if analyzed word by word.

Bigram frequency table.

As with unigrams, bigram frequencies can be summarized in a table to facilitate interpretation. While unigrams focus on individual words, bigrams capture pairs of adjacent words, allowing us to observe short-range dependencies and common word combinations.

bigram_freq = Counter(bigrams)
bigram_freq
## Counter({('statistics', 'supports'): 3, ('Applied', 'statistics'): 2, ('supports', 'data-driven'): 1, ('data-driven', 'decision'): 1, ('decision', 'making.'): 1, ('making.', 'Applied'): 1, ('supports', 'better'): 1, ('better', 'decision'): 1, ('decision', 'making'): 1, ('making', 'in'): 1, ('in', 'practice,'): 1, ('practice,', 'and'): 1, ('and', 'applied'): 1, ('applied', 'statistics'): 1, ('supports', 'all'): 1, ('all', 'decisions'): 1})

For greater clarity, the bigram counts are organized into a structured table, where each row represents a two-word sequence and its frequency:

bigram_table = (
pd.DataFrame(
[(" ".join(k), v) for k, v in bigram_freq.items()],
columns=["Bigram", "Frequency"]
)
.sort_values("Frequency", ascending=False)
.reset_index(drop=True)
)

bigram_table
##                   Bigram  Frequency
## 0    statistics supports          3
## 1     Applied statistics          2
## 2   supports data-driven          1
## 3   data-driven decision          1
## 4       decision making.          1
## 5        making. Applied          1
## 6        supports better          1
## 7        better decision          1
## 8        decision making          1
## 9              making in          1
## 10          in practice,          1
## 11         practice, and          1
## 12           and applied          1
## 13    applied statistics          1
## 14          supports all          1
## 15         all decisions          1

The bigram frequency table highlights short expressions that recur in the text. For example, statistics supports appears more than once, suggesting a meaningful local dependency between these words. Compared to unigrams, bigrams preserve word order and provide richer contextual information, although the context remains limited to two-word windows.

Trigrams.

Trigrams extend this idea by capturing sequences of three consecutive words. They are particularly useful for representing compound concepts and fixed expressions, at the cost of increased sparsity.

trigrams = list(ngrams(tokens, 3))
[" ".join(token) for token in trigrams]
## ['Applied statistics supports', 'statistics supports data-driven', 'supports data-driven decision', 'data-driven decision making.', 'decision making. Applied', 'making. Applied statistics', 'Applied statistics supports', 'statistics supports better', 'supports better decision', 'better decision making', 'decision making in', 'making in practice,', 'in practice, and', 'practice, and applied', 'and applied statistics', 'applied statistics supports', 'statistics supports all', 'supports all decisions']

Trigram frequency table.

Trigram frequencies summarize sequences of three consecutive tokens extracted from the text. By extending the context window beyond individual words and word pairs, trigrams are able to represent longer expressions and more specific semantic patterns.

trigram_freq = Counter(trigrams)
trigram_freq
## Counter({('Applied', 'statistics', 'supports'): 2, ('statistics', 'supports', 'data-driven'): 1, ('supports', 'data-driven', 'decision'): 1, ('data-driven', 'decision', 'making.'): 1, ('decision', 'making.', 'Applied'): 1, ('making.', 'Applied', 'statistics'): 1, ('statistics', 'supports', 'better'): 1, ('supports', 'better', 'decision'): 1, ('better', 'decision', 'making'): 1, ('decision', 'making', 'in'): 1, ('making', 'in', 'practice,'): 1, ('in', 'practice,', 'and'): 1, ('practice,', 'and', 'applied'): 1, ('and', 'applied', 'statistics'): 1, ('applied', 'statistics', 'supports'): 1, ('statistics', 'supports', 'all'): 1, ('supports', 'all', 'decisions'): 1})

For improved readability, the trigram counts can be arranged in a table format, where each row corresponds to a three-word sequence and its observed frequency:

trigram_table = (
pd.DataFrame(
[(" ".join(k), v) for k, v in trigram_freq.items()],
columns=["Trigram", "Frequency"]
)
.sort_values("Frequency", ascending=False)
.reset_index(drop=True)
)

trigram_table
##                             Trigram  Frequency
## 0       Applied statistics supports          2
## 1   statistics supports data-driven          1
## 2     supports data-driven decision          1
## 3      data-driven decision making.          1
## 4          decision making. Applied          1
## 5        making. Applied statistics          1
## 6        statistics supports better          1
## 7          supports better decision          1
## 8            better decision making          1
## 9                decision making in          1
## 10              making in practice,          1
## 11                 in practice, and          1
## 12            practice, and applied          1
## 13           and applied statistics          1
## 14      applied statistics supports          1
## 15          statistics supports all          1
## 16           supports all decisions          1

In this example, the trigram applied statistics supports appears three times, while all other trigrams occur only once. This indicates the presence of a repeated local pattern in the text, whereas the remaining trigrams correspond to unique contextual sequences.

Nevertheless, even when frequencies are equal, trigrams provide valuable information by preserving local syntactic and semantic context. For instance, expressions such as applied statistics supports or data-driven decision making capture relationships between words that are not visible when using unigrams or bigrams alone.

This illustrates a key trade-off in n-gram modeling: as the value of n increases, n-grams tend to become more informative in terms of contextual richness, but also more sparse. In larger corpora, repeated trigrams typically emerge, and their frequency distributions become more meaningful for statistical modeling and feature extraction.

A utility function for visualizing n-gram frequencies.

Frequency tables are the primary analytical output in n-gram analysis. However, simple visualizations can be useful for exploratory and pedagogical purposes, especially when introducing text-based features for the first time.

In this section, we define a lightweight visualization utility that produces:

  • A bar plot of the most frequent n-grams.

  • A word cloud summarizing relative frequency patterns.

These visualizations are intended to support interpretation and intuition. They do not replace frequency tables, which remain the authoritative analytical representation.

#pip install matplotlib
#pip install wordcloud
#pip install numpy
#pip install collections

import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
from matplotlib.gridspec import GridSpec

def to_freq_dict(x):
    """
    Convert input into a clean frequency dictionary {str: int}.
    """
    # Case 1: already a Counter or dict
    if isinstance(x, (Counter, dict)):
        items = x.items()
    else:
      # if it's a list of tokens
        items = Counter(x).items()

    clean = {}
    for k, v in items:
      # Ensure value is numeric
        clean[str(k)] = int(v)

    return clean

def plot_ngram(
    freq_like,
    title,
    top_n=15,
    max_font_size=150,
    min_font_size=18,
    scale=4
):
    words = to_freq_dict(freq_like)

    top = dict(
        sorted(words.items(), key=lambda kv: kv[1], reverse=True)[:top_n]
    )

    fig = plt.figure(figsize=(18,6))
    gs = GridSpec(1, 2, width_ratios=[1, 1.5])  # más espacio a la nube
    
    # Bar plot
    ax1 = fig.add_subplot(gs[0])
    #ax1.bar(top.keys(), top.values())
    
    # Bar plot (horizontal)
    labels = list(top.keys())
    values = list(top.values())
    
    # sort for nicer horizontal plotting
    pairs = sorted(zip(labels, values), key=lambda x: x[1])
    labels, values = zip(*pairs)
    
    ax1.barh(labels, values)
    ax1.set_title(f"{title} – Top {top_n} Frequencies")
    ax1.tick_params(axis="y", labelsize=16)   # controla tamaño etiquetas
    #ax1.tick_params(axis="x", rotation=45)
    
    # Word cloud
    ax2 = fig.add_subplot(gs[1])
    wc = WordCloud(
        width=1300,
        height=600,
        background_color="white",
        max_font_size=max_font_size,
        min_font_size=min_font_size,
        scale=scale,
        prefer_horizontal=1.0,
        collocations=False
    ).generate_from_frequencies(top)

    ax2.imshow(wc)
    ax2.axis("off")
    ax2.set_title(f"{title} – Word Cloud", fontsize=14)

    plt.tight_layout()
    plt.show()

Explanation of the helper functions

The code above defines two helper functions that work together to prepare and visualize n-gram frequency information.

Function to_freq_dict()

This function converts different types of input into a standardized frequency dictionary of the form:

n-gram  →  frequency

Its purpose is to ensure that the visualization function receives data in a consistent and error-free format, regardless of whether the input is:

  • A Counter object.

  • A regular dictionary.

  • Or a list of tokens or n-grams.

In practical terms, this function:

  • Extracts the frequency counts.

  • Converts all keys to strings.

  • Ensures that all frequencies are numeric.

This preprocessing step avoids errors and makes the plotting function more robust.

Function plot_ngram()

This function generates two complementary visual summaries from the frequency information:

  1. Bar plot: Displays the most frequent n-grams and their counts, allowing for direct quantitative comparison.

  2. Word cloud: Provides a qualitative visualization where more frequent n-grams appear more prominently, offering an intuitive overview of relative importance.

For readability, only the top n most frequent n-grams are displayed (controlled by the top_n argument).

Overall, this function plays an exploratory role: it helps us visually inspect patterns in the data, while the frequency tables remain the primary analytical reference.

Applying the visualization to n-gram examples

We now apply the visualization utility to the unigram, bigram, and trigram frequency objects computed earlier. This illustrates how the same function can be reused to explore different levels of textual context.

The resulting plots facilitate comparison across n-gram types and help highlight how contextual information increases as n grows. As emphasized throughout this section, frequency tables remain the primary analytical reference.

# Example usage (these can be Counter/dict or lists)
plot_ngram(unigram_freq, "Unigrams")

plot_ngram(bigram_freq, "Bigrams", top_n=10)

plot_ngram(trigram_freq, "Trigrams", top_n=5,  max_font_size=200,  min_font_size=25, scale=4)

6.0.7 Removing HTML tags

In many NLP applications, text data is collected from web sources. Such data often contains HTML tags, which may introduce noise into the analysis.

In most cases, HTML tags do not contribute to the linguistic content of the text and should be removed. However, in some specialized applications, tags may encode useful information. Therefore, their treatment depends on the specific task.

The following example illustrates how HTML tags can be removed using the BeautifulSoup library::

from bs4 import BeautifulSoup

html = "<html><body><h2>Course Announcement</h2><p>Final project due next week.</p></body></html>"
soup = BeautifulSoup(html)
text = soup.get_text()
text
## 'Course AnnouncementFinal project due next week.'

In this output, all HTML tags (such as <html>, <body>, <h2>, and <p>) are removed, leaving only the raw textual content. The heading and the paragraph are preserved as text, but the structural information encoded by the HTML markup is lost.

Notice that the resulting string concatenates the heading and the paragraph without an explicit space or line break between them. This highlights an important practical issue: while HTML tag removal simplifies the text, it may introduce formatting artifacts that require additional post-processing, such as inserting spaces, line breaks, or performing sentence segmentation.

Note.

In simple cases, HTML tags can also be removed using regular expressions. However, this approach is generally discouraged for real-world HTML data, as it is fragile and may fail when tags are nested, malformed, or contain attributes. Libraries such as BeautifulSoup provide a more robust and reliable solution for practical NLP pipelines.

6.0.8 How does all this fit into my NLP pipeline?

The preprocessing steps discussed so far—lexical normalization, stopword handling, case folding, n-grams, and noise removal—are typically applied before building statistical or machine learning models.

Which steps are applied, and in what order, depends entirely on the use case. For this reason, preprocessing choices should be viewed as modeling decisions, not fixed or universal rules.

After preprocessing, tokens can be aggregated to form a vocabulary, which defines the set of units used to represent text numerically. The vocabulary serves as the interface between raw text and quantitative representations.

sentence = "Applied statistics supports data-driven decision making"
tokens = set(sentence.lower().split())
vocabulary = sorted(tokens)
vocabulary
## ['applied', 'data-driven', 'decision', 'making', 'statistics', 'supports']

In this example, the vocabulary consists of the unique tokens obtained after basic preprocessing (lowercasing and tokenization). Each element represents a distinct unit that can later be mapped to numerical features.

This vocabulary forms the foundation for representing text in a structured and consistent way, serving as the interface between raw language data and quantitative analysis.

8 Summary

In this document, we examined the main steps involved in constructing a vocabulary for natural language processing tasks. These steps form the foundation of text preprocessing and play a central role in how linguistic data is prepared for analysis.

Text preprocessing is a critical component of any machine learning workflow, and this is especially true in NLP. Thoughtful preprocessing helps reduce noise, control variability, and shape the structure of the data in ways that facilitate effective modeling. When these steps are carefully designed and aligned with the task at hand, they often lead to more stable and interpretable results than approaches that rely on raw text alone.

As discussed in the final sections of this document, many preprocessing decisions (particularly those related to tokenization) also play a fundamental role in modern large language models, where they directly influence efficiency, representation, and overall model behavior.

In other documents (click here), we build on these concepts by applying the preprocessing techniques discussed here to construct mathematical representations of text that can be used directly by machine learning algorithms.

9 Applied activity: from lyrics to vocabulary construction

This activity is designed to integrate and apply all the concepts introduced in this document. The reader is asked to work with a short song fragment of their choice and perform a complete lexical analysis using R.

9.0.1 Objective

To construct a reproducible lexical analysis pipeline that moves from raw text to tokenization, vocabulary construction, and normalization, illustrating key NLP preprocessing concepts.

9.0.2 Instructions

  1. Select a song of your choice and work with:

    • A short fragment (e.g., 6–8 lines), or

    • A song with public-domain or open licensing.

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

  3. The document must include both:

    • The R code, and

    • The resulting output (tables, printed objects, or visual summaries).

9.0.3 Required Sections

1. Text Description.

Briefly describe the chosen song and the reason for selecting it. Include the text fragment used for the analysis.

2. Lexicons.

Define a small lexicon (at least 15 entries) derived from the text, including:

  • The lexical item.

  • A conceptual category (e.g., emotion, action, place).

  • A short description or interpretation.

3. Phonemes, Graphemes, and Morphemes.

Explain, in conceptual terms, the distinction between phonemes, graphemes, and morphemes. Illustrate these concepts using a small set of words from the selected text.

4. Tokenization.

Apply and compare different tokenization strategies, including:

  • Sentence tokenization.

  • Word tokenization.

  • Character-level tokenization.

Report:

  • The total number of tokens.

  • The most frequent tokens.

  • A short interpretation of the results.

5. Different Types of Tokenizers.

Using rule-based tokenization, design at least two regular expressions to extract specific entities from the text (e.g., numbers, dates, prices, hashtags).
Show the matched results and explain what each pattern captures.

6. Word Normalization.

Apply common normalization techniques, such as:

  • Lowercasing.

  • Punctuation removal.

  • Stopword removal.

  • Stemming or lemmatization.

Compare the vocabulary before and after normalization and discuss the observed changes.

7. Summary.

Provide a concise reflection (5–8 lines) on how lexical choices, tokenization, and normalization affect vocabulary construction and textual representation in NLP.

9.0.4 Reproducibility Requirement

The R Markdown document must be fully reproducible, meaning that all code chunks execute without errors and generate the reported outputs when the document is compiled.

 

 
If you found any ERRORS or have SUGGESTIONS, please report them to my email. Thanks.  
LS0tDQp0aXRsZTogIk1BVEhFTUFUSUNTIEJFSElORCBMQU5HVUFHRSBSRVBSRVNFTlRBVElPTiINCnN1YnRpdGxlOiA8aDE+KipMZXhpY2FsIGZvdW5kYXRpb25zIGFuZCB2b2NhYnVsYXJ5IGNvbnN0cnVjdGlvbiBpbiBOTFAqKjwvaDE+DQoNCmF1dGhvcjogDQogIC0gbmFtZSAgICAgICAgICA6ICJEci4gcmVyLiBuYXQuIEh1bWJlcnRvIExMaW7DoXMgU29sYW5vIg0KICAgIGFmZmlsaWF0aW9uICAgOiAiRGVwYXJ0YW1lbnRvIGRlIE1hdGVtw6F0aWNhcyB5IEVzdGFkw61zdGljYSwgVW5pdmVyc2lkYWQgZGVsIE5vcnRlIChCYXJyYW5xdWlsbGEsIENvbG9tYmlhKSINCiAgICAgI2NvcnJlc3BvbmRpbmcgOiB5ZXMgICAgIyBEZWZpbmUgb25seSBvbmUgY29ycmVzcG9uZGluZyBhdXRob3INCiAgICAgI2FkZHJlc3MgICAgICAgOiAiRGVwYXJ0YW1lbnRvIGRlIE1hdGVtw6F0aWNhcyB5IEVzdGFkw61zdGljYSINCiAgICBlbWFpbCAgICAgICAgIDogfA0KICAgICAgaGxsaW5hc0B1bmlub3J0ZS5lZHUuY28NCiAgICAgIA0KICAgICAgW0Jpb2dyYXBoaWNhbCBza2V0Y2hdKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvQmlvX1NrZXRjaCkNCiAgICAgIA0KICAgICAgYHIgZm9ybWF0KFN5cy50aW1lKCksICIlZC8lbS8leSIpYCANCiAgICAgIA0KICAgICAjcm9sZTogICAgICAgICAjIENvbnRyaWJ1dG9yc2hpcCByb2xlcyAoZS5nLiwgQ1JlZGlULCBodHRwczovL2Nhc3JhaS5vcmcvY3JlZGl0LykNCiAgIyAgICAtIENvbmNlcHR1YWxpemF0aW9uDQogICMgICAgLSBXcml0aW5nIC0gT3JpZ2luYWwgRHJhZnQgUHJlcGFyYXRpb24NCiAgIyAgICAtIFdyaXRpbmcgLSBSZXZpZXcgJiBFZGl0aW5nDQogIyAtIG5hbWUgICAgICAgICAgOiAiQXV0b3IgbnVtZXJvIDIiDQogIyAgIGFmZmlsaWF0aW9uICAgOiAiMSwyIg0KICMgICByb2xlOg0KICMgICAgIC0gV3JpdGluZyAtIFJldmlldyAmIEVkaXRpbmcNCiAgICAgI2FmZmlsaWF0aW9uOg0KICAjLSBpZCAgICAgICAgICAgIDogIjEiDQogICMgIGluc3RpdHV0aW9uICAgOiAiVW5pdmVyc2lkYWQgZGVsIE5vcnRlIChCYXJyYW5xdWlsbGEsIENvbG9tYmlhKSINCiAgIyFbXShobGxpbmFzLmpwZyl7d2lkdGg9MWlufSANCiAgDQojZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiJWQvJW0vJXkiKWAnICAjIHZlciBodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24tY29va2Jvb2svdXBkYXRlLWRhdGUuaHRtbA0Kb3V0cHV0OiANCiAgICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6IA0KICAgICAgICAgICNPSk8gU2FsZW4gY2FwaXR1bG9zLCBzZWNjaW9uZXMgeSBUZW9yZW1hcw0KICAgICNib29rZG93bjo6aHRtbF9ib29rOg0KICAgICAgICAgICNPSk8gRVJST1IgU2FsZW4gdGVvcmVtYXMsIHBlcm8gbm8gc2FsZW4gbG9zIGNhcGl0dWxvcyANCiAgICAjaHRtbF9kb2N1bWVudDoNCiAgICAgICAgICB0b2M6IHRydWUgICAgICAjIHRhYmxlIG9mIGNvbnRlbnQgdHJ1ZQ0KICAgICAgICAgIHRvY19kZXB0aDogNCAgICMgdXB0byB0aHJlZSBkZXB0aHMgb2YgaGVhZGluZ3MgKHNwZWNpZmllZCBieSAjLCAjIyBhbmQgIyMjKQ0KICAgICAgICAgIHRvY19mbG9hdDogdHJ1ZSAjQ29uIHRydWUsIHRvYyBzYWxlIGFsIG1hcmdlbiBpenF1aWVyZG8gZGUgbGEgcMOhZ2luYTsgZGUgbG8gY29udHJhcmlvLCBhcnJpYmENCiAgICAgICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UNCiAgICAgICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUgICAjIGlmIHlvdSB3YW50IG51bWJlciBzZWN0aW9ucyBhdCBlYWNoIHRhYmxlIGhlYWRlcg0KICAgICAgICAgICN0aGVtZTogc2FuZHN0b25lDQogICAgICAgICAgI3RoZW1lOiB1bml0ZWQgICMgbWFueSBvcHRpb25zIGZvciB0aGVtZSwgdGhpcyBvbmUgaXMgbXkgZmF2b3JpdGUuDQogICAgICAgICAgI3RoZW1lOiBmbGF0bHkgICMgDQogICAgICAgICAgI3RoZW1lOiBjZXJ1bGVhbiAgIyANCiAgICAgICAgICAjaGlnaGxpZ2h0OiB0YW5nbyAgIyBzcGVjaWZpZXMgdGhlIHN5bnRheCBoaWdobGlnaHRpbmcgc3R5bGUNCiAgICAgICAgICAjY3NzOiBTY3JpcHRzIGFjY2Vzb3Jpb3MvZXN0aWxvYm90b24uY3NzDQogICAgICAgICAgI2NzczogbXkuY3NzICAgIyB5b3UgY2FuIGFkZCB5b3VyIGN1c3RvbSBjc3MsIHNob3VsZCBiZSBpbiBzYW1lIGZvbGRlcg0KICAgICAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAgICAgICAjaGlnaGxpZ2h0OiB0YW5nbyAgIyBjYW1iaWFyIGNvbG9yIGRlIGxpYnJhcnkgZW4gYXp1bA0KICAgICMgYm9va2Rvd246OmdpdGJvb2s6DQogICAgIyAgICAgIGluY2x1ZGVzOg0KICAgICMgICAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwNCiAgICAjIGJvb2tkb3duOjpwZGZfYm9vazoNCiAgICAjICAgICAgIGtlZXBfdGV4OiB5ZXMNCiAgICAjIGJvb2tkb3duOjpodG1sX2Jvb2s6DQogICAgIyAgICAgICBjc3M6IHRvYy5jc3MNCiAgICAjIGJvb2tkb3duOjpodG1sX2Jvb2s6DQogICAgIyAgICAgICAgIGluY2x1ZGVzOg0KICAgICMgICAgICAgICAgIGluX2hlYWRlcjogc3R5bGUuY3NzDQogICAgI2Jvb2tkb3duOjpodG1sX2RvY3VtZW50MjogZGVmYXVsdA0KICAgICMgYm9va2Rvd246OnBkZl9kb2N1bWVudDI6DQogICAgIyAgICAgIGtlZXBfdGV4OiB0cnVlDQogICAgI2JpYmxpb2dyYXBoeTogcmVmZXJlbmNlcy5iaWINCiAgICBtYXRoamF4OiAiaHR0cDovL2V4YW1wbGUuY29tL21hdGhqYXgvTWF0aEpheC5qcz9jb25maWc9VGVYLUFNUy1NTUxfSFRNTG9yTU1MIg0KaGVhZGVyLWluY2x1ZGVzOg0KICAgIFx1c2VwYWNrYWdlW3gxMW5hbWVzXXt4Y29sb3J9IA0KICAgIA0KY3NsOiBzY2llbmNlLmNzbA0KI09qbzogU2UgdXRpbGl6YSBsZW5ndWFqZSBZQU1MDQoNCmFic3RyYWN0OiB8DQogKipFbiBbUnB1YnM6OiB0b2NdKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvdG9jKSBzZSBwdWVkZW4gdmVyIG90cm9zIGRvY3VtZW50b3MgZGUgcG9zaWJsZSBpbnRlcsOpcy4qKg0KICANCi0tLQ0KICANCiANCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KIyBTaSBxdWllcmVzIGVzcGVjaWZpY2FyIHVuYSB2ZXJzacOzbiBlc3BlY8OtZmljYSBkZSBQeXRob246DQojdXNlX3B5dGhvbigiL3Vzci9iaW4vcHl0aG9uIikNCiMgbyB1c2FyIHVuIGVudG9ybm8gdmlydHVhbCBvIGNvbmRhOg0KIyB1c2VfdmlydHVhbGVudigifi9taW5pY29uZGEzL2VudnMvdG9yY2giKSANCnVzZV9jb25kYWVudigidG9yY2giKQ0KDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZmlnLmFsaWduPSJjZW50ZXIiLCAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSMsDQogICAgICAgICAgICAgICAgICAgICNzdHlsZSA9ICJjb2xvcjpkYXJrYmx1ZSINCiAgICAgICAgICAgICAgICAgICAgIyBjbGFzcy5zb3VyY2U9ImJnLWRhbmdlciIsIGNsYXNzLm91dHB1dD0iYmctd2FybmluZyIgICAjQ29sb3JlcyBkZW50cm8gZGVsIGNodW5rDQogICAgICAgICAgICAgICAgICAgICApDQpsaWJyYXJ5KHJnbCkNCmtuaXRyOjprbml0X2hvb2tzJHNldCh3ZWJnbCA9IGhvb2tfd2ViZ2wpDQpgYGANCg0KDQoNCjwhLS0gbWFya2Rvd25saW50LWRpc2FibGUtbmV4dC1saW5lIE1EMDMzIC0tPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KYm9keXsgLyogTm9ybWFsICAqLw0KICAgICAgZm9udC1zaXplOiAxNHB4Ow0KICB9DQoNCi8qIHRkIHsgZm9udC1zaXplOiA4cHg7IH0gIENvbWVudGFkbyBwYXJhIG5vIGFmZWN0YXIgdGFtYcOxbyBkZSB0YWJsYXMgY29uIGthYmxlRXh0cmEgKi8NCg0KaDEudGl0bGUgew0KICBmb250LXNpemU6IDM4cHg7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCn0NCmgxICB7IC8qIEhlYWRlciAxICovDQogIGZvbnQtc2l6ZTogMjJweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7ICAvKiBvIHVzYSA3MDAgc2kgcHJlZmllcmVzICovDQogIGNvbG9yOiBCbGFjazsNCn0NCmgyIHsgLyogSGVhZGVyIDIgKi8NCiAgICBmb250LXNpemU6IDIycHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7ICAvKiBvIHVzYSA3MDAgc2kgcHJlZmllcmVzICovDQogIGNvbG9yOiBEYXJrQmx1ZTsNCn0NCmgzIHsgLyogSGVhZGVyIDMgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICAgZm9udC13ZWlnaHQ6IGJvbGQ7ICAvKiBvIHVzYSA3MDAgc2kgcHJlZmllcmVzICovDQogIC8qIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOyAqLw0KICBjb2xvcjogRGFya0dyZWVuOw0KfQ0KaDQgew0KICBmb250LXNpemU6IDE4cHg7DQogIGNvbG9yOiBHcmVlbjsNCiAgZm9udC13ZWlnaHQ6IDkwMDsgIC8qIG8gdXNhIDcwMCBzaSBwcmVmaWVyZXMgKi8NCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQp9DQoNCmNvZGUucnsgLyogQ29kZSBibG9jayAqLw0KICAgIGZvbnQtc2l6ZTogMTJweDsNCn0NCnByZSB7IC8qIENvZGUgYmxvY2sgLSBkZXRlcm1pbmVzIGNvZGUgc3BhY2luZyBiZXR3ZWVuIGxpbmVzICovDQogICAgZm9udC1zaXplOiAxNHB4Ow0KfQ0KPC9zdHlsZT4NCg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBldmFsPUZBTFNFfQ0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duL2xhbmd1YWdlLWVuZ2luZXMuaHRtbA0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ib29rZG93bi9tYXJrZG93bi1zeW50YXguaHRtbA0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ib29rZG93bi9hLXNpbmdsZS1kb2N1bWVudC5odG1sDQoNCmh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL2Jvb2tkb3duL21hcmtkb3duLWV4dGVuc2lvbnMtYnktYm9va2Rvd24uaHRtbA0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24vYm9va2Rvd24tbWFya2Rvd24uaHRtbCAgIyBUZW9yZW1zIGFuZCBwcm9vZnMNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvYm9va2Rvd24vbWFya2Rvd24tZXh0ZW5zaW9ucy1ieS1ib29rZG93bi5odG1sI3RoZW9yZW1zDQoNCmh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL2Jvb2tkb3duL2h0bWwuaHRtbA0KDQpodHRwczovL3d3dy5kYXRhLXRvLXZpei5jb20vDQogIA0KW1JwdWJzXShsaW5rKQ0KICANCihcI2VxOmVjLSksICBFY3VhY2lvbiBcQHJlZihlcTplYy0pLCBGaWd1cmEgXEByZWYoZmlnOkZpZy0pLCBUYWJsZSBcQHJlZih0YWI6bXRjYXJzKSwgVGhlb3JlbSBcQHJlZih0aG06Ym9yaW5nKQ0KDQojIFRpdHVsbyB7I1RpdHVsb1NlY2Npb259ICAgXEByZWYoVGl0dWxvU2VjY2lvbikNCg0KIyBTZWUgRmlndXJlIFxAcmVmKGZpZzpGaWcxLVRyYW5zZikuICANCiAgICANCiMgRm9yIEhUTUwsIHdlIGNhbiBzZXQgY29sb3Igd2l0aCBDU1MsIGUuZy4sIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+dGV4dDwvc3Bhbj4NCiAgDQojIGh0dHBzOi8vcmFkaWFudC1yc3RhdHMuZ2l0aHViLmlvL2RvY3MvbW9kZWwvbG9naXN0aWMuaHRtbCBTaGlubnkgTG9naXQgIA0KICANCiMjIyMgRWwgY8OzZGlnby4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCiAgDQpgYGANCg0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1GQUxTRX0NCiNMYSBmb3RvIHRhbWHDsW8gY8OpZHVsYQ0KDQpodG1sdG9vbHM6OmltZyhzcmMgPSBrbml0cjo6aW1hZ2VfdXJpKGZpbGUucGF0aChSLmhvbWUoImRvYyIpLCAiaHRtbCIsICJsb2dvLmpwZyIpKSwgDQogICAgICAgICAgICAgICBhbHQgPSAnaGxsaW5hcycsIA0KICAgICAgICAgICAgICAgc3R5bGUgPSAncG9zaXRpb246YWJzb2x1dGU7IHRvcDowOyByaWdodDowOyBwYWRkaW5nOjEwcHg7JywNCiAgICAgICAgICAgICAgIHdpZHRoID0gIjIwMHB4IikgICMgQXF1w60gZXNwZWNpZmljYXMgZWwgYW5jaG8gZGVzZWFkbyBlbiBww614ZWxlcyBvIHBvcmNlbnRhamUNCmBgYA0KDQoNCg0KYGBge3IsIGVjaG89RkFMU0UsIH0NCiMgTGEgZm90byBncmFuZGUNCg0KaHRtbHRvb2xzOjppbWcoc3JjID0ga25pdHI6OmltYWdlX3VyaSgiaGxsaW5hczIwMjMuanBnIiksIA0KICAgICAgICAgICAgICAgYWx0ID0gJ2hsbGluYXMyMDIzJywgDQogICAgICAgICAgICAgICBzdHlsZSA9ICdwb3NpdGlvbjphYnNvbHV0ZTsgdG9wOjA7IHJpZ2h0OjA7IHBhZGRpbmc6MXB4OycsDQogICAgICAgICAgICAgICB3aWR0aD0iMTUlIikNCmBgYA0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yICAtLT4NCg0KYGBge2NzcywgZWNobz1GQUxTRX0NCi5jb2x1bW5zIHtkaXNwbGF5OiBmbGV4O30NCmgxIHtjb2xvcjogRGFya0JsdWU7fQ0KaDMge2NvbG9yOiBEYXJrR3JlZW47fQ0KaDQge2NvbG9yOiBEYXJrR3JlZW47fQ0KDQoNCi5lcnJvci1ibG9jayB7DQogIG1hcmdpbi1sZWZ0OiAyZW07DQp9DQoNCi5lcnJvci1ibG9jayBzdHJvbmcgew0KICBtYXJnaW4tbGVmdDogLTFlbTsNCn0NCg0KLnNhbmdyaWEzIHsNCiAgbWFyZ2luLWxlZnQ6IDNlbTsNCn0NCg0KLnNhbmdyaWE0IHsNCiAgbWFyZ2luLWxlZnQ6IDRlbTsNCn0NCg0KLnNhbmdyaWE1IHsNCiAgbWFyZ2luLWxlZnQ6IDVlbTsNCn0NCg0KLnNhbmdyaWE2IHsNCiAgbWFyZ2luLWxlZnQ6IDZlbTsNCn0NCg0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBDYXDDrXR1bG8gMSAtLT4NCg0KYGBge3IsIGVjaG89RkFMU0UsIGV2YWw9RkFMU0V9DQojTXVsdGlwbGUgYXV0aG9ycyBhbmQgc3VidGl0bGVzIGluIFJtYXJrZG93biB5YW1sOiANCiNodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNjA0MzgwNy9tdWx0aXBsZS1hdXRob3JzLWFuZC1zdWJ0aXRsZXMtaW4tcm1hcmtkb3duLXlhbWwNCg0KI0luc2VydCBhIGxvZ28gaW4gdXBwZXIgcmlnaHQgY29ybmVyIG9mIFIgbWFya2Rvd24gaHRtbCBkb2N1bWVudDoNCiNodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy80MzAwOTc4OC9pbnNlcnQtYS1sb2dvLWluLXVwcGVyLXJpZ2h0LWNvcm5lci1vZi1yLW1hcmtkb3duLWh0bWwtZG9jdW1lbnQvNDMwMTA2MzINCg0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyBJbnRyb2R1Y3Rpb24NCg0KIyMjIFByZWxpbWluYXJzDQoNClRvIHVuZGVyc3RhbmQgdGhhdCBpbml0aWFsIHN0ZXAsIHdl4oCZbGwgc3R1ZHkgaG93IHZvY2FidWxhcmllcyBhcmUgYnVpbHQgYW5kIGhvdyBsYW5ndWFnZSBpcyByZXByZXNlbnRlZCBzeW1ib2xpY2FsbHkuIFRoaXMgaW5jbHVkZXMgbGV4aWNvbnMsIHBob25lbWVzLCBncmFwaGVtZXMsIG1vcnBoZW1lcywgdG9rZW5pemF0aW9uIHN0cmF0ZWdpZXMsIGFuZCB3b3JkIG5vcm1hbGl6YXRpb24gdGVjaG5pcXVlcy4NCg0KQmVmb3JlIGFwcGx5aW5nIGFueSBtb2RlbCB0byBhIHRleHQgZGF0YXNldCwgaXQgaXMgZXNzZW50aWFsIHRvIGNvbnZlcnQgbGFuZ3VhZ2UgaW50byBhIGZvcm1hdCB0aGF0IG1hY2hpbmVzIGNhbiB1bmRlcnN0YW5kLiBJbiB0aGlzIGNoYXB0ZXIsIHdl4oCZbGwgZXhwbG9yZSB0aGUgY29yZSBlbGVtZW50cyBvZiB0aGF0IHRyYW5zZm9ybWF0aW9uIChmcm9tIHJhdyB0ZXh0IHRvIHN0cnVjdHVyZWQgdG9rZW5zKS4gDQoNClRoZSBGaWd1cmUgXEByZWYoZmlnOkZpZy1TdGVwMSkgaWxsdXN0cmF0ZXMgdGhlIHByb2dyZXNzaW9uIGZyb20gcmF3IHRleHQgdG8gc3RydWN0dXJlZCB0b2tlbnMsIGhpZ2hsaWdodGluZyBrZXkgbGluZ3Vpc3RpYyB1bml0cyBzdWNoIGFzIGxleGljb25zLCBwaG9uZW1lcywgZ3JhcGhlbWVzLCBtb3JwaGVtZXMsIGFuZCB0b2tlbml6YXRpb24gc3RyYXRlZ2llcyB1c2VkIGluIG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZy4NCg0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLVN0ZXAxLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIkNvcmUgQ29tcG9uZW50cyBvZiBMaW5ndWlzdGljIFJlcHJlc2VudGF0aW9uLiBTb3VyY2U6IENyZWF0ZWQgYnkgdGhlIGF1dGhvciB3aXRoIENoYXRHUFQgKE9wZW5BSSkiLCBvdXQud2lkdGggPSAiODAlIn0NCiMgZmlnLndpZHRoID0gMjAgIyBObyBmdW5jaW9uYSBlc3RhIG9wY2lvbiBlbiBlbCBjaHVuaw0KDQojaHR0cDovL3pldnJvc3MuY29tL2Jsb2cvMjAxNy8wNi8xOS90aXBzLWFuZC10cmlja3MtZm9yLXdvcmtpbmctd2l0aC1pbWFnZXMtYW5kLWZpZ3VyZXMtaW4tci1tYXJrZG93bi1kb2N1bWVudHMvDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJTdGVwMS5wbmciKQ0KDQojT3RyYSBtYW5lcmEsIHBlcm8gbm8gc2FsZSBlbCBjYXB0aW9uOg0KIzxjZW50ZXI+DQojIVsoI2ZpZzpGaWctY2FwdGlvbikgTWkgZmlndXJhXShOb21icmUucG5nKXt3aWR0aD00MDBweH0NCiM8L2NlbnRlcj4NCmBgYA0KPC9jZW50ZXI+DQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIE1vdGl2YXRpb246IFRyYW5zZm9ybWVycyBhcmNoaXRlY3R1cmUNCg0KQXMgYSBtb3RpdmF0aW5nIGV4YW1wbGUsIGNvbnNpZGVyIHRoZSBUcmFuc2Zvcm1lciBhcmNoaXRlY3R1cmUgWyhWYXN3YW5pIGV0IGFsLiwgMjAxNyldKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xNzA2LjAzNzYyKSwgd2lkZWx5IHVzZWQgaW4gbW9kZXJuIE5MUC4gU2VlIEZpZ3VyZSBcQHJlZihmaWc6RmlnLVRyYW5zZikuIA0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLVRyYW5zZiwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJHZW5lcmFsIGFyY2hpdGVjdHVyZSBvZiB0aGUgVHJhbnNmb3JtZXIgbW9kZWwuIFNvdXJjZTogW1Zhc3dhbmkgZXQgYWwuICgyMDE3KV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE3MDYuMDM3NjIpIiwgb3V0LndpZHRoID0gIjU1JSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiRmlnLVRyYW5zZi5wbmciKQ0KDQojT3RyYSBtYW5lcmEsIHBlcm8gbm8gc2FsZSBlbCBjYXB0aW9uOg0KIzxjZW50ZXI+DQojIVsoI2ZpZzpGaWctY2FwdGlvbikgTWkgZmlndXJhXShOb21icmUucG5nKXt3aWR0aD00MDBweH0NCiM8L2NlbnRlcj4NCmBgYA0KPC9jZW50ZXI+DQoNClRoZSBwcm9jZXNzIGJlZ2lucyB3aXRoICoqdG9rZW5pemF0aW9uKiogKGBJbnB1dHNgLCBzaG93biBhdCB0aGUgbG93ZXIgbGVmdCBvZiB0aGUgZmlndXJlKSwgd2hlcmUgaW5wdXQgc2VudGVuY2VzIGFyZSBicm9rZW4gZG93biBpbnRvIGJhc2ljIHVuaXRzICh1c3VhbGx5IHN1YndvcmRzIG9yIHdvcmQtcGllY2VzKS4gVGhlc2UgdW5pdHMgdGhlbiBmbG93IHRocm91Z2ggdGhlIGVudGlyZSBtb2RlbC4gVG8gdW5kZXJzdGFuZCB0aGF0IGluaXRpYWwgc3RlcCwgd2XigJlsbCBzdHVkeSBob3cgdm9jYWJ1bGFyaWVzIGFyZSBidWlsdCBhbmQgaG93IGxhbmd1YWdlIGlzIHJlcHJlc2VudGVkIHN5bWJvbGljYWxseS4gDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIFVuZGVyc3RhbmRpbmcgdGhlIGZpcnN0IHN0YWdlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KVG8gdW5kZXJzdGFuZCB0aGlzIGluaXRpYWwgc3RhZ2UsIGl0IGlzIG5lY2Vzc2FyeSB0byBleGFtaW5lIGhvdyB2b2NhYnVsYXJpZXMgYXJlIGNvbnN0cnVjdGVkIGFuZCBob3cgbGFuZ3VhZ2UgaXMgcmVwcmVzZW50ZWQgaW4gc3ltYm9saWMgZm9ybS4gVGhpcyBsZWFkcyB1cyB0byBmdW5kYW1lbnRhbCBjb25jZXB0cyBzdWNoIGFzICpsZXhpY29ucyosICpwaG9uZW1lcyosICpncmFwaGVtZXMqLCAqbW9ycGhlbWVzKiwgKnRva2VuaXphdGlvbiBtZXRob2RzKiwgYW5kICpub3JtYWxpemF0aW9uIHByb2NlZHVyZXMqLCB3aGljaCB0b2dldGhlciBkZWZpbmUgaG93IHJhdyB0ZXh0IGlzIHRyYW5zZm9ybWVkIGludG8gbW9kZWwtcmVhZHkgaW5wdXRzLg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgVGVjaG5pY2FsIHJlcXVpcmVtZW50cw0KDQpBbGwgY29kZSBleGFtcGxlcyB1c2VkIGluIHRoaXMgc2VjdGlvbiBhcmUgYXZhaWxhYmxlIGluIHRoZSBmb2xsb3dpbmcgR2l0SHViIHJlcG9zaXRvcnk6IA0KDQo8aHR0cHM6Ly9naXRodWIuY29tL1BhY2t0UHVibGlzaGluZy9IYW5kcy1Pbi1QeXRob24tTmF0dXJhbC1MYW5ndWFnZS1Qcm9jZXNzaW5nL3RyZWUvbWFzdGVyL0NoYXB0ZXIwMz4uDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMgTGV4aWNvbnMNCg0KIyMjIyBEZWZpbml0aW9uLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KLSBBICoqbGV4aWNvbioqIGlzIHRoZSBzZXQgb2Ygd29yZHMgdXNlZCBpbiBhIGxhbmd1YWdlIG9yIHdpdGhpbiBhIHNwZWNpZmljIGRvbWFpbi4gDQoNCi0gSW4gcHJhY3RpY2UsIGl0IGZ1bmN0aW9ucyBsaWtlIGEgZGljdGlvbmFyeSB0aGF0IGRlZmluZXMgd2hpY2ggdGVybXMgYXJlIG1lYW5pbmdmdWwgaW4gYSBnaXZlbiBjb250ZXh0Lg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIEV4YW1wbGVzLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KVGhlIGZvbGxvd2luZyBleGFtcGxlcyBpbGx1c3RyYXRlIGhvdyBsZXhpY29ucyB2YXJ5IGRlcGVuZGluZyBvbiB0aGUgY29udGV4dCBvciBkb21haW46DQoNCi0gR2VuZXJhbCBsYW5ndWFnZTogYGhvdXNlYCwgYHJ1bmAsIGBoYXBweWANCg0KLSBNZWRpY2luZTogYGRpYWdub3Npc2AsIGBkb3NhZ2VgLCBgc3ltcHRvbWANCg0KLSBUZWNobm9sb2d5OiBgYWxnb3JpdGhtYCwgYGRhdGFzZXRgLCBgbW9kZWxgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMjIyMgTm90ZS4gey51bmxpc3RlZCAudW5udW1iZXJlZH0gDQoNCi0gSW4gbmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nLCB0aGUgbGV4aWNvbiBkZWZpbmVzIHRoZSAqdm9jYWJ1bGFyeSBhIG1vZGVsIGNhbiByZWNvZ25pemUgYW5kIHByb2Nlc3MqLiAgDQoNCi0gV29yZHMgbm90IGluY2x1ZGVkIGluIHRoZSBsZXhpY29uIGFyZSB0eXBpY2FsbHkgaWdub3JlZCwgcmVwbGFjZWQsIG9yIGRlY29tcG9zZWQgaW50byBzbWFsbGVyIHVuaXRzLg0KDQotIEZvciB0aGlzIHJlYXNvbiwgYnVpbGRpbmcgb3IgbGVhcm5pbmcgYSBsZXhpY29uIGlzIGEgZm91bmRhdGlvbmFsIHN0ZXAgYmVmb3JlIHRva2VuaXphdGlvbiBhbmQgbW9kZWxpbmcuDQoNCi0gVGhlIEZpZ3VyZSBcQHJlZihmaWc6RmlnLUxleGljb24pIGNsYXJpZmllcyB0aGUgY29uY2VwdHVhbCBkaWZmZXJlbmNlcyBiZXR3ZWVuIGNsb3NlbHkgcmVsYXRlZCB0ZXJtcyB0aGF0IGFyZSBvZnRlbiB1c2VkIGludGVyY2hhbmdlYWJseSBpbiBOTFAgYW5kIGxpbmd1aXN0aWNzLiBJdCBkaXN0aW5ndWlzaGVzIGJldHdlZW4gbGV4aWNvbiwgdm9jYWJ1bGFyeSwgYW5kIHdvcmQsIGhpZ2hsaWdodGluZyB0aGVpciBzY29wZSwgdXNhZ2UsIGFuZCByb2xlIGluIGxhbmd1YWdlIHJlcHJlc2VudGF0aW9uLg0KDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctTGV4aWNvbiwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJMZXhpY29uIHZzIHZvY2FidWxhcnkgdnMgd29yZC4gU291cmNlOiBDcmVhdGVkIGJ5IHRoZSBhdXRob3Igd2l0aCBDaGF0R1BUIChPcGVuQUkpIiwgb3V0LndpZHRoID0gIjgwJSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiTGV4aWNvbi5wbmciKQ0KDQojT3RyYSBtYW5lcmEsIHBlcm8gbm8gc2FsZSBlbCBjYXB0aW9uOg0KIzxjZW50ZXI+DQojIVsoI2ZpZzpGaWctY2FwdGlvbikgTWkgZmlndXJhXShOb21icmUucG5nKXt3aWR0aD00MDBweH0NCiM8L2NlbnRlcj4NCmBgYA0KPC9jZW50ZXI+DQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIFBob25vbWVzLCBncmFwaGVtZXMsIGFuZCBtb3JwaGVtZXMNCg0KQmVmb3JlIGJ1aWxkaW5nIGEgdm9jYWJ1bGFyeSwgaXQgaXMgdXNlZnVsIHRvIHVuZGVyc3RhbmQgdGhyZWUgYmFzaWMgbGluZ3Vpc3RpYyB1bml0czogcGhvbmVtZXMsIGdyYXBoZW1lcywgYW5kIG1vcnBoZW1lcy4gIFRoZXNlIGNvbmNlcHRzIHByb3ZpZGUgdGhlIGxpbmd1aXN0aWMgZm91bmRhdGlvbiBmb3IgdGhlIHRva2VuaXphdGlvbiBhbmQgcmVwcmVzZW50YXRpb24gbWV0aG9kcyB1c2VkIGluIE5MUCBzeXN0ZW1zLg0KDQojIyMgUGhvbmVtZXMuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpUaGV5IGFyZSB0aGUgc21hbGxlc3Qgc291bmQgdW5pdHMgdGhhdCBkaXN0aW5ndWlzaCBtZWFuaW5nIGluIHNwb2tlbiBsYW5ndWFnZS4gICpFeGFtcGxlczogKg0KDQoxLiBFbmdsaXNoOiB0aGUgc291bmRzIGAvay9gLCBgL8OmL2AsIGFuZCBgL3QvYCBmb3JtIHRoZSB3b3JkICpjYXQqLg0KDQoyLiBFbmdsaXNoOiB0aGUgc291bmRzIGAvZi9gLCBgL3XLkC9gLCBhbmQgYC9kL2AgZm9ybSB0aGUgd29yZCAqZm9vZCouDQoNCjIuIEZyZW5jaDogdGhlIHNvdW5kcyBgL8qDL2AsIGAvYS9gLCBhbmQgYC90L2AgZm9ybSB0aGUgd29yZCAqY2hhdCouDQoNCjMuIFNwYW5pc2g6IHRoZSBzb3VuZHMgYC9nL2AsIGAvYS9gLCBhbmQgYC90L2AsIGAvby9gIGZvcm0gdGhlIHdvcmQgKmdhdG8qLg0KDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctUGhvbmVtZSwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJQaG9uZW1lcy4gU291cmNlOiBbQ2hhcmdlIE1vbW15IEJvb2tzXShodHRwczovL2NoYXJnZW1vbW15Ym9va3MuY29tL3JlYWRpbmcvcGhvbmVtaWMtYXdhcmVuZXNzKSIsIG91dC53aWR0aCA9ICI3NSUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIlBob25lbWVzLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgR3JhcGhlbWVzLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfSANCg0KVGhleSBhcmUgbGV0dGVycyBvciBsZXR0ZXIgZ3JvdXBzIHRoYXQgcmVwcmVzZW50IHBob25lbWVzIGluIHdyaXR0ZW4gbGFuZ3VhZ2UuICAqRXhhbXBsZXM6ICoNCg0KMS4gSW4gKnNwb29uKiwgdGhlIGdyYXBoZW1lcyBgc2AsIGBwYCwgYG9vYCwgYW5kIGBuYCByZXByZXNlbnQgZm91ciBwaG9uZW1lcy4NCg0KMi4gSW4gKnNoZWV0KiwgdGhlIGdyYXBoZW1lcyBgc2hgLCBgZWVgLCBhbmQgYHRgIHJlcHJlc2VudCB0aHJlZSBwaG9uZW1lcy4NCg0KMy4gSW4gKnNoaXAqLCB0aGUgZ3JhcGhlbWVzIGBzaGAsIGBpYCwgYW5kIGBwYCByZXByZXNlbnQgdGhyZWUgcGhvbmVtZXMuDQoNCjQuIEluICpmb29kKiwgdGhlIGdyYXBoZW1lIGBvb2AgcmVwcmVzZW50cyBhIHNpbmdsZSBwaG9uZW1lICh3aXRoaW4gYSB3b3JkIHRoYXQgY29udGFpbnMgbXVsdGlwbGUgcGhvbmVtZXMpLiAgDQoNCjUuIEluICpjaGF0KiwgdGhlIGdyYXBoZW1lIGBjaGAgcmVwcmVzZW50cyBhIHNpbmdsZSBwaG9uZW1lLiANCg0KNi4gSW4gU3BhbmlzaCwgdGhlIHdvcmQgKmdhdG8qIGlzIGNvbXBvc2VkIG9mIHRoZSBmb2xsb3dpbmcgZ3JhcGhlbWVzOiBgZ2AsIGBhYCwgYHRgLCBhbmQgYG9gLiBFYWNoIGdyYXBoZW1lIGNvcnJlc3BvbmRzIHRvIGEgd3JpdHRlbiB1bml0IHRoYXQgcmVwcmVzZW50cyBhIHBob25lbWUgaW4gdGhlIHdvcmQuDQoNCg0KPGNlbnRlcj4NCmBgYHtyIEZpZy1HcmFwaGVtZSwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJHcmFwaGVtZXMuIFNvdXJjZTogW1JlYWRpbmdEb2N0b3JdKGh0dHBzOi8vd3d3LnJlYWRpbmdkb2N0b3IuY29tLmF1L3Bob25lbWVzLWdyYXBoZW1lcy1sZXR0ZXJzLXdvcmQtYnVyZ2VyKSIsIG91dC53aWR0aCA9ICI0NSUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkdyYXBoZW1lcy5wbmciKQ0KDQojT3RyYSBtYW5lcmEsIHBlcm8gbm8gc2FsZSBlbCBjYXB0aW9uOg0KIzxjZW50ZXI+DQojIVsoI2ZpZzpGaWctY2FwdGlvbikgTWkgZmlndXJhXShOb21icmUucG5nKXt3aWR0aD00MDBweH0NCiM8L2NlbnRlcj4NCmBgYA0KPC9jZW50ZXI+DQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIE1vcnBoZW1lcy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0gDQoNClRoZXkgYXJlIHRoZSBzbWFsbGVzdCB1bml0cyB0aGF0IGNhcnJ5IG1lYW5pbmcuICAqRXhhbXBsZXM6ICoNCg0KMS4gVGhlIHdvcmQgKnVuaGFwcGluZXNzKiBjYW4gYmUgZGVjb21wb3NlZCBpbnRvIHRocmVlIG1vcnBoZW1lczoNCg0KICAgLSBgdW4tYCAoYSBib3VuZCBtb3JwaGVtZSBzaWduaWZ5aW5nICpub3QqKS4NCiAgICANCiAgIC0gYGhhcHB5YCAodGhlIHJvb3QgbW9ycGhlbWUpLg0KICAgIA0KICAgLSBgLW5lc3NgIChhIGZyZWUgbW9ycGhlbWUgc2lnbmlmeWluZyAqc3RhdGUqIG9yICpxdWFsaXR5KikuDQogICANCiAgIA0KMi4gVGhlIHdvcmQgKnRlYWNoZXIqIGNvbnNpc3RzIG9mIHR3byBtb3JwaGVtZXM6DQoNCiAgIC0gYHRlYWNoYCAocm9vdCkuDQoNCiAgIC0gYC1lcmAgKGEgcGVyc29uIHdobyBwZXJmb3JtcyBhbiBhY3Rpb24pLg0KICAgDQoNCjMuIFRoZSB3b3JkICpyZXVzYWJsZSogY2FuIGJlIGRlY29tcG9zZWQgaW50bzoNCiAgICANCiAgIC0gYHJlLWAgKGFnYWluKS4NCiAgICANCiAgIC0gYHVzZWAgKHJvb3QpLg0KICAgIA0KICAgLSBgLWFibGVgIChjYW4gYmUgZG9uZSkuDQogICANCiAgIA0KNC4gVGhlIHdvcmQgKmltcG9ydGVkKiBjYW4gYmUgZGVjb21wb3NlZCBpbnRvOg0KICAgIA0KICAgLSBgaW0tYCAocHJlZml4KS4NCiAgICANCiAgIC0gYHBvcnRgIChyb290KS4NCiAgICANCiAgIC0gYC1lZGAgKHN1Zml4KS4NCg0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLU1vcnBoZW1lLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIk1vcnBoZW1lcy4gU291cmNlOiBbTGl0ZXJhY3kgTGVhcm5dKGh0dHBzOi8vbGl0ZXJhY3lsZWFybi5jb20vYWJvdXQtcGhvbmVtZXMtZ3JhcGhlbWVzLW1vcnBoZW1lcy8pIiwgb3V0LndpZHRoID0gIjQwJSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiTW9ycGhlbWVzLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIFRva2VuaXphdGlvbg0KDQojIyMgVG9rZW4gYW5kIHRva2VuaXphdGlvbg0KDQpUbyBjb25zdHJ1Y3QgYSB2b2NhYnVsYXJ5LCB0ZXh0IG11c3QgZmlyc3QgYmUgZGl2aWRlZCBpbnRvIHNtYWxsZXIgdW5pdHMgY2FsbGVkICoqdG9rZW5zKiouIA0KDQpUaGlzIHByb2Nlc3MsIGtub3duIGFzICoqdG9rZW5pemF0aW9uKiosIGNvbnNpc3RzIG9mIHNlZ21lbnRpbmcgc2VudGVuY2VzIG9yIGRvY3VtZW50cyBpbnRvIG1lYW5pbmdmdWwgZWxlbWVudHMgdGhhdCBjYW4gYmUgcHJvY2Vzc2VkIGJ5IGEgbW9kZWwuDQoNCkluIG1vc3QgY2FzZXMsIHRva2VucyBjb3JyZXNwb25kIHRvIHdvcmRzIG9yIG51bWJlcnMsIGFsdGhvdWdoIHB1bmN0dWF0aW9uIHN5bWJvbHMgYW5kIG90aGVyIHRleHR1YWwgZWxlbWVudHMgbWF5IGFsc28gYmUgdHJlYXRlZCBhcyB0b2tlbnMgZGVwZW5kaW5nIG9uIHRoZSBhcHBsaWNhdGlvbi4gVGhlIEZpZ3VyZSBcQHJlZihmaWc6RmlnLVRva2VuMSkgaXMgYSB2aXN1YWwgcmVwcmVzZW50YXRpb24gb2YgdG9rZW5zIGdlbmVyYXRlZCBieSBncHQtNG8gb24gW1Rpa3Rva2VuaXplcl0oaHR0cHM6Ly90aWt0b2tlbml6ZXIudmVyY2VsLmFwcC8pOg0KDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctVG9rZW4xLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIlZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiB0b2tlbnMgZ2VuZXJhdGVkIGJ5IGdwdC00byBvbiBbVGlrdG9rZW5pemVyXShodHRwczovL3Rpa3Rva2VuaXplci52ZXJjZWwuYXBwLykiLCBvdXQud2lkdGggPSAiMTAwJSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiVG9rZW4xLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpBIHNpbXBsZSBleGFtcGxlIGlsbHVzdHJhdGVzIHRoaXMgaWRlYToNCg0KYGBge3B5dGhvbn0NCnNlbnRlbmNlID0gIk1hY2hpbmUgbGVhcm5pbmcgaW1wcm92ZXMgZGVjaXNpb24gbWFraW5nIg0Kc2VudGVuY2Uuc3BsaXQoKQ0KYGBgDQoNClRoaXMgYmFzaWMgc3BsaXR0aW5nIG9wZXJhdGlvbiBzZXBhcmF0ZXMgdGhlIHNlbnRlbmNlIGludG8gaW5kaXZpZHVhbCB3b3JkIHRva2Vucy4gV2hpbGUgdGhpcyBhcHByb2FjaCBpcyBpbnR1aXRpdmUsIHJlYWwtd29ybGQgdG9rZW5pemF0aW9uIGlzIG9mdGVuIG1vcmUgY29tcGxleCBhbmQgcmVxdWlyZXMgbW9yZSBhZHZhbmNlZCBzdHJhdGVnaWVzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBJc3N1ZXMgd2l0aCB0b2tlbml6YXRpb24NCg0KUmVhbC13b3JsZCB0ZXh0IHByZXNlbnRzIG11bHRpcGxlIGNoYWxsZW5nZXMgdGhhdCBjYW5ub3QgYmUgYWRlcXVhdGVseSBoYW5kbGVkIGJ5IHNpbXBsZSB0b2tlbml6YXRpb24gcnVsZXMgc3VjaCBhcyB3aGl0ZXNwYWNlIHNwbGl0dGluZy4gIA0KRmlndXJlIFxAcmVmKGZpZzpGaWctSXNzdWUpIHByb3ZpZGVzIGEgaGlnaC1sZXZlbCBvdmVydmlldyBvZiBzb21lIGNvbW1vbiB0b2tlbml6YXRpb24gaXNzdWVzIGVuY291bnRlcmVkIGluIG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZywgaW5jbHVkaW5nIGFwb3N0cm9waGVzLCBjb250cmFjdGlvbnMsIG11bHRpLXdvcmQgZXhwcmVzc2lvbnMsIHB1bmN0dWF0aW9uLCBub24tbGV4aWNhbCB0b2tlbnMsIGFuZCBzb2NpYWwgbWVkaWEgYXJ0aWZhY3RzLg0KDQpUaGVzZSBpc3N1ZXMgYXJlIGludHJvZHVjZWQgaGVyZSBmb3IgY29uY2VwdHVhbCBvcmllbnRhdGlvbi4gRWFjaCBjYXNlIHdpbGwgYmUgZXhhbWluZWQgaW4gZGV0YWlsIGluIHRoZSBmb2xsb3dpbmcgc3Vic2VjdGlvbnMsIHRvZ2V0aGVyIHdpdGggaWxsdXN0cmF0aXZlIGV4YW1wbGVzIGFuZCBkaXNjdXNzaW9uIG9mIHdoeSBtb3JlIHNvcGhpc3RpY2F0ZWQgdG9rZW5pemF0aW9uIHN0cmF0ZWdpZXMgYXJlIHJlcXVpcmVkLg0KDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctSXNzdWUsIGVjaG89RkFMU0UsIGZpZy5jYXAgPSAiSXNzdWVTIHdpdGggdG9rZW5pemF0aW9uLiAgU291cmNlOiBDcmVhdGVkIGJ5IHRoZSBhdXRob3Igd2l0aCBDaGF0R1BUIChPcGVuQUkpIiwgb3V0LndpZHRoID0gIjkwJSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiSXNzdWUucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQoNCiMjIyMgQXBvc3Ryb3BoZXMgYW5kIHBvc3Nlc3NpdmVzIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9IA0KDQpTaW1wbGUgdG9rZW5pemF0aW9uIG1ldGhvZHMgb2Z0ZW4gc3RydWdnbGUgd2l0aCBjb21tb24gbGFuZ3VhZ2UgcGF0dGVybnMuIENvbnNpZGVyIHRoZSBmb2xsb3dpbmcgc2VudGVuY2U6DQoNCmBgYHtweXRob259DQpzZW50ZW5jZSA9ICJNYWNoaW5lIGxlYXJuaW5nJ3MgaW1wYWN0IGlzIGdyb3dpbmciDQpzZW50ZW5jZS5zcGxpdCgpDQpgYGANCg0KSGVyZSwgdGhlIHRva2VuaXplciBjYW5ub3QgZGV0ZXJtaW5lIHdoZXRoZXIgdGhlIGNvcnJlY3QgdG9rZW4gc2hvdWxkIGJlIGBsZWFybmluZ2AsIGBsZWFybmluZ3NgLCBvciBgbGVhcm5pbmcnc2AuIEFwb3N0cm9waGVzIGludHJvZHVjZSBhbWJpZ3VpdHkgdGhhdCBiYXNpYyBzcGxpdHRpbmcgcnVsZXMgY2Fubm90IHJlc29sdmUuIA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBDb250cmFjdGlvbnMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KQ29udHJhY3Rpb25zIHByZXNlbnQgYSBzaW1pbGFyIGNoYWxsZW5nZS4gRm9yIGV4YW1wbGU6DQoNCmBgYHtweXRob259DQpzZW50ZW5jZSA9ICJXZSdsbCBhcHBseSBtYWNoaW5lIGxlYXJuaW5nIHRvbW9ycm93Ig0Kc2VudGVuY2Uuc3BsaXQoKQ0KYGBgDQoNClRoZSBjb250cmFjdGlvbiBgd2UnbGxgIGFjdHVhbGx5IHJlcHJlc2VudHMgYHdlIHdpbGxgLCBidXQgYSBzaW1wbGUgc3BsaXQgZG9lcyBub3QgY2FwdHVyZQ0KdGhpcyBkaXN0aW5jdGlvbi4gSWRlYWxseSwgYSB0b2tlbml6ZXIgc2hvdWxkIGNvbnZlcnQgaXQgaW50byB0d28gdG9rZW5zOiBgd2VgIGFuZCBgd2lsbGAuDQoNCkEgcmVsYXRlZCBjYXNlIGFwcGVhcnMgd2l0aCBwcm9ub3VuIGNvbnRyYWN0aW9uczoNCg0KDQpgYGB7cHl0aG9ufQ0Kc2VudGVuY2UgPSAiSSdtIHN0dWR5aW5nIG1hY2hpbmUgbGVhcm5pbmciDQpzZW50ZW5jZS5zcGxpdCgpDQpgYGANCg0KSGVyZSwgYEknbWAgc2hvdWxkIGJlIGludGVycHJldGVkIGFzIGBJIGFtYCwgd2hpY2ggYWdhaW4gcmVxdWlyZXMgbGluZ3Vpc3RpYyBhd2FyZW5lc3MgYmV5b25kIHNpbXBsZSBzdHJpbmcgc3BsaXR0aW5nLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBNdWx0aS13b3JkIGV4cHJlc3Npb25zIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KTXVsdGktd29yZCBleHByZXNzaW9ucyBhbHNvIHJhaXNlIGltcG9ydGFudCBxdWVzdGlvbnMuIENvbnNpZGVyOg0KDQpgYGB7cHl0aG9ufQ0Kc2VudGVuY2UgPSAiRGVlcCBsZWFybmluZyBpcyBhIGJyYW5jaCBvZiBtYWNoaW5lIGxlYXJuaW5nIg0Kc2VudGVuY2Uuc3BsaXQoKQ0KYGBgDQoNClNob3VsZCBgbWFjaGluZSBsZWFybmluZ2AgYmUgdHJlYXRlZCBhcyB0d28gc2VwYXJhdGUgdG9rZW5zIG9yIGFzIGEgc2luZ2xlIG1lYW5pbmdmdWwgdW5pdD8NCkluIG1hbnkgY29udGV4dHMsIGl0IGZ1bmN0aW9ucyBhcyBvbmUgY29uY2VwdCByYXRoZXIgdGhhbiB0d28gaW5kZXBlbmRlbnQgd29yZHMuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFB1bmN0dWF0aW9uIGFuZCBhYmJyZXZpYXRpb25zIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClB1bmN0dWF0aW9uIGludHJvZHVjZXMgYWRkaXRpb25hbCBjb21wbGV4aXR5LiBJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUsIHRoZSBwZXJpb2QgZG9lcw0Kbm90IG1hcmsgdGhlIGVuZCBvZiBhIHNlbnRlbmNlOg0KDQoNCmBgYHtweXRob259DQpzZW50ZW5jZSA9ICJTaGUgaG9sZHMgYSBQaC5ELiBpbiBtYWNoaW5lIGxlYXJuaW5nIg0Kc2VudGVuY2Uuc3BsaXQoKQ0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBOb24tbGV4aWNhbCB0b2tlbnMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQpGaW5hbGx5LCBub3QgYWxsIHRva2VucyBhcmUgc3RhbmRhcmQgd29yZHMuIFNvbWUgZWxlbWVudHMgbWF5IGFwcGVhciBtZWFuaW5nbGVzcyBidXQgc3RpbGwNCmNhcnJ5IGNvbnRleHR1YWwgdmFsdWU6DQoNCg0KDQpgYGB7cHl0aG9ufQ0Kc2VudGVuY2UgPSAiSSB3YXMgdW1tIHRoaW5raW5nIGFib3V0IHRoaXMgcHJvYmxlbSINCnNlbnRlbmNlLnNwbGl0KCkNCmBgYA0KDQpBbHRob3VnaCBgdW1tYCBpcyBub3QgcGFydCBvZiBmb3JtYWwgdm9jYWJ1bGFyeSwgaXQgbWF5IGJlIHJlbGV2YW50IGluIGFwcGxpY2F0aW9ucyBzdWNoIGFzDQpzcGVlY2ggYW5hbHlzaXMuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRva2VuaXphdGlvbiBpbiBzb2NpYWwgbWVkaWEgdGV4dCB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUZXh0IGZyb20gc29jaWFsIG1lZGlhIGludHJvZHVjZXMgYWRkaXRpb25hbCBjaGFsbGVuZ2VzIGZvciB0b2tlbml6YXRpb24uIEl0IG9mdGVuIGNvbnRhaW5zIGVtb3RpY29ucywgZW1vamlzLCBoYXNodGFncywgcmVwZWF0ZWQgcHVuY3R1YXRpb24sIGFuZCBpbmZvcm1hbCBleHByZXNzaW9ucyB0aGF0IGRvIG5vdCBmb2xsb3cgc3RhbmRhcmQgZ3JhbW1hdGljYWwgcnVsZXMuDQoNCkNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgZXhhbXBsZToNCg0KYGBge3B5dGhvbn0NCnNlbnRlbmNlID0gIkxlYXJuaW5nIE5MUCBpcyBmdW4hISA+MTAg8J+YhCDwn5qAICNOTFAgI0FJIg0Kc2VudGVuY2Uuc3BsaXQoKQ0KYGBgDQoNCg0KQSBzaW1wbGUgc3BsaXQgZmFpbHMgdG8gY29ycmVjdGx5IGhhbmRsZSBlbGVtZW50cyBzdWNoIGFzIGVtb3RpY29ucyAo8J+YhCwg8J+agCksIHN5bWJvbHMgKGA+MTBgKSwgaGFzaHRhZ3MgKGAjTkxQYCwgYCNBSWApLCBhbmQgcmVwZWF0ZWQgcHVuY3R1YXRpb24gKGAhIWApLiBBbHRob3VnaCB0aGVzZSBlbGVtZW50cyBhcmUgbm90IHN0YW5kYXJkIHdvcmRzLCB0aGV5IG9mdGVuIGNvbnZleSBlbW90aW9uYWwgdG9uZSwgZW1waGFzaXMsIG9yIHRvcGljYWwgaW5mb3JtYXRpb24gdGhhdCBjYW4gYmUgcmVsZXZhbnQgaW4gTkxQIHRhc2tzLiBUaGVzZSBsaW1pdGF0aW9ucyBtb3RpdmF0ZSB0aGUgdXNlIG9mIHNwZWNpYWxpemVkIHRva2VuaXplcnMsIHN1Y2ggYXMgYFR3ZWV0VG9rZW5pemVyYCBmcm9tIHRoZSBgbmx0a2AgcGFja2FnZSwgd2hpY2ggd2UgaW50cm9kdWNlIG5leHQuDQoNCg0KVGhlc2UgZXhhbXBsZXMgaGlnaGxpZ2h0IHdoeSByZWFsLXdvcmxkIE5MUCBzeXN0ZW1zIHJlbHkgb24gbW9yZSBzb3BoaXN0aWNhdGVkIHRva2VuaXphdGlvbiBzdHJhdGVnaWVzIHRoYW4gc2ltcGxlIHN0cmluZyBzcGxpdHRpbmcuDQoNCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIERpZmZlcmVudCB0eXBlcyBvZiB0b2tlbml6ZXJzDQoNClNvIGZhciwgd2UgaGF2ZSBzZWVuIHRoYXQgc2ltcGxlIHNwbGl0dGluZyBydWxlcyBhcmUgb2Z0ZW4gaW5zdWZmaWNpZW50IGZvciByZWFsIHRleHQuIFRvIGFkZHJlc3MgZGlmZmVyZW50IGxpbmd1aXN0aWMgcGF0dGVybnMgYW5kIHVzZSBjYXNlcywgc2V2ZXJhbCB0eXBlcyBvZiB0b2tlbml6ZXJzIGhhdmUgYmVlbiBkZXZlbG9wZWQuIEluIHRoaXMgc2VjdGlvbiwgd2UgaW50cm9kdWNlOiANCg0KLSBSdWxlLWJhc2VkIHRva2VuaXplcnMgKHN1Y2ggYXMgcmVndWxhciBleHByZXNzaW9u4oCTYmFzZWQgdG9rZW5pemVycyksIA0KDQotIExpbmd1aXN0aWNhbGx5IG1vdGl2YXRlZCB0b2tlbml6ZXJzIChzdWNoIGFzIHRoZSBgVHJlZWJhbmtgIHRva2VuaXplciksIGFuZCANCg0KLSBUb2tlbml6ZXJzIGRlc2lnbmVkIGZvciBpbmZvcm1hbCBhbmQgc29jaWFsIG1lZGlhIHRleHQgKHN1Y2ggYXMgYFR3ZWV0VG9rZW5pemVyYCkuIA0KDQoNCkVhY2ggdHlwZSBpcyBwcmVzZW50ZWQgaW4gdGhlIGZvbGxvd2luZyBzdWJzZWN0aW9ucywgYWxvbmcgd2l0aCBzaW1wbGUgZXhhbXBsZXMgdGhhdCBpbGx1c3RyYXRlIHdoZW4gYW5kIHdoeSBpdCBzaG91bGQgYmUgdXNlZCBpbiBOTFAgYXBwbGljYXRpb25zLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNClRoZSBGaWd1cmUgXEByZWYoZmlnOkZpZy1Ub2tlbml6ZXJzKSBpbGx1c3RyYXRlcyB0aGUgbWFpbiBjYXRlZ29yaWVzIG9mIHRva2VuaXplcnMsIGhpZ2hsaWdodGluZyB0aGVpciB1bmRlcmx5aW5nIHByaW5jaXBsZXMgYW5kIHR5cGljYWwgdXNlIGNhc2VzLCBpbmNsdWRpbmcgcnVsZS1iYXNlZCwgbGluZ3Vpc3RpY2FsbHkgbW90aXZhdGVkLCBhbmQgc29jaWFsIG1lZGlh4oCTb3JpZW50ZWQgYXBwcm9hY2hlcy4uDQoNCg0KPGNlbnRlcj4NCmBgYHtyIEZpZy1Ub2tlbml6ZXJzLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIkRpZmZlcmVudCB0eXBlcyBvZiB0b2tlbml6ZXJzLiBTb3VyY2U6IENyZWF0ZWQgYnkgdGhlIGF1dGhvciB3aXRoIENoYXRHUFQgKE9wZW5BSSkiLCBvdXQud2lkdGggPSAiOTAlIn0NCiMgZmlnLndpZHRoID0gMjAgIyBObyBmdW5jaW9uYSBlc3RhIG9wY2lvbiBlbiBlbCBjaHVuaw0KDQojaHR0cDovL3pldnJvc3MuY29tL2Jsb2cvMjAxNy8wNi8xOS90aXBzLWFuZC10cmlja3MtZm9yLXdvcmtpbmctd2l0aC1pbWFnZXMtYW5kLWZpZ3VyZXMtaW4tci1tYXJrZG93bi1kb2N1bWVudHMvDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJUb2tlbml6ZXJzLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIFJlZ3VsYXIgZXhwcmVzaW9ucw0KDQoqKlJlZ3VsYXIgZXhwcmVzc2lvbnMqKiAoYHJlZ2V4YCkgYXJlIGZvcm1hbCBwYXR0ZXJucyB1c2VkIHRvIGlkZW50aWZ5LCBtYXRjaCwgYW5kIGV4dHJhY3Qgc3BlY2lmaWMgc3RydWN0dXJlcyBpbiB0ZXh0LiBUaGV5IGNvbnN0aXR1dGUgb25lIG9mIHRoZSBlYXJsaWVzdCBhbmQgbW9zdCB3aWRlbHkgdXNlZCB0b29scyBmb3IgdGV4dCBwcm9jZXNzaW5nIGFuZCByZW1haW4gZnVuZGFtZW50YWwgaW4gbWFueSBOTFAgcGlwZWxpbmVzLg0KDQpGaWd1cmUgXEByZWYoZmlnOkZpZy1yZWdleCkgcHJvdmlkZXMgYSB2aXN1YWwgb3ZlcnZpZXcgb2Ygc2V2ZXJhbCBjb21tb25seSB1c2VkIHJlZ3VsYXIgZXhwcmVzc2lvbiBwYXR0ZXJucyBhbmQgaWxsdXN0cmF0ZXMgaG93IHRoZXkgb3BlcmF0ZSBvbiBjb25jcmV0ZSB0ZXh0IGV4YW1wbGVzLg0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLXJlZ2V4LCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIlJlZ3VsYXIgZXhwcmVzc2lvbnMuIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI3NSUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIlJlZ2V4LnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQpSZWdleC1iYXNlZCBhcHByb2FjaGVzIGFyZSBlc3BlY2lhbGx5IHVzZWZ1bCB3aGVuIHRoZSB0YXJnZXQgcGF0dGVybnMgYXJlICp3ZWxsIGRlZmluZWQqIGFuZCBmb2xsb3cgcmVjb2duaXphYmxlIGZvcm1hdHMsIHN1Y2ggYXMgZGF0ZXMsIHByaWNlcywgZW1haWwgYWRkcmVzc2VzLCBudW1lcmljYWwgdmFsdWVzLCBvciBpZGVudGlmaWVycy4gSW4gc3VjaCBjYXNlcywgcnVsZS1iYXNlZCBtZXRob2RzIGFyZSBvZnRlbiBzaW1wbGVyLCBtb3JlIHRyYW5zcGFyZW50LCBhbmQgY29tcHV0YXRpb25hbGx5IG1vcmUgZWZmaWNpZW50IHRoYW4gbWFjaGluZSBsZWFybmluZyBhbHRlcm5hdGl2ZXMuDQoNCkJlY2F1c2UgdGhlc2UgZWxlbWVudHMgZXhoaWJpdCBmaXhlZCBzdHJ1Y3R1cmFsIHJlZ3VsYXJpdGllcywgcmVndWxhciBleHByZXNzaW9ucyBhcmUgcGFydGljdWxhcmx5IHdlbGwgc3VpdGVkIGZvciBydWxlLWJhc2VkIGV4dHJhY3Rpb24gZHVyaW5nIHRva2VuaXphdGlvbiBhbmQgdGV4dCBwcmVwcm9jZXNzaW5nLg0KDQpGb3IgZnVydGhlciByZWFkaW5nIGFuZCBpbnRlcmFjdGl2ZSBwcmFjdGljZSwgc2VlOg0KW01ETiBXZWIgRG9jcyAtIFJlZ3VsYXIgRXhwcmVzc2lvbnNdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0phdmFTY3JpcHQvR3VpZGUvUmVndWxhcl9leHByZXNzaW9ucykgICBhbmQgW3JlZ2V4MTAxXShodHRwczovL3JlZ2V4MTAxLmNvbSkuICAqTUROIFdlYiBEb2NzKiBpcyBhbiBhdXRob3JpdGF0aXZlLCBkZXZlbG9wZXItb3JpZW50ZWQgZG9jdW1lbnRhdGlvbiByZXNvdXJjZSB0aGF0IHByb3ZpZGVzIGNsZWFyIGFuZCBwcmFjdGljYWwgZXhwbGFuYXRpb25zIG9mIHByb2dyYW1taW5nIGNvbmNlcHRzLCBpbmNsdWRpbmcgcmVndWxhciBleHByZXNzaW9ucy4gSW4gY29udHJhc3QsICpyZWdleDEwMSogaXMgYW4gaW50ZXJhY3RpdmUgcGxhdGZvcm0gZGVzaWduZWQgZm9yIHRlc3RpbmcsIHZpc3VhbGl6aW5nLCBhbmQgZGVidWdnaW5nIHJlZ3VsYXIgZXhwcmVzc2lvbiBwYXR0ZXJucyBpbiByZWFsIHRpbWUuLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgQ29tbW9uIGByZWdleGAgbWV0YWNoYXJhY3RlcnMgKHF1aWNrIHJlZmVyZW5jZSkuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KVGhlIHRhYmxlIGJlbG93IHByb3ZpZGVzIGEgKnF1aWNrIHJlZmVyZW5jZSB0byBzb21lIGNvbW1vbmx5IHVzZWQgcmVndWxhciBleHByZXNzaW9uIG1ldGFjaGFyYWN0ZXJzKi4gICBJdCBpcyAqbm90IGV4aGF1c3RpdmUqIChtYW55IGFkZGl0aW9uYWwgc3ltYm9scyBhbmQgY29uc3RydWN0cyBleGlzdCBkZXBlbmRpbmcgb24gdGhlIHJlZ2V4IGVuZ2luZSkgYnV0IGl0IGNvdmVycyB0aGUgbW9zdCBmcmVxdWVudGx5IGVuY291bnRlcmVkIGVsZW1lbnRzIGluIGludHJvZHVjdG9yeSBOTFAgdGFza3MuDQoNCg0KYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KDQpyZWdleF90YmwgPC0gZGF0YS5mcmFtZSgNCiAgTWV0YWNoYXJhY3RlciA9IGMoIlsgXSIsICJcXFxcIiwgIihjb25jYXRlbmF0aW9uKSIsICJcXCsiLCAiXFxeIiwgIlxcKiIsICJcXC4iLCAiXFwkIiwgIlxcPyIsICJcXHsgXFx9IiwgIiggKSIsICIhIiksDQogIE5hbWUgICAgICAgICAgPSBjKCJTcXVhcmUgYnJhY2tldHMiLCAiQmFja3NsYXNoIChlc2NhcGUpIiwgIlNlcXVlbmNlIChBTkQpIiwgIlBsdXMiLCAiQ2FyZXQiLCAiQXN0ZXJpc2siLCAiRG90IiwgIkRvbGxhciBzaWduIiwgIlF1ZXN0aW9uIG1hcmsiLCAiQ3VybHkgYnJhY2VzIiwgIlBhcmVudGhlc2VzIiwgIkV4Y2xhbWF0aW9uIG1hcmsiKSwNCiAgTWVhbmluZyAgICAgICA9IGMoDQogICAgIkNoYXJhY3RlciBjbGFzczogbWF0Y2hlcyBvbmUgY2hhcmFjdGVyIGZyb20gYSBzcGVjaWZpZWQgc2V0L3JhbmdlIChlLmcuLCBgW2FiY11gLCBgWzAtOV1gKS4iLA0KICAgICJFc2NhcGVzIGEgbWV0YWNoYXJhY3RlciAob3IgaW50cm9kdWNlcyBzcGVjaWFsIHNlcXVlbmNlcyBkZXBlbmRpbmcgb24gdGhlIHJlZ2V4IGVuZ2luZSkuIiwNCiAgICAiTG9naWNhbCBBTkQgZXhwcmVzc2VkIGJ5IHNlcXVlbmNlOiBwYXR0ZXJucyBtdXN0IGFwcGVhciBjb25zZWN1dGl2ZWx5IChlLmcuLCBgY2F0ZG9nYCBtZWFucyBgY2F0YCBBTkQgYGRvZ2ApLiIsDQogICAgIlF1YW50aWZpZXI6IHJlcGVhdHMgdGhlIHByZXZpb3VzIHRva2VuIG9uZSBvciBtb3JlIHRpbWVzLiIsDQogICAgIkFuY2hvcnMgdGhlIG1hdGNoIGF0IHRoZSBzdGFydCBvZiB0aGUgc3RyaW5nL2xpbmUgKGRlcGVuZGluZyBvbiBmbGFncykuIiwNCiAgICAiUXVhbnRpZmllcjogcmVwZWF0cyB0aGUgcHJldmlvdXMgdG9rZW4gemVybyBvciBtb3JlIHRpbWVzLiIsDQogICAgIldpbGRjYXJkOiBtYXRjaGVzIChhbG1vc3QpIGFueSBzaW5nbGUgY2hhcmFjdGVyIGV4Y2VwdCBuZXdsaW5lIChieSBkZWZhdWx0KS4iLA0KICAgICJBbmNob3JzIHRoZSBtYXRjaCBhdCB0aGUgZW5kIG9mIHRoZSBzdHJpbmcvbGluZSAoZGVwZW5kaW5nIG9uIGZsYWdzKS4iLA0KICAgICJRdWFudGlmaWVyOiBtYWtlcyB0aGUgcHJldmlvdXMgdG9rZW4gb3B0aW9uYWwgKHplcm8gb3Igb25lIHRpbWUpLiIsDQogICAgIlF1YW50aWZpZXI6IHJlcGVhdHMgdGhlIHByZXZpb3VzIHRva2VuIGEgc3BlY2lmaWVkIG51bWJlciBvZiB0aW1lcyAoZS5nLiwgYHszfWAsIGB7Miw1fWApLiIsDQogICAgIkdyb3VwaW5nOiBncm91cHMgdG9rZW5zOyBhbHNvIHVzZWQgdG8gY2FwdHVyZSBzdWJwYXR0ZXJucyBmb3IgbGF0ZXIgcmVmZXJlbmNlLiIsDQogICAgIk5lZ2F0aW9uIChlbmdpbmUtZGVwZW5kZW50KTogb2Z0ZW4gdXNlZCBmb3Ig4oCcTk9U4oCdL25lZ2F0aXZlIGNvbnN0cnVjdHMgKG5vdCB1bml2ZXJzYWwgYWNyb3NzIGFsbCByZWdleCBmbGF2b3JzKS4iDQogICksDQogIGNoZWNrLm5hbWVzID0gRkFMU0UNCikNCg0Ka2FibGUoDQogIHJlZ2V4X3RibCwNCiAgYWxpZ24gPSBjKCJsIiwibCIsImwiKSwNCiAgY29sLm5hbWVzID0gYygiTWV0YWNoYXJhY3RlciIsICJOYW1lIiwgIk1lYW5pbmciKSwNCiAgY2FwdGlvbiA9ICJDb21tb24gcmVnZXggbWV0YWNoYXJhY3RlcnMuIg0KKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpICU+JQ0KICBrYWJsZV9jbGFzc2ljXzIoZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPiANCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFNpbXBsZSByZWd1bGFyIGV4cHJlc3Npb24gZXhhbXBsZXMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KVGhlIGZvbGxvd2luZyBleGFtcGxlcyBpbGx1c3RyYXRlIGhvdyBzb21lIG9mIHRoZSBtZXRhY2hhcmFjdGVycyBhYm92ZSBiZWhhdmUgaW4gcHJhY3RpY2UuIEVhY2ggZXhhbXBsZSBmb2N1c2VzIG9uIHBhdHRlcm4gaW50dWl0aW9uLCBub3Qgb24gZXhoYXVzdGl2ZSBtYXRjaGluZyBydWxlcy4NCg0KDQotIEFsdGVybmF0aW9uIChgfGApIGFuZCByZXBldGl0aW9uIChgKmApOiBtYXRjaGVzIG9uZSBvcHRpb24gb3IgcmVwZWF0ZWQgb2NjdXJyZW5jZXMgb2YgYW5vdGhlci4gRXhhbXBsZSBgYXxiKmAgbWF0Y2hlcyBlaXRoZXIgdGhlIHN5bWJvbCBgYWAgKm9yKiB6ZXJvIG9yIG1vcmUgcmVwZXRpdGlvbnMgb2YgYGJgOiBgIGAsIGBhYCwgYGJgLCBgYmJgLCBgYmJiYCAoYnV0IG5vdCBgYWJgLCBgYmFgLCBgYWFgKS4gDQoNCi0gR3JvdXBpbmcgd2l0aCByZXBldGl0aW9uOiBnZW5lcmF0ZXMgc3RyaW5ncyB1c2luZyBvbmx5IHRoZSBzcGVjaWZpZWQgc3ltYm9scy4gRXhhbXBsZSBgKGF8YikqYCBtYXRjaGVzIGFueSBzZXF1ZW5jZSBmb3JtZWQgYnkgdGhlIHN5bWJvbHMgYGFgIGFuZCBgYmAsIGluY2x1ZGluZyB0aGUgZW1wdHkgc3RyaW5nOiBgIGAsIGBhYCwgYGJgLCBgYWJgLCBgYmFgLCBgYWFiYCAoYnV0IG5vdCBgY2AsIGBhYmNgLCBgYWFieGApLiANCg0KLSBPcHRpb25hbCBlbGVtZW50IChgP2ApOiBhbGxvd3MgYSBzeW1ib2wgdG8gYXBwZWFyIHplcm8gb3Igb25lIHRpbWUuIEV4YW1wbGUgYGFiKihjKT9gIG1hdGNoZXMgc3RyaW5ncyB0aGF0IHN0YXJ0IHdpdGggYGFgLCBmb2xsb3dlZCBieSB6ZXJvIG9yIG1vcmUgYGJg4oCZcywgYW5kIG9wdGlvbmFsbHkgZW5kIHdpdGggYGNgOiBgYWAsIGBhYmAsIGBhYmJgLCBgYWNgLCBgYWJjYCAoYnV0IG5vdCBgYmAsIGBiY2AsIGBhYmJjZGVgKS4gDQoNCi0gV2lsZGNhcmQgKGAuYCk6IG1hdGNoZXMgYW55IHNpbmdsZSBjaGFyYWN0ZXIgaW4gYSBmaXhlZCBwb3NpdGlvbi4gRXhhbXBsZSBgLmluZ2AgbWF0Y2hlcyBhbnkgc2VxdWVuY2Ugd2hlcmUgKm9uZSBjaGFyYWN0ZXIqIGlzIGZvbGxvd2VkIGJ5IHRoZSBzdWJzdHJpbmcgYGluZ2AuICAgVGhlIG1hdGNoIGRvZXMgKm5vdCogbmVlZCB0byBzdGFydCBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSB3b3JkOiBgc2luZ2AtPmBzaW5nYCwgYGZseWluZ2AtPmB5aW5nYCwgYGdvaW5nYC0+YG9pbmdgLiBEb2VzIG5vdCBtYXRjaDogYGluZ2AsIGB0aGluZ2AsIGBicmluZ2AsICBiZWNhdXNlIGAuaW5nYCByZXF1aXJlcyBleGFjdGx5IG9uZSBjaGFyYWN0ZXIgYmVmb3JlIGBpbmdgKS4gDQoNCi0gQ2hhcmFjdGVyIGNsYXNzIChgW11gKTogbWF0Y2hlcyBvbmUgY2hhcmFjdGVyIGZyb20gYSBkZWZpbmVkIHNldC4gRXhhbXBsZSBgW21oXW91c2VgIG1hdGNoZXMgb25lIGNoYXJhY3RlciBmcm9tIHRoZSBzcGVjaWZpZWQgc2V0LCBmb2xsb3dlZCBieSBhIGZpeGVkIHN1ZmZpeDogYG1vdXNlYCwgYGhvdXNlYC4gRG9lcyBub3QgbWF0Y2g6IGBsb3VzZWAsIGBNb3VzZWAsICBiZWNhdXNlIG9ubHkgYG1gIG9yIGBoYCBhcmUgYWxsb3dlZCwgYW5kIG1hdGNoaW5nIGlzIGNhc2Utc2Vuc2l0aXZlLg0KDQotIE5lZ2F0ZWQgY2hhcmFjdGVyIGNsYXNzIChgW14gXWApOiBtYXRjaGVzIGFueSBjaGFyYWN0ZXIgbm90IGxpc3RlZC4gRXhhbXBsZSBgW15oXW91c2VgIG1hdGNoZXMgYW55IHNpbmdsZSBjaGFyYWN0ZXIgKmV4Y2VwdCogdGhvc2UgbGlzdGVkIGluc2lkZSB0aGUgYnJhY2tldHMgKGBoYCk6IGBtb3VzZSwgY2hlZXNlIGFuZCBob3VzZWAg4oaSIGBtb3VzZWAgKGJ1dCBub3QgYGhvdXNlYCwgYGFuZGAsIGFuZCBgY2hlZXNlYCwgYmVjYXVzZSBgaGAgaXMgZXhwbGljaXRseSBleGNsdWRlZCkuDQoNCi0gQW5jaG9ycyAoYF5gLCBgJGApOiByZXN0cmljdCBtYXRjaGVzIHRvIHRoZSBzdGFydCBhbmQgZW5kIG9mIGEgc3RyaW5nLiBFeGFtcGxlIGBeW21oXW91c2UkYCBtYXRjaGVzIG9ubHkgY29tcGxldGUgc3RyaW5ncyB0aGF0IHN0YXJ0IGFuZCBlbmQgZXhhY3RseSB3aXRoIHRoZSBzcGVjaWZpZWQgcGF0dGVybjogYG1vdXNlYCwgYGhvdXNlYCAoYnV0IG5vdCBtYXRjaDogYHdhcmVob3VzZWAsIGBtb3VzZXBhZGAsIGBteSBob3VzZWApLg0KDQotIEdyZWVkeSBtYXRjaGluZyAoYC4qYCk6IG1hdGNoZXMgYSBzeW1ib2wgZm9sbG93ZWQgYnkgYW55IG51bWJlciBvZiBjaGFyYWN0ZXJzLiBFeGFtcGxlIGBmLipgIG1hdGNoZXMgdGhlICBjaGFyYWN0ZXIgYGZgIGZvbGxvd2VkIGJ5IHplcm8gb3IgbW9yZSBjaGFyYWN0ZXJzIG9mIGFueSBraW5kOiBgZmAsIGBmbHlgLCBgZm9vdCBkYXRhYCwgYGZhc3QgdGV4dCBwcm9jZXNzaW5nYC4NCg0KLSBPbmUgb3IgbW9yZSByZXBldGl0aW9ucyAoYCtgKTogcmVxdWlyZXMgYXQgbGVhc3Qgb25lIG9jY3VycmVuY2UuIEV4YW1wbGUgYFttcF0rb3VzZWAgbWF0Y2hlczogYG1vdXNlYCwgYHBtb3VzZWAsIGBtbW91c2VgLCBgcHBvdXNlYCAoYnV0IG5vdCBgb3VzZWAsIGBob3VzZWApLiBSZXF1aXJlcyBhdCBsZWFzdCBvbmUgb2NjdXJyZW5jZSBvZiB0aGUgc3BlY2lmaWVkIGNoYXJhY3RlcnMgYmVmb3JlIHRoZSByZW1haW5pbmcgcGF0dGVybi4gDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBSZWd1bGFyIGV4cHJlc2lvbnMtYmFzZWQgdG9rZW5pemVyczogYFJlZ2V4cFRva2VuaXplcmANCg0KVGhlIGBubHRrYCBsaWJyYXJ5IHByb3ZpZGVzIGEgdG9rZW5pemVyIGJhc2VkIG9uICoqcmVndWxhciBleHByZXNzaW9ucyoqLCBrbm93biBhcyBgUmVnZXhwVG9rZW5pemVyYC4gVGhpcyB0b2tlbml6ZXIgYWxsb3dzIHVzIHRvIGRlZmluZSBleHBsaWNpdCBydWxlcyB0aGF0IGNvbnRyb2wgaG93IHRleHQgaXMgc3BsaXQgaW50byB0b2tlbnMuDQoNCkluc3RlYWQgb2YgcmVseWluZyBvbiBzcGFjZXMgb3IgcHVuY3R1YXRpb24gYWxvbmUsIGBSZWdleHBUb2tlbml6ZXJgIHVzZXMgYSBgcmVnZXhgIHBhdHRlcm4gdG8gc3BlY2lmeSB3aGljaCBjaGFyYWN0ZXIgc2VxdWVuY2VzIHNob3VsZCBiZSB0cmVhdGVkIGFzIHRva2Vucy4NCg0KRmlndXJlIFxAcmVmKGZpZzpGaWctUmVnZXhwVG9rZW5pemVyMSkgIGlsbHVzdHJhdGVzIHRoZSBtYWluIHZhcmlhbnRzIGJ1aWx0IG9uIHRvcCBvZiBSZWdleHBUb2tlbml6ZXIuIFRoZXNlIHRva2VuaXplcnMgYXJlIGludHJvZHVjZWQgaGVyZSBmb3Igb3JpZW50YXRpb24gcHVycG9zZXMgYW5kIHdpbGwgYmUgZXhhbWluZWQgaW4gZGV0YWlsIGluIHN1YnNlcXVlbnQgc2VjdGlvbnMuDQoNCjxjZW50ZXI+DQpgYGB7ciBGaWctUmVnZXhwVG9rZW5pemVyMSwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICJgUmVnZXhwVG9rZW5pemVyYCBhbmQgaXRzIHZhcmlhbnRzLiBTb3VyY2U6IENyZWF0ZWQgYnkgdGhlIGF1dGhvciB3aXRoIENoYXRHUFQgKE9wZW5BSSkiLCBvdXQud2lkdGggPSAiNTUlIn0NCiMgZmlnLndpZHRoID0gMjAgIyBObyBmdW5jaW9uYSBlc3RhIG9wY2lvbiBlbiBlbCBjaHVuaw0KDQojaHR0cDovL3pldnJvc3MuY29tL2Jsb2cvMjAxNy8wNi8xOS90aXBzLWFuZC10cmlja3MtZm9yLXdvcmtpbmctd2l0aC1pbWFnZXMtYW5kLWZpZ3VyZXMtaW4tci1tYXJrZG93bi1kb2N1bWVudHMvDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJSZWdleHBUb2tlbml6ZXIxLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBFeGFtcGxlIChgUmVnZXhwVG9rZW5pemVyYCkuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBzZW50ZW5jZToNCg0KYGBge3B5dGhvbn0NCnNlbnRlbmNlID0gIlRoZSBwcmljZSByYW5nZXMgZnJvbSAkMTIwLjUwIHRvICQzNTAuMDAgdG9kYXkuIg0Kc2VudGVuY2UNCmBgYA0KDQpXZSBkZWZpbmUgYSB0b2tlbml6ZXIgdGhhdCByZWNvZ25pemVzIHdvcmRzIGFuZCBwcmljZXM6DQoNCmBgYHtweXRob259DQpmcm9tIG5sdGsudG9rZW5pemUgaW1wb3J0IFJlZ2V4cFRva2VuaXplcg0KDQp0b2tlbml6ZXIgPSBSZWdleHBUb2tlbml6ZXIociJcdyt8JFtcZC5dK3xcUysiKQ0KdG9rZW5pemVyLnRva2VuaXplKHNlbnRlbmNlKQ0KYGBgDQoNCkluIHRoaXMgcGF0dGVybjoNCg0KLSBgXHcrYCBtYXRjaGVzIHdvcmRzIGFuZCBudW1iZXJzICAoZXF1YWwgdG8gYFthLXpBLVowLTlfXWApLg0KDQotICBgXCRgIGluIGBcJFtcZFwuXStgbWF0Y2hlcyBwcmljZXMgc3RhcnRpbmcgd2l0aCBgJGA7ICBgXGRgIG1hdGNoZXMgYSBkaWdpdCBiZXR3ZWVuIDAgYW5kIDksIA0KYFwuYCBtYXRjaGVzIHRoZSBjaGFyYWN0ZXIgYC5gIChwZXJpb2QpLCBhbmQgYCtgIGFnYWluIGFjdHMgYXMgYSBxdWFudGlmaWVyIG1hdGNoaW5nIGJldHdlZW4gb25lIGFuZCB1bmxpbWl0ZWQgdGltZXMuDQoNCi0gYFxTYCBhY2NlcHRzIGFueSBub24td2hpdGVzcGFjZSBjaGFyYWN0ZXIgYW5kIGArYCBhZ2FpbiBhY3RzIHRoZSBzYW1lIHdheSBhcyBpbiB0aGUgcHJlY2VkaW5nIHR3byBhbHRlcm5hdGl2ZXMuDQoNClRoaXMgYXBwcm9hY2ggaXMgdXNlZnVsIHdoZW4gd2Ugd2FudCBwcmVjaXNlIGNvbnRyb2wgb3ZlciB3aGljaCBlbGVtZW50cyBhcmUgcHJlc2VydmVkIGFzIHRva2Vucy4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBSZWd1bGFyIGV4cHJlc2lvbnMtYmFzZWQgdG9rZW5pemVyczogTW9yZSBgUmVnZXhwVG9rZW5pemVyYCAoYFJlZ2V4cFRva2VuaXplcmAgRmFtaWx5KSANCg0KU2V2ZXJhbCB0b2tlbml6ZXJzIGluIE5MVEsgYXJlIGltcGxlbWVudGVkIGFzIHdyYXBwZXJzIG9yIHZhcmlhbnRzIG9mIGBSZWdleHBUb2tlbml6ZXJgIGluY2x1ZGU6DQoNCi0gYFdvcmRQdW5jdFRva2VuaXplcmAsIHdoaWNoIHNlcGFyYXRlcyBhbHBoYWJldGljIGFuZCBub24tYWxwaGFiZXRpYyBjaGFyYWN0ZXJzLA0KDQotIGBCbGFua2xpbmVUb2tlbml6ZXJgLCB3aGljaCB1c2VzIGVtcHR5IGxpbmVzIGFzIGRlbGltaXRlcnMuDQoNCg0KPGNlbnRlcj4NCmBgYHtyIEZpZy1SZWdleHBUb2tlbml6ZXIyLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIlZhcmlhbnRzIG9mIGBSZWdleHBUb2tlbml6ZXJgLiBTb3VyY2U6IENyZWF0ZWQgYnkgdGhlIGF1dGhvciB3aXRoIENoYXRHUFQgKE9wZW5BSSkiLCBvdXQud2lkdGggPSAiODAlIn0NCiMgZmlnLndpZHRoID0gMjAgIyBObyBmdW5jaW9uYSBlc3RhIG9wY2lvbiBlbiBlbCBjaHVuaw0KDQojaHR0cDovL3pldnJvc3MuY29tL2Jsb2cvMjAxNy8wNi8xOS90aXBzLWFuZC10cmlja3MtZm9yLXdvcmtpbmctd2l0aC1pbWFnZXMtYW5kLWZpZ3VyZXMtaW4tci1tYXJrZG93bi1kb2N1bWVudHMvDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJSZWdleHBUb2tlbml6ZXIyLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBFeGFtcGxlIChgV29yZFB1bmN0VG9rZW5pemVyYCkuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KVGhpcyB0b2tlbml6ZXIgc2VwYXJhdGVzIGFscGhhYmV0aWMgdG9rZW5zIGZyb20gcHVuY3R1YXRpb24gYW5kIHN5bWJvbHMsIG1ha2luZyBlYWNoIHB1bmN0dWF0aW9uIG1hcmsgYW4gaW5kaXZpZHVhbCB0b2tlbi4NCg0KYGBge3B5dGhvbn0NCmZyb20gbmx0ay50b2tlbml6ZSBpbXBvcnQgV29yZFB1bmN0VG9rZW5pemVyDQoNCnNlbnRlbmNlID0gIlByaWNlOiAkMTIwLjUwLCBhdmFpbGFibGUgdG9kYXkhIg0KdG9rZW5pemVyID0gV29yZFB1bmN0VG9rZW5pemVyKCkNCnRva2VuaXplci50b2tlbml6ZShzZW50ZW5jZSkNCmBgYA0KDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZSAoYEJsYW5rbGluZVRva2VuaXplcmApLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGlzIHRva2VuaXplciBzcGxpdHMgdGV4dCBpbnRvIGNodW5rcyBiYXNlZCBvbiBibGFuayBsaW5lcywgd2hpY2ggaXMgdXNlZnVsIHdoZW4gcHJvY2Vzc2luZyBkb2N1bWVudHMgc3RydWN0dXJlZCBpbnRvIHBhcmFncmFwaHMuDQoNCmBgYHtweXRob259DQpmcm9tIG5sdGsudG9rZW5pemUgaW1wb3J0IEJsYW5rbGluZVRva2VuaXplcg0KDQpzZW50ZW5jZSA9ICJUaGlzIGlzIHRoZSBmaXJzdCBwYXJhZ3JhcGguXG5cblRoaXMgaXMgdGhlIHNlY29uZCBwYXJhZ3JhcGguIg0KdG9rZW5pemVyID0gQmxhbmtsaW5lVG9rZW5pemVyKCkNCnRva2VuaXplci50b2tlbml6ZShzZW50ZW5jZSkNCmBgYA0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRyeSBpdCBvdXQ6IHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCldyaXRlIGEgcmVndWxhciBleHByZXNzaW9uIHRvIGV4dHJhY3QgZW1haWwgYWRkcmVzc2VzIGZyb20gYSB0ZXh0IGFuZCB0ZXN0IGl0IGF0IFtyZWdleDEwMV0oaHR0cHM6Ly9yZWdleDEwMS5jb20vci9DN0tkUUQvMSkuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgVHJlZWJhbmsgdG9rZW5pemVyDQoNClRoZSAqKlRyZWViYW5rIHRva2VuaXplcioqIGFwcGxpZXMgYSBzZXQgb2YgKmxpbmd1aXN0aWNhbGx5IG1vdGl2YXRlZCBydWxlcyogaW5zcGlyZWQgYnkgdGhlIFBlbm4gVHJlZWJhbmsgYW5ub3RhdGlvbiBndWlkZWxpbmVzLiAgDQpBbHRob3VnaCBpdCByZWxpZXMgaW50ZXJuYWxseSBvbiByZWd1bGFyIGV4cHJlc3Npb25zLCBpdHMgbWFpbiBnb2FsIGlzIG5vdCBwdXJlbHkgcGF0dGVybiBtYXRjaGluZywgYnV0IHJhdGhlciAqbGluZ3Vpc3RpY2FsbHkgaW5mb3JtZWQgdG9rZW5pemF0aW9uKi4NCg0KSW4gcGFydGljdWxhciwgdGhpcyB0b2tlbml6ZXIgaXMgZGVzaWduZWQgdG8gaGFuZGxlICpjb250cmFjdGlvbnMqLCAqcHVuY3R1YXRpb24qLCBhbmQgb3RoZXIgY29tbW9uIHN5bnRhY3RpYyBwaGVub21lbmEgaW4gYSB3YXkgdGhhdCBiZXR0ZXIgcmVmbGVjdHMgdGhlIHN0cnVjdHVyZSBvZiBuYXR1cmFsIGxhbmd1YWdlLiBBcyBhIHJlc3VsdCwgaXQgaXMgZXNwZWNpYWxseSBlZmZlY3RpdmUgZm9yIHByZXByb2Nlc3NpbmcgRW5nbGlzaCB0ZXh0IGluIGRvd25zdHJlYW0gTkxQIHRhc2tzLg0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLVRyZWViYW5rLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIlRyZWViYW5rIHRva2VuaXplcmAuIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI4MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIlRyZWViYW5rLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KQXMgaWxsdXN0cmF0ZWQgaW4gRmlndXJlIFxAcmVmKGZpZzpGaWctVHJlZWJhbmspLCB0aGUgYFRyZWViYW5rYCB0b2tlbml6ZXIgc3lzdGVtYXRpY2FsbHkgc3BsaXRzIGNvbnRyYWN0aW9ucyAoZS5nLiwgYHdlJ2xsYCDihpIgYHdlYCArIGB3aWxsYCkgYW5kIHNlcGFyYXRlcyBwdW5jdHVhdGlvbiBtYXJrcyBmcm9tIHdvcmRzLCBwcm9kdWNpbmcgdG9rZW5zIHRoYXQgYXJlIG1vcmUgc3VpdGFibGUgZm9yIHN5bnRhY3RpYyBhbmQgc2VtYW50aWMgYW5hbHlzaXMuIFRoZXNlIGRlc2lnbiBjaG9pY2VzIG1ha2UgaXQgYSBzdGFuZGFyZCBiYXNlbGluZSB0b2tlbml6ZXIgaW4gbWFueSBOTFAgcGlwZWxpbmVzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgRXhhbXBsZSAoYFRyZWViYW5rYCkuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCkNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgZXhhbXBsZToNCg0KYGBge3B5dGhvbiwgZWNobz1GQUxTRX0NCnNlbnRlbmNlID0gIkknbSBzdXJlIHRoaXMgbW9kZWwgZG9lc24ndCBwZXJmb3JtIHBlcmZlY3RseS4iDQpzZW50ZW5jZQ0KYGBgDQoNClRva2VuaXppbmcgd2l0aCB0aGUgYFRyZWViYW5rYCB0b2tlbml6ZXI6DQoNCmBgYHtweXRob259DQpmcm9tIG5sdGsudG9rZW5pemUgaW1wb3J0IFRyZWViYW5rV29yZFRva2VuaXplcg0KDQp0b2tlbml6ZXIgPSBUcmVlYmFua1dvcmRUb2tlbml6ZXIoKQ0KdG9rZW5pemVyLnRva2VuaXplKHNlbnRlbmNlKQ0KYGBgDQoNCkhlcmUsIGNvbnRyYWN0aW9ucyBzdWNoIGFzIGBJJ21gIGFuZCBgZG9lc24ndGAgYXJlIHNwbGl0IGludG8gbWVhbmluZ2Z1bCBjb21wb25lbnRzOg0KDQotIGBJJ21gIOKGkiBgSWAgYW5kIGAnbWANCg0KLSBgZG9lc24ndGAg4oaSIGBkb2VzYCBhbmQgYG4ndGANCg0KVGhpcyBkZWNvbXBvc2l0aW9uIGhlbHBzIGlzb2xhdGUgZ3JhbW1hdGljYWwgYW5kIHNlbWFudGljIGVsZW1lbnRzIHN1Y2ggYXMgbmVnYXRpb24sIHdoaWNoIHdvdWxkIGJlIGhhcmRlciB0byBhbmFseXplIGlmIHRyZWF0ZWQgYXMgYSBzaW5nbGUgdG9rZW4uDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIFR3ZWV0VG9rZW5pemVyDQoNClRleHQgZnJvbSBzb2NpYWwgbWVkaWEgb2Z0ZW4gZGlmZmVycyBzdWJzdGFudGlhbGx5IGZyb20gc3RhbmRhcmQgd3JpdHRlbiBsYW5ndWFnZS4gSXQgdHlwaWNhbGx5IGluY2x1ZGVzICB1c2VyIG1lbnRpb25zLCBoYXNodGFncywgZW1vamlzLCBVUkxzLCBlbG9uZ2F0ZWQgd29yZHMsIHJlcGVhdGVkIGNoYXJhY3RlcnMsIGFsbCBvZiB3aGljaCBwb3NlIGNoYWxsZW5nZXMgZm9yIHNpbXBsZSB3aGl0ZXNwYWNlLSBvciBwdW5jdHVhdGlvbi1iYXNlZCB0b2tlbml6ZXJzLg0KDQpUbyBhZGRyZXNzIHRoZXNlIGNoYXJhY3RlcmlzdGljcywgdGhlIGBubHRrYCBsaWJyYXJ5IHByb3ZpZGVzIHRoZSAqKlR3ZWV0VG9rZW5pemVyKiosIGEgdG9rZW5pemVyIHNwZWNpZmljYWxseSBkZXNpZ25lZCB0byBoYW5kbGUgdGhlIGluZm9ybWFsIGFuZCBoaWdobHkgdmFyaWFibGUgbmF0dXJlIG9mIHNvY2lhbCBtZWRpYSB0ZXh0LiAgUmF0aGVyIHRoYW4gZGlzY2FyZGluZyB0aGVzZSBlbGVtZW50cywgVHdlZXRUb2tlbml6ZXIgcHJlc2VydmVzIHRoZW0gYXMgbWVhbmluZ2Z1bCB0b2tlbnMsIGFsbG93aW5nIGRvd25zdHJlYW0gTkxQIG1vZGVscyB0byBjYXB0dXJlIGVtb3Rpb25hbCBjdWVzLCBlbXBoYXNpcywgYW5kIHRvcGljYWwgaW5mb3JtYXRpb24uDQoNCg0KDQo8Y2VudGVyPg0KYGBge3IgRmlnLVR3ZWV0LCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIlR3ZWV0IHRva2VuaXplcmAuIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI4MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIlR3ZWV0LnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyBubyBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KQXMgaWxsdXN0cmF0ZWQgaW4gdGhlIEZpZ3VyZSBcQHJlZihmaWc6RmlnLVR3ZWV0KSwgVHdlZXRUb2tlbml6ZXIgaXMgYWJsZSB0byBjb3JyZWN0bHkgaWRlbnRpZnkgYW5kIHNlcGFyYXRlIG1lbnRpb25zIChlLmcuLCBgQHVzZXJgKSwgaGFzaHRhZ3MgKGUuZy4sIGAjQUlgKSwgZW1vamlzLCBVUkxzLCBhbmQgZXhwcmVzc2l2ZSBwdW5jdHVhdGlvbiwgcHJvZHVjaW5nIGEgdG9rZW4gc2VxdWVuY2UgdGhhdCBiZXR0ZXIgcmVmbGVjdHMgdGhlIHN0cnVjdHVyZSBhbmQgc2VtYW50aWNzIG9mIHNvY2lhbCBtZWRpYSBjb21tdW5pY2F0aW9uLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBFeGFtcGxlIChgVHdlZXRUb2tlbml6ZXJgKS4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBleGFtcGxlOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLnRva2VuaXplIGltcG9ydCBUd2VldFRva2VuaXplcg0KDQpzZW50ZW5jZSA9ICJAZGF0YWZhbiBOTFAgaXMgc29vbyBleGNpdGluZyEhISDwn5iE8J+agCAjVGV4dE1pbmluZyAjQUkiDQpzZW50ZW5jZQ0KYGBgDQoNClVzaW5nIGBUd2VldFRva2VuaXplcmA6DQoNCmBgYHtweXRob259DQpmcm9tIG5sdGsudG9rZW5pemUgaW1wb3J0IFR3ZWV0VG9rZW5pemVyDQoNCnRva2VuaXplciA9IFR3ZWV0VG9rZW5pemVyKCkNCnRva2VuaXplci50b2tlbml6ZShzZW50ZW5jZSkNCmBgYA0KDQpUaGlzIHRva2VuaXplciBwcmVzZXJ2ZXMgaW1wb3J0YW50IGVsZW1lbnRzIHN1Y2ggYXM6DQoNCi0gVXNlciBtZW50aW9ucyAoYEBkYXRhZmFuYCksDQoNCi0gRW1vamlzICjwn5iELCDwn5qAKSwNCg0KLSBIYXNodGFncyAoYCNUZXh0TWluaW5nYCwgYCNBSWApLCBhbmQNCg0KLSBSZXBlYXRlZCBwdW5jdHVhdGlvbiBvciBjaGFyYWN0ZXJzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBUd2VldFRva2VuaXplciAoZXhhbXBsZSB3aXRoIG90aGVyIGNvbmZpZ3VyYXRpb25zKQ0KDQpUaGUgYFR3ZWV0VG9rZW5pemVyYCBhbHNvIG9mZmVycyB1c2VmdWwgY29uZmlndXJhdGlvbiBvcHRpb25zOg0KDQoNCmBgYHtweXRob259DQp0b2tlbml6ZXIgPSBUd2VldFRva2VuaXplcihzdHJpcF9oYW5kbGVzPVRydWUsIHJlZHVjZV9sZW49VHJ1ZSwgcHJlc2VydmVfY2FzZT1GYWxzZSkNCnRva2VuaXplci50b2tlbml6ZShzZW50ZW5jZSkNCmBgYA0KDQpJbiB0aGlzIGV4YW1wbGUsIGVhY2ggcGFyYW1ldGVyIG1vZGlmaWVzIHRoZSB0b2tlbml6YXRpb24gYmVoYXZpb3IgYXMgZm9sbG93czoNCg0KLSBUaGUgcGFyYW1ldGVyIGBzdHJpcF9oYW5kbGVzPVRydWVgICh3aGVuIHNldCB0byBgVHJ1ZWApIHJlbW92ZXMgdXNlciBtZW50aW9ucyBpbiBhIHBvc3QvdHdlZXQuIEZvciBleGFtcGxlLCBUaGUgdXNlciBtZW50aW9uIChlLmcuLCBgQGRhdGFmYW5gKSBpcyByZW1vdmVkIGZyb20gdGhlIG91dHB1dC4gVGhpcyBpcyB1c2VmdWwgd2hlbiB1c2VyIGlkZW50aWZpZXJzIGFyZSBub3QgcmVsZXZhbnQgZm9yIHRoZSBhbmFseXNpcy4NCg0KLSBgcmVkdWNlX2xlbj1UcnVlYCBzaG9ydGVucyBleGFnZ2VyYXRlZCByZXBldGl0aW9ucywgYnV0IG5vdCBjb21wbGV0ZWx5IHJlbW92ZWQuIEZvciBpbnN0YW5jZSwgYHNvb29gIOKGkiBgc29vYCAgKGhlcmUsIGBzb29vYCBpcyBwcmVzZXJ2ZWQgaW4gYSByZWR1Y2VkIGZvcm0gcmF0aGVyIHRoYW4gYmVpbmcgZnVsbHkgbm9ybWFsaXplZCkuDQoNCi0gYHByZXNlcnZlX2Nhc2U9RmFsc2VgICh3aGVuIHNldCB0byBgRmFsc2VgKSBjb252ZXJ0cyB0ZXh0IHRvIGxvd2VyY2FzZSBmb3Igdm9jYWJ1bGFyeSBub3JtYWxpemF0aW9uIChgTkxQYCDihpIgYG5scGAsIGAjQUlgIOKGkiBgI2FpYCkuIFRoaXMgaGVscHMgcmVkdWNlIHZvY2FidWxhcnkgc2l6ZSBkdXJpbmcgbm9ybWFsaXphdGlvbi4gVGhlIGRlZmF1bHQgdmFsdWUgZm9yIHRoaXMgcGFyYW1ldGVyIGlzIGBUcnVlYC4NCg0KVG9nZXRoZXIsIHRoZXNlIG9wdGlvbnMgYWxsb3cgYFR3ZWV0VG9rZW5pemVyYCB0byByZXRhaW4gbWVhbmluZ2Z1bCBzb2NpYWwgbWVkaWEgZWxlbWVudHMgKGVtb2ppcywgaGFzaHRhZ3MsIGVtcGhhc2lzKSB3aGlsZSByZWR1Y2luZyBub2lzZSBhbmQgdmFyaWFiaWxpdHkgaW4gaW5mb3JtYWwgdGV4dC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgVW5kZXJzdGFuZGluZyB3b3JkIG5vcm1hbGl6YXRpb24NCg0KSW4gbWFueSBOTFAgdGFza3MsIGtlZXBpbmcgZXZlcnkgcG9zc2libGUgd29yZCBmb3JtIGluIHRoZSB2b2NhYnVsYXJ5IGlzIHVubmVjZXNzYXJ5IChhbmQgb2Z0ZW4gdW5kZXNpcmFibGUpLiBBIGNvbW1vbiBzb2x1dGlvbiBpcyAqKndvcmQgbm9ybWFsaXphdGlvbioqLCB3aGVyZSBkaWZmZXJlbnQgc3VyZmFjZSBmb3JtcyBhcmUgbWFwcGVkIHRvIGEgbW9yZSBjb25zaXN0ZW50IHJlcHJlc2VudGF0aW9uLg0KDQpGb3IgaW5zdGFuY2UsIHZlcmIgZm9ybXMgc3VjaCBhcyBgYW1gLCBgYXJlYCwgYW5kIGBpc2AgY2FuIGJlIG1hcHBlZCB0byB0aGUgc2FtZSBiYXNlIGNvbmNlcHQgYGJlYC4gTGlrZXdpc2UsIHZhcmlhbnRzIHN1Y2ggYXMgYGNhcmAsIGBjYXJzYCwgYW5kIGBjYXInc2AgbWF5IGJlIHRyZWF0ZWQgYXMgdGhlIHNhbWUgdW5kZXJseWluZyB3b3JkLCBkZXBlbmRpbmcgb24gdGhlIGdvYWwgb2YgdGhlIGFuYWx5c2lzLg0KDQpOb3JtYWxpemF0aW9uIGlzIG1haW5seSB1c2VkIHRvICpjb250cm9sIHZvY2FidWxhcnkgc2l6ZSogYW5kICpyZWR1Y2Ugbm9pc2UqIGluIHRleHQgZGF0YS4gSG93ZXZlciwgdGhlIGNob2ljZSBvZiB0ZWNobmlxdWUgaXMgdGFzay1kZXBlbmRlbnQ6IHdvcmRzIHRoYXQgYXJlIG9mdGVuIHJlbW92ZWQgaW4gZ2VuZXJhbCBOTFAgcGlwZWxpbmVzIChlLmcuLCBgd2hlbmAsIGB3aHlgLCBgd2hlcmVgKSBtYXkgYmUgdW5pbmZvcm1hdGl2ZSBmb3Igc29tZSBjbGFzc2lmaWNhdGlvbiB0YXNrcywgYnV0IGVzc2VudGlhbCBmb3IgYXBwbGljYXRpb25zIHN1Y2ggYXMgcXVlc3Rpb24gYW5zd2VyaW5nLg0KDQpGaWd1cmUgXEByZWYoZmlnOkZpZy1Ob3JtYWxpemF0aW9uKSBzdW1tYXJpemVzIHRoZSBtYWluIG5vcm1hbGl6YXRpb24gc3RlcHMgY292ZXJlZCBpbiB0aGlzIHNlY3Rpb24gYW5kIGhvdyB0aGV5IHR5cGljYWxseSBmaXQgaW50byBhIHByZXByb2Nlc3Npbmcgd29ya2Zsb3cuDQoNCg0KPGNlbnRlcj4NCmBgYHtyIEZpZy1Ob3JtYWxpemF0aW9uLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIldvcmQgbm9ybWFsaXphdGlvbmAuIFNvdXJjZTogQ3JlYXRlZCBieSB0aGUgYXV0aG9yIHdpdGggQ2hhdEdQVCAoT3BlbkFJKSIsIG91dC53aWR0aCA9ICI3MCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIk5vcm1hbGl6YXRpb24ucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvIG5vIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIFN0ZW1taW5nDQoNCg0KKipTdGVtbWluZyoqIGlzIGEgdGVjaG5pcXVlIHVzZWQgdG8gcmVkdWNlIHdvcmRzIHRvIGEgc2ltcGxpZmllZCBiYXNlIGZvcm0sIGtub3duIGFzIHRoZSAqKnN0ZW0qKiwgYnkgcmVtb3ZpbmcgcHJlZml4ZXMgb3Igc3VmZml4ZXMuDQoNCkZvciBleGFtcGxlLCB3b3JkcyBzdWNoIGFzIGBjb21wdXRlYCwgYGNvbXB1dGVyYCwgYW5kIGBjb21wdXRpbmdgIG1heSBhbGwgYmUgcmVkdWNlZCB0byB0aGUgc3RlbSBgY29tcHV0YC4gIA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmNvbXB1dGUsIGNvbXB1dGVyLCBjb21wdXRpbmcg4oaSIGNvbXB1dA0KYGBgDQoNCkltcG9ydGFudGx5LCB0aGUgcmVzdWx0aW5nIHN0ZW0gaXMgKm5vdCBndWFyYW50ZWVkIHRvIGJlIGEgdmFsaWQgZGljdGlvbmFyeSB3b3JkKi4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgU3RlbW1pbmcgYWxnb3JpdGhtcy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQpTdGVtbWluZyByZWxpZXMgb24gaGV1cmlzdGljIHJ1bGVzIHJhdGhlciB0aGFuIGxpbmd1aXN0aWMgYW5hbHlzaXMsIHdoaWNoIG1ha2VzIGl0IGZhc3QgYnV0IHNvbWV0aW1lcyBpbXByZWNpc2UuIFR3byB3aWRlbHkgdXNlZCBzdGVtbWluZyBhbGdvcml0aG1zIGFyZToNCg0KLSAqKlBvcnRlciBzdGVtbWVyKiogKGBQb3J0ZXJTdGVtbWVyYCksIGRlc2lnbmVkIGZvciBFbmdsaXNoLg0KDQotICoqU25vd2JhbGwgc3RlbW1lcioqIChgU25vd2JhbGxTdGVtbWVyYCksIGFuIGV4dGVuc2lvbiBvZiB0aGUgYFBvcnRlclN0ZW1tZXJgIHRoYXQgc3VwcG9ydHMgbXVsdGlwbGUgIGxhbmd1YWdlcy4NCg0KVGhlIGBTbm93YmFsbFN0ZW1tZXJgIHN1cHBvcnRzIHRoZSBmb2xsb3dpbmcgbGFuZ3VhZ2VzOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLnN0ZW0uc25vd2JhbGwgaW1wb3J0IFNub3diYWxsU3RlbW1lcg0KU25vd2JhbGxTdGVtbWVyLmxhbmd1YWdlcw0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIGBQb3J0ZXJTdGVtbWVyYC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQpMZXQgdXMgZmlyc3QgYXBwbHkgdGhlIGBQb3J0ZXJTdGVtbWVyYCB0byBhIHNtYWxsIHNldCBvZiB3b3JkczoNCg0KYGBge3B5dGhvbn0NCmZyb20gbmx0ay5zdGVtLnBvcnRlciBpbXBvcnQgUG9ydGVyU3RlbW1lcg0KDQp3b3JkcyA9IFsicnVubmluZyIsICJydW5zIiwgInJ1bm5lciIsICJlYXNpbHkiLCAiZmFpcmx5Il0NCnN0ZW1tZXIgPSBQb3J0ZXJTdGVtbWVyKCkNCltzdGVtbWVyLnN0ZW0od29yZCkgZm9yIHdvcmQgaW4gd29yZHNdDQpgYGANCg0KVGhlIG91dHB1dCBzaG93cyB0aGF0IHRoZSBgUG9ydGVyU3RlbW1lcmAgYWdncmVzc2l2ZWx5IHJlbW92ZXMgc3VmZml4ZXM6DQoNCi0gYHJ1bm5pbmdgIGFuZCBgcnVuc2AgYXJlIGNvcnJlY3RseSByZWR1Y2VkIHRvIGBydW5gLg0KDQotIGBydW5uZXJgIHJlbWFpbnMgdW5jaGFuZ2VkLCBzaW5jZSB0aGUgYWxnb3JpdGhtIGRvZXMgbm90IHRyZWF0IGl0IGFzIGFuIGluZmxlY3RlZCBmb3JtLg0KDQotIGBlYXNpbHlgIGFuZCBgZmFpcmx5YCBhcmUgcmVkdWNlZCB0byBgZWFzaWxpYCBhbmQgYGZhaXJsaWAsIHdoaWNoIGFyZSBub3QgdmFsaWQgRW5nbGlzaCB3b3Jkcy4NCg0KVGhpcyBpbGx1c3RyYXRlcyBhIGtleSBwcm9wZXJ0eSBvZiBzdGVtbWluZzogdGhlIHJlc3VsdGluZyBzdGVtIGlzIG5vdCByZXF1aXJlZCB0byBiZSBsaW5ndWlzdGljYWxseSBjb3JyZWN0LCBvbmx5IGNvbnNpc3RlbnQuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBgU25vd2JhbGxTdGVtbWVyYC4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KTm93LCBhcHBseWluZyB0aGUgYFNub3diYWxsU3RlbW1lcmAgdG8gdGhlIHNhbWUgd29yZHM6DQoNCmBgYHtweXRob259DQpmcm9tIG5sdGsuc3RlbS5zbm93YmFsbCBpbXBvcnQgU25vd2JhbGxTdGVtbWVyDQoNCnN0ZW1tZXIgPSBTbm93YmFsbFN0ZW1tZXIobGFuZ3VhZ2U9ImVuZ2xpc2giKQ0KW3N0ZW1tZXIuc3RlbSh3b3JkKSBmb3Igd29yZCBpbiB3b3Jkc10NCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQpUaGUgYFNub3diYWxsc3RlbW1lcmAgcHJvZHVjZXMgcmVzdWx0cyB0aGF0IGFyZSB2ZXJ5IHNpbWlsYXIgdG8gdGhvc2Ugb2YgdGhlIGBQb3J0ZXJTdGVtbWVyYCwgYnV0IHdpdGggc21hbGwgcmVmaW5lbWVudHM6DQoNCi0gQXMgYmVmb3JlLCBgcnVubmluZ2AgYW5kIGBydW5zYCBhcmUgcmVkdWNlZCB0byBgcnVuYC4NCg0KLSBgcnVubmVyYCByZW1haW5zIHVuY2hhbmdlZC4NCg0KLSBgZWFzaWx5YCBpcyBzdGlsbCByZWR1Y2VkIHRvIGBlYXNpbGlgLg0KDQotIEhvd2V2ZXIsIGBmYWlybHlgIGlzIHJlZHVjZWQgdG8gYGZhaXJgLCB3aGljaCBpcyBhIG1vcmUgcmVhZGFibGUgYW5kIG1lYW5pbmdmdWwgc3RlbSAgdGhhbiBgZmFpcmxpYC4NCg0KVGhpcyBkaWZmZXJlbmNlIGlsbHVzdHJhdGVzIGhvdyBTbm93YmFsbCByZWZpbmVzIHNvbWUgb2YgdGhlIG9yaWdpbmFsIFBvcnRlciBydWxlcywgbGVhZGluZyB0byBzbGlnaHRseSBtb3JlIGludGVycHJldGFibGUgc3RlbXMgaW4gY2VydGFpbiBjYXNlcy4NCg0KT3ZlcmFsbCwgYm90aCBzdGVtbWVycyBiZWhhdmUgc2ltaWxhcmx5LCBidXQgU25vd2JhbGwgb2Z0ZW4gcHJvdmlkZXMgbW9kZXN0IGltcHJvdmVtZW50cyB3aGlsZSBwcmVzZXJ2aW5nIHRoZSBzcGVlZCBhbmQgc2ltcGxpY2l0eSBvZiBydWxlLWJhc2VkIHN0ZW1taW5nLg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIE92ZXItc3RlbW1pbmcgYW5kIHVuZGVyLXN0ZW1taW5nLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpTdGVtbWluZyBjYW4gaW50cm9kdWNlIHR3byBjb21tb24gdHlwZXMgb2YgZXJyb3JzOg0KDQotICoqT3Zlci1zdGVtbWluZyoqIG9jY3VycyB3aGVuIGRpZmZlcmVudCB3b3JkcyBhcmUgcmVkdWNlZCB0byB0aGUgc2FtZSBzdGVtIGV2ZW4gdGhvdWdoDQogIHRoZXkgaGF2ZSBkaWZmZXJlbnQgbWVhbmluZ3MuICAgKkV4YW1wbGUqOiAgYHVuaXZlcnNpdHlgIGFuZCBgdW5pdmVyc2VgIG1heSBiZSBpbmNvcnJlY3RseSBtYXBwZWQgdG8gYSBzaW1pbGFyIHN0ZW0uDQoNCi0gKipVbmRlci1zdGVtbWluZyoqIG9jY3VycyB3aGVuIHJlbGF0ZWQgd29yZHMgYXJlIG5vdCByZWR1Y2VkIHRvIHRoZSBzYW1lIHN0ZW0uICAqRXhhbXBsZSo6IGBhbmFseXNpc2AgYW5kIGBhbmFseXN0YCBtYXkgcmVtYWluIHNlcGFyYXRlIGRlc3BpdGUgYmVpbmcgY29uY2VwdHVhbGx5IHJlbGF0ZWQuDQoNClRoZXNlIGxpbWl0YXRpb25zIGhpZ2hsaWdodCB0aGF0IHN0ZW1taW5nIGlzIGEgY3J1ZGUgbm9ybWFsaXphdGlvbiB0ZWNobmlxdWUgYW5kIHNob3VsZCBiZSBhcHBsaWVkIHdpdGggY2FyZSwgZGVwZW5kaW5nIG9uIHRoZSBOTFAgdGFzay4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIE5vdGUgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KIEZvciBhIGRldGFpbGVkIGRpc2N1c3Npb24gb24gc3RlbW1pbmcgYWxnb3JpdGhtcywgc2VlIHRoaXMgcGFwZXI6IFtBIENvbXBhcmF0aXZlIFN0dWR5IG9mIFN0ZW1taW5nIEFsZ29yaXRobXMgKEppdmFuaSBldCBhbC4sIDIwMTEpXShodHRwczovL3BkZnMuc2VtYW50aWNzY2hvbGFyLm9yZy8xYzBjLzBmYTM1ZDRmZjhhMmY5MjVlYjk1NWU0OGQ2NTU0OTRiZDE2Ny5wZGYpDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBMZW1tYXRpemF0aW9uDQoNClVubGlrZSBzdGVtbWluZywgd2hpY2ggcmVtb3ZlcyBjaGFyYWN0ZXJzIHVzaW5nIGhldXJpc3RpYyBydWxlcywgKipsZW1tYXRpemF0aW9uKiogYWltcyB0byBjb252ZXJ0IGEgd29yZCBpbnRvIGl0cyAqbWVhbmluZ2Z1bCBiYXNlIGZvcm0qLCBrbm93biBhcyB0aGUgKipsZW1tYSoqLiBUaGUgbGVtbWEgdXN1YWxseSBjb3JyZXNwb25kcyB0byBhIHZhbGlkIGRpY3Rpb25hcnkgd29yZC4NCg0KTGVtbWF0aXphdGlvbiBncm91cHMgdG9nZXRoZXIgZGlmZmVyZW50IHdvcmQgZm9ybXMgdGhhdCBzaGFyZSB0aGUgc2FtZSBiYXNlIG1lYW5pbmcuIEZvciBleGFtcGxlLCBgYW1gLCBgYXJlYCwgYW5kIGBpc2AgY2FuIGFsbCBiZSBtYXBwZWQgdG8gdGhlIGxlbW1hIGBiZWAuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBMaW5ndWlzdGljIGluZm9ybWF0aW9uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCg0KVGhpcyBwcm9jZXNzIHJlbGllcyBvbiBsaW5ndWlzdGljIGluZm9ybWF0aW9uIHN1Y2ggYXM6DQoNCi0gVGhlICpwYXJ0IG9mIHNwZWVjaCogKGBQT1NgKSBvZiBhIHdvcmQsDQoNCi0gSXRzIGNvbnRleHR1YWwgdXNhZ2UsIGFuZCwgDQoNCi0gSW4gc29tZSBjYXNlcywgc2VtYW50aWMga25vd2xlZGdlLg0KDQpCZWNhdXNlIHRoZSBzYW1lIHdvcmQgY2FuIGhhdmUgZGlmZmVyZW50IGxlbW1hcyBkZXBlbmRpbmcgb24gY29udGV4dCwgbGVtbWF0aXphdGlvbiBpcyBnZW5lcmFsbHkgbW9yZSBhY2N1cmF0ZSAoYnV0IGFsc28gbW9yZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlKSB0aGFuIHN0ZW1taW5nLg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIGlsbHVzdHJhdGUgbGVtbWF0aXphdGlvbiB1c2luZyB0aGUgKipXb3JkTmV0IGxlbW1hdGl6ZXIqKiAoYFdvcmROZXRMZW1tYXRpemVyYCkgYW5kIHRoZSAqKnNwYUN5IGxlbW1hdGl6ZXIqKiAoYHNwYWN5YCkuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgTGVtbWF0aXphdGlvbiAoYHdvcmROZXRgIGxlbW1hdGl6ZXIpIA0KDQoqKldvcmROZXQqKiBpcyBhIGxhcmdlIGxleGljYWwgZGF0YWJhc2Ugb2YgRW5nbGlzaCBpbiB3aGljaCB3b3JkcyBhcmUgZ3JvdXBlZCBpbnRvIHNldHMgb2Ygc3lub255bXMgKGNhbGxlZCAqKnN5bnNldHMqKikgdGhhdCByZXByZXNlbnQgZGlzdGluY3QgY29uY2VwdHMuIFRoZSBgbmx0a2AgbGlicmFyeSBwcm92aWRlcyBhbiBpbnRlcmZhY2UgdG8gV29yZE5ldCB0aGF0IGNhbiBiZSB1c2VkIGZvciBsZW1tYXRpemF0aW9uLg0KDQpDb25zaWRlciB0aGUgZm9sbG93aW5nIHNlbnRlbmNlOg0KDQpgYGB7cHl0aG9ufQ0Kc2VudGVuY2UgPSAiV2UgYXJlIHB1dHRpbmcgaW4gZWZmb3J0cyB0byBpbXByb3ZlIG91ciB1bmRlcnN0YW5kaW5nIG9mIGxlbW1hdGl6YXRpb24iDQpzZW50ZW5jZQ0KYGBgDQoNCkFwcGx5aW5nIHRoZSBXb3JkTmV0IGxlbW1hdGl6ZXIgKGBXb3JkTmV0TGVtbWF0aXplcmApIHdpdGhvdXQgYWRkaXRpb25hbCBpbmZvcm1hdGlvbjoNCg0KYGBge3B5dGhvbn0NCmltcG9ydCBubHRrDQpmcm9tIG5sdGsuc3RlbSBpbXBvcnQgV29yZE5ldExlbW1hdGl6ZXINCg0KbGVtbWF0aXplciA9IFdvcmROZXRMZW1tYXRpemVyKCkNCnRva2VucyA9IHNlbnRlbmNlLnNwbGl0KCkNCmxlbW1hdGl6ZWQgPSBbbGVtbWF0aXplci5sZW1tYXRpemUodG9rZW4pIGZvciB0b2tlbiBpbiB0b2tlbnNdDQpsZW1tYXRpemVkDQpgYGANCg0KTW9zdCB3b3JkcyByZW1haW4gdW5jaGFuZ2VkLiBUaGlzIG9jY3VycyBiZWNhdXNlLCBieSBkZWZhdWx0LCB0aGUgV29yZE5ldCBsZW1tYXRpemVyIGFzc3VtZXMgdGhhdCBhbGwgd29yZHMgYXJlIG5vdW5zLiBBcyBhIHJlc3VsdCwgdmVyYnMgc3VjaCBhcyBhcmUgb3IgcHV0dGluZyBhcmUgbm90IHJlZHVjZWQgdG8gdGhlaXIgY29ycmVjdCBiYXNlIGZvcm1zLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgVGhlIHJvbGUgb2YgUGFydC1vZi1TcGVlY2ggKFBPUykgaW5mb3JtYXRpb24uIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCkxlbW1hdGl6YXRpb24gaXMgaW5oZXJlbnRseSBjb250ZXh0LWRlcGVuZGVudDogdGhlIGNvcnJlY3QgbGVtbWEgb2YgYSB3b3JkIGRlcGVuZHMgb24gaXRzIGdyYW1tYXRpY2FsIHJvbGUgaW4gdGhlIHNlbnRlbmNlLiBGb3IgdGhpcyByZWFzb24sIGxlbW1hdGl6ZXJzIHR5cGljYWxseSByZWx5IG9uIHBhcnQtb2Ytc3BlZWNoIChQT1MpIHRhZ3MuDQoNClRoZSBgbmx0a2AgbGlicmFyeSBwcm92aWRlcyBhIHByZXRyYWluZWQgYFBPU2AgdGFnZ2VyIHRoYXQgYXNzaWducyBncmFtbWF0aWNhbCBsYWJlbHMgdG8gdG9rZW5zOg0KDQpgYGB7cHl0aG9ufQ0KcG9zX3RhZ3MgPSBubHRrLnBvc190YWcodG9rZW5zKQ0KcG9zX3RhZ3MNCmBgYA0KDQpTb21lIGNvbW1vbiBgUE9TYCB0YWdzIGFwcGVhcmluZyBpbiB0aGlzIGV4YW1wbGUgYXJlOg0KDQotIGBQUlBgOiBwZXJzb25hbCBwcm9ub3VuICANCi0gYFBSUCRgOiBwb3NzZXNzaXZlIHByb25vdW4gIA0KLSBgVkJgOiB2ZXJiIChiYXNlIGZvcm0pICANCi0gYFZCUGA6IHZlcmIgKHByZXNlbnQgdGVuc2UpICANCi0gYFZCR2A6IHZlcmIgKGdlcnVuZCBvciBwcmVzZW50IHBhcnRpY2lwbGUpICANCi0gYE5OYDogbm91biAoc2luZ3VsYXIpICANCi0gYE5OU2A6IG5vdW4gKHBsdXJhbCkgIA0KLSBgSU5gOiBwcmVwb3NpdGlvbiBvciBzdWJvcmRpbmF0aW5nIGNvbmp1bmN0aW9uICANCi0gYFRPYDogaW5maW5pdGl2ZSBtYXJrZXINCg0KIEEgY29tcGxldGUgZGVzY3JpcHRpb24gb2YgdGhlIFBlbm4gVHJlZWJhbmsgUE9TIHRhZ3NldCBjYW4gYmUgZm91bmQgYXQ6IFtBbHBoYWJldGljYWwgbGlzdCBvZiBwYXJ0LW9mLXNwZWVjaCB0YWdzIHVzZWQgaW4gdGhlIFBlbm4gVHJlZWJhbmsgUHJvamVjdF0oaHR0cHM6Ly93d3cubGluZy51cGVubi5lZHUvY291cnNlcy9GYWxsXzIwMDMvbGluZzAwMS9wZW5uX3RyZWViYW5rX3Bvcy5odG1sKS4gDQogDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgTWFwcGluZyBQT1MgdGFncyB0byBXb3JkTmV0IGNhdGVnb3JpZXMuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQogDQogDQpJbnRlcm5hbGx5LCBXb3JkTmV0IHVzZXMgYSBzaW1wbGlmaWVkIHNldCBvZiBncmFtbWF0aWNhbCBjYXRlZ29yaWVzIChub3VuLCB2ZXJiLCBhZGplY3RpdmUsIGFkdmVyYikuIFRoZXJlZm9yZSwgYFBPU2AgdGFncyBwcm9kdWNlZCBieSB0aGUgdGFnZ2VyIG11c3QgYmUgbWFwcGVkIHRvIFdvcmROZXQtY29tcGF0aWJsZSBsYWJlbHMuDQoNClRoZSBmb2xsb3dpbmcgaGVscGVyIGZ1bmN0aW9uIHBlcmZvcm1zIHRoaXMgbWFwcGluZzoNCg0KYGBge3B5dGhvbn0NCmZyb20gbmx0ay5jb3JwdXMgaW1wb3J0IHdvcmRuZXQNCmltcG9ydCBubHRrDQoNCmRlZiBnZXRfd29yZG5ldF9wb3ModG9rZW4pOg0KICAgIHRhZyA9IG5sdGsucG9zX3RhZyhbdG9rZW5dKVswXVsxXVswXS51cHBlcigpDQogICAgdGFnX21hcCA9IHsNCiAgICAgICAgIkoiOiB3b3JkbmV0LkFESiwNCiAgICAgICAgIk4iOiB3b3JkbmV0Lk5PVU4sDQogICAgICAgICJWIjogd29yZG5ldC5WRVJCLA0KICAgICAgICAiUiI6IHdvcmRuZXQuQURWDQogICAgfQ0KICAgIHJldHVybiB0YWdfbWFwLmdldCh0YWcsIHdvcmRuZXQuTk9VTikNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBMZW1tYXRpemF0aW9uIHdpdGggUE9TIGluZm9ybWF0aW9uLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KIA0KDQoNCldoZW4gYFBPU2AgaW5mb3JtYXRpb24gaXMgaW5jb3Jwb3JhdGVkLCBsZW1tYXRpemF0aW9uIGJlY29tZXMgbW9yZSBhY2N1cmF0ZToNCg0KYGBge3B5dGhvbn0NCmxlbW1hdGl6ZWRfd2l0aF9wb3MgPSBbDQpsZW1tYXRpemVyLmxlbW1hdGl6ZSh0b2tlbiwgZ2V0X3dvcmRuZXRfcG9zKHRva2VuKSkNCmZvciB0b2tlbiBpbiB0b2tlbnMNCl0NCiIgIi5qb2luKGxlbW1hdGl6ZWRfd2l0aF9wb3MpDQpgYGANCg0KVGhpcyB0aW1lLCB0aGUgbGVtbWF0aXplciBjb3JyZWN0bHkgaWRlbnRpZmllcyBtZWFuaW5nZnVsIGJhc2UgZm9ybXMsIGZvciBleGFtcGxlOg0KDQotIGBhcmVgIOKGkiBgYmVgDQoNCi0gYHB1dHRpbmdgIOKGkiBgcHV0YA0KDQotIGB1bmRlcnN0YW5kaW5nYCDihpIgYHVuZGVyc3RhbmRgDQoNClRoaXMgZXhhbXBsZSBpbGx1c3RyYXRlcyB0aGF0IGxlbW1hdGl6YXRpb24gaXMgbGluZ3Vpc3RpY2FsbHkgaW5mb3JtZWQgYW5kIGNvbnRleHQtYXdhcmUsIGluIGNvbnRyYXN0IHRvIHN0ZW1taW5nLCB3aGljaCBhcHBsaWVzIHB1cmVseSBydWxlLWJhc2VkIHRydW5jYXRpb24gd2l0aG91dCByZWdhcmQgdG8gZ3JhbW1hdGljYWwgcm9sZSBvciBzZW1hbnRpYyB2YWxpZGl0eS4NCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBOb3RlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KIA0KV2hpbGUgbW9kZXJuIFRyYW5zZm9ybWVyLWJhc2VkIG1vZGVscyBkbyBub3QgZXhwbGljaXRseSBwZXJmb3JtIGxlbW1hdGl6YXRpb24sIHVuZGVyc3RhbmRpbmcgdGhlc2Ugbm9ybWFsaXphdGlvbiBzdGVwcyBoZWxwcyBjbGFyaWZ5IGhvdyBsaW5ndWlzdGljIHN0cnVjdHVyZSBpcyBzaW1wbGlmaWVkIGJlZm9yZSB0b2tlbml6YXRpb24gYW5kIGVtYmVkZGluZy4NCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIENvbXBhcmlzb24gd2l0aCBzdGVtbWluZy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KRm9yIGNvbXBhcmlzb24sIGNvbnNpZGVyIHRoZSBvdXRwdXQgb2YgYSBzdGVtbWVyIGFwcGxpZWQgdG8gdGhlIHNhbWUgdG9rZW5zOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLnN0ZW0uc25vd2JhbGwgaW1wb3J0IFNub3diYWxsU3RlbW1lcg0KDQpzdGVtbWVyID0gU25vd2JhbGxTdGVtbWVyKGxhbmd1YWdlPSJlbmdsaXNoIikNCnN0ZW1tZWQgPSBbc3RlbW1lci5zdGVtKHRva2VuKSBmb3IgdG9rZW4gaW4gdG9rZW5zXQ0KIiAiLmpvaW4oc3RlbW1lZCkNCmBgYA0KDQpTZXZlcmFsIHdvcmRzIGFyZSB0cnVuY2F0ZWQgdG8gZm9ybXMgdGhhdCBkbyBub3QgY29ycmVzcG9uZCB0byB2YWxpZCBkaWN0aW9uYXJ5IGVudHJpZXMsIGZvciBleGFtcGxlOg0KDQotIGBpbXByb3ZlYCDihpIgYGltcHJvdmANCg0KLSBgdW5kZXJzdGFuZGluZ2Ag4oaSIGB1bmRlcnN0YW5kYA0KDQotIGBsZW1tYXRpemF0aW9uYCDihpIgYGxlbW1hdGANCg0KVW5saWtlIGxlbW1hdGl6YXRpb24sIHN0ZW1taW5nIGFwcGxpZXMgcHVyZWx5IHJ1bGUtYmFzZWQgc3VmZml4IHJlbW92YWwgd2l0aG91dCBjb25zaWRlcmluZyBncmFtbWF0aWNhbCByb2xlIG9yIG1lYW5pbmcuIEFzIGEgcmVzdWx0LCBpdCBtYXkgZ2VuZXJhdGUgaW5jb21wbGV0ZSBvciBub24tc3RhbmRhcmQgd29yZCBmb3Jtcy4gTGVtbWF0aXphdGlvbiwgYnkgY29udHJhc3QsIGFpbXMgdG8gcHJlc2VydmUgbGluZ3Vpc3RpYyB2YWxpZGl0eSBhbmQgaW50ZXJwcmV0YWJpbGl0eS4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBMZW1tYXRpemF0aW9uIChgc3BhQ3lgIExlbW1hdGl6ZXIpIA0KDQoNClRoZSAqKmBzcGFDeWAgbGVtbWF0aXplcioqIHJlbGllcyBvbiBwcmV0cmFpbmVkIGxhbmd1YWdlIG1vZGVscyB0aGF0IHBlcmZvcm0gdG9rZW5pemF0aW9uLCBgUE9TYCB0YWdnaW5nLCBhbmQgbGVtbWF0aXphdGlvbiBhcyBwYXJ0IG9mIGEgc2luZ2xlIGludGVncmF0ZWQgcGlwZWxpbmUuDQoNCkFmdGVyIGluc3RhbGxpbmcgYHNwYUN5YCBhbmQgZG93bmxvYWRpbmcgYSBsYW5ndWFnZSBtb2RlbCwgbGVtbWF0aXphdGlvbiBjYW4gYmUgYXBwbGllZCBkaXJlY3RseToNCg0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHNwYWN5DQoNCm5scCA9IHNwYWN5LmxvYWQoImVuX2NvcmVfd2ViX3NtIikNCmRvYyA9IG5scCgiV2UgYXJlIHB1dHRpbmcgaW4gZWZmb3J0cyB0byBpbXByb3ZlIG91ciB1bmRlcnN0YW5kaW5nIG9mIGxlbW1hdGl6YXRpb24iKQ0KW3Rva2VuLmxlbW1hXyBmb3IgdG9rZW4gaW4gZG9jXQ0KYGBgDQoNCg0KSW4gdGhpcyBvdXRwdXQsIGBzcGFDeWAgYXV0b21hdGljYWxseSBpbmZlcnMgdGhlIGdyYW1tYXRpY2FsIHJvbGUgb2YgZWFjaCB0b2tlbiBhbmQgYXNzaWducyBhbiBhcHByb3ByaWF0ZSBsZW1tYToNCg0KLSBgYXJlYCDihpIgYGJlYCAodmVyYiBub3JtYWxpemF0aW9uKS4NCg0KLSBgcHV0dGluZ2Ag4oaSIGBwdXRgICh2ZXJiIGJhc2UgZm9ybSkuDQoNCi0gYGVmZm9ydHNgIOKGkiBgZWZmb3J0YCAoc2luZ3VsYXIgbm91bikuDQoNCi0gRnVuY3Rpb24gd29yZHMgc3VjaCBhcyBgaW5gLCBgdG9gLCBhbmQgYG9mYCByZW1haW4gdW5jaGFuZ2VkLg0KDQotIENvbnRlbnQgd29yZHMgbGlrZSBgdW5kZXJzdGFuZGluZ2AgYW5kIGBsZW1tYXRpemF0aW9uYCBhbHJlYWR5IGFwcGVhciBpbiB0aGVpciBiYXNlIGZvcm0gYW5kIHRoZXJlZm9yZSBkbyBub3QgY2hhbmdlLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIE5vdGUuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCi0gVW5saWtlIHRoZSBXb3JkTmV0LWJhc2VkIGFwcHJvYWNoLCBgc3BhQ3lgIGRvZXMgbm90IHJlcXVpcmUgYFBPU2AgdGFncyB0byBiZSBzdXBwbGllZCBleHBsaWNpdGx5LCBhcyBncmFtbWF0aWNhbCBpbmZvcm1hdGlvbiBpcyBpbmZlcnJlZCBpbnRlcm5hbGx5IGJ5IHRoZSBtb2RlbC4NCg0KLSBJbiBzb21lIGBzcGFDeWAgbGFuZ3VhZ2UgbW9kZWxzLCBwcm9ub3VucyBhcmUgcmVwcmVzZW50ZWQgdXNpbmcgdGhlIHBsYWNlaG9sZGVyIGAtUFJPTi1gLiBUaGlzIGlzIGEgZGVzaWduIGNob2ljZSBpbnRlbmRlZCB0byBhYnN0cmFjdCBhd2F5IHN1cmZhY2UgZm9ybXMgb2YgcHJvbm91bnMgcmF0aGVyIHRoYW4gYSBsZW1tYXRpemF0aW9uIGVycm9yLiBEZXBlbmRpbmcgb24gdGhlIGFwcGxpY2F0aW9uLCB0aGlzIGJlaGF2aW9yIG1heSBiZSB1c2VmdWwgKGUuZy4sIGZvciBub3JtYWxpemF0aW9uKSBvciB1bmRlc2lyYWJsZSAoZS5nLiwgZm9yIGludGVycHJldGFiaWxpdHkpLg0KDQotIGBzcGFDeWAgc3VwcG9ydHMgbXVsdGlwbGUgbGFuZ3VhZ2VzLiBBIGxpc3Qgb2YgYXZhaWxhYmxlIGxhbmd1YWdlIG1vZGVscyBjYW4gYmUgZm91bmQgYXQ6ICBbU3BhQ1k6IE1vZGVscyAmIExhbmd1YWdlc10oaHR0cHM6Ly9zcGFjeS5pby91c2FnZS9tb2RlbHMpLiANCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIyBFeGFtcGxlcyBvZiBzcGFDeSBsYW5ndWFnZSBtb2RlbHMuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCmBzcGFDeWAgc3VwcG9ydHMgbXVsdGlwbGUgbGFuZ3VhZ2VzIHRocm91Z2ggKnByZXRyYWluZWQgbGFuZ3VhZ2UgbW9kZWxzKi4gVGhlc2UgbW9kZWxzIGFyZSAqbm90IGluc3RhbGxlZCBieSBkZWZhdWx0KiBhbmQgbXVzdCBiZSBkb3dubG9hZGVkIHNlcGFyYXRlbHkgZnJvbSB0aGUgY29tbWFuZCBsaW5lIGJlZm9yZSB1c2UuIEZvciBleGFtcGxlOg0KDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KIHB5dGhvbiAtbSBzcGFjeSBkb3dubG9hZCBlbl9jb3JlX3dlYl9zbQ0KIHB5dGhvbiAtbSBzcGFjeSBkb3dubG9hZCBlc19jb3JlX25ld3Nfc20NCiBweXRob24gLW0gc3BhY3kgZG93bmxvYWQgZnJfY29yZV9uZXdzX3NtDQpgYGANCg0KSW4gdGhpcyBjb3Vyc2UsIHRoZSBmb2xsb3dpbmcgZXhhbXBsZXMgYXJlIHByZXNlbnRlZCBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHksIGluIG9yZGVyIHRvIGhpZ2hsaWdodCBgc3BhQ3lg4oCZcyBtdWx0aWxpbmd1YWwgY2FwYWJpbGl0aWVzLiBUaGUgY29kZSBiZWxvdyBzaG93cyBob3cgZGlmZmVyZW50IGxhbmd1YWdlIG1vZGVscyB3b3VsZCBiZSBsb2FkZWQgaWYgdGhleSB3ZXJlIGluc3RhbGxlZC4NCg0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHNwYWN5DQoNCiMgRW5nbGlzaA0KbmxwX2VuID0gc3BhY3kubG9hZCgiZW5fY29yZV93ZWJfc20iKQ0KDQojIFNwYW5pc2gNCm5scF9lcyA9IHNwYWN5LmxvYWQoImVzX2NvcmVfbmV3c19zbSIpDQoNCiMgRnJlbmNoDQpubHBfZnIgPSBzcGFjeS5sb2FkKCJmcl9jb3JlX25ld3Nfc20iKQ0KYGBgDQoNClRoZSBjb2RlIGFib3ZlIGlzIHByb3ZpZGVkIGZvciBpbGx1c3RyYXRpdmUgcHVycG9zZXMgb25seSBhbmQgaXMgbm90IGludGVuZGVkIHRvIGJlIGV4ZWN1dGVkIGluIHRoaXMgY291cnNlLiBUaGlzIGF2b2lkcyBpbnN0YWxsYXRpb24gaXNzdWVzIHdoaWxlIHByZXNlcnZpbmcgdGhlIGNvbmNlcHR1YWwgdW5kZXJzdGFuZGluZyBvZiBgc3BhQ3lg4oCZcyBtdWx0aWxpbmd1YWwgbW9kZWwgc3RydWN0dXJlLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIFN0b3B3b3JkIHJlbW92YWwNCg0KSW4gcHJldmlvdXMgc2VjdGlvbnMsIHdlIGJyaWVmbHkgbWVudGlvbmVkICoqc3RvcHdvcmQgcmVtb3ZhbCoqIGFzIGEgY29tbW9uIHByZXByb2Nlc3Npbmcgc3RlcCBpbiBOTFAuIFdlIG5vdyBleGFtaW5lIHRoaXMgdGVjaG5pcXVlIGluIG1vcmUgZGV0YWlsLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIyBXaGF0IGFyZSBzdG9wd29yZHM/IHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClN0b3B3b3JkcyBhcmUgd29yZHMgc3VjaCBhcyBgYWAsIGBhbmAsIGB0aGVgLCBgaW5gLCBgYXRgLCBhbmQgYHRvYCB0aGF0IG9jY3VyIHZlcnkgZnJlcXVlbnRseSBpbiB0ZXh0IGNvcnBvcmEgYnV0IHVzdWFsbHkgY2FycnkgbGltaXRlZCBzZW1hbnRpYyBpbmZvcm1hdGlvbiBvbiB0aGVpciBvd24uIEFsdGhvdWdoIHRoZXNlIHdvcmRzIGFyZSBlc3NlbnRpYWwgZm9yIGdyYW1tYXRpY2FsIGNvcnJlY3RuZXNzLCB0aGV5IG9mdGVuIGNvbnRyaWJ1dGUgbGl0dGxlIHRvIHRhc2tzIGZvY3VzZWQgb24gY29udGVudCBvciBtZWFuaW5nLg0KDQpBcyBhIHJlc3VsdCwgc3RvcHdvcmQgcmVtb3ZhbCBpcyBjb21tb25seSB1c2VkIHRvOg0KDQotIFJlZHVjZSB2b2NhYnVsYXJ5IHNpemUsDQoNCi0gU2ltcGxpZnkgdGV4dCByZXByZXNlbnRhdGlvbnMsIGFuZA0KDQotIEltcHJvdmUgZWZmaWNpZW5jeSBpbiBjZXJ0YWluIE5MUCB0YXNrcy4NCg0KSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCAqdGhlcmUgaXMgbm8gdW5pdmVyc2FsIHN0b3B3b3JkIGxpc3QqLiBTdG9wd29yZHMgZGVwZW5kIG9uIHRoZToNCg0KLSBUaGUgbGFuZ3VhZ2UsDQoNCi0gVGhlIGFwcGxpY2F0aW9uLCBhbmQNCg0KLSBUaGUgc3BlY2lmaWMgdGFzayBiZWluZyBhZGRyZXNzZWQuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIFN0b3B3b3JkcyBpbiBwcmFjdGljZSAoYG5sdGtgIGV4YW1wbGUpLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgYG5sdGtgIGxpYnJhcnkgcHJvdmlkZXMgcHJlZGVmaW5lZCBzdG9wd29yZCBsaXN0cyBmb3Igc2V2ZXJhbCBsYW5ndWFnZXMuIFRoZSBmb2xsb3dpbmcgZXhhbXBsZSBpbGx1c3RyYXRlcyBob3cgc3RvcHdvcmRzIGNhbiBiZSByZXRyaWV2ZWQgZm9yIEVuZ2xpc2g6DQoNCg0KYGBge3B5dGhvbn0NCiNubHRrLmRvd25sb2FkKCdzdG9wd29yZHMnKQ0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQoNCnN0b3AgPSBzZXQoc3RvcHdvcmRzLndvcmRzKCdlbmdsaXNoJykpDQoiLCAiLmpvaW4oc3RvcCkNCmBgYA0KDQpUaGUgSXRhbGlhbiBzdG9wd29yZCBsaXN0IGluY2x1ZGVzIGZyZXF1ZW50IGZ1bmN0aW9uIHdvcmRzIHN1Y2ggYXMgZGksIGUsIGlsLCBsYSwgY2hlLCBwZXIsIHdoaWNoIG1heSBiZSByZW1vdmVkIGRlcGVuZGluZyBvbiB0aGUgZ29hbHMgb2YgdGhlIGFuYWx5c2lzLg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQoNCnN0b3BfaXQgPSBzZXQoc3RvcHdvcmRzLndvcmRzKCJpdGFsaWFuIikpDQoiLCAiLmpvaW4oc3RvcF9pdCkNCg0KYGBgDQoNCg0KVGhlIEZyZW5jaCBzdG9wd29yZCBsaXN0IGNvbnRhaW5zIGNvbW1vbiBncmFtbWF0aWNhbCB3b3JkcyBzdWNoIGFzIGxlLCBsYSwgbGVzLCBkZSwgZXQsIMOgLCB3aGljaCBhcmUgZnJlcXVlbnRseSByZW1vdmVkIGluIHByZXByb2Nlc3Npbmcgc3RlcHMgZGVwZW5kaW5nIG9uIHRoZSB0YXNrLg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQoNCnN0b3BfZnIgPSBzZXQoc3RvcHdvcmRzLndvcmRzKCJmcmVuY2giKSkNCiIsICIuam9pbihzdG9wX2ZyKQ0KYGBgDQoNCg0KQWx0aG91Z2ggc3RvcHdvcmQgbGlzdHMgYXJlIGxhbmd1YWdlLXNwZWNpZmljLCB0aGUgdW5kZXJseWluZyBwcmluY2lwbGUgcmVtYWlucyB0aGUgc2FtZTogc3RvcHdvcmQgcmVtb3ZhbCBpcyBhIHRhc2stZGVwZW5kZW50IHByZXByb2Nlc3NpbmcgZGVjaXNpb24gcmF0aGVyIHRoYW4gYSB1bml2ZXJzYWwgcnVsZS4gVGhlc2UgbGlzdHMgdHlwaWNhbGx5IGNvbnNpc3Qgb2YgaGlnaGx5IGZyZXF1ZW50IGZ1bmN0aW9uIHdvcmRzOyBob3dldmVyLCBhcHBseWluZyB0aGVtIGJsaW5kbHkgY2FuIGxlYWQgdG8gdGhlIHJlbW92YWwgb2YgbGluZ3Vpc3RpY2FsbHkgb3Igc2VtYW50aWNhbGx5IHJlbGV2YW50IHRlcm1zLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgVGFzay1kZXBlbmRlbnQgc3RvcHdvcmQgc2VsZWN0aW9uLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpBcyBkaXNjdXNzZWQgZWFybGllciwgc3RvcHdvcmQgbGlzdHMgc2hvdWxkIG5vdCBiZSBhcHBsaWVkIGJsaW5kbHkuIEluIHBhcnRpY3VsYXIsIGB3aC1gIHdvcmRzIHN1Y2ggYXMgYHdob2AsIGB3aGF0YCwgYHdoZW5gLCBgd2h5YCwgYGhvd2AsIGB3aGljaGAsIGB3aGVyZWAsIGFuZCBgd2hvbWAgb2Z0ZW4gcGxheSBhIGNydWNpYWwgcm9sZSBpbiB0YXNrcyBpbnZvbHZpbmcgcXVlc3Rpb25zIG9yIGluZm9ybWF0aW9uLXNlZWtpbmcgYmVoYXZpb3IuDQoNCg0KV2hpbGUgcmVtb3ZpbmcgdGhlc2Ugd29yZHMgbWF5IGJlIGFjY2VwdGFibGUgaW4gc29tZSBjb250ZXh0cywgaXQgY2FuIGJlIGhhcm1mdWwgaW4gYXBwbGljYXRpb25zIHN1Y2ggYXM6DQoNCi0gUXVlc3Rpb24gYW5zd2VyaW5nLA0KDQotIFF1ZXN0aW9uIGNsYXNzaWZpY2F0aW9uLA0KDQotIEluZm9ybWF0aW9uIHJldHJpZXZhbC4NCg0KVGhlIGZvbGxvd2luZyBleGFtcGxlIGlsbHVzdHJhdGVzIGhvdyBhIHN0b3B3b3JkIGxpc3QgY2FuIGJlIGFkYXB0ZWQgdG8gcHJlc2VydmUgYHdoLWAgd29yZHMgd2hlbiB0aGV5IGFyZSByZWxldmFudCBmb3IgaW50ZXJwcmV0YXRpb24uDQoNCg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQoNCndoX3dvcmRzID0gWyJ3aG8iLCAid2hhdCIsICJ3aGVuIiwgIndoeSIsICJob3ciLCAid2hpY2giLCAid2hlcmUiLCAid2hvbSJdDQoNCnN0b3AgPSBzZXQoc3RvcHdvcmRzLndvcmRzKCJlbmdsaXNoIikpDQpmb3Igd29yZCBpbiB3aF93b3JkczoNCiAgICBzdG9wLnJlbW92ZSh3b3JkKQ0KDQpzZW50ZW5jZSA9ICJob3cgZG8gc3R1ZGVudHMgYW5hbHl6ZSB0ZXh0IGRhdGEgaW4gYXBwbGllZCBzdGF0aXN0aWNzIGNvdXJzZXMiDQpmaWx0ZXJlZF9zZW50ZW5jZSA9IFt0b2tlbiBmb3IgdG9rZW4gaW4gc2VudGVuY2Uuc3BsaXQoKSBpZiB0b2tlbiBub3QgaW4gc3RvcF0NCiIgIi5qb2luKGZpbHRlcmVkX3NlbnRlbmNlKQ0KYGBgDQoNCg0KVGhlIG9yaWdpbmFsIHNlbnRlbmNlOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmhvdyBkbyBzdHVkZW50cyBhbmFseXplIHRleHQgZGF0YSBpbiBhcHBsaWVkIHN0YXRpc3RpY3MgY291cnNlcw0KYGBgDQoNCg0KDQppcyB0cmFuc2Zvcm1lZCBpbnRvOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmhvdyBzdHVkZW50cyBhbmFseXplIHRleHQgZGF0YSBhcHBsaWVkIHN0YXRpc3RpY3MgY291cnNlcw0KYGBgDQoNCg0KSW4gdGhpcyBwcm9jZXNzLCBjb21tb24gZnVuY3Rpb24gd29yZHMgc3VjaCBhcyBgZG9gIGFuZCBgaW5gIGFyZSByZW1vdmVkLCB3aGlsZSB0aGUgYHdoLWAgd29yZCBgaG93YCBpcyBwcmVzZXJ2ZWQgZHVlIHRvIGl0cyBpbXBvcnRhbmNlIGZvciBpbnRlcnByZXRhdGlvbi4gVGhpcyBleGFtcGxlIGhpZ2hsaWdodHMgdGhhdCBzdG9wd29yZCByZW1vdmFsIG11c3QgYmUgYWRhcHRlZCB0byB0aGUgc3BlY2lmaWMgZ29hbHMgb2YgdGhlIGFuYWx5c2lzIHJhdGhlciB0aGFuIGFwcGxpZWQgbWVjaGFuaWNhbGx5Lg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIENhc2UgRm9sZGluZw0KDQpBbm90aGVyIGNvbW1vbiBub3JtYWxpemF0aW9uIHN0cmF0ZWd5IGluIE5MUCBpcyAqKmNhc2UgZm9sZGluZyoqLCB3aGljaCBjb25zaXN0cyBvZiBjb252ZXJ0aW5nIGFsbCBjaGFyYWN0ZXJzIGluIGEgdGV4dCBjb3JwdXMgdG8gbG93ZXJjYXNlLiBVbmRlciBjYXNlIGZvbGRpbmcsIHRva2VucyBzdWNoIGFzIGBUaGVgIGFuZCBgdGhlYCBhcmUgdHJlYXRlZCBhcyBpZGVudGljYWwsIHdoZXJlYXMgdGhleSB3b3VsZCBiZSBjb25zaWRlcmVkIGRpc3RpbmN0IGluIGEgY2FzZS1zZW5zaXRpdmUgcmVwcmVzZW50YXRpb24uDQoNCkNhc2UgZm9sZGluZyBpcyBwYXJ0aWN1bGFybHkgdXNlZnVsIGluIGFwcGxpY2F0aW9ucyBzdWNoIGFzIGluZm9ybWF0aW9uIHJldHJpZXZhbCBhbmQgdGV4dCBtYXRjaGluZywgd2hlcmUgZGlmZmVyZW5jZXMgaW4gY2FwaXRhbGl6YXRpb24gYXJlIHVzdWFsbHkgbm90IG1lYW5pbmdmdWwuIEZvciBleGFtcGxlLCB3aGV0aGVyIGEgdXNlciB0eXBlcyBgU3RhdGlzdGljc2Agb3IgYHN0YXRpc3RpY3NgIHNob3VsZCBub3QgYWZmZWN0IHRoZSByZXRyaWV2YWwgb2YgcmVsZXZhbnQgZG9jdW1lbnRzLg0KDQpIb3dldmVyLCBjYXNlIGZvbGRpbmcgY2FuIGludHJvZHVjZSBsaW1pdGF0aW9ucyBpbiBjZXJ0YWluIGNvbnRleHRzLiBQcm9wZXIgbm91bnMgbWF5IGxvc2UgaW1wb3J0YW50IGRpc3RpbmN0aW9ucyB3aGVuIGNvbnZlcnRlZCB0byBsb3dlcmNhc2UuIEZvciBpbnN0YW5jZSwgYWNyb255bXMgc3VjaCBhcyBgTkFTQWAgb3IgYFVOYCBtYXkgYmUgdHJhbnNmb3JtZWQgaW50byBjb21tb24gbm91bnMuIFNpbWlsYXJseSwgbmFtZWQgZW50aXRpZXMgY29tcG9zZWQgb2YgY29tbW9uIHdvcmRzIGNhbiBiZWNvbWUgYW1iaWd1b3VzIGFmdGVyIGNhc2UgZm9sZGluZy4NCg0KQWx0aG91Z2ggbW9yZSBzb3BoaXN0aWNhdGVkIGFwcHJvYWNoZXMgYXR0ZW1wdCB0byBwcmVzZXJ2ZSBjYXBpdGFsaXphdGlvbiBzZWxlY3RpdmVseSB1c2luZyBjb250ZXh0dWFsIGluZm9ybWF0aW9uLCBzdWNoIG1ldGhvZHMgYXJlIG5vdCBhbHdheXMgcmVsaWFibGXigJRlc3BlY2lhbGx5IHdoZW4gdXNlcnMgcHJlZG9taW5hbnRseSB3cml0ZSBpbiBsb3dlcmNhc2UuIEFzIGEgcmVzdWx0LCBmdWxseSBsb3dlcmNhc2luZyB0ZXh0IHJlbWFpbnMgYSB3aWRlbHkgdXNlZCBhbmQgcHJhY3RpY2FsIHNvbHV0aW9uLg0KDQpJdCBpcyBhbHNvIGltcG9ydGFudCB0byBub3RlIHRoYXQgdGhlIHJlbGV2YW5jZSBvZiBjYXBpdGFsaXphdGlvbiB2YXJpZXMgYWNyb3NzIGxhbmd1YWdlcy4gSW4gbGFuZ3VhZ2VzIHN1Y2ggYXMgRW5nbGlzaCwgY2FwaXRhbGl6YXRpb24gb2Z0ZW4gY29udmV5cyBzeW50YWN0aWMgb3Igc2VtYW50aWMgaW5mb3JtYXRpb24sIHdoZXJlYXMgaW4gb3RoZXIgbGFuZ3VhZ2VzIGl0IG1heSBwbGF5IGEgbGVzcyBzaWduaWZpY2FudCByb2xlLg0KDQpUaGUgZm9sbG93aW5nIGV4YW1wbGUgaWxsdXN0cmF0ZXMgYSBzaW1wbGUgY2FzZS1mb2xkaW5nIG9wZXJhdGlvbiBpbiBQeXRob24gdXNpbmcgdGhlIGBsb3dlcigpYCBtZXRob2Q6DQoNCg0KYGBge3B5dGhvbn0NCnNlbnRlbmNlID0gIkdyYWR1YXRlIFN0dWRlbnRzIEFwcGx5IFN0YXRpc3RpY2FsIE1vZGVscyB0byBUZXh0IEFuYWx5c2lzIg0Kc2VudGVuY2UgPSBzZW50ZW5jZS5sb3dlcigpDQpzZW50ZW5jZQ0KYGBgDQoNCg0KSW4gdGhpcyBvdXRwdXQsIGFsbCB1cHBlcmNhc2UgbGV0dGVycyBhcmUgY29udmVydGVkIHRvIGxvd2VyY2FzZS4gQXMgYSByZXN1bHQsIHdvcmRzIHN1Y2ggYXMgYEdyYWR1YXRlYCwgYFN0dWRlbnRzYCwgYW5kIGBTdGF0aXN0aWNhbGAgbG9zZSB0aGVpciBjYXBpdGFsaXphdGlvbiBhbmQgYmVjb21lIGluZGlzdGluZ3Vpc2hhYmxlIGZyb20gdGhlaXIgbG93ZXJjYXNlIGNvdW50ZXJwYXJ0cy4gVGhpcyB0cmFuc2Zvcm1hdGlvbiByZWR1Y2VzIHZhcmlhYmlsaXR5IGluIHRoZSB0ZXh0IHJlcHJlc2VudGF0aW9uLCB3aGljaCBjYW4gYmUgYmVuZWZpY2lhbCBmb3IgdGFza3Mgc3VjaCBhcyB0ZXh0IG1hdGNoaW5nIGFuZCBpbmZvcm1hdGlvbiByZXRyaWV2YWwsIGJ1dCBtYXkgYWxzbyByZW1vdmUgdXNlZnVsIHNpZ25hbHMgd2hlbiBjYXBpdGFsaXphdGlvbiBjYXJyaWVzIHNlbWFudGljIG9yIHN5bnRhY3RpYyBtZWFuaW5nLg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIE5vdGU6IENhc2UgZm9sZGluZyBhbmQgbW9kZXJuIGVtYmVkZGluZ3Mgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KDQpJbiB0cmFkaXRpb25hbCBOTFAgcGlwZWxpbmVzLCBjYXNlIGZvbGRpbmcgaXMgb2Z0ZW4gYXBwbGllZCBleHBsaWNpdGx5IGFzIGEgcHJlcHJvY2Vzc2luZyBzdGVwLiBJbiBjb250cmFzdCwgbW9kZXJuIG5ldXJhbCBsYW5ndWFnZSBtb2RlbHMgbWF5IGhhbmRsZSBjYXBpdGFsaXphdGlvbiBkaWZmZXJlbnRseSBkZXBlbmRpbmcgb24gdGhlaXIgYXJjaGl0ZWN0dXJlIGFuZCB0cmFpbmluZyBkYXRhLg0KDQpGb3IgZXhhbXBsZSwgdW5jYXNlZCBtb2RlbHMgcmVseSBvbiBmdWxseSBsb3dlcmNhc2VkIHRleHQsIHdoZXJlYXMgY2FzZWQgbW9kZWxzIHByZXNlcnZlIGNhcGl0YWxpemF0aW9uIGFuZCBtYXkgdXNlIGl0IGFzIGEgc2lnbmFsIGZvciBtZWFuaW5nIG9yIG5hbWVkLWVudGl0eSByZWNvZ25pdGlvbi4gQ29uc2VxdWVudGx5LCB0aGUgZGVjaXNpb24gdG8gYXBwbHkgY2FzZSBmb2xkaW5nIHNob3VsZCBiZSBhbGlnbmVkIHdpdGggdGhlIHJlcHJlc2VudGF0aW9uIG1vZGVsIGJlaW5nIHVzZWQuDQoNCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBLZXkgdGFrZWF3YXlzLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQotIFN0b3B3b3JkIHJlbW92YWwgYW5kIGNhc2UgZm9sZGluZyBhcmUgY29tbW9uIHRleHQgbm9ybWFsaXphdGlvbiB0ZWNobmlxdWVzLCBidXQgbmVpdGhlciBzaG91bGQgYmUgYXBwbGllZCBibGluZGx5Lg0KDQotIEJvdGggdGVjaG5pcXVlcyBpbnZvbHZlIG1vZGVsaW5nIGRlY2lzaW9ucyB0aGF0IGRlcGVuZCBvbiB0aGUgdGFzaywgbGFuZ3VhZ2UsIGFuZCBkb3duc3RyZWFtIGFwcGxpY2F0aW9uLg0KDQotIFJlbW92aW5nIHN0b3B3b3JkcyBtYXkgc2ltcGxpZnkgcmVwcmVzZW50YXRpb25zLCBidXQgY2FuIGJlIGhhcm1mdWwgaW4gdGFza3Mgc3VjaCBhcyBxdWVzdGlvbiBhbnN3ZXJpbmcgb3IgaW5mb3JtYXRpb24gcmV0cmlldmFsLg0KDQotIENhc2UgZm9sZGluZyByZWR1Y2VzIHNwYXJzaXR5IGJ1dCBtYXkgZWxpbWluYXRlIG1lYW5pbmdmdWwgZGlzdGluY3Rpb25zLCBwYXJ0aWN1bGFybHkgZm9yIHByb3BlciBub3VucyBhbmQgYWNyb255bXMuDQoNCi0gSW4gbW9kZXJuIE5MUCBzeXN0ZW1zLCBpbmNsdWRpbmcgVHJhbnNmb3JtZXItYmFzZWQgbW9kZWxzLCBzb21lIG5vcm1hbGl6YXRpb24gc3RlcHMgbWF5IGJlIGhhbmRsZWQgaW1wbGljaXRseSByYXRoZXIgdGhhbiBleHBsaWNpdGx5Lg0KDQoNCg0KVGhpcyBjb25jbHVkZXMgdGhlIGRpc2N1c3Npb24gb24gbGV4aWNhbCBub3JtYWxpemF0aW9uLiBXZSBub3cgdHVybiB0byB0b2tlbml6YXRpb24gc3RyYXRlZ2llcyB0aGF0IGNhcHR1cmUgbm90IG9ubHkgaW5kaXZpZHVhbCB3b3JkcywgYnV0IGFsc28gc2hvcnQgc2VxdWVuY2VzIG9mIHdvcmRzIHRoYXQgY29udmV5IG1lYW5pbmcgam9pbnRseS4NCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMgTi1ncmFtcy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KU28gZmFyLCB3ZSBoYXZlIGltcGxpY2l0bHkgd29ya2VkIHdpdGggKip1bmlncmFtcyoqLCB0aGF0IGlzLCBpbmRpdmlkdWFsIHdvcmRzIHRyZWF0ZWQgYXMgaW5kZXBlbmRlbnQgdG9rZW5zLiBVbmlncmFtcyByZXByZXNlbnQgdGhlIHNpbXBsZXN0IGxldmVsIG9mIHRleHQgcmVwcmVzZW50YXRpb24gYW5kIGFyZSBvZnRlbiB1c2VkIHRvIG1vZGVsIHdvcmQgZnJlcXVlbmN5IGFuZCBiYXNpYyBsZXhpY2FsIGluZm9ybWF0aW9uLiBIb3dldmVyLCBtYW55IGV4cHJlc3Npb25zIGluIG5hdHVyYWwgbGFuZ3VhZ2UgY29udmV5IG1lYW5pbmcgb25seSB3aGVuIG11bHRpcGxlIHdvcmRzIGFyZSBjb25zaWRlcmVkIHRvZ2V0aGVyLiBFeGFtcGxlcyBpbmNsdWRlIGNvbXBvdW5kIHRlcm1zLCBuYW1lZCBlbnRpdGllcywgYW5kIGZpeGVkIGV4cHJlc3Npb25zLiBUbyBjYXB0dXJlIHN1Y2ggbG9jYWwgY29udGV4dCwgTkxQIHJlbGllcyBvbiAqKm4tZ3JhbXMqKiwgd2hpY2ggYXJlIGNvbnRpZ3VvdXMgc2VxdWVuY2VzIG9mICpuKiB0b2tlbnMuDQoNCi0gVW5pZ3JhbXMgKG4gPSAxKTogc2luZ2xlIHdvcmRzLg0KDQotIEJpZ3JhbXMgKG4gPSAyKTogcGFpcnMgb2Ygd29yZHMuDQoNCi0gVHJpZ3JhbXMgKG4gPSAzKTogc2VxdWVuY2VzIG9mIHRocmVlIHdvcmRzLg0KDQpJbiBwcmFjdGljZSwgbW9zdCBOTFAgYXBwbGljYXRpb25zIHVzZSB1bmlncmFtcywgYmlncmFtcywgYW5kIHRyaWdyYW1zLCBhcyBsYXJnZXIgbi1ncmFtcyB0ZW5kIHRvIGJlIHNwYXJzZSBhbmQgbGVzcyBpbmZvcm1hdGl2ZS4NCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBzZW50ZW5jZToNCg0KYGBge3B5dGhvbn0NCnNlbnRlbmNlID0gIkFwcGxpZWQgc3RhdGlzdGljcyBzdXBwb3J0cyBkYXRhLWRyaXZlbiBkZWNpc2lvbiBtYWtpbmcuIEFwcGxpZWQgc3RhdGlzdGljcyBzdXBwb3J0cyBiZXR0ZXIgZGVjaXNpb24gbWFraW5nIGluIHByYWN0aWNlLCAgYW5kICBhcHBsaWVkIHN0YXRpc3RpY3Mgc3VwcG9ydHMgYWxsIGRlY2lzaW9ucyINCnNlbnRlbmNlDQpgYGANCg0KDQpUaGUgcGhyYXNlIGBkYXRhLWRyaXZlbiBkZWNpc2lvbiBtYWtpbmdgIGNhcnJpZXMgYSBzcGVjaWZpYyBtZWFuaW5nIHRoYXQgd291bGQgYmUgcGFydGlhbGx5IGxvc3QgaWYgZWFjaCB3b3JkIHdlcmUgYW5hbHl6ZWQgaW5kZXBlbmRlbnRseS4gTi1ncmFtcyBhbGxvdyB1cyB0byBwcmVzZXJ2ZSBzdWNoIGxvY2FsIGNvbnRleHQuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFVuaWdyYW1zLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpJbiB0aGlzIGNhc2UsIHVuaWdyYW1zIGNvcnJlc3BvbmQgdG8gdGhlIGluZGl2aWR1YWwgd29yZHMgaW4gdGhlIHNlbnRlbmNlLiBUaGV5IGNhcHR1cmUgYmFzaWMgbGV4aWNhbCBpbmZvcm1hdGlvbiBidXQgaWdub3JlIHdvcmQgb3JkZXIgYW5kIGxvY2FsIGRlcGVuZGVuY2llcy4NCg0KYGBge3B5dGhvbn0NCnRva2VucyA9IHNlbnRlbmNlLmxvd2VyKCkuc3BsaXQoKQ0KdG9rZW5zDQpgYGANCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFVuaWdyYW0gZnJlcXVlbmN5IHRhYmxlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUbyBzdW1tYXJpemUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBpbmRpdmlkdWFsIHdvcmRzIGluIHRoZSB0ZXh0LCB1bmlncmFtIGZyZXF1ZW5jaWVzIGFyZSBjb21wdXRlZCBhbmQgb3JnYW5pemVkIGludG8gYSB0YWJsZS4gUHJlc2VudGluZyBmcmVxdWVuY2llcyBpbiB0YWJ1bGFyIGZvcm0gZmFjaWxpdGF0ZXMgaW5zcGVjdGlvbiBhbmQgY29tcGFyaXNvbiwgbWFraW5nIGl0IGVhc2llciB0byBpZGVudGlmeSB3aGljaCB0ZXJtcyBkb21pbmF0ZSB0aGUgdGV4dC4NCg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcg0KdW5pZ3JhbV9mcmVxID0gQ291bnRlcih0b2tlbnMpDQp1bmlncmFtX2ZyZXENCmBgYA0KDQpGb3IgaW1wcm92ZWQgcmVhZGFiaWxpdHksIHRoZSBmcmVxdWVuY3kgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkIGFzIGEgc3RydWN0dXJlZCB0YWJsZSB3aXRoIG9uZSByb3cgcGVyIHVuaWdyYW06DQoNCmBgYHtweXRob259DQojcGlwIGluc3RhbGwgbWF0cGxvdGxpYg0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KDQp1bmlncmFtX3RhYmxlID0gKA0KcGQuRGF0YUZyYW1lKHVuaWdyYW1fZnJlcS5pdGVtcygpLCBjb2x1bW5zPVsiVW5pZ3JhbSIsICJGcmVxdWVuY3kiXSkNCi5zb3J0X3ZhbHVlcygiRnJlcXVlbmN5IiwgYXNjZW5kaW5nPUZhbHNlKQ0KLnJlc2V0X2luZGV4KGRyb3A9VHJ1ZSkNCikNCg0KdW5pZ3JhbV90YWJsZQ0KYGBgDQoNCg0KDQpUaGUgdW5pZ3JhbSBmcmVxdWVuY3kgdGFibGUgc2hvd3MgaG93IG9mdGVuIGVhY2ggaW5kaXZpZHVhbCB3b3JkIGFwcGVhcnMgaW4gdGhlIHRleHQuIFdvcmRzIHN1Y2ggYXMgYGFwcGxpZWRgLCBgc3RhdGlzdGljc2AsIGBzdXBwb3J0c2AsIGFuZCBgZGVjaXNpb25gIG9jY3VyIG11bHRpcGxlIHRpbWVzLCBpbmRpY2F0aW5nIHRoZWlyIGNlbnRyYWwgcm9sZSBpbiB0aGUgc2VudGVuY2UuIEhvd2V2ZXIsIGJlY2F1c2UgdW5pZ3JhbXMgdHJlYXQgd29yZHMgaW5kZXBlbmRlbnRseSwgdGhpcyByZXByZXNlbnRhdGlvbiBkb2VzIG5vdCBwcmVzZXJ2ZSB3b3JkIG9yZGVyIG9yIGNhcHR1cmUgbXVsdGktd29yZCBleHByZXNzaW9ucywgbGltaXRpbmcgdGhlIGNvbnRleHR1YWwgaW5mb3JtYXRpb24gYXZhaWxhYmxlLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIEJpZ3JhbXMuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCldoaWxlIHVuaWdyYW1zIGZvY3VzIG9uIGluZGl2aWR1YWwgd29yZHMsIGJpZ3JhbXMgY2FwdHVyZSBwYWlycyBvZiBhZGphY2VudCB3b3Jkcy4gVGhpcyBhbGxvd3MgdGhlIHJlcHJlc2VudGF0aW9uIHRvIHByZXNlcnZlIHNob3J0LXJhbmdlIGRlcGVuZGVuY2llcyBhbmQgY29tbW9uIHR3by13b3JkIGV4cHJlc3Npb25zLg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBubHRrLnV0aWwgaW1wb3J0IG5ncmFtcw0KDQp0b2tlbnMgPSBzZW50ZW5jZS5zcGxpdCgpDQpiaWdyYW1zID0gbGlzdChuZ3JhbXModG9rZW5zLCAyKSkNClsiICIuam9pbih0b2tlbikgZm9yIHRva2VuIGluIGJpZ3JhbXNdDQpgYGANCg0KQmlncmFtcyBjYXB0dXJlIHBhaXJzIG9mIGFkamFjZW50IHdvcmRzLiBUaGlzIGFsbG93cyB0aGUgbW9kZWwgdG8gcHJlc2VydmUgc2hvcnQtcmFuZ2UgZGVwZW5kZW5jaWVzIGFuZCBjb21tb24gcGhyYXNlcyBzdWNoIGFzIGBkYXRhLWRyaXZlbiBkZWNpc2lvbmAgb3IgYGRlY2lzaW9uIG1ha2luZ2AsIHdoaWNoIHdvdWxkIGxvc2UgbWVhbmluZyBpZiBhbmFseXplZCB3b3JkIGJ5IHdvcmQuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIEJpZ3JhbSBmcmVxdWVuY3kgdGFibGUuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCkFzIHdpdGggdW5pZ3JhbXMsIGJpZ3JhbSBmcmVxdWVuY2llcyBjYW4gYmUgc3VtbWFyaXplZCBpbiBhIHRhYmxlIHRvIGZhY2lsaXRhdGUgaW50ZXJwcmV0YXRpb24uIFdoaWxlIHVuaWdyYW1zIGZvY3VzIG9uIGluZGl2aWR1YWwgd29yZHMsIGJpZ3JhbXMgY2FwdHVyZSBwYWlycyBvZiBhZGphY2VudCB3b3JkcywgYWxsb3dpbmcgdXMgdG8gb2JzZXJ2ZSBzaG9ydC1yYW5nZSBkZXBlbmRlbmNpZXMgYW5kIGNvbW1vbiB3b3JkIGNvbWJpbmF0aW9ucy4NCg0KDQpgYGB7cHl0aG9ufQ0KYmlncmFtX2ZyZXEgPSBDb3VudGVyKGJpZ3JhbXMpDQpiaWdyYW1fZnJlcQ0KYGBgDQoNCkZvciBncmVhdGVyIGNsYXJpdHksIHRoZSBiaWdyYW0gY291bnRzIGFyZSBvcmdhbml6ZWQgaW50byBhIHN0cnVjdHVyZWQgdGFibGUsIHdoZXJlIGVhY2ggcm93IHJlcHJlc2VudHMgYSB0d28td29yZCBzZXF1ZW5jZSBhbmQgaXRzIGZyZXF1ZW5jeTogDQoNCmBgYHtweXRob259DQpiaWdyYW1fdGFibGUgPSAoDQpwZC5EYXRhRnJhbWUoDQpbKCIgIi5qb2luKGspLCB2KSBmb3IgaywgdiBpbiBiaWdyYW1fZnJlcS5pdGVtcygpXSwNCmNvbHVtbnM9WyJCaWdyYW0iLCAiRnJlcXVlbmN5Il0NCikNCi5zb3J0X3ZhbHVlcygiRnJlcXVlbmN5IiwgYXNjZW5kaW5nPUZhbHNlKQ0KLnJlc2V0X2luZGV4KGRyb3A9VHJ1ZSkNCikNCg0KYmlncmFtX3RhYmxlDQpgYGANCg0KDQpUaGUgYmlncmFtIGZyZXF1ZW5jeSB0YWJsZSBoaWdobGlnaHRzIHNob3J0IGV4cHJlc3Npb25zIHRoYXQgcmVjdXIgaW4gdGhlIHRleHQuIEZvciBleGFtcGxlLCBgc3RhdGlzdGljcyBzdXBwb3J0c2AgYXBwZWFycyBtb3JlIHRoYW4gb25jZSwgc3VnZ2VzdGluZyBhIG1lYW5pbmdmdWwgbG9jYWwgZGVwZW5kZW5jeSBiZXR3ZWVuIHRoZXNlIHdvcmRzLiBDb21wYXJlZCB0byB1bmlncmFtcywgYmlncmFtcyBwcmVzZXJ2ZSB3b3JkIG9yZGVyIGFuZCBwcm92aWRlIHJpY2hlciBjb250ZXh0dWFsIGluZm9ybWF0aW9uLCBhbHRob3VnaCB0aGUgY29udGV4dCByZW1haW5zIGxpbWl0ZWQgdG8gdHdvLXdvcmQgd2luZG93cy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRyaWdyYW1zLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUcmlncmFtcyBleHRlbmQgdGhpcyBpZGVhIGJ5IGNhcHR1cmluZyBzZXF1ZW5jZXMgb2YgdGhyZWUgY29uc2VjdXRpdmUgd29yZHMuIFRoZXkgYXJlIHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIHJlcHJlc2VudGluZyBjb21wb3VuZCBjb25jZXB0cyBhbmQgZml4ZWQgZXhwcmVzc2lvbnMsIGF0IHRoZSBjb3N0IG9mIGluY3JlYXNlZCBzcGFyc2l0eS4NCg0KDQpgYGB7cHl0aG9ufQ0KdHJpZ3JhbXMgPSBsaXN0KG5ncmFtcyh0b2tlbnMsIDMpKQ0KWyIgIi5qb2luKHRva2VuKSBmb3IgdG9rZW4gaW4gdHJpZ3JhbXNdDQpgYGANCg0KDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMjIFRyaWdyYW0gZnJlcXVlbmN5IHRhYmxlLiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUcmlncmFtIGZyZXF1ZW5jaWVzIHN1bW1hcml6ZSBzZXF1ZW5jZXMgb2YgdGhyZWUgY29uc2VjdXRpdmUgdG9rZW5zIGV4dHJhY3RlZCBmcm9tIHRoZSB0ZXh0LiBCeSBleHRlbmRpbmcgdGhlIGNvbnRleHQgd2luZG93IGJleW9uZCBpbmRpdmlkdWFsIHdvcmRzIGFuZCB3b3JkIHBhaXJzLCB0cmlncmFtcyBhcmUgYWJsZSB0byByZXByZXNlbnQgbG9uZ2VyIGV4cHJlc3Npb25zIGFuZCBtb3JlIHNwZWNpZmljIHNlbWFudGljIHBhdHRlcm5zLg0KDQoNCmBgYHtweXRob259DQp0cmlncmFtX2ZyZXEgPSBDb3VudGVyKHRyaWdyYW1zKQ0KdHJpZ3JhbV9mcmVxDQpgYGANCg0KRm9yIGltcHJvdmVkIHJlYWRhYmlsaXR5LCB0aGUgdHJpZ3JhbSBjb3VudHMgY2FuIGJlIGFycmFuZ2VkIGluIGEgdGFibGUgZm9ybWF0LCB3aGVyZSBlYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhIHRocmVlLXdvcmQgc2VxdWVuY2UgYW5kIGl0cyBvYnNlcnZlZCBmcmVxdWVuY3k6DQoNCmBgYHtweXRob259DQp0cmlncmFtX3RhYmxlID0gKA0KcGQuRGF0YUZyYW1lKA0KWygiICIuam9pbihrKSwgdikgZm9yIGssIHYgaW4gdHJpZ3JhbV9mcmVxLml0ZW1zKCldLA0KY29sdW1ucz1bIlRyaWdyYW0iLCAiRnJlcXVlbmN5Il0NCikNCi5zb3J0X3ZhbHVlcygiRnJlcXVlbmN5IiwgYXNjZW5kaW5nPUZhbHNlKQ0KLnJlc2V0X2luZGV4KGRyb3A9VHJ1ZSkNCikNCg0KdHJpZ3JhbV90YWJsZQ0KYGBgDQoNCg0KSW4gdGhpcyBleGFtcGxlLCB0aGUgdHJpZ3JhbSBgYXBwbGllZCBzdGF0aXN0aWNzIHN1cHBvcnRzYCBhcHBlYXJzIHRocmVlIHRpbWVzLCB3aGlsZSBhbGwgb3RoZXIgdHJpZ3JhbXMgb2NjdXIgb25seSBvbmNlLiBUaGlzIGluZGljYXRlcyB0aGUgcHJlc2VuY2Ugb2YgYSByZXBlYXRlZCBsb2NhbCBwYXR0ZXJuIGluIHRoZSB0ZXh0LCB3aGVyZWFzIHRoZSByZW1haW5pbmcgdHJpZ3JhbXMgY29ycmVzcG9uZCB0byB1bmlxdWUgY29udGV4dHVhbCBzZXF1ZW5jZXMuDQoNCk5ldmVydGhlbGVzcywgZXZlbiB3aGVuIGZyZXF1ZW5jaWVzIGFyZSBlcXVhbCwgdHJpZ3JhbXMgcHJvdmlkZSB2YWx1YWJsZSBpbmZvcm1hdGlvbiBieSBwcmVzZXJ2aW5nIGxvY2FsIHN5bnRhY3RpYyBhbmQgc2VtYW50aWMgY29udGV4dC4gRm9yIGluc3RhbmNlLCBleHByZXNzaW9ucyBzdWNoIGFzIGBhcHBsaWVkIHN0YXRpc3RpY3Mgc3VwcG9ydHNgIG9yIGBkYXRhLWRyaXZlbiBkZWNpc2lvbiBtYWtpbmdgIGNhcHR1cmUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHdvcmRzIHRoYXQgYXJlIG5vdCB2aXNpYmxlIHdoZW4gdXNpbmcgdW5pZ3JhbXMgb3IgYmlncmFtcyBhbG9uZS4NCg0KVGhpcyBpbGx1c3RyYXRlcyBhIGtleSB0cmFkZS1vZmYgaW4gbi1ncmFtIG1vZGVsaW5nOiBhcyB0aGUgdmFsdWUgb2YgbiBpbmNyZWFzZXMsIG4tZ3JhbXMgdGVuZCB0byBiZWNvbWUgbW9yZSBpbmZvcm1hdGl2ZSBpbiB0ZXJtcyBvZiBjb250ZXh0dWFsIHJpY2huZXNzLCBidXQgYWxzbyBtb3JlIHNwYXJzZS4gSW4gbGFyZ2VyIGNvcnBvcmEsIHJlcGVhdGVkIHRyaWdyYW1zIHR5cGljYWxseSBlbWVyZ2UsIGFuZCB0aGVpciBmcmVxdWVuY3kgZGlzdHJpYnV0aW9ucyBiZWNvbWUgbW9yZSBtZWFuaW5nZnVsIGZvciBzdGF0aXN0aWNhbCBtb2RlbGluZyBhbmQgZmVhdHVyZSBleHRyYWN0aW9uLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBBIHV0aWxpdHkgZnVuY3Rpb24gZm9yIHZpc3VhbGl6aW5nIG4tZ3JhbSBmcmVxdWVuY2llcy4gey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KRnJlcXVlbmN5IHRhYmxlcyBhcmUgdGhlIHByaW1hcnkgYW5hbHl0aWNhbCBvdXRwdXQgaW4gbi1ncmFtIGFuYWx5c2lzLiBIb3dldmVyLCBzaW1wbGUgdmlzdWFsaXphdGlvbnMgY2FuIGJlIHVzZWZ1bCBmb3IgKmV4cGxvcmF0b3J5IGFuZCBwZWRhZ29naWNhbCBwdXJwb3NlcyosIGVzcGVjaWFsbHkgd2hlbiBpbnRyb2R1Y2luZyB0ZXh0LWJhc2VkIGZlYXR1cmVzIGZvciB0aGUgZmlyc3QgdGltZS4NCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBkZWZpbmUgYSBsaWdodHdlaWdodCB2aXN1YWxpemF0aW9uIHV0aWxpdHkgdGhhdCBwcm9kdWNlczoNCg0KLSBBIGJhciBwbG90IG9mIHRoZSBtb3N0IGZyZXF1ZW50IG4tZ3JhbXMuDQoNCi0gQSB3b3JkIGNsb3VkIHN1bW1hcml6aW5nIHJlbGF0aXZlIGZyZXF1ZW5jeSBwYXR0ZXJucy4NCg0KVGhlc2UgdmlzdWFsaXphdGlvbnMgYXJlIGludGVuZGVkIHRvIHN1cHBvcnQgaW50ZXJwcmV0YXRpb24gYW5kIGludHVpdGlvbi4gVGhleSBkbyAqbm90KiByZXBsYWNlIGZyZXF1ZW5jeSB0YWJsZXMsIHdoaWNoIHJlbWFpbiB0aGUgYXV0aG9yaXRhdGl2ZSBhbmFseXRpY2FsIHJlcHJlc2VudGF0aW9uLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQpgYGB7cHl0aG9uLCBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQ0KI3BpcCBpbnN0YWxsIG1hdHBsb3RsaWINCiNwaXAgaW5zdGFsbCB3b3JkY2xvdWQNCiNwaXAgaW5zdGFsbCBudW1weQ0KI3BpcCBpbnN0YWxsIGNvbGxlY3Rpb25zDQoNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gd29yZGNsb3VkIGltcG9ydCBXb3JkQ2xvdWQNCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IENvdW50ZXINCmZyb20gbWF0cGxvdGxpYi5ncmlkc3BlYyBpbXBvcnQgR3JpZFNwZWMNCg0KZGVmIHRvX2ZyZXFfZGljdCh4KToNCiAgICAiIiINCiAgICBDb252ZXJ0IGlucHV0IGludG8gYSBjbGVhbiBmcmVxdWVuY3kgZGljdGlvbmFyeSB7c3RyOiBpbnR9Lg0KICAgICIiIg0KICAgICMgQ2FzZSAxOiBhbHJlYWR5IGEgQ291bnRlciBvciBkaWN0DQogICAgaWYgaXNpbnN0YW5jZSh4LCAoQ291bnRlciwgZGljdCkpOg0KICAgICAgICBpdGVtcyA9IHguaXRlbXMoKQ0KICAgIGVsc2U6DQogICAgICAjIGlmIGl0J3MgYSBsaXN0IG9mIHRva2Vucw0KICAgICAgICBpdGVtcyA9IENvdW50ZXIoeCkuaXRlbXMoKQ0KDQogICAgY2xlYW4gPSB7fQ0KICAgIGZvciBrLCB2IGluIGl0ZW1zOg0KICAgICAgIyBFbnN1cmUgdmFsdWUgaXMgbnVtZXJpYw0KICAgICAgICBjbGVhbltzdHIoayldID0gaW50KHYpDQoNCiAgICByZXR1cm4gY2xlYW4NCg0KDQpkZWYgcGxvdF9uZ3JhbShmcmVxX2xpa2UsIHRpdGxlLCB0b3Bfbj0xNSwgbWF4X2ZvbnRfc2l6ZT0xNTAsIG1pbl9mb250X3NpemU9MTgsIHNjYWxlPTIpOg0KICAgIHdvcmRzID0gdG9fZnJlcV9kaWN0KGZyZXFfbGlrZSkNCg0KICAgICMgS2VlcCB0b3BfbiBmb3IgcmVhZGFiaWxpdHkNCiAgICB0b3AgPSBkaWN0KA0KICAgICAgICBzb3J0ZWQod29yZHMuaXRlbXMoKSwga2V5PWxhbWJkYSBrdjoga3ZbMV0sIHJldmVyc2U9VHJ1ZSlbOnRvcF9uXQ0KICAgICkNCg0KICAgIHBsdC5maWd1cmUoZmlnc2l6ZT0oMTIsNCkpDQogICAgDQogICAgIyBCYXIgcGxvdA0KICAgIHBsdC5zdWJwbG90KDEsMiwxKQ0KICAgIHBsdC5iYXIodG9wLmtleXMoKSwgdG9wLnZhbHVlcygpKQ0KICAgIHBsdC54dGlja3Mocm90YXRpb249NDUsIGhhPSJyaWdodCIpDQogICAgcGx0LnRpdGxlKGYie3RpdGxlfSDigJMgVG9wIHt0b3Bfbn0gRnJlcXVlbmNpZXMiKQ0KDQogICAgIyBXb3JkIGNsb3VkDQogICAgcGx0LnN1YnBsb3QoMSwyLDIpDQogICAgI3djID0gV29yZENsb3VkKGJhY2tncm91bmRfY29sb3I9IndoaXRlIikuZ2VuZXJhdGVfZnJvbV9mcmVxdWVuY2llcyh0b3ApDQogICAgd2MgPSBXb3JkQ2xvdWQoDQogICAgICAgIHdpZHRoPTgwMCwNCiAgICAgICAgaGVpZ2h0PTQwMCwNCiAgICAgICAgYmFja2dyb3VuZF9jb2xvcj0id2hpdGUiLA0KICAgICAgICBtYXhfZm9udF9zaXplPW1heF9mb250X3NpemUsDQogICAgICAgIG1pbl9mb250X3NpemU9bWluX2ZvbnRfc2l6ZSwNCiAgICAgICAgcHJlZmVyX2hvcml6b250YWw9MS4wLA0KICAgICAgICBjb2xsb2NhdGlvbnM9RmFsc2UsDQogICAgICAgIHNjYWxlPXNjYWxlDQogICAgKS5nZW5lcmF0ZV9mcm9tX2ZyZXF1ZW5jaWVzKHRvcCkNCiAgICANCiAgICBwbHQuaW1zaG93KHdjKQ0KICAgIHBsdC5heGlzKCJvZmYiKQ0KICAgIHBsdC50aXRsZShmInt0aXRsZX0g4oCTIFdvcmQgQ2xvdWQiKQ0KDQogICAgcGx0LnRpZ2h0X2xheW91dCgpDQogICAgcGx0LnNob3coKQ0KYGBgDQoNCg0KYGBge3B5dGhvbn0NCiNwaXAgaW5zdGFsbCBtYXRwbG90bGliDQojcGlwIGluc3RhbGwgd29yZGNsb3VkDQojcGlwIGluc3RhbGwgbnVtcHkNCiNwaXAgaW5zdGFsbCBjb2xsZWN0aW9ucw0KDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQpmcm9tIHdvcmRjbG91ZCBpbXBvcnQgV29yZENsb3VkDQpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBDb3VudGVyDQpmcm9tIG1hdHBsb3RsaWIuZ3JpZHNwZWMgaW1wb3J0IEdyaWRTcGVjDQoNCmRlZiB0b19mcmVxX2RpY3QoeCk6DQogICAgIiIiDQogICAgQ29udmVydCBpbnB1dCBpbnRvIGEgY2xlYW4gZnJlcXVlbmN5IGRpY3Rpb25hcnkge3N0cjogaW50fS4NCiAgICAiIiINCiAgICAjIENhc2UgMTogYWxyZWFkeSBhIENvdW50ZXIgb3IgZGljdA0KICAgIGlmIGlzaW5zdGFuY2UoeCwgKENvdW50ZXIsIGRpY3QpKToNCiAgICAgICAgaXRlbXMgPSB4Lml0ZW1zKCkNCiAgICBlbHNlOg0KICAgICAgIyBpZiBpdCdzIGEgbGlzdCBvZiB0b2tlbnMNCiAgICAgICAgaXRlbXMgPSBDb3VudGVyKHgpLml0ZW1zKCkNCg0KICAgIGNsZWFuID0ge30NCiAgICBmb3IgaywgdiBpbiBpdGVtczoNCiAgICAgICMgRW5zdXJlIHZhbHVlIGlzIG51bWVyaWMNCiAgICAgICAgY2xlYW5bc3RyKGspXSA9IGludCh2KQ0KDQogICAgcmV0dXJuIGNsZWFuDQoNCmRlZiBwbG90X25ncmFtKA0KICAgIGZyZXFfbGlrZSwNCiAgICB0aXRsZSwNCiAgICB0b3Bfbj0xNSwNCiAgICBtYXhfZm9udF9zaXplPTE1MCwNCiAgICBtaW5fZm9udF9zaXplPTE4LA0KICAgIHNjYWxlPTQNCik6DQogICAgd29yZHMgPSB0b19mcmVxX2RpY3QoZnJlcV9saWtlKQ0KDQogICAgdG9wID0gZGljdCgNCiAgICAgICAgc29ydGVkKHdvcmRzLml0ZW1zKCksIGtleT1sYW1iZGEga3Y6IGt2WzFdLCByZXZlcnNlPVRydWUpWzp0b3Bfbl0NCiAgICApDQoNCiAgICBmaWcgPSBwbHQuZmlndXJlKGZpZ3NpemU9KDE4LDYpKQ0KICAgIGdzID0gR3JpZFNwZWMoMSwgMiwgd2lkdGhfcmF0aW9zPVsxLCAxLjVdKSAgIyBtw6FzIGVzcGFjaW8gYSBsYSBudWJlDQogICAgDQogICAgIyBCYXIgcGxvdA0KICAgIGF4MSA9IGZpZy5hZGRfc3VicGxvdChnc1swXSkNCiAgICAjYXgxLmJhcih0b3Aua2V5cygpLCB0b3AudmFsdWVzKCkpDQogICAgDQogICAgIyBCYXIgcGxvdCAoaG9yaXpvbnRhbCkNCiAgICBsYWJlbHMgPSBsaXN0KHRvcC5rZXlzKCkpDQogICAgdmFsdWVzID0gbGlzdCh0b3AudmFsdWVzKCkpDQogICAgDQogICAgIyBzb3J0IGZvciBuaWNlciBob3Jpem9udGFsIHBsb3R0aW5nDQogICAgcGFpcnMgPSBzb3J0ZWQoemlwKGxhYmVscywgdmFsdWVzKSwga2V5PWxhbWJkYSB4OiB4WzFdKQ0KICAgIGxhYmVscywgdmFsdWVzID0gemlwKCpwYWlycykNCiAgICANCiAgICBheDEuYmFyaChsYWJlbHMsIHZhbHVlcykNCiAgICBheDEuc2V0X3RpdGxlKGYie3RpdGxlfSDigJMgVG9wIHt0b3Bfbn0gRnJlcXVlbmNpZXMiKQ0KICAgIGF4MS50aWNrX3BhcmFtcyhheGlzPSJ5IiwgbGFiZWxzaXplPTE2KSAgICMgY29udHJvbGEgdGFtYcOxbyBldGlxdWV0YXMNCiAgICAjYXgxLnRpY2tfcGFyYW1zKGF4aXM9IngiLCByb3RhdGlvbj00NSkNCiAgICANCiAgICAjIFdvcmQgY2xvdWQNCiAgICBheDIgPSBmaWcuYWRkX3N1YnBsb3QoZ3NbMV0pDQogICAgd2MgPSBXb3JkQ2xvdWQoDQogICAgICAgIHdpZHRoPTEzMDAsDQogICAgICAgIGhlaWdodD02MDAsDQogICAgICAgIGJhY2tncm91bmRfY29sb3I9IndoaXRlIiwNCiAgICAgICAgbWF4X2ZvbnRfc2l6ZT1tYXhfZm9udF9zaXplLA0KICAgICAgICBtaW5fZm9udF9zaXplPW1pbl9mb250X3NpemUsDQogICAgICAgIHNjYWxlPXNjYWxlLA0KICAgICAgICBwcmVmZXJfaG9yaXpvbnRhbD0xLjAsDQogICAgICAgIGNvbGxvY2F0aW9ucz1GYWxzZQ0KICAgICkuZ2VuZXJhdGVfZnJvbV9mcmVxdWVuY2llcyh0b3ApDQoNCiAgICBheDIuaW1zaG93KHdjKQ0KICAgIGF4Mi5heGlzKCJvZmYiKQ0KICAgIGF4Mi5zZXRfdGl0bGUoZiJ7dGl0bGV9IOKAkyBXb3JkIENsb3VkIiwgZm9udHNpemU9MTQpDQoNCiAgICBwbHQudGlnaHRfbGF5b3V0KCkNCiAgICBwbHQuc2hvdygpDQpgYGANCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBFeHBsYW5hdGlvbiBvZiB0aGUgaGVscGVyIGZ1bmN0aW9ucyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgY29kZSBhYm92ZSBkZWZpbmVzIHR3byBoZWxwZXIgZnVuY3Rpb25zIHRoYXQgd29yayB0b2dldGhlciB0byBwcmVwYXJlIGFuZCB2aXN1YWxpemUgbi1ncmFtIGZyZXF1ZW5jeSBpbmZvcm1hdGlvbi4NCg0KKipGdW5jdGlvbiBgdG9fZnJlcV9kaWN0KClgKioNCg0KVGhpcyBmdW5jdGlvbiBjb252ZXJ0cyBkaWZmZXJlbnQgdHlwZXMgb2YgaW5wdXQgaW50byBhIHN0YW5kYXJkaXplZCBmcmVxdWVuY3kgZGljdGlvbmFyeSBvZiB0aGUgZm9ybToNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpuLWdyYW0gIOKGkiAgZnJlcXVlbmN5DQpgYGANCg0KSXRzIHB1cnBvc2UgaXMgdG8gZW5zdXJlIHRoYXQgdGhlIHZpc3VhbGl6YXRpb24gZnVuY3Rpb24gcmVjZWl2ZXMgZGF0YSBpbiBhIGNvbnNpc3RlbnQgYW5kIGVycm9yLWZyZWUgZm9ybWF0LCByZWdhcmRsZXNzIG9mIHdoZXRoZXIgdGhlIGlucHV0IGlzOg0KDQotIEEgYENvdW50ZXJgIG9iamVjdC4NCg0KLSBBIHJlZ3VsYXIgZGljdGlvbmFyeS4NCg0KLSBPciBhIGxpc3Qgb2YgdG9rZW5zIG9yIG4tZ3JhbXMuDQoNCkluIHByYWN0aWNhbCB0ZXJtcywgdGhpcyBmdW5jdGlvbjoNCg0KLSBFeHRyYWN0cyB0aGUgZnJlcXVlbmN5IGNvdW50cy4NCg0KLSBDb252ZXJ0cyBhbGwga2V5cyB0byBzdHJpbmdzLg0KDQotIEVuc3VyZXMgdGhhdCBhbGwgZnJlcXVlbmNpZXMgYXJlIG51bWVyaWMuDQoNClRoaXMgcHJlcHJvY2Vzc2luZyBzdGVwIGF2b2lkcyBlcnJvcnMgYW5kIG1ha2VzIHRoZSBwbG90dGluZyBmdW5jdGlvbiBtb3JlIHJvYnVzdC4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCioqRnVuY3Rpb24gYHBsb3RfbmdyYW0oKWAqKg0KDQpUaGlzIGZ1bmN0aW9uIGdlbmVyYXRlcyB0d28gY29tcGxlbWVudGFyeSB2aXN1YWwgc3VtbWFyaWVzIGZyb20gdGhlIGZyZXF1ZW5jeSBpbmZvcm1hdGlvbjoNCg0KMS4gKkJhciBwbG90KjogICBEaXNwbGF5cyB0aGUgbW9zdCBmcmVxdWVudCBuLWdyYW1zIGFuZCB0aGVpciBjb3VudHMsIGFsbG93aW5nIGZvciBkaXJlY3QgcXVhbnRpdGF0aXZlIGNvbXBhcmlzb24uDQoNCjIuICpXb3JkIGNsb3VkKjogIFByb3ZpZGVzIGEgcXVhbGl0YXRpdmUgdmlzdWFsaXphdGlvbiB3aGVyZSBtb3JlIGZyZXF1ZW50IG4tZ3JhbXMgYXBwZWFyIG1vcmUgcHJvbWluZW50bHksIG9mZmVyaW5nIGFuIGludHVpdGl2ZSBvdmVydmlldyBvZiByZWxhdGl2ZSBpbXBvcnRhbmNlLg0KDQpGb3IgcmVhZGFiaWxpdHksIG9ubHkgdGhlIHRvcCAqbiogbW9zdCBmcmVxdWVudCBuLWdyYW1zIGFyZSBkaXNwbGF5ZWQgKGNvbnRyb2xsZWQgYnkgdGhlIGB0b3BfbmAgYXJndW1lbnQpLg0KDQpPdmVyYWxsLCB0aGlzIGZ1bmN0aW9uIHBsYXlzIGFuICpleHBsb3JhdG9yeSogcm9sZTogaXQgaGVscHMgdXMgdmlzdWFsbHkgaW5zcGVjdCBwYXR0ZXJucyBpbiB0aGUgZGF0YSwgd2hpbGUgdGhlIGZyZXF1ZW5jeSB0YWJsZXMgcmVtYWluIHRoZSBwcmltYXJ5IGFuYWx5dGljYWwgcmVmZXJlbmNlLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyBBcHBseWluZyB0aGUgdmlzdWFsaXphdGlvbiB0byBuLWdyYW0gZXhhbXBsZXMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KV2Ugbm93IGFwcGx5IHRoZSB2aXN1YWxpemF0aW9uIHV0aWxpdHkgdG8gdGhlIHVuaWdyYW0sIGJpZ3JhbSwgYW5kIHRyaWdyYW0gZnJlcXVlbmN5IG9iamVjdHMgY29tcHV0ZWQgZWFybGllci4gVGhpcyBpbGx1c3RyYXRlcyBob3cgdGhlIHNhbWUgZnVuY3Rpb24gY2FuIGJlIHJldXNlZCB0byBleHBsb3JlIGRpZmZlcmVudCBsZXZlbHMgb2YgdGV4dHVhbCBjb250ZXh0Lg0KDQpUaGUgcmVzdWx0aW5nIHBsb3RzIGZhY2lsaXRhdGUgY29tcGFyaXNvbiBhY3Jvc3Mgbi1ncmFtIHR5cGVzIGFuZCBoZWxwIGhpZ2hsaWdodCBob3cgY29udGV4dHVhbCBpbmZvcm1hdGlvbiBpbmNyZWFzZXMgYXMgKm4qIGdyb3dzLiBBcyBlbXBoYXNpemVkIHRocm91Z2hvdXQgdGhpcyBzZWN0aW9uLCBmcmVxdWVuY3kgdGFibGVzIHJlbWFpbiB0aGUgcHJpbWFyeSBhbmFseXRpY2FsIHJlZmVyZW5jZS4NCg0KDQpgYGB7cHl0aG9ufQ0KIyBFeGFtcGxlIHVzYWdlICh0aGVzZSBjYW4gYmUgQ291bnRlci9kaWN0IG9yIGxpc3RzKQ0KcGxvdF9uZ3JhbSh1bmlncmFtX2ZyZXEsICJVbmlncmFtcyIpDQpwbG90X25ncmFtKGJpZ3JhbV9mcmVxLCAiQmlncmFtcyIsIHRvcF9uPTEwKQ0KcGxvdF9uZ3JhbSh0cmlncmFtX2ZyZXEsICJUcmlncmFtcyIsIHRvcF9uPTUsICBtYXhfZm9udF9zaXplPTIwMCwgIG1pbl9mb250X3NpemU9MjUsIHNjYWxlPTQpDQpgYGANCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBSZW1vdmluZyBIVE1MIHRhZ3MNCg0KSW4gbWFueSBOTFAgYXBwbGljYXRpb25zLCB0ZXh0IGRhdGEgaXMgY29sbGVjdGVkIGZyb20gd2ViIHNvdXJjZXMuIFN1Y2ggZGF0YSBvZnRlbiBjb250YWlucyBIVE1MIHRhZ3MsIHdoaWNoIG1heSBpbnRyb2R1Y2Ugbm9pc2UgaW50byB0aGUgYW5hbHlzaXMuDQoNCkluIG1vc3QgY2FzZXMsIEhUTUwgdGFncyBkbyBub3QgY29udHJpYnV0ZSB0byB0aGUgbGluZ3Vpc3RpYyBjb250ZW50IG9mIHRoZSB0ZXh0IGFuZCBzaG91bGQgYmUgcmVtb3ZlZC4gSG93ZXZlciwgaW4gc29tZSBzcGVjaWFsaXplZCBhcHBsaWNhdGlvbnMsIHRhZ3MgbWF5IGVuY29kZSB1c2VmdWwgaW5mb3JtYXRpb24uIFRoZXJlZm9yZSwgdGhlaXIgdHJlYXRtZW50IGRlcGVuZHMgb24gdGhlIHNwZWNpZmljIHRhc2suDQoNClRoZSBmb2xsb3dpbmcgZXhhbXBsZSBpbGx1c3RyYXRlcyBob3cgSFRNTCB0YWdzIGNhbiBiZSByZW1vdmVkIHVzaW5nIHRoZSBgQmVhdXRpZnVsU291cGAgbGlicmFyeTo6DQoNCmBgYHtweXRob259DQpmcm9tIGJzNCBpbXBvcnQgQmVhdXRpZnVsU291cA0KDQpodG1sID0gIjxodG1sPjxib2R5PjxoMj5Db3Vyc2UgQW5ub3VuY2VtZW50PC9oMj48cD5GaW5hbCBwcm9qZWN0IGR1ZSBuZXh0IHdlZWsuPC9wPjwvYm9keT48L2h0bWw+Ig0Kc291cCA9IEJlYXV0aWZ1bFNvdXAoaHRtbCkNCnRleHQgPSBzb3VwLmdldF90ZXh0KCkNCnRleHQNCmBgYA0KDQpJbiB0aGlzIG91dHB1dCwgYWxsIEhUTUwgdGFncyAoc3VjaCBhcyBgPGh0bWw+YCwgYDxib2R5PmAsIGA8aDI+YCwgYW5kIGA8cD5gKSBhcmUgcmVtb3ZlZCwgbGVhdmluZyBvbmx5IHRoZSByYXcgdGV4dHVhbCBjb250ZW50LiBUaGUgaGVhZGluZyBhbmQgdGhlIHBhcmFncmFwaCBhcmUgcHJlc2VydmVkIGFzIHRleHQsIGJ1dCB0aGUgc3RydWN0dXJhbCBpbmZvcm1hdGlvbiBlbmNvZGVkIGJ5IHRoZSBIVE1MIG1hcmt1cCBpcyBsb3N0Lg0KDQpOb3RpY2UgdGhhdCB0aGUgcmVzdWx0aW5nIHN0cmluZyBjb25jYXRlbmF0ZXMgdGhlIGhlYWRpbmcgYW5kIHRoZSBwYXJhZ3JhcGggd2l0aG91dCBhbiBleHBsaWNpdCBzcGFjZSBvciBsaW5lIGJyZWFrIGJldHdlZW4gdGhlbS4gVGhpcyBoaWdobGlnaHRzIGFuIGltcG9ydGFudCBwcmFjdGljYWwgaXNzdWU6IHdoaWxlIEhUTUwgdGFnIHJlbW92YWwgc2ltcGxpZmllcyB0aGUgdGV4dCwgaXQgbWF5IGludHJvZHVjZSBmb3JtYXR0aW5nIGFydGlmYWN0cyB0aGF0IHJlcXVpcmUgYWRkaXRpb25hbCBwb3N0LXByb2Nlc3NpbmcsIHN1Y2ggYXMgaW5zZXJ0aW5nIHNwYWNlcywgbGluZSBicmVha3MsIG9yIHBlcmZvcm1pbmcgc2VudGVuY2Ugc2VnbWVudGF0aW9uLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMjIE5vdGUuIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCkluIHNpbXBsZSBjYXNlcywgSFRNTCB0YWdzIGNhbiBhbHNvIGJlIHJlbW92ZWQgdXNpbmcgcmVndWxhciBleHByZXNzaW9ucy4gSG93ZXZlciwgdGhpcyBhcHByb2FjaCBpcyBnZW5lcmFsbHkgZGlzY291cmFnZWQgZm9yIHJlYWwtd29ybGQgSFRNTCBkYXRhLCBhcyBpdCBpcyBmcmFnaWxlIGFuZCBtYXkgZmFpbCB3aGVuIHRhZ3MgYXJlIG5lc3RlZCwgbWFsZm9ybWVkLCBvciBjb250YWluIGF0dHJpYnV0ZXMuIExpYnJhcmllcyBzdWNoIGFzIGBCZWF1dGlmdWxTb3VwYCBwcm92aWRlIGEgbW9yZSByb2J1c3QgYW5kIHJlbGlhYmxlIHNvbHV0aW9uIGZvciBwcmFjdGljYWwgTkxQIHBpcGVsaW5lcy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMjIyBIb3cgZG9lcyBhbGwgdGhpcyBmaXQgaW50byBteSBOTFAgcGlwZWxpbmU/DQoNClRoZSBwcmVwcm9jZXNzaW5nIHN0ZXBzIGRpc2N1c3NlZCBzbyBmYXLigJRsZXhpY2FsIG5vcm1hbGl6YXRpb24sIHN0b3B3b3JkIGhhbmRsaW5nLCBjYXNlIGZvbGRpbmcsIG4tZ3JhbXMsIGFuZCBub2lzZSByZW1vdmFs4oCUYXJlIHR5cGljYWxseSBhcHBsaWVkICpiZWZvcmUqIGJ1aWxkaW5nIHN0YXRpc3RpY2FsIG9yIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLg0KDQpXaGljaCBzdGVwcyBhcmUgYXBwbGllZCwgYW5kIGluIHdoYXQgb3JkZXIsIGRlcGVuZHMgZW50aXJlbHkgb24gdGhlIHVzZSBjYXNlLiBGb3IgdGhpcyByZWFzb24sIHByZXByb2Nlc3NpbmcgY2hvaWNlcyBzaG91bGQgYmUgdmlld2VkIGFzICptb2RlbGluZyBkZWNpc2lvbnMqLCBub3QgZml4ZWQgb3IgdW5pdmVyc2FsIHJ1bGVzLg0KDQpBZnRlciBwcmVwcm9jZXNzaW5nLCB0b2tlbnMgY2FuIGJlIGFnZ3JlZ2F0ZWQgdG8gZm9ybSBhICp2b2NhYnVsYXJ5Kiwgd2hpY2ggZGVmaW5lcyB0aGUgc2V0IG9mIHVuaXRzIHVzZWQgdG8gcmVwcmVzZW50IHRleHQgbnVtZXJpY2FsbHkuIFRoZSB2b2NhYnVsYXJ5IHNlcnZlcyBhcyB0aGUgaW50ZXJmYWNlIGJldHdlZW4gcmF3IHRleHQgYW5kIHF1YW50aXRhdGl2ZSByZXByZXNlbnRhdGlvbnMuDQoNCg0KYGBge3B5dGhvbn0NCnNlbnRlbmNlID0gIkFwcGxpZWQgc3RhdGlzdGljcyBzdXBwb3J0cyBkYXRhLWRyaXZlbiBkZWNpc2lvbiBtYWtpbmciDQp0b2tlbnMgPSBzZXQoc2VudGVuY2UubG93ZXIoKS5zcGxpdCgpKQ0Kdm9jYWJ1bGFyeSA9IHNvcnRlZCh0b2tlbnMpDQp2b2NhYnVsYXJ5DQpgYGANCg0KSW4gdGhpcyBleGFtcGxlLCB0aGUgdm9jYWJ1bGFyeSBjb25zaXN0cyBvZiB0aGUgdW5pcXVlIHRva2VucyBvYnRhaW5lZCBhZnRlciBiYXNpYyBwcmVwcm9jZXNzaW5nICgqbG93ZXJjYXNpbmcqIGFuZCAqdG9rZW5pemF0aW9uKikuIEVhY2ggZWxlbWVudCByZXByZXNlbnRzIGEgZGlzdGluY3QgdW5pdCB0aGF0IGNhbiBsYXRlciBiZSBtYXBwZWQgdG8gbnVtZXJpY2FsIGZlYXR1cmVzLg0KDQpUaGlzIHZvY2FidWxhcnkgZm9ybXMgdGhlIGZvdW5kYXRpb24gZm9yIHJlcHJlc2VudGluZyB0ZXh0IGluIGEgc3RydWN0dXJlZCBhbmQgY29uc2lzdGVudCB3YXksIHNlcnZpbmcgYXMgdGhlIGludGVyZmFjZSBiZXR3ZWVuIHJhdyBsYW5ndWFnZSBkYXRhIGFuZCBxdWFudGl0YXRpdmUgYW5hbHlzaXMuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIE1vZGVybiBhbmQgcG9wdWxhciBsYXJnZSBsYW5ndWFnZSBtb2RlbHMgKExMTXMpDQoNCiMjIyBUb2tlbml6YXRpb24gaW4gbW9kZXJuIExMTXMNCg0KV2hpbGUgdGhlIHRva2VuaXphdGlvbiBtZXRob2RzIGRpc2N1c3NlZCBzbyBmYXIgYXJlIHdpZGVseSB1c2VkIGluIGNsYXNzaWNhbCBOTFAgcGlwZWxpbmVzLCBtb2Rlcm4gKipsYXJnZSBsYW5ndWFnZSBtb2RlbHMgKExMTXMpKiogcmVseSBvbiBtb3JlIHNvcGhpc3RpY2F0ZWQgKnN1YndvcmQtYmFzZWQgdG9rZW5pemF0aW9uIHNjaGVtZXMqIGRlc2lnbmVkIHRvIGJhbGFuY2UgbGluZ3Vpc3RpYyBjb3ZlcmFnZSwgZWZmaWNpZW5jeSwgYW5kIHNjYWxhYmlsaXR5Lg0KDQpNb3N0IGNvbnRlbXBvcmFyeSBMTE1zIGRvICpub3QqIG9wZXJhdGUgZGlyZWN0bHkgb24gd29yZHMgb3IgY2hhcmFjdGVycy4gSW5zdGVhZCwgdGhleSB0b2tlbml6ZSB0ZXh0IGludG8gKnN1YndvcmQgdW5pdHMqLCB3aGljaCBtYXkgY29ycmVzcG9uZCB0byBmdWxsIHdvcmRzLCB3b3JkIGZyYWdtZW50cywgb3IgZXZlbiBpbmRpdmlkdWFsIGNoYXJhY3RlcnMsIGRlcGVuZGluZyBvbiBmcmVxdWVuY3kgYW5kIGNvbnRleHQuDQoNCkluIHByYWN0aWNlLCBjdXJyZW50IG1vZGVscyBhZG9wdCB2YXJpYXRpb25zIG9mICpkYXRhLWRyaXZlbiBzdWJ3b3JkIHRva2VuaXphdGlvbiosIGluY2x1ZGluZzoNCg0KLSAqKkJ5dGUgUGFpciBFbmNvZGluZyAoQlBFKSoqIGFuZCBpdHMgZXh0ZW5zaW9ucy4gDQoNCi0gKipVbmlncmFtIGxhbmd1YWdlIG1vZGVsIHRva2VuaXphdGlvbioqLiAgDQoNCi0gKipCeXRlLWxldmVsIHRva2VuaXphdGlvbioqLCB3aGljaCBvcGVyYXRlcyBkaXJlY3RseSBvbiByYXcgYnl0ZXMgcmF0aGVyIHRoYW4gY2hhcmFjdGVycy4gIA0KDQpUaGVzZSBhcHByb2FjaGVzIGFsbG93IG1vZGVscyB0byBoYW5kbGUgcmFyZSB3b3JkcywgbXVsdGlsaW5ndWFsIHRleHQsIGFuZCBwcmV2aW91c2x5IHVuc2VlbiBzdHJpbmdzIHdoaWxlIGtlZXBpbmcgdm9jYWJ1bGFyeSBzaXplcyBtYW5hZ2VhYmxlLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBUb2tlbml6YXRpb24gc3RyYXRlZ2llcyB1c2VkIGJ5IHBvcHVsYXIgTExNcyANCg0KQWx0aG91Z2ggZXhhY3QgaW1wbGVtZW50YXRpb25zIGFyZSBvZnRlbiBwcm9wcmlldGFyeSwgdGhlIGZvbGxvd2luZyBoaWdoLWxldmVsIHBhdHRlcm5zIGFyZSB3ZWxsIGVzdGFibGlzaGVkOg0KDQotICoqQ2hhdEdQVCAvIEdQVC1mYW1pbHkgbW9kZWxzIChPcGVuQUkpLioqICAgVXNlICpieXRlLWxldmVsIEJQReKAk3N0eWxlIHRva2VuaXphdGlvbiosIHdoZXJlIHRva2VucyBtYXkgcmVwcmVzZW50IGNoYXJhY3RlcnMsIHN1YndvcmRzLCBvciBmcmVxdWVudCB3b3JkIHNlcXVlbmNlcy4NCg0KLSAqKkNsYXVkZSAoQW50aHJvcGljKS4qKiAgIFJlbGllcyBvbiAqc3Vid29yZCB0b2tlbml6YXRpb24qIHdpdGggc3Ryb25nIGVtcGhhc2lzIG9uIHJvYnVzdG5lc3MgdG8gcmFyZSBhbmQgb3V0LW9mLXZvY2FidWxhcnkgc3RyaW5ncy4NCg0KLSAqKkdlbWluaSBtb2RlbHMgKEdvb2dsZSkuKiogIEJ1aWxkIHVwb24gKlNlbnRlbmNlUGllY2Utc3R5bGUgdG9rZW5pemF0aW9uKiwgc3VwcG9ydGluZyBtdWx0aWxpbmd1YWwgYW5kIGJ5dGUtYXdhcmUgcmVwcmVzZW50YXRpb25zLg0KDQotICoqRGVlcFNlZWsgbW9kZWxzLioqICBFeHBsb3JlIGFkdmFuY2VkIGNvbXByZXNzaW9uLWF3YXJlIGFuZCBjb250ZXh0LXNlbnNpdGl2ZSB0b2tlbml6YXRpb24gc3RyYXRlZ2llcywgcGFydGljdWxhcmx5IGZvciBsb25nLWNvbnRleHQgYW5kIG11bHRpbW9kYWwgaW5wdXRzLg0KDQpSYXRoZXIgdGhhbiByZWZsZWN0aW5nIGxpbmd1aXN0aWMgdW5pdHMgZGlyZWN0bHksIHRoZXNlIHRva2VuaXplcnMgYXJlIG9wdGltaXplZCBmb3IgKnN0YXRpc3RpY2FsIGVmZmljaWVuY3kgYW5kIG1vZGVsIHBlcmZvcm1hbmNlKi4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgUmVjZW50IHJlc2VhcmNoIG9uIHRva2VuaXphdGlvbiBpbiBMTE1zIA0KDQpUb2tlbml6YXRpb24gcmVtYWlucyBhbiBhY3RpdmUgcmVzZWFyY2ggYXJlYSwgcGFydGljdWxhcmx5IGFzIGxhcmdlIGxhbmd1YWdlIG1vZGVscyBjb250aW51ZSB0byBzY2FsZSBpbiAqKm1vZGVsIHNpemUsIGNvbnRleHQgbGVuZ3RoLCBhbmQgbW9kYWxpdHkqKi4gUmVjZW50IHN0dWRpZXMgaGF2ZSByZXZpc2l0ZWQgdGhlIHJvbGUgb2YgdG9rZW5pemF0aW9uLCBleHBsb3JpbmcgYWx0ZXJuYXRpdmVzIHRvIHRyYWRpdGlvbmFsIHN1YndvcmQtYmFzZWQgc2NoZW1lcyBhbmQgaGlnaGxpZ2h0aW5nIGl0cyBpbXBhY3Qgb24gZWZmaWNpZW5jeSwgcmVwcmVzZW50YXRpb24sIGFuZCBsZWFybmluZyBkeW5hbWljcy4NCg0KU2V2ZXJhbCByZWNlbnQgY29udHJpYnV0aW9ucyBpbGx1c3RyYXRlIHRoZXNlIHRyZW5kczoNCg0KLSBbR29vZ2xlIChhcnhpdiwgMTcgZGljLiAyMDI1KV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzI1MTIuMTQ5ODIpOiAqKlByb21wdCBSZXBldGl0aW9uIEltcHJvdmVzIE5vbi1SZWFzb25pbmcgTExNcyoqLiBUaGlzIHdvcmsgZW1waGFzaXplcyB0aGF0IHRva2VuaXphdGlvbi1yZWxhdGVkIGRlc2lnbiBjaG9pY2VzIGNhbiBzaWduaWZpY2FudGx5IGFmZmVjdCBtb2RlbCBlZmZpY2llbmN5IGFuZCByZXByZXNlbnRhdGlvbmFsIGNhcGFjaXR5LCBlc3BlY2lhbGx5IGluIGxhcmdlLWNvbnRleHQgc2V0dGluZ3MuDQoNCi0gW0hvbmcgS29uZyBhbmQgSHVhemhvbmcgVW5pdmVyc2l0aWVzIChhcnhpdiwgMjQgb2N0LiAyMDI1KV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzI0MDkuMDQ3MDEpOiAqKlVuaVRvazogQSBVbmlmaWVkIFRva2VuaXplciBmb3IgVmlzdWFsIEdlbmVyYXRpb24gYW5kIFVuZGVyc3RhbmRpbmcuKiogICBUaGlzIHdvcmsgaW52ZXN0aWdhdGVzIGhvdyB0b2tlbml6YXRpb24gY2hvaWNlcyBpbnRlcmFjdCB3aXRoIG1vZGVsIGFyY2hpdGVjdHVyZSBhbmQgdHJhaW5pbmcgZHluYW1pY3MgaW4gbGFyZ2UgbGFuZ3VhZ2UgbW9kZWxzLiBUaGUgYXV0aG9ycyBzaG93IHRoYXQgdG9rZW5pemF0aW9uIGFmZmVjdHMgbm90IG9ubHkgc2VxdWVuY2UgbGVuZ3RoIGFuZCBlZmZpY2llbmN5LCBidXQgYWxzbyBvcHRpbWl6YXRpb24gYmVoYXZpb3IgYW5kIGdlbmVyYWxpemF0aW9uLCBmdXJ0aGVyIHN1cHBvcnRpbmcgdGhlIHZpZXcgb2YgdG9rZW5pemF0aW9uIGFzIGEgY29yZSBtb2RlbGluZyBkZWNpc2lvbi4NCg0KLSBbRGVlcHNlZWsgKGFyeGl2LCAyMSBvY3QuIDIwMjUpXShodHRwczovL2FyeGl2Lm9yZy9hYnMvMjUxMC4xODIzNCk6ICoqRGVlcFNlZWstT0NSOiBDb250ZXh0cyBPcHRpY2FsIENvbXByZXNzaW9uLioqICAgIEludHJvZHVjZXMgY29tcHJlc3Npb24tb3JpZW50ZWQgdG9rZW5pemF0aW9uIHN0cmF0ZWdpZXMgYWltZWQgYXQgaW1wcm92aW5nIGVmZmljaWVuY3kgYW5kIHNjYWxhYmlsaXR5IGluIGxvbmctY29udGV4dCBsYW5ndWFnZSBtb2RlbHMuDQoNCg0KLSBbR3VudGhlciBldCBhbC4gKGFyeGl2LCA3IGp1bC4gMjAyNSldKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8yNDA5LjA0NzAxKTogKipSZXRoaW5raW5nIHRva2VuaXphdGlvbiBmb3IgbGFyZ2UgbGFuZ3VhZ2UgbW9kZWxzLioqICAgRXhhbWluZXMgbGltaXRhdGlvbnMgb2YgY29udmVudGlvbmFsIHN1YndvcmQgdG9rZW5pemVycyBhbmQgcHJvcG9zZXMgYWx0ZXJuYXRpdmUgZm9ybXVsYXRpb25zIHRoYXQgYmV0dGVyIGFsaWduIHdpdGggbW9kZXJuIExMTSBhcmNoaXRlY3R1cmVzLg0KDQotIFtQYWdub25pIGV0IGFsLiAoYXJ4aXYsIDEzIGRpYy4gMjAyNCldKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8yNDEyLjA5ODcxKTogICoqQnl0ZSBMYXRlbnQgVHJhbnNmb3JtZXI6IFBhdGNoZXMgU2NhbGUgQmV0dGVyIFRoYW4gVG9rZW5zKiouICBTaG93cyB0aGF0IHRva2VuaXphdGlvbiBzY2hlbWVzIGluZHVjZSBzdHJ1Y3R1cmFsIGJpYXNlcyBpbiBMTE1zLCBhZmZlY3RpbmcgbGVhcm5lZCByZXByZXNlbnRhdGlvbnMgYW5kIGRvd25zdHJlYW0gYmVoYXZpb3IuIFN1cHBvcnRzIHRoZSB2aWV3IG9mIHRva2VuaXphdGlvbiBhcyBhIGNvcmUgYXJjaGl0ZWN0dXJhbCBkZXNpZ24gY2hvaWNlLg0KDQotIFtTY2htaWR0IGV0IGFsLiAoYXJ4aXYsIDcgb2N0LiAyMDI0KV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzI0MDIuMTgzNzYpOiAqKlRva2VuaXphdGlvbiBpcyBtb3JlIHRoYW4gY29tcHJlc3Npb24uKiogIEFuYWx5emVzIGhvdyB0b2tlbml6YXRpb24gY2hvaWNlcyBpbmZsdWVuY2UgcmVwcmVzZW50YXRpb24gbGVhcm5pbmcgYW5kIGRvd25zdHJlYW0gdGFzayBwZXJmb3JtYW5jZSBiZXlvbmQgc2ltcGxlIGNvbXByZXNzaW9uIGVmZmljaWVuY3kuDQoNClRvZ2V0aGVyLCB0aGVzZSBzdHVkaWVzIGhpZ2hsaWdodCB0aGF0IHRva2VuaXphdGlvbiBpcyAqbm90IG1lcmVseSBhIHByZXByb2Nlc3Npbmcgc3RlcCosIGJ1dCBhIGNvcmUgZGVzaWduIGNvbXBvbmVudCB0aGF0IGRpcmVjdGx5IHNoYXBlcyBtb2RlbCBjYXBhY2l0eSwgZWZmaWNpZW5jeSwgYW5kIGdlbmVyYWxpemF0aW9uLg0KDQpUaGlzIHBlcnNwZWN0aXZlIHByb3ZpZGVzIGEgbmF0dXJhbCBicmlkZ2UgYmV0d2VlbiBjbGFzc2ljYWwgTkxQIHByZXByb2Nlc3NpbmcgdGVjaG5pcXVlcyBhbmQgdGhlIHJlcHJlc2VudGF0aW9uIGxlYXJuaW5nIG1ldGhvZHMgZW1wbG95ZWQgaW4gbW9kZXJuIGRlZXAgbGVhcm5pbmfigJNiYXNlZCBsYW5ndWFnZSBtb2RlbHMuIFRva2VuaXphdGlvbiBoYXMgZXZvbHZlZCBmcm9tIGEgcHJlcHJvY2Vzc2luZyBoZXVyaXN0aWMgaW50byBhIGNlbnRyYWwgcmVzZWFyY2ggdG9waWMgaW4gbGFyZ2Utc2NhbGUgbGFuZ3VhZ2UgbW9kZWxpbmcuDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgU3VtbWFyeQ0KDQpJbiB0aGlzIGRvY3VtZW50LCB3ZSBleGFtaW5lZCB0aGUgbWFpbiBzdGVwcyBpbnZvbHZlZCBpbiBjb25zdHJ1Y3RpbmcgYSB2b2NhYnVsYXJ5IGZvciBuYXR1cmFsIGxhbmd1YWdlIHByb2Nlc3NpbmcgdGFza3MuIFRoZXNlIHN0ZXBzIGZvcm0gdGhlIGZvdW5kYXRpb24gb2YgdGV4dCBwcmVwcm9jZXNzaW5nIGFuZCBwbGF5IGEgY2VudHJhbCByb2xlIGluIGhvdyBsaW5ndWlzdGljIGRhdGEgaXMgcHJlcGFyZWQgZm9yIGFuYWx5c2lzLg0KDQpUZXh0IHByZXByb2Nlc3NpbmcgaXMgYSBjcml0aWNhbCBjb21wb25lbnQgb2YgYW55IG1hY2hpbmUgbGVhcm5pbmcgd29ya2Zsb3csIGFuZCB0aGlzIGlzIGVzcGVjaWFsbHkgdHJ1ZSBpbiBOTFAuIFRob3VnaHRmdWwgcHJlcHJvY2Vzc2luZyBoZWxwcyByZWR1Y2Ugbm9pc2UsIGNvbnRyb2wgdmFyaWFiaWxpdHksIGFuZCBzaGFwZSB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhIGluIHdheXMgdGhhdCBmYWNpbGl0YXRlIGVmZmVjdGl2ZSBtb2RlbGluZy4gV2hlbiB0aGVzZSBzdGVwcyBhcmUgY2FyZWZ1bGx5IGRlc2lnbmVkIGFuZCBhbGlnbmVkIHdpdGggdGhlIHRhc2sgYXQgaGFuZCwgdGhleSBvZnRlbiBsZWFkIHRvIG1vcmUgc3RhYmxlIGFuZCBpbnRlcnByZXRhYmxlIHJlc3VsdHMgdGhhbiBhcHByb2FjaGVzIHRoYXQgcmVseSBvbiByYXcgdGV4dCBhbG9uZS4NCg0KQXMgZGlzY3Vzc2VkIGluIHRoZSBmaW5hbCBzZWN0aW9ucyBvZiB0aGlzIGRvY3VtZW50LCBtYW55IHByZXByb2Nlc3NpbmcgZGVjaXNpb25zIChwYXJ0aWN1bGFybHkgdGhvc2UgcmVsYXRlZCB0byB0b2tlbml6YXRpb24pIGFsc28gcGxheSBhIGZ1bmRhbWVudGFsIHJvbGUgaW4gbW9kZXJuIGxhcmdlIGxhbmd1YWdlIG1vZGVscywgd2hlcmUgdGhleSBkaXJlY3RseSBpbmZsdWVuY2UgZWZmaWNpZW5jeSwgcmVwcmVzZW50YXRpb24sIGFuZCBvdmVyYWxsIG1vZGVsIGJlaGF2aW9yLg0KDQpJbiBvdGhlciBkb2N1bWVudHMgKFtjbGljayBoZXJlXShodHRwczovL3JwdWJzLmNvbS9obGxpbmFzL1JfQUkxX3RvYykpLCB3ZSBidWlsZCBvbiB0aGVzZSBjb25jZXB0cyBieSBhcHBseWluZyB0aGUgcHJlcHJvY2Vzc2luZyB0ZWNobmlxdWVzIGRpc2N1c3NlZCBoZXJlIHRvIGNvbnN0cnVjdCBtYXRoZW1hdGljYWwgcmVwcmVzZW50YXRpb25zIG9mIHRleHQgdGhhdCBjYW4gYmUgdXNlZCBkaXJlY3RseSBieSBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMuDQoNCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIEFwcGxpZWQgYWN0aXZpdHk6IGZyb20gbHlyaWNzIHRvIHZvY2FidWxhcnkgY29uc3RydWN0aW9uDQoNClRoaXMgYWN0aXZpdHkgaXMgZGVzaWduZWQgdG8gaW50ZWdyYXRlIGFuZCBhcHBseSBhbGwgdGhlIGNvbmNlcHRzIGludHJvZHVjZWQgaW4gdGhpcyBkb2N1bWVudC4gVGhlIHJlYWRlciBpcyBhc2tlZCB0byB3b3JrIHdpdGggYSBzaG9ydCBzb25nIGZyYWdtZW50IG9mIHRoZWlyIGNob2ljZSBhbmQgcGVyZm9ybSBhIGNvbXBsZXRlIGxleGljYWwgYW5hbHlzaXMgdXNpbmcgUi4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBPYmplY3RpdmUNCg0KVG8gY29uc3RydWN0IGEgcmVwcm9kdWNpYmxlIGxleGljYWwgYW5hbHlzaXMgcGlwZWxpbmUgdGhhdCBtb3ZlcyBmcm9tIHJhdyB0ZXh0IHRvIHRva2VuaXphdGlvbiwgdm9jYWJ1bGFyeSBjb25zdHJ1Y3Rpb24sIGFuZCBub3JtYWxpemF0aW9uLCBpbGx1c3RyYXRpbmcga2V5IE5MUCBwcmVwcm9jZXNzaW5nIGNvbmNlcHRzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBJbnN0cnVjdGlvbnMNCg0KMS4gU2VsZWN0IGEgc29uZyBvZiB5b3VyIGNob2ljZSBhbmQgd29yayB3aXRoOg0KDQogICAtIEEgc2hvcnQgZnJhZ21lbnQgKGUuZy4sIDbigJM4IGxpbmVzKSwgb3IgIA0KICAgDQogICAtIEEgc29uZyB3aXRoIHB1YmxpYy1kb21haW4gb3Igb3BlbiBsaWNlbnNpbmcuDQoNCjIuIENyZWF0ZSBhbiAqUiBNYXJrZG93biAoYC5SbWRgKSogZG9jdW1lbnQgdGhhdCBjb21waWxlcyBzdWNjZXNzZnVsbHkgdG8gKkhUTUwqIChvciBQREYpLg0KDQozLiBUaGUgZG9jdW1lbnQgbXVzdCBpbmNsdWRlIGJvdGg6DQoNCiAgIC0gVGhlICpSIGNvZGUqLCBhbmQgIA0KICAgDQogICAtIFRoZSAqcmVzdWx0aW5nIG91dHB1dCogKHRhYmxlcywgcHJpbnRlZCBvYmplY3RzLCBvciB2aXN1YWwgc3VtbWFyaWVzKS4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgUmVxdWlyZWQgU2VjdGlvbnMNCg0KDQojIyMjIDEuIFRleHQgRGVzY3JpcHRpb24uICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCkJyaWVmbHkgZGVzY3JpYmUgdGhlIGNob3NlbiBzb25nIGFuZCB0aGUgcmVhc29uIGZvciBzZWxlY3RpbmcgaXQuIEluY2x1ZGUgdGhlIHRleHQgZnJhZ21lbnQgdXNlZCBmb3IgdGhlIGFuYWx5c2lzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgMi4gTGV4aWNvbnMuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNCkRlZmluZSBhIHNtYWxsIGxleGljb24gKGF0IGxlYXN0IDE1IGVudHJpZXMpIGRlcml2ZWQgZnJvbSB0aGUgdGV4dCwgaW5jbHVkaW5nOg0KDQotIFRoZSBsZXhpY2FsIGl0ZW0uDQoNCi0gQSBjb25jZXB0dWFsIGNhdGVnb3J5IChlLmcuLCBlbW90aW9uLCBhY3Rpb24sIHBsYWNlKS4NCg0KLSBBIHNob3J0IGRlc2NyaXB0aW9uIG9yIGludGVycHJldGF0aW9uLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIyAzLiBQaG9uZW1lcywgR3JhcGhlbWVzLCBhbmQgTW9ycGhlbWVzLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpFeHBsYWluLCBpbiBjb25jZXB0dWFsIHRlcm1zLCB0aGUgZGlzdGluY3Rpb24gYmV0d2VlbiBwaG9uZW1lcywgZ3JhcGhlbWVzLCBhbmQgbW9ycGhlbWVzLiAgSWxsdXN0cmF0ZSB0aGVzZSBjb25jZXB0cyB1c2luZyBhIHNtYWxsIHNldCBvZiB3b3JkcyBmcm9tIHRoZSBzZWxlY3RlZCB0ZXh0Lg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgNC4gVG9rZW5pemF0aW9uLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpBcHBseSBhbmQgY29tcGFyZSBkaWZmZXJlbnQgdG9rZW5pemF0aW9uIHN0cmF0ZWdpZXMsIGluY2x1ZGluZzoNCg0KLSBTZW50ZW5jZSB0b2tlbml6YXRpb24uDQoNCi0gV29yZCB0b2tlbml6YXRpb24uDQoNCi0gQ2hhcmFjdGVyLWxldmVsIHRva2VuaXphdGlvbi4NCg0KUmVwb3J0Og0KDQotIFRoZSB0b3RhbCBudW1iZXIgb2YgdG9rZW5zLg0KDQotIFRoZSBtb3N0IGZyZXF1ZW50IHRva2Vucy4NCg0KLSBBIHNob3J0IGludGVycHJldGF0aW9uIG9mIHRoZSByZXN1bHRzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgNS4gRGlmZmVyZW50IFR5cGVzIG9mIFRva2VuaXplcnMuICB7LnVubGlzdGVkIC51bm51bWJlcmVkfSAgDQoNClVzaW5nICpydWxlLWJhc2VkIHRva2VuaXphdGlvbiosIGRlc2lnbiBhdCBsZWFzdCB0d28gcmVndWxhciBleHByZXNzaW9ucyB0byBleHRyYWN0IHNwZWNpZmljIGVudGl0aWVzIGZyb20gdGhlIHRleHQgKGUuZy4sIG51bWJlcnMsIGRhdGVzLCBwcmljZXMsIGhhc2h0YWdzKS4gIA0KU2hvdyB0aGUgbWF0Y2hlZCByZXN1bHRzIGFuZCBleHBsYWluIHdoYXQgZWFjaCBwYXR0ZXJuIGNhcHR1cmVzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgNi4gV29yZCBOb3JtYWxpemF0aW9uLiAgey51bmxpc3RlZCAudW5udW1iZXJlZH0gIA0KDQpBcHBseSBjb21tb24gbm9ybWFsaXphdGlvbiB0ZWNobmlxdWVzLCBzdWNoIGFzOg0KDQotIExvd2VyY2FzaW5nLg0KDQotIFB1bmN0dWF0aW9uIHJlbW92YWwuDQoNCi0gU3RvcHdvcmQgcmVtb3ZhbC4NCg0KLSBTdGVtbWluZyBvciBsZW1tYXRpemF0aW9uLg0KDQpDb21wYXJlIHRoZSB2b2NhYnVsYXJ5ICpiZWZvcmUgYW5kIGFmdGVyKiBub3JtYWxpemF0aW9uIGFuZCBkaXNjdXNzIHRoZSBvYnNlcnZlZCBjaGFuZ2VzLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyMgNy4gU3VtbWFyeS4gIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9ICANCg0KUHJvdmlkZSBhIGNvbmNpc2UgcmVmbGVjdGlvbiAoNeKAkzggbGluZXMpIG9uIGhvdyBsZXhpY2FsIGNob2ljZXMsIHRva2VuaXphdGlvbiwgYW5kIG5vcm1hbGl6YXRpb24gYWZmZWN0IHZvY2FidWxhcnkgY29uc3RydWN0aW9uIGFuZCB0ZXh0dWFsIHJlcHJlc2VudGF0aW9uIGluIE5MUC4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgUmVwcm9kdWNpYmlsaXR5IFJlcXVpcmVtZW50DQoNClRoZSBSIE1hcmtkb3duIGRvY3VtZW50IG11c3QgYmUgZnVsbHkgcmVwcm9kdWNpYmxlLCBtZWFuaW5nIHRoYXQgYWxsIGNvZGUgY2h1bmtzIGV4ZWN1dGUgd2l0aG91dCBlcnJvcnMgYW5kIGdlbmVyYXRlIHRoZSByZXBvcnRlZCBvdXRwdXRzIHdoZW4gdGhlIGRvY3VtZW50IGlzIGNvbXBpbGVkLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiZuYnNwOw0KDQoNCiZuYnNwOw0KPGNlbnRlcj4NCn5+fg0KSWYgeW91IGZvdW5kIGFueSBFUlJPUlMgb3IgaGF2ZSBTVUdHRVNUSU9OUywgcGxlYXNlIHJlcG9ydCB0aGVtIHRvIG15IGVtYWlsLiBUaGFua3MuICANCn5+fg0KPC9jZW50ZXI+DQoNCg0K