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
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
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
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)
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
RNN |
Basic |
Base |
Low |
Fast |
No gates |
LSTM |
4 sub-layers (input, forget, output, cell) |
4× |
High |
Slow |
Excellent for long dependencies |
GRU |
3 sub-layers (update, reset, new) |
3× |
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=