© 2018 Chris Culy, March 2018

Overview

This is one of a series of posts on using word vectors with small corpora. In this post I discuss one approach to finding useful parameters and methods, by evaluating word vector models against standard testbeds.

The surprising results are that including all words is more effective than eliminating low frequency words, and that the choice of window size and the number of dimensions is not significant for these small corpora.

Background

In the previous sections (Stabilizing randomness and A new measure for evaluation), we have discussed three different word vector models: positive point-wise mutual information + SVD (ppmi_svd), word2vec, and FastText. There are various parameters to set when constructing a word vector model. Therefore, we would like to know which models and which settings will be the most useful.

As mentioned in the evaluation post, one way of evaluating word vectors is to apply them to tasks that people do, and compare those results to people’s results. Two common tasks are similarity (or relatedness) and analogies. For similarities, the task is to judge how similar (or related) two words are. For analogies, the task is to fill in the missing term in a series “A is to B as C is to —”. In this post, I will focus on similarity, and following [1], I will use four standard testsets: ws353 [2], ws353_similarity [3], ws353_relatedness [4], and bruni_men [5].

Methodology

Approach

My general approach is similar to that in [1], namely try all combinations of some set of parameter values (i.e. a grid search), evaluating each one with each model. However, since the centroid models are significantly slower to calculate than the ppmi_svd models, I will focus on the ppmi_svd models. As an example of the relative times, here are the times to do the 100 combinations of parameters discussed below applied to the novel Vanity Fair, using a 2.9 GHz Intel Core i7 laptop with 8GB of RAM:

  • ppmi_svd: ~ 2 hours
  • word2vec: ~ 5 hours
  • FastText: ~ 14.75 hours

While there are multiple parameters to set when setting up a word vector model, I will focus here on the parameters of window size (win), number of dimensions (dim), and the minimal count for items to be included (min_count).

Texts

For the corpora, I chose to use 19th century novels written in English. I arbitrarily used this list of “best” novels written in English:

https://www.theguardian.com/books/2015/aug/17/the-100-best-novels-written-in-english-the-full-list

From that list, I excluded children’s books (Alice’s Adventures in Wonderland, Little Women, and Huckleberry Finn), which leaves the following texts:

    1. Emma by Jane Austen (1816)
    1. Frankenstein by Mary Shelley (1818)
    1. Nightmare Abbey by Thomas Love Peacock (1818)
    1. The Narrative of Arthur Gordon Pym of Nantucket by Edgar Allan Poe (1838)
    1. Sybil by Benjamin Disraeli (1845)
    1. Jane Eyre by Charlotte Brontë (1847)
    1. Wuthering Heights by Emily Brontë (1847)
    1. Vanity Fair by William Thackeray (1848)
    1. David Copperfield by Charles Dickens (1850)
    1. The Scarlet Letter by Nathaniel Hawthorne (1850)
    1. Moby-Dick by Herman Melville (1851)
    1. The Moonstone by Wilkie Collins (1868)
    1. Middlemarch by George Eliot (1871-2)
    1. The Way We Live Now by Anthony Trollope (1875)
    1. Kidnapped by Robert Louis Stevenson (1886)
    1. Three Men in a Boat by Jerome K Jerome (1889)
    1. The Sign of Four by Arthur Conan Doyle (1890)
    1. The Picture of Dorian Gray by Oscar Wilde (1891)
    1. New Grub Street by George Gissing (1891)
    1. Jude the Obscure by Thomas Hardy (1895)
    1. The Red Badge of Courage by Stephen Crane (1895)
    1. Dracula by Bram Stoker (1897)
    1. Heart of Darkness by Joseph Conrad (1899)

I used the most recent versions of the texts from Project Gutenberg, with the following manual processing:

  • Convert to UTF-8
  • LF ends of line
  • Remove Project Gutenberg transcriber material.
  • Keep Title, TOC, but not editor notes.
  • Remove editorial, but not authorial footnotes.
  • Remove transcriber annotations for illustrations, but keep original captions

Of those 23 books, I used 20 for testing, and held out 3 for potential evaluation: the third shortest (The Sign of the Four), the median (Jude the Obscure), and the third longest (Middlemarch). That makes an 87%-13% test/evaluation split. In this post, I discuss only the 20 testing texts.

Vectors and evaluation

To create the ppmi_svd models, I used the hyperwords [6] package, which I ported to python3. To create the other models I used gensim [7]. All the evaluations were done using gensim, skipping unknown words (dummy4unknown=False), with additional custom code for calculating the sF1 score (see below):

vecs.evaluate_word_pairs(pairs, restrict_vocab=len(vecs.vocab), case_insensitive=True, dummy4unknown=False)

In creating the ppmi_svd vectors, there are two other hyperparameters to set. Following [1], I set Context Distribution Smoothing (cds) to 0.75. As well, to prevent subsampling (which introduces an element of randomness), I set sub to 0. In addition, in order to use gensim to evaluate the svd_ppmi vectors created by hyperwords, they first had to be converted to the word2vec format.

As discussed in the evaluation measure post, small corpora do not contain all the words of the evaluation sets, which greatly affects recall and which makes the Spearman \(\rho\) measure of correlation less useful on its own. In order to have a single measure which combines recall and Spearman \(\rho\), we use the analogue of the F1 measure, scaling \(\rho\) (\(\rho'\)) to be in the range [0,1] and using that in place of precision. The result is the sF1 score:

\[sF1 = \frac{2 * \rho' * recall}{\rho' + recall}\]

Based on the literature and on a small pilot test, I tested these parameter settings:

  • min_count: 1, 3, 5, 10, 20
  • win: 2, 5, 10, 20
  • dim: 25, 50, 100, 200, 400

The testing

# Load libraries
library(readr)
library(tidyverse)

There are a few cases where the recall is 0 or 1, and hence Spearman’s rho, and by extension, sF1, are not defined. We’ll remove those cases from further consideration.

eval_texts <- list('sign4','jude','midmarch')
test_names <- list("ws353","ws353_similarity","ws353_relatedness","bruni_men")
#Load data
types_tokens <- read_delim("types_tokens.csv", "\t", escape_double = FALSE, trim_ws = TRUE) %>%
  mutate(ttr = types/tokens)
SVD_sim_tests <- read_delim("testsets_parameters/SVD-sim_tests.csv", "\t", escape_double = FALSE, trim_ws = TRUE) %>%
  mutate(method="ppmi_svd")
word2vec_sim_tests <- read_delim("testsets_parameters/word2vec-sim_tests.csv", "\t", escape_double = FALSE, trim_ws = TRUE) %>%
  mutate(method="word2vec")
FastText_sim_tests <- read_delim("testsets_parameters/FastText-sim_tests.csv", "\t", escape_double = FALSE, trim_ws = TRUE) %>%
  mutate(method="FastText")
sim_tests <- rbind(SVD_sim_tests,word2vec_sim_tests,FastText_sim_tests) %>%
  inner_join(types_tokens) %>%
  rename(min_count=thresh) %>% 
  filter(! is.nan(sF1))
sim_evals <- sim_tests %>%
  filter(text %in% eval_texts)
sim_tests <- sim_tests %>%
  filter(!(text %in% eval_texts))
rm(SVD_sim_tests,word2vec_sim_tests,FastText_sim_tests)

We’ll look at the minimum count first.

sim_tests %>%
  ggplot() + theme_classic() + labs(title=paste("min_count vs sF1")) +
  geom_point(aes(min_count,sF1,color=text)) +
  geom_line(aes(min_count,sF1,color=text)) +
  facet_wrap(testset ~ method, ncol=3)

The best sF1 scores are always with a min_count of 1, i.e. including all words. This is in contrast to the standard practice with large corpora of eliminating low frequency words (a min_count of 100 is common). One possible reason for the success of including all the words is that it gives us more information to work with.

From here on, we’ll work only with the subset of parameter settings where min_count is 1.

sim_tests_1 <- sim_tests %>% filter(min_count==1)

Next we can look at the window and dimension sizes, restricting our attention to the ppmi_svd method. The best scores are distributed across win + dim combinates. The same exact distribution holds for all the testsets.

sim_tests_1 %>% filter(method=='ppmi_svd') %>%
  group_by(testset,text) %>%
  filter(sF1==max(sF1)) %>%
  ungroup() %>%
  group_by(testset,win,dim) %>%
  summarise(counts=n()) %>%
  spread(testset,counts) %>%
  arrange(-ws353,win,dim)

The amount of variation in sF1 score for a given text is fairly small, both across testsets and across parameter settings. The greatest variation in sF1 scores is across different texts.

ranges <- sim_tests_1 %>% 
  group_by(testset,text) %>%
  mutate(best=max(sF1), worst=min(sF1), range=best-worst) %>% 
  select(testset,text,best,worst,range) %>%
  unique()
bm <- filter(ranges, testset=='bruni_men') %>% arrange(range)
ranges$text <- factor(ranges$text, levels = bm$text[order(-bm$range)])
ranges %>%
  ggplot() + theme_classic() + labs(title="Ranges of sF1 scores by testset") +
  theme(axis.text.x=element_text(angle=45,hjust=1,vjust=1)) +
  geom_linerange(aes(x=text,ymin=worst,ymax=best,color=text), show.legend = FALSE) +
  facet_wrap(~testset)

ranges %>% 
  ungroup() %>%
  group_by(text) %>%
  mutate(best_sF1=max(best),worst_sF1=min(worst),sF1_range=best_sF1-worst_sF1) %>%
  select(text,best_sF1,worst_sF1,sF1_range) %>% 
  unique() %>%
  arrange(-sF1_range)

We can use linear regression for another take on the issue of parameters as predictors of the sF1 score. When we do that, we see that win and dim are not significant predictors of the sF1 score, and neither is the testset. Only the inherent properties of the text (type, tokens, and their ratio) are significant predictors.

m <- lm(sF1 ~ win*dim + tokens + types + ttr + testset,
        data=filter(sim_tests_1,method=='ppmi_svd'))
print(anova(m))
Analysis of Variance Table

Response: sF1
            Df Sum Sq Mean Sq   F value Pr(>F)    
win          1 0.0048  0.0048    3.3969 0.0655 .  
dim          1 0.0002  0.0002    0.1694 0.6807    
tokens       1 5.8259  5.8259 4153.1802 <2e-16 ***
types        1 2.3592  2.3592 1681.7929 <2e-16 ***
ttr          1 0.5371  0.5371  382.9086 <2e-16 ***
testset      3 0.0000  0.0000    0.0068 0.9992    
win:dim      1 0.0002  0.0002    0.1547 0.6941    
Residuals 1590 2.2304  0.0014                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
pts <- data.frame(predicted=fitted(m), residuals=residuals(m))

ggplot(pts) + theme_classic() +
  labs(title="Predicted vs residuals") + 
  geom_point(aes(predicted,residuals))

We look at how tokens and types and their ratio are related to sF1. For simplicity, we’ll limit ourselves just to bruni_men.

sim_tests_1 %>% filter(testset=='bruni_men') %>%
  ggplot() + theme_classic() + labs(title=paste("tokens vs sF1 for bruni_men")) +
  scale_x_continuous(labels = scales::comma) +
  geom_point(aes(tokens,sF1,color=text)) +
  geom_smooth(aes(tokens,sF1), color='orange', method='loess')

sim_tests_1 %>% 
  filter(testset=='bruni_men') %>%
  ggplot() + theme_classic() + labs(title=paste("types vs sF1 for bruni_men")) +
  scale_x_continuous(labels = scales::comma) +
  geom_point(aes(types,sF1,color=text)) +
  geom_smooth(aes(types,sF1), color='orange', method='loess')

The connection between types (vocabulary size) and sF1 score is not surprising, since the vocabulary size affects recall, and recall is the dominant aspect of the sF1 score for good scores (remember that we’re only considering settings with a min_count of 1, since those produced the best scores).

sim_tests_1 %>% 
  filter(testset=='bruni_men') %>%
  ggplot() + theme_classic() + labs(title=paste("recall vs sF1 for bruni_men")) +
  geom_point(aes(recall,sF1,color=text,shape=testset)) +
  geom_smooth(aes(recall,sF1), color='orange', method='loess')

In fact, for small corpora, the Spearman \(\rho\) alone is not a great indicator of quality.

sim_tests_1 %>% 
  filter(testset=='bruni_men') %>%
  ggplot() + theme_classic() + labs(title=paste("spearman vs sF1 for bruni_men")) +
  geom_point(aes(spearman,sF1,color=text,shape=testset))

Finally we can look at the type/token ratio versus the sF1 score. We see that the trend has a distinct rise until a ttr of about 0.06, and then an almost linear decline.

sim_tests_1 %>% 
  filter(testset=='bruni_men') %>%
  ggplot() + theme_classic() + labs(title=paste("type/token ratio vs sF1 for bruni_men")) +
  geom_point(aes(ttr,sF1,color=text)) +
  geom_smooth(aes(ttr,sF1), color='orange', method='loess')

Of the those three inherent properties, the number of tokens (i.e. vocabulary size) shows the most straightfoward connection with sF1.

Given that win and dim are important for large corpora, we should wonder at what point they do become significant. We can divide our testset into 3 rough groups based on their vocabulary size, using 8000 and 12000 tokens as the dividing lines:

texts_grouped <- types_tokens %>% 
  filter(!(text %in% eval_texts)) %>%
  mutate(size_group=if_else(types<8000,"small",
                                           if_else(types<12000,"medium","large"))) 
texts_grouped %>%
  select(size_group,text,types) %>%
  arrange(types,text)

When we do the regression models, it turns out neither win nor dim are significant for the small texts; win is slightly (p<0.05) significant for the medium texts; and both win and dim are highly significant for the large texts.

#see if win/dim are signifcant with <8000 types
m8k <- lm(sF1 ~ win*dim + tokens + types + ttr + testset,
        data=filter(sim_tests_1,method=='ppmi_svd',types<8000))
print(anova(m8k))
Analysis of Variance Table

Response: sF1
           Df  Sum Sq Mean Sq  F value Pr(>F)    
win         1 0.00031 0.00031   0.2598 0.6104    
dim         1 0.00012 0.00012   0.1029 0.7485    
tokens      1 0.13750 0.13750 115.0901 <2e-16 ***
types       1 0.86804 0.86804 726.5663 <2e-16 ***
ttr         1 0.28799 0.28799 241.0540 <2e-16 ***
testset     3 0.00009 0.00003   0.0253 0.9945    
win:dim     1 0.00001 0.00001   0.0102 0.9194    
Residuals 710 0.84825 0.00119                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#see if win/dim are signifcant when [8000,12000] types
m8_12k <- lm(sF1 ~ win*dim + tokens + types + ttr + testset,
        data=filter(sim_tests_1,method=='ppmi_svd',types>8000 & types<12000))
print(anova(m8_12k))
Analysis of Variance Table

Response: sF1
           Df   Sum Sq  Mean Sq  F value    Pr(>F)    
win         1 0.001441 0.001441   4.2805   0.03902 *  
dim         1 0.000024 0.000024   0.0728   0.78747    
tokens      1 0.016835 0.016835  50.0093 4.668e-12 ***
types       1 0.017981 0.017981  53.4123 9.575e-13 ***
ttr         1 0.288872 0.288872 858.1030 < 2.2e-16 ***
testset     3 0.000005 0.000002   0.0046   0.99958    
win:dim     1 0.000359 0.000359   1.0664   0.30221    
Residuals 550 0.185152 0.000337                       
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#see if win/dim are signifcant when >12000 types
sim_tests_12k <- filter(sim_tests_1,method=='ppmi_svd',types>12000)
m12k <- lm(sF1 ~ win*dim + tokens + types + ttr + testset,
        data=sim_tests_12k)
print(anova(m12k))
Analysis of Variance Table

Response: sF1
           Df    Sum Sq   Mean Sq  F value    Pr(>F)    
win         1 0.0060388 0.0060388 119.0324 < 2.2e-16 ***
dim         1 0.0019847 0.0019847  39.1220 1.319e-09 ***
tokens      1 0.0211591 0.0211591 417.0751 < 2.2e-16 ***
types       1 0.0010636 0.0010636  20.9654 6.780e-06 ***
ttr         1 0.0071706 0.0071706 141.3417 < 2.2e-16 ***
testset     3 0.0000132 0.0000044   0.0866   0.96734    
win:dim     1 0.0001723 0.0001723   3.3960   0.06631 .  
Residuals 310 0.0157270 0.0000507                       
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

The best parameters for the four largest corpora

sim_tests_12k %>%
  group_by(text) %>%
  filter(sF1==max(sF1)) %>%
  select(text,tokens,sF1,spearman,recall,win,dim) %>%
  arrange(-tokens)

Even though win and dim are significant, there is no obvious pattern. There are only 4 “large” texts; even so, the boundary of where win and dim become significant, as well as any patterns in their values, are worth exploring further.

Comparing methods

As I mentioned at the beginning, I haven’t done the full complement of parameters acorss all the texts for word2vec and FastText. However, we can compare Vanity Fair (a long text) and Heart of Darkness (a short text) across all 3 methods. We already saw above the min_count=1 is always the best across the methods, so again, we’ll limit our attention to that subset.

sims_vfhd <- sim_tests %>% filter(text=='vfair' | text=='heartd', min_count==1)

Doing a simplified regression model, we see that method is significant, at least with these two texts, as is dim.

m_vfhd <- lm(sF1 ~ win*dim +  tokens + testset + method,
        data=sims_vfhd)
print(anova(m_vfhd))
Analysis of Variance Table

Response: sF1
           Df Sum Sq Mean Sq    F value    Pr(>F)    
win         1 0.0000  0.0000     0.0030    0.9566    
dim         1 0.0012  0.0012    20.4999 7.582e-06 ***
tokens      1 5.1859  5.1859 91909.7900 < 2.2e-16 ***
testset     3 0.0000  0.0000     0.1048    0.9572    
method      2 0.0052  0.0026    45.9455 < 2.2e-16 ***
win:dim     1 0.0000  0.0000     0.0093    0.9233    
Residuals 466 0.0263  0.0001                         
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Taking just the best sF1 scores and using the bruni_men testset as an example, we have the following table.

Comparing methods for Vanity Fair and Heart of Darkness

sims_vfhd %>% filter(testset=="bruni_men") %>%
  group_by(method,text) %>%
  filter(sF1==max(sF1)) %>%
  select(text,sF1,method,dim) %>%
  arrange(-sF1,method,text,dim)

With respect to method, ppmi_svd has its highest sF1 score for the small Heart of Darkness, and its lowest for Vanity Fair, while FastText is the opposite.

For dimensions, FastText has its highest sF1 score with a small number of dimensions; ppmi_svd has its highest sF1 score with a larger number of dimensions; and word2vec is mixed.

Of course, it would be good to have a full complement of sF1 scores for different corpus sizes, but at the very least we can tell already that there is no commonality in the parameter settings across the methods.

Discussion and Conclusion

In the spirit of [1], here are a few “lessons learned”:

Now that we’ve gone through these evaluations with the word similarity testsets, it is worth questioning how useful these testsets are for evaluating small corpora. First, we saw above that the vocabulary size is very strongly associated with the sF1 score. Second, we can ask whether these testsets are even relevant, especially in the context of inquiries into authors’ use of words.

The next post, Exploring similarities, will explore a different perspective on parameter settings.

Back to the introduction

Other posts

References

[1] Omer Levy, Yoav Goldberg, and Ido Dagan. 2015. Improving Distributional Similarity with Lessons Learned from Word Embeddings. Transactions of the Association for Computational Linguistics, vol. 3, pp. 211–225.

[2] Lev Finkelstein, Evgeniy Gabrilovich, Yossi Matias, Ehud Rivlin, Zach Solan, Gadi Wolfman, and Eytan Ruppin. 2002. Placing search in context: The concept revisited. ACM Transactions on Information Systems, 20(1):116–131.

[3] Torsten Zesch, Christof Müller, and Iryna Gurevych. 2008. Using wiktionary for computing semantic relatedness. In Proceedings of the 23rd National Conference on Artificial Intelligence - Volume 2, AAAI’08, 861–866. AAAI Press.

[4] Eneko Agirre, Enrique Alfonseca, Keith Hall, Jana Kravalova, Marius Pasca, and Aitor Soroa. 2009. A study on similarity and relatedness using distributional and wordnet-based approaches. In Proceedings of Human Language Technologies: The 2009 Annual Conference of the North American Chapter of the Association for Computational Linguistics, pages 19–27, Boulder, Colorado, June. Association for Computational Linguistics.

[5] Elia Bruni, Gemma Boleda, Marco Baroni, and Nam Khanh Tran. 2012. Distributional semantics in technicolor. In Proceedings of the 50th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers), pages 136–145, Jeju Island, Korea, July. Association for Computational Linguistics.

[6] Hyperwords: https://bitbucket.org/omerlevy/hyperwords, published as 1

[7] Gensim: https://radimrehurek.com/gensim/, published as: Software Framework for Topic Modelling with Large Corpora. Radim Řehůřek and Petr Sojka, Proceedings of the LREC 2010 Workshop on New Challenges for NLP Frameworks, pp. 45-50, 22 May 2010.

LS0tCnRpdGxlOiAiV29yZCB2ZWN0b3JzIHdpdGggc21hbGwgY29ycG9yYTo8YnI+RmluZGluZyB1c2VmdWwgcGFyYW1ldGVycyBhbmQgbWV0aG9kcyIKb3V0cHV0OiBodG1sX25vdGVib29rCmZpZ19jYXB0aW9uOiB5ZXMKLS0tCgojIyDCqSAyMDE4IENocmlzIEN1bHksIE1hcmNoIDIwMTgKIyMjIFtjaHJpc2N1bHkubmV0XShodHRwOi8vY2hyaXNjdWx5Lm5ldC8pCgojIyBPdmVydmlldwpUaGlzIGlzIG9uZSBvZiBhIFtzZXJpZXMgb2YgcG9zdHNdKHd2ZWNzX2ludHJvLmh0bWwpIG9uIHVzaW5nIHdvcmQgdmVjdG9ycyB3aXRoIHNtYWxsIGNvcnBvcmEuIEluIHRoaXMgcG9zdCBJIGRpc2N1c3Mgb25lIGFwcHJvYWNoIHRvIGZpbmRpbmcgdXNlZnVsIHBhcmFtZXRlcnMgYW5kIG1ldGhvZHMsIGJ5IGV2YWx1YXRpbmcgd29yZCB2ZWN0b3IgbW9kZWxzIGFnYWluc3Qgc3RhbmRhcmQgdGVzdGJlZHMuIAoKVGhlIHN1cnByaXNpbmcgcmVzdWx0cyBhcmUgdGhhdCBpbmNsdWRpbmcgYWxsIHdvcmRzIGlzIG1vcmUgZWZmZWN0aXZlIHRoYW4gZWxpbWluYXRpbmcgbG93IGZyZXF1ZW5jeSB3b3JkcywgYW5kIHRoYXQgdGhlIGNob2ljZSBvZiB3aW5kb3cgc2l6ZSBhbmQgdGhlIG51bWJlciBvZiBkaW1lbnNpb25zIGlzIG5vdCBzaWduaWZpY2FudCBmb3IgdGhlc2Ugc21hbGwgY29ycG9yYS4KCiMjIEJhY2tncm91bmQKCkluIHRoZSBwcmV2aW91cyBzZWN0aW9ucyAoW1N0YWJpbGl6aW5nIHJhbmRvbW5lc3NdKHd2ZWNzX3JhbmRvbV9maXguaHRtbCkgYW5kIFtBIG5ldyBtZWFzdXJlIGZvciBldmFsdWF0aW9uXSh3dmVjc19ldmFsdWF0aW9uX21lYXN1cmUuaHRtbCkpLCB3ZSBoYXZlIGRpc2N1c3NlZCB0aHJlZSBkaWZmZXJlbnQgd29yZCB2ZWN0b3IgbW9kZWxzOiBwb3NpdGl2ZSBwb2ludC13aXNlIG11dHVhbCBpbmZvcm1hdGlvbiArIFNWRCAocHBtaV9zdmQpLCB3b3JkMnZlYywgYW5kIEZhc3RUZXh0LiBUaGVyZSBhcmUgdmFyaW91cyBwYXJhbWV0ZXJzIHRvIHNldCB3aGVuIGNvbnN0cnVjdGluZyBhIHdvcmQgdmVjdG9yIG1vZGVsLiBUaGVyZWZvcmUsIHdlIHdvdWxkIGxpa2UgdG8ga25vdyB3aGljaCBtb2RlbHMgYW5kIHdoaWNoIHNldHRpbmdzIHdpbGwgYmUgdGhlIG1vc3QgdXNlZnVsLgoKQXMgbWVudGlvbmVkIGluIHRoZSBbZXZhbHVhdGlvbiBwb3N0XSh3dmVjc19ldmFsdWF0aW9uX21lYXN1cmUuaHRtbCksIG9uZSB3YXkgb2YgZXZhbHVhdGluZyB3b3JkIHZlY3RvcnMgaXMgdG8gYXBwbHkgdGhlbSB0byB0YXNrcyB0aGF0IHBlb3BsZSBkbywgYW5kIGNvbXBhcmUgdGhvc2UgcmVzdWx0cyB0byBwZW9wbGUncyByZXN1bHRzLiBUd28gY29tbW9uIHRhc2tzIGFyZSBzaW1pbGFyaXR5IChvciByZWxhdGVkbmVzcykgYW5kIGFuYWxvZ2llcy4gRm9yIHNpbWlsYXJpdGllcywgdGhlIHRhc2sgaXMgdG8ganVkZ2UgaG93IHNpbWlsYXIgKG9yIHJlbGF0ZWQpIHR3byB3b3JkcyBhcmUuIEZvciBhbmFsb2dpZXMsIHRoZSB0YXNrIGlzIHRvIGZpbGwgaW4gdGhlIG1pc3NpbmcgdGVybSBpbiBhIHNlcmllcyAiQSBpcyB0byBCIGFzIEMgaXMgdG8g4oCUIi4gSW4gdGhpcyBwb3N0LCBJIHdpbGwgZm9jdXMgb24gc2ltaWxhcml0eSwgYW5kIGZvbGxvd2luZyBbWzFdXSgjcmVmMSksIEkgd2lsbCB1c2UgZm91ciBzdGFuZGFyZCB0ZXN0c2V0czogX3dzMzUzXyBbWzJdXSgjcmVmMiksIF93czM1M19zaW1pbGFyaXR5XyBbWzNdXSgjcmVmMyksIF93czM1M19yZWxhdGVkbmVzc18gW1s0XV0oI3JlZjQpLCBhbmQgX2JydW5pX21lbl8gW1s1XV0oI3JlZjUpLiAKCiMjIE1ldGhvZG9sb2d5CiMjIyBBcHByb2FjaApNeSBnZW5lcmFsIGFwcHJvYWNoIGlzIHNpbWlsYXIgdG8gdGhhdCBpbiBbWzFdXSgjcmVmMSksIG5hbWVseSB0cnkgYWxsIGNvbWJpbmF0aW9ucyBvZiBzb21lIHNldCBvZiBwYXJhbWV0ZXIgdmFsdWVzIChpLmUuIGEgZ3JpZCBzZWFyY2gpLCBldmFsdWF0aW5nIGVhY2ggb25lIHdpdGggZWFjaCBtb2RlbC4gSG93ZXZlciwgc2luY2UgdGhlIGNlbnRyb2lkIG1vZGVscyBhcmUgc2lnbmlmaWNhbnRseSBzbG93ZXIgdG8gY2FsY3VsYXRlIHRoYW4gdGhlIHBwbWlfc3ZkIG1vZGVscywgSSB3aWxsIGZvY3VzIG9uIHRoZSBwcG1pX3N2ZCBtb2RlbHMuIEFzIGFuIGV4YW1wbGUgb2YgdGhlIHJlbGF0aXZlIHRpbWVzLCBoZXJlIGFyZSB0aGUgdGltZXMgdG8gZG8gdGhlIDEwMCBjb21iaW5hdGlvbnMgb2YgcGFyYW1ldGVycyBkaXNjdXNzZWQgYmVsb3cgYXBwbGllZCB0byB0aGUgbm92ZWwgX1Zhbml0eSBGYWlyXywgdXNpbmcgYSAyLjkgR0h6IEludGVsIENvcmUgaTcgbGFwdG9wIHdpdGggOEdCIG9mIFJBTToKCiogcHBtaV9zdmQ6IH4gMiBob3VycwoqIHdvcmQydmVjOiB+IDUgaG91cnMKKglGYXN0VGV4dDogfiAxNC43NSBob3VycwoKV2hpbGUgdGhlcmUgYXJlIG11bHRpcGxlIHBhcmFtZXRlcnMgdG8gc2V0IHdoZW4gc2V0dGluZyB1cCBhIHdvcmQgdmVjdG9yIG1vZGVsLCBJIHdpbGwgZm9jdXMgaGVyZSBvbiB0aGUgcGFyYW1ldGVycyBvZiB3aW5kb3cgc2l6ZSAoX3dpbl8pLCBudW1iZXIgb2YgZGltZW5zaW9ucyAoX2RpbV8pLCBhbmQgdGhlIG1pbmltYWwgY291bnQgZm9yIGl0ZW1zIHRvIGJlIGluY2x1ZGVkIChfbWluX2NvdW50XykuCgojIyMgVGV4dHMKRm9yIHRoZSBjb3Jwb3JhLCBJIGNob3NlIHRvIHVzZSAxOXRoIGNlbnR1cnkgbm92ZWxzIHdyaXR0ZW4gaW4gRW5nbGlzaC4gSSBhcmJpdHJhcmlseSB1c2VkIHRoaXMgbGlzdCBvZiAiYmVzdCIgbm92ZWxzIHdyaXR0ZW4gaW4gRW5nbGlzaDoKCltodHRwczovL3d3dy50aGVndWFyZGlhbi5jb20vYm9va3MvMjAxNS9hdWcvMTcvdGhlLTEwMC1iZXN0LW5vdmVscy13cml0dGVuLWluLWVuZ2xpc2gtdGhlLWZ1bGwtbGlzdF0oaHR0cHM6Ly93d3cudGhlZ3VhcmRpYW4uY29tL2Jvb2tzLzIwMTUvYXVnLzE3L3RoZS0xMDAtYmVzdC1ub3ZlbHMtd3JpdHRlbi1pbi1lbmdsaXNoLXRoZS1mdWxsLWxpc3QpCgpGcm9tIHRoYXQgbGlzdCwgSSBleGNsdWRlZCBjaGlsZHJlbidzIGJvb2tzIChfQWxpY2UncyBBZHZlbnR1cmVzIGluIFdvbmRlcmxhbmRfLCBfTGl0dGxlIFdvbWVuXywgYW5kIF9IdWNrbGViZXJyeSBGaW5uXyksIHdoaWNoIGxlYXZlcyB0aGUgZm9sbG93aW5nIHRleHRzOgoKKiA3LiBFbW1hIGJ5IEphbmUgQXVzdGVuICgxODE2KQoqIDguIEZyYW5rZW5zdGVpbiBieSBNYXJ5IFNoZWxsZXkgKDE4MTgpCiogOS4gTmlnaHRtYXJlIEFiYmV5IGJ5IFRob21hcyBMb3ZlIFBlYWNvY2sgKDE4MTgpCiogMTAuIFRoZSBOYXJyYXRpdmUgb2YgQXJ0aHVyIEdvcmRvbiBQeW0gb2YgTmFudHVja2V0IGJ5IEVkZ2FyIEFsbGFuIFBvZSAoMTgzOCkKKiAxMS4gU3liaWwgYnkgQmVuamFtaW4gRGlzcmFlbGkgKDE4NDUpCiogMTIuIEphbmUgRXlyZSBieSBDaGFybG90dGUgQnJvbnTDqyAoMTg0NykKKiAxMy4gV3V0aGVyaW5nIEhlaWdodHMgYnkgRW1pbHkgQnJvbnTDqyAoMTg0NykKKiAxNC4gVmFuaXR5IEZhaXIgYnkgV2lsbGlhbSBUaGFja2VyYXkgKDE4NDgpCiogMTUuIERhdmlkIENvcHBlcmZpZWxkIGJ5IENoYXJsZXMgRGlja2VucyAoMTg1MCkKKiAxNi4gVGhlIFNjYXJsZXQgTGV0dGVyIGJ5IE5hdGhhbmllbCBIYXd0aG9ybmUgKDE4NTApCiogMTcuIE1vYnktRGljayBieSBIZXJtYW4gTWVsdmlsbGUgKDE4NTEpCiogMTkuIFRoZSBNb29uc3RvbmUgYnkgV2lsa2llIENvbGxpbnMgKDE4NjgpCiogMjEuIE1pZGRsZW1hcmNoIGJ5IEdlb3JnZSBFbGlvdCAoMTg3MS0yKQoqIDIyLiBUaGUgV2F5IFdlIExpdmUgTm93IGJ5IEFudGhvbnkgVHJvbGxvcGUgKDE4NzUpCiogMjQuIEtpZG5hcHBlZCBieSBSb2JlcnQgTG91aXMgU3RldmVuc29uICgxODg2KQoqIDI1LiBUaHJlZSBNZW4gaW4gYSBCb2F0IGJ5IEplcm9tZSBLIEplcm9tZSAoMTg4OSkKKiAyNi4gVGhlIFNpZ24gb2YgRm91ciBieSBBcnRodXIgQ29uYW4gRG95bGUgKDE4OTApCiogMjcuIFRoZSBQaWN0dXJlIG9mIERvcmlhbiBHcmF5IGJ5IE9zY2FyIFdpbGRlICgxODkxKQoqIDI4LiBOZXcgR3J1YiBTdHJlZXQgYnkgR2VvcmdlIEdpc3NpbmcgKDE4OTEpCiogMjkuIEp1ZGUgdGhlIE9ic2N1cmUgYnkgVGhvbWFzIEhhcmR5ICgxODk1KQoqIDMwLiBUaGUgUmVkIEJhZGdlIG9mIENvdXJhZ2UgYnkgU3RlcGhlbiBDcmFuZSAoMTg5NSkKKiAzMS4gRHJhY3VsYSBieSBCcmFtIFN0b2tlciAoMTg5NykKKiAzMi4gSGVhcnQgb2YgRGFya25lc3MgYnkgSm9zZXBoIENvbnJhZCAoMTg5OSkKCkkgdXNlZCB0aGUgbW9zdCByZWNlbnQgdmVyc2lvbnMgb2YgdGhlIHRleHRzIGZyb20gW1Byb2plY3QgR3V0ZW5iZXJnXShodHRwczovL3d3dy5ndXRlbmJlcmcub3JnKSwgd2l0aCB0aGUgZm9sbG93aW5nIG1hbnVhbCBwcm9jZXNzaW5nOgoKKiBDb252ZXJ0IHRvIFVURi04CiogTEYgZW5kcyBvZiBsaW5lCioJUmVtb3ZlIFByb2plY3QgR3V0ZW5iZXJnIHRyYW5zY3JpYmVyIG1hdGVyaWFsLiAKKiBLZWVwIFRpdGxlLCBUT0MsIGJ1dCBub3QgZWRpdG9yIG5vdGVzLiAKKglSZW1vdmUgZWRpdG9yaWFsLCBidXQgbm90IGF1dGhvcmlhbCBmb290bm90ZXMuCiogUmVtb3ZlIHRyYW5zY3JpYmVyIGFubm90YXRpb25zIGZvciBpbGx1c3RyYXRpb25zLCBidXQga2VlcCBvcmlnaW5hbCBjYXB0aW9ucwoKT2YgdGhvc2UgMjMgYm9va3MsIEkgdXNlZCAyMCBmb3IgdGVzdGluZywgYW5kIGhlbGQgb3V0IDMgZm9yIHBvdGVudGlhbCBldmFsdWF0aW9uOiB0aGUgdGhpcmQgc2hvcnRlc3QgKF9UaGUgU2lnbiBvZiB0aGUgRm91cl8pLCB0aGUgbWVkaWFuIChfSnVkZSB0aGUgT2JzY3VyZV8pLCBhbmQgdGhlIHRoaXJkIGxvbmdlc3QgKF9NaWRkbGVtYXJjaF8pLiBUaGF0IG1ha2VzIGFuIDg3JS0xMyUgdGVzdC9ldmFsdWF0aW9uIHNwbGl0LiBJbiB0aGlzIHBvc3QsIEkgZGlzY3VzcyBvbmx5IHRoZSAyMCB0ZXN0aW5nIHRleHRzLgoKIyMjIFZlY3RvcnMgYW5kIGV2YWx1YXRpb24KClRvIGNyZWF0ZSB0aGUgcHBtaV9zdmQgbW9kZWxzLCBJIHVzZWQgdGhlIGh5cGVyd29yZHMgW1s2XV0oI3JlZjYpIHBhY2thZ2UsIHdoaWNoIEkgcG9ydGVkIHRvIHB5dGhvbjMuIFRvIGNyZWF0ZSB0aGUgb3RoZXIgbW9kZWxzIEkgdXNlZCBnZW5zaW0gW1s3XV0oI3JlZjcpLiBBbGwgdGhlIGV2YWx1YXRpb25zIHdlcmUgZG9uZSB1c2luZyBnZW5zaW0sIHNraXBwaW5nIHVua25vd24gd29yZHMgKGAgZHVtbXk0dW5rbm93bj1GYWxzZSBgKSwgd2l0aCBhZGRpdGlvbmFsIGN1c3RvbSBjb2RlIGZvciBjYWxjdWxhdGluZyB0aGUgc0YxIHNjb3JlIChzZWUgYmVsb3cpOgoKYCB2ZWNzLmV2YWx1YXRlX3dvcmRfcGFpcnMocGFpcnMsIHJlc3RyaWN0X3ZvY2FiPWxlbih2ZWNzLnZvY2FiKSwgY2FzZV9pbnNlbnNpdGl2ZT1UcnVlLCBkdW1teTR1bmtub3duPUZhbHNlKSBgICAKCkluIGNyZWF0aW5nIHRoZSBwcG1pX3N2ZCB2ZWN0b3JzLCB0aGVyZSBhcmUgdHdvIG90aGVyIGh5cGVycGFyYW1ldGVycyB0byBzZXQuIEZvbGxvd2luZyBbWzFdXSgjcmVmMSksIEkgc2V0IENvbnRleHQgRGlzdHJpYnV0aW9uIFNtb290aGluZyAoX2Nkc18pIHRvIDAuNzUuIEFzIHdlbGwsIHRvICBwcmV2ZW50IHN1YnNhbXBsaW5nICh3aGljaCBpbnRyb2R1Y2VzIGFuIGVsZW1lbnQgb2YgcmFuZG9tbmVzcyksIEkgc2V0IF9zdWJfIHRvIDAuIEluIGFkZGl0aW9uLCBpbiBvcmRlciB0byB1c2UgZ2Vuc2ltIHRvIGV2YWx1YXRlIHRoZSBzdmRfcHBtaSB2ZWN0b3JzIGNyZWF0ZWQgYnkgaHlwZXJ3b3JkcywgdGhleSBmaXJzdCBoYWQgdG8gYmUgY29udmVydGVkIHRvIHRoZSAgd29yZDJ2ZWMgZm9ybWF0LgoKQXMgZGlzY3Vzc2VkIGluIHRoZSBbZXZhbHVhdGlvbiBtZWFzdXJlXSh3dmVjc19ldmFsdWF0aW9uX21lYXN1cmUuaHRtbCkgcG9zdCwgc21hbGwgY29ycG9yYSBkbyBub3QgY29udGFpbiBhbGwgdGhlIHdvcmRzIG9mIHRoZSBldmFsdWF0aW9uIHNldHMsIHdoaWNoIGdyZWF0bHkgYWZmZWN0cyByZWNhbGwgYW5kIHdoaWNoIG1ha2VzIHRoZSBTcGVhcm1hbiAkXHJobyQgbWVhc3VyZSBvZiBjb3JyZWxhdGlvbiBsZXNzIHVzZWZ1bCBvbiBpdHMgb3duLiBJbiBvcmRlciB0byBoYXZlIGEgc2luZ2xlIG1lYXN1cmUgd2hpY2ggY29tYmluZXMgcmVjYWxsIGFuZCBTcGVhcm1hbiAkXHJobyQsIHdlIHVzZSB0aGUgYW5hbG9ndWUgb2YgdGhlIEYxIG1lYXN1cmUsIHNjYWxpbmcgJFxyaG8kICgkXHJobyckKSB0byBiZSBpbiB0aGUgcmFuZ2UgWzAsMV0gYW5kIHVzaW5nIHRoYXQgaW4gcGxhY2Ugb2YgcHJlY2lzaW9uLiBUaGUgcmVzdWx0IGlzIHRoZSBfc0YxXyBzY29yZToKCiAgJCRzRjEgPSBcZnJhY3syICogXHJobycgKiByZWNhbGx9e1xyaG8nICsgcmVjYWxsfSQkCgpCYXNlZCBvbiB0aGUgbGl0ZXJhdHVyZSBhbmQgb24gYSBzbWFsbCBwaWxvdCB0ZXN0LCBJIHRlc3RlZCB0aGVzZSBwYXJhbWV0ZXIgc2V0dGluZ3M6CgoqIG1pbl9jb3VudDogMSwgMywgNSwgMTAsIDIwCiogd2luOiAyLCA1LCAxMCwgMjAKKiBkaW06IDI1LCA1MCwgMTAwLCAyMDAsIDQwMAoKIyMgVGhlIHRlc3RpbmcKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CiMgTG9hZCBsaWJyYXJpZXMKbGlicmFyeShyZWFkcikKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKVGhlcmUgYXJlIGEgZmV3IGNhc2VzIHdoZXJlIHRoZSByZWNhbGwgaXMgMCBvciAxLCBhbmQgaGVuY2UgU3BlYXJtYW4ncyByaG8sIGFuZCBieSBleHRlbnNpb24sIHNGMSwgYXJlIG5vdCBkZWZpbmVkLiBXZSdsbCByZW1vdmUgdGhvc2UgY2FzZXMgZnJvbSBmdXJ0aGVyIGNvbnNpZGVyYXRpb24uCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQoKZXZhbF90ZXh0cyA8LSBsaXN0KCdzaWduNCcsJ2p1ZGUnLCdtaWRtYXJjaCcpCnRlc3RfbmFtZXMgPC0gbGlzdCgid3MzNTMiLCJ3czM1M19zaW1pbGFyaXR5Iiwid3MzNTNfcmVsYXRlZG5lc3MiLCJicnVuaV9tZW4iKQoKI0xvYWQgZGF0YQp0eXBlc190b2tlbnMgPC0gcmVhZF9kZWxpbSgidHlwZXNfdG9rZW5zLmNzdiIsICJcdCIsIGVzY2FwZV9kb3VibGUgPSBGQUxTRSwgdHJpbV93cyA9IFRSVUUpICU+JQogIG11dGF0ZSh0dHIgPSB0eXBlcy90b2tlbnMpCgpTVkRfc2ltX3Rlc3RzIDwtIHJlYWRfZGVsaW0oInRlc3RzZXRzX3BhcmFtZXRlcnMvU1ZELXNpbV90ZXN0cy5jc3YiLCAiXHQiLCBlc2NhcGVfZG91YmxlID0gRkFMU0UsIHRyaW1fd3MgPSBUUlVFKSAlPiUKICBtdXRhdGUobWV0aG9kPSJwcG1pX3N2ZCIpCgp3b3JkMnZlY19zaW1fdGVzdHMgPC0gcmVhZF9kZWxpbSgidGVzdHNldHNfcGFyYW1ldGVycy93b3JkMnZlYy1zaW1fdGVzdHMuY3N2IiwgIlx0IiwgZXNjYXBlX2RvdWJsZSA9IEZBTFNFLCB0cmltX3dzID0gVFJVRSkgJT4lCiAgbXV0YXRlKG1ldGhvZD0id29yZDJ2ZWMiKQoKRmFzdFRleHRfc2ltX3Rlc3RzIDwtIHJlYWRfZGVsaW0oInRlc3RzZXRzX3BhcmFtZXRlcnMvRmFzdFRleHQtc2ltX3Rlc3RzLmNzdiIsICJcdCIsIGVzY2FwZV9kb3VibGUgPSBGQUxTRSwgdHJpbV93cyA9IFRSVUUpICU+JQogIG11dGF0ZShtZXRob2Q9IkZhc3RUZXh0IikKCnNpbV90ZXN0cyA8LSByYmluZChTVkRfc2ltX3Rlc3RzLHdvcmQydmVjX3NpbV90ZXN0cyxGYXN0VGV4dF9zaW1fdGVzdHMpICU+JQogIGlubmVyX2pvaW4odHlwZXNfdG9rZW5zKSAlPiUKICByZW5hbWUobWluX2NvdW50PXRocmVzaCkgJT4lIAogIGZpbHRlcighIGlzLm5hbihzRjEpKQoKCnNpbV9ldmFscyA8LSBzaW1fdGVzdHMgJT4lCiAgZmlsdGVyKHRleHQgJWluJSBldmFsX3RleHRzKQoKc2ltX3Rlc3RzIDwtIHNpbV90ZXN0cyAlPiUKICBmaWx0ZXIoISh0ZXh0ICVpbiUgZXZhbF90ZXh0cykpCgpybShTVkRfc2ltX3Rlc3RzLHdvcmQydmVjX3NpbV90ZXN0cyxGYXN0VGV4dF9zaW1fdGVzdHMpCgpgYGAKCldlJ2xsIGxvb2sgYXQgdGhlIG1pbmltdW0gY291bnQgZmlyc3QuCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIHdpZHRoPTEyfQpzaW1fdGVzdHMgJT4lCiAgZ2dwbG90KCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBsYWJzKHRpdGxlPXBhc3RlKCJtaW5fY291bnQgdnMgc0YxIikpICsKICBnZW9tX3BvaW50KGFlcyhtaW5fY291bnQsc0YxLGNvbG9yPXRleHQpKSArCiAgZ2VvbV9saW5lKGFlcyhtaW5fY291bnQsc0YxLGNvbG9yPXRleHQpKSArCiAgZmFjZXRfd3JhcCh0ZXN0c2V0IH4gbWV0aG9kLCBuY29sPTMpCmBgYAoKVGhlIGJlc3Qgc0YxIHNjb3JlcyBhcmUgYWx3YXlzIHdpdGggYSBtaW5fY291bnQgb2YgMSwgaS5lLiBpbmNsdWRpbmcgYWxsIHdvcmRzLiBUaGlzIGlzIGluIGNvbnRyYXN0IHRvIHRoZSBzdGFuZGFyZCBwcmFjdGljZSB3aXRoIGxhcmdlIGNvcnBvcmEgb2YgZWxpbWluYXRpbmcgbG93IGZyZXF1ZW5jeSB3b3JkcyAoYSBtaW5fY291bnQgb2YgMTAwIGlzIGNvbW1vbikuIE9uZSBwb3NzaWJsZSByZWFzb24gZm9yIHRoZSBzdWNjZXNzIG9mIGluY2x1ZGluZyBhbGwgdGhlIHdvcmRzIGlzIHRoYXQgaXQgZ2l2ZXMgdXMgbW9yZSBpbmZvcm1hdGlvbiB0byB3b3JrIHdpdGguCgpGcm9tIGhlcmUgb24sIHdlJ2xsIHdvcmsgb25seSB3aXRoIHRoZSBzdWJzZXQgb2YgcGFyYW1ldGVyIHNldHRpbmdzIHdoZXJlIG1pbl9jb3VudCBpcyAxLgoKYGBge3J9CnNpbV90ZXN0c18xIDwtIHNpbV90ZXN0cyAlPiUgZmlsdGVyKG1pbl9jb3VudD09MSkKYGBgCgoKTmV4dCB3ZSBjYW4gbG9vayBhdCB0aGUgd2luZG93IGFuZCBkaW1lbnNpb24gc2l6ZXMsIHJlc3RyaWN0aW5nIG91ciBhdHRlbnRpb24gdG8gdGhlIHBwbWlfc3ZkIG1ldGhvZC4gVGhlIGJlc3Qgc2NvcmVzIGFyZSBkaXN0cmlidXRlZCBhY3Jvc3Mgd2luICsgZGltIGNvbWJpbmF0ZXMuIFRoZSBzYW1lIGV4YWN0IGRpc3RyaWJ1dGlvbiBob2xkcyBmb3IgYWxsIHRoZSB0ZXN0c2V0cy4KCmBgYHtyfQpzaW1fdGVzdHNfMSAlPiUgZmlsdGVyKG1ldGhvZD09J3BwbWlfc3ZkJykgJT4lCiAgZ3JvdXBfYnkodGVzdHNldCx0ZXh0KSAlPiUKICBmaWx0ZXIoc0YxPT1tYXgoc0YxKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KHRlc3RzZXQsd2luLGRpbSkgJT4lCiAgc3VtbWFyaXNlKGNvdW50cz1uKCkpICU+JQogIHNwcmVhZCh0ZXN0c2V0LGNvdW50cykgJT4lCiAgYXJyYW5nZSgtd3MzNTMsd2luLGRpbSkKYGBgCgoKVGhlIGFtb3VudCBvZiB2YXJpYXRpb24gaW4gc0YxIHNjb3JlIGZvciBhIGdpdmVuIHRleHQgaXMgZmFpcmx5IHNtYWxsLCBib3RoIGFjcm9zcyB0ZXN0c2V0cyBhbmQgYWNyb3NzIHBhcmFtZXRlciBzZXR0aW5ncy4gVGhlIGdyZWF0ZXN0IHZhcmlhdGlvbiBpbiBzRjEgc2NvcmVzIGlzIGFjcm9zcyBkaWZmZXJlbnQgdGV4dHMuCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQpyYW5nZXMgPC0gc2ltX3Rlc3RzXzEgJT4lIAogIGdyb3VwX2J5KHRlc3RzZXQsdGV4dCkgJT4lCiAgbXV0YXRlKGJlc3Q9bWF4KHNGMSksIHdvcnN0PW1pbihzRjEpLCByYW5nZT1iZXN0LXdvcnN0KSAlPiUgCiAgc2VsZWN0KHRlc3RzZXQsdGV4dCxiZXN0LHdvcnN0LHJhbmdlKSAlPiUKICB1bmlxdWUoKQoKYm0gPC0gZmlsdGVyKHJhbmdlcywgdGVzdHNldD09J2JydW5pX21lbicpICU+JSBhcnJhbmdlKHJhbmdlKQpyYW5nZXMkdGV4dCA8LSBmYWN0b3IocmFuZ2VzJHRleHQsIGxldmVscyA9IGJtJHRleHRbb3JkZXIoLWJtJHJhbmdlKV0pCgpyYW5nZXMgJT4lCiAgZ2dwbG90KCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBsYWJzKHRpdGxlPSJSYW5nZXMgb2Ygc0YxIHNjb3JlcyBieSB0ZXN0c2V0IikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT00NSxoanVzdD0xLHZqdXN0PTEpKSArCiAgZ2VvbV9saW5lcmFuZ2UoYWVzKHg9dGV4dCx5bWluPXdvcnN0LHltYXg9YmVzdCxjb2xvcj10ZXh0KSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofnRlc3RzZXQpCmBgYAoKYGBge3J9CnJhbmdlcyAlPiUgCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KHRleHQpICU+JQogIG11dGF0ZShiZXN0X3NGMT1tYXgoYmVzdCksd29yc3Rfc0YxPW1pbih3b3JzdCksc0YxX3JhbmdlPWJlc3Rfc0YxLXdvcnN0X3NGMSkgJT4lCiAgc2VsZWN0KHRleHQsYmVzdF9zRjEsd29yc3Rfc0YxLHNGMV9yYW5nZSkgJT4lIAogIHVuaXF1ZSgpICU+JQogIGFycmFuZ2UoLXNGMV9yYW5nZSkKYGBgCgoKV2UgY2FuIHVzZSBsaW5lYXIgcmVncmVzc2lvbiBmb3IgYW5vdGhlciB0YWtlIG9uIHRoZSBpc3N1ZSBvZiBwYXJhbWV0ZXJzIGFzIHByZWRpY3RvcnMgb2YgdGhlIHNGMSBzY29yZS4gV2hlbiB3ZSBkbyB0aGF0LCB3ZSBzZWUgdGhhdCB3aW4gYW5kIGRpbSBhcmUgbm90IHNpZ25pZmljYW50IHByZWRpY3RvcnMgb2YgdGhlIHNGMSBzY29yZSwgYW5kIG5laXRoZXIgaXMgdGhlIHRlc3RzZXQuIE9ubHkgdGhlIGluaGVyZW50IHByb3BlcnRpZXMgb2YgdGhlIHRleHQgKHR5cGUsIHRva2VucywgYW5kIHRoZWlyIHJhdGlvKSBhcmUgc2lnbmlmaWNhbnQgcHJlZGljdG9ycy4KCgpgYGB7cn0KbSA8LSBsbShzRjEgfiB3aW4qZGltICsgdG9rZW5zICsgdHlwZXMgKyB0dHIgKyB0ZXN0c2V0LAogICAgICAgIGRhdGE9ZmlsdGVyKHNpbV90ZXN0c18xLG1ldGhvZD09J3BwbWlfc3ZkJykpCgpwcmludChhbm92YShtKSkKYGBgCgpgYGB7ciBtZXNzc2FnZT1GQUxTRSwgZXZhbD1GQUxTRX0KcHRzIDwtIGRhdGEuZnJhbWUocHJlZGljdGVkPWZpdHRlZChtKSwgcmVzaWR1YWxzPXJlc2lkdWFscyhtKSkKCmdncGxvdChwdHMpICsgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgdnMgcmVzaWR1YWxzIikgKyAKICBnZW9tX3BvaW50KGFlcyhwcmVkaWN0ZWQscmVzaWR1YWxzKSkKCmBgYAoKV2UgbG9vayBhdCBob3cgdG9rZW5zIGFuZCB0eXBlcyBhbmQgdGhlaXIgcmF0aW8gYXJlIHJlbGF0ZWQgdG8gc0YxLiBGb3Igc2ltcGxpY2l0eSwgd2UnbGwgbGltaXQgb3Vyc2VsdmVzIGp1c3QgdG8gYnJ1bmlfbWVuLgoKYGBge3IgZmlnLmhlaWdodD02fQoKc2ltX3Rlc3RzXzEgJT4lIGZpbHRlcih0ZXN0c2V0PT0nYnJ1bmlfbWVuJykgJT4lCiAgZ2dwbG90KCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBsYWJzKHRpdGxlPXBhc3RlKCJ0b2tlbnMgdnMgc0YxIGZvciBicnVuaV9tZW4iKSkgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArCiAgZ2VvbV9wb2ludChhZXModG9rZW5zLHNGMSxjb2xvcj10ZXh0KSkgKwogIGdlb21fc21vb3RoKGFlcyh0b2tlbnMsc0YxKSwgY29sb3I9J29yYW5nZScsIG1ldGhvZD0nbG9lc3MnKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9Nn0Kc2ltX3Rlc3RzXzEgJT4lIAogIGZpbHRlcih0ZXN0c2V0PT0nYnJ1bmlfbWVuJykgJT4lCiAgZ2dwbG90KCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBsYWJzKHRpdGxlPXBhc3RlKCJ0eXBlcyB2cyBzRjEgZm9yIGJydW5pX21lbiIpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsKICBnZW9tX3BvaW50KGFlcyh0eXBlcyxzRjEsY29sb3I9dGV4dCkpICsKICBnZW9tX3Ntb290aChhZXModHlwZXMsc0YxKSwgY29sb3I9J29yYW5nZScsIG1ldGhvZD0nbG9lc3MnKQpgYGAKClRoZSBjb25uZWN0aW9uIGJldHdlZW4gdHlwZXMgKHZvY2FidWxhcnkgc2l6ZSkgYW5kIHNGMSBzY29yZSBpcyBub3Qgc3VycHJpc2luZywgc2luY2UgdGhlIHZvY2FidWxhcnkgc2l6ZSBhZmZlY3RzIHJlY2FsbCwgYW5kIHJlY2FsbCBpcyB0aGUgZG9taW5hbnQgYXNwZWN0IG9mIHRoZSBzRjEgc2NvcmUgZm9yIGdvb2Qgc2NvcmVzIChyZW1lbWJlciB0aGF0IHdlJ3JlIG9ubHkgY29uc2lkZXJpbmcgc2V0dGluZ3Mgd2l0aCBhIG1pbl9jb3VudCBvZiAxLCBzaW5jZSB0aG9zZSBwcm9kdWNlZCB0aGUgYmVzdCBzY29yZXMpLgoKYGBge3IgZmlnLmhlaWdodD02fQpzaW1fdGVzdHNfMSAlPiUgCiAgZmlsdGVyKHRlc3RzZXQ9PSdicnVuaV9tZW4nKSAlPiUKICBnZ3Bsb3QoKSArIHRoZW1lX2NsYXNzaWMoKSArIGxhYnModGl0bGU9cGFzdGUoInJlY2FsbCB2cyBzRjEgZm9yIGJydW5pX21lbiIpKSArCiAgZ2VvbV9wb2ludChhZXMocmVjYWxsLHNGMSxjb2xvcj10ZXh0LHNoYXBlPXRlc3RzZXQpKSArCiAgZ2VvbV9zbW9vdGgoYWVzKHJlY2FsbCxzRjEpLCBjb2xvcj0nb3JhbmdlJywgbWV0aG9kPSdsb2VzcycpCmBgYAoKSW4gZmFjdCwgZm9yIHNtYWxsIGNvcnBvcmEsIHRoZSBTcGVhcm1hbiAkXHJobyQgX2Fsb25lXyBpcyBub3QgYSBncmVhdCBpbmRpY2F0b3Igb2YgcXVhbGl0eS4KCmBgYHtyIGZpZy5oZWlnaHQ9Nn0Kc2ltX3Rlc3RzXzEgJT4lIAogIGZpbHRlcih0ZXN0c2V0PT0nYnJ1bmlfbWVuJykgJT4lCiAgZ2dwbG90KCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBsYWJzKHRpdGxlPXBhc3RlKCJzcGVhcm1hbiB2cyBzRjEgZm9yIGJydW5pX21lbiIpKSArCiAgZ2VvbV9wb2ludChhZXMoc3BlYXJtYW4sc0YxLGNvbG9yPXRleHQsc2hhcGU9dGVzdHNldCkpCmBgYAoKRmluYWxseSB3ZSBjYW4gbG9vayBhdCB0aGUgdHlwZS90b2tlbiByYXRpbyB2ZXJzdXMgdGhlIHNGMSBzY29yZS4gV2Ugc2VlIHRoYXQgdGhlIHRyZW5kIGhhcyBhIGRpc3RpbmN0IHJpc2UgdW50aWwgYSB0dHIgb2YgYWJvdXQgMC4wNiwgYW5kIHRoZW4gYW4gYWxtb3N0IGxpbmVhciBkZWNsaW5lLgoKYGBge3IgZmlnLmhlaWdodD02fQpzaW1fdGVzdHNfMSAlPiUgCiAgZmlsdGVyKHRlc3RzZXQ9PSdicnVuaV9tZW4nKSAlPiUKICBnZ3Bsb3QoKSArIHRoZW1lX2NsYXNzaWMoKSArIGxhYnModGl0bGU9cGFzdGUoInR5cGUvdG9rZW4gcmF0aW8gdnMgc0YxIGZvciBicnVuaV9tZW4iKSkgKwogIGdlb21fcG9pbnQoYWVzKHR0cixzRjEsY29sb3I9dGV4dCkpICsKICBnZW9tX3Ntb290aChhZXModHRyLHNGMSksIGNvbG9yPSdvcmFuZ2UnLCBtZXRob2Q9J2xvZXNzJykKYGBgCgpPZiB0aGUgdGhvc2UgdGhyZWUgaW5oZXJlbnQgcHJvcGVydGllcywgdGhlIG51bWJlciBvZiB0b2tlbnMgKGkuZS4gdm9jYWJ1bGFyeSBzaXplKSBzaG93cyB0aGUgbW9zdCBzdHJhaWdodGZvd2FyZCBjb25uZWN0aW9uIHdpdGggc0YxLgoKR2l2ZW4gdGhhdCB3aW4gYW5kIGRpbSBfYXJlXyBpbXBvcnRhbnQgZm9yIGxhcmdlIGNvcnBvcmEsIHdlIHNob3VsZCB3b25kZXIgYXQgd2hhdCBwb2ludCB0aGV5IF9kb18gYmVjb21lIHNpZ25pZmljYW50LiBXZSBjYW4gZGl2aWRlIG91ciB0ZXN0c2V0IGludG8gMyByb3VnaCBncm91cHMgYmFzZWQgb24gdGhlaXIgdm9jYWJ1bGFyeSBzaXplLCB1c2luZyA4MDAwIGFuZCAxMjAwMCB0b2tlbnMgYXMgdGhlIGRpdmlkaW5nIGxpbmVzOgoKYGBge3J9CnRleHRzX2dyb3VwZWQgPC0gdHlwZXNfdG9rZW5zICU+JSAKICBmaWx0ZXIoISh0ZXh0ICVpbiUgZXZhbF90ZXh0cykpICU+JQogIG11dGF0ZShzaXplX2dyb3VwPWlmX2Vsc2UodHlwZXM8ODAwMCwic21hbGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZfZWxzZSh0eXBlczwxMjAwMCwibWVkaXVtIiwibGFyZ2UiKSkpIAoKdGV4dHNfZ3JvdXBlZCAlPiUKICBzZWxlY3Qoc2l6ZV9ncm91cCx0ZXh0LHR5cGVzKSAlPiUKICBhcnJhbmdlKHR5cGVzLHRleHQpCmBgYAoKCldoZW4gd2UgZG8gdGhlIHJlZ3Jlc3Npb24gbW9kZWxzLCBpdCB0dXJucyBvdXQgbmVpdGhlciB3aW4gbm9yIGRpbSBhcmUgIHNpZ25pZmljYW50IGZvciB0aGUgc21hbGwgdGV4dHM7IHdpbiBpcyBfc2xpZ2h0bHlfIChwPDAuMDUpIHNpZ25pZmljYW50IGZvciB0aGUgbWVkaXVtIHRleHRzOyBhbmQgYm90aCB3aW4gYW5kIGRpbSBhcmUgaGlnaGx5IHNpZ25pZmljYW50IGZvciB0aGUgbGFyZ2UgdGV4dHMuIAoKYGBge3J9CiNzZWUgaWYgd2luL2RpbSBhcmUgc2lnbmlmY2FudCB3aXRoIDw4MDAwIHR5cGVzCm04ayA8LSBsbShzRjEgfiB3aW4qZGltICsgdG9rZW5zICsgdHlwZXMgKyB0dHIgKyB0ZXN0c2V0LAogICAgICAgIGRhdGE9ZmlsdGVyKHNpbV90ZXN0c18xLG1ldGhvZD09J3BwbWlfc3ZkJyx0eXBlczw4MDAwKSkKCnByaW50KGFub3ZhKG04aykpCmBgYAoKYGBge3J9CiNzZWUgaWYgd2luL2RpbSBhcmUgc2lnbmlmY2FudCB3aGVuIFs4MDAwLDEyMDAwXSB0eXBlcwptOF8xMmsgPC0gbG0oc0YxIH4gd2luKmRpbSArIHRva2VucyArIHR5cGVzICsgdHRyICsgdGVzdHNldCwKICAgICAgICBkYXRhPWZpbHRlcihzaW1fdGVzdHNfMSxtZXRob2Q9PSdwcG1pX3N2ZCcsdHlwZXM+ODAwMCAmIHR5cGVzPDEyMDAwKSkKCnByaW50KGFub3ZhKG04XzEyaykpCmBgYAoKYGBge3J9CiNzZWUgaWYgd2luL2RpbSBhcmUgc2lnbmlmY2FudCB3aGVuID4xMjAwMCB0eXBlcwoKc2ltX3Rlc3RzXzEyayA8LSBmaWx0ZXIoc2ltX3Rlc3RzXzEsbWV0aG9kPT0ncHBtaV9zdmQnLHR5cGVzPjEyMDAwKQoKbTEyayA8LSBsbShzRjEgfiB3aW4qZGltICsgdG9rZW5zICsgdHlwZXMgKyB0dHIgKyB0ZXN0c2V0LAogICAgICAgIGRhdGE9c2ltX3Rlc3RzXzEyaykKCnByaW50KGFub3ZhKG0xMmspKQpgYGAKCiMjIyBUaGUgYmVzdCBwYXJhbWV0ZXJzIGZvciB0aGUgZm91ciBsYXJnZXN0IGNvcnBvcmEKYGBge3J9CnNpbV90ZXN0c18xMmsgJT4lCiAgZ3JvdXBfYnkodGV4dCkgJT4lCiAgZmlsdGVyKHNGMT09bWF4KHNGMSkpICU+JQogIHNlbGVjdCh0ZXh0LHRva2VucyxzRjEsc3BlYXJtYW4scmVjYWxsLHdpbixkaW0pICU+JQogIGFycmFuZ2UoLXRva2VucykKYGBgCkV2ZW4gdGhvdWdoIHdpbiBhbmQgZGltIGFyZSBzaWduaWZpY2FudCwgdGhlcmUgaXMgbm8gb2J2aW91cyBwYXR0ZXJuLiBUaGVyZSBhcmUgb25seSA0ICJsYXJnZSIgdGV4dHM7IGV2ZW4gc28sIHRoZSBib3VuZGFyeSBvZiB3aGVyZSB3aW4gYW5kIGRpbSBiZWNvbWUgc2lnbmlmaWNhbnQsIGFzIHdlbGwgYXMgYW55IHBhdHRlcm5zIGluIHRoZWlyIHZhbHVlcywgYXJlIHdvcnRoIGV4cGxvcmluZyBmdXJ0aGVyLgoKIyMgQ29tcGFyaW5nIG1ldGhvZHMKCkFzIEkgbWVudGlvbmVkIGF0IHRoZSBiZWdpbm5pbmcsIEkgaGF2ZW4ndCBkb25lIHRoZSBmdWxsIGNvbXBsZW1lbnQgb2YgcGFyYW1ldGVycyBhY29yc3MgYWxsIHRoZSB0ZXh0cyBmb3Igd29yZDJ2ZWMgYW5kIEZhc3RUZXh0LiBIb3dldmVyLCB3ZSBjYW4gY29tcGFyZSBfVmFuaXR5IEZhaXJfIChhIGxvbmcgdGV4dCkgYW5kIF9IZWFydCBvZiBEYXJrbmVzc18gKGEgc2hvcnQgdGV4dCkgYWNyb3NzIGFsbCAzIG1ldGhvZHMuIFdlIGFscmVhZHkgc2F3IGFib3ZlIHRoZSBtaW5fY291bnQ9MSBpcyBhbHdheXMgdGhlIGJlc3QgYWNyb3NzIHRoZSBtZXRob2RzLCBzbyBhZ2Fpbiwgd2UnbGwgbGltaXQgb3VyIGF0dGVudGlvbiB0byB0aGF0IHN1YnNldC4KCmBgYHtyfQpzaW1zX3ZmaGQgPC0gc2ltX3Rlc3RzICU+JSBmaWx0ZXIodGV4dD09J3ZmYWlyJyB8IHRleHQ9PSdoZWFydGQnLCBtaW5fY291bnQ9PTEpCmBgYAoKRG9pbmcgYSBzaW1wbGlmaWVkIHJlZ3Jlc3Npb24gbW9kZWwsIHdlIHNlZSB0aGF0IF9tZXRob2RfIGlzIHNpZ25pZmljYW50LCBhdCBsZWFzdCB3aXRoIHRoZXNlIHR3byB0ZXh0cywgYXMgaXMgX2RpbV8uCgoKYGBge3J9Cm1fdmZoZCA8LSBsbShzRjEgfiB3aW4qZGltICsgIHRva2VucyArIHRlc3RzZXQgKyBtZXRob2QsCiAgICAgICAgZGF0YT1zaW1zX3ZmaGQpCgpwcmludChhbm92YShtX3ZmaGQpKQpgYGAKClRha2luZyBqdXN0IHRoZSBiZXN0IHNGMSBzY29yZXMgYW5kIHVzaW5nIHRoZSBicnVuaV9tZW4gdGVzdHNldCBhcyBhbiBleGFtcGxlLCB3ZSBoYXZlIHRoZSBmb2xsb3dpbmcgdGFibGUuIAoKIyMjIENvbXBhcmluZyBtZXRob2RzIGZvciBfVmFuaXR5IEZhaXJfIGFuZCBfSGVhcnQgb2YgRGFya25lc3NfCmBgYHtyIGZpZy5oZWlnaHQ9M30Kc2ltc192ZmhkICU+JSBmaWx0ZXIodGVzdHNldD09ImJydW5pX21lbiIpICU+JQogIGdyb3VwX2J5KG1ldGhvZCx0ZXh0KSAlPiUKICBmaWx0ZXIoc0YxPT1tYXgoc0YxKSkgJT4lCiAgc2VsZWN0KHRleHQsc0YxLG1ldGhvZCxkaW0pICU+JQogIGFycmFuZ2UoLXNGMSxtZXRob2QsdGV4dCxkaW0pCmBgYApXaXRoIHJlc3BlY3QgdG8gbWV0aG9kLCBwcG1pX3N2ZCBoYXMgaXRzIGhpZ2hlc3Qgc0YxIHNjb3JlIGZvciB0aGUgc21hbGwgX0hlYXJ0IG9mIERhcmtuZXNzXywgYW5kIGl0cyBsb3dlc3QgZm9yIF9WYW5pdHkgRmFpcl8sIHdoaWxlIEZhc3RUZXh0IGlzIHRoZSBvcHBvc2l0ZS4gCgpGb3IgZGltZW5zaW9ucywgRmFzdFRleHQgaGFzIGl0cyBoaWdoZXN0IHNGMSBzY29yZSB3aXRoIGEgc21hbGwgbnVtYmVyIG9mIGRpbWVuc2lvbnM7IHBwbWlfc3ZkIGhhcyBpdHMgaGlnaGVzdCBzRjEgc2NvcmUgd2l0aCBhIGxhcmdlciBudW1iZXIgb2YgZGltZW5zaW9uczsgYW5kIHdvcmQydmVjIGlzIG1peGVkLgoKT2YgY291cnNlLCBpdCB3b3VsZCBiZSBnb29kIHRvIGhhdmUgYSBmdWxsIGNvbXBsZW1lbnQgb2Ygc0YxIHNjb3JlcyBmb3IgZGlmZmVyZW50IGNvcnB1cyBzaXplcywgYnV0IGF0IHRoZSB2ZXJ5IGxlYXN0IHdlIGNhbiB0ZWxsIGFscmVhZHkgdGhhdCB0aGVyZSBpcyBubyBjb21tb25hbGl0eSBpbiB0aGUgcGFyYW1ldGVyIHNldHRpbmdzIGFjcm9zcyB0aGUgbWV0aG9kcy4KCiMjIERpc2N1c3Npb24gYW5kIENvbmNsdXNpb24KCkluIHRoZSBzcGlyaXQgb2YgW1sxXV0oI3JlZjEpLCBoZXJlIGFyZSBhIGZldyAibGVzc29ucyBsZWFybmVkIjoKCiogSW5jbHVkaW5nIGFsbCB0aGUgd29yZHMgKG1pbl9jb3VudD0xKSBpcyB0aGUgYmVzdCBzdHJhdGVneSBmb3IgYWxsIHNtYWxsIGNvcnBvcmEKKiBGb3IgdGhlIHNtYWxsZXN0IGFuZCByZWxhdGl2ZWx5IHNtYWxsIGNvcnBvcmEgKDwgMTIwMDAgX3R5cGVzXyksIHRoZSBjaG9pY2VzIG9mIHdpbmRvdyBzaXplIGFuZCBudW1iZXIgb2YgZGltZW5zaW9ucyBhcmUgbm90IHNpZ25pZmljYW50CiogVGhlIGNob2ljZSBvZiB3aGljaCBtZXRob2QgdG8gdXNlIHdpbGwgZGVwZW5kIHBhcnRseSBvbiBjb3JwdXMgc2l6ZSwgYnV0IG1vcmUgb24gY29tcHV0YXRpb25hbCBjb25zdHJhaW50cyBpZiB0aGUgY2VudHJvaWQgdGVjaG5pcXVlIGlzIHVzZWQuIEluIGFkZGl0aW9uLCB0ZXN0aW5nIG9uIHRoZSBzcGVjaWZpYyBjb3JwdXMgb2YgaW50ZXJlc3QgaXMgcmVsZXZhbnQuCgpOb3cgdGhhdCB3ZSd2ZSBnb25lIHRocm91Z2ggdGhlc2UgZXZhbHVhdGlvbnMgd2l0aCB0aGUgd29yZCBzaW1pbGFyaXR5IHRlc3RzZXRzLCBpdCBpcyB3b3J0aCBxdWVzdGlvbmluZyBob3cgdXNlZnVsIHRoZXNlIHRlc3RzZXRzIGFyZSBmb3IgZXZhbHVhdGluZyBzbWFsbCBjb3Jwb3JhLiBGaXJzdCwgd2Ugc2F3IGFib3ZlIHRoYXQgdGhlIHZvY2FidWxhcnkgc2l6ZSBpcyB2ZXJ5IHN0cm9uZ2x5IGFzc29jaWF0ZWQgd2l0aCB0aGUgc0YxIHNjb3JlLiBTZWNvbmQsIHdlIGNhbiBhc2sgd2hldGhlciB0aGVzZSB0ZXN0c2V0cyBhcmUgZXZlbiByZWxldmFudCwgZXNwZWNpYWxseSBpbiB0aGUgY29udGV4dCBvZiBpbnF1aXJpZXMgaW50byBhdXRob3JzJyB1c2Ugb2Ygd29yZHMuIAoKVGhlIG5leHQgcG9zdCwgW0V4cGxvcmluZyBzaW1pbGFyaXRpZXNdKHd2ZWNzX2V4cGxvcmluZ19zaW1pbGFyaXRpZXMubmIuaHRtbCksIHdpbGwgZXhwbG9yZSBhIGRpZmZlcmVudCBwZXJzcGVjdGl2ZSBvbiBwYXJhbWV0ZXIgc2V0dGluZ3MuCgpbQmFjayB0byB0aGUgaW50cm9kdWN0aW9uXSh3dmVjc19pbnRyby5odG1sKQoKT3RoZXIgcG9zdHMKCiogW1N0YWJpbGl6aW5nIHJhbmRvbW5lc3NdKHd2ZWNzX3JhbmRvbV9maXguaHRtbCkKKiBbQSBuZXcgbWVhc3VyZSBmb3IgZXZhbHVhdGlvbl0od3ZlY3NfZXZhbHVhdGlvbl9tZWFzdXJlLmh0bWwpCiogW0V4cGxvcmluZyBzaW1pbGFyaXRpZXNdKHd2ZWNzX2V4cGxvcmluZ19zaW1pbGFyaXRpZXMubmIuaHRtbCkKKiBbVmlzdWFsaXppbmcgd29yZCB2ZWN0b3JzXSh3dmVjc192aXN1YWxpemF0aW9uLmh0bWwpCgojIyBSZWZlcmVuY2VzCgo8c3BhbiBpZD0icmVmMSI+WzFdPC9zcGFuPiBPbWVyIExldnksIFlvYXYgR29sZGJlcmcsIGFuZCBJZG8gRGFnYW4uIDIwMTUuIEltcHJvdmluZyBEaXN0cmlidXRpb25hbCBTaW1pbGFyaXR5IHdpdGggTGVzc29ucyBMZWFybmVkIGZyb20gV29yZCBFbWJlZGRpbmdzLiBUcmFuc2FjdGlvbnMgb2YgdGhlIEFzc29jaWF0aW9uIGZvciBDb21wdXRhdGlvbmFsIExpbmd1aXN0aWNzLCB2b2wuIDMsIHBwLiAyMTHigJMyMjUuCgo8c3BhbiBpZD0icmVmMiI+WzJdPC9zcGFuPiBMZXYgRmlua2Vsc3RlaW4sIEV2Z2VuaXkgR2Ficmlsb3ZpY2gsIFlvc3NpIE1hdGlhcywgRWh1ZCBSaXZsaW4sIFphY2ggU29sYW4sIEdhZGkgV29sZm1hbiwgYW5kIEV5dGFuIFJ1cHBpbi4gMjAwMi4gUGxhY2luZyBzZWFyY2ggaW4gY29udGV4dDogVGhlIGNvbmNlcHQgcmV2aXNpdGVkLiBBQ00gVHJhbnNhY3Rpb25zIG9uIEluZm9ybWF0aW9uIFN5c3RlbXMsIDIwKDEpOjExNuKAkzEzMS4KCjxzcGFuIGlkPSJyZWYzIj5bM108L3NwYW4+IFRvcnN0ZW4gWmVzY2gsIENocmlzdG9mIE3DvGxsZXIsIGFuZCBJcnluYSBHdXJldnljaC4gMjAwOC4gVXNpbmcgd2lrdGlvbmFyeSBmb3IgY29tcHV0aW5nIHNlbWFudGljIHJlbGF0ZWRuZXNzLiBJbiBQcm9jZWVkaW5ncyBvZiB0aGUgMjNyZCBOYXRpb25hbCBDb25mZXJlbmNlIG9uIEFydGlmaWNpYWwgSW50ZWxsaWdlbmNlIC0gVm9sdW1lIDIsIEFBQUnigJkwOCwgODYx4oCTODY2LiBBQUFJIFByZXNzLgoKPHNwYW4gaWQ9InJlZjQiPls0XTwvc3Bhbj4gRW5la28gQWdpcnJlLCBFbnJpcXVlIEFsZm9uc2VjYSwgS2VpdGggSGFsbCwgSmFuYSBLcmF2YWxvdmEsIE1hcml1cyBQYXNjYSwgYW5kIEFpdG9yIFNvcm9hLiAyMDA5LiBBIHN0dWR5IG9uIHNpbWlsYXJpdHkgYW5kIHJlbGF0ZWRuZXNzIHVzaW5nIGRpc3RyaWJ1dGlvbmFsIGFuZCB3b3JkbmV0LWJhc2VkIGFwcHJvYWNoZXMuIEluIFByb2NlZWRpbmdzIG9mIEh1bWFuIExhbmd1YWdlIFRlY2hub2xvZ2llczogVGhlIDIwMDkgQW5udWFsIENvbmZlcmVuY2Ugb2YgdGhlIE5vcnRoIEFtZXJpY2FuIENoYXB0ZXIgb2YgdGhlIEFzc29jaWF0aW9uIGZvciBDb21wdXRhdGlvbmFsIExpbmd1aXN0aWNzLCBwYWdlcyAxOeKAkzI3LCBCb3VsZGVyLCBDb2xvcmFkbywgSnVuZS4gQXNzb2NpYXRpb24gZm9yIENvbXB1dGF0aW9uYWwgTGluZ3Vpc3RpY3MuCgo8c3BhbiBpZD0icmVmNSI+WzVdPC9zcGFuPiBFbGlhIEJydW5pLCBHZW1tYSBCb2xlZGEsIE1hcmNvIEJhcm9uaSwgYW5kIE5hbSBLaGFuaCBUcmFuLiAyMDEyLiBEaXN0cmlidXRpb25hbCBzZW1hbnRpY3MgaW4gdGVjaG5pY29sb3IuIEluIFByb2NlZWRpbmdzIG9mIHRoZSA1MHRoIEFubnVhbCBNZWV0aW5nIG9mIHRoZSBBc3NvY2lhdGlvbiBmb3IgQ29tcHV0YXRpb25hbCBMaW5ndWlzdGljcyAoVm9sdW1lIDE6IExvbmcgUGFwZXJzKSwgcGFnZXMgMTM24oCTMTQ1LCBKZWp1IElzbGFuZCwgS29yZWEsIEp1bHkuIEFzc29jaWF0aW9uIGZvciBDb21wdXRhdGlvbmFsIExpbmd1aXN0aWNzLgoKPHNwYW4gaWQ9InJlZjYiPls2XTwvc3Bhbj4gSHlwZXJ3b3JkczogaHR0cHM6Ly9iaXRidWNrZXQub3JnL29tZXJsZXZ5L2h5cGVyd29yZHMsIHB1Ymxpc2hlZCBhcyBbMV0oI3JlZjEpCgo8c3BhbiBpZD0icmVmNyI+WzddPC9zcGFuPiBHZW5zaW06IGh0dHBzOi8vcmFkaW1yZWh1cmVrLmNvbS9nZW5zaW0vLCBwdWJsaXNoZWQgYXM6IFNvZnR3YXJlIEZyYW1ld29yayBmb3IgVG9waWMgTW9kZWxsaW5nIHdpdGggTGFyZ2UgQ29ycG9yYS4gUmFkaW0gxZhlaMWvxZllayBhbmQgUGV0ciBTb2prYSwgUHJvY2VlZGluZ3Mgb2YgdGhlIExSRUMgMjAxMCBXb3Jrc2hvcCBvbiBOZXcgQ2hhbGxlbmdlcyBmb3IgTkxQIEZyYW1ld29ya3MsIHBwLiA0NS01MCwgMjIgTWF5IDIwMTAuCgo=