Author: Daniel R. Brown, Jr.

Discussion

Defining Naïve Bayes

The Bayes Theorem

To understand the Naïve Bayes algorithm, one must first understand the Bayes Theorem, which states \[ P\left(A\vert B\right) = \frac{P\left(B\vert A\right) P\left( A \right )}{P\left(B\right)} \] \(P\left(A\vert B\right)\) is known as the conditional probability or the posterior probability (as in, looking back one can see it more clearly), and is read as the probability of A when B occurs. If one considers basic probability to be the ratio of the amount of times something happens versus the amount of times something could happen, then conditional probability is the ratio of event A happening when B happens versus the amount of times B happens regardless of A.

Along with the conditional probability, there is the prior probability, or \(P\left(B\right)\). This is an educated guess as to how likely something is to have a certain attribute. Lantz, for example, mentions how twenty percent of emails in the text scenario received were spam emails. Next we have the likelihood. Similar to our prior probability, likelihood is a measure of how many times something occurred in previous results. Finally, our marginal likelihood is the probability that event B occurred at all.

Lantz puts it into simple terms best, saying “…if we know event B occurred, the probability of event A is higher the more often that A and B occur together each time B is observed”. (Lantz 2015)

What is Naïve Bayes?

Naïve Bayes is an interesting application of the the Bayes theorem to the problem of classification. Many machine learning methods use Bayesian Probability, but Naïve Bayes is the most common.

Why Naïve?

The reason the algorithm is called naïve is because it is assumed that 1.) each feature is linearly independent of the other features, and 2.) each feature in the dataset are as important as any other feature (often neither is the case). Fortunately enough Naïve Bayes is still a strong enough algorithm that has strong results even when this assumption is violated.

There are some distinct advantages for using Naïve Bayes to classify data:

  1. Algorithm is simple, fast, and effective
  2. Algorithm can handle missing or disorganized datasets relatively well
  3. Algorithm requires less training examples than other methods
  4. Algorithm can easily obtain the estimated probability for a prediction.

The method also has some disadvantages:

  1. Algorithm relies on the above two assumptions:
    1. Equally important features
    2. Independent features
  2. Algorithm is not as well equipped to handle datasets with many numerical features.
  3. Estimated probabilities are less reliable than predicted classes.

Using the Naïve Bayes Algorithm

Lantz’s book utilizes a collection of sms text messages that are previously classified as either spam (unwanted messages) or ham (desired messages). The dataset provided contains 5559 messages that belong to either of the two previously mentioned categories(Almeida and Hidalgo 2011). By looking at the data and checking for various patterns, we can form probabilities that a message is spam or ham based on the words within the texts.

Naïve Bayes on sms data

Step one - collecting data

The data mentioned above has already been collected and as such we can move on to step two. ### Step two - exploring and preparing the data Let’s begin by taking a look at the dataset

sms_raw <- read.csv("sms_spam.csv", stringsAsFactors = FALSE)
str(sms_raw)
'data.frame':   5559 obs. of  2 variables:
 $ type: chr  "ham" "ham" "ham" "spam" ...
 $ text: chr  "Hope you are having a good week. Just checking in" "K..give back my thanks." "Am also doing in cbe only. But have to pay." "complimentary 4 STAR Ibiza Holiday or £10,000 cash needs your URGENT collection. 09066364349 NOW from Landline "| __truncated__ ...

This shows us that we have two features - one containing the categorical data of either spam or ham called type and one called text, which has the actual text messages contained within. As the data in type is currently not represented as categorical data, we can set that manually:

sms_raw$type <- factor(sms_raw$type)
str(sms_raw$type) #Success!
 Factor w/ 2 levels "ham","spam": 1 1 1 2 2 1 1 1 2 1 ...

Cleaning and standardizing text data

We can now utilize the R package tm to clean the strings of text contained in the text field. The packages is actually a text mining package developed by Ingo Feinerer(Ingo Feinerer and Meyer 2008).

The tm package contains a function called VCorpus() which creates a corpus, or a “body” of text documents as a list object. The V is short for volatile, meaning it is stored into your computer’s RAM and is not permanent.

sms_corpus <- VCorpus(VectorSource(sms_raw$text))
typeof(sms_corpus) # Just to show that it is a list
[1] "list"
print(sms_corpus)
<<VCorpus>>
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 5559

If we would like to see a specific document or sms from our corpus, we can inspect() individual sms from our new list sms_corpus. The book selected the top two results from the list but I think we can go a little classier than that and select two random results from the list using sample() and the pipe operator %>% from the package dyplr (conveniently loaded in our tidyverse package1)

length(sms_corpus) %>%
sample(replace = FALSE) %>%
  sort.list(decreasing = FALSE) %>%
  head(2) %>%
  sms_corpus[.] %>%
  inspect()
<<VCorpus>>
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 2

[[1]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 70

[[2]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 24

Next, we will need to standardize the documents within our corpus to ensure that the computer can properly characterize them. This includes modifing character strings like Hello!, Hello, and hello and remove stop words like to, and, but, and or. Stop words are basically articles of speech that humans use but are technically unnecesary for a computer. After that we will stem our documents. Stemming refers to the process where a word is stripped to its root word. For example, a verb that has the suffixes ed, ing and s will have those suffixes removed, i.e. jump, jumped, jumping and jumps will all be standardized to jump. It may be important to note that words that are spelled slightly differently (like run and ran) may not get corrected by this process. Stemming can be performed using the SnowballC package within, demonstrated below:

wordStem(c("jump", "jumping", "jumped", "jumps"))
[1] "jump" "jump" "jump" "jump"

After performing our stemming, we will need to remove the whitespace or blank spaces that are left from the previous preprocessing steps using the function stripWhitespace().

sms_corpus_clean <- sms_corpus %>%
  tm_map(content_transformer(tolower)) %>%
  tm_map(removeNumbers) %>%
  tm_map(removeWords, stopwords()) %>%
  tm_map(removePunctuation) %>%
  tm_map(stemDocument) %>%
  tm_map(stripWhitespace)

The text has us do all of these steps individually, but by using the tidyverse package and the pipe operator %>%, we are able to do this in one step without continuously modifying our sms_corpus_clean list every time. Now we can look at the change before processing and after processing.

cat("The text document prior processing:", "\n")
The text document prior processing: 
for(i in 1:3){
  print(as.character(sms_corpus[[i]]))
}
[1] "Hope you are having a good week. Just checking in"
[1] "K..give back my thanks."
[1] "Am also doing in cbe only. But have to pay."
cat("\n")
cat("The text document after processing:", "\n")
The text document after processing: 
for(i in 1:3){
  print(as.character(sms_corpus_clean[[i]]))
}
[1] "hope good week just check"
[1] "kgive back thank"
[1] "also cbe pay"

As a note, I had some difficulty using the code as.character(sms_corpus[1:3]) to print just the text from the sms_corpus. R would output this as with a bunch of metadata that we did may not want to display.

Splitting text documents into words

The final step in our standardization journey is to split the messages into individual words while still maintaining the identity of the messages. This can be done through a process called tokenization, where each word in the text string is considered a token.

To do this, we can use another function of the tm package: the DocumentTermMatrix() or DTM function. A DRM is a data structure provided by tm that splits a single text message into a row where each word is a column, creating an \(N\times M\) matrix. The package also contains functionality for the transpose of our \(N\times M\) matrix, which may be useful for performing analysis on fewer documents that are longer. This is called (surprise!) a Term Document Matrix or TDM. The DTM or TDM can both be considered a sparse matrix because there will be a row for every individual word contained in the corpus. The “sparseness” of the matrix is because each row will have a majority of the fields as \(0\) because there is such a small chance that each document will contain many of the total words.

sms_dtm <- DocumentTermMatrix(sms_corpus_clean)

For future reference, you can also apply all of the data preproccessing we performed in the previous steps to the DocumentTermMatrix() function, as shown below:

sms_dtm_no_prep <- DocumentTermMatrix(
  sms_corpus,
  control = list(
    tolower = TRUE,
    removeNumbers = TRUE,
    stopwords = TRUE,
    removePunctuation = TRUE,
    stemming = TRUE
  )
)

Let’s compare the two matrices:

cat("Our matrix with preprocessing:", "\n")
Our matrix with preprocessing: 
sms_dtm
<<DocumentTermMatrix (documents: 5559, terms: 6559)>>
Non-/sparse entries: 42147/36419334
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)
cat("\n")
cat("Our matrix without preprocessing:", "\n")
Our matrix without preprocessing: 
sms_dtm_no_prep
<<DocumentTermMatrix (documents: 5559, terms: 6961)>>
Non-/sparse entries: 43221/38652978
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)

Creating training and test datasets

Now we are in the familiar territory of splitting our data into test and training sets. Because the data was stored randomly, we can simply take the first 75% of our entries as our training set and the remainder can be taken as our test set.

sms_dtm_train <- sms_dtm[1:4169, ]
sms_dtm_test <- sms_dtm[4170:5559, ]
sms_train_labels <- sms_raw[1:4169, ]$type
sms_test_labels <- sms_raw[4170:5559, ]$type

A proportion table will help show if our test and training sets are both representative of the whole dataset.

cat("Our training data")
Our training data
sms_train_labels %>%
  table %>%
  prop.table
.
      ham      spam 
0.8647158 0.1352842 
cat("\n")
cat("Our testing data")
Our testing data
sms_test_labels %>%
  table %>%
  prop.table
.
      ham      spam 
0.8683453 0.1316547 

Creating a Word Cloud visualization

A word cloud will allow us to visually look at our corpus to quickly see the frequency of words within the corpus. Words that appear more frequently will be shown in a larger font size in the cloud, and less frequently a smaller font size. This can be done via the wordcloud R package.

wordcloud(sms_corpus_clean, min.freq = 50, random.order = FALSE)

We can also create visualizations of frequency for our raw sms data using the subset() function using the categorical feature $type:

par(mfcol = c(1, 2))
spam <- sms_raw %>%
  subset(type == "spam")
spamCloud <- wordcloud(spam$text, max.words = 40, scale = c(3, 0.5))
ham <- sms_raw %>%
  subset(type == "ham")
hamCloud <- wordcloud(ham$text, max.words = 40, scale = c(3, 0.5))

Creating indicator features for frequent words

To complete our data preprocessing, we must reduce the number of features in our test and training DT Matrices. To do this, we will use the findFreqTerms() function (again found in the tm package).

sms_dtm_freq_train <- sms_dtm_train %>%
  findFreqTerms(5) %>%
  sms_dtm_train[ , .]
sms_dtm_freq_test <- sms_dtm_test %>%
  findFreqTerms(5) %>%
  sms_dtm_test[ , .]

Now we shall write a function that converts our sparse DT matrices from numeric to categorical “yes/no” matrices that our algorithm can process.

convert_counts <- function(x) {
  x <- ifelse(x > 0, "Yes", "No")
}

Applying our convert_counts function:

sms_train <- sms_dtm_freq_train %>%
  apply(MARGIN = 2, convert_counts)
sms_test <- sms_dtm_freq_test %>%
  apply(MARGIN = 2, convert_counts)

Step 3 - training a model on the data

Now comes the fairly straightforward step of training our model on the data and then using that classifier to make predictions on the test set. This requires the e1071 package to apply the naiveBays() function:

sms_classifier <- naiveBayes(sms_train, sms_train_labels)
sms_pred <- predict(sms_classifier, sms_test)

Step 4 - evaluating model performance

Now we can use the CrossTable() function from the gmodels package to see how our predictions fared.

CrossTable(sms_pred, sms_test_labels, prop.chisq = FALSE, chisq = FALSE, 
           prop.t = FALSE,
           dnn = c("Predicted", "Actual"))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  1390 

 
             | Actual 
   Predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1201 |        30 |      1231 | 
             |     0.976 |     0.024 |     0.886 | 
             |     0.995 |     0.164 |           | 
-------------|-----------|-----------|-----------|
        spam |         6 |       153 |       159 | 
             |     0.038 |     0.962 |     0.114 | 
             |     0.005 |     0.836 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1207 |       183 |      1390 | 
             |     0.868 |     0.132 |           | 
-------------|-----------|-----------|-----------|

 

Our cross table shows us that we have \(\approx 97.4\%\) accuracuy in our classifier predicting whether or not a message is ham or spam. Can we do better?

Step 5 - Improving model performance

Let’s attempt to improve our model performance by using a different laplace value for our classifier

sms_classifier2 <- naiveBayes(sms_train, sms_train_labels, laplace = 1)
sms_pred2 <- predict(sms_classifier2, sms_test)
CrossTable(sms_pred2, sms_test_labels, prop.chisq = FALSE, chisq = FALSE, 
           prop.t = FALSE,
           dnn = c("Predicted", "Actual"))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  1390 

 
             | Actual 
   Predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1202 |        29 |      1231 | 
             |     0.976 |     0.024 |     0.886 | 
             |     0.996 |     0.158 |           | 
-------------|-----------|-----------|-----------|
        spam |         5 |       154 |       159 | 
             |     0.031 |     0.969 |     0.114 | 
             |     0.004 |     0.842 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1207 |       183 |      1390 | 
             |     0.868 |     0.132 |           | 
-------------|-----------|-----------|-----------|

 

We obtain an even better result of \(97.5\%\) accuracy! But what did adding a Laplace Smoothing Variable do for us?

Laplace Smoothing

That is smoothing? In a research paper from 1996, professors Chen and Goodman of Harvard University stated that “Smoothing is a technique essential in the construction of n-gram language models… [or] probability distributions over strings \(P\left(s\right)\) that attempts to reflect the frequrency with which each string \(s\) occurs as a sentence in natural text.”(Chen and Goodman 1996) Smoothing simply avoids giving words that appear less frequently less weight than words that appear more frequently. For example, if the word “Supercalifragilisticexpialidocious” only occurs in our text messages once in a spam message, should it be considered noise that it occurs at all? Smoothing avoids this entirely. In the book *Introduction to Information retrieval, the authors have this to say about smoothing: “[T]he probability of words occurring once in the document is normally over-estimated, since their one occurrence was partly by chance… the smoothing of terms actually implements major parts of the term weighting component. It is not just that an unsmoothed model has conjunctive semantics [or words occuring by chance]; an unsmoothed model works badly because it lacks parts of the term weighting compontents”(Christopher D. Manning and Schütze 2008).

Laplace smoothing is simply one of the more common methods of smoothing to be used. It is also known as additive smoothing or Lidstone smoothing and can be expressed as \[ \hat{P}\left(t\vert d\right) = \frac{tf_{t,d} + \alpha\hat{P}\left(t\vert M_{c}\right)}{L_{d}+\alpha} \] where \(tf_{t,d}\) is the raw term frequency of term \(t\) in document \(d\), \(L_{d}\) is the number of tokens in the document, \(M_{c}\) is a language model built from all documents, \(\alpha\) refers to a pseudocount which is a number corresponding to the use of a uniform distribution of the prior probability and represents our belief in the strength of uniformity of our distribution.

Long story short; our use of Laplace Smoothing makes a great model even better because it helps to weight the words in our corpus.

Creating analysis on iris dataset

Again we will be taking a look at the iris dataset(Anderson 1936)

Collecting data

Data is already available in base R

iris.df <- iris

Exploring and preparing the data

random <- sample(nrow(iris.df), 112, replace = FALSE)
iris_test <- iris.df[-random, ]
iris_train <- iris.df[random, ]

Training a model on the data

iris_classifier <- naiveBayes(iris_train[ , -5], iris_train$Species)
iris_pred <- predict(iris_classifier, iris_test)

Evaluating model performance

CrossTable(iris_pred, iris_test$Species, prop.chisq = FALSE, chisq = FALSE, 
           prop.t = FALSE,
           dnn = c("Predicted", "Actual"))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  38 

 
             | Actual 
   Predicted |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         11 |          0 |          0 |         11 | 
             |      1.000 |      0.000 |      0.000 |      0.289 | 
             |      1.000 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         16 |          2 |         18 | 
             |      0.000 |      0.889 |      0.111 |      0.474 | 
             |      0.000 |      1.000 |      0.182 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          0 |          9 |          9 | 
             |      0.000 |      0.000 |      1.000 |      0.237 | 
             |      0.000 |      0.000 |      0.818 |            | 
-------------|------------|------------|------------|------------|
Column Total |         11 |         16 |         11 |         38 | 
             |      0.289 |      0.421 |      0.289 |            | 
-------------|------------|------------|------------|------------|

 

This is about \(92\%\) effective. Let’s smack on that Laplace Smoothing and see what it does for us. ### Improving model performance

iris_classifier2 <- naiveBayes(iris_train[ , -5], iris_train$Species, laplace = 1)
iris_pred2 <- predict(iris_classifier2, iris_test)

CrossTable(iris_pred2, iris_test$Species, prop.chisq = FALSE, chisq = FALSE, 
           prop.t = FALSE,
           dnn = c("Predicted", "Actual"))

Well, that didn’t change much.

Conclusion

We did show the high accuracy that the Naive Bayes algorithm has for classifying text base data, and that it is less accurate on numerical data. The reason that the iris data set was select was because I really like working with it and also because I wanted to try to make a model with numerical values the second time around.

References

Almeida, Tiago A., and José María Gómez Hidalgo. 2011. “SMS Spam Collection.” Federal University of Sao Carlos. http://www.dt.fee.unicamp.br/~tiago/smsspamcollection/.

Anderson, Edgar. 1936. “The Species Problem in Iris.” Annals of the Missouri Botanical Garden. doi:10.2307/2394164.

Chen, Stanley F., and Joshua Goodman. 1996. “An Empirical Study of Smoothing Techniques for Language Modeling.” In Proceedings of the 34th Annual Meeting of the Association for Computational Linguistics, 310–18. Santa Cruz, California, USA: Association for Computational Linguistics. doi:10.3115/981863.981904.

Christopher D. Manning, Prabhaker Raghavan, and Hirich Schütze. 2008. Introduction to Information Retrieval. New York, New York: Cambridge University Press.

Ingo Feinerer, Kurt Hornik, and David Meyer. 2008. “Text Mining Infrastructure in R.” Journal of Statistical Software. doi:10.18637/jss.v025.i05.

Lantz, Brett. 2015. Machine Learning with R. Birmingham, United Kingdom: Packt Publishing.

Wickham, Hadley. 2017. “Tidyverse.” https://www.tidyverse.org/.


  1. Just as a shameless plug for tidyverse(Wickham 2017), because I absolutely love the package and think that it’s an amazingly useful and dare I say fun package to work with.

LS0tCnRpdGxlOiAiVXNpbmcgTmHDr3ZlIEJheWVzIGFsZ29yaXRobSBmb3IgcHJlZGljdGl2ZSBjbGFzc2lmaWNhdGlvbiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwpiaWJsaW9ncmFwaHk6IHJlZi5iaWIKLS0tCmBgYHtyIFBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodG0pCmxpYnJhcnkoU25vd2JhbGxDKQpsaWJyYXJ5KHdvcmRjbG91ZCkKbGlicmFyeShlMTA3MSkKbGlicmFyeShnbW9kZWxzKQpgYGAKCkF1dGhvcjogRGFuaWVsIFIuIEJyb3duLCBKci4KCiMgRGlzY3Vzc2lvbgojIyBEZWZpbmluZyBOYcOvdmUgQmF5ZXMKIyMjIFRoZSBCYXllcyBUaGVvcmVtClRvIHVuZGVyc3RhbmQgdGhlIE5hw692ZSBCYXllcyBhbGdvcml0aG0sIG9uZSBtdXN0IGZpcnN0IHVuZGVyc3RhbmQgdGhlIEJheWVzClRoZW9yZW0sIHdoaWNoIHN0YXRlcwokJApQXGxlZnQoQVx2ZXJ0IEJccmlnaHQpID0gXGZyYWN7UFxsZWZ0KEJcdmVydCBBXHJpZ2h0KSBQXGxlZnQoIEEgXHJpZ2h0ICl9e1BcbGVmdChCXHJpZ2h0KX0KJCQKJFBcbGVmdChBXHZlcnQgQlxyaWdodCkkIGlzIGtub3duIGFzIHRoZSAqY29uZGl0aW9uYWwgcHJvYmFiaWxpdHkqIG9yIHRoZSAqcG9zdGVyaW9yIHByb2JhYmlsaXR5KiAoYXMgaW4sIGxvb2tpbmcgYmFjayBvbmUgY2FuIHNlZSBpdCBtb3JlIGNsZWFybHkpLCBhbmQgaXMgcmVhZCBhcyB0aGUgcHJvYmFiaWxpdHkgb2YgKkEqIHdoZW4gKkIqIG9jY3Vycy4gSWYgb25lIGNvbnNpZGVycyBiYXNpYyBwcm9iYWJpbGl0eSB0byBiZSB0aGUgcmF0aW8gb2YgdGhlIGFtb3VudCBvZiB0aW1lcyBzb21ldGhpbmcgaGFwcGVucyB2ZXJzdXMgdGhlIGFtb3VudCBvZiB0aW1lcyBzb21ldGhpbmcgKmNvdWxkKiBoYXBwZW4sIHRoZW4gY29uZGl0aW9uYWwgcHJvYmFiaWxpdHkgaXMgdGhlIHJhdGlvIG9mIGV2ZW50ICpBKiBoYXBwZW5pbmcgd2hlbiAqQiogaGFwcGVucyB2ZXJzdXMgdGhlIGFtb3VudCBvZiB0aW1lcyAqQiogaGFwcGVucyByZWdhcmRsZXNzIG9mICpBKi4KCkFsb25nIHdpdGggdGhlIGNvbmRpdGlvbmFsIHByb2JhYmlsaXR5LCB0aGVyZSBpcyB0aGUgKnByaW9yIHByb2JhYmlsaXR5Kiwgb3IgJFBcbGVmdChCXHJpZ2h0KSQuIFRoaXMgaXMgYW4gZWR1Y2F0ZWQgZ3Vlc3MgYXMgdG8gaG93IGxpa2VseSBzb21ldGhpbmcgaXMgdG8gaGF2ZSBhIGNlcnRhaW4gYXR0cmlidXRlLiBMYW50eiwgZm9yIGV4YW1wbGUsIG1lbnRpb25zIGhvdyB0d2VudHkgcGVyY2VudCBvZiBlbWFpbHMgaW4gdGhlIHRleHQgc2NlbmFyaW8gcmVjZWl2ZWQgd2VyZSBzcGFtIGVtYWlscy4gTmV4dCB3ZSBoYXZlIHRoZSAqbGlrZWxpaG9vZCouIFNpbWlsYXIgdG8gb3VyIHByaW9yIHByb2JhYmlsaXR5LCBsaWtlbGlob29kIGlzIGEgbWVhc3VyZSBvZiBob3cgbWFueSB0aW1lcyBzb21ldGhpbmcgb2NjdXJyZWQgaW4gcHJldmlvdXMgcmVzdWx0cy4gRmluYWxseSwgb3VyICptYXJnaW5hbCBsaWtlbGlob29kKiBpcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCBldmVudCAqQiogb2NjdXJyZWQgYXQgYWxsLgoKTGFudHogcHV0cyBpdCBpbnRvIHNpbXBsZSB0ZXJtcyBiZXN0LCBzYXlpbmcgIi4uLmlmIHdlIGtub3cgZXZlbnQgKkIqIG9jY3VycmVkLCB0aGUgcHJvYmFiaWxpdHkgb2YgZXZlbnQgKkEqIGlzIGhpZ2hlciB0aGUgbW9yZSBvZnRlbiB0aGF0ICpBKiBhbmQgKkIqIG9jY3VyIHRvZ2V0aGVyIGVhY2ggdGltZSAqQiogaXMgb2JzZXJ2ZWQiLiBbQGxhbnR6MTVdCgojIyBXaGF0IGlzIE5hw692ZSBCYXllcz8KKipOYcOvdmUgQmF5ZXMqKiBpcyBhbiBpbnRlcmVzdGluZyBhcHBsaWNhdGlvbiBvZiB0aGUgdGhlIEJheWVzIHRoZW9yZW0gdG8gdGhlIHByb2JsZW0gb2YgY2xhc3NpZmljYXRpb24uIE1hbnkgbWFjaGluZSBsZWFybmluZyBtZXRob2RzIHVzZSBCYXllc2lhbiBQcm9iYWJpbGl0eSwgYnV0IE5hw692ZSBCYXllcyBpcyB0aGUgbW9zdCBjb21tb24uCgojIyMgV2h5IE5hw692ZT8KVGhlIHJlYXNvbiB0aGUgYWxnb3JpdGhtIGlzIGNhbGxlZCAqbmHDr3ZlKiBpcyBiZWNhdXNlIGl0IGlzIGFzc3VtZWQgdGhhdCAqMS4pKiBlYWNoIGZlYXR1cmUgaXMgbGluZWFybHkgaW5kZXBlbmRlbnQgb2YgdGhlIG90aGVyIGZlYXR1cmVzLCBhbmQgKjIuKSogZWFjaCBmZWF0dXJlIGluIHRoZSBkYXRhc2V0IGFyZSBhcyBpbXBvcnRhbnQgYXMgYW55IG90aGVyIGZlYXR1cmUgKG9mdGVuIG5laXRoZXIgaXMgdGhlIGNhc2UpLiBGb3J0dW5hdGVseSBlbm91Z2ggTmHDr3ZlIEJheWVzIGlzIHN0aWxsIGEgc3Ryb25nIGVub3VnaCBhbGdvcml0aG0gdGhhdCBoYXMgc3Ryb25nIHJlc3VsdHMgZXZlbiB3aGVuIHRoaXMgYXNzdW1wdGlvbiBpcyB2aW9sYXRlZC4KClRoZXJlIGFyZSBzb21lIGRpc3RpbmN0IGFkdmFudGFnZXMgZm9yIHVzaW5nIE5hw692ZSBCYXllcyB0byBjbGFzc2lmeSBkYXRhOgoKMS4gQWxnb3JpdGhtIGlzIHNpbXBsZSwgZmFzdCwgYW5kIGVmZmVjdGl2ZQoyLiBBbGdvcml0aG0gY2FuIGhhbmRsZSBtaXNzaW5nIG9yIGRpc29yZ2FuaXplZCBkYXRhc2V0cyByZWxhdGl2ZWx5IHdlbGwKMy4gQWxnb3JpdGhtIHJlcXVpcmVzIGxlc3MgdHJhaW5pbmcgZXhhbXBsZXMgdGhhbiBvdGhlciBtZXRob2RzCjQuIEFsZ29yaXRobSBjYW4gZWFzaWx5IG9idGFpbiB0aGUgZXN0aW1hdGVkIHByb2JhYmlsaXR5IGZvciBhIHByZWRpY3Rpb24uCgpUaGUgbWV0aG9kIGFsc28gaGFzIHNvbWUgZGlzYWR2YW50YWdlczoKCjEuIEFsZ29yaXRobSByZWxpZXMgb24gdGhlIGFib3ZlIHR3byBhc3N1bXB0aW9uczoKICAgIDEuIEVxdWFsbHkgaW1wb3J0YW50IGZlYXR1cmVzCiAgICAyLiBJbmRlcGVuZGVudCBmZWF0dXJlcwoyLiBBbGdvcml0aG0gaXMgbm90IGFzIHdlbGwgZXF1aXBwZWQgdG8gaGFuZGxlIGRhdGFzZXRzIHdpdGggbWFueSBudW1lcmljYWwKZmVhdHVyZXMuCjMuIEVzdGltYXRlZCBwcm9iYWJpbGl0aWVzIGFyZSBsZXNzIHJlbGlhYmxlIHRoYW4gcHJlZGljdGVkIGNsYXNzZXMuCgojIyBVc2luZyB0aGUgTmHDr3ZlIEJheWVzIEFsZ29yaXRobQpMYW50eidzIGJvb2sgdXRpbGl6ZXMgYSBjb2xsZWN0aW9uIG9mIHNtcyB0ZXh0IG1lc3NhZ2VzIHRoYXQgYXJlIHByZXZpb3VzbHkgY2xhc3NpZmllZCBhcyBlaXRoZXIgKipzcGFtKiogKHVud2FudGVkIG1lc3NhZ2VzKSBvciAqKmhhbSoqIChkZXNpcmVkIG1lc3NhZ2VzKS4gVGhlIGRhdGFzZXQgcHJvdmlkZWQgY29udGFpbnMgNTU1OSBtZXNzYWdlcyB0aGF0IGJlbG9uZyB0byBlaXRoZXIgb2YgdGhlIHR3byBwcmV2aW91c2x5IG1lbnRpb25lZCBjYXRlZ29yaWVzW0BhbG1laTExXS4gQnkgbG9va2luZyBhdCB0aGUgZGF0YSBhbmQgY2hlY2tpbmcgZm9yIHZhcmlvdXMgcGF0dGVybnMsIHdlIGNhbiBmb3JtIHByb2JhYmlsaXRpZXMgdGhhdCBhIG1lc3NhZ2UgaXMgc3BhbSBvciBoYW0gYmFzZWQgb24gdGhlIHdvcmRzIHdpdGhpbiB0aGUgdGV4dHMuCgojIyBOYcOvdmUgQmF5ZXMgb24gc21zIGRhdGEKIyMjIFN0ZXAgb25lIC0gY29sbGVjdGluZyBkYXRhClRoZSBkYXRhIG1lbnRpb25lZCBhYm92ZSBoYXMgYWxyZWFkeSBiZWVuIGNvbGxlY3RlZCBhbmQgYXMgc3VjaCB3ZSBjYW4gbW92ZSBvbiB0byBzdGVwIHR3by4KIyMjIFN0ZXAgdHdvIC0gZXhwbG9yaW5nIGFuZCBwcmVwYXJpbmcgdGhlIGRhdGEKTGV0J3MgYmVnaW4gYnkgdGFraW5nIGEgbG9vayBhdCB0aGUgZGF0YXNldApgYGB7ciBJbXBvcnRpbmcgc21zIGRhdGFzZXR9CnNtc19yYXcgPC0gcmVhZC5jc3YoInNtc19zcGFtLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKCnN0cihzbXNfcmF3KQpgYGAKVGhpcyBzaG93cyB1cyB0aGF0IHdlIGhhdmUgdHdvIGZlYXR1cmVzIC0gb25lIGNvbnRhaW5pbmcgdGhlIGNhdGVnb3JpY2FsIGRhdGEgb2YgZWl0aGVyIHNwYW0gb3IgaGFtIGNhbGxlZCAqKnR5cGUqKiBhbmQgb25lIGNhbGxlZCAqKnRleHQqKiwgd2hpY2ggaGFzIHRoZSBhY3R1YWwgdGV4dCBtZXNzYWdlcyBjb250YWluZWQgd2l0aGluLiBBcyB0aGUgZGF0YSBpbiAqKnR5cGUqKiBpcyBjdXJyZW50bHkgbm90IHJlcHJlc2VudGVkIGFzIGNhdGVnb3JpY2FsIGRhdGEsIHdlIGNhbiBzZXQgdGhhdCBtYW51YWxseToKYGBge3IgZW5jb2RpbmcgY2hhciBpbnRvIGZhY3Rvcn0Kc21zX3JhdyR0eXBlIDwtIGZhY3RvcihzbXNfcmF3JHR5cGUpCnN0cihzbXNfcmF3JHR5cGUpICNTdWNjZXNzIQpgYGAKIyMjIyBDbGVhbmluZyBhbmQgc3RhbmRhcmRpemluZyB0ZXh0IGRhdGEKV2UgY2FuIG5vdyB1dGlsaXplIHRoZSBSIHBhY2thZ2UgYGBgdG1gYGAgdG8gY2xlYW4gdGhlIHN0cmluZ3Mgb2YgdGV4dCBjb250YWluZWQgaW4gdGhlIHRleHQgZmllbGQuIFRoZSBwYWNrYWdlcyBpcyBhY3R1YWxseSBhIHRleHQgbWluaW5nIHBhY2thZ2UgZGV2ZWxvcGVkIGJ5IEluZ28gRmVpbmVyZXJbQGZlaW5lMDhdLgoKVGhlIGBgYHRtYGBgIHBhY2thZ2UgY29udGFpbnMgYSBmdW5jdGlvbiBjYWxsZWQgYGBgVkNvcnB1cygpYGBgIHdoaWNoIGNyZWF0ZXMgYSAqKmNvcnB1cyoqLCBvciBhICJib2R5IiBvZiB0ZXh0IGRvY3VtZW50cyBhcyBhIGxpc3Qgb2JqZWN0LiBUaGUgKlYqIGlzIHNob3J0IGZvciB2b2xhdGlsZSwgbWVhbmluZyBpdCBpcyBzdG9yZWQgaW50byB5b3VyIGNvbXB1dGVyJ3MgUkFNIGFuZCBpcyBub3QgcGVybWFuZW50LgoKYGBge3IgY3JlYXRpbmcgb3VyIFZDb3JwdXN9CnNtc19jb3JwdXMgPC0gVkNvcnB1cyhWZWN0b3JTb3VyY2Uoc21zX3JhdyR0ZXh0KSkKCnR5cGVvZihzbXNfY29ycHVzKSAjIEp1c3QgdG8gc2hvdyB0aGF0IGl0IGlzIGEgbGlzdApwcmludChzbXNfY29ycHVzKQpgYGAKSWYgd2Ugd291bGQgbGlrZSB0byBzZWUgYSBzcGVjaWZpYyBkb2N1bWVudCBvciBzbXMgZnJvbSBvdXIgY29ycHVzLCB3ZSBjYW4gYGBgaW5zcGVjdCgpYGBgIGluZGl2aWR1YWwgc21zIGZyb20gb3VyIG5ldyBsaXN0IGBgYHNtc19jb3JwdXNgYGAuIFRoZSBib29rIHNlbGVjdGVkIHRoZSB0b3AgdHdvIHJlc3VsdHMgZnJvbSB0aGUgbGlzdCBidXQgSSB0aGluayB3ZSBjYW4gZ28gYSBsaXR0bGUgY2xhc3NpZXIgdGhhbiB0aGF0IGFuZCBzZWxlY3QgdHdvIHJhbmRvbSByZXN1bHRzIGZyb20gdGhlIGxpc3QgdXNpbmcgYGBgc2FtcGxlKClgYGAgYW5kIHRoZSBwaXBlIG9wZXJhdG9yIGBgYCU+JWBgYCBmcm9tIHRoZSBwYWNrYWdlIGBgYGR5cGxyYGBgIChjb252ZW5pZW50bHkgbG9hZGVkIGluIG91ciBgYGB0aWR5dmVyc2VgYGAgcGFja2FnZVteMV0pCgpgYGB7ciBJbnNwZWN0aW5nIG91ciBWQ29ycHVzfQpsZW5ndGgoc21zX2NvcnB1cykgJT4lCnNhbXBsZShyZXBsYWNlID0gRkFMU0UpICU+JQogIHNvcnQubGlzdChkZWNyZWFzaW5nID0gRkFMU0UpICU+JQogIGhlYWQoMikgJT4lCiAgc21zX2NvcnB1c1suXSAlPiUKICBpbnNwZWN0KCkKYGBgCk5leHQsIHdlIHdpbGwgbmVlZCB0byBzdGFuZGFyZGl6ZSB0aGUgZG9jdW1lbnRzIHdpdGhpbiBvdXIgY29ycHVzIHRvIGVuc3VyZSB0aGF0IHRoZSBjb21wdXRlciBjYW4gcHJvcGVybHkgY2hhcmFjdGVyaXplIHRoZW0uIFRoaXMgaW5jbHVkZXMgbW9kaWZpbmcgY2hhcmFjdGVyIHN0cmluZ3MgbGlrZSAqKkhlbGxvISoqLCAqKkhlbGxvKiosIGFuZCAqKmhlbGxvKiogYW5kIHJlbW92ZSAqKnN0b3Agd29yZHMqKiBsaWtlICoqdG8qKiwgKiphbmQqKiwgKipidXQqKiwgYW5kICoqb3IqKi4gU3RvcCB3b3JkcyBhcmUgYmFzaWNhbGx5IGFydGljbGVzIG9mIHNwZWVjaCB0aGF0IGh1bWFucyB1c2UgYnV0IGFyZSB0ZWNobmljYWxseSB1bm5lY2VzYXJ5IGZvciBhIGNvbXB1dGVyLiBBZnRlciB0aGF0IHdlIHdpbGwgKipzdGVtKiogb3VyIGRvY3VtZW50cy4gKipTdGVtbWluZyoqIHJlZmVycyB0byB0aGUgcHJvY2VzcyB3aGVyZSBhIHdvcmQgaXMgc3RyaXBwZWQgdG8gaXRzIHJvb3Qgd29yZC4gRm9yIGV4YW1wbGUsIGEgdmVyYiB0aGF0IGhhcyB0aGUgc3VmZml4ZXMgKmVkKiwgKmluZyogYW5kICpzKiB3aWxsIGhhdmUgdGhvc2Ugc3VmZml4ZXMgcmVtb3ZlZCwgaS5lLiAqanVtcCosICpqdW1wZWQqLCAqanVtcGluZyogYW5kICpqdW1wcyogd2lsbCBhbGwgYmUgc3RhbmRhcmRpemVkIHRvICpqdW1wKi4gSXQgbWF5IGJlIGltcG9ydGFudCB0byBub3RlIHRoYXQgd29yZHMgdGhhdCBhcmUgc3BlbGxlZCBzbGlnaHRseSBkaWZmZXJlbnRseSAobGlrZSAqcnVuKiBhbmQgKnJhbiopIG1heSBub3QgZ2V0IGNvcnJlY3RlZCBieSB0aGlzIHByb2Nlc3MuIFN0ZW1taW5nIGNhbiBiZSBwZXJmb3JtZWQgdXNpbmcgdGhlIGBgYFNub3diYWxsQ2BgYCBwYWNrYWdlIHdpdGhpbiwgZGVtb25zdHJhdGVkIGJlbG93OgoKYGBge3IgU25vd2JhbGxDIGRlbW99CndvcmRTdGVtKGMoImp1bXAiLCAianVtcGluZyIsICJqdW1wZWQiLCAianVtcHMiKSkKYGBgCkFmdGVyIHBlcmZvcm1pbmcgb3VyICpzdGVtbWluZyosIHdlIHdpbGwgbmVlZCB0byByZW1vdmUgdGhlICp3aGl0ZXNwYWNlKiBvciBibGFuayBzcGFjZXMgdGhhdCBhcmUgbGVmdCBmcm9tIHRoZSBwcmV2aW91cyBwcmVwcm9jZXNzaW5nIHN0ZXBzIHVzaW5nIHRoZSBmdW5jdGlvbiBgYGBzdHJpcFdoaXRlc3BhY2UoKWBgYC4KCmBgYHtyIENsZWFuaW5nIG91ciBjb3JwdXN9CnNtc19jb3JwdXNfY2xlYW4gPC0gc21zX2NvcnB1cyAlPiUKICB0bV9tYXAoY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkgJT4lCiAgdG1fbWFwKHJlbW92ZU51bWJlcnMpICU+JQogIHRtX21hcChyZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCkpICU+JQogIHRtX21hcChyZW1vdmVQdW5jdHVhdGlvbikgJT4lCiAgdG1fbWFwKHN0ZW1Eb2N1bWVudCkgJT4lCiAgdG1fbWFwKHN0cmlwV2hpdGVzcGFjZSkKYGBgCgpUaGUgdGV4dCBoYXMgdXMgZG8gYWxsIG9mIHRoZXNlIHN0ZXBzIGluZGl2aWR1YWxseSwgYnV0IGJ5IHVzaW5nIHRoZSBgYGB0aWR5dmVyc2VgYGAgcGFja2FnZSBhbmQgdGhlIHBpcGUgb3BlcmF0b3IgYGBgJT4lYGBgLCB3ZSBhcmUgYWJsZSB0byBkbyB0aGlzIGluIG9uZSBzdGVwIHdpdGhvdXQgY29udGludW91c2x5IG1vZGlmeWluZyBvdXIgYGBgc21zX2NvcnB1c19jbGVhbmBgYCBsaXN0IGV2ZXJ5IHRpbWUuIE5vdyB3ZSBjYW4gbG9vayBhdCB0aGUgY2hhbmdlIGJlZm9yZSBwcm9jZXNzaW5nIGFuZCBhZnRlciBwcm9jZXNzaW5nLgpgYGB7ciBUaGUgZnJ1aXRzIG9mIG91ciBwcmVwcm9jZXNzaW5nIGxhYm9yfQpjYXQoIlRoZSB0ZXh0IGRvY3VtZW50IHByaW9yIHByb2Nlc3Npbmc6IiwgIlxuIikKZm9yKGkgaW4gMTozKXsKICBwcmludChhcy5jaGFyYWN0ZXIoc21zX2NvcnB1c1tbaV1dKSkKfQpjYXQoIlxuIikKY2F0KCJUaGUgdGV4dCBkb2N1bWVudCBhZnRlciBwcm9jZXNzaW5nOiIsICJcbiIpCmZvcihpIGluIDE6Myl7CiAgcHJpbnQoYXMuY2hhcmFjdGVyKHNtc19jb3JwdXNfY2xlYW5bW2ldXSkpCn0KYGBgCiBBcyBhIG5vdGUsIEkgaGFkIHNvbWUgZGlmZmljdWx0eSB1c2luZyB0aGUgY29kZSBgYGBhcy5jaGFyYWN0ZXIoc21zX2NvcnB1c1sxOjNdKWBgYCB0byBwcmludCBqdXN0IHRoZSB0ZXh0IGZyb20gdGhlIHNtc19jb3JwdXMuIFIgd291bGQgb3V0cHV0IHRoaXMgYXMgd2l0aCBhIGJ1bmNoIG9mIG1ldGFkYXRhIHRoYXQgd2UgZGlkIG1heSBub3Qgd2FudCB0byBkaXNwbGF5LgoKIyMjIyBTcGxpdHRpbmcgdGV4dCBkb2N1bWVudHMgaW50byB3b3JkcwpUaGUgZmluYWwgc3RlcCBpbiBvdXIgc3RhbmRhcmRpemF0aW9uIGpvdXJuZXkgaXMgdG8gc3BsaXQgdGhlIG1lc3NhZ2VzIGludG8gaW5kaXZpZHVhbCB3b3JkcyB3aGlsZSBzdGlsbCBtYWludGFpbmluZyB0aGUgaWRlbnRpdHkgb2YgdGhlIG1lc3NhZ2VzLiBUaGlzIGNhbiBiZSBkb25lIHRocm91Z2ggYSBwcm9jZXNzIGNhbGxlZCAqKnRva2VuaXphdGlvbioqLCB3aGVyZSBlYWNoIHdvcmQgaW4gdGhlIHRleHQgc3RyaW5nIGlzIGNvbnNpZGVyZWQgYSAqdG9rZW4qLgoKVG8gZG8gdGhpcywgd2UgY2FuIHVzZSBhbm90aGVyIGZ1bmN0aW9uIG9mIHRoZSBgYGB0bWBgYCBwYWNrYWdlOiB0aGUgYGBgRG9jdW1lbnRUZXJtTWF0cml4KClgYGAgb3IgRFRNIGZ1bmN0aW9uLiBBIERSTSBpcyBhIGRhdGEgc3RydWN0dXJlIHByb3ZpZGVkIGJ5IGBgYHRtYGBgIHRoYXQgc3BsaXRzIGEgc2luZ2xlIHRleHQgbWVzc2FnZSBpbnRvIGEgcm93IHdoZXJlIGVhY2ggd29yZCBpcyBhIGNvbHVtbiwgY3JlYXRpbmcgYW4gJE5cdGltZXMgTSQgbWF0cml4LiBUaGUgcGFja2FnZSBhbHNvIGNvbnRhaW5zIGZ1bmN0aW9uYWxpdHkgZm9yIHRoZSB0cmFuc3Bvc2Ugb2Ygb3VyICROXHRpbWVzIE0kIG1hdHJpeCwgd2hpY2ggbWF5IGJlIHVzZWZ1bCBmb3IgcGVyZm9ybWluZyBhbmFseXNpcyBvbiBmZXdlciBkb2N1bWVudHMgdGhhdCBhcmUgbG9uZ2VyLiBUaGlzIGlzIGNhbGxlZCAoc3VycHJpc2UhKSBhIFRlcm0gRG9jdW1lbnQgTWF0cml4IG9yIFRETS4gVGhlIERUTSBvciBURE0gY2FuIGJvdGggYmUgY29uc2lkZXJlZCBhICoqc3BhcnNlIG1hdHJpeCoqIGJlY2F1c2UgdGhlcmUgd2lsbCBiZSBhIHJvdyBmb3IgZXZlcnkgaW5kaXZpZHVhbCB3b3JkIGNvbnRhaW5lZCBpbiB0aGUgY29ycHVzLiBUaGUgIipzcGFyc2VuZXNzKiIgb2YgdGhlIG1hdHJpeCBpcyBiZWNhdXNlIGVhY2ggcm93IHdpbGwgaGF2ZSBhIG1ham9yaXR5IG9mIHRoZSBmaWVsZHMgYXMgJDAkIGJlY2F1c2UgdGhlcmUgaXMgc3VjaCBhIHNtYWxsIGNoYW5jZSB0aGF0IGVhY2ggZG9jdW1lbnQgd2lsbCBjb250YWluIG1hbnkgb2YgdGhlIHRvdGFsIHdvcmRzLgoKYGBge3IgQ3JlYXRpbmcgb3VyIERvY3VtZW50VGVybU1hdHJpeH0Kc21zX2R0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoc21zX2NvcnB1c19jbGVhbikKYGBgCgpGb3IgZnV0dXJlIHJlZmVyZW5jZSwgeW91IGNhbiBhbHNvIGFwcGx5IGFsbCBvZiB0aGUgZGF0YSBwcmVwcm9jY2Vzc2luZyB3ZSBwZXJmb3JtZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXBzIHRvIHRoZSBgYGBEb2N1bWVudFRlcm1NYXRyaXgoKWBgYCBmdW5jdGlvbiwgYXMgc2hvd24gYmVsb3c6CiAKYGBge3IgQ3JlYXRpbmcgYW5vdGhlciBEb2N1bWVudFRlcm1NYXRyaXh9CnNtc19kdG1fbm9fcHJlcCA8LSBEb2N1bWVudFRlcm1NYXRyaXgoCiAgc21zX2NvcnB1cywKICBjb250cm9sID0gbGlzdCgKICAgIHRvbG93ZXIgPSBUUlVFLAogICAgcmVtb3ZlTnVtYmVycyA9IFRSVUUsCiAgICBzdG9wd29yZHMgPSBUUlVFLAogICAgcmVtb3ZlUHVuY3R1YXRpb24gPSBUUlVFLAogICAgc3RlbW1pbmcgPSBUUlVFCiAgKQopCmBgYAoKTGV0J3MgY29tcGFyZSB0aGUgdHdvIG1hdHJpY2VzOgoKYGBge3IgQ29tcGFyaW5nIG91ciBEVE1zfQpjYXQoIk91ciBtYXRyaXggd2l0aCBwcmVwcm9jZXNzaW5nOiIsICJcbiIpCnNtc19kdG0KY2F0KCJcbiIpCmNhdCgiT3VyIG1hdHJpeCB3aXRob3V0IHByZXByb2Nlc3Npbmc6IiwgIlxuIikKc21zX2R0bV9ub19wcmVwCmBgYAojIyMjIENyZWF0aW5nIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXRzCk5vdyB3ZSBhcmUgaW4gdGhlIGZhbWlsaWFyIHRlcnJpdG9yeSBvZiBzcGxpdHRpbmcgb3VyIGRhdGEgaW50byB0ZXN0IGFuZCB0cmFpbmluZyBzZXRzLiBCZWNhdXNlIHRoZSBkYXRhIHdhcyBzdG9yZWQgcmFuZG9tbHksIHdlIGNhbiBzaW1wbHkgdGFrZSB0aGUgZmlyc3QgNzUlIG9mIG91ciBlbnRyaWVzIGFzIG91ciB0cmFpbmluZyBzZXQgYW5kIHRoZSByZW1haW5kZXIgY2FuIGJlIHRha2VuIGFzIG91ciB0ZXN0IHNldC4KCmBgYHtyIFNwbGl0dGluZyB0cmFpbmluZyBhbmQgdGVzdCBkYXRhc2V0c30Kc21zX2R0bV90cmFpbiA8LSBzbXNfZHRtWzE6NDE2OSwgXQpzbXNfZHRtX3Rlc3QgPC0gc21zX2R0bVs0MTcwOjU1NTksIF0Kc21zX3RyYWluX2xhYmVscyA8LSBzbXNfcmF3WzE6NDE2OSwgXSR0eXBlCnNtc190ZXN0X2xhYmVscyA8LSBzbXNfcmF3WzQxNzA6NTU1OSwgXSR0eXBlCmBgYAoKQSBwcm9wb3J0aW9uIHRhYmxlIHdpbGwgaGVscCBzaG93IGlmIG91ciB0ZXN0IGFuZCB0cmFpbmluZyBzZXRzIGFyZSBib3RoIHJlcHJlc2VudGF0aXZlIG9mIHRoZSB3aG9sZSBkYXRhc2V0LgpgYGB7cn0KY2F0KCJPdXIgdHJhaW5pbmcgZGF0YSIpCnNtc190cmFpbl9sYWJlbHMgJT4lCiAgdGFibGUgJT4lCiAgcHJvcC50YWJsZQpjYXQoIlxuIikKY2F0KCJPdXIgdGVzdGluZyBkYXRhIikKc21zX3Rlc3RfbGFiZWxzICU+JQogIHRhYmxlICU+JQogIHByb3AudGFibGUKYGBgCiMjIyMgQ3JlYXRpbmcgYSBXb3JkIENsb3VkIHZpc3VhbGl6YXRpb24KQSAqKndvcmQgY2xvdWQqKiB3aWxsIGFsbG93IHVzIHRvIHZpc3VhbGx5IGxvb2sgYXQgb3VyIGNvcnB1cyB0byBxdWlja2x5IHNlZSB0aGUgZnJlcXVlbmN5IG9mIHdvcmRzIHdpdGhpbiB0aGUgY29ycHVzLiBXb3JkcyB0aGF0IGFwcGVhciBtb3JlIGZyZXF1ZW50bHkgd2lsbCBiZSBzaG93biBpbiBhIGxhcmdlciBmb250IHNpemUgaW4gdGhlIGNsb3VkLCBhbmQgbGVzcyBmcmVxdWVudGx5IGEgc21hbGxlciBmb250IHNpemUuIFRoaXMgY2FuIGJlIGRvbmUgdmlhIHRoZSBgYGB3b3JkY2xvdWRgYGAgUiBwYWNrYWdlLgoKYGBge3IgQ3JlYXRpbmcgYSBXb3JkIENsb3VkLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CndvcmRjbG91ZChzbXNfY29ycHVzX2NsZWFuLCBtaW4uZnJlcSA9IDUwLCByYW5kb20ub3JkZXIgPSBGQUxTRSkKYGBgCldlIGNhbiBhbHNvIGNyZWF0ZSB2aXN1YWxpemF0aW9ucyBvZiBmcmVxdWVuY3kgZm9yIG91ciByYXcgc21zIGRhdGEgdXNpbmcgdGhlIGBgYHN1YnNldCgpYGBgIGZ1bmN0aW9uIHVzaW5nIHRoZSBjYXRlZ29yaWNhbCBmZWF0dXJlIGBgYCR0eXBlYGBgOgpgYGB7ciBXb3JkY2xvdWQ6IFNwYW0gb3IgSGFtPywgd2FybmluZz1GQUxTRX0KcGFyKG1mY29sID0gYygxLCAyKSkKc3BhbSA8LSBzbXNfcmF3ICU+JQogIHN1YnNldCh0eXBlID09ICJzcGFtIikKc3BhbUNsb3VkIDwtIHdvcmRjbG91ZChzcGFtJHRleHQsIG1heC53b3JkcyA9IDQwLCBzY2FsZSA9IGMoMywgMC41KSkKaGFtIDwtIHNtc19yYXcgJT4lCiAgc3Vic2V0KHR5cGUgPT0gImhhbSIpCmhhbUNsb3VkIDwtIHdvcmRjbG91ZChoYW0kdGV4dCwgbWF4LndvcmRzID0gNDAsIHNjYWxlID0gYygzLCAwLjUpKQpgYGAKCiMjIyMgQ3JlYXRpbmcgaW5kaWNhdG9yIGZlYXR1cmVzIGZvciBmcmVxdWVudCB3b3JkcwpUbyBjb21wbGV0ZSBvdXIgZGF0YSBwcmVwcm9jZXNzaW5nLCB3ZSBtdXN0IHJlZHVjZSB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIGluIG91ciB0ZXN0IGFuZCB0cmFpbmluZyBEVCBNYXRyaWNlcy4gVG8gZG8gdGhpcywgd2Ugd2lsbCB1c2UgdGhlIGBgYGZpbmRGcmVxVGVybXMoKWBgYCBmdW5jdGlvbiAoYWdhaW4gZm91bmQgaW4gdGhlIGBgYHRtYGBgIHBhY2thZ2UpLgoKYGBge3IgRmluZGluZyB0aG9zZSBmcmVxLXkgdGVybXN9CnNtc19kdG1fZnJlcV90cmFpbiA8LSBzbXNfZHRtX3RyYWluICU+JQogIGZpbmRGcmVxVGVybXMoNSkgJT4lCiAgc21zX2R0bV90cmFpblsgLCAuXQoKc21zX2R0bV9mcmVxX3Rlc3QgPC0gc21zX2R0bV90ZXN0ICU+JQogIGZpbmRGcmVxVGVybXMoNSkgJT4lCiAgc21zX2R0bV90ZXN0WyAsIC5dCmBgYAoKTm93IHdlIHNoYWxsIHdyaXRlIGEgZnVuY3Rpb24gdGhhdCBjb252ZXJ0cyBvdXIgc3BhcnNlIERUIG1hdHJpY2VzIGZyb20gbnVtZXJpYyB0byBjYXRlZ29yaWNhbCAieWVzL25vIiBtYXRyaWNlcyB0aGF0IG91ciBhbGdvcml0aG0gY2FuIHByb2Nlc3MuCgpgYGB7ciBDcmVhdGluZyBhIGNvbnZlcnRfY291bnRzIGZ1bmN0aW9ufQpjb252ZXJ0X2NvdW50cyA8LSBmdW5jdGlvbih4KSB7CiAgeCA8LSBpZmVsc2UoeCA+IDAsICJZZXMiLCAiTm8iKQp9CmBgYAoKQXBwbHlpbmcgb3VyIGBgYGNvbnZlcnRfY291bnRzYGBgIGZ1bmN0aW9uOgpgYGB7ciBDb252ZXJ0aW5nIHNwYXJzZSBudW1lcmljYWwgbWF0cml4IGludG8gWUVTL05PIG1hdHJpeH0Kc21zX3RyYWluIDwtIHNtc19kdG1fZnJlcV90cmFpbiAlPiUKICBhcHBseShNQVJHSU4gPSAyLCBjb252ZXJ0X2NvdW50cykKc21zX3Rlc3QgPC0gc21zX2R0bV9mcmVxX3Rlc3QgJT4lCiAgYXBwbHkoTUFSR0lOID0gMiwgY29udmVydF9jb3VudHMpCmBgYAoKIyMjIFN0ZXAgMyAtIHRyYWluaW5nIGEgbW9kZWwgb24gdGhlIGRhdGEKTm93IGNvbWVzIHRoZSBmYWlybHkgc3RyYWlnaHRmb3J3YXJkIHN0ZXAgb2YgdHJhaW5pbmcgb3VyIG1vZGVsIG9uIHRoZSBkYXRhIGFuZCB0aGVuIHVzaW5nIHRoYXQgY2xhc3NpZmllciB0byBtYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldC4gVGhpcyByZXF1aXJlcyB0aGUgYGBgZTEwNzFgYGAgcGFja2FnZSB0byBhcHBseSB0aGUgYGBgbmFpdmVCYXlzKClgYGAgZnVuY3Rpb246CgpgYGB7ciBCdWlsZGluZyB0aGUgY2xhc3NpZmllciBhbmQgb3VyIHByZWRpY3Rvcn0Kc21zX2NsYXNzaWZpZXIgPC0gbmFpdmVCYXllcyhzbXNfdHJhaW4sIHNtc190cmFpbl9sYWJlbHMpCnNtc19wcmVkIDwtIHByZWRpY3Qoc21zX2NsYXNzaWZpZXIsIHNtc190ZXN0KQpgYGAKIyMjIFN0ZXAgNCAtIGV2YWx1YXRpbmcgbW9kZWwgcGVyZm9ybWFuY2UKTm93IHdlIGNhbiB1c2UgdGhlIGBgYENyb3NzVGFibGUoKWBgYCBmdW5jdGlvbiBmcm9tIHRoZSBgYGBnbW9kZWxzYGBgIHBhY2thZ2UgdG8gc2VlIGhvdyBvdXIgcHJlZGljdGlvbnMgZmFyZWQuCmBgYHtyIGNyZWF0aW5nIGEgQ3Jvc3N0YWJsZX0KQ3Jvc3NUYWJsZShzbXNfcHJlZCwgc21zX3Rlc3RfbGFiZWxzLCBwcm9wLmNoaXNxID0gRkFMU0UsIGNoaXNxID0gRkFMU0UsIAogICAgICAgICAgIHByb3AudCA9IEZBTFNFLAogICAgICAgICAgIGRubiA9IGMoIlByZWRpY3RlZCIsICJBY3R1YWwiKSkKYGBgCk91ciBjcm9zcyB0YWJsZSBzaG93cyB1cyB0aGF0IHdlIGhhdmUgJFxhcHByb3ggOTcuNFwlJCBhY2N1cmFjdXkgaW4gb3VyIGNsYXNzaWZpZXIgcHJlZGljdGluZyB3aGV0aGVyIG9yIG5vdCBhIG1lc3NhZ2UgaXMgKipoYW0qKiBvciAqKnNwYW0qKi4gQ2FuIHdlIGRvIGJldHRlcj8KCiMjIyBTdGVwIDUgLSBJbXByb3ZpbmcgbW9kZWwgcGVyZm9ybWFuY2UKTGV0J3MgYXR0ZW1wdCB0byBpbXByb3ZlIG91ciBtb2RlbCBwZXJmb3JtYW5jZSBieSB1c2luZyBhIGRpZmZlcmVudCBsYXBsYWNlIHZhbHVlIGZvciBvdXIgY2xhc3NpZmllcgpgYGB7ciBtb2RlbCB0dW5pbmd9CnNtc19jbGFzc2lmaWVyMiA8LSBuYWl2ZUJheWVzKHNtc190cmFpbiwgc21zX3RyYWluX2xhYmVscywgbGFwbGFjZSA9IDEpCnNtc19wcmVkMiA8LSBwcmVkaWN0KHNtc19jbGFzc2lmaWVyMiwgc21zX3Rlc3QpCkNyb3NzVGFibGUoc21zX3ByZWQyLCBzbXNfdGVzdF9sYWJlbHMsIHByb3AuY2hpc3EgPSBGQUxTRSwgY2hpc3EgPSBGQUxTRSwgCiAgICAgICAgICAgcHJvcC50ID0gRkFMU0UsCiAgICAgICAgICAgZG5uID0gYygiUHJlZGljdGVkIiwgIkFjdHVhbCIpKQpgYGAKV2Ugb2J0YWluIGFuIGV2ZW4gYmV0dGVyIHJlc3VsdCBvZiAkOTcuNVwlJCBhY2N1cmFjeSEgQnV0IHdoYXQgZGlkIGFkZGluZyBhIExhcGxhY2UgU21vb3RoaW5nIFZhcmlhYmxlIGRvIGZvciB1cz8KCiMjIyMgTGFwbGFjZSBTbW9vdGhpbmcKVGhhdCBpcyBzbW9vdGhpbmc/IEluIGEgcmVzZWFyY2ggcGFwZXIgZnJvbSAxOTk2LCBwcm9mZXNzb3JzIENoZW4gYW5kIEdvb2RtYW4gb2YgSGFydmFyZCBVbml2ZXJzaXR5IHN0YXRlZCB0aGF0ICIqU21vb3RoaW5nKiBpcyBhIHRlY2huaXF1ZSBlc3NlbnRpYWwgaW4gdGhlIGNvbnN0cnVjdGlvbiBvZiAqbiotZ3JhbSBsYW5ndWFnZSBtb2RlbHMuLi4gW29yXSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb25zIG92ZXIgc3RyaW5ncyAkUFxsZWZ0KHNccmlnaHQpJCB0aGF0IGF0dGVtcHRzIHRvIHJlZmxlY3QgdGhlIGZyZXF1cmVuY3kgd2l0aCB3aGljaCBlYWNoIHN0cmluZyAkcyQgb2NjdXJzIGFzIGEgc2VudGVuY2UgaW4gbmF0dXJhbCB0ZXh0LiJbQGdvb2RtOTZdClNtb290aGluZyBzaW1wbHkgYXZvaWRzIGdpdmluZyB3b3JkcyB0aGF0IGFwcGVhciBsZXNzIGZyZXF1ZW50bHkgbGVzcyB3ZWlnaHQgdGhhbiB3b3JkcyB0aGF0IGFwcGVhciBtb3JlIGZyZXF1ZW50bHkuIEZvciBleGFtcGxlLCBpZiB0aGUgd29yZCAiU3VwZXJjYWxpZnJhZ2lsaXN0aWNleHBpYWxpZG9jaW91cyIgb25seSBvY2N1cnMgaW4gb3VyIHRleHQgbWVzc2FnZXMgb25jZSBpbiBhIHNwYW0gbWVzc2FnZSwgc2hvdWxkIGl0IGJlIGNvbnNpZGVyZWQgbm9pc2UgdGhhdCBpdCBvY2N1cnMgYXQgYWxsPyBTbW9vdGhpbmcgYXZvaWRzIHRoaXMgZW50aXJlbHkuIApJbiB0aGUgYm9vayAqSW50cm9kdWN0aW9uIHRvIEluZm9ybWF0aW9uIHJldHJpZXZhbCwgdGhlIGF1dGhvcnMgaGF2ZSB0aGlzIHRvIHNheSBhYm91dCBzbW9vdGhpbmc6ICJbVF1oZSBwcm9iYWJpbGl0eSBvZiB3b3JkcyBvY2N1cnJpbmcgb25jZSBpbiB0aGUgZG9jdW1lbnQgaXMgbm9ybWFsbHkgb3Zlci1lc3RpbWF0ZWQsIHNpbmNlIHRoZWlyIG9uZSBvY2N1cnJlbmNlIHdhcyBwYXJ0bHkgYnkgY2hhbmNlLi4uIHRoZSBzbW9vdGhpbmcgb2YgdGVybXMgYWN0dWFsbHkgaW1wbGVtZW50cyBtYWpvciBwYXJ0cyBvZiB0aGUgdGVybSB3ZWlnaHRpbmcgY29tcG9uZW50LiBJdCBpcyBub3QganVzdCB0aGF0IGFuIHVuc21vb3RoZWQgbW9kZWwgaGFzIGNvbmp1bmN0aXZlIHNlbWFudGljcyBbb3Igd29yZHMgb2NjdXJpbmcgYnkgY2hhbmNlXTsgYW4gdW5zbW9vdGhlZCBtb2RlbCB3b3JrcyBiYWRseSBiZWNhdXNlIGl0IGxhY2tzIHBhcnRzIG9mIHRoZSB0ZXJtIHdlaWdodGluZyBjb21wb250ZW50cyJbQG1hbm5pMDhdLgoKKkxhcGxhY2Ugc21vb3RoaW5nKiBpcyBzaW1wbHkgb25lIG9mIHRoZSBtb3JlIGNvbW1vbiBtZXRob2RzIG9mIHNtb290aGluZyB0byBiZSB1c2VkLiBJdCBpcyBhbHNvIGtub3duIGFzICphZGRpdGl2ZSBzbW9vdGhpbmcqIG9yICpMaWRzdG9uZSBzbW9vdGhpbmcqIGFuZCBjYW4gYmUgZXhwcmVzc2VkIGFzCiQkClxoYXR7UH1cbGVmdCh0XHZlcnQgZFxyaWdodCkgPSBcZnJhY3t0Zl97dCxkfSArIFxhbHBoYVxoYXR7UH1cbGVmdCh0XHZlcnQgTV97Y31ccmlnaHQpfXtMX3tkfStcYWxwaGF9CiQkCndoZXJlICR0Zl97dCxkfSQgaXMgdGhlIHJhdyB0ZXJtIGZyZXF1ZW5jeSBvZiB0ZXJtICR0JCBpbiBkb2N1bWVudCAkZCQsICRMX3tkfSQgaXMgdGhlIG51bWJlciBvZiB0b2tlbnMgaW4gdGhlIGRvY3VtZW50LCAkTV97Y30kIGlzIGEgbGFuZ3VhZ2UgbW9kZWwgYnVpbHQgZnJvbSBhbGwgZG9jdW1lbnRzLCAkXGFscGhhJCByZWZlcnMgdG8gYSAqcHNldWRvY291bnQqIHdoaWNoIGlzIGEgbnVtYmVyIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHVzZSBvZiBhIHVuaWZvcm0gZGlzdHJpYnV0aW9uIG9mIHRoZSBwcmlvciBwcm9iYWJpbGl0eSBhbmQgcmVwcmVzZW50cyBvdXIgYmVsaWVmIGluIHRoZSBzdHJlbmd0aCBvZiB1bmlmb3JtaXR5IG9mIG91ciBkaXN0cmlidXRpb24uCgpMb25nIHN0b3J5IHNob3J0OyBvdXIgdXNlIG9mIExhcGxhY2UgU21vb3RoaW5nIG1ha2VzIGEgZ3JlYXQgbW9kZWwgZXZlbiBiZXR0ZXIgYmVjYXVzZSBpdCBoZWxwcyB0byB3ZWlnaHQgdGhlIHdvcmRzIGluIG91ciBjb3JwdXMuCgpbXjFdOiBKdXN0IGFzIGEgc2hhbWVsZXNzIHBsdWcgZm9yIGBgYHRpZHl2ZXJzZWBgYFtAd2lja2gxN10sIGJlY2F1c2UgSSBhYnNvbHV0ZWx5IGxvdmUgdGhlIHBhY2thZ2UgYW5kIHRoaW5rIHRoYXQgaXQncyBhbiBhbWF6aW5nbHkgdXNlZnVsIGFuZCBkYXJlIEkgc2F5ICpmdW4qIHBhY2thZ2UgdG8gd29yayB3aXRoLgoKIyMgQ3JlYXRpbmcgYW5hbHlzaXMgb24gaXJpcyBkYXRhc2V0CkFnYWluIHdlIHdpbGwgYmUgdGFraW5nIGEgbG9vayBhdCB0aGUgaXJpcyBkYXRhc2V0W0BhbmRlcjM2XQoKIyMjIENvbGxlY3RpbmcgZGF0YQpEYXRhIGlzIGFscmVhZHkgYXZhaWxhYmxlIGluIGJhc2UgUgpgYGB7ciBJbXBvcnQgaXJpcyBkYXRhc2V0fQppcmlzLmRmIDwtIGlyaXMKYGBgCgojIyMgRXhwbG9yaW5nIGFuZCBwcmVwYXJpbmcgdGhlIGRhdGEKYGBge3IgU3BsaXR0aW5nIGRhdGEgaW50byB0ZXN0IC8gdHJhaW5pbmcgc2V0c30KcmFuZG9tIDwtIHNhbXBsZShucm93KGlyaXMuZGYpLCAxMTIsIHJlcGxhY2UgPSBGQUxTRSkKaXJpc190ZXN0IDwtIGlyaXMuZGZbLXJhbmRvbSwgXQppcmlzX3RyYWluIDwtIGlyaXMuZGZbcmFuZG9tLCBdCmBgYAoKIyMjIFRyYWluaW5nIGEgbW9kZWwgb24gdGhlIGRhdGEKYGBge3IgVHJhaW5pbmcgb3VyIG1vZGVsfQppcmlzX2NsYXNzaWZpZXIgPC0gbmFpdmVCYXllcyhpcmlzX3RyYWluWyAsIC01XSwgaXJpc190cmFpbiRTcGVjaWVzKQppcmlzX3ByZWQgPC0gcHJlZGljdChpcmlzX2NsYXNzaWZpZXIsIGlyaXNfdGVzdCkKYGBgCgojIyMgRXZhbHVhdGluZyBtb2RlbCBwZXJmb3JtYW5jZQpgYGB7ciBJcmlzIENyb3NzIFRhYmxlfQpDcm9zc1RhYmxlKGlyaXNfcHJlZCwgaXJpc190ZXN0JFNwZWNpZXMsIHByb3AuY2hpc3EgPSBGQUxTRSwgY2hpc3EgPSBGQUxTRSwgCiAgICAgICAgICAgcHJvcC50ID0gRkFMU0UsCiAgICAgICAgICAgZG5uID0gYygiUHJlZGljdGVkIiwgIkFjdHVhbCIpKQpgYGAKVGhpcyBpcyBhYm91dCAkOTJcJSQgZWZmZWN0aXZlLiBMZXQncyBzbWFjayBvbiB0aGF0IExhcGxhY2UgU21vb3RoaW5nIGFuZCBzZWUgd2hhdCBpdCBkb2VzIGZvciB1cy4KIyMjIEltcHJvdmluZyBtb2RlbCBwZXJmb3JtYW5jZQpgYGB7ciBJbXByb3ZlIHRoYXQgbW9kZWwsIHlvfQppcmlzX2NsYXNzaWZpZXIyIDwtIG5haXZlQmF5ZXMoaXJpc190cmFpblsgLCAtNV0sIGlyaXNfdHJhaW4kU3BlY2llcywgbGFwbGFjZSA9IDEpCmlyaXNfcHJlZDIgPC0gcHJlZGljdChpcmlzX2NsYXNzaWZpZXIyLCBpcmlzX3Rlc3QpCgpDcm9zc1RhYmxlKGlyaXNfcHJlZDIsIGlyaXNfdGVzdCRTcGVjaWVzLCBwcm9wLmNoaXNxID0gRkFMU0UsIGNoaXNxID0gRkFMU0UsIAogICAgICAgICAgIHByb3AudCA9IEZBTFNFLAogICAgICAgICAgIGRubiA9IGMoIlByZWRpY3RlZCIsICJBY3R1YWwiKSkKYGBgCldlbGwsIHRoYXQgZGlkbid0IGNoYW5nZSBtdWNoLgoKIyMgQ29uY2x1c2lvbgpXZSBkaWQgc2hvdyB0aGUgaGlnaCBhY2N1cmFjeSB0aGF0IHRoZSBOYWl2ZSBCYXllcyBhbGdvcml0aG0gaGFzIGZvciBjbGFzc2lmeWluZyB0ZXh0IGJhc2UgZGF0YSwgYW5kIHRoYXQgaXQgaXMgbGVzcyBhY2N1cmF0ZSBvbiBudW1lcmljYWwgZGF0YS4KVGhlIHJlYXNvbiB0aGF0IHRoZSBpcmlzIGRhdGEgc2V0IHdhcyBzZWxlY3Qgd2FzIGJlY2F1c2UgSSByZWFsbHkgbGlrZSB3b3JraW5nIHdpdGggaXQgYW5kIGFsc28gYmVjYXVzZSBJIHdhbnRlZCB0byB0cnkgdG8gbWFrZSBhIG1vZGVsIHdpdGggbnVtZXJpY2FsIHZhbHVlcyB0aGUgc2Vjb25kIHRpbWUgYXJvdW5kLgoKIyMgUmVmZXJlbmNlcwo=