1 Background

The problem of solving “the correct ranking” is everywhere.

One inspiring example is Google’s Gmail Smart-Reply feature where the user can choose from 3 machine suggested quick replies upon receiving an email. In Henderson et al. (2017) the model is a ranking neural network but solving for a classification problem under the hood.

In one of its simplest format we can visualize the network model as the following graph:1

This is a text-to-text ranking network accepting two features, both are natural language represented by bag-of-word embeddings. Such model can also be used in conversational application that is widely adopted in lots of online platform already.

We can also easily extend the model by adding more feature dimensions (and concatenate them before feeding to the hidden layer). Input features can take ANY form. If it is an image we can use an Image model to do the encoding instead.

1.1 Neural Embedding

The idea of “everything can be embedded” by neural networks is being explicitly exploited in Wu et al. (2018). In their work a framework called StarSpace is proposed to solve a variety of problems including but not limited to:

  • Classification
  • Multi-label classification
  • Context-aware recommender system
  • Information retrieval
  • Unsupervised word and sentence embeddings

Their framework has been open sourced and will be our main topic in this notebook.

1.2 Other Ranking Models

For a technical discussions about other notable approaches in the literature, readers may refer to this notebook: Introduction to Learning-to-Rank

2 Methodology Walkthrough

We will assume readers are already familiar with the foundation of learning-to-rank. If not, feel free to visit the above-mentioned notebook first before proceeding.

2.1 Solving Ranking as Classification

Let’s go back to the Gmail Smart Reply problem first. Given the network architecture we just layout, how do we train the model? What is the objective function in our learning task?

In such application our data is prepared in a pairwise manner. For example, question-answer, email content-reply, user-item, …, etc. One simple yet proved to work example is to train the model with a softmax loss by batch. That is, we treat each batch of paired examples as a multi-classification problem to predict each pair’s own class.

In training time, a set of pseudo class labels are created for each paired example, and the model need to learn for each base feature (or the LHS in starspace terminology) what is the best rank feature (the RHS) within the batch. So for each LHS the RHS from THE OTHER paired examples will act as the negative examples for the model to learn. This idea results in remarkably simple coding and network architecture yet is powerful enough to solve real world problems.

2.2 Extension

In starspace there are two options in setting the objective function. In their experiment for large applications a hinge loss is showing better performance than a softmax loss.

The logit (similarity score between a given LHS and RHS) is either a dot-product or cosine function of their encoded vectors. In the experiment they also point out the latter is performing better in large applications.

Since we are using a classifier to solve a ranking problem, the model’s feed-forward operation won’t be usable in inference time. It will only output within-batch cross-pair logits. But what we need is to rank against a list of candidates (RHS) given a query (LHS). So in actual application we will need to use the model as a feature extractor rather than a real predictor.

We will see concrete examples to make this clear in our following working demo section.

Let’s start coding!

3 Working Demo

3.1 Data

We use the Quora Duplicated Question Pairs dataset to demonstrate the modeling process. One will need a Kaggle account to download it. Here we use the kaggle API to download the data. To install the API, run:

And to download the data, run:

The dataset contains paired (is_duplicate == 1, or positive) and also unpaired (negative) examples. Hence it fits easily to a traditional classification model as well. But in our use case we will pretend as if we only observed paired examples in our training data. This is indeed quite common for a lot of applications where explicit negative examples are not prepared in the first place. A general ranking framework will need to take care of the negative sampling process in order to optimize the desired metric.

3.1.1 Tokenization

We use sentencepiece to learn a tokenizer on the data. Instead of the original library, We will use a R wrapper(Wijffels (2020b)) for our implementation.

[1] '0.1.2'

SentencePiece is a very powerful language-agnostic tokenizer that can be trained in an unsupervised manner. For more discussions about the underlying approach readers can refer to the notebook: On Subword Units.

Now let’s quickly train a tokenizer particularly for the quora question dataset:

Give it a try:

[[1]]
 [1] "▁what"       "▁is"         "▁the"        "▁difference" "▁between"   
 [6] "▁a"          "▁turkey"     "▁and"        "▁a"          "▁chicken"   
[11] "?"          

[[2]]
 [1] "▁how"      "▁do"       "▁i"        "▁retrieve" "▁credit"   "▁card"    
 [7] "▁from"     "▁google"   "▁wallet"   "?"        
[[1]]
 [1]    5    7    4   99   66    9 2333   16    9 2341    3

[[2]]
 [1]   10   13    8 2889  786  331   44  195 3639    3

3.1.2 Pairwise Training Format

3.1.2.1 starspace

Starspace is a general embedding framework that supports multiple types of problems. Here we are going to adopt one of its specific trainMode, as documented in the following paragraph:

trainMode = 3:

Each example contains a collection of labels. At training time, two labels from the collection are randomly picked as the LHS and RHS.

Use case: learn pairwise similarity from collections of similar objects, e.g. sentence similiarity.

Here a “label” is represented by a set of discrete features, in our case it is bag-of-words for a given question. Each word is space-separated and each label (question) is tab-separated.2

Note that for English (or other space-separated languages) training data it is possible to skip the tokenization phase and directly put the raw text in training a StarSpace model. But here we choose a more general approach by explicitly tokenize the language features beforehand. Such workflow will be applicable to whatever natural languages facing us.

To prepare the required input format should we use the StarSpace C++ library directly, we can simply concatenate all the parsed tokens and write them to disk:

[1] "▁how ▁do ▁i ▁delete ▁questions ▁from ▁quora ?\t▁how ▁can ▁you ▁delete ▁a ▁question ▁that ▁you ▁asked ▁on ▁quora ?"

If we have multiple sentences (more than 2) we can just append them with tab as delimiter. Here for one example we always have exactly two sentences (questions).

3.1.2.2 ruimtehol

Instead we can use another R wrapper package ruimtehol(Wijffels (2020a)) to do the training.

[1] '0.2.3'

Rather than the original input format, ruimtehol expects a long format with 1 token per row. We can use the convenience function unnest_tokens from package tidytext(Silge and Robinson (2016)) to manipulate text data in tabular form:

Note that <U+2581> is just the special prefix used in sentencepiece to represent white space and also dummy sentence beginning, so we can distinguish between a subword and a full word.

3.2 Model Training and Prediction

3.2.1 starspace

The command line to train a baseline model could be something like:

After training, we can read back the resulting embeddings:

[1] 5035   64

One may realize that the resulting vocabulary dimension is larger than our specified vocab_size in our tokenizer. The most possible reason is because we encode the original text into word pieces instead of vocabulary id, so the unknown vocabulary (out of vocab_size) will remain. For example, here is one question that can be found in the dataset involving an unknown word “改善” (it means improving and is originated from Japanese but also used in Mandarin for the same meaning). If we parse it as tokens any unknown word remain. But if we parse it as ids all unknowns become the unknown id which by default is 0.

[1] "▁what" "▁is"   "▁ka"   "ize"   "n"     "▁("    "改善"  ")?"   
[1]    5    7  821 1141   76   61    0  201

Let’s verify this by comparing the vocabulary difference between our sentencepiece model and starspace one:

 [1] "–"            "<"            "~"            "‪"             "‬"            
 [6] "∂"            "„"            "`"            "∀ཡ"           "☉"           
[11] "—"            "⚪"            "−"            "€"            "•"           
[16] "ṭ"            "∆"            "ಮಾರುಕ"        "ೆ"             "್"            
[21] "ਸ਼ੇਰ"           "ਬੱਬਰ"          "✂"            "☺"            "阿"          
[26] "⚪⚪⚪"          "⁠⁠⁠"             "′"            "日本手話語族" "మూజువాణి"      
[31] "योग"          "वासिष्ठ"       "ệ"            "译文"         "改善"        
[36] ""            "〽"           "∧"            ""            "⏰"           

 0 49 
40 40 

As we can see, there are only two IDs revealed after parsing. 0 is unknown and 49 is just the special prefix ▁.

To workaround this issue we can pass instead the token id into the starspace model. However by doing so we will need some extra engineering effort to convert the model output into human readable form, by calling sentencepiece to decode the returned id sequence. For simplicity we will keep it as is for now.

3.2.1.1 Ranking Prediction

Let’s examine how good our baseline model is, by finding the most similar questions among all the question2 in our validation set for the first 3 question1 in the same dataset.

To use the prediction interface, we need to provide a set of basedoc as ranking candidates:

Now we run the test interface in starspace:

The results:

Example 0:
LHS:
▁how ▁can ▁i ▁find ▁all ▁my ▁old ▁gmail ▁accounts ? 
RHS: 
▁how ▁can ▁i ▁get ▁a ▁complete ▁list ▁of ▁all ▁my ▁gmail ▁accounts ? 
Predictions: 
(--) [0.983167] ▁how ▁can ▁i ▁find ▁all ▁my ▁old ▁gmail ▁accounts 
(--) [0.890135] ▁how ▁do ▁i ▁find ▁all ▁of ▁my ▁gmail ▁accounts 
(--) [0.866763] ▁how ▁can ▁you ▁find ▁all ▁of ▁your ▁gmail ▁accounts 
(++) [0.82531]  ▁how ▁can ▁i ▁get ▁a ▁complete ▁list ▁of ▁all ▁my ▁gmail ▁accounts ? 
(--) [0.824634] ▁where ▁are ▁all ▁my ▁gmail ▁accounts 
(--) [0.814087] ▁how ▁do ▁i ▁find ▁my ▁own ▁gmail ▁accounts ▁list 
(--) [0.777964] ▁how ▁do ▁i ▁find ▁my ▁list ▁of ▁gmail ▁address es 
(--) [0.774349] ▁how ▁do ▁i ▁get ▁a ▁list ▁of ▁my ▁gmail ▁accounts 
(--) [0.745509] ▁where ▁is ▁my ▁other ▁gmail ▁accounts 
(--) [0.741857] ▁how ▁do ▁i ▁get ▁all ▁the ▁list ▁registered ▁to ▁your ▁gmail ▁account 

Example 1:
LHS:
▁how ▁will ▁demonetization ▁affect ▁india ? 
RHS: 
▁how ▁is ▁demonetization ▁affect ing ▁people ▁of ▁india ? 
Predictions: 
(--) [0.975787] ▁how ▁will ▁demonetization ▁affect ▁india 
(++) [0.885226] ▁how ▁is ▁demonetization ▁affect ing ▁people ▁of ▁india ? 
(--) [0.755914] ▁how ▁reservation ▁affect ing ▁future ▁of ▁india , ▁is ▁it ▁increasing ▁brain ▁drain 
(--) [0.740456] ▁how ▁does ▁brexit ▁affect ▁india 
(--) [0.724499] ▁is ▁demonetization ▁illegal ▁in ▁india 
(--) [0.681335] ▁will ▁demonetization ▁really ▁help ▁in ▁the ▁growth ▁of ▁the ▁indian ▁economy 
(--) [0.679345] ▁what ▁will ▁be ▁the ▁effect ▁of ▁demonetization ▁on ▁indian ▁economy 
(--) [0.657395] ▁will ▁the ▁currency ▁ban ▁in ▁india ▁affect ▁the ▁economy 
(--) [0.640339] ▁how ▁can ▁modi ▁transform ▁india 
(--) [0.623425] ▁what ▁is ▁the ▁future ▁of ▁photography ▁in ▁india 

Example 2:
LHS:
▁how ▁can ▁i ▁improve ▁my ▁english ▁pronunciation ? 
RHS: 
▁what ▁are ▁some ▁ways ▁to ▁improve ▁english ? 
Predictions: 
(--) [0.932885] ▁how ▁can ▁i ▁improve ▁my ▁english ▁pronunciation 
(--) [0.896106] ▁how ▁can ▁i ▁improve ▁my ▁pronunciation ▁in ▁english 
(--) [0.885215] ▁how ▁do ▁i ▁improve ▁my ▁pronunciation ▁in ▁english 
(--) [0.857608] ▁how ▁can ▁i ▁improve ▁my ▁pronunciation ▁of ▁english ▁words 
(--) [0.844284] ▁how ▁can ▁i ▁improve ▁my ▁spoken ▁english 
(--) [0.829061] ▁how ▁can ▁i ▁continue ▁to ▁improve ▁my ▁english 
(--) [0.816511] ▁what ▁are ▁the ▁best ▁ways ▁to ▁improve ▁my ▁english ▁because ▁i ' m ▁not ▁good ▁in ▁english 
(--) [0.815]    ▁how ▁i ▁can ▁improve ▁my ▁english ▁communication 
(--) [0.80697]  ▁how ▁could ▁i ▁improve ▁my ▁english 
(--) [0.805334] ▁how ▁can ▁i ▁improve ▁my ▁english ▁language 

The model works pretty well indeed.

Though the result only consider the exact RHS sentence to be the correct answer (prefix by a (++)), in our dataset since there are usually more than 1 duplicated questions for each given question, we found that those ranked as highly similar are really OTHER duplicates in the dataset.

Of course our model didn’t directly apply to the original problem, where we need to give a binary answer on any given question pair. But we can definitely use the learned embeddings to train another downstream model for this. In such a downstream model since our learned embeddings are already context-aware (they are derived from a learning task optimizing pairwise similarity), the result should be better than a model using other pre-trained word embeddings.

3.2.2 ruimtehol

Now let’s also demonstrate how we can run starspace through ruimtehol, its R wrapper:3

Indeed this function will end up converting the long format into the StarSpace format we just prepared before, then call the starspace binary. There are quite some overheads in this process before the training job begins.4

There is one more difference other than input format preparation between the original starspace and the R wrapper ruimtehol::embed_sentencespace. The latter comes with a parameter early_stopping which will set aside additional validation set to implement early stopping, which is quite a convenient feature. By default it is set to 0.75 so even though we didn’t specify a validation set in the verbose output one can see the validation score on the random 25% of the training examples.

To bypass the pre-processing overhead, we can instead use the low-level API:

This time let’s get some sentence embeddings and calculate the similarity on our own:

One final remark: On Windows system using ruimtehol may encounter some encoding issues which make the model less effective (since the vocab becomes a little bit noisier).

Starspace also come with a Python wrapper officially but the work seems to be only partially done. We leave the exploration for the readers.

4 References

Chollet, François, and others. 2015. “Keras.” https://github.com/fchollet/keras; GitHub.

Dowle, Matt, and Arun Srinivasan. 2019. Data.table: Extension of ‘Data.frame‘. https://CRAN.R-project.org/package=data.table.

Henderson, Matthew, Rami Al-Rfou, Brian Strope, Yun-Hsuan Sung, László Lukács, Ruiqi Guo, Sanjiv Kumar, Balint Miklos, and Ray Kurzweil. 2017. “Efficient Natural Language Response Suggestion for Smart Reply.” arXiv Preprint arXiv:1705.00652.

Silge, Julia, and David Robinson. 2016. “Tidytext: Text Mining and Analysis Using Tidy Data Principles in R.” JOSS 1 (3). https://doi.org/10.21105/joss.00037.

Wijffels, Jan. 2020a. Ruimtehol: Learn Text ’Embeddings’ with ’Starspace’. https://CRAN.R-project.org/package=ruimtehol.

———. 2020b. Sentencepiece: Text Tokenization Using Byte Pair Encoding and Unigram Modelling. https://CRAN.R-project.org/package=sentencepiece.

Wu, Ledell Yu, Adam Fisch, Sumit Chopra, Keith Adams, Antoine Bordes, and Jason Weston. 2018. “Starspace: Embed All the Things!” In Thirty-Second Aaai Conference on Artificial Intelligence.


  1. We use Keras(Chollet and others (2015)) to generate the plot.

  2. In their document the term “label” is a loose term. For example in the SentenceSpace section it is mentioned that from one example of multiple collections two are randomly picked up and “one as the input and one as the label.”

  3. Note that the wrapping version of starspace in ruimtehol may not be the up-to-date version available in starspace’s original repository.

  4. Indeed our pre-processing code without the need to prepare the long-format will run considerably faster than ruimtehol, because we take the advantage of knowing there are always 2 questions in 1 document in this particular dataset.

LS0tCnRpdGxlOiAiR2VuZXJhbC1QdXJwb3NlIFJhbmtpbmcgTW9kZWxzIgpzdWJ0aXRsZTogIkFkdmVudHVyZSB3aXRoIFN0YXJTcGFjZTogQSBOZXVyYWwgRW1iZWRkaW5nIEFwcHJvYWNoIgphdXRob3I6Ci0gbmFtZTogS3lsZSBDaHVuZwogIGFmZmlsaWF0aW9uOgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlYiAlWScpYCBMYXN0IFVwZGF0ZWQgKDI2IEp1bHkgMjAyMCBGaXJzdCBVcGxvYWRlZCkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgaGlnaGxpZ2h0OiB6ZW5idXJuCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0CiAgICB0b2NfZmxvYXQ6IHllcwogICAgaW5jbHVkZXM6CiAgICAgIGluX2hlYWRlcjogL3RtcC9tZXRhX2hlYWRlci5odG1sCiAgY29kZV9kb3dubG9hZDogdHJ1ZQpiaWJsaW9ncmFwaHk6IG5ldXJhbF9yYW5raW5nLmJpYgpuby1jaXRlOiB8CiAgQGRhdGEudGFibGUKbGluay1jaXRhdGlvbnM6IHllcwphYnN0cmFjdDogfAogIFN0YXJTcGFjZSBpcyBhbiBvcGVuIHNvdXJjZSBtYWNoaW5lIGxlYXJuaW5nIGxpYnJhcnkgZGV2ZWxvcGVkIGJ5IEZhY2VCb29rIFJlc2VhcmNoLiBJbiB0aGlzIG5vdGVib29rIHdlIGJyaWVmbHkgd2Fsa3Rocm91Z2ggaXRzIHJlbWFya2FibHkgc2ltcGxlIHlldCBwb3dlcmZ1bCBpZGVhIG9uIGhvdyB0byB1c2UgbmV1cmFsIGVtYmVkZGluZ3MgdG8gc29sdmUgYSB2YXJpZXR5IG9mIGRpZmZlcmVudCBwcm9ibGVtcywgYWxsIGZpdCBpbnRvIGEgdW5pZmllZCByYW5raW5nIGZyYW1ld29yay4gQW5kIHdlIHdpbGwgdXNlIFF1b3JhIER1cGxpY2F0ZWQgUXVlc3Rpb24gUGFpcnMgZGF0YXNldCBhcyBhIHdvcmtpbmcgZGVtbyB0byBkbyBzb21lIGV4ZXJjaXNlcyB3aXRoIGJvdGggdGhlIFN0YXJTcGFjZSBsaWJyYXJ5IGFuZCBvbmUgb2YgaXRzIFIgd3JhcHBlciBjYWxsZWQgcnVpbXRlaG9sLgotLS0KCmBgYHtyIG1ldGEsIGluY2x1ZGU9RkFMU0V9Cm1ldGFfaGVhZGVyX2ZpbGUgPC0gZmlsZSgiL3RtcC9tZXRhX2hlYWRlci5odG1sIikKCiMgQWRkIG9wZW4gZ3JhcGggbWV0YS4KbWV0YSA8LSBjKAogICc8bWV0YSBuYW1lPSJhdXRob3IiIGNvbnRlbnQ9Ikt5bGUgQ2h1bmciPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzp0aXRsZSIgY29udGVudD0iR2VuZXJhbC1QdXJwb3NlIFJhbmtpbmcgTW9kZWxzIj4nLAogICc8bWV0YSBwcm9wZXJ0eT0ib2c6dHlwZSIgY29udGVudD0iYXJ0aWNsZSI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOnVybCIgY29udGVudD0iaHR0cHM6Ly9ldmVyZGFyay5naXRodWIuaW8vazkvbm90ZWJvb2tzL21sL25ldXJhbF9yYW5raW5nL25ldXJhbF9yYW5raW5nLm5iLmh0bWwiPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzppbWFnZSIgY29udGVudD0iaHR0cHM6Ly9ldmVyZGFyay5naXRodWIuaW8vazkvbm90ZWJvb2tzL21sL25ldXJhbF9yYW5raW5nL3NhbXBsZV9yYW5raW5nX25ldC5wbmciPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzpkZXNjcmlwdGlvbiIgY29udGVudD0iQSBkYXRhIHNjaWVuY2Ugbm90ZWJvb2sgYWJvdXQgc3RhcnNwYWNlOiBhIGdlbmVyYWwtcHVycG9zZSBuZXVyYWwgZW1iZWRkaW5nIG1vZGVsIGZvciByYW5raW5nIHByb2JsZW1zLiI+JwopCmNvbnRlbnRzIDwtIG1ldGEKCiMgQWRkIEdpdGh1YiBjb3JuZXIuCmdpdGh1Yl9jb3JuZXJfc3ZnIDwtICIuLi8uLi8uLi9hc3NldHMvZ2l0aHViX2Nvcm5lci5odG1sIgpnaXRodWJfY29ybmVyX2NvbmYgPC0gbGlzdChnaXRodWJfbGluaz0iaHR0cHM6Ly9naXRodWIuY29tL2V2ZXJkYXJrL2s5L3RyZWUvbWFzdGVyL25vdGVib29rcy9tbC9uZXVyYWxfcmFua2luZyIpCmNvbnRlbnRzIDwtIGMoY29udGVudHMsIHN0cmluZ3I6OnN0cl9pbnRlcnAocmVhZExpbmVzKGdpdGh1Yl9jb3JuZXJfc3ZnKSwgZ2l0aHViX2Nvcm5lcl9jb25mKSkKd3JpdGVMaW5lcyhjb250ZW50cywgbWV0YV9oZWFkZXJfZmlsZSkKCmNsb3NlKG1ldGFfaGVhZGVyX2ZpbGUpCmBgYAoKIyBCYWNrZ3JvdW5kCgpUaGUgcHJvYmxlbSBvZiBzb2x2aW5nICJ0aGUgY29ycmVjdCByYW5raW5nIiBpcyBldmVyeXdoZXJlLgoKT25lIGluc3BpcmluZyBleGFtcGxlIGlzIEdvb2dsZSdzIEdtYWlsIFNtYXJ0LVJlcGx5IGZlYXR1cmUgd2hlcmUgdGhlIHVzZXIgY2FuIGNob29zZSBmcm9tIDMgbWFjaGluZSBzdWdnZXN0ZWQgcXVpY2sgcmVwbGllcyB1cG9uIHJlY2VpdmluZyBhbiBlbWFpbC4KSW4gQGhlbmRlcnNvbjIwMTdlZmZpY2llbnQgdGhlIG1vZGVsIGlzIGEgcmFua2luZyBuZXVyYWwgbmV0d29yayBidXQgc29sdmluZyBmb3IgYSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtIHVuZGVyIHRoZSBob29kLgoKSW4gb25lIG9mIGl0cyBzaW1wbGVzdCBmb3JtYXQgd2UgY2FuIHZpc3VhbGl6ZSB0aGUgbmV0d29yayBtb2RlbCBhcyB0aGUgZm9sbG93aW5nIGdyYXBoOl5bV2UgdXNlIFtgS2VyYXNgXShodHRwczovL2tlcmFzLmlvLykoQGNob2xsZXQyMDE1a2VyYXMpIHRvIGdlbmVyYXRlIHRoZSBwbG90Ll0KCiFbXShzYW1wbGVfcmFua2luZ19uZXQucG5nKQoKVGhpcyBpcyBhIHRleHQtdG8tdGV4dCByYW5raW5nIG5ldHdvcmsgYWNjZXB0aW5nIHR3byBmZWF0dXJlcywKYm90aCBhcmUgbmF0dXJhbCBsYW5ndWFnZSByZXByZXNlbnRlZCBieSBiYWctb2Ytd29yZCBlbWJlZGRpbmdzLgpTdWNoIG1vZGVsIGNhbiBhbHNvIGJlIHVzZWQgaW4gY29udmVyc2F0aW9uYWwgYXBwbGljYXRpb24gdGhhdCBpcyB3aWRlbHkgYWRvcHRlZCBpbiBsb3RzIG9mIG9ubGluZSBwbGF0Zm9ybSBhbHJlYWR5LgoKV2UgY2FuIGFsc28gZWFzaWx5IGV4dGVuZCB0aGUgbW9kZWwgYnkgYWRkaW5nIG1vcmUgZmVhdHVyZSBkaW1lbnNpb25zIChhbmQgY29uY2F0ZW5hdGUgdGhlbSBiZWZvcmUgZmVlZGluZyB0byB0aGUgaGlkZGVuIGxheWVyKS4KSW5wdXQgZmVhdHVyZXMgY2FuIHRha2UgQU5ZIGZvcm0uCklmIGl0IGlzIGFuIGltYWdlIHdlIGNhbiB1c2UgYW4gSW1hZ2UgbW9kZWwgdG8gZG8gdGhlIGVuY29kaW5nIGluc3RlYWQuCgojIyBOZXVyYWwgRW1iZWRkaW5nCgpUaGUgaWRlYSBvZiAiZXZlcnl0aGluZyBjYW4gYmUgZW1iZWRkZWQiIGJ5IG5ldXJhbCBuZXR3b3JrcyBpcyBiZWluZyBleHBsaWNpdGx5IGV4cGxvaXRlZCBpbiBAd3UyMDE4c3RhcnNwYWNlLgpJbiB0aGVpciB3b3JrIGEgZnJhbWV3b3JrIGNhbGxlZCBgU3RhclNwYWNlYCBpcyBwcm9wb3NlZCB0byBzb2x2ZSBhIHZhcmlldHkgb2YgcHJvYmxlbXMgaW5jbHVkaW5nIGJ1dCBub3QgbGltaXRlZCB0bzoKCisgQ2xhc3NpZmljYXRpb24KKyBNdWx0aS1sYWJlbCBjbGFzc2lmaWNhdGlvbgorIENvbnRleHQtYXdhcmUgcmVjb21tZW5kZXIgc3lzdGVtCisgSW5mb3JtYXRpb24gcmV0cmlldmFsCisgVW5zdXBlcnZpc2VkIHdvcmQgYW5kIHNlbnRlbmNlIGVtYmVkZGluZ3MKClRoZWlyIGZyYW1ld29yayBoYXMgYmVlbiBvcGVuIHNvdXJjZWQgYW5kIHdpbGwgYmUgb3VyIG1haW4gdG9waWMgaW4gdGhpcyBub3RlYm9vay4KCiMjIE90aGVyIFJhbmtpbmcgTW9kZWxzCgpGb3IgYSB0ZWNobmljYWwgZGlzY3Vzc2lvbnMgYWJvdXQgb3RoZXIgbm90YWJsZSBhcHByb2FjaGVzIGluIHRoZSBsaXRlcmF0dXJlLApyZWFkZXJzIG1heSByZWZlciB0byB0aGlzIG5vdGVib29rOiBbSW50cm9kdWN0aW9uIHRvIExlYXJuaW5nLXRvLVJhbmtdKGh0dHBzOi8vZXZlcmRhcmsuZ2l0aHViLmlvL2s5L25vdGVib29rcy9tbC9sZWFybmluZ190b19yYW5rL2xlYXJuaW5nX3RvX3JhbmsuaHRtbCkKCiMgTWV0aG9kb2xvZ3kgV2Fsa3Rocm91Z2gKCldlIHdpbGwgYXNzdW1lIHJlYWRlcnMgYXJlIGFscmVhZHkgZmFtaWxpYXIgd2l0aCB0aGUgZm91bmRhdGlvbiBvZiBsZWFybmluZy10by1yYW5rLgpJZiBub3QsIGZlZWwgZnJlZSB0byB2aXNpdCB0aGUgYWJvdmUtbWVudGlvbmVkIG5vdGVib29rIGZpcnN0IGJlZm9yZSBwcm9jZWVkaW5nLgoKIyMgU29sdmluZyBSYW5raW5nIGFzIENsYXNzaWZpY2F0aW9uCgpMZXQncyBnbyBiYWNrIHRvIHRoZSBHbWFpbCBTbWFydCBSZXBseSBwcm9ibGVtIGZpcnN0LgpHaXZlbiB0aGUgbmV0d29yayBhcmNoaXRlY3R1cmUgd2UganVzdCBsYXlvdXQsCmhvdyBkbyB3ZSB0cmFpbiB0aGUgbW9kZWw/CldoYXQgaXMgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbiBpbiBvdXIgbGVhcm5pbmcgdGFzaz8KCkluIHN1Y2ggYXBwbGljYXRpb24gb3VyIGRhdGEgaXMgcHJlcGFyZWQgaW4gYSBwYWlyd2lzZSBtYW5uZXIuCkZvciBleGFtcGxlLCBxdWVzdGlvbi1hbnN3ZXIsIGVtYWlsIGNvbnRlbnQtcmVwbHksIHVzZXItaXRlbSwgLi4uLCBldGMuCk9uZSBzaW1wbGUgeWV0IHByb3ZlZCB0byB3b3JrIGV4YW1wbGUgaXMgdG8gdHJhaW4gdGhlIG1vZGVsIHdpdGggYSBzb2Z0bWF4IGxvc3MgYnkgYmF0Y2guClRoYXQgaXMsCndlIHRyZWF0IGVhY2ggYmF0Y2ggb2YgcGFpcmVkIGV4YW1wbGVzIGFzIGEgbXVsdGktY2xhc3NpZmljYXRpb24gcHJvYmxlbSB0byBwcmVkaWN0IGVhY2ggcGFpcidzIG93biBjbGFzcy4KCkluIHRyYWluaW5nIHRpbWUsCmEgc2V0IG9mIHBzZXVkbyBjbGFzcyBsYWJlbHMgYXJlIGNyZWF0ZWQgZm9yIGVhY2ggcGFpcmVkIGV4YW1wbGUsCmFuZCB0aGUgbW9kZWwgbmVlZCB0byBsZWFybiBmb3IgZWFjaCBiYXNlIGZlYXR1cmUgKG9yIHRoZSBMSFMgaW4gYHN0YXJzcGFjZWAgdGVybWlub2xvZ3kpIHdoYXQgaXMgdGhlIGJlc3QgcmFuayBmZWF0dXJlICh0aGUgUkhTKSB3aXRoaW4gdGhlIGJhdGNoLgpTbyBmb3IgZWFjaCBMSFMgdGhlIFJIUyBmcm9tIFRIRSBPVEhFUiBwYWlyZWQgZXhhbXBsZXMgd2lsbCBhY3QgYXMgdGhlIG5lZ2F0aXZlIGV4YW1wbGVzIGZvciB0aGUgbW9kZWwgdG8gbGVhcm4uClRoaXMgaWRlYSByZXN1bHRzIGluIHJlbWFya2FibHkgc2ltcGxlIGNvZGluZyBhbmQgbmV0d29yayBhcmNoaXRlY3R1cmUgeWV0IGlzIHBvd2VyZnVsIGVub3VnaCB0byBzb2x2ZSByZWFsIHdvcmxkIHByb2JsZW1zLgoKIyMgRXh0ZW5zaW9uCgpJbiBgc3RhcnNwYWNlYCB0aGVyZSBhcmUgdHdvIG9wdGlvbnMgaW4gc2V0dGluZyB0aGUgb2JqZWN0aXZlIGZ1bmN0aW9uLgpJbiB0aGVpciBleHBlcmltZW50IGZvciBsYXJnZSBhcHBsaWNhdGlvbnMgYSBbaGluZ2UgbG9zc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSGluZ2VfbG9zcykgaXMgc2hvd2luZyBiZXR0ZXIgcGVyZm9ybWFuY2UgdGhhbiBhIHNvZnRtYXggbG9zcy4KClRoZSBsb2dpdCAoc2ltaWxhcml0eSBzY29yZSBiZXR3ZWVuIGEgZ2l2ZW4gTEhTIGFuZCBSSFMpIGlzIGVpdGhlciBhIGRvdC1wcm9kdWN0IG9yIGNvc2luZSBmdW5jdGlvbiBvZiB0aGVpciBlbmNvZGVkIHZlY3RvcnMuCkluIHRoZSBleHBlcmltZW50IHRoZXkgYWxzbyBwb2ludCBvdXQgdGhlIGxhdHRlciBpcyBwZXJmb3JtaW5nIGJldHRlciBpbiBsYXJnZSBhcHBsaWNhdGlvbnMuCgpTaW5jZSB3ZSBhcmUgdXNpbmcgYSBjbGFzc2lmaWVyIHRvIHNvbHZlIGEgcmFua2luZyBwcm9ibGVtLAp0aGUgbW9kZWwncyBmZWVkLWZvcndhcmQgb3BlcmF0aW9uIHdvbid0IGJlIHVzYWJsZSBpbiBpbmZlcmVuY2UgdGltZS4KSXQgd2lsbCBvbmx5IG91dHB1dCB3aXRoaW4tYmF0Y2ggY3Jvc3MtcGFpciBsb2dpdHMuCkJ1dCB3aGF0IHdlIG5lZWQgaXMgdG8gcmFuayBhZ2FpbnN0IGEgbGlzdCBvZiBjYW5kaWRhdGVzIChSSFMpIGdpdmVuIGEgcXVlcnkgKExIUykuClNvIGluIGFjdHVhbCBhcHBsaWNhdGlvbiB3ZSB3aWxsIG5lZWQgdG8gdXNlIHRoZSBtb2RlbCBhcyBhIGZlYXR1cmUgZXh0cmFjdG9yIHJhdGhlciB0aGFuIGEgcmVhbCBwcmVkaWN0b3IuCgpXZSB3aWxsIHNlZSBjb25jcmV0ZSBleGFtcGxlcyB0byBtYWtlIHRoaXMgY2xlYXIgaW4gb3VyIGZvbGxvd2luZyB3b3JraW5nIGRlbW8gc2VjdGlvbi4KCkxldCdzIHN0YXJ0IGNvZGluZyEKCiMgV29ya2luZyBEZW1vCgojIyBEYXRhCgpXZSB1c2UgdGhlIFtRdW9yYSBEdXBsaWNhdGVkIFF1ZXN0aW9uIFBhaXJzXShodHRwczovL3d3dy5rYWdnbGUuY29tL3F1b3JhL3F1ZXN0aW9uLXBhaXJzLWRhdGFzZXQpIGRhdGFzZXQgdG8gZGVtb25zdHJhdGUgdGhlIG1vZGVsaW5nIHByb2Nlc3MuCk9uZSB3aWxsIG5lZWQgYSBLYWdnbGUgYWNjb3VudCB0byBkb3dubG9hZCBpdC4KSGVyZSB3ZSB1c2UgdGhlIGBrYWdnbGVgIEFQSSB0byBkb3dubG9hZCB0aGUgZGF0YS4KVG8gaW5zdGFsbCB0aGUgQVBJLCBydW46CgpgYGBzaApwaXAgaW5zdGFsbCBrYWdnbGUKYGBgCgpBbmQgdG8gZG93bmxvYWQgdGhlIGRhdGEsIHJ1bjoKCmBgYHNoCmthZ2dsZSBkYXRhc2V0cyBkb3dubG9hZCAtZCBxdW9yYS9xdWVzdGlvbi1wYWlycy1kYXRhc2V0IC1wIGRhdGEKYGBgCgpgYGB7ciBsb2FkX2RhdGEsIHJlc3VsdHM9J2hpZGUnfQpsaWJyYXJ5KGRhdGEudGFibGUpCgpzZXQuc2VlZCg3NzcpCmRhdGEgPC0gZnJlYWQoInVuemlwIC1wIGRhdGEvcXVlc3Rpb24tcGFpcnMtZGF0YXNldC56aXAiKQpgYGAKCgpgYGB7ciBwcmludF9kYXRhfQpoZWFkKGRhdGEpCmRhdGFbLCAuTiwgYnk9Lihpc19kdXBsaWNhdGUpXSAgIyBMYWJlbCBkaXN0cmlidXRpb24uCmBgYAoKVGhlIGRhdGFzZXQgY29udGFpbnMgcGFpcmVkIChgaXNfZHVwbGljYXRlID09IDFgLCBvciBwb3NpdGl2ZSkgYW5kIGFsc28gdW5wYWlyZWQgKG5lZ2F0aXZlKSBleGFtcGxlcy4KSGVuY2UgaXQgZml0cyBlYXNpbHkgdG8gYSB0cmFkaXRpb25hbCBjbGFzc2lmaWNhdGlvbiBtb2RlbCBhcyB3ZWxsLgpCdXQgaW4gb3VyIHVzZSBjYXNlIHdlIHdpbGwgcHJldGVuZCBhcyBpZiB3ZSBvbmx5IG9ic2VydmVkIHBhaXJlZCBleGFtcGxlcyBpbiBvdXIgdHJhaW5pbmcgZGF0YS4KVGhpcyBpcyBpbmRlZWQgcXVpdGUgY29tbW9uIGZvciBhIGxvdCBvZiBhcHBsaWNhdGlvbnMgd2hlcmUgZXhwbGljaXQgbmVnYXRpdmUgZXhhbXBsZXMgYXJlIG5vdCBwcmVwYXJlZCBpbiB0aGUgZmlyc3QgcGxhY2UuCkEgZ2VuZXJhbCByYW5raW5nIGZyYW1ld29yayB3aWxsIG5lZWQgdG8gdGFrZSBjYXJlIG9mIHRoZSBuZWdhdGl2ZSBzYW1wbGluZyBwcm9jZXNzIGluIG9yZGVyIHRvIG9wdGltaXplIHRoZSBkZXNpcmVkIG1ldHJpYy4KCmBgYHtyIHByZXBfZGF0YX0KIyBDb25zaWRlciBsb3dlci1jYXNlZCBjaGFyYWN0ZXJzIG9ubHksIGZvciBzaW1wbGljaXR5LgpkYXRhWywgcXVlc3Rpb24xOj10b2xvd2VyKHF1ZXN0aW9uMSldCmRhdGFbLCBxdWVzdGlvbjI6PXRvbG93ZXIocXVlc3Rpb24yKV0KCiMgVHJhaW4tdmFsaWQgc3BsaXQuCmlzX3ZhbGlkIDwtIHNhbXBsZShyb3VuZChucm93KGRhdGEpICogLjEpKQpkYXRhX3RyYWluIDwtIGRhdGFbIWlzX3ZhbGlkXSAKZGF0YV92YWxpZCA8LSBkYXRhW2lzX3ZhbGlkXQoKIyBXcml0ZSBvdXQgY29ycHVzIHRvIHRyYWluIGEgc2VudGVuY2VwaWVjZSB0b2tlbml6ZXIgbGF0dGVyLgppZiAoICFmaWxlLmV4aXN0cygiZGF0YS9zZW50ZW5jZXMudHh0IikgKSB7CiAgd3JpdGVMaW5lcyhjKGRhdGFfdHJhaW4kcXVlc3Rpb24xLCBkYXRhX3RyYWluJHF1ZXN0aW9uMiksIGNvbj0iZGF0YS9zZW50ZW5jZXMudHh0IikKfQoKIyBGb3IgdHJhaW5pbmcsIHdlIHVzZSBvbmx5IHBhaXJlZCBleGFtcGxlcy4KZGF0YV90cmFpbiA8LSBkYXRhX3RyYWluW2lzX2R1cGxpY2F0ZSA9PSAxXQpgYGAKCiMjIyBUb2tlbml6YXRpb24KCldlIHVzZSBbYHNlbnRlbmNlcGllY2VgXShodHRwczovL2dpdGh1Yi5jb20vZ29vZ2xlL3NlbnRlbmNlcGllY2UpIHRvIGxlYXJuIGEgdG9rZW5pemVyIG9uIHRoZSBkYXRhLgpJbnN0ZWFkIG9mIHRoZSBvcmlnaW5hbCBsaWJyYXJ5LApXZSB3aWxsIHVzZSBhIFtSIHdyYXBwZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9ibm9zYWMvc2VudGVuY2VwaWVjZSkoQHNwbSkgZm9yIG91ciBpbXBsZW1lbnRhdGlvbi4KCmBgYHtyIGltcG9ydF9zcG19CmxpYnJhcnkoc2VudGVuY2VwaWVjZSkKcGFja2FnZVZlcnNpb24oInNlbnRlbmNlcGllY2UiKQpgYGAKClNlbnRlbmNlUGllY2UgaXMgYSB2ZXJ5IHBvd2VyZnVsIGxhbmd1YWdlLWFnbm9zdGljIHRva2VuaXplciB0aGF0IGNhbiBiZSB0cmFpbmVkIGluIGFuIHVuc3VwZXJ2aXNlZCBtYW5uZXIuCkZvciBtb3JlIGRpc2N1c3Npb25zIGFib3V0IHRoZSB1bmRlcmx5aW5nIGFwcHJvYWNoIHJlYWRlcnMgY2FuIHJlZmVyIHRvIHRoZSBub3RlYm9vazogW09uIFN1YndvcmQgVW5pdHNdKGh0dHBzOi8vZXZlcmRhcmsuZ2l0aHViLmlvL2s5L25vdGVib29rcy9tbC9uYXR1cmFsX2xhbmd1YWdlX3VuZGVyc3RhbmRpbmcvc3Vid29yZF91bml0cy9zdWJ3b3JkX3VuaXRzLm5iLmh0bWwpLgoKTm93IGxldCdzIHF1aWNrbHkgdHJhaW4gYSB0b2tlbml6ZXIgcGFydGljdWxhcmx5IGZvciB0aGUgcXVvcmEgcXVlc3Rpb24gZGF0YXNldDoKCmBgYHtyIHRyYWluX3NwbX0KZGlyLmNyZWF0ZSgibW9kZWwiLCBzaG93V2FybmluZ3M9RkFMU0UpCmlmICggIWZpbGUuZXhpc3RzKCJtb2RlbC9zcC5tb2RlbCIpICkgewogICMgTm90ZSB0aGF0IGRhdGEgcGF0aCBtdXN0IGJlIHJlbGF0aXZlIHRvIG1vZGVsX2Rpci4KICBzZW50ZW5jZXBpZWNlKCIuLi9kYXRhL3NlbnRlbmNlcy50eHQiLCB0eXBlPSJ1bmlncmFtIiwgdm9jYWJfc2l6ZT01MDAwLAogICAgICAgICAgICAgICAgbW9kZWxfcHJlZml4PSJzcCIsIG1vZGVsX2Rpcj0ibW9kZWwiKQp9CnNwbSA8LSBzZW50ZW5jZXBpZWNlX2xvYWRfbW9kZWwoIm1vZGVsL3NwLm1vZGVsIikKYGBgCgpHaXZlIGl0IGEgdHJ5OgoKYGBge3IgZGVtb19zcG19CnByaW50KHNlbnRlbmNlcGllY2VfZW5jb2RlKHNwbSwgeD1kYXRhX3ZhbGlkJHF1ZXN0aW9uMVsyOjNdKSkgICMgVG9rZW5pemUgYXMgd29yZHBpZWNlLgpzZW50ZW5jZXBpZWNlX2VuY29kZShzcG0sIHg9ZGF0YV92YWxpZCRxdWVzdGlvbjFbMjozXSwgdHlwZT0iaWRzIikgICMgVG9rZW5pemUgYXMgdG9rZW4gaWQuCmBgYAojIyMgUGFpcndpc2UgVHJhaW5pbmcgRm9ybWF0CgojIyMjIGBzdGFyc3BhY2VgCgpgU3RhcnNwYWNlYCBpcyBhIGdlbmVyYWwgZW1iZWRkaW5nIGZyYW1ld29yayB0aGF0IHN1cHBvcnRzIG11bHRpcGxlIHR5cGVzIG9mIHByb2JsZW1zLgpIZXJlIHdlIGFyZSBnb2luZyB0byBhZG9wdCBvbmUgb2YgaXRzIHNwZWNpZmljIGB0cmFpbk1vZGVgLAphcyBkb2N1bWVudGVkIGluIHRoZSBmb2xsb3dpbmcgcGFyYWdyYXBoOgoKPnRyYWluTW9kZSA9IDM6Cj4KPkVhY2ggZXhhbXBsZSBjb250YWlucyBhIGNvbGxlY3Rpb24gb2YgbGFiZWxzLiBBdCB0cmFpbmluZyB0aW1lLCB0d28gbGFiZWxzIGZyb20gdGhlIGNvbGxlY3Rpb24gYXJlIHJhbmRvbWx5IHBpY2tlZCBhcyB0aGUgTEhTIGFuZCBSSFMuCj4KPlVzZSBjYXNlOiBsZWFybiBwYWlyd2lzZSBzaW1pbGFyaXR5IGZyb20gY29sbGVjdGlvbnMgb2Ygc2ltaWxhciBvYmplY3RzLCBlLmcuIHNlbnRlbmNlIHNpbWlsaWFyaXR5LgoKSGVyZSBhICJsYWJlbCIgaXMgcmVwcmVzZW50ZWQgYnkgYSBzZXQgb2YgZGlzY3JldGUgZmVhdHVyZXMsCmluIG91ciBjYXNlIGl0IGlzIGJhZy1vZi13b3JkcyBmb3IgYSBnaXZlbiBxdWVzdGlvbi4KRWFjaCB3b3JkIGlzIHNwYWNlLXNlcGFyYXRlZCBhbmQgZWFjaCBsYWJlbCAocXVlc3Rpb24pIGlzIHRhYi1zZXBhcmF0ZWQuXltJbiB0aGVpciBkb2N1bWVudCB0aGUgdGVybSAibGFiZWwiIGlzIGEgbG9vc2UgdGVybS4gRm9yIGV4YW1wbGUgaW4gdGhlIFtTZW50ZW5jZVNwYWNlIHNlY3Rpb25dKGh0dHBzOi8vZ2l0aHViLmNvbS9mYWNlYm9va3Jlc2VhcmNoL1N0YXJTcGFjZSNzZW50ZW5jZXNwYWNlLWxlYXJuaW5nLXNlbnRlbmNlLWVtYmVkZGluZ3MpIGl0IGlzIG1lbnRpb25lZCB0aGF0IGZyb20gb25lIGV4YW1wbGUgb2YgbXVsdGlwbGUgY29sbGVjdGlvbnMgdHdvIGFyZSByYW5kb21seSBwaWNrZWQgdXAgYW5kICJvbmUgYXMgdGhlIGlucHV0IGFuZCBvbmUgYXMgdGhlIGxhYmVsLiJdCgpOb3RlIHRoYXQgZm9yIEVuZ2xpc2ggKG9yIG90aGVyIHNwYWNlLXNlcGFyYXRlZCBsYW5ndWFnZXMpIHRyYWluaW5nIGRhdGEgaXQgaXMgcG9zc2libGUgdG8gc2tpcCB0aGUgdG9rZW5pemF0aW9uIHBoYXNlIGFuZCBkaXJlY3RseSBwdXQgdGhlIHJhdyB0ZXh0IGluIHRyYWluaW5nIGEgU3RhclNwYWNlIG1vZGVsLgpCdXQgaGVyZSB3ZSBjaG9vc2UgYSBtb3JlIGdlbmVyYWwgYXBwcm9hY2ggYnkgZXhwbGljaXRseSB0b2tlbml6ZSB0aGUgbGFuZ3VhZ2UgZmVhdHVyZXMgYmVmb3JlaGFuZC4KU3VjaCB3b3JrZmxvdyB3aWxsIGJlIGFwcGxpY2FibGUgdG8gd2hhdGV2ZXIgbmF0dXJhbCBsYW5ndWFnZXMgZmFjaW5nIHVzLgoKVG8gcHJlcGFyZSB0aGUgcmVxdWlyZWQgaW5wdXQgZm9ybWF0IHNob3VsZCB3ZSB1c2UgdGhlIGBTdGFyU3BhY2VgIEMrKyBsaWJyYXJ5IGRpcmVjdGx5LAp3ZSBjYW4gc2ltcGx5IGNvbmNhdGVuYXRlIGFsbCB0aGUgcGFyc2VkIHRva2VucyBhbmQgd3JpdGUgdGhlbSB0byBkaXNrOgoKYGBge3IgZm9ybWF0X2lucHV0fQp0b19maWxlIDwtIGZ1bmN0aW9uKERULCBvdXRmaWxlKSB7CiAgIyBVc2UgYSBiaW5hcnkgZmlsZSBjb25uZWN0aW9uIHRvIG1ha2Ugc3VyZSBcciBpcyBub3Qgd3JpdHRlbiBvdXQgb24gV2luZG93cy4KICAjIFRoYXQgaXMsIHdlIHN0cmljdGx5IHJlcXVpcmUgdW5peCBzdHlsZSBFT0wuCiAgIyBUaGlzIGlzIGVzc2VudGlhbCBhcyBzdGFyc3BhY2UgbW9kZWwgc2VlbXMgdG8gYmUgY29uZnVzZWQgYnkgXHIgY2hhcmFjdGVyCiAgIyBhbmQgaGVuY2UgdGhlIHJlc3VsdGluZyBsZWFybmVkIHZvY2FidWxhcnkgd2lsbCBiZSBjb250YW1pbmF0ZWQuCiAgZiA8LSBmaWxlKG91dGZpbGUsIG9wZW49IndiIikKICBvbi5leGl0KGNsb3NlKGYpKQogIHExIDwtIHNlbnRlbmNlcGllY2VfZW5jb2RlKHNwbSwgeD1EVCRxdWVzdGlvbjEpCiAgcTIgPC0gc2VudGVuY2VwaWVjZV9lbmNvZGUoc3BtLCB4PURUJHF1ZXN0aW9uMikKICBxMSA8LSBzYXBwbHkocTEsIHBhc3RlLCBjb2xsYXBzZT0iICIpCiAgcTIgPC0gc2FwcGx5KHEyLCBwYXN0ZSwgY29sbGFwc2U9IiAiKQogIHNlbnRfc3BhY2UgPC0gcGFzdGUocTEsIHEyLCBzZXA9Ilx0IikKICB3cml0ZUxpbmVzKHNlbnRfc3BhY2UsIGNvbj1mLCBzZXA9IlxuIiwgdXNlQnl0ZXM9VFJVRSkKfQoKaWYgKCAhZmlsZS5leGlzdHMoImRhdGEvc3NfdHJhaW4udHh0IikgKSB7CiAgdG9fZmlsZShkYXRhX3RyYWluLCAiZGF0YS9zc190cmFpbi50eHQiKQp9CgojIFRha2UgYSBsb29rIGF0IGEgc2luZ2xlIGV4YW1wbGUuCnByaW50KHJlYWRMaW5lcygiZGF0YS9zc190cmFpbi50eHQiLCBuPTEsIGVuY29kaW5nPSJVVEYtOCIpKQpgYGAKSWYgd2UgaGF2ZSBtdWx0aXBsZSBzZW50ZW5jZXMgKG1vcmUgdGhhbiAyKSB3ZSBjYW4ganVzdCBhcHBlbmQgdGhlbSB3aXRoIHRhYiBhcyBkZWxpbWl0ZXIuCkhlcmUgZm9yIG9uZSBleGFtcGxlIHdlIGFsd2F5cyBoYXZlIGV4YWN0bHkgdHdvIHNlbnRlbmNlcyAocXVlc3Rpb25zKS4KCiMjIyMgYHJ1aW10ZWhvbGAKCkluc3RlYWQgd2UgY2FuIHVzZSBhbm90aGVyIFIgd3JhcHBlciBwYWNrYWdlIGBydWltdGVob2xgKEBydWltdGVob2wpIHRvIGRvIHRoZSB0cmFpbmluZy4KCmBgYHtyIGltcG9ydF9ydWltdGVob2x9CmxpYnJhcnkocnVpbXRlaG9sKQpwYWNrYWdlVmVyc2lvbigicnVpbXRlaG9sIikKYGBgCgpSYXRoZXIgdGhhbiB0aGUgb3JpZ2luYWwgaW5wdXQgZm9ybWF0LApgcnVpbXRlaG9sYCBleHBlY3RzIGEgbG9uZyBmb3JtYXQgd2l0aCAxIHRva2VuIHBlciByb3cuCldlIGNhbiB1c2UgdGhlIGNvbnZlbmllbmNlIGZ1bmN0aW9uIGB1bm5lc3RfdG9rZW5zYCBmcm9tIHBhY2thZ2UgW2B0aWR5dGV4dGBdKGh0dHBzOi8vZ2l0aHViLmNvbS9qdWxpYXNpbGdlL3RpZHl0ZXh0KShAdGlkeXRleHQpIHRvIG1hbmlwdWxhdGUgdGV4dCBkYXRhIGluIHRhYnVsYXIgZm9ybToKCmBgYHtyIGZvcm1hdF9sb25nX2lucHV0fQpxMXQgPC0gdGlkeXRleHQ6OnVubmVzdF90b2tlbnMoCiAgZGF0YV90cmFpblssIC4oaWQsIHFpZDEsIHF1ZXN0aW9uMSldLAogIGlucHV0PSJxdWVzdGlvbjEiLCBvdXRwdXQ9InRva2VuIiwKICB0b2tlbj1mdW5jdGlvbih4KSBzZW50ZW5jZXBpZWNlX2VuY29kZShzcG0sIHgpKQpxMnQgPC0gdGlkeXRleHQ6OnVubmVzdF90b2tlbnMoCiAgZGF0YV90cmFpblssIC4oaWQsIHFpZDIsIHF1ZXN0aW9uMildLAogIGlucHV0PSJxdWVzdGlvbjIiLCBvdXRwdXQ9InRva2VuIiwKICB0b2tlbj1mdW5jdGlvbih4KSBzZW50ZW5jZXBpZWNlX2VuY29kZShzcG0sIHgpKQpzZW50X3NwYWNlX2xvbmcgPC0gcmJpbmRsaXN0KGxpc3QocTF0LCBxMnQpLCB1c2UubmFtZXM9RkFMU0UpCgojIFJlbmFtZSB0byBtYXRjaCBBUEkgcmVxdWlyZW1lbnQuCnNldG5hbWVzKHNlbnRfc3BhY2VfbG9uZywgYygiZG9jX2lkIiwgInNlbnRlbmNlX2lkIiwgInRva2VuIikpCgojIFRha2UgYSBsb29rLgpoZWFkKHNlbnRfc3BhY2VfbG9uZykgICMgVW5pY29kZSBjaGFyIHdvbid0IHNob3cgcHJvcGVybHkgb24gV2luZG93cy4KYGBgCgpOb3RlIHRoYXQgYDxVKzI1ODE+YCBpcyBqdXN0IHRoZSBzcGVjaWFsIHByZWZpeCB1c2VkIGluIGBzZW50ZW5jZXBpZWNlYCB0byByZXByZXNlbnQgd2hpdGUgc3BhY2UgYW5kIGFsc28gZHVtbXkgc2VudGVuY2UgYmVnaW5uaW5nLCBzbyB3ZSBjYW4gZGlzdGluZ3Vpc2ggYmV0d2VlbiBhIHN1YndvcmQgYW5kIGEgZnVsbCB3b3JkLgoKIyMgTW9kZWwgVHJhaW5pbmcgYW5kIFByZWRpY3Rpb24KCiMjIyBgc3RhcnNwYWNlYAoKVGhlIGNvbW1hbmQgbGluZSB0byB0cmFpbiBhIGJhc2VsaW5lIG1vZGVsIGNvdWxkIGJlIHNvbWV0aGluZyBsaWtlOgoKYGBgYmFzaApzdGFyc3BhY2UgdHJhaW4gXAogICAgLXRyYWluRmlsZSBkYXRhL3NzX3RyYWluLnR4dCBcCiAgICAtbW9kZWwgbW9kZWwvc3MubW9kZWwgXAogICAgLXRyYWluTW9kZSAzIFwKICAgIC1maWxlRm9ybWF0IGxhYmVsRG9jIFwKICAgIC1kaW0gNjQgXAogICAgLXZlcmJvc2UgMQpgYGAKCkFmdGVyIHRyYWluaW5nLAp3ZSBjYW4gcmVhZCBiYWNrIHRoZSByZXN1bHRpbmcgZW1iZWRkaW5nczoKCmBgYHtyIHJlYWRfZW1iZWRkaW5nfQplbWJlZGRpbmdzIDwtIGZyZWFkKCJtb2RlbC9zcy5tb2RlbC50c3YiLCBxdW90ZT0iIiwgZW5jb2Rpbmc9IlVURi04IikKZGltKGVtYmVkZGluZ3NbLC0xXSkgICMgdm9jYWIgc2l6ZSAqIGVtYmVkZGluZyBzaXplCmBgYApPbmUgbWF5IHJlYWxpemUgdGhhdCB0aGUgcmVzdWx0aW5nIHZvY2FidWxhcnkgZGltZW5zaW9uIGlzIGxhcmdlciB0aGFuIG91ciBzcGVjaWZpZWQgYHZvY2FiX3NpemVgIGluIG91ciB0b2tlbml6ZXIuClRoZSBtb3N0IHBvc3NpYmxlIHJlYXNvbiBpcyBiZWNhdXNlIHdlIGVuY29kZSB0aGUgb3JpZ2luYWwgdGV4dCBpbnRvIHdvcmQgcGllY2VzIGluc3RlYWQgb2Ygdm9jYWJ1bGFyeSBpZCwKc28gdGhlIHVua25vd24gdm9jYWJ1bGFyeSAob3V0IG9mIGB2b2NhYl9zaXplYCkgd2lsbCByZW1haW4uCkZvciBleGFtcGxlLApoZXJlIGlzIG9uZSBxdWVzdGlvbiB0aGF0IGNhbiBiZSBmb3VuZCBpbiB0aGUgZGF0YXNldCBpbnZvbHZpbmcgYW4gdW5rbm93biB3b3JkICLmlLnlloQiIChpdCBtZWFucyBpbXByb3ZpbmcgYW5kIGlzIG9yaWdpbmF0ZWQgZnJvbSBKYXBhbmVzZSBidXQgYWxzbyB1c2VkIGluIE1hbmRhcmluIGZvciB0aGUgc2FtZSBtZWFuaW5nKS4KSWYgd2UgcGFyc2UgaXQgYXMgdG9rZW5zIGFueSB1bmtub3duIHdvcmQgcmVtYWluLgpCdXQgaWYgd2UgcGFyc2UgaXQgYXMgaWRzIGFsbCB1bmtub3ducyBiZWNvbWUgdGhlIHVua25vd24gaWQgd2hpY2ggYnkgZGVmYXVsdCBpcyAwLgoKYGBge3IgcGFyc2VfdW5rbm93bn0KcyA8LSAid2hhdCBpcyBrYWl6ZW4gKOaUueWWhCk/IgpwcmludChzZW50ZW5jZXBpZWNlX2VuY29kZShzcG0sIHMpW1sxXV0pCnNlbnRlbmNlcGllY2VfZW5jb2RlKHNwbSwgcywgdHlwZT0iaWRzIilbWzFdXQpgYGAKTGV0J3MgdmVyaWZ5IHRoaXMgYnkgY29tcGFyaW5nIHRoZSB2b2NhYnVsYXJ5IGRpZmZlcmVuY2UgYmV0d2VlbiBvdXIgYHNlbnRlbmNlcGllY2VgIG1vZGVsIGFuZCBgc3RhcnNwYWNlYCBvbmU6CgpgYGB7ciBkaWZmX3ZvY2FifQp2MSA8LSBmcmVhZCgibW9kZWwvc3Audm9jYWIiLCBxdW90ZT0iIiwgZW5jb2Rpbmc9IlVURi04IikkVjEKdjIgPC0gZW1iZWRkaW5ncyRWMQoKIyBTaG93IHVua25vd24gd29yZHM6CnVua193b3JkcyA8LSBzZXRkaWZmKHYyLCB2MSkKcHJpbnQodW5rX3dvcmRzKQpgYGAKCmBgYHtyIGNoZWNrX3Vua30KdGFibGUodW5saXN0KHNlbnRlbmNlcGllY2VfZW5jb2RlKHNwbSwgdW5rX3dvcmRzLCB0eXBlPSJpZHMiKSkpCmBgYApBcyB3ZSBjYW4gc2VlLAp0aGVyZSBhcmUgb25seSB0d28gSURzIHJldmVhbGVkIGFmdGVyIHBhcnNpbmcuCmAwYCBpcyB1bmtub3duIGFuZCBgNDlgIGlzIGp1c3QgdGhlIHNwZWNpYWwgcHJlZml4IGByIHYxWzQ5KzFdYC4KClRvIHdvcmthcm91bmQgdGhpcyBpc3N1ZSB3ZSBjYW4gcGFzcyBpbnN0ZWFkIHRoZSB0b2tlbiBpZCBpbnRvIHRoZSBgc3RhcnNwYWNlYCBtb2RlbC4KSG93ZXZlciBieSBkb2luZyBzbyB3ZSB3aWxsIG5lZWQgc29tZSBleHRyYSBlbmdpbmVlcmluZyBlZmZvcnQgdG8gY29udmVydCB0aGUgbW9kZWwgb3V0cHV0IGludG8gaHVtYW4gcmVhZGFibGUgZm9ybSwKYnkgY2FsbGluZyBgc2VudGVuY2VwaWVjZWAgdG8gZGVjb2RlIHRoZSByZXR1cm5lZCBpZCBzZXF1ZW5jZS4KRm9yIHNpbXBsaWNpdHkgd2Ugd2lsbCBrZWVwIGl0IGFzIGlzIGZvciBub3cuCgojIyMjIFJhbmtpbmcgUHJlZGljdGlvbgoKTGV0J3MgZXhhbWluZSBob3cgZ29vZCBvdXIgYmFzZWxpbmUgbW9kZWwgaXMsCmJ5IGZpbmRpbmcgdGhlIG1vc3Qgc2ltaWxhciBxdWVzdGlvbnMgYW1vbmcgYWxsIHRoZSBgcXVlc3Rpb24yYCBpbiBvdXIgdmFsaWRhdGlvbiBzZXQgZm9yIHRoZSBmaXJzdCAzIGBxdWVzdGlvbjFgIGluIHRoZSBzYW1lIGRhdGFzZXQuCgpUbyB1c2UgdGhlIHByZWRpY3Rpb24gaW50ZXJmYWNlLAp3ZSBuZWVkIHRvIHByb3ZpZGUgYSBzZXQgb2YgYGJhc2Vkb2NgIGFzIHJhbmtpbmcgY2FuZGlkYXRlczoKCmBgYHtyIHRlc3RfcHJlZGljdGlvbn0KIyBUYWtlIDMgZXhhbXBsZXMgZm9yIHRlc3RpbmcuCnRvX2ZpbGUoZGF0YV92YWxpZFtpc19kdXBsaWNhdGUgPT0gMV1bMTozXSwgImRhdGEvdGVzdF9zZW50ZW5jZXMudHh0IikKCiMgVXNlIGFsbCBxdWVzdGlvbnMgaW4gcXVlc3Rpb24yIGFzIHRoZSByYW5raW5nIGNhbmRpZGF0ZXMuCnEyIDwtIHNlbnRlbmNlcGllY2VfZW5jb2RlKHNwbSwgeD11bmlxdWUoZGF0YV92YWxpZCRxdWVzdGlvbjIpKQpxMiA8LSBzYXBwbHkocTIsIHBhc3RlLCBjb2xsYXBzZT0iICIpCndyaXRlTGluZXMocTIsIGNvbj0iZGF0YS9yYW5rX3NlbnRlbmNlcy50eHQiLCB1c2VCeXRlcz1UUlVFKQpgYGAKCk5vdyB3ZSBydW4gdGhlIHRlc3QgaW50ZXJmYWNlIGluIGBzdGFyc3BhY2VgOgoKYGBgc2gKc3RhcnNwYWNlIHRlc3QgXAogICAgLXRlc3RGaWxlIGRhdGEvdGVzdF9zZW50ZW5jZXMudHh0IFwKICAgIC1tb2RlbCBtb2RlbC9zcy5tb2RlbCBcCiAgICAtdHJhaW5Nb2RlIDMgXAogICAgLWJhc2Vkb2MgZGF0YS9yYW5rX3NlbnRlbmNlcy50eHQgXAogICAgLXByZWRpY3Rpb25GaWxlIGRhdGEvcHJlZGljdGlvbnMudHh0IFwKICAgIC1LIDEwIFwKICAgIC1maWxlRm9ybWF0IGxhYmVsRG9jCmBgYAoKVGhlIHJlc3VsdHM6CgpgYGB7ciBzaG93X3ByZWRpY3Rpb24sIGNvbW1lbnQ9Jyd9CmNhdChyZWFkTGluZXMoImRhdGEvcHJlZGljdGlvbnMudHh0IiwgZW5jb2Rpbmc9IlVURi04IiksIHNlcD0iXG4iKQpgYGAKClRoZSBtb2RlbCB3b3JrcyBwcmV0dHkgd2VsbCBpbmRlZWQuCgpUaG91Z2ggdGhlIHJlc3VsdCBvbmx5IGNvbnNpZGVyIHRoZSAqZXhhY3QqIFJIUyBzZW50ZW5jZSB0byBiZSB0aGUgY29ycmVjdCBhbnN3ZXIgKHByZWZpeCBieSBhIGAoKyspYCksCmluIG91ciBkYXRhc2V0IHNpbmNlIHRoZXJlIGFyZSB1c3VhbGx5IG1vcmUgdGhhbiAxIGR1cGxpY2F0ZWQgcXVlc3Rpb25zIGZvciBlYWNoIGdpdmVuIHF1ZXN0aW9uLAp3ZSBmb3VuZCB0aGF0IHRob3NlIHJhbmtlZCBhcyBoaWdobHkgc2ltaWxhciBhcmUgcmVhbGx5IE9USEVSIGR1cGxpY2F0ZXMgaW4gdGhlIGRhdGFzZXQuCgpPZiBjb3Vyc2Ugb3VyIG1vZGVsIGRpZG4ndCBkaXJlY3RseSBhcHBseSB0byB0aGUgb3JpZ2luYWwgcHJvYmxlbSwKd2hlcmUgd2UgbmVlZCB0byBnaXZlIGEgYmluYXJ5IGFuc3dlciBvbiBhbnkgZ2l2ZW4gcXVlc3Rpb24gcGFpci4KQnV0IHdlIGNhbiBkZWZpbml0ZWx5IHVzZSB0aGUgbGVhcm5lZCBlbWJlZGRpbmdzIHRvIHRyYWluIGFub3RoZXIgZG93bnN0cmVhbSBtb2RlbCBmb3IgdGhpcy4KSW4gc3VjaCBhIGRvd25zdHJlYW0gbW9kZWwgc2luY2Ugb3VyIGxlYXJuZWQgZW1iZWRkaW5ncyBhcmUgYWxyZWFkeSBjb250ZXh0LWF3YXJlICh0aGV5IGFyZSBkZXJpdmVkIGZyb20gYSBsZWFybmluZyB0YXNrIG9wdGltaXppbmcgcGFpcndpc2Ugc2ltaWxhcml0eSksCnRoZSByZXN1bHQgc2hvdWxkIGJlIGJldHRlciB0aGFuIGEgbW9kZWwgdXNpbmcgb3RoZXIgcHJlLXRyYWluZWQgd29yZCBlbWJlZGRpbmdzLgoKIyMjIGBydWltdGVob2xgCgpOb3cgbGV0J3MgYWxzbyBkZW1vbnN0cmF0ZSBob3cgd2UgY2FuIHJ1biBgc3RhcnNwYWNlYCB0aHJvdWdoIGBydWltdGVob2xgLAppdHMgUiB3cmFwcGVyOl5bTm90ZSB0aGF0IHRoZSB3cmFwcGluZyB2ZXJzaW9uIG9mIGBzdGFyc3BhY2VgIGluIGBydWltdGVob2xgIG1heSBub3QgYmUgdGhlIHVwLXRvLWRhdGUgdmVyc2lvbiBhdmFpbGFibGUgaW4gYHN0YXJzcGFjZWAncyBvcmlnaW5hbCByZXBvc2l0b3J5Ll0KCmBgYHtyIHJ1bl9ydWltdGVob2x9CmlmICggIWZpbGUuZXhpc3RzKCJtb2RlbC9ydWltdGVob2wubW9kZWwiKSApIHsKICBzc19tb2RlbCA8LSBlbWJlZF9zZW50ZW5jZXNwYWNlKHNlbnRfc3BhY2VfbG9uZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbD0ibW9kZWwvcnVpbXRlaG9sLm1vZGVsIikKICAjIFRoZSBjYWxsIGRvZXNuJ3Qgc2VlbSB0byB3cml0ZSB0aGUgbW9kZWwgZmlsZSBhcyBzdWdnZXN0ZWQgYnkgdGhlIGRvY3VtZW50LgogICMgU2F2ZSBpdCBleHBsaWNpdGx5IGluc3RlYWQuCiAgc3RhcnNwYWNlX3NhdmVfbW9kZWwoc3NfbW9kZWwsIGZpbGU9Im1vZGVsL3J1aW10ZWhvbC5tb2RlbCIpCn0gZWxzZSB7CiAgc3NfbW9kZWwgPC0gc3RhcnNwYWNlX2xvYWRfbW9kZWwoIm1vZGVsL3J1aW10ZWhvbC5tb2RlbCIpCn0KYGBgCgpJbmRlZWQgdGhpcyBmdW5jdGlvbiB3aWxsIGVuZCB1cCBjb252ZXJ0aW5nIHRoZSBsb25nIGZvcm1hdCBpbnRvIHRoZSBTdGFyU3BhY2UgZm9ybWF0IHdlIGp1c3QgcHJlcGFyZWQgYmVmb3JlLAp0aGVuIGNhbGwgdGhlIGBzdGFyc3BhY2VgIGJpbmFyeS4KVGhlcmUgYXJlIHF1aXRlIHNvbWUgb3ZlcmhlYWRzIGluIHRoaXMgcHJvY2VzcyBiZWZvcmUgdGhlIHRyYWluaW5nIGpvYiBiZWdpbnMuXltJbmRlZWQgb3VyIHByZS1wcm9jZXNzaW5nIGNvZGUgd2l0aG91dCB0aGUgbmVlZCB0byBwcmVwYXJlIHRoZSBsb25nLWZvcm1hdCB3aWxsIHJ1biBjb25zaWRlcmFibHkgZmFzdGVyIHRoYW4gYHJ1aW10ZWhvbGAsIGJlY2F1c2Ugd2UgdGFrZSB0aGUgYWR2YW50YWdlIG9mIGtub3dpbmcgdGhlcmUgYXJlIGFsd2F5cyAyIHF1ZXN0aW9ucyBpbiAxIGRvY3VtZW50IGluIHRoaXMgcGFydGljdWxhciBkYXRhc2V0Ll0KClRoZXJlIGlzIG9uZSBtb3JlIGRpZmZlcmVuY2Ugb3RoZXIgdGhhbiBpbnB1dCBmb3JtYXQgcHJlcGFyYXRpb24gYmV0d2VlbiB0aGUgb3JpZ2luYWwgYHN0YXJzcGFjZWAgYW5kIHRoZSBSIHdyYXBwZXIgYHJ1aW10ZWhvbDo6ZW1iZWRfc2VudGVuY2VzcGFjZWAuClRoZSBsYXR0ZXIgY29tZXMgd2l0aCBhIHBhcmFtZXRlciBgZWFybHlfc3RvcHBpbmdgIHdoaWNoIHdpbGwgc2V0IGFzaWRlIGFkZGl0aW9uYWwgdmFsaWRhdGlvbiBzZXQgdG8gaW1wbGVtZW50IGVhcmx5IHN0b3BwaW5nLAp3aGljaCBpcyBxdWl0ZSBhIGNvbnZlbmllbnQgZmVhdHVyZS4KQnkgZGVmYXVsdCBpdCBpcyBzZXQgdG8gMC43NSBzbyBldmVuIHRob3VnaCB3ZSBkaWRuJ3Qgc3BlY2lmeSBhIHZhbGlkYXRpb24gc2V0IGluIHRoZSB2ZXJib3NlIG91dHB1dCBvbmUgY2FuIHNlZSB0aGUgdmFsaWRhdGlvbiBzY29yZSBvbiB0aGUgcmFuZG9tIDI1JSBvZiB0aGUgdHJhaW5pbmcgZXhhbXBsZXMuCgpUbyBieXBhc3MgdGhlIHByZS1wcm9jZXNzaW5nIG92ZXJoZWFkLAp3ZSBjYW4gaW5zdGVhZCB1c2UgdGhlIGxvdy1sZXZlbCBBUEk6CgpgYGByCnNzX21vZGVsIDwtIHN0YXJzcGFjZShtb2RlbD0ibW9kZWwvcnVpbXRlaG9sLm1vZGVsIiwKICAgICAgICAgICAgICAgICAgICAgIGZpbGU9ImRhdGEvc3NfdHJhaW4udHh0IiwKICAgICAgICAgICAgICAgICAgICAgIGZpbGVGb3JtYXQ9ImxhYmVsRG9jIiwKICAgICAgICAgICAgICAgICAgICAgIHRyYWluTW9kZT0zLAogICAgICAgICAgICAgICAgICAgICAgZGltPTY0KQpgYGAKClRoaXMgdGltZSBsZXQncyBnZXQgc29tZSBzZW50ZW5jZSBlbWJlZGRpbmdzIGFuZCBjYWxjdWxhdGUgdGhlIHNpbWlsYXJpdHkgb24gb3VyIG93bjoKCmBgYHtyIGVtYmVkX3NlbnR9CiMgR2V0IG9uZSBMSFMgc2VudGVuY2UuCnExIDwtIHBhc3RlKHNlbnRlbmNlcGllY2VfZW5jb2RlKAogIHNwbSwgeD1kYXRhX3ZhbGlkW2lzX2R1cGxpY2F0ZSA9PSAxXSRxdWVzdGlvbjFbMV0pW1sxXV0sIGNvbGxhcHNlPSIgIikKTEhTIDwtIHN0YXJzcGFjZV9lbWJlZGRpbmcoc3NfbW9kZWwsIHg9cTEpCgojIEV4dHJhY3QgdGhlIGVtYmVkZGluZ3MgZm9yIHRoZSBmaXJzdCAxMDAgc2VudGVuY2VzLgpSSFMgPC0gc3RhcnNwYWNlX2VtYmVkZGluZyhzc19tb2RlbCwgeD1xMlsxOjEwMF0pCgojIERvIHBhaXJ3aXNlIGNvc2luZSBzaW1pbGFyaXR5LgpyZXMgPC0gZW1iZWRkaW5nX3NpbWlsYXJpdHkoTEhTLCBSSFMsIHRvcF9uPTUpCnNldERUKHJlcykKCnJlc1ssIC4odGVybTIsIHNpbWlsYXJpdHksIHJhbmspXQpgYGAKCk9uZSBmaW5hbCByZW1hcms6Ck9uIFdpbmRvd3Mgc3lzdGVtIHVzaW5nIGBydWltdGVob2xgIG1heSBlbmNvdW50ZXIgc29tZSBlbmNvZGluZyBpc3N1ZXMgd2hpY2ggbWFrZSB0aGUgbW9kZWwgbGVzcyBlZmZlY3RpdmUgKHNpbmNlIHRoZSB2b2NhYiBiZWNvbWVzIGEgbGl0dGxlIGJpdCBub2lzaWVyKS4KCmBTdGFyc3BhY2VgIGFsc28gY29tZSB3aXRoIGEgUHl0aG9uIHdyYXBwZXIgb2ZmaWNpYWxseSBidXQgdGhlIHdvcmsgc2VlbXMgdG8gYmUgb25seSBwYXJ0aWFsbHkgZG9uZS4KV2UgbGVhdmUgdGhlIGV4cGxvcmF0aW9uIGZvciB0aGUgcmVhZGVycy4KCiMgUmVmZXJlbmNlcwo=