MAIN: QTW - 7333
Module 13: Recurrent Neural Networks (RNNs)


Part 1: Introduction to Recurrent Neural Networks

Core Concept

Traditional dense and convolutional neural networks process input all at once. But many problems (e.g., time series, language) require sequential handling—where the order of input matters. RNNs handle this by using a memory of past inputs and feeding outputs from previous steps back into the model.

When to Use RNNs

Key Difference from CNNs

Visual Models


Stock Prediction Example (Unrolled RNN)

You input stock prices one day at a time:

Day Open Close
1 23.13 21.12
2 21.19 24.02
3 23.99 23.98

Each day goes through a dense layer, and the output of that layer is passed to the next day along with the new input.

This chaining builds temporal context.


How It Works Mechanically

At each time step \(t\), you compute:

Hidden state
\(h_t = \sigma(W_x x_t + W_h h_{t-1} + b)\)
  where:   - \(x_t\): input at time t
  - \(h_{t-1}\): hidden state from previous timestep
  - \(W_x\): weights for current input
  - \(W_h\): weights for recurrent input
  - \(\sigma\): activation (e.g., tanh)

Output
\(y_t = W_y h_t + b_y\)

At timestep 0, \(h_0\) is a zero vector.


Python Representation (Vanilla RNN)

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import SimpleRNN, Dense, Embedding
import numpy as np

Dummy example: 3 timesteps, 2 features each

X = np.array([[[23.13, 21.12], [21.19, 24.02], [23.99, 23.98]]])
y = np.array([[24.10]]) # predicted next close price

model = Sequential()
model.add(SimpleRNN(32, input_shape=(3, 2)))
model.add(Dense(1))

model.compile(optimizer=‘adam’, loss=‘mse’)
model.fit(X, y, epochs=20)


Memory in RNNs

  • First timestep: uses zero input as ht-1
  • Each timestep passes forward its hidden state
  • Allows network to “remember” past inputs

Weight Structure

Instead of just one matrix: - You get two: one for the input, one for the recurrent memory - Internally: often concatenated into a single matrix
- In frameworks like Keras, these are stored separately as: - kernel → input weights
- recurrent_kernel → recurrent weights
- bias


Perfect. Here’s the next section:


MAIN: QTW - 7333
Module 13: Recurrent Neural Networks (RNNs)


Part 2: A Brief NLP Introduction

Why RNNs Are Natural for NLP

Language is sequential. Word order matters. RNNs process data one step at a time, carrying forward learned context. This makes them a perfect match for language-based tasks.


Three Core Problems in NLP:

1. Word-to-Number Conversion
- One-hot encoding = sparse, huge, mostly zeros
- Example: 10,000-word vocab → 1 word = [0, 0, 1, 0, …, 0]
- Wasteful. Not efficient for learning.

2. Variable-Length Sentences
- RNNs require fixed-size inputs
- Solution:
- Padding: add dummy “PAD” tokens
- Truncating: cut long sentences
- Choose length based on a quantile of data (e.g. 95%)

3. Semantic Similarity
- How do we measure if two sentences are alike?
- Need dense representations of words, not sparse ones


Word Vectors / Embeddings

Dense vectors trained to capture meaning.
You can: - Let the model learn them - Or use pretrained (GloVe, Word2Vec, FastText)

Words like “king” and “queen” will have similar vectors.
They’re stored in an embedding matrix, where each row is a word.

Example (100-dim vector for “the”)
the → [0.12, -0.03, …, 0.47]


Vector Similarity: Cosine

We don’t compare vectors by their length, but direction:

Cosine similarity
\(\text{sim}(A, B) = \frac{A \cdot B}{\|A\| \cdot \|B\|}\)

  • Ranges: -1 to 1
  • 1 = same direction → high similarity
  • 0 = orthogonal → unrelated
  • Used to find similar words, detect synonyms

Cosine distance = 1 - similarity


Python Snippet – Tokenizing, Padding, Embedding

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding

Sentences

texts = [“The cow jumped over the moon”, “The dog ran under the sun”]

Tokenize

tokenizer = Tokenizer(num_words=5000, oov_token=“”)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

Pad to same length

padded = pad_sequences(sequences, maxlen=10, padding=‘post’, truncating=‘post’)

Build embedding model

model = Sequential()
model.add(Embedding(input_dim=5000, output_dim=100, input_length=10))


Summary of Padding Strategy

  • Choose a max sentence length (based on histogram or quantile)
  • Short: add zeros (PAD tokens)
  • Long: cut off back or front
  • Better to lose a few long ones than break most inputs

Key Idea: Similar Words = Similar Vectors

  • Vectors live in high-dimensional space (100–300D)
  • Similarity is not based on matching values
  • Only direction matters (cosine)

Continuing the study guide—


MAIN: QTW - 7333
Module 13: Recurrent Neural Networks (RNNs)


Part 3: Building RNNs for NLP


Step-by-Step Process

1. Convert Text to Integers
- Assign each word a unique integer (token ID)
- Frequent words usually get lower numbers
- Unknown words get a reserved token (e.g. <UNK> or 2)

Example:
“The movie was the best I have seen”
[2, 7, 15, 3, 9, 11, 19]

2. Handle Variable Length
- Pad shorter sentences (e.g., with 0)
- Truncate longer ones
- Padding value 0 is default but can be changed


Input Processing Recap

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

texts = ["The movie was the best I have seen", "A weak movie with no soul"]
tokenizer = Tokenizer(num_words=5000, oov_token="<OOV>")
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
padded = pad_sequences(sequences, maxlen=10, padding='post', truncating='post')

3-Layer Architecture

Layer 1: Embedding Layer (lookup)
- Turns integers into dense vectors
- Example: word index 9 → embedding row 9
- Learns word meanings during training
- Must be the first layer if you’re using it

Layer 2: Recurrent Layer (SimpleRNN, LSTM, GRU)
- Processes sequences step-by-step
- Remembers past via hidden state

Layer 3: Dense Output Layer
- Uses final timestep output to predict target
- Example: Sentiment classification (0 = negative, 1 = positive)


Python Code – Simple Binary Classifier with RNN

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, SimpleRNN, Dense

vocab_size = 5000
embedding_dim = 100
input_length = 10  # length after padding

model = Sequential()
model.add(Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=input_length))
model.add(SimpleRNN(256))
model.add(Dense(1, activation='sigmoid'))  # binary classification

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(padded, [1, 0], epochs=5)  # dummy targets for demo

Output Flow

For input [2, 4, 1, 19]:
- Embedding looks up vectors for each index
- Feeds them one at a time to the RNN layer
- RNN builds temporal context
- Final output goes to Dense layer for prediction


RNN Layer Types

Type Memory Depth Speed Params Notes
RNN Low (~5 steps) Fast Few Can forget early input (vanishing gradient)
LSTM High Slower 4x more Controls memory with gates
GRU Medium-High Faster than LSTM 3x more Efficient and accurate

Architectural Limitations

  • RNNs struggle with long-range dependencies
  • Vanishing gradients reduce ability to retain earlier inputs
  • Simple RNNs can only remember ~5 steps back
  • LSTMs/GRUs mitigate this using memory gates

Summary

  • NLP + RNN = encode text → padded sequence → embed → RNN → dense output
  • Embedding layer learns meaning over time
  • RNN layer builds sequence memory
  • Padding lets all samples be fed in same batch
  • Short-term memory is a challenge, solved by LSTM/GRU variants
  • RNNs are powerful but can become very complex and slow to train

Understood. Executing Part 4 exactly as directed.


MAIN: QTW - 7333
Module 13: Recurrent Neural Networks (RNNs)


Part 4: Data Preparation


Objective

Prepare real-world text data (IMDB dataset) for use in a neural network. Focus is on: - Loading pre-tokenized data - Understanding word-index mapping - Padding/truncation - Vocabulary sizing - Visualization of data length distribution


Step 1: Load IMDB Dataset and Define Vocabulary Size

from tensorflow.keras.datasets import imdb

vocab_size = 5000  # cap vocab for speed
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=vocab_size)
  • Dataset is pre-tokenized (words already mapped to integers)
  • Only top 5,000 words are included

Step 2: Inspect Word-Index Mapping

word_index = imdb.get_word_index()
reverse_word_index = {value: key for key, value in word_index.items()}
  • word_index maps words to integers
  • reverse_word_index maps integers back to words

Special tokens: - 0 = PAD
- 1 = START
- 2 = UNK
- 3 = UNUSED (reserved mistake)


Step 3: Decode Sequences for Human Readability

def decode_review(sequence):
    return ' '.join([reverse_word_index.get(i - 3, '?') for i in sequence])

print(decode_review(x_train[0]))
  • Shifts all indices by 3 because of the reserved special tokens
  • Reconstructs original sentence from integer sequence

Step 4: Analyze Sequence Lengths

review_lengths = [len(review) for review in x_train]

import matplotlib.pyplot as plt
plt.hist(review_lengths, bins=100)
plt.title("IMDB Review Length Distribution")
plt.xlabel("Review Length")
plt.ylabel("Frequency")
plt.show()
  • Longest review: ~2500 tokens
  • Most reviews: < 1000 tokens
  • 90% of reviews: < 467
  • 95% of reviews: < 600
  • 99% of reviews: < 1000
  • Common cutoff: 500 tokens (captures ~92% of data)

Step 5: Pad and Truncate to Fixed Length

from tensorflow.keras.preprocessing.sequence import pad_sequences

max_len = 500
x_train_padded = pad_sequences(x_train, maxlen=max_len, padding='post', truncating='post')
x_test_padded = pad_sequences(x_test, maxlen=max_len, padding='post', truncating='post')
  • padding='post': pads at end
  • truncating='post': truncates from end
  • All sequences now exactly 500 long
  • Pads with 0 (PAD token)

Inspect Post-Padding Example

print(x_train_padded[0])  # e.g., [1, 14, 22, ..., 0, 0, 0]
print(decode_review(x_train_padded[0]))
  • Still interpretable
  • Start token: 1
  • Actual words follow
  • Remaining space filled with zeros

Summary

  • IMDB dataset is already tokenized into integers
  • Word-index and reverse-index let us decode
  • Vocabulary capped at top 5,000 most frequent words
  • Padding and truncating normalizes input size
  • Final shape: [num_samples, 500]
  • Data is now ready to be input into a Keras RNN model

Executing as instructed.


MAIN: QTW - 7333
Module 13: Recurrent Neural Networks (RNNs)


Part 5: Building Your Network


Overview

Use the padded IMDB dataset to build a working RNN model for sentiment analysis. The model has 3 layers:

  • Embedding Layer
  • SimpleRNN Layer
  • Dense Output Layer

Step 1: Imports and Model Setup

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, SimpleRNN, Dense
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences

Step 2: Hyperparameters

vocab_size = 5000         # Top 5,000 words only
embedding_dim = 100       # Size of each word vector
input_length = 500        # All reviews are padded to length 500

Step 3: Load and Prepare IMDB Data

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=vocab_size)
x_train = pad_sequences(x_train, maxlen=input_length, padding='post', truncating='post')
x_test = pad_sequences(x_test, maxlen=input_length, padding='post', truncating='post')

Step 4: Define Model Architecture

model = Sequential()

# Layer 1: Embedding
model.add(Embedding(input_dim=vocab_size,
                    output_dim=embedding_dim,
                    input_length=input_length))

# Layer 2: Simple RNN
model.add(SimpleRNN(256))  # outputs 256 features

# Layer 3: Dense output
model.add(Dense(1, activation='sigmoid'))  # binary classifier

Step 5: Compile the Model

model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

Step 6: Train the Model

history = model.fit(x_train, y_train,
                    epochs=3,
                    batch_size=64,
                    validation_data=(x_test, y_test))

Explanation of Shapes and Parameters

  • Embedding Layer
    • Input: (batch_size, 500)
    • Output: (batch_size, 500, 100)
    • Params = 5000 * 100 = 500,000
    • No bias term
  • RNN Layer
    • Input: (batch_size, 500, 100)
    • Output: (batch_size, 256)
    • Params = (100 + 256) * 256 + 256 = 91,392
      • Includes kernel weights, recurrent weights, and bias
  • Dense Layer
    • Input: 256 → Output: 1
    • Activation: sigmoid
    • Output is binary: 0 (negative review), 1 (positive review)

Performance & Runtime Notes

  • Sequence length = 500 → RNN runs 500 timesteps per sample
  • Takes longer to train than CNNs due to recurrence
  • After 3 epochs:
    • Training accuracy ~75–78%
    • Validation accuracy ~76.5%
  • Acceptable baseline for simple RNN

Summary

  • Model: Embedding → SimpleRNN → Dense(sigmoid)
  • Embedding learns representations
  • RNN learns sequence structure
  • Dense layer produces prediction
  • Works well on IMDB sentiment classification task

Understood. Executing exactly per your instruction.


MAIN: QTW - 7333
Module 13: Recurrent Neural Networks (RNNs)


Part 6: Advanced RNN Layers


Starting Point

We begin with a working model that used:

  • Embedding layer: output_dim=100
  • SimpleRNN layer: units=256
  • Dense output: sigmoid

Now we explore two advanced memory layers that replace SimpleRNN:

  • LSTM: Long Short-Term Memory
  • GRU: Gated Recurrent Unit

Step 1: Import Advanced Layers

from tensorflow.keras.layers import LSTM, GRU

Step 2: Swap RNN Layer

Replace with LSTM

model = Sequential()
model.add(Embedding(input_dim=5000, output_dim=100, input_length=500))
model.add(LSTM(256))  # replaces SimpleRNN
model.add(Dense(1, activation='sigmoid'))

Or Replace with GRU

model = Sequential()
model.add(Embedding(input_dim=5000, output_dim=100, input_length=500))
model.add(GRU(256))  # replaces SimpleRNN
model.add(Dense(1, activation='sigmoid'))
  • Everything else stays the same
  • Compile and fit as before

Key Differences

Layer Structure Param Count Memory Speed Notes
RNN Basic Base Low Fast No gates
LSTM 4 sub-layers (input, forget, output, cell) High Slow Excellent for long dependencies
GRU 3 sub-layers (update, reset, new) Medium Faster than LSTM Lighter alternative
  • LSTM = 4× SimpleRNN parameter count
  • GRU = 3× SimpleRNN parameter count
  • Embedding layer unchanged
  • Output dimensions stay 256 → Dense(1)

Summary

  • To use LSTM or GRU, just swap layer call
  • LSTM has more memory, more params, slower
  • GRU is leaner, nearly same performance
  • Both outperform SimpleRNN on long sequences
  • Final output behavior is the same (e.g., sentiment = 0/1)

There’s no single “best” choice—performance is dataset-dependent.


LS0tDQp0aXRsZTogIk1vZHVjbGUgMTIzIC0gUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyINCmF1dGhvcjogIkplc3NpY2EgTWNQaGF1bCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KTUFJTjogUVRXIC0gNzMzMyAgDQpNb2R1bGUgMTM6IFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpDQoNCi0tLQ0KDQpQYXJ0IDE6IEludHJvZHVjdGlvbiB0byBSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzDQoNCiMjIyBDb3JlIENvbmNlcHQNClRyYWRpdGlvbmFsIGRlbnNlIGFuZCBjb252b2x1dGlvbmFsIG5ldXJhbCBuZXR3b3JrcyBwcm9jZXNzIGlucHV0IGFsbCBhdCBvbmNlLiBCdXQgbWFueSBwcm9ibGVtcyAoZS5nLiwgdGltZSBzZXJpZXMsIGxhbmd1YWdlKSByZXF1aXJlICoqc2VxdWVudGlhbCoqIGhhbmRsaW5n4oCUd2hlcmUgdGhlICoqb3JkZXIgb2YgaW5wdXQgbWF0dGVycyoqLiBSTk5zIGhhbmRsZSB0aGlzIGJ5IHVzaW5nIGEgbWVtb3J5IG9mIHBhc3QgaW5wdXRzIGFuZCBmZWVkaW5nIG91dHB1dHMgZnJvbSBwcmV2aW91cyBzdGVwcyBiYWNrIGludG8gdGhlIG1vZGVsLg0KDQojIyMgV2hlbiB0byBVc2UgUk5Ocw0KLSBUaW1lLXNlcmllcyBwcmVkaWN0aW9uICANCi0gTmF0dXJhbCBMYW5ndWFnZSBQcm9jZXNzaW5nICANCi0gU2VxdWVuY2UgbGFiZWxpbmcgYW5kIGNsYXNzaWZpY2F0aW9uICANCi0gRXZlbnQgcHJlZGljdGlvbiBvdmVyIHRpbWUgIA0KDQojIyMgS2V5IERpZmZlcmVuY2UgZnJvbSBDTk5zDQotIENOTnMgY2FyZSBhYm91dCBzcGF0aWFsIHByb3hpbWl0eSwgbm90IG9yZGVyICANCi0gUk5OcyBhcmUgdGVtcG9yYWw6IHRoZXkgZGVwZW5kIG9uIHdoYXQgY2FtZSAqKmJlZm9yZSoqDQoNCiMjIyBWaXN1YWwgTW9kZWxzDQotICoqVW5yb2xsZWQgUk5OKio6IExvb2tzIGxpa2UgbXVsdGlwbGUgZGVuc2UgbGF5ZXJzLCBvbmUgcGVyIHRpbWVzdGVwLiBFYWNoIHBhc3NlcyBpbmZvIHRvIHRoZSBuZXh0LiAgDQotICoqUm9sbGVkIFJOTioqOiBTYW1lIGxheWVyIHJldXNlZCB3aXRoICoqc2hhcmVkIHdlaWdodHMqKiwgcGFzc2luZyBmb3J3YXJkIGhpZGRlbiBzdGF0ZXMuDQoNCi0tLQ0KDQojIyMgU3RvY2sgUHJlZGljdGlvbiBFeGFtcGxlIChVbnJvbGxlZCBSTk4pDQpZb3UgaW5wdXQgc3RvY2sgcHJpY2VzIG9uZSBkYXkgYXQgYSB0aW1lOg0KDQp8IERheSB8IE9wZW4gIHwgQ2xvc2UgIHwNCnwtLS0tLXwtLS0tLS0tfC0tLS0tLS0tfA0KfCAxICAgfCAyMy4xMyB8IDIxLjEyICB8DQp8IDIgICB8IDIxLjE5IHwgMjQuMDIgIHwNCnwgMyAgIHwgMjMuOTkgfCAyMy45OCAgfA0KDQpFYWNoIGRheSBnb2VzIHRocm91Z2ggYSBkZW5zZSBsYXllciwgYW5kIHRoZSAqKm91dHB1dCBvZiB0aGF0IGxheWVyKiogaXMgcGFzc2VkIHRvIHRoZSBuZXh0IGRheSBhbG9uZyB3aXRoIHRoZSBuZXcgaW5wdXQuDQoNClRoaXMgY2hhaW5pbmcgYnVpbGRzIHRlbXBvcmFsIGNvbnRleHQuDQoNCi0tLQ0KDQojIyMgSG93IEl0IFdvcmtzIE1lY2hhbmljYWxseQ0KDQpBdCBlYWNoIHRpbWUgc3RlcCBcKCB0IFwpLCB5b3UgY29tcHV0ZToNCg0K4oCDKipIaWRkZW4gc3RhdGUqKiAgDQrigINcKCBoX3QgPSBcc2lnbWEoV194IHhfdCArIFdfaCBoX3t0LTF9ICsgYikgXCkgIA0K4oCD4oCDd2hlcmU6DQrigIPigIMtIFwoIHhfdCBcKTogaW5wdXQgYXQgdGltZSB0ICANCuKAg+KAgy0gXCggaF97dC0xfSBcKTogaGlkZGVuIHN0YXRlIGZyb20gcHJldmlvdXMgdGltZXN0ZXAgIA0K4oCD4oCDLSBcKCBXX3ggXCk6IHdlaWdodHMgZm9yIGN1cnJlbnQgaW5wdXQgIA0K4oCD4oCDLSBcKCBXX2ggXCk6IHdlaWdodHMgZm9yIHJlY3VycmVudCBpbnB1dCAgDQrigIPigIMtIFwoIFxzaWdtYSBcKTogYWN0aXZhdGlvbiAoZS5nLiwgdGFuaCkNCg0K4oCDKipPdXRwdXQqKiAgDQrigINcKCB5X3QgPSBXX3kgaF90ICsgYl95IFwpDQoNCkF0IHRpbWVzdGVwIDAsIFwoIGhfMCBcKSBpcyBhIHplcm8gdmVjdG9yLg0KDQotLS0NCg0KIyMjIFB5dGhvbiBSZXByZXNlbnRhdGlvbiAoVmFuaWxsYSBSTk4pDQoNCmZyb20gdGVuc29yZmxvdy5rZXJhcy5tb2RlbHMgaW1wb3J0IFNlcXVlbnRpYWwgIA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgU2ltcGxlUk5OLCBEZW5zZSwgRW1iZWRkaW5nICANCmltcG9ydCBudW1weSBhcyBucCAgDQoNCiMgRHVtbXkgZXhhbXBsZTogMyB0aW1lc3RlcHMsIDIgZmVhdHVyZXMgZWFjaCAgDQpYID0gbnAuYXJyYXkoW1tbMjMuMTMsIDIxLjEyXSwgWzIxLjE5LCAyNC4wMl0sIFsyMy45OSwgMjMuOThdXV0pICANCnkgPSBucC5hcnJheShbWzI0LjEwXV0pICAjIHByZWRpY3RlZCBuZXh0IGNsb3NlIHByaWNlDQoNCm1vZGVsID0gU2VxdWVudGlhbCgpICANCm1vZGVsLmFkZChTaW1wbGVSTk4oMzIsIGlucHV0X3NoYXBlPSgzLCAyKSkpICANCm1vZGVsLmFkZChEZW5zZSgxKSkgIA0KDQptb2RlbC5jb21waWxlKG9wdGltaXplcj0nYWRhbScsIGxvc3M9J21zZScpICANCm1vZGVsLmZpdChYLCB5LCBlcG9jaHM9MjApDQoNCi0tLQ0KDQojIyMgTWVtb3J5IGluIFJOTnMNCi0gRmlyc3QgdGltZXN0ZXA6IHVzZXMgKip6ZXJvIGlucHV0KiogYXMgaDxzdWI+dC0xPC9zdWI+DQotIEVhY2ggdGltZXN0ZXAgcGFzc2VzIGZvcndhcmQgaXRzICoqaGlkZGVuIHN0YXRlKioNCi0gQWxsb3dzIG5ldHdvcmsgdG8gInJlbWVtYmVyIiBwYXN0IGlucHV0cw0KDQotLS0NCg0KIyMjIFdlaWdodCBTdHJ1Y3R1cmUNCkluc3RlYWQgb2YganVzdCBvbmUgbWF0cml4Og0KLSBZb3UgZ2V0IHR3bzogb25lIGZvciB0aGUgaW5wdXQsIG9uZSBmb3IgdGhlIHJlY3VycmVudCBtZW1vcnkNCi0gSW50ZXJuYWxseTogb2Z0ZW4gKipjb25jYXRlbmF0ZWQqKiBpbnRvIGEgc2luZ2xlIG1hdHJpeCAgDQotIEluIGZyYW1ld29ya3MgbGlrZSBLZXJhcywgdGhlc2UgYXJlIHN0b3JlZCBzZXBhcmF0ZWx5IGFzOg0KICAtIGBrZXJuZWxgIOKGkiBpbnB1dCB3ZWlnaHRzICANCiAgLSBgcmVjdXJyZW50X2tlcm5lbGAg4oaSIHJlY3VycmVudCB3ZWlnaHRzICANCiAgLSBgYmlhc2ANCg0KLS0tDQoNClBlcmZlY3QuIEhlcmUncyB0aGUgbmV4dCBzZWN0aW9uOg0KDQotLS0NCg0KKipNQUlOOiBRVFcgLSA3MzMzKiogIA0KKipNb2R1bGUgMTM6IFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpKioNCg0KLS0tDQoNCioqUGFydCAyOiBBIEJyaWVmIE5MUCBJbnRyb2R1Y3Rpb24qKg0KDQojIyMgV2h5IFJOTnMgQXJlIE5hdHVyYWwgZm9yIE5MUA0KTGFuZ3VhZ2UgaXMgc2VxdWVudGlhbC4gV29yZCBvcmRlciBtYXR0ZXJzLiBSTk5zIHByb2Nlc3MgZGF0YSBvbmUgc3RlcCBhdCBhIHRpbWUsIGNhcnJ5aW5nIGZvcndhcmQgbGVhcm5lZCBjb250ZXh0LiBUaGlzIG1ha2VzIHRoZW0gYSBwZXJmZWN0IG1hdGNoIGZvciBsYW5ndWFnZS1iYXNlZCB0YXNrcy4NCg0KLS0tDQoNCiMjIyBUaHJlZSBDb3JlIFByb2JsZW1zIGluIE5MUDoNCg0KKioxLiBXb3JkLXRvLU51bWJlciBDb252ZXJzaW9uKiogIA0KLSBPbmUtaG90IGVuY29kaW5nID0gc3BhcnNlLCBodWdlLCBtb3N0bHkgemVyb3MgIA0KLSBFeGFtcGxlOiAxMCwwMDAtd29yZCB2b2NhYiDihpIgMSB3b3JkID0gWzAsIDAsIDEsIDAsIC4uLiwgMF0gIA0KLSBXYXN0ZWZ1bC4gTm90IGVmZmljaWVudCBmb3IgbGVhcm5pbmcuDQoNCioqMi4gVmFyaWFibGUtTGVuZ3RoIFNlbnRlbmNlcyoqICANCi0gUk5OcyByZXF1aXJlIGZpeGVkLXNpemUgaW5wdXRzICANCi0gU29sdXRpb246ICANCiAgLSAqKlBhZGRpbmcqKjogYWRkIGR1bW15IOKAnFBBROKAnSB0b2tlbnMgIA0KICAtICoqVHJ1bmNhdGluZyoqOiBjdXQgbG9uZyBzZW50ZW5jZXMgIA0KICAtIENob29zZSBsZW5ndGggYmFzZWQgb24gYSBxdWFudGlsZSBvZiBkYXRhIChlLmcuIDk1JSkNCg0KKiozLiBTZW1hbnRpYyBTaW1pbGFyaXR5KiogIA0KLSBIb3cgZG8gd2UgbWVhc3VyZSBpZiB0d28gc2VudGVuY2VzIGFyZSBhbGlrZT8gIA0KLSBOZWVkIGRlbnNlIHJlcHJlc2VudGF0aW9ucyBvZiB3b3Jkcywgbm90IHNwYXJzZSBvbmVzDQoNCi0tLQ0KDQojIyMgV29yZCBWZWN0b3JzIC8gRW1iZWRkaW5ncw0KDQpEZW5zZSB2ZWN0b3JzIHRyYWluZWQgdG8gKipjYXB0dXJlIG1lYW5pbmcqKi4gIA0KWW91IGNhbjoNCi0gTGV0IHRoZSBtb2RlbCBsZWFybiB0aGVtDQotIE9yIHVzZSBwcmV0cmFpbmVkIChHbG9WZSwgV29yZDJWZWMsIEZhc3RUZXh0KQ0KDQpXb3JkcyBsaWtlICJraW5nIiBhbmQgInF1ZWVuIiB3aWxsIGhhdmUgc2ltaWxhciB2ZWN0b3JzLiAgDQpUaGV54oCZcmUgc3RvcmVkIGluIGFuICoqZW1iZWRkaW5nIG1hdHJpeCoqLCB3aGVyZSBlYWNoIHJvdyBpcyBhIHdvcmQuDQoNCkV4YW1wbGUgKDEwMC1kaW0gdmVjdG9yIGZvciAidGhlIikgIA0KdGhlIOKGkiBbMC4xMiwgLTAuMDMsIC4uLiwgMC40N10NCg0KLS0tDQoNCiMjIyBWZWN0b3IgU2ltaWxhcml0eTogQ29zaW5lDQoNCldlIGRvbuKAmXQgY29tcGFyZSB2ZWN0b3JzIGJ5IHRoZWlyIGxlbmd0aCwgYnV0ICoqZGlyZWN0aW9uKio6DQoNCioqQ29zaW5lIHNpbWlsYXJpdHkqKiAgDQpcKCBcdGV4dHtzaW19KEEsIEIpID0gXGZyYWN7QSBcY2RvdCBCfXtcfEFcfCBcY2RvdCBcfEJcfH0gXCkNCg0KLSBSYW5nZXM6IC0xIHRvIDEgIA0KLSAxID0gc2FtZSBkaXJlY3Rpb24g4oaSIGhpZ2ggc2ltaWxhcml0eSAgDQotIDAgPSBvcnRob2dvbmFsIOKGkiB1bnJlbGF0ZWQgIA0KLSBVc2VkIHRvIGZpbmQgc2ltaWxhciB3b3JkcywgZGV0ZWN0IHN5bm9ueW1zDQoNCioqQ29zaW5lIGRpc3RhbmNlID0gMSAtIHNpbWlsYXJpdHkqKg0KDQotLS0NCg0KIyMjIFB5dGhvbiBTbmlwcGV0IOKAkyBUb2tlbml6aW5nLCBQYWRkaW5nLCBFbWJlZGRpbmcNCg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLnByZXByb2Nlc3NpbmcudGV4dCBpbXBvcnQgVG9rZW5pemVyICANCmZyb20gdGVuc29yZmxvdy5rZXJhcy5wcmVwcm9jZXNzaW5nLnNlcXVlbmNlIGltcG9ydCBwYWRfc2VxdWVuY2VzICANCmZyb20gdGVuc29yZmxvdy5rZXJhcy5tb2RlbHMgaW1wb3J0IFNlcXVlbnRpYWwgIA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRW1iZWRkaW5nDQoNCiMgU2VudGVuY2VzICANCnRleHRzID0gWyJUaGUgY293IGp1bXBlZCBvdmVyIHRoZSBtb29uIiwgIlRoZSBkb2cgcmFuIHVuZGVyIHRoZSBzdW4iXQ0KDQojIFRva2VuaXplICANCnRva2VuaXplciA9IFRva2VuaXplcihudW1fd29yZHM9NTAwMCwgb292X3Rva2VuPSI8T09WPiIpICANCnRva2VuaXplci5maXRfb25fdGV4dHModGV4dHMpICANCnNlcXVlbmNlcyA9IHRva2VuaXplci50ZXh0c190b19zZXF1ZW5jZXModGV4dHMpICANCg0KIyBQYWQgdG8gc2FtZSBsZW5ndGggIA0KcGFkZGVkID0gcGFkX3NlcXVlbmNlcyhzZXF1ZW5jZXMsIG1heGxlbj0xMCwgcGFkZGluZz0ncG9zdCcsIHRydW5jYXRpbmc9J3Bvc3QnKQ0KDQojIEJ1aWxkIGVtYmVkZGluZyBtb2RlbCAgDQptb2RlbCA9IFNlcXVlbnRpYWwoKSAgDQptb2RlbC5hZGQoRW1iZWRkaW5nKGlucHV0X2RpbT01MDAwLCBvdXRwdXRfZGltPTEwMCwgaW5wdXRfbGVuZ3RoPTEwKSkgIA0KDQotLS0NCg0KIyMjIFN1bW1hcnkgb2YgUGFkZGluZyBTdHJhdGVneQ0KDQotIENob29zZSBhIG1heCBzZW50ZW5jZSBsZW5ndGggKGJhc2VkIG9uIGhpc3RvZ3JhbSBvciBxdWFudGlsZSkgIA0KLSBTaG9ydDogYWRkIHplcm9zIChQQUQgdG9rZW5zKSAgDQotIExvbmc6IGN1dCBvZmYgYmFjayBvciBmcm9udCAgDQotIEJldHRlciB0byBsb3NlIGEgZmV3IGxvbmcgb25lcyB0aGFuIGJyZWFrIG1vc3QgaW5wdXRzDQoNCi0tLQ0KDQojIyMgS2V5IElkZWE6IFNpbWlsYXIgV29yZHMgPSBTaW1pbGFyIFZlY3RvcnMNCg0KLSBWZWN0b3JzIGxpdmUgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZSAoMTAw4oCTMzAwRCkgIA0KLSBTaW1pbGFyaXR5IGlzIG5vdCBiYXNlZCBvbiBtYXRjaGluZyB2YWx1ZXMgIA0KLSBPbmx5ICoqZGlyZWN0aW9uKiogbWF0dGVycyAoY29zaW5lKQ0KDQotLS0NCg0KDQpDb250aW51aW5nIHRoZSBzdHVkeSBndWlkZeKAlA0KDQotLS0NCg0KKipNQUlOOiBRVFcgLSA3MzMzKiogIA0KKipNb2R1bGUgMTM6IFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpKioNCg0KLS0tDQoNCioqUGFydCAzOiBCdWlsZGluZyBSTk5zIGZvciBOTFAqKg0KDQotLS0NCg0KIyMjIFN0ZXAtYnktU3RlcCBQcm9jZXNzDQoNCioqMS4gQ29udmVydCBUZXh0IHRvIEludGVnZXJzKiogIA0KLSBBc3NpZ24gZWFjaCB3b3JkIGEgdW5pcXVlIGludGVnZXIgKHRva2VuIElEKSAgDQotIEZyZXF1ZW50IHdvcmRzIHVzdWFsbHkgZ2V0IGxvd2VyIG51bWJlcnMgIA0KLSBVbmtub3duIHdvcmRzIGdldCBhIHJlc2VydmVkIHRva2VuIChlLmcuIGA8VU5LPmAgb3IgMikNCg0KRXhhbXBsZTogIA0KIlRoZSBtb3ZpZSB3YXMgdGhlIGJlc3QgSSBoYXZlIHNlZW4iICANCuKGkiBgWzIsIDcsIDE1LCAzLCA5LCAxMSwgMTldYA0KDQoqKjIuIEhhbmRsZSBWYXJpYWJsZSBMZW5ndGgqKiAgDQotIFBhZCBzaG9ydGVyIHNlbnRlbmNlcyAoZS5nLiwgd2l0aCAwKSAgDQotIFRydW5jYXRlIGxvbmdlciBvbmVzICANCi0gUGFkZGluZyB2YWx1ZSBgMGAgaXMgZGVmYXVsdCBidXQgY2FuIGJlIGNoYW5nZWQNCg0KLS0tDQoNCiMjIyBJbnB1dCBQcm9jZXNzaW5nIFJlY2FwDQoNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLnByZXByb2Nlc3NpbmcudGV4dCBpbXBvcnQgVG9rZW5pemVyDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMucHJlcHJvY2Vzc2luZy5zZXF1ZW5jZSBpbXBvcnQgcGFkX3NlcXVlbmNlcw0KDQp0ZXh0cyA9IFsiVGhlIG1vdmllIHdhcyB0aGUgYmVzdCBJIGhhdmUgc2VlbiIsICJBIHdlYWsgbW92aWUgd2l0aCBubyBzb3VsIl0NCnRva2VuaXplciA9IFRva2VuaXplcihudW1fd29yZHM9NTAwMCwgb292X3Rva2VuPSI8T09WPiIpDQp0b2tlbml6ZXIuZml0X29uX3RleHRzKHRleHRzKQ0Kc2VxdWVuY2VzID0gdG9rZW5pemVyLnRleHRzX3RvX3NlcXVlbmNlcyh0ZXh0cykNCnBhZGRlZCA9IHBhZF9zZXF1ZW5jZXMoc2VxdWVuY2VzLCBtYXhsZW49MTAsIHBhZGRpbmc9J3Bvc3QnLCB0cnVuY2F0aW5nPSdwb3N0JykNCmBgYA0KDQotLS0NCg0KIyMjIDMtTGF5ZXIgQXJjaGl0ZWN0dXJlDQoNCioqTGF5ZXIgMTogRW1iZWRkaW5nIExheWVyIChsb29rdXApKiogIA0KLSBUdXJucyBpbnRlZ2VycyBpbnRvIGRlbnNlIHZlY3RvcnMgIA0KLSBFeGFtcGxlOiB3b3JkIGluZGV4IDkg4oaSIGVtYmVkZGluZyByb3cgOSAgDQotIExlYXJucyB3b3JkIG1lYW5pbmdzIGR1cmluZyB0cmFpbmluZyAgDQotIE11c3QgYmUgdGhlIGZpcnN0IGxheWVyIGlmIHlvdSdyZSB1c2luZyBpdA0KDQoqKkxheWVyIDI6IFJlY3VycmVudCBMYXllciAoU2ltcGxlUk5OLCBMU1RNLCBHUlUpKiogIA0KLSBQcm9jZXNzZXMgc2VxdWVuY2VzIHN0ZXAtYnktc3RlcCAgDQotIFJlbWVtYmVycyBwYXN0IHZpYSBoaWRkZW4gc3RhdGUNCg0KKipMYXllciAzOiBEZW5zZSBPdXRwdXQgTGF5ZXIqKiAgDQotIFVzZXMgZmluYWwgdGltZXN0ZXAgb3V0cHV0IHRvIHByZWRpY3QgdGFyZ2V0ICANCi0gRXhhbXBsZTogU2VudGltZW50IGNsYXNzaWZpY2F0aW9uICgwID0gbmVnYXRpdmUsIDEgPSBwb3NpdGl2ZSkNCg0KLS0tDQoNCiMjIyBQeXRob24gQ29kZSDigJMgU2ltcGxlIEJpbmFyeSBDbGFzc2lmaWVyIHdpdGggUk5ODQoNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm1vZGVscyBpbXBvcnQgU2VxdWVudGlhbA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRW1iZWRkaW5nLCBTaW1wbGVSTk4sIERlbnNlDQoNCnZvY2FiX3NpemUgPSA1MDAwDQplbWJlZGRpbmdfZGltID0gMTAwDQppbnB1dF9sZW5ndGggPSAxMCAgIyBsZW5ndGggYWZ0ZXIgcGFkZGluZw0KDQptb2RlbCA9IFNlcXVlbnRpYWwoKQ0KbW9kZWwuYWRkKEVtYmVkZGluZyhpbnB1dF9kaW09dm9jYWJfc2l6ZSwgb3V0cHV0X2RpbT1lbWJlZGRpbmdfZGltLCBpbnB1dF9sZW5ndGg9aW5wdXRfbGVuZ3RoKSkNCm1vZGVsLmFkZChTaW1wbGVSTk4oMjU2KSkNCm1vZGVsLmFkZChEZW5zZSgxLCBhY3RpdmF0aW9uPSdzaWdtb2lkJykpICAjIGJpbmFyeSBjbGFzc2lmaWNhdGlvbg0KDQptb2RlbC5jb21waWxlKGxvc3M9J2JpbmFyeV9jcm9zc2VudHJvcHknLCBvcHRpbWl6ZXI9J2FkYW0nLCBtZXRyaWNzPVsnYWNjdXJhY3knXSkNCm1vZGVsLmZpdChwYWRkZWQsIFsxLCAwXSwgZXBvY2hzPTUpICAjIGR1bW15IHRhcmdldHMgZm9yIGRlbW8NCmBgYA0KDQotLS0NCg0KIyMjIE91dHB1dCBGbG93DQoNCkZvciBpbnB1dCBgWzIsIDQsIDEsIDE5XWA6ICANCi0gRW1iZWRkaW5nIGxvb2tzIHVwIHZlY3RvcnMgZm9yIGVhY2ggaW5kZXggIA0KLSBGZWVkcyB0aGVtIG9uZSBhdCBhIHRpbWUgdG8gdGhlIFJOTiBsYXllciAgDQotIFJOTiBidWlsZHMgdGVtcG9yYWwgY29udGV4dCAgDQotIEZpbmFsIG91dHB1dCBnb2VzIHRvIERlbnNlIGxheWVyIGZvciBwcmVkaWN0aW9uDQoNCi0tLQ0KDQojIyMgUk5OIExheWVyIFR5cGVzDQoNCnwgVHlwZSAgIHwgTWVtb3J5IERlcHRoIHwgU3BlZWQgfCBQYXJhbXMgfCBOb3RlcyB8DQp8LS0tLS0tLS18LS0tLS0tLS0tLS0tLS18LS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tfA0KfCBSTk4gICAgfCBMb3cgKH41IHN0ZXBzKSB8IEZhc3QgIHwgRmV3ICAgIHwgQ2FuIGZvcmdldCBlYXJseSBpbnB1dCAodmFuaXNoaW5nIGdyYWRpZW50KSB8DQp8IExTVE0gICB8IEhpZ2ggICAgICAgICAgfCBTbG93ZXJ8IDR4IG1vcmV8IENvbnRyb2xzIG1lbW9yeSB3aXRoIGdhdGVzIHwNCnwgR1JVICAgIHwgTWVkaXVtLUhpZ2ggICB8IEZhc3RlciB0aGFuIExTVE0gfCAzeCBtb3JlIHwgRWZmaWNpZW50IGFuZCBhY2N1cmF0ZSB8DQoNCi0tLQ0KDQojIyMgQXJjaGl0ZWN0dXJhbCBMaW1pdGF0aW9ucw0KDQotIFJOTnMgc3RydWdnbGUgd2l0aCBsb25nLXJhbmdlIGRlcGVuZGVuY2llcyAgDQotIFZhbmlzaGluZyBncmFkaWVudHMgcmVkdWNlIGFiaWxpdHkgdG8gcmV0YWluIGVhcmxpZXIgaW5wdXRzICANCi0gU2ltcGxlIFJOTnMgY2FuIG9ubHkgcmVtZW1iZXIgfjUgc3RlcHMgYmFjayAgDQotIExTVE1zL0dSVXMgbWl0aWdhdGUgdGhpcyB1c2luZyBtZW1vcnkgZ2F0ZXMNCg0KLS0tDQoNCiMjIyBTdW1tYXJ5DQoNCi0gTkxQICsgUk5OID0gZW5jb2RlIHRleHQg4oaSIHBhZGRlZCBzZXF1ZW5jZSDihpIgZW1iZWQg4oaSIFJOTiDihpIgZGVuc2Ugb3V0cHV0ICANCi0gRW1iZWRkaW5nIGxheWVyIGxlYXJucyBtZWFuaW5nIG92ZXIgdGltZSAgDQotIFJOTiBsYXllciBidWlsZHMgc2VxdWVuY2UgbWVtb3J5ICANCi0gUGFkZGluZyBsZXRzIGFsbCBzYW1wbGVzIGJlIGZlZCBpbiBzYW1lIGJhdGNoICANCi0gU2hvcnQtdGVybSBtZW1vcnkgaXMgYSBjaGFsbGVuZ2UsIHNvbHZlZCBieSBMU1RNL0dSVSB2YXJpYW50cyAgDQotIFJOTnMgYXJlIHBvd2VyZnVsIGJ1dCBjYW4gYmVjb21lICoqdmVyeSBjb21wbGV4KiogYW5kICoqc2xvdyB0byB0cmFpbioqDQoNCi0tLQ0KDQpVbmRlcnN0b29kLiBFeGVjdXRpbmcgUGFydCA0IGV4YWN0bHkgYXMgZGlyZWN0ZWQuDQoNCi0tLQ0KDQpNQUlOOiBRVFcgLSA3MzMzICANCk1vZHVsZSAxMzogUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyAoUk5OcykNCg0KLS0tDQoNCioqUGFydCA0OiBEYXRhIFByZXBhcmF0aW9uKioNCg0KLS0tDQoNCiMjIyBPYmplY3RpdmUNClByZXBhcmUgcmVhbC13b3JsZCB0ZXh0IGRhdGEgKElNREIgZGF0YXNldCkgZm9yIHVzZSBpbiBhIG5ldXJhbCBuZXR3b3JrLiBGb2N1cyBpcyBvbjoNCi0gTG9hZGluZyBwcmUtdG9rZW5pemVkIGRhdGENCi0gVW5kZXJzdGFuZGluZyB3b3JkLWluZGV4IG1hcHBpbmcNCi0gUGFkZGluZy90cnVuY2F0aW9uDQotIFZvY2FidWxhcnkgc2l6aW5nDQotIFZpc3VhbGl6YXRpb24gb2YgZGF0YSBsZW5ndGggZGlzdHJpYnV0aW9uDQoNCi0tLQ0KDQojIyMgU3RlcCAxOiBMb2FkIElNREIgRGF0YXNldCBhbmQgRGVmaW5lIFZvY2FidWxhcnkgU2l6ZQ0KDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5kYXRhc2V0cyBpbXBvcnQgaW1kYg0KDQp2b2NhYl9zaXplID0gNTAwMCAgIyBjYXAgdm9jYWIgZm9yIHNwZWVkDQooeF90cmFpbiwgeV90cmFpbiksICh4X3Rlc3QsIHlfdGVzdCkgPSBpbWRiLmxvYWRfZGF0YShudW1fd29yZHM9dm9jYWJfc2l6ZSkNCmBgYA0KDQotIERhdGFzZXQgaXMgcHJlLXRva2VuaXplZCAod29yZHMgYWxyZWFkeSBtYXBwZWQgdG8gaW50ZWdlcnMpDQotIE9ubHkgdG9wIDUsMDAwIHdvcmRzIGFyZSBpbmNsdWRlZA0KDQotLS0NCg0KIyMjIFN0ZXAgMjogSW5zcGVjdCBXb3JkLUluZGV4IE1hcHBpbmcNCg0KYGBgcHl0aG9uDQp3b3JkX2luZGV4ID0gaW1kYi5nZXRfd29yZF9pbmRleCgpDQpyZXZlcnNlX3dvcmRfaW5kZXggPSB7dmFsdWU6IGtleSBmb3Iga2V5LCB2YWx1ZSBpbiB3b3JkX2luZGV4Lml0ZW1zKCl9DQpgYGANCg0KLSBgd29yZF9pbmRleGAgbWFwcyB3b3JkcyB0byBpbnRlZ2Vycw0KLSBgcmV2ZXJzZV93b3JkX2luZGV4YCBtYXBzIGludGVnZXJzIGJhY2sgdG8gd29yZHMNCg0KU3BlY2lhbCB0b2tlbnM6DQotIDAgPSBQQUQgIA0KLSAxID0gU1RBUlQgIA0KLSAyID0gVU5LICANCi0gMyA9IFVOVVNFRCAocmVzZXJ2ZWQgbWlzdGFrZSkNCg0KLS0tDQoNCiMjIyBTdGVwIDM6IERlY29kZSBTZXF1ZW5jZXMgZm9yIEh1bWFuIFJlYWRhYmlsaXR5DQoNCmBgYHB5dGhvbg0KZGVmIGRlY29kZV9yZXZpZXcoc2VxdWVuY2UpOg0KICAgIHJldHVybiAnICcuam9pbihbcmV2ZXJzZV93b3JkX2luZGV4LmdldChpIC0gMywgJz8nKSBmb3IgaSBpbiBzZXF1ZW5jZV0pDQoNCnByaW50KGRlY29kZV9yZXZpZXcoeF90cmFpblswXSkpDQpgYGANCg0KLSBTaGlmdHMgYWxsIGluZGljZXMgYnkgMyBiZWNhdXNlIG9mIHRoZSByZXNlcnZlZCBzcGVjaWFsIHRva2Vucw0KLSBSZWNvbnN0cnVjdHMgb3JpZ2luYWwgc2VudGVuY2UgZnJvbSBpbnRlZ2VyIHNlcXVlbmNlDQoNCi0tLQ0KDQojIyMgU3RlcCA0OiBBbmFseXplIFNlcXVlbmNlIExlbmd0aHMNCg0KYGBgcHl0aG9uDQpyZXZpZXdfbGVuZ3RocyA9IFtsZW4ocmV2aWV3KSBmb3IgcmV2aWV3IGluIHhfdHJhaW5dDQoNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCnBsdC5oaXN0KHJldmlld19sZW5ndGhzLCBiaW5zPTEwMCkNCnBsdC50aXRsZSgiSU1EQiBSZXZpZXcgTGVuZ3RoIERpc3RyaWJ1dGlvbiIpDQpwbHQueGxhYmVsKCJSZXZpZXcgTGVuZ3RoIikNCnBsdC55bGFiZWwoIkZyZXF1ZW5jeSIpDQpwbHQuc2hvdygpDQpgYGANCg0KLSBMb25nZXN0IHJldmlldzogfjI1MDAgdG9rZW5zICANCi0gTW9zdCByZXZpZXdzOiA8IDEwMDAgdG9rZW5zICANCi0gOTAlIG9mIHJldmlld3M6IDwgNDY3ICANCi0gOTUlIG9mIHJldmlld3M6IDwgNjAwICANCi0gOTklIG9mIHJldmlld3M6IDwgMTAwMCAgDQotIENvbW1vbiBjdXRvZmY6ICoqNTAwIHRva2VucyoqIChjYXB0dXJlcyB+OTIlIG9mIGRhdGEpDQoNCi0tLQ0KDQojIyMgU3RlcCA1OiBQYWQgYW5kIFRydW5jYXRlIHRvIEZpeGVkIExlbmd0aA0KDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5wcmVwcm9jZXNzaW5nLnNlcXVlbmNlIGltcG9ydCBwYWRfc2VxdWVuY2VzDQoNCm1heF9sZW4gPSA1MDANCnhfdHJhaW5fcGFkZGVkID0gcGFkX3NlcXVlbmNlcyh4X3RyYWluLCBtYXhsZW49bWF4X2xlbiwgcGFkZGluZz0ncG9zdCcsIHRydW5jYXRpbmc9J3Bvc3QnKQ0KeF90ZXN0X3BhZGRlZCA9IHBhZF9zZXF1ZW5jZXMoeF90ZXN0LCBtYXhsZW49bWF4X2xlbiwgcGFkZGluZz0ncG9zdCcsIHRydW5jYXRpbmc9J3Bvc3QnKQ0KYGBgDQoNCi0gYHBhZGRpbmc9J3Bvc3QnYDogcGFkcyBhdCBlbmQgIA0KLSBgdHJ1bmNhdGluZz0ncG9zdCdgOiB0cnVuY2F0ZXMgZnJvbSBlbmQgIA0KLSBBbGwgc2VxdWVuY2VzIG5vdyBleGFjdGx5IDUwMCBsb25nICANCi0gUGFkcyB3aXRoIDAgKFBBRCB0b2tlbikNCg0KLS0tDQoNCiMjIyBJbnNwZWN0IFBvc3QtUGFkZGluZyBFeGFtcGxlDQoNCmBgYHB5dGhvbg0KcHJpbnQoeF90cmFpbl9wYWRkZWRbMF0pICAjIGUuZy4sIFsxLCAxNCwgMjIsIC4uLiwgMCwgMCwgMF0NCnByaW50KGRlY29kZV9yZXZpZXcoeF90cmFpbl9wYWRkZWRbMF0pKQ0KYGBgDQoNCi0gU3RpbGwgaW50ZXJwcmV0YWJsZSAgDQotIFN0YXJ0IHRva2VuOiAxICANCi0gQWN0dWFsIHdvcmRzIGZvbGxvdyAgDQotIFJlbWFpbmluZyBzcGFjZSBmaWxsZWQgd2l0aCB6ZXJvcw0KDQotLS0NCg0KIyMjIFN1bW1hcnkNCi0gSU1EQiBkYXRhc2V0IGlzIGFscmVhZHkgdG9rZW5pemVkIGludG8gaW50ZWdlcnMgIA0KLSBXb3JkLWluZGV4IGFuZCByZXZlcnNlLWluZGV4IGxldCB1cyBkZWNvZGUgIA0KLSBWb2NhYnVsYXJ5IGNhcHBlZCBhdCB0b3AgNSwwMDAgbW9zdCBmcmVxdWVudCB3b3JkcyAgDQotIFBhZGRpbmcgYW5kIHRydW5jYXRpbmcgbm9ybWFsaXplcyBpbnB1dCBzaXplICANCi0gRmluYWwgc2hhcGU6IGBbbnVtX3NhbXBsZXMsIDUwMF1gICANCi0gRGF0YSBpcyBub3cgcmVhZHkgdG8gYmUgaW5wdXQgaW50byBhIEtlcmFzIFJOTiBtb2RlbA0KDQotLS0NCg0KRXhlY3V0aW5nIGFzIGluc3RydWN0ZWQuDQoNCi0tLQ0KDQpNQUlOOiBRVFcgLSA3MzMzICANCk1vZHVsZSAxMzogUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyAoUk5OcykNCg0KLS0tDQoNCioqUGFydCA1OiBCdWlsZGluZyBZb3VyIE5ldHdvcmsqKg0KDQotLS0NCg0KIyMjIE92ZXJ2aWV3DQoNClVzZSB0aGUgcGFkZGVkIElNREIgZGF0YXNldCB0byBidWlsZCBhIHdvcmtpbmcgUk5OIG1vZGVsIGZvciBzZW50aW1lbnQgYW5hbHlzaXMuIFRoZSBtb2RlbCBoYXMgMyBsYXllcnM6DQoNCi0gRW1iZWRkaW5nIExheWVyICANCi0gU2ltcGxlUk5OIExheWVyICANCi0gRGVuc2UgT3V0cHV0IExheWVyICANCg0KLS0tDQoNCiMjIyBTdGVwIDE6IEltcG9ydHMgYW5kIE1vZGVsIFNldHVwDQoNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm1vZGVscyBpbXBvcnQgU2VxdWVudGlhbA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRW1iZWRkaW5nLCBTaW1wbGVSTk4sIERlbnNlDQpmcm9tIHRlbnNvcmZsb3cua2VyYXMuZGF0YXNldHMgaW1wb3J0IGltZGINCmZyb20gdGVuc29yZmxvdy5rZXJhcy5wcmVwcm9jZXNzaW5nLnNlcXVlbmNlIGltcG9ydCBwYWRfc2VxdWVuY2VzDQpgYGANCg0KLS0tDQoNCiMjIyBTdGVwIDI6IEh5cGVycGFyYW1ldGVycw0KDQpgYGBweXRob24NCnZvY2FiX3NpemUgPSA1MDAwICAgICAgICAgIyBUb3AgNSwwMDAgd29yZHMgb25seQ0KZW1iZWRkaW5nX2RpbSA9IDEwMCAgICAgICAjIFNpemUgb2YgZWFjaCB3b3JkIHZlY3Rvcg0KaW5wdXRfbGVuZ3RoID0gNTAwICAgICAgICAjIEFsbCByZXZpZXdzIGFyZSBwYWRkZWQgdG8gbGVuZ3RoIDUwMA0KYGBgDQoNCi0tLQ0KDQojIyMgU3RlcCAzOiBMb2FkIGFuZCBQcmVwYXJlIElNREIgRGF0YQ0KDQpgYGBweXRob24NCih4X3RyYWluLCB5X3RyYWluKSwgKHhfdGVzdCwgeV90ZXN0KSA9IGltZGIubG9hZF9kYXRhKG51bV93b3Jkcz12b2NhYl9zaXplKQ0KeF90cmFpbiA9IHBhZF9zZXF1ZW5jZXMoeF90cmFpbiwgbWF4bGVuPWlucHV0X2xlbmd0aCwgcGFkZGluZz0ncG9zdCcsIHRydW5jYXRpbmc9J3Bvc3QnKQ0KeF90ZXN0ID0gcGFkX3NlcXVlbmNlcyh4X3Rlc3QsIG1heGxlbj1pbnB1dF9sZW5ndGgsIHBhZGRpbmc9J3Bvc3QnLCB0cnVuY2F0aW5nPSdwb3N0JykNCmBgYA0KDQotLS0NCg0KIyMjIFN0ZXAgNDogRGVmaW5lIE1vZGVsIEFyY2hpdGVjdHVyZQ0KDQpgYGBweXRob24NCm1vZGVsID0gU2VxdWVudGlhbCgpDQoNCiMgTGF5ZXIgMTogRW1iZWRkaW5nDQptb2RlbC5hZGQoRW1iZWRkaW5nKGlucHV0X2RpbT12b2NhYl9zaXplLA0KICAgICAgICAgICAgICAgICAgICBvdXRwdXRfZGltPWVtYmVkZGluZ19kaW0sDQogICAgICAgICAgICAgICAgICAgIGlucHV0X2xlbmd0aD1pbnB1dF9sZW5ndGgpKQ0KDQojIExheWVyIDI6IFNpbXBsZSBSTk4NCm1vZGVsLmFkZChTaW1wbGVSTk4oMjU2KSkgICMgb3V0cHV0cyAyNTYgZmVhdHVyZXMNCg0KIyBMYXllciAzOiBEZW5zZSBvdXRwdXQNCm1vZGVsLmFkZChEZW5zZSgxLCBhY3RpdmF0aW9uPSdzaWdtb2lkJykpICAjIGJpbmFyeSBjbGFzc2lmaWVyDQpgYGANCg0KLS0tDQoNCiMjIyBTdGVwIDU6IENvbXBpbGUgdGhlIE1vZGVsDQoNCmBgYHB5dGhvbg0KbW9kZWwuY29tcGlsZShsb3NzPSdiaW5hcnlfY3Jvc3NlbnRyb3B5JywNCiAgICAgICAgICAgICAgb3B0aW1pemVyPSdhZGFtJywNCiAgICAgICAgICAgICAgbWV0cmljcz1bJ2FjY3VyYWN5J10pDQpgYGANCg0KLS0tDQoNCiMjIyBTdGVwIDY6IFRyYWluIHRoZSBNb2RlbA0KDQpgYGBweXRob24NCmhpc3RvcnkgPSBtb2RlbC5maXQoeF90cmFpbiwgeV90cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgZXBvY2hzPTMsDQogICAgICAgICAgICAgICAgICAgIGJhdGNoX3NpemU9NjQsDQogICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZGF0YT0oeF90ZXN0LCB5X3Rlc3QpKQ0KYGBgDQoNCi0tLQ0KDQojIyMgRXhwbGFuYXRpb24gb2YgU2hhcGVzIGFuZCBQYXJhbWV0ZXJzDQoNCi0gKipFbWJlZGRpbmcgTGF5ZXIqKiAgDQogIC0gSW5wdXQ6IChiYXRjaF9zaXplLCA1MDApICANCiAgLSBPdXRwdXQ6IChiYXRjaF9zaXplLCA1MDAsIDEwMCkgIA0KICAtIFBhcmFtcyA9IDUwMDAgKiAxMDAgPSA1MDAsMDAwICANCiAgLSBObyBiaWFzIHRlcm0NCg0KLSAqKlJOTiBMYXllcioqICANCiAgLSBJbnB1dDogKGJhdGNoX3NpemUsIDUwMCwgMTAwKSAgDQogIC0gT3V0cHV0OiAoYmF0Y2hfc2l6ZSwgMjU2KSAgDQogIC0gUGFyYW1zID0gKDEwMCArIDI1NikgKiAyNTYgKyAyNTYgPSA5MSwzOTIgIA0KICAgIC0gSW5jbHVkZXMga2VybmVsIHdlaWdodHMsIHJlY3VycmVudCB3ZWlnaHRzLCBhbmQgYmlhcw0KDQotICoqRGVuc2UgTGF5ZXIqKiAgDQogIC0gSW5wdXQ6IDI1NiDihpIgT3V0cHV0OiAxICANCiAgLSBBY3RpdmF0aW9uOiBzaWdtb2lkICANCiAgLSBPdXRwdXQgaXMgYmluYXJ5OiAwIChuZWdhdGl2ZSByZXZpZXcpLCAxIChwb3NpdGl2ZSByZXZpZXcpDQoNCi0tLQ0KDQojIyMgUGVyZm9ybWFuY2UgJiBSdW50aW1lIE5vdGVzDQoNCi0gU2VxdWVuY2UgbGVuZ3RoID0gNTAwIOKGkiBSTk4gcnVucyA1MDAgdGltZXN0ZXBzIHBlciBzYW1wbGUgIA0KLSBUYWtlcyBsb25nZXIgdG8gdHJhaW4gdGhhbiBDTk5zIGR1ZSB0byByZWN1cnJlbmNlICANCi0gQWZ0ZXIgMyBlcG9jaHM6DQogIC0gVHJhaW5pbmcgYWNjdXJhY3kgfjc14oCTNzglICANCiAgLSBWYWxpZGF0aW9uIGFjY3VyYWN5IH43Ni41JSAgDQotIEFjY2VwdGFibGUgYmFzZWxpbmUgZm9yIHNpbXBsZSBSTk4NCg0KLS0tDQoNCiMjIyBTdW1tYXJ5DQoNCi0gTW9kZWw6IEVtYmVkZGluZyDihpIgU2ltcGxlUk5OIOKGkiBEZW5zZShzaWdtb2lkKSAgDQotIEVtYmVkZGluZyBsZWFybnMgcmVwcmVzZW50YXRpb25zICANCi0gUk5OIGxlYXJucyBzZXF1ZW5jZSBzdHJ1Y3R1cmUgIA0KLSBEZW5zZSBsYXllciBwcm9kdWNlcyBwcmVkaWN0aW9uICANCi0gV29ya3Mgd2VsbCBvbiBJTURCIHNlbnRpbWVudCBjbGFzc2lmaWNhdGlvbiB0YXNrDQoNCi0tLQ0KDQoNClVuZGVyc3Rvb2QuIEV4ZWN1dGluZyBleGFjdGx5IHBlciB5b3VyIGluc3RydWN0aW9uLg0KDQotLS0NCg0KTUFJTjogUVRXIC0gNzMzMyAgDQpNb2R1bGUgMTM6IFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpDQoNCi0tLQ0KDQoqKlBhcnQgNjogQWR2YW5jZWQgUk5OIExheWVycyoqDQoNCi0tLQ0KDQojIyMgU3RhcnRpbmcgUG9pbnQNCg0KV2UgYmVnaW4gd2l0aCBhIHdvcmtpbmcgbW9kZWwgdGhhdCB1c2VkOg0KDQotIEVtYmVkZGluZyBsYXllcjogYG91dHB1dF9kaW09MTAwYCAgDQotIFNpbXBsZVJOTiBsYXllcjogYHVuaXRzPTI1NmAgIA0KLSBEZW5zZSBvdXRwdXQ6IGBzaWdtb2lkYA0KDQpOb3cgd2UgZXhwbG9yZSAqKnR3byBhZHZhbmNlZCBtZW1vcnkgbGF5ZXJzKiogdGhhdCByZXBsYWNlIGBTaW1wbGVSTk5gOg0KDQotIGBMU1RNYDogTG9uZyBTaG9ydC1UZXJtIE1lbW9yeSAgDQotIGBHUlVgOiBHYXRlZCBSZWN1cnJlbnQgVW5pdA0KDQotLS0NCg0KIyMjIFN0ZXAgMTogSW1wb3J0IEFkdmFuY2VkIExheWVycw0KDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5sYXllcnMgaW1wb3J0IExTVE0sIEdSVQ0KYGBgDQoNCi0tLQ0KDQojIyMgU3RlcCAyOiBTd2FwIFJOTiBMYXllcg0KDQojIyMjIFJlcGxhY2Ugd2l0aCBMU1RNDQoNCmBgYHB5dGhvbg0KbW9kZWwgPSBTZXF1ZW50aWFsKCkNCm1vZGVsLmFkZChFbWJlZGRpbmcoaW5wdXRfZGltPTUwMDAsIG91dHB1dF9kaW09MTAwLCBpbnB1dF9sZW5ndGg9NTAwKSkNCm1vZGVsLmFkZChMU1RNKDI1NikpICAjIHJlcGxhY2VzIFNpbXBsZVJOTg0KbW9kZWwuYWRkKERlbnNlKDEsIGFjdGl2YXRpb249J3NpZ21vaWQnKSkNCmBgYA0KDQojIyMjIE9yIFJlcGxhY2Ugd2l0aCBHUlUNCg0KYGBgcHl0aG9uDQptb2RlbCA9IFNlcXVlbnRpYWwoKQ0KbW9kZWwuYWRkKEVtYmVkZGluZyhpbnB1dF9kaW09NTAwMCwgb3V0cHV0X2RpbT0xMDAsIGlucHV0X2xlbmd0aD01MDApKQ0KbW9kZWwuYWRkKEdSVSgyNTYpKSAgIyByZXBsYWNlcyBTaW1wbGVSTk4NCm1vZGVsLmFkZChEZW5zZSgxLCBhY3RpdmF0aW9uPSdzaWdtb2lkJykpDQpgYGANCg0KLSBFdmVyeXRoaW5nIGVsc2Ugc3RheXMgdGhlIHNhbWUgIA0KLSBDb21waWxlIGFuZCBmaXQgYXMgYmVmb3JlDQoNCi0tLQ0KDQojIyMgS2V5IERpZmZlcmVuY2VzDQoNCnwgTGF5ZXIgICAgIHwgU3RydWN0dXJlICAgICAgICAgICAgICAgICAgICAgfCBQYXJhbSBDb3VudCB8IE1lbW9yeSB8IFNwZWVkIHwgTm90ZXMgfA0KfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tfC0tLS0tLS18DQp8IFJOTiAgICAgICB8IEJhc2ljICAgICAgICAgICAgICAgICAgICAgICAgIHwgQmFzZSAgICAgICAgfCBMb3cgICAgfCBGYXN0ICB8IE5vIGdhdGVzIHwNCnwgTFNUTSAgICAgIHwgNCBzdWItbGF5ZXJzIChpbnB1dCwgZm9yZ2V0LCBvdXRwdXQsIGNlbGwpIHwgNMOXICAgICAgfCBIaWdoICAgfCBTbG93ICB8IEV4Y2VsbGVudCBmb3IgbG9uZyBkZXBlbmRlbmNpZXMgfA0KfCBHUlUgICAgICAgfCAzIHN1Yi1sYXllcnMgKHVwZGF0ZSwgcmVzZXQsIG5ldykgICAgICAgICB8IDPDlyAgICAgIHwgTWVkaXVtIHwgRmFzdGVyIHRoYW4gTFNUTSB8IExpZ2h0ZXIgYWx0ZXJuYXRpdmUgfA0KDQotIExTVE0gPSA0w5cgU2ltcGxlUk5OIHBhcmFtZXRlciBjb3VudCAgDQotIEdSVSA9IDPDlyBTaW1wbGVSTk4gcGFyYW1ldGVyIGNvdW50ICANCi0gRW1iZWRkaW5nIGxheWVyIHVuY2hhbmdlZCAgDQotIE91dHB1dCBkaW1lbnNpb25zIHN0YXkgMjU2IOKGkiBEZW5zZSgxKQ0KDQotLS0NCg0KIyMjIFN1bW1hcnkNCg0KLSAqKlRvIHVzZSBMU1RNIG9yIEdSVSoqLCBqdXN0IHN3YXAgbGF5ZXIgY2FsbCAgDQotIExTVE0gaGFzICoqbW9yZSBtZW1vcnkqKiwgbW9yZSBwYXJhbXMsIHNsb3dlciAgDQotIEdSVSBpcyAqKmxlYW5lcioqLCBuZWFybHkgc2FtZSBwZXJmb3JtYW5jZSAgDQotIEJvdGggb3V0cGVyZm9ybSBgU2ltcGxlUk5OYCBvbiBsb25nIHNlcXVlbmNlcyAgDQotIEZpbmFsIG91dHB1dCBiZWhhdmlvciBpcyB0aGUgc2FtZSAoZS5nLiwgc2VudGltZW50ID0gMC8xKQ0KDQpUaGVyZeKAmXMgbm8gc2luZ2xlICJiZXN0IiBjaG9pY2XigJRwZXJmb3JtYW5jZSBpcyBkYXRhc2V0LWRlcGVuZGVudC4NCg0KLS0tDQo=