The general idea

Data transfer is highly controlled. The key notions are authentication and protocol.

Downloading toots with rtoot

There are several packages that run an interface with twitter: rtweet, RTwitterAPI, streamR and twitteR.
But since Auth V2, we will need RTwitterV2! But this only runs on R v4.2!
Documentation: https://github.com/MaelKubli/RTwitterV2.
Recent packages are better because firms update their API policies (and access), thus old protocols sometimes do not work!
Unfortunately, the Twitter API is no longer free!
Hence, in this notebook, we will test the competitor: mastodon!
The package for this will be rtoot.

First things first

First, the packages. Download…

if(!require(rtoot)){install.packages("rtoot")}

… and activate.

library(tidyverse)
library(plotly)
library(rtoot)

Authentication

Second: authentication You have to choose a particular instance of the network. Personally, I am registered on “sciences.social”, the largest one is “mastodon.social” (see https://mastodonservers.net/servers/top) => Write the answer without the quotation marks and choose a public tocken

rtoot::auth_setup(
  instance = "mastodon.social",
  type = "public"
)
Token of type "public" for instance mastodon.social is valid
<mastodon bearer token> for instance: mastodon.social of type: public 
# get_timeline_hashtag(hashtag = "rstats", 
#                      instance = "mastodon.social",
#                      limit = 200)

Authentication can be an important part of the process. For more info on that:
- https://cran.r-project.org/web/packages/googlesheets/vignettes/managing-auth-tokens.html
- https://httr.r-lib.org/reference/index.html (section Authentication)
- https://blog.r-hub.io/2021/01/25/oauth-2.0/

Extraction

If no error appears, we are ready to query. Depending on the number of requested tweets, this can take some time.

There are different types of queries that the packages allows.
For instance, below we use the get_timeline_hashtag function to access toots that include one particular term, the “hashtag”.

search_term <- "election"
toots <- get_timeline_hashtag(hashtag = search_term, 
                              instance = "mastodon.social",
                              limit = 2000)

Text mining

References

The reference book is: https://www.tidytextmining.com
A great interactive tutorial: https://juliasilge.shinyapps.io/learntidytext/
And the package is:

if(!require(tidytext)){install.packages("tidytext", repos = "https://cloud.r-project.org/")}
library(tidytext)

(see also: https://quanteda.io/index.html)

Data retrieval

Now, let’s move forward to simple text analysis. First, we need to prepare the data! (as usual)

tokens <- toots %>% 
    select(id, content) %>%             # Keeps only id and text/content of the tweet
    unnest_tokens(word, content)        # Creates tokens!
tokens

Let’s have a look at word frequencies.

tokens %>%
    count(word, sort = TRUE)

This is polluted by small words. Let’s filter that (FIRST METHOD).

tokens %>% mutate(length = nchar(word))

Data frequencies

Now let’s omit the small words (smaller than 5 characters).
NOTE: all the thresholds below depend on the sample!

tokens %>%
    mutate(length = nchar(word)) %>%
    filter(length > 4) %>%             # Keep words with length larger than 4
    count(word, sort = TRUE) %>%       # Count words
    head(21) %>%                       # Keep only top 12 words
    ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words") + theme_bw()

A better way to proceed is to remove “stop words” like “a”, “I”, “of”, “the”, etc (SECOND METHOD). Also, it would make sense to remove the search item and “https”.

data("stop_words")
tidy_tokens <- tokens %>% 
    anti_join(stop_words)                    # Remove unrelevant terms
tidy_tokens %>%
    count(word, sort = TRUE) %>%             # Count words
    head(20) %>%                             # Keep only top 15 words
    ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words") + theme_bw()

Problem: strange characters remain. We are going to remove them by converting the text to ASCII format and omit NA data.

new_stop_words <- c("https", "span", "class", "href", "target", "_blank", "rel", "tag",
                    "mastodon.social", "ellipsis", "mastodon.online", "mstdn.social", "amp",
                    "http", "invisible", "03", search_term, tolower(search_term), "d0", "src",
                    "tags", "mention", "noreferrer", "noopener", "nofollow", "hashtag", "translate",
                    "www", "url", "die", "der", "und", "a", "p", "br", "1", "2", "01", "02")
tidy_tokens <- tokens %>% 
    anti_join(stop_words) %>%                            # Remove unrelevant
    mutate(word = iconv(word, from = "UTF-8", to = "ASCII")) %>% # Put in latin format
    na.omit() %>%                                        # Remove missing
    filter(nchar(word) > 2,                              # Remove small words
           !(word %in% new_stop_words)  # search_term defined above
    )
tidy_tokens %>%
    count(word, sort = TRUE) %>%         # Count words
    head(30) %>%                         # Keep only top words
    ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words") + theme_bw()

Perfect!

Word cloud

This data can also be shown with a word cloud. We simply use the wordcloud package: https://cran.r-project.org/web/packages/wordcloud/index.html

The package wordcloud2 adds a few features: https://cran.r-project.org/web/packages/wordcloud2/vignettes/wordcloud.html

if(!require(wordcloud)){install.packages("wordcloud")}
library(wordcloud)
cloud_data <- tidy_tokens %>% count(word)
wordcloud(words = cloud_data$word, 
          freq = cloud_data$n, min.freq = 10,
          max.words = 82, random.order = FALSE, rot.per = 0.15, 
          colors = brewer.pal(8, "Dark2")) 

n-grams

See https://www.tidytextmining.com/ngrams.html

toots %>% 
    mutate(id = 1:nrow(toots)) %>%        # This creates a tweet id
    select(id, content, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(bigram, content, token = "ngrams", n = 2) %>%
    filter(!(bigram %in% c("span a", "a href", "a a", "_blank span", "href https", "span class",
                           "a p", "p p", "br a", "target _blank", "noreferrer target", "span span",
                           "hastag rel", "class mention", "rel nofollow", "mention hashtag",
                           "class invisible", "invisible https", "p a",
                           "nofollow noopener", "noopener noreferrer", "hashtag rel"))) %>%
    group_by(bigram) %>%
    count(sort = T) %>%
    head(20) %>%
    ggplot(aes(y = reorder(bigram, n), x = n)) + 
  geom_col() + theme_bw() + ylab("bigrams")

Again: same issue with stop words! So we must remove them again. But it’s more complicated now. We can use the separate() function to help us.

toots %>% 
    mutate(id = row_number()) %>%         # This creates a tweet id
    select(id, content, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(bigram, content, token = "ngrams", n = 2) %>%
    mutate(bigram = iconv(bigram, from = "UTF-8", to = "ASCII")) %>%
    na.omit() %>%
    separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
    filter(!(word1 %in% c(new_stop_words, stop_words$word)),
           !(word2 %in% c(new_stop_words, stop_words$word))) %>%
    group_by(bigram) %>%
    count(sort = T) %>%
    head(24) %>%
    ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col() + ylab("Bi-gram") +
    theme_bw()

cloud_data <- toots %>% 
    mutate(id = row_number()) %>%         # This creates a tweet id
    select(id, content, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(bigram, content, token = "ngrams", n = 2) %>%
    mutate(bigram = iconv(bigram, from = "UTF-8", to = "ASCII")) %>%
    na.omit() %>%
    separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
    filter(!(word1 %in% c(new_stop_words, stop_words$word)),
           !(word2 %in% c(new_stop_words, stop_words$word))) %>%
    group_by(bigram) %>%
    count(sort = T) |>
  mutate(length = nchar(bigram)) |>
  filter(length < 15)
  
cloud_data
wordcloud(words = cloud_data$bigram, 
          freq = cloud_data$n, min.freq = 10,
          max.words = 35, random.order = FALSE, rot.per = 0.10, 
          colors = brewer.pal(8, "Dark2")) 

Sentiment

This section is inspired from: https://www.tidytextmining.com/sentiment.html
Sometimes, you may be asked in the process if you really want to download data (lexicons).
Just say yes in the console (type the correct answer: if not, you will be blocked/struck).

First, we need to load some sentiment lexicon. AFINN is one such sentiment database.

if(!require(textdata)){install.packages("textdata", repos = "https://cloud.r-project.org/")}
Loading required package: textdata
library(tidytext)
library(textdata)
afinn <- get_sentiments("afinn")
afinn
afinn |> filter(value > 3)

To create a nice visualization, we need to extract the time of the tweets.

tokens_time <- toots %>% 
    mutate(id = row_number()) %>%         # This creates a tweet id
    select(id, content, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(word, content)          # Creates tokens!
tokens_time

We then use inner_join() to merge the two sets. This function removes the cases when a match does not occur.

library(lubridate)
sentiment <- tokens_time %>% 
    inner_join(afinn) %>%
    mutate(day = day(created_at),
           hour = hour(created_at) / 24,
           minute = minute(created_at) / 60 / 24,
           time = day + hour + minute)
Joining with `by = join_by(word)`
sentiment

We then compute the average sentiment, minute-by-minute, or day-by-day, depending on frequency.
Of course, average sentiment can be misleading. Indeed, if a text contains the terms “I’m not happy”, then only “happy” will be tagged, which is the opposite of the intended meaning.

sentiment %>%
  mutate(date = as.Date(created_at)) |>
    group_by(date) %>%
    #filter(year(date)==2024) |>
    summarise(avg_sentiment = mean(value)) %>%
    ggplot(aes(x = date, y = avg_sentiment)) + geom_col() + theme_bw()

What about emotions? The NRC lexicon categorizes emotions. Below, we order emotions. The most important impact is the dichotomy between positive & negative emotions.

nrc <- get_sentiments("nrc")
nrc <- nrc %>%
    mutate(sentiment = as.factor(sentiment),
           sentiment = recode_factor(sentiment,
                                     joy = "joy",
                                     trust = "trust",
                                     surprise = "surprise",
                                     anticipation = "anticipation",
                                     positive = "positive",
                                     negative = "negative",
                                     sadness = "sadness",
                                     anger = "anger",
                                     fear = "fear",
                                     digust = "disgust",
                                     .ordered = T))
nrc

We then create the merged dataset.

emotions <- tokens_time %>% 
    inner_join(nrc) %>%                     # Merge data with sentiment
    mutate(date = as.Date(created_at))      # Create day column
Joining with `by = join_by(word)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
emotions                                    # Show the result

The merging has reduced the size of the dataset, but there still remains enough to pursue the study.
Finally, we move to the pivot-table that counts emotions for each day.

g <- emotions %>% 
    group_by(date, sentiment) %>%
    summarise(intensity = n()) %>%
    filter(year(date) == 2024) |>
    ggplot(aes(x = date, y = intensity, fill = sentiment)) + geom_col() + 
    theme(axis.text.x = element_text(angle = 80, 
                                     size = 10,
                                     hjust = 1)) + xlab("Time") +
    scale_fill_viridis_d(option = "magma", direction = -1) + theme_bw()
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.
ggplotly(g)

This can also be shown in percentage format.

g <- emotions %>% 
    group_by(date, sentiment) %>%
    filter(year(date) == 2024) |>
    summarise(intensity = n()) %>%
    ggplot(aes(x = date, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
    theme(axis.text.x = element_text(angle = 80, 
                                     size = 10,
                                     hjust = 1)) + xlab("Time") +
    scale_fill_viridis_d(option = "magma", direction = -1) + theme_bw() + 
    geom_hline(yintercept = 0.5, linetype = 2)
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.
ggplotly(g)
emotions %>% 
    mutate(sentiment = if_else(sentiment < "negative", "positive", "negative")) %>% 
    group_by(date, sentiment) %>%
    summarise(intensity = n()) %>%
    ggplot(aes(x = date, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
    theme_bw() + 
    theme(axis.text.x = element_text(angle = 80, 
                                     size = 10,
                                     hjust = 1)) + xlab("Time") +
    geom_hline(yintercept = 0.5) + 
    scale_fill_manual(values = c("#223333", "#FFBB99")) 
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.

Advanced sentiment

The problem with the preceding methods is that they don’t take into account valence shifters (i.e., negators, amplifiers (intensifiers), de-amplifiers (downtoners), and adversative conjunctions). If a tweet says not happy, counting the word happy is not a good idea! The package sentimentr is built to circumvent these issues: have a look at https://github.com/trinker/sentimentr
(see also: https://www.sentometrics.org and the book Supervised Machine Learning for Text Analysis in R hosted at https://smltar.com)

I haven’t tested aws.comprehend, but it seems promising: https://github.com/cloudyr/aws.comprehend

if(!require(sentimentr)){install.packages(c("sentimentr", "textcat"))}
library(sentimentr)
library(textcat)

First, let’s keep only the tweets written in English!

# toots_en <- toots %>%
#     mutate(language = textcat(content)) %>%
#     filter(language == "english") %>%
#     dplyr::select(created_at, content)

toots_en <- toots |> filter(language == "en")

NOTE: the code above was used to show the function textcat: the language is already coded in the tweets via the lang column/variable. (it suffices to keep the instances for which lang == “en”)

Next, we compute advanced sentiment.

tweet_sent <- toots_en$content %>%
    get_sentences() %>%  # Intermediate function
    sentiment()          # Sentiment!
tweet_sent

NOTE: depending on frequency issues, it is better to analyze at daily or hourly scales. If a word is very popular, then, higher frequencies are more relevant.

    ggplot(aes(x = date, y = avg_sent)) + geom_col() 
Error in `fortify()`:
! `data` must be a <data.frame>, or an object coercible by `fortify()`, not a <uneval> object.
ℹ Did you accidentally pass `aes()` to the `data` argument?
Backtrace:
 1. ggplot2::ggplot(aes(x = date, y = avg_sent))
 2. ggplot2:::ggplot.default(aes(x = date, y = avg_sent))
 4. ggplot2:::fortify.default(data, ...)

Resources

Below, a short list of resources (to access third-party data):

Possibly deprecated:
- Facebook: https://cran.r-project.org/web/packages/Rfacebook/index.html
- Instagram: https://cran.r-project.org/web/packages/instaR/index.html

LS0tCnRpdGxlOiAiVGhpcmQgcGFydHkgZGF0YSBhbmQgYmFzaWMgdGV4dCBtaW5pbmciCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgojIFRoZSBnZW5lcmFsIGlkZWEKCkRhdGEgdHJhbnNmZXIgaXMgaGlnaGx5IGNvbnRyb2xsZWQuIFRoZSBrZXkgbm90aW9ucyBhcmUgKiphdXRoZW50aWNhdGlvbioqIGFuZCAqKnByb3RvY29sKiouCgojIERvd25sb2FkaW5nIHRvb3RzIHdpdGggKnJ0b290KgoKVGhlcmUgYXJlIHNldmVyYWwgcGFja2FnZXMgdGhhdCBydW4gYW4gaW50ZXJmYWNlIHdpdGggdHdpdHRlcjogKnJ0d2VldCosICpSVHdpdHRlckFQSSosICpzdHJlYW1SKiBhbmQgKnR3aXR0ZVIqLgkKQnV0IHNpbmNlIEF1dGggVjIsIHdlIHdpbGwgbmVlZCAqKlJUd2l0dGVyVjIqKiEgQnV0IHRoaXMgb25seSBydW5zIG9uIFIgdjQuMiEgIApEb2N1bWVudGF0aW9uOiBodHRwczovL2dpdGh1Yi5jb20vTWFlbEt1YmxpL1JUd2l0dGVyVjIuICAgIApSZWNlbnQgcGFja2FnZXMgYXJlIGJldHRlciBiZWNhdXNlIGZpcm1zIHVwZGF0ZSB0aGVpciBBUEkgcG9saWNpZXMgKGFuZCBhY2Nlc3MpLCB0aHVzIG9sZCBwcm90b2NvbHMgc29tZXRpbWVzIGRvIG5vdCB3b3JrISAgIApVbmZvcnR1bmF0ZWx5LCB0aGUgVHdpdHRlciBBUEkgaXMgbm8gbG9uZ2VyIGZyZWUhICAgCkhlbmNlLCBpbiB0aGlzIG5vdGVib29rLCB3ZSB3aWxsIHRlc3QgdGhlIGNvbXBldGl0b3I6IFsqKm1hc3RvZG9uKipdKGh0dHBzOi8vam9pbm1hc3RvZG9uLm9yZy8pISAgClRoZSBwYWNrYWdlIGZvciB0aGlzIHdpbGwgYmUgWyoqcnRvb3QqKl0oaHR0cHM6Ly9zY2hvY2hhc3RpY3MuZ2l0aHViLmlvL3J0b290LykuCgojIyBGaXJzdCB0aGluZ3MgZmlyc3QKCioqRmlyc3QqKiwgdGhlIHBhY2thZ2VzLiBEb3dubG9hZC4uLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQppZighcmVxdWlyZShydG9vdCkpe2luc3RhbGwucGFja2FnZXMoInJ0b290Iil9CmBgYAoKLi4uIGFuZCBhY3RpdmF0ZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJ0b290KQpgYGAKCiMjIEF1dGhlbnRpY2F0aW9uCgoqKlNlY29uZCoqOiBhdXRoZW50aWNhdGlvbgpZb3UgaGF2ZSB0byBjaG9vc2UgYSBwYXJ0aWN1bGFyIGluc3RhbmNlIG9mIHRoZSBuZXR3b3JrLgpQZXJzb25hbGx5LCBJIGFtIHJlZ2lzdGVyZWQgb24gInNjaWVuY2VzLnNvY2lhbCIsIHRoZSBsYXJnZXN0IG9uZSBpcyAibWFzdG9kb24uc29jaWFsIiAoc2VlIGh0dHBzOi8vbWFzdG9kb25zZXJ2ZXJzLm5ldC9zZXJ2ZXJzL3RvcCkKPT4gV3JpdGUgdGhlIGFuc3dlciB3aXRob3V0IHRoZSBxdW90YXRpb24gbWFya3MgYW5kIGNob29zZSBhIHB1YmxpYyB0b2NrZW4KCmBgYHtyfQpydG9vdDo6YXV0aF9zZXR1cCgKICBpbnN0YW5jZSA9ICJtYXN0b2Rvbi5zb2NpYWwiLAogIHR5cGUgPSAicHVibGljIgopCmBgYAoKYGBge3J9CiMgZ2V0X3RpbWVsaW5lX2hhc2h0YWcoaGFzaHRhZyA9ICJyc3RhdHMiLCAKIyAgICAgICAgICAgICAgICAgICAgICBpbnN0YW5jZSA9ICJtYXN0b2Rvbi5zb2NpYWwiLAojICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gMjAwKQpgYGAKCgpBdXRoZW50aWNhdGlvbiBjYW4gYmUgYW4gaW1wb3J0YW50IHBhcnQgb2YgdGhlIHByb2Nlc3MuIEZvciBtb3JlIGluZm8gb24gdGhhdDogIAotIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nb29nbGVzaGVldHMvdmlnbmV0dGVzL21hbmFnaW5nLWF1dGgtdG9rZW5zLmh0bWwgICAKLSBodHRwczovL2h0dHIuci1saWIub3JnL3JlZmVyZW5jZS9pbmRleC5odG1sIChzZWN0aW9uIEF1dGhlbnRpY2F0aW9uKSAgIAotIGh0dHBzOi8vYmxvZy5yLWh1Yi5pby8yMDIxLzAxLzI1L29hdXRoLTIuMC8KCiMjIEV4dHJhY3Rpb24KCklmIG5vIGVycm9yIGFwcGVhcnMsIHdlIGFyZSByZWFkeSB0byBxdWVyeS4gRGVwZW5kaW5nIG9uIHRoZSBudW1iZXIgb2YgcmVxdWVzdGVkIHR3ZWV0cywgdGhpcyBjYW4gdGFrZSBzb21lIHRpbWUuICAKClRoZXJlIGFyZSBkaWZmZXJlbnQgdHlwZXMgb2YgcXVlcmllcyB0aGF0IHRoZSBwYWNrYWdlcyBhbGxvd3MuICAgCkZvciBpbnN0YW5jZSwgYmVsb3cgd2UgdXNlIHRoZSAqKmdldF90aW1lbGluZV9oYXNodGFnKiogZnVuY3Rpb24gdG8gYWNjZXNzIHRvb3RzIHRoYXQgaW5jbHVkZSBvbmUgcGFydGljdWxhciB0ZXJtLCB0aGUgImhhc2h0YWciLiAgCgoKCmBgYHtyfQpzZWFyY2hfdGVybSA8LSAiZWxlY3Rpb24iCnRvb3RzIDwtIGdldF90aW1lbGluZV9oYXNodGFnKGhhc2h0YWcgPSBzZWFyY2hfdGVybSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluc3RhbmNlID0gIm1hc3RvZG9uLnNvY2lhbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gMjAwMCkKYGBgCgoKCiMgVGV4dCBtaW5pbmcKCiMjIFJlZmVyZW5jZXMKVGhlIHJlZmVyZW5jZSBib29rIGlzOiBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20gICAgICAKQSBncmVhdCBpbnRlcmFjdGl2ZSB0dXRvcmlhbDogaHR0cHM6Ly9qdWxpYXNpbGdlLnNoaW55YXBwcy5pby9sZWFybnRpZHl0ZXh0LyAgICAKQW5kIHRoZSBwYWNrYWdlIGlzOiAgCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHRpZHl0ZXh0KSl7aW5zdGFsbC5wYWNrYWdlcygidGlkeXRleHQiLCByZXBvcyA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmcvIil9CmxpYnJhcnkodGlkeXRleHQpCmBgYAoKKHNlZSBhbHNvOiBodHRwczovL3F1YW50ZWRhLmlvL2luZGV4Lmh0bWwpCgojIyBEYXRhIHJldHJpZXZhbAoKTm93LCBsZXQncyBtb3ZlIGZvcndhcmQgdG8gc2ltcGxlIHRleHQgYW5hbHlzaXMuIEZpcnN0LCB3ZSBuZWVkIHRvIHByZXBhcmUgdGhlIGRhdGEhIChhcyB1c3VhbCkKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zIDwtIHRvb3RzICU+JSAKICAgIHNlbGVjdChpZCwgY29udGVudCkgJT4lICAgICAgICAgICAgICMgS2VlcHMgb25seSBpZCBhbmQgdGV4dC9jb250ZW50IG9mIHRoZSB0d2VldAogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCBjb250ZW50KSAgICAgICAgIyBDcmVhdGVzIHRva2VucyEKdG9rZW5zCmBgYAoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgd29yZCBmcmVxdWVuY2llcy4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCmBgYAoKVGhpcyBpcyBwb2xsdXRlZCBieSBzbWFsbCB3b3Jkcy4gTGV0J3MgZmlsdGVyIHRoYXQgKCpGSVJTVCBNRVRIT0QqKS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JSBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpCmBgYAoKCiMjIERhdGEgZnJlcXVlbmNpZXMKTm93IGxldCdzIG9taXQgdGhlIHNtYWxsIHdvcmRzIChzbWFsbGVyIHRoYW4gNSBjaGFyYWN0ZXJzKS4gICAKKipOT1RFKio6IGFsbCB0aGUgdGhyZXNob2xkcyBiZWxvdyBkZXBlbmQgb24gdGhlIHNhbXBsZSEgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUKICAgIG11dGF0ZShsZW5ndGggPSBuY2hhcih3b3JkKSkgJT4lCiAgICBmaWx0ZXIobGVuZ3RoID4gNCkgJT4lICAgICAgICAgICAgICMgS2VlcCB3b3JkcyB3aXRoIGxlbmd0aCBsYXJnZXIgdGhhbiA0CiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjEpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdG9wIDEyIHdvcmRzCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiV29yZHMiKSArIHRoZW1lX2J3KCkKYGBgCgpBIGJldHRlciB3YXkgdG8gcHJvY2VlZCBpcyB0byByZW1vdmUgInN0b3Agd29yZHMiIGxpa2UgImEiLCAiSSIsICJvZiIsICJ0aGUiLCBldGMgKCpTRUNPTkQgTUVUSE9EKikuCkFsc28sIGl0IHdvdWxkIG1ha2Ugc2Vuc2UgdG8gcmVtb3ZlIHRoZSBzZWFyY2ggaXRlbSBhbmQgImh0dHBzIi4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KZGF0YSgic3RvcF93b3JkcyIpCnRpZHlfdG9rZW5zIDwtIHRva2VucyAlPiUgCiAgICBhbnRpX2pvaW4oc3RvcF93b3JkcykgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHVucmVsZXZhbnQgdGVybXMKdGlkeV90b2tlbnMgJT4lCiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjApICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdG9wIDE1IHdvcmRzCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiV29yZHMiKSArIHRoZW1lX2J3KCkKYGBgCgoqKlByb2JsZW0qKjogc3RyYW5nZSBjaGFyYWN0ZXJzIHJlbWFpbi4gV2UgYXJlIGdvaW5nIHRvIHJlbW92ZSB0aGVtIGJ5IGNvbnZlcnRpbmcgdGhlIHRleHQgdG8gQVNDSUkgZm9ybWF0IGFuZCBvbWl0ICpOQSogZGF0YS4gCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9Cm5ld19zdG9wX3dvcmRzIDwtIGMoImh0dHBzIiwgInNwYW4iLCAiY2xhc3MiLCAiaHJlZiIsICJ0YXJnZXQiLCAiX2JsYW5rIiwgInJlbCIsICJ0YWciLAogICAgICAgICAgICAgICAgICAgICJtYXN0b2Rvbi5zb2NpYWwiLCAiZWxsaXBzaXMiLCAibWFzdG9kb24ub25saW5lIiwgIm1zdGRuLnNvY2lhbCIsICJhbXAiLAogICAgICAgICAgICAgICAgICAgICJodHRwIiwgImludmlzaWJsZSIsICIwMyIsIHNlYXJjaF90ZXJtLCB0b2xvd2VyKHNlYXJjaF90ZXJtKSwgImQwIiwgInNyYyIsCiAgICAgICAgICAgICAgICAgICAgInRhZ3MiLCAibWVudGlvbiIsICJub3JlZmVycmVyIiwgIm5vb3BlbmVyIiwgIm5vZm9sbG93IiwgImhhc2h0YWciLCAidHJhbnNsYXRlIiwKICAgICAgICAgICAgICAgICAgICAid3d3IiwgInVybCIsICJkaWUiLCAiZGVyIiwgInVuZCIsICJhIiwgInAiLCAiYnIiLCAiMSIsICIyIiwgIjAxIiwgIjAyIikKdGlkeV90b2tlbnMgPC0gdG9rZW5zICU+JSAKICAgIGFudGlfam9pbihzdG9wX3dvcmRzKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgdW5yZWxldmFudAogICAgbXV0YXRlKHdvcmQgPSBpY29udih3b3JkLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkiKSkgJT4lICMgUHV0IGluIGxhdGluIGZvcm1hdAogICAgbmEub21pdCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBtaXNzaW5nCiAgICBmaWx0ZXIobmNoYXIod29yZCkgPiAyLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHNtYWxsIHdvcmRzCiAgICAgICAgICAgISh3b3JkICVpbiUgbmV3X3N0b3Bfd29yZHMpICAjIHNlYXJjaF90ZXJtIGRlZmluZWQgYWJvdmUKICAgICkKdGlkeV90b2tlbnMgJT4lCiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICAgIyBDb3VudCB3b3JkcwogICAgaGVhZCgzMCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCB3b3JkcwogICAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcih3b3JkLG4pLCB4ID0gbikpICsgZ2VvbV9jb2woKSArIHlsYWIoIldvcmRzIikgKyB0aGVtZV9idygpCmBgYAoKUGVyZmVjdCEKCiMjIFdvcmQgY2xvdWQKClRoaXMgZGF0YSBjYW4gYWxzbyBiZSBzaG93biB3aXRoIGEgd29yZCBjbG91ZC4gV2Ugc2ltcGx5IHVzZSB0aGUgKndvcmRjbG91ZCogcGFja2FnZTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRjbG91ZC9pbmRleC5odG1sIAoKVGhlIHBhY2thZ2UgKndvcmRjbG91ZDIqIGFkZHMgYSBmZXcgZmVhdHVyZXM6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93b3JkY2xvdWQyL3ZpZ25ldHRlcy93b3JkY2xvdWQuaHRtbAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGg9OH0KaWYoIXJlcXVpcmUod29yZGNsb3VkKSl7aW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIil9CmxpYnJhcnkod29yZGNsb3VkKQpjbG91ZF9kYXRhIDwtIHRpZHlfdG9rZW5zICU+JSBjb3VudCh3b3JkKQp3b3JkY2xvdWQod29yZHMgPSBjbG91ZF9kYXRhJHdvcmQsIAogICAgICAgICAgZnJlcSA9IGNsb3VkX2RhdGEkbiwgbWluLmZyZXEgPSAxMCwKICAgICAgICAgIG1heC53b3JkcyA9IDgyLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgcm90LnBlciA9IDAuMTUsIAogICAgICAgICAgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkgCmBgYAoKIyMgbi1ncmFtcwoKU2VlIGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS9uZ3JhbXMuaHRtbAoKYGBge3IgYmlncmFtcywgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQp0b290cyAlPiUgCiAgICBtdXRhdGUoaWQgPSAxOm5yb3codG9vdHMpKSAlPiUgICAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgY29udGVudCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCBjb250ZW50LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgICBmaWx0ZXIoIShiaWdyYW0gJWluJSBjKCJzcGFuIGEiLCAiYSBocmVmIiwgImEgYSIsICJfYmxhbmsgc3BhbiIsICJocmVmIGh0dHBzIiwgInNwYW4gY2xhc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiYSBwIiwgInAgcCIsICJiciBhIiwgInRhcmdldCBfYmxhbmsiLCAibm9yZWZlcnJlciB0YXJnZXQiLCAic3BhbiBzcGFuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgImhhc3RhZyByZWwiLCAiY2xhc3MgbWVudGlvbiIsICJyZWwgbm9mb2xsb3ciLCAibWVudGlvbiBoYXNodGFnIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgImNsYXNzIGludmlzaWJsZSIsICJpbnZpc2libGUgaHR0cHMiLCAicCBhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5vZm9sbG93IG5vb3BlbmVyIiwgIm5vb3BlbmVyIG5vcmVmZXJyZXIiLCAiaGFzaHRhZyByZWwiKSkpICU+JQogICAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICAgIGNvdW50KHNvcnQgPSBUKSAlPiUKICAgIGhlYWQoMjApICU+JQogICAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihiaWdyYW0sIG4pLCB4ID0gbikpICsgCiAgZ2VvbV9jb2woKSArIHRoZW1lX2J3KCkgKyB5bGFiKCJiaWdyYW1zIikKYGBgCgpBZ2Fpbjogc2FtZSBpc3N1ZSB3aXRoIHN0b3Agd29yZHMhIFNvIHdlIG11c3QgcmVtb3ZlIHRoZW0gYWdhaW4uIEJ1dCBpdCdzIG1vcmUgY29tcGxpY2F0ZWQgbm93LgpXZSBjYW4gdXNlIHRoZSAqc2VwYXJhdGUqKCkgZnVuY3Rpb24gdG8gaGVscCB1cy4KCmBgYHtyfQp0b290cyAlPiUgCiAgICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JSAgICAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgY29udGVudCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCBjb250ZW50LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgICBtdXRhdGUoYmlncmFtID0gaWNvbnYoYmlncmFtLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkiKSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIiwgcmVtb3ZlID0gRikgJT4lCiAgICBmaWx0ZXIoISh3b3JkMSAlaW4lIGMobmV3X3N0b3Bfd29yZHMsIHN0b3Bfd29yZHMkd29yZCkpLAogICAgICAgICAgICEod29yZDIgJWluJSBjKG5ld19zdG9wX3dvcmRzLCBzdG9wX3dvcmRzJHdvcmQpKSkgJT4lCiAgICBncm91cF9ieShiaWdyYW0pICU+JQogICAgY291bnQoc29ydCA9IFQpICU+JQogICAgaGVhZCgyNCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKGJpZ3JhbSwgbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiQmktZ3JhbSIpICsKICAgIHRoZW1lX2J3KCkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTh9CmNsb3VkX2RhdGEgPC0gdG9vdHMgJT4lIAogICAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUgICAgICAgICAjIFRoaXMgY3JlYXRlcyBhIHR3ZWV0IGlkCiAgICBzZWxlY3QoaWQsIGNvbnRlbnQsIGNyZWF0ZWRfYXQpICU+JSAgICMgS2VlcHMgaWQsIHRleHQgYW5kIGRhdGUgb2YgdGhlIHR3ZWV0CiAgICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgY29udGVudCwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpICU+JQogICAgbXV0YXRlKGJpZ3JhbSA9IGljb252KGJpZ3JhbSwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJIikpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIsIHJlbW92ZSA9IEYpICU+JQogICAgZmlsdGVyKCEod29yZDEgJWluJSBjKG5ld19zdG9wX3dvcmRzLCBzdG9wX3dvcmRzJHdvcmQpKSwKICAgICAgICAgICAhKHdvcmQyICVpbiUgYyhuZXdfc3RvcF93b3Jkcywgc3RvcF93b3JkcyR3b3JkKSkpICU+JQogICAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICAgIGNvdW50KHNvcnQgPSBUKSB8PgogIG11dGF0ZShsZW5ndGggPSBuY2hhcihiaWdyYW0pKSB8PgogIGZpbHRlcihsZW5ndGggPCAxNSkKICAKY2xvdWRfZGF0YQp3b3JkY2xvdWQod29yZHMgPSBjbG91ZF9kYXRhJGJpZ3JhbSwgCiAgICAgICAgICBmcmVxID0gY2xvdWRfZGF0YSRuLCBtaW4uZnJlcSA9IDEwLAogICAgICAgICAgbWF4LndvcmRzID0gMzUsIHJhbmRvbS5vcmRlciA9IEZBTFNFLCByb3QucGVyID0gMC4xMCwgCiAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpKSAKYGBgCgoKIyMgU2VudGltZW50CgpUaGlzIHNlY3Rpb24gaXMgaW5zcGlyZWQgZnJvbTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3NlbnRpbWVudC5odG1sICAgIApTb21ldGltZXMsIHlvdSBtYXkgYmUgYXNrZWQgaW4gdGhlIHByb2Nlc3MgaWYgeW91ICpyZWFsbHkqIHdhbnQgdG8gZG93bmxvYWQgZGF0YSAobGV4aWNvbnMpLiAgCkp1c3Qgc2F5IHllcyBpbiB0aGUgKipjb25zb2xlKiogKHR5cGUgdGhlIGNvcnJlY3QgYW5zd2VyOiBpZiBub3QsIHlvdSB3aWxsIGJlIGJsb2NrZWQvc3RydWNrKS4KCkZpcnN0LCB3ZSBuZWVkIHRvIGxvYWQgc29tZSBzZW50aW1lbnQgbGV4aWNvbi4gQUZJTk4gaXMgb25lIHN1Y2ggc2VudGltZW50IGRhdGFiYXNlLiAKCmBgYHtyfQppZighcmVxdWlyZSh0ZXh0ZGF0YSkpe2luc3RhbGwucGFja2FnZXMoInRleHRkYXRhIiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHRleHRkYXRhKQphZmlubiA8LSBnZXRfc2VudGltZW50cygiYWZpbm4iKQphZmlubgphZmlubiB8PiBmaWx0ZXIodmFsdWUgPiAzKQpgYGAKClRvIGNyZWF0ZSBhIG5pY2UgdmlzdWFsaXphdGlvbiwgd2UgbmVlZCB0byBleHRyYWN0IHRoZSAqKnRpbWUqKiBvZiB0aGUgdHdlZXRzLgoKYGBge3J9CnRva2Vuc190aW1lIDwtIHRvb3RzICU+JSAKICAgIG11dGF0ZShpZCA9IHJvd19udW1iZXIoKSkgJT4lICAgICAgICAgIyBUaGlzIGNyZWF0ZXMgYSB0d2VldCBpZAogICAgc2VsZWN0KGlkLCBjb250ZW50LCBjcmVhdGVkX2F0KSAlPiUgICAjIEtlZXBzIGlkLCB0ZXh0IGFuZCBkYXRlIG9mIHRoZSB0d2VldAogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCBjb250ZW50KSAgICAgICAgICAjIENyZWF0ZXMgdG9rZW5zIQp0b2tlbnNfdGltZQpgYGAKCldlIHRoZW4gdXNlICoqaW5uZXJfam9pbioqKCkgdG8gbWVyZ2UgdGhlIHR3byBzZXRzLiBUaGlzIGZ1bmN0aW9uIHJlbW92ZXMgdGhlIGNhc2VzIHdoZW4gYSBtYXRjaCBkb2VzIG5vdCBvY2N1ci4KCmBgYHtyfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKc2VudGltZW50IDwtIHRva2Vuc190aW1lICU+JSAKICAgIGlubmVyX2pvaW4oYWZpbm4pICU+JQogICAgbXV0YXRlKGRheSA9IGRheShjcmVhdGVkX2F0KSwKICAgICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KSAvIDI0LAogICAgICAgICAgIG1pbnV0ZSA9IG1pbnV0ZShjcmVhdGVkX2F0KSAvIDYwIC8gMjQsCiAgICAgICAgICAgdGltZSA9IGRheSArIGhvdXIgKyBtaW51dGUpCnNlbnRpbWVudApgYGAKCldlIHRoZW4gY29tcHV0ZSB0aGUgYXZlcmFnZSBzZW50aW1lbnQsIG1pbnV0ZS1ieS1taW51dGUsIG9yIGRheS1ieS1kYXksIGRlcGVuZGluZyBvbiBmcmVxdWVuY3kuICAgCk9mIGNvdXJzZSwgYXZlcmFnZSBzZW50aW1lbnQgY2FuIGJlIG1pc2xlYWRpbmcuIEluZGVlZCwgaWYgYSB0ZXh0IGNvbnRhaW5zIHRoZSB0ZXJtcyAiKkknbSBub3QgaGFwcHkqIiwgdGhlbiBvbmx5ICIqaGFwcHkqIiB3aWxsIGJlIHRhZ2dlZCwgd2hpY2ggaXMgdGhlIG9wcG9zaXRlIG9mIHRoZSBpbnRlbmRlZCBtZWFuaW5nLgoKYGBge3J9CnNlbnRpbWVudCAlPiUKICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoY3JlYXRlZF9hdCkpIHw+CiAgICBncm91cF9ieShkYXRlKSAlPiUKICAgICNmaWx0ZXIoeWVhcihkYXRlKT09MjAyNCkgfD4KICAgIHN1bW1hcmlzZShhdmdfc2VudGltZW50ID0gbWVhbih2YWx1ZSkpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IGF2Z19zZW50aW1lbnQpKSArIGdlb21fY29sKCkgKyB0aGVtZV9idygpCmBgYAoKCldoYXQgYWJvdXQgZW1vdGlvbnM/IFRoZSAqKk5SQyoqIGxleGljb24gY2F0ZWdvcml6ZXMgKmVtb3Rpb25zKi4gQmVsb3csIHdlIG9yZGVyIGVtb3Rpb25zLiBUaGUgbW9zdCBpbXBvcnRhbnQgaW1wYWN0IGlzIHRoZSBkaWNob3RvbXkgYmV0d2VlbiBwb3NpdGl2ZSAmIG5lZ2F0aXZlIGVtb3Rpb25zLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbnJjIDwtIGdldF9zZW50aW1lbnRzKCJucmMiKQpucmMgPC0gbnJjICU+JQogICAgbXV0YXRlKHNlbnRpbWVudCA9IGFzLmZhY3RvcihzZW50aW1lbnQpLAogICAgICAgICAgIHNlbnRpbWVudCA9IHJlY29kZV9mYWN0b3Ioc2VudGltZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgam95ID0gImpveSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnVzdCA9ICJ0cnVzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJwcmlzZSA9ICJzdXJwcmlzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbnRpY2lwYXRpb24gPSAiYW50aWNpcGF0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gInBvc2l0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5lZ2F0aXZlID0gIm5lZ2F0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhZG5lc3MgPSAic2FkbmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmdlciA9ICJhbmdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWFyID0gImZlYXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlndXN0ID0gImRpc2d1c3QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLm9yZGVyZWQgPSBUKSkKbnJjCmBgYAoKV2UgdGhlbiBjcmVhdGUgdGhlIG1lcmdlZCBkYXRhc2V0LgoKYGBge3J9CmVtb3Rpb25zIDwtIHRva2Vuc190aW1lICU+JSAKICAgIGlubmVyX2pvaW4obnJjKSAlPiUgICAgICAgICAgICAgICAgICAgICAjIE1lcmdlIGRhdGEgd2l0aCBzZW50aW1lbnQKICAgIG11dGF0ZShkYXRlID0gYXMuRGF0ZShjcmVhdGVkX2F0KSkgICAgICAjIENyZWF0ZSBkYXkgY29sdW1uCmVtb3Rpb25zICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTaG93IHRoZSByZXN1bHQKYGBgCgpUaGUgbWVyZ2luZyBoYXMgcmVkdWNlZCB0aGUgc2l6ZSBvZiB0aGUgZGF0YXNldCwgYnV0IHRoZXJlIHN0aWxsIHJlbWFpbnMgZW5vdWdoIHRvIHB1cnN1ZSB0aGUgc3R1ZHkuICAgCkZpbmFsbHksIHdlIG1vdmUgdG8gdGhlIHBpdm90LXRhYmxlIHRoYXQgY291bnRzIGVtb3Rpb25zIGZvciBlYWNoIGRheS4KCmBgYHtyfQpnIDwtIGVtb3Rpb25zICU+JSAKICAgIGdyb3VwX2J5KGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICAgIGZpbHRlcih5ZWFyKGRhdGUpID09IDIwMjQpIHw+CiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gaW50ZW5zaXR5LCBmaWxsID0gc2VudGltZW50KSkgKyBnZW9tX2NvbCgpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJtYWdtYSIsIGRpcmVjdGlvbiA9IC0xKSArIHRoZW1lX2J3KCkKZ2dwbG90bHkoZykKYGBgCgpUaGlzIGNhbiBhbHNvIGJlIHNob3duIGluIHBlcmNlbnRhZ2UgZm9ybWF0LiAKCmBgYHtyfQpnIDwtIGVtb3Rpb25zICU+JSAKICAgIGdyb3VwX2J5KGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgICBmaWx0ZXIoeWVhcihkYXRlKSA9PSAyMDI0KSB8PgogICAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gaW50ZW5zaXR5LCBmaWxsID0gc2VudGltZW50KSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArIHhsYWIoIlRpbWUiKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAibWFnbWEiLCBkaXJlY3Rpb24gPSAtMSkgKyB0aGVtZV9idygpICsgCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gMikKZ2dwbG90bHkoZykKYGBgCgpgYGB7cn0KZW1vdGlvbnMgJT4lIAogICAgbXV0YXRlKHNlbnRpbWVudCA9IGlmX2Vsc2Uoc2VudGltZW50IDwgIm5lZ2F0aXZlIiwgInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSAKICAgIGdyb3VwX2J5KGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArCiAgICB0aGVtZV9idygpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSkgKyAKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMyMjMzMzMiLCAiI0ZGQkI5OSIpKSAKYGBgCgoKCgojIyBBZHZhbmNlZCBzZW50aW1lbnQgCgpUaGUgcHJvYmxlbSB3aXRoIHRoZSBwcmVjZWRpbmcgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZG9uJ3QgdGFrZSBpbnRvIGFjY291bnQgKip2YWxlbmNlIHNoaWZ0ZXJzKiogKGkuZS4sIG5lZ2F0b3JzLCBhbXBsaWZpZXJzIChpbnRlbnNpZmllcnMpLCBkZS1hbXBsaWZpZXJzIChkb3dudG9uZXJzKSwgYW5kIGFkdmVyc2F0aXZlIGNvbmp1bmN0aW9ucykuIElmIGEgdHdlZXQgc2F5cyAqbm90IGhhcHB5KiwgY291bnRpbmcgdGhlIHdvcmQgKmhhcHB5KiBpcyBub3QgYSBnb29kIGlkZWEhIFRoZSBwYWNrYWdlICpzZW50aW1lbnRyKiBpcyBidWlsdCB0byBjaXJjdW12ZW50IHRoZXNlIGlzc3VlczogaGF2ZSBhIGxvb2sgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvc2VudGltZW50ciAgCihzZWUgYWxzbzogaHR0cHM6Ly93d3cuc2VudG9tZXRyaWNzLm9yZyBhbmQgdGhlIGJvb2sgKipTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFRleHQgQW5hbHlzaXMgaW4gUioqIGhvc3RlZCBhdCBodHRwczovL3NtbHRhci5jb20pCgpJIGhhdmVuJ3QgdGVzdGVkICoqYXdzLmNvbXByZWhlbmQqKiwgYnV0IGl0IHNlZW1zIHByb21pc2luZzogaHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkeXIvYXdzLmNvbXByZWhlbmQKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2VudGltZW50cikpe2luc3RhbGwucGFja2FnZXMoYygic2VudGltZW50ciIsICJ0ZXh0Y2F0IikpfQpsaWJyYXJ5KHNlbnRpbWVudHIpCmxpYnJhcnkodGV4dGNhdCkKYGBgCgpGaXJzdCwgbGV0J3Mga2VlcCBvbmx5IHRoZSB0d2VldHMgd3JpdHRlbiBpbiBFbmdsaXNoIQoKYGBge3J9CiMgdG9vdHNfZW4gPC0gdG9vdHMgJT4lCiMgICAgIG11dGF0ZShsYW5ndWFnZSA9IHRleHRjYXQoY29udGVudCkpICU+JQojICAgICBmaWx0ZXIobGFuZ3VhZ2UgPT0gImVuZ2xpc2giKSAlPiUKIyAgICAgZHBseXI6OnNlbGVjdChjcmVhdGVkX2F0LCBjb250ZW50KQoKdG9vdHNfZW4gPC0gdG9vdHMgfD4gZmlsdGVyKGxhbmd1YWdlID09ICJlbiIpCmBgYAoKKipOT1RFKio6IHRoZSBjb2RlIGFib3ZlIHdhcyB1c2VkIHRvIHNob3cgdGhlIGZ1bmN0aW9uICp0ZXh0Y2F0KjogdGhlIGxhbmd1YWdlIGlzIGFscmVhZHkgY29kZWQgaW4gdGhlIHR3ZWV0cyB2aWEgdGhlICoqbGFuZyoqIGNvbHVtbi92YXJpYWJsZS4gKGl0IHN1ZmZpY2VzIHRvIGtlZXAgdGhlIGluc3RhbmNlcyBmb3Igd2hpY2ggbGFuZyA9PSAiZW4iKQoKTmV4dCwgd2UgY29tcHV0ZSBhZHZhbmNlZCBzZW50aW1lbnQuIAoKYGBge3J9CnR3ZWV0X3NlbnQgPC0gdG9vdHNfZW4kY29udGVudCAlPiUKICAgIGdldF9zZW50ZW5jZXMoKSAlPiUgICMgSW50ZXJtZWRpYXRlIGZ1bmN0aW9uCiAgICBzZW50aW1lbnQoKSAgICAgICAgICAjIFNlbnRpbWVudCEKdHdlZXRfc2VudApgYGAKCioqTk9URSoqOiBkZXBlbmRpbmcgb24gZnJlcXVlbmN5IGlzc3VlcywgaXQgaXMgYmV0dGVyIHRvIGFuYWx5emUgYXQgZGFpbHkgb3IgaG91cmx5IHNjYWxlcy4gSWYgYSB3b3JkIGlzIHZlcnkgcG9wdWxhciwgdGhlbiwgaGlnaGVyIGZyZXF1ZW5jaWVzIGFyZSBtb3JlIHJlbGV2YW50LiAKCmBgYHtyfQp0b290c19lbiAlPiUKICAgIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICMgVGhpcyBjcmVhdGVzIGEgbmV3IGNvbHVtbiB3aXRoIHJvdyBudW1iZXIKCnRvb3RzX2VuICU+JQogICAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgICBsZWZ0X2pvaW4odHdlZXRfc2VudCwgYnkgPSAiZWxlbWVudF9pZCIpCgp0b290c19lbiAlPiUKICAgIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICU+JQogICAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICAgIGdyb3VwX2J5KGRhdGUgPSBtYWtlX2RhdGUoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9udGggPSBtb250aChjcmVhdGVkX2F0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhciA9IHllYXIoY3JlYXRlZF9hdCkpKSAlPiUKICAgIHN1bW1hcmlzZShhdmdfc2VudCA9IG1lYW4oc2VudGltZW50KSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gYXZnX3NlbnQpKSArIGdlb21fY29sKCkgCgp0b290c19lbiAlPiUKICAgIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICU+JQogICAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICAgIGZpbHRlcihzZW50aW1lbnQgIT0gMCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoaG91cihjcmVhdGVkX2F0KSksIHkgPSBzZW50aW1lbnQpKSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKwogICAgZ2VvbV9qaXR0ZXIoc2l6ZSA9IDAuMikgKwogICAgZ2VvbV9ib3hwbG90KGFlcyhjb2xvciA9IGFzLmZhY3Rvcihob3VyKGNyZWF0ZWRfYXQpKSksIGFscGhhID0gMC41KSArCiAgICB0aGVtZV9idygpICsgCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgeGxhYigiaG91ciIpIApgYGAKCgoKIyBSZXNvdXJjZXMKCkJlbG93LCBhIHNob3J0IGxpc3Qgb2YgcmVzb3VyY2VzICh0byBhY2Nlc3MgdGhpcmQtcGFydHkgZGF0YSk6ICAgCgotICoqdGV4dCBtaW5pbmcgd2l0aCBSKiogKG9ubGluZSBib29rKTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tICAgICAgCi0gKipCbG9vbWJlcmcqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JibHBhcGkvaW5kZXguaHRtbCAgIAotICoqZ21haWwqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dtYWlsci92aWduZXR0ZXMvZ21haWxyLmh0bWwgICAKLSAqKkdvb2dsZSBNYXBzKio6IGh0dHBzOi8vY3Jhbi5yc3R1ZGlvLmNvbS93ZWIvcGFja2FnZXMvbWFwc2FwaS92aWduZXR0ZXMvaW50cm8uaHRtbCAgCi0gKipHb29nbGUgdHJlbmRzKio6IGh0dHBzOi8vZ2l0aHViLmNvbS9QTWFzc2ljb3R0ZS9ndHJlbmRzUgotICoqR29vZ2xlIEFQSXMqKiAobW9yZSBnZW5lcmFsbHkpOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2FyZ2xlL3ZpZ25ldHRlcy9hdXRoLWZyb20td2ViLmh0bWwKLSAqKkZhY2Vib29rIEFQSSoqOiBkZXZlbG9wZXJzLmZhY2Vib29rLmNvbS9hZHMvYmxvZy9wb3N0L3YyLzIwMTgvMDUvMTUvZmFjZWJvb2stcmVhY2gtZnJlcXVlbmN5LWFwaS8gIAoKUG9zc2libHkgZGVwcmVjYXRlZDogIAotICoqRmFjZWJvb2sqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JmYWNlYm9vay9pbmRleC5odG1sICAgIAotICoqSW5zdGFncmFtKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9pbnN0YVIvaW5kZXguaHRtbAoKYGBge3J9CgpgYGAK