For approach to analysis see the tidytext book.
Setup
In case the tidyverse is not installed on your computer already (you haven’t used it in R before) you need to install it before loading the library. This you need to do only once as long as you use the same computer or don’t need to update a loaded package.
These are our main packages:
Additional packages for specific analyses:
Data import
We read the data with read_csv, not with the base package read.csv, to create a tibble.
text_df <- read_csv("Comments.csv")
The data come in the format CommentID, RespondentID, QuestionID, GroupID, CommentText
Note that this table is in the ‘long’ format, so we need to filter rows rather than select columns to select variables such as questions and speakers.
Data cleaning
Clearly there are the typical stop words that we don’t want to have in counts. Let’s remove them. There’s a data frame that has stop words specified for us, as part of tidytext:
data(stop_words)
We can View(stop_words) to see them, and add our own ones, of course.
Let’s get to the words:
words_df <- text_df %>%
unnest_tokens(word, CommentText)
The key function is unnest_tokens(), which converts text into a column of n-grams (with default value 1, i.e., single words). Since the survey data is in one-word-per-row format, we can remove stop symbols with an anti_join (from dplyr):
words_df <- anti_join(words_df, stop_words)
In general the anti-join command is:
anti_join(a, b, by = "x1") -- all rows in a that do not have a mach in b (on variable x1)
Let’s look at punctuation:
words_df %>%
filter(str_detect(word, "[:punct:]"))
There are more than 3000 words in the word column, many of which are not really words, but appear so because there is no space between the points. This would need to be addressed in the next round of cleaning, but for the moment we siimply remove all these.
Address this issue in the next round.
words_df <- words_df %>%
filter(!str_detect(word, "[:punct:]"))
The function str_detect() comes from the stringr package, see help.
We may also want to remove numbers and special characters. First we need a tibble to store these kind of stop symbols under:
stop_symbols <- tibble(word = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "#", "(", ")", "."))
words_df <- words_df %>%
anti_join(stop_symbols)
Word frequencies
A first look at all words:
words_df %>%
count(word, sort = TRUE)
Let’s do a bit of graphing of words with a frequency GT 750.
words_df %>%
count(word, sort = TRUE) %>%
filter(n > 750) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
xlab(NULL) +
coord_flip()

Now we can calculate word frequencies for each person. First, we group by person and count how many times each person used each word. Then we use left_join() to add a column of the total number of words used by each person. Finally, we calculate a frequency for each person and word.
frequency <- words_df %>%
group_by(RespondentID) %>%
count(word, sort = TRUE) %>%
left_join(words_df %>%
group_by(RespondentID) %>%
summarise(total = n())) %>%
mutate(freq = n/total)
Joining, by = "RespondentID"
A left_join joins matching rows from b into a: inner_join(a,b, by = "x").
Let’s look at the most frequent words:
head(frequency)
Word cloud
This uses the library wordcloud:
words_df %>%
count(word) %>%
with(wordcloud(word, n, max.words = 100))

Sentiment analysis
Tidytext comes with three lexicons that contains words for sentiments, overall more than 27,000 words:
head(sentiments)
Let’s use the nrc lexicon and look for joyfull words. First, get the joyful words into a df:
nrc_joy <- get_sentiments("nrc") %>%
filter(sentiment == "joy")
Then look for them in answers For this, we use inner_join, which retains only the matching rows in both sets. The general syntax is inner_join(a, b, by = "x1"), but in the shorter format this reduces to:
words_df %>%
inner_join(nrc_joy) %>%
count(word, sort = TRUE) %>%
top_n(10)
Joining, by = "word"
Selecting by n
The long form would be inner_join(words_q1, nrc_joy, by = "word") but R is able to figure the arguments provided in the short form.
To do the same analysis for the sentiment ‘fear’, simply replace ‘joy’ with ‘fear’ above (and perhaps rename the variable to nrc_fear or such).
Sentiment anaysis on one question
Just for Q1 and all follow-up questions:
words_df %>%
filter(QuestionID == 3861750) %>%
inner_join(nrc_joy) %>%
count(word, sort = TRUE)
Joining, by = "word"
Sentiment on all questions
words_df %>%
group_by(QuestionID) %>%
inner_join(nrc_joy) %>%
count(word, sort = FALSE)
Joining, by = "word"
We turned sorting on the count off here because we want to have the results grouped by question id. If Sorting was TRUE, the numeric count would be used first, then the question ID.
Sentiment analysis on a sub-set of all questions
This not particularly useful here, but you get the idea:
words_dfs %>%
filter(Question == "Q1" | Question == "Q3" | Question == "Q7") %>%
inner_join(nrc_joy) %>%
count(word, sort = TRUE)
Most common positive and negative sentiment words
Using the lexicon bing, which classifies sentiment words as positive or negative, we can find how words costribute to these overall sentiments:
bing_word_counts <- words_df %>%
inner_join(get_sentiments("bing")) %>%
count(word, sentiment, sort = TRUE) %>%
ungroup()
Joining, by = "word"
head(bing_word_counts)
Let’s graph this with ggplot2:
bing_word_counts %>%
group_by(sentiment) %>%
top_n(10) %>%
ungroup() %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment",
x = NULL) +
coord_flip()
Selecting by n

The top_n(10) is returning more than 10 rows if there are ties. Will have to write a more complex expression with filter() and min_rank() because that’s what top_n wraps.
Here’s a cool combination of wordclouds with bing words:
library(reshape2)
Attaching package: ‘reshape2’
The following object is masked from ‘package:tidyr’:
smiths
We need reshape2 for the acast function. acast is a complex function used to transform between data frames and matrices. comparison.cloud() needs a matrix as input. For more see http://had.co.nz/reshape/.
words_df %>%
inner_join(get_sentiments("bing")) %>%
count(word, sentiment, sort = TRUE) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("gray20", "gray80"),
max.words = 100)
Joining, by = "word"

Word and Document Frequency
Frequencies by some unit of analysis, like by question or some other category. Here I demonstrate how to do this by GroupID. Not sure that make sense; it would if one had reasons to expect that the/some groups are different.
Calculating term frequency per group
Whole corpus
Step 1: A tibble word count by interview sorted by count. This has length 1097 observations, less than the full corpus (2617 obs) because we aggregate in each interview: count(x, ..., wt = NULL, sort = FALSE) with x the tbl and ... the variables to group by; wt is for weighting by counting number of rows.
group_words <- words_df %>%
count(GroupID, word, sort = TRUE) %>%
ungroup()
head(group_words)
So here we have count(words_df, Interview, word, wt = NULL, sort = TRUE.
Step 2: total of words per group
total_words <- group_words %>%
group_by(GroupID) %>%
summarize(total = sum(n))
total_words
Step 3: Add the total count column to each of the words and their individual frequency
Left_join(a, b, by = "x1') joins mathing rows from b to a. The “by” is included here for clarity, but could be omitted because it is the single shared variable in both tbls.
Let’s look at the term frequency. To make this visually inspectable, we filter out “question” and facet on a smaller number of groups. (A better solution requires fiddling with the geom_histrogram.)
group_words_small <- group_words %>%
filter(GroupID < 19235 & word != "question")
ggplot(group_words_small, aes(n/total, fill = GroupID)) +
geom_histogram(show.legend = FALSE) +
facet_wrap(~GroupID, ncol = 2, scales = "free_y")

tf_idf For students reponses only
The tf_idf value be calcuated based on the table just created like this:
group_words <- group_words %>%
bind_tf_idf(word, GroupID, n)
head(group_words)
Words that are very common across all groups have a low idf term, and so will the tf_idf. Let’s find the distinct words by sorting on the tf_idf:
group_words %>%
arrange(desc(tf_idf))
We can plot this result (again, only on the first 8 groups for visibility)
group_words %>%
filter(GroupID < 19235) %>%
arrange(desc(tf_idf)) %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>%
group_by(GroupID) %>%
top_n(10) %>%
ungroup %>%
ggplot(aes(word, tf_idf, fill = GroupID)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
facet_wrap(~GroupID, ncol = 2, scales = "free") +
coord_flip()
Selecting by tf_idf

Todo: unpack the mutate line
Topic models
In general, TMs classify documents into topics and determine the overlap of a document with a topic. Each topic is made up out of a number of words.
Casting the tidy text df into a document-term matrix
Most text mining packages use DTM format, which is a matrix where:
- each row represents one document (such as a book or article),
- each column represents one term, and
- each value (typically) contains the number of appearances of that term in that document.
Here’s how to turn a tidy text df into a DTM:
library(tm)
Loading required package: NLP
Attaching package: ‘NLP’
The following object is masked from ‘package:ggplot2’:
annotate
First we filter out the overly frequent word “question(s)” and some other words which are necessarily frequent because they refer to the mcq genre. And there’s that number which also needs to go.
# remove question from corpus
words_df <- words_df %>%
filter(word != "question") %>%
filter(word != "questions") %>%
filter(word != "option") %>%
filter(word != "options") %>%
filter(word != "answer") %>%
filter(word != "answers") %>%
filter(word != "mcq") %>%
filter(word != "327029")
students_dtm <- words_df %>%
count(QuestionID, word) %>%
cast_dtm(QuestionID, word, n)
students_dtm
<<DocumentTermMatrix (documents: 1670, terms: 9996)>>
Non-/sparse entries: 135820/16557500
Sparsity : 99%
Maximal term length: 24
Weighting : term frequency (tf)
Topics across questions
With this we have a dtm for each of the questions. Based on this we can mine for topics:
library(topicmodels)
# set a seed so that the output of the model is predictable
students_lda <- LDA(students_dtm, k = 5, control = list(seed = 1234))
students_lda
A LDA_VEM topic model with 5 topics.
This object contains a model with five groups. This model needs to be inspected to gain insights. For instance, we can look at how the words relate to the topics, extracting the beta value from the matrix. We use tidy to turn that column into a tibble topic-term-beta. The betas can be interpreted as probabilities or the term belonging to the topic.
students_topics <- tidy(students_lda, matrix = "beta")
students_topics
Let’s look at the top-10 terms:
students_top_terms <- students_topics %>%
group_by(topic) %>%
top_n(10, beta) %>%
ungroup() %>%
arrange(topic, -beta)
And visualise them:
students_top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip()

The alternative view is to look for terms which differ most between topics.
beta_spread <- students_topics %>%
mutate(topic = paste0("topic", topic)) %>%
spread(topic, beta) %>%
filter(topic1 > .001 | topic2 > .001) %>%
mutate(log_ratio = log2(topic2 / topic1))
beta_spread
To do: Plot a graph with the highest (positive and negative) differences in log ratio.
Relationship between words - N-grams
NOT MODIFIED YET FOR COMMENTS ANALYSIS
bigrams <- text_df %>%
unnest_tokens(bigram, Text, token = "ngrams", n = 2)
head(bigrams)
The way to control the token size is to set paramaters of the unnest_token function, see help(unnest_token"). A single word is an ngram of length 1, so two adjacent words can be used as the token unit by unnest_tokens(ngram, txt, token = "ngrams", n = 2).
Removing stop words
Many of the bi-grams will be stop word phrases, of which we need to get rid off. The pattern is separate/filter/count/unite:
Step 1: separate into two columns
bigrams_separated <- bigrams %>%
separate(bigram, c("word1", "word2"), sep = " ")
Step 2: Filter out stopwords
bigrams_filtered <- bigrams_separated %>%
filter(!word1 %in% stop_words$word) %>%
filter(!word2 %in% stop_words$word)
Much less pairs! Let’s count them:
Step 3: count
bigram_counts <- bigrams_filtered %>%
count(word1, word2, sort = TRUE)
But still lots of non-meaning words, all those ahs and uhs, because this is talk. Let’s filter some more.
bigrams_filtered <- bigrams_filtered %>%
filter(!word1 %in% stop_words2$word) %>%
filter(!word2 %in% stop_words2$word)
Step 4: And let’s produce a united version again:
bigrams_united <- bigrams_filtered %>%
unite(bigram, word1, word2, sep = " ")
head(bigrams_united)
Analysis
Counting because we can:
bigrams_united %>%
count(bigram, sort = TRUE)
Lets look at all phrases with a specific word in position 2
bigrams_filtered %>%
filter(word2 == "assignment") %>%
count(Interview, word1, sort = TRUE)
Improvement: use regex to deal with different forms of the stem:
bigrams_filtered %>%
filter(grepl("assign", word2)) %>%
count(Interview, word1, sort = TRUE)
grepl(pattern, x) is the logic form of grep, returnign a vector of match or not for each element of x.
Sentiment analysis with words in context
For sentiment analysis we need in particular into negations""
bigrams_separated %>%
filter(word1 == "not") %>%
count(word1, word2, sort = TRUE)
Let’s use that for some analysis with the AFINN lexicon, with uses a numeric score to express sentiment direction:
AFINN <- get_sentiments("afinn")
head(AFINN)
not_words <- bigrams_separated %>%
filter(word1 == "not") %>%
inner_join(AFINN, by = c(word2 = "word")) %>%
count(word2, score, sort = TRUE) %>%
ungroup()
not_words
(This needs some unpacking) We see how important it is to watch out for negations when it comes to sentiments. Other negation words deserve attention, too:
negation_words <- c("not", "no", "never", "without")
negated_words <- bigrams_separated %>%
filter(word1 %in% negation_words) %>%
inner_join(AFINN, by = c(word2 = "word")) %>%
count(word1, word2, score, sort = TRUE) %>%
ungroup()
negated_words
This only makes a small difference in the 5 interviews, but with a larger corpus we are bound to see more impact. We can compute and visualise the impact by computing a value score * n:
negated_words %>%
mutate(contribution = n * score) %>%
arrange(desc(abs(contribution))) %>%
head(20) %>%
mutate(word2 = reorder(word2, contribution)) %>%
ggplot(aes(word2, n * score, fill = n * score > 0)) +
geom_col(show.legend = FALSE) +
xlab("Words preceded by \"not\"") +
ylab("Sentiment score * number of occurrences") +
coord_flip()
This also makes it visible that in the first 5 interviews there were no negatve words negated, such as “no doubt”.
Graphing bigram networks
library(igraph)
The dataframe we need for this is bigram_counts with word1, word2 and n as the “weight”. Let’s use only the those with n GEG 2:
bigram_graph <- bigram_counts %>%
filter(n >= 2) %>%
graph_from_data_frame()
bigram_graph
For plottigng we use ggraph:
library(ggraph)
set.seed(2017)
ggraph(bigram_graph, layout = "fr") +
geom_edge_link() +
geom_node_point() +
geom_node_text(aes(label = name), vjust = 1, hjust = 1)
Not a particularly connected graph, but the principle is clear.
Correlations among questions
library(widyr)
word_pairs <- words_df %>%
pairwise_count(word, Question, sort = TRUE)
word_pairs
These are pair counts per question. Here’s the same for interviews:
word_pairs <- words_df %>%
pairwise_count(word, Interview, sort = TRUE)
word_pairs
Note that these become rather big tables because so many things go togehter on that scale. We can also look this way for specific combinations of words. Let’s go back to pairs within questions, and further reduce to the first interview:
word_pairs <- words_df %>%
filter(Interview == 1) %>%
pairwise_count(word, Question, sort = TRUE)
word_pairs
Adn lets look for pairs that have “assignment” in them. Note that the level of counting is the Question. One problem is that we have questions with labels Q and Q-F, which in this analysis get counted separately.
word_pairs %>%
filter(item1 == "assignment")
Let’s calculate a correlation between (frequent) words:
word_cors <- words_df %>%
group_by(word) %>%
filter(n() >= 20) %>%
pairwise_cor(word, Question, sort = TRUE)
word_cors
Plots help to get an overview:
word_cors %>%
filter(item1 %in% c("mark", "time", "idea", "assignment")) %>%
group_by(item1) %>%
top_n(6) %>%
ungroup() %>%
mutate(item2 = reorder(item2, correlation)) %>%
ggplot(aes(item2, correlation)) +
geom_bar(stat = "identity") +
facet_wrap(~ item1, scales = "free") +
coord_flip()
And based on correlations we can create a network graph:
set.seed(2016)
word_cors %>%
filter(correlation > .15) %>%
graph_from_data_frame() %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = correlation), show.legend = FALSE) +
geom_node_point(color = "lightblue", size = 5) +
geom_node_text(aes(label = name), repel = TRUE) +
theme_void()
Miscalleneous
Stemming
This now also is the point were we also could do some stemming. For this we need this library:
library(SnowballC)
Which gives us a function ‘wordstem’:
words_stemmed <- mutate(words_df, word = wordStem(word))
head(words_stemmed, n=20L)
But we don’t want to use the stemmed words just now because it would intefere with the sentiment analysis. Stemming is useful, but only once we no longer need “real” words, such as for topic modelling and the likes.
Appendices
Lessons learned
To make analysis flexible, use a distinct variable name for the data set the analysis is based on, and bind it at the beginning of the analysis to the specific data set, which you expect will be varied.
LS0tCnRpdGxlOiAiUGVlcndpc2UgQ29tbWVudHMgQW5hbHlzaXMgKDEyMTAwIGNvbW1lbnRzKSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCkZvciBhcHByb2FjaCB0byBhbmFseXNpcyBzZWUgW3RoZSB0aWR5dGV4dCBib29rXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vc2VudGltZW50Lmh0bWwpLiAKCgojIyBTZXR1cAoKSW4gY2FzZSB0aGUgdGlkeXZlcnNlIGlzIG5vdCBpbnN0YWxsZWQgb24geW91ciBjb21wdXRlciBhbHJlYWR5ICh5b3UgaGF2ZW4ndCB1c2VkIGl0IGluIFIgYmVmb3JlKSB5b3UgbmVlZCB0byBpbnN0YWxsIGl0IGJlZm9yZSBsb2FkaW5nIHRoZSBsaWJyYXJ5LiBUaGlzIHlvdSBuZWVkIHRvIGRvIG9ubHkgb25jZSBhcyBsb25nIGFzIHlvdSB1c2UgdGhlIHNhbWUgY29tcHV0ZXIgb3IgZG9uJ3QgbmVlZCB0byB1cGRhdGUgYSBsb2FkZWQgcGFja2FnZS4KClRoZXNlIGFyZSBvdXIgbWFpbiBwYWNrYWdlczoKYGBge3IgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXRleHQpCmBgYAoKQWRkaXRpb25hbCBwYWNrYWdlcyBmb3Igc3BlY2lmaWMgYW5hbHlzZXM6CgoKYGBge3IgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh3b3JkY2xvdWQpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KGdncmFwaCkKbGlicmFyeSh3aWR5cikKbGlicmFyeSh0b3BpY21vZGVscykKYGBgCgoKIyMgRGF0YSBpbXBvcnQKCldlIHJlYWQgdGhlIGRhdGEgd2l0aCBgcmVhZF9jc3ZgLCBub3Qgd2l0aCB0aGUgYmFzZSBwYWNrYWdlIGByZWFkLmNzdmAsIHRvIGNyZWF0ZSBhIHRpYmJsZS4gCmBgYHtyIFJlYWRpbmcgZnJvbSBmaWxlLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0ZXh0X2RmIDwtIHJlYWRfY3N2KCJDb21tZW50cy5jc3YiKQpgYGAKClRoZSBkYXRhIGNvbWUgaW4gdGhlIGZvcm1hdCBDb21tZW50SUQsIFJlc3BvbmRlbnRJRCwgUXVlc3Rpb25JRCwgR3JvdXBJRCwgQ29tbWVudFRleHQKCk5vdGUgdGhhdCB0aGlzIHRhYmxlIGlzIGluIHRoZSAnbG9uZycgZm9ybWF0LCBzbyB3ZSBuZWVkIHRvIGZpbHRlciByb3dzIHJhdGhlciB0aGFuIHNlbGVjdCBjb2x1bW5zIHRvIHNlbGVjdCB2YXJpYWJsZXMgc3VjaCBhcyBxdWVzdGlvbnMgYW5kIHNwZWFrZXJzLiAKCgojIyBEYXRhIGNsZWFuaW5nCgpDbGVhcmx5IHRoZXJlIGFyZSB0aGUgdHlwaWNhbCBzdG9wIHdvcmRzIHRoYXQgd2UgZG9uJ3Qgd2FudCB0byBoYXZlIGluIGNvdW50cy4gTGV0J3MgcmVtb3ZlIHRoZW0uIFRoZXJlJ3MgYSBkYXRhIGZyYW1lIHRoYXQgaGFzIHN0b3Agd29yZHMgc3BlY2lmaWVkIGZvciB1cywgYXMgcGFydCBvZiB0aWR5dGV4dDogCgpgYGB7ciBzdG9wLXdvcmRzfQpkYXRhKHN0b3Bfd29yZHMpCmBgYApXZSBjYW4gYFZpZXcoc3RvcF93b3JkcylgIHRvIHNlZSB0aGVtLCBhbmQgYWRkIG91ciBvd24gb25lcywgb2YgY291cnNlLiAKCkxldCdzIGdldCB0byB0aGUgd29yZHM6IAoKYGBge3IgZmlyc3QgY2FsbCB0byB1bm5lc3RfdG9rZW4oKX0Kd29yZHNfZGYgPC0gdGV4dF9kZiAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIENvbW1lbnRUZXh0KQpgYGAKClRoZSBrZXkgZnVuY3Rpb24gaXMgdW5uZXN0X3Rva2VucygpLCB3aGljaCBjb252ZXJ0cyB0ZXh0IGludG8gYSBjb2x1bW4gb2Ygbi1ncmFtcyAod2l0aCBkZWZhdWx0IHZhbHVlIDEsIGkuZS4sIHNpbmdsZSB3b3JkcykuIFNpbmNlIHRoZSBzdXJ2ZXkgZGF0YSBpcyBpbiBvbmUtd29yZC1wZXItcm93IGZvcm1hdCwgd2UgY2FuIHJlbW92ZSBzdG9wIHN5bWJvbHMgd2l0aCBhbiBhbnRpX2pvaW4gKGZyb20gZHBseXIpOgoKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp3b3Jkc19kZiA8LSBhbnRpX2pvaW4od29yZHNfZGYsIHN0b3Bfd29yZHMpCmBgYAoKSW4gZ2VuZXJhbCB0aGUgYW50aS1qb2luIGNvbW1hbmQgaXM6CmBgYAphbnRpX2pvaW4oYSwgYiwgYnkgPSAieDEiKSAgLS0gYWxsIHJvd3MgaW4gYSB0aGF0IGRvIG5vdCBoYXZlIGEgbWFjaCBpbiBiIChvbiB2YXJpYWJsZSB4MSkKYGBgCgpMZXQncyBsb29rIGF0IHB1bmN0dWF0aW9uOiAKCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdCh3b3JkLCAiWzpwdW5jdDpdIikpCmBgYAoKVGhlcmUgYXJlIG1vcmUgdGhhbiAzMDAwIHdvcmRzIGluIHRoZSB3b3JkIGNvbHVtbiwgbWFueSBvZiB3aGljaCBhcmUgbm90IHJlYWxseSB3b3JkcywgYnV0IGFwcGVhciBzbyBiZWNhdXNlIHRoZXJlIGlzIG5vIHNwYWNlIGJldHdlZW4gdGhlIHBvaW50cy4gVGhpcyB3b3VsZCBuZWVkIHRvIGJlIGFkZHJlc3NlZCBpbiB0aGUgbmV4dCByb3VuZCBvZiBjbGVhbmluZywgYnV0IGZvciB0aGUgbW9tZW50IHdlIHNpaW1wbHkgcmVtb3ZlIGFsbCB0aGVzZS4gCgo+IEFkZHJlc3MgdGhpcyBpc3N1ZSBpbiB0aGUgbmV4dCByb3VuZC4gCgpgYGB7cn0Kd29yZHNfZGYgPC0gd29yZHNfZGYgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KHdvcmQsICJbOnB1bmN0Ol0iKSkKYGBgCgpUaGUgZnVuY3Rpb24gYHN0cl9kZXRlY3QoKWAgY29tZXMgZnJvbSB0aGUgc3RyaW5nciBwYWNrYWdlLCBzZWUgaGVscC4gCgpXZSBtYXkgYWxzbyB3YW50IHRvIHJlbW92ZSBudW1iZXJzIGFuZCBzcGVjaWFsIGNoYXJhY3RlcnMuIEZpcnN0IHdlIG5lZWQgYSB0aWJibGUgdG8gc3RvcmUgdGhlc2Uga2luZCBvZiBzdG9wIHN5bWJvbHMgdW5kZXI6CgpgYGB7cn0Kc3RvcF9zeW1ib2xzIDwtIHRpYmJsZSh3b3JkID0gYygiMSIsICIyIiwgIjMiLCAiNCIsICI1IiwgIjYiLCAiNyIsICI4IiwgIjkiLCAiMCIsICIjIiwgIigiLCAiKSIsICIuIikpCmBgYAoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CndvcmRzX2RmIDwtIHdvcmRzX2RmICU+JQogIGFudGlfam9pbihzdG9wX3N5bWJvbHMpCmBgYAoKIyMgV29yZCBmcmVxdWVuY2llcwoKQSBmaXJzdCBsb29rIGF0IGFsbCB3b3JkczogCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKYGBgCgoKCkxldCdzIGRvIGEgYml0IG9mIGdyYXBoaW5nIG9mIHdvcmRzIHdpdGggYSBmcmVxdWVuY3kgR1QgNzUwLiAKCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgZmlsdGVyKG4gPiA3NTApICU+JQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCBuKSkgKwogIGdlb21fY29sKCkgKwogIHhsYWIoTlVMTCkgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCgpOb3cgd2UgY2FuIGNhbGN1bGF0ZSB3b3JkIGZyZXF1ZW5jaWVzIGZvciBlYWNoIHBlcnNvbi4gRmlyc3QsIHdlIGdyb3VwIGJ5IHBlcnNvbiBhbmQgY291bnQgaG93IG1hbnkgdGltZXMgZWFjaCBwZXJzb24gdXNlZCBlYWNoIHdvcmQuIFRoZW4gd2UgdXNlIGxlZnRfam9pbigpIHRvIGFkZCBhIGNvbHVtbiBvZiB0aGUgdG90YWwgbnVtYmVyIG9mIHdvcmRzIHVzZWQgYnkgZWFjaCBwZXJzb24uIEZpbmFsbHksIHdlIGNhbGN1bGF0ZSBhIGZyZXF1ZW5jeSBmb3IgZWFjaCBwZXJzb24gYW5kIHdvcmQuCgpgYGB7ciBGcmVxdWVuY3kgcGVyIHJlc3BvbmRlbnR9CmZyZXF1ZW5jeSA8LSB3b3Jkc19kZiAlPiUgCiAgZ3JvdXBfYnkoUmVzcG9uZGVudElEKSAlPiUgCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JSAKICBsZWZ0X2pvaW4od29yZHNfZGYgJT4lIAogICAgICAgICAgICAgIGdyb3VwX2J5KFJlc3BvbmRlbnRJRCkgJT4lIAogICAgICAgICAgICAgIHN1bW1hcmlzZSh0b3RhbCA9IG4oKSkpICU+JQogIG11dGF0ZShmcmVxID0gbi90b3RhbCkKYGBgCkEgbGVmdF9qb2luIGpvaW5zIG1hdGNoaW5nIHJvd3MgZnJvbSBiIGludG8gYTogYGlubmVyX2pvaW4oYSxiLCBieSA9ICJ4IilgLiAKCkxldCdzIGxvb2sgYXQgdGhlIG1vc3QgZnJlcXVlbnQgd29yZHM6CgpgYGB7cn0KaGVhZChmcmVxdWVuY3kpCmBgYAoKCiMjIyBXb3JkIGNsb3VkCgpUaGlzIHVzZXMgdGhlIGxpYnJhcnkgd29yZGNsb3VkOiAKCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBjb3VudCh3b3JkKSAlPiUKICB3aXRoKHdvcmRjbG91ZCh3b3JkLCBuLCBtYXgud29yZHMgPSAxMDApKQpgYGAKCgojIyBTZW50aW1lbnQgYW5hbHlzaXMgCgpUaWR5dGV4dCBjb21lcyB3aXRoIHRocmVlIGxleGljb25zIHRoYXQgY29udGFpbnMgd29yZHMgZm9yIHNlbnRpbWVudHMsIG92ZXJhbGwgbW9yZSB0aGFuIDI3LDAwMCB3b3JkczoKCgpoZWFkKHNlbnRpbWVudHMpCgpMZXQncyB1c2UgdGhlIG5yYyBsZXhpY29uIGFuZCBsb29rIGZvciBqb3lmdWxsIHdvcmRzLiBGaXJzdCwgZ2V0IHRoZSBqb3lmdWwgd29yZHMgaW50byBhIGRmOgoKCmBgYHtyfQpucmNfam95IDwtIGdldF9zZW50aW1lbnRzKCJucmMiKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAiam95IikKYGBgCgpUaGVuIGxvb2sgZm9yIHRoZW0gaW4gYW5zd2VycyBGb3IgdGhpcywgd2UgdXNlIGlubmVyX2pvaW4sIHdoaWNoIHJldGFpbnMgb25seSB0aGUgbWF0Y2hpbmcgcm93cyBpbiBib3RoIHNldHMuIFRoZSBnZW5lcmFsIHN5bnRheCBpcyBgaW5uZXJfam9pbihhLCBiLCBieSA9ICJ4MSIpYCwgYnV0IGluIHRoZSBzaG9ydGVyIGZvcm1hdCB0aGlzIHJlZHVjZXMgdG86CgpgYGB7cn0Kd29yZHNfZGYgJT4lCiAgaW5uZXJfam9pbihucmNfam95KSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgdG9wX24oMTApCmBgYAoKVGhlIGxvbmcgZm9ybSB3b3VsZCBiZSBgaW5uZXJfam9pbih3b3Jkc19xMSwgbnJjX2pveSwgYnkgPSAid29yZCIpYApidXQgUiBpcyBhYmxlIHRvIGZpZ3VyZSB0aGUgIGFyZ3VtZW50cyBwcm92aWRlZCBpbiB0aGUgc2hvcnQgZm9ybS4KClRvIGRvIHRoZSBzYW1lIGFuYWx5c2lzIGZvciB0aGUgc2VudGltZW50ICdmZWFyJywgc2ltcGx5IHJlcGxhY2UgJ2pveScgd2l0aCAnZmVhcicgYWJvdmUgKGFuZCBwZXJoYXBzIHJlbmFtZSB0aGUgdmFyaWFibGUgdG8gbnJjX2ZlYXIgb3Igc3VjaCkuIAoKCiMjIyBTZW50aW1lbnQgYW5heXNpcyBvbiBvbmUgcXVlc3Rpb24KCkp1c3QgZm9yIFExIGFuZCBhbGwgZm9sbG93LXVwIHF1ZXN0aW9uczoKCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBmaWx0ZXIoUXVlc3Rpb25JRCA9PSAzODYxNzUwKSAlPiUKICBpbm5lcl9qb2luKG5yY19qb3kpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpgYGAKCgojIyMgU2VudGltZW50IG9uIGFsbCBxdWVzdGlvbnMKCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBncm91cF9ieShRdWVzdGlvbklEKSAlPiUKICBpbm5lcl9qb2luKG5yY19qb3kpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBGQUxTRSkKYGBgCldlIHR1cm5lZCBzb3J0aW5nIG9uIHRoZSBjb3VudCBvZmYgaGVyZSBiZWNhdXNlIHdlIHdhbnQgdG8gaGF2ZSB0aGUgcmVzdWx0cyBncm91cGVkIGJ5IHF1ZXN0aW9uIGlkLiBJZiBTb3J0aW5nIHdhcyBUUlVFLCB0aGUgbnVtZXJpYyBjb3VudCB3b3VsZCBiZSB1c2VkIGZpcnN0LCB0aGVuIHRoZSBxdWVzdGlvbiBJRC4gCgojIyMgU2VudGltZW50IGFuYWx5c2lzIG9uIGEgc3ViLXNldCBvZiBhbGwgcXVlc3Rpb25zCgpUaGlzIG5vdCBwYXJ0aWN1bGFybHkgdXNlZnVsIGhlcmUsICBidXQgeW91IGdldCB0aGUgaWRlYToKCmBgYAp3b3Jkc19kZnMgJT4lCiAgZmlsdGVyKFF1ZXN0aW9uID09ICJRMSIgfCBRdWVzdGlvbiA9PSAiUTMiIHwgUXVlc3Rpb24gPT0gIlE3IikgJT4lCiAgaW5uZXJfam9pbihucmNfam95KSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKYGBgCgoKIyMjIE1vc3QgY29tbW9uIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzZW50aW1lbnQgd29yZHMKClVzaW5nIHRoZSBsZXhpY29uIGJpbmcsIHdoaWNoIGNsYXNzaWZpZXMgc2VudGltZW50IHdvcmRzIGFzIHBvc2l0aXZlIG9yIG5lZ2F0aXZlLCB3ZSBjYW4gZmluZCBob3cgd29yZHMgY29zdHJpYnV0ZSB0byB0aGVzZSBvdmVyYWxsIHNlbnRpbWVudHM6CgpgYGB7cn0KYmluZ193b3JkX2NvdW50cyA8LSB3b3Jkc19kZiAlPiUKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQpoZWFkKGJpbmdfd29yZF9jb3VudHMpCmBgYApMZXQncyBncmFwaCB0aGlzIHdpdGggZ2dwbG90MjogCgpgYGB7cn0KYmluZ193b3JkX2NvdW50cyAlPiUKICBncm91cF9ieShzZW50aW1lbnQpICU+JQogIHRvcF9uKDEwKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofnNlbnRpbWVudCwgc2NhbGVzID0gImZyZWVfeSIpICsKICBsYWJzKHkgPSAiQ29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsCiAgICAgICB4ID0gTlVMTCkgKwogIGNvb3JkX2ZsaXAoKQpgYGAKVGhlIHRvcF9uKDEwKSBpcyByZXR1cm5pbmcgbW9yZSB0aGFuIDEwIHJvd3MgaWYgdGhlcmUgYXJlIHRpZXMuIFdpbGwgaGF2ZSB0byB3cml0ZSBhIG1vcmUgY29tcGxleCBleHByZXNzaW9uIHdpdGggZmlsdGVyKCkgYW5kIG1pbl9yYW5rKCkgYmVjYXVzZSB0aGF0J3Mgd2hhdCB0b3BfbiB3cmFwcy4gCgoKSGVyZSdzIGEgY29vbCBjb21iaW5hdGlvbiBvZiB3b3JkY2xvdWRzIHdpdGggYmluZyB3b3JkczoKCmBgYHtyfQpsaWJyYXJ5KHJlc2hhcGUyKQpgYGAKV2UgbmVlZCByZXNoYXBlMiBmb3IgdGhlIGBhY2FzdGAgZnVuY3Rpb24uIGBhY2FzdGAgaXMgYSBjb21wbGV4IGZ1bmN0aW9uIHVzZWQgdG8gdHJhbnNmb3JtIGJldHdlZW4gZGF0YSBmcmFtZXMgYW5kIG1hdHJpY2VzLiBgY29tcGFyaXNvbi5jbG91ZCgpYCBuZWVkcyBhIG1hdHJpeCBhcyBpbnB1dC4gRm9yIG1vcmUgc2VlIGh0dHA6Ly9oYWQuY28ubnovcmVzaGFwZS8uIAoKYGBge3J9CndvcmRzX2RmICU+JQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCJncmF5MjAiLCAiZ3JheTgwIiksCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAxMDApCmBgYAoKIyBXb3JkIGFuZCBEb2N1bWVudCBGcmVxdWVuY3kKCkZyZXF1ZW5jaWVzIGJ5IHNvbWUgdW5pdCBvZiBhbmFseXNpcywgbGlrZSBieSBxdWVzdGlvbiBvciBzb21lIG90aGVyIGNhdGVnb3J5LiBIZXJlIEkgZGVtb25zdHJhdGUgaG93IHRvIGRvIHRoaXMgYnkgR3JvdXBJRC4gTm90IHN1cmUgdGhhdCBtYWtlIHNlbnNlOyBpdCB3b3VsZCBpZiBvbmUgaGFkIHJlYXNvbnMgdG8gZXhwZWN0IHRoYXQgdGhlL3NvbWUgZ3JvdXBzIGFyZSBkaWZmZXJlbnQuIAoKIyMgQ2FsY3VsYXRpbmcgdGVybSBmcmVxdWVuY3kgcGVyIGdyb3VwCgojIyMgV2hvbGUgY29ycHVzCgpTdGVwIDE6IEEgdGliYmxlIHdvcmQgY291bnQgYnkgaW50ZXJ2aWV3IHNvcnRlZCBieSBjb3VudC4gVGhpcyBoYXMgbGVuZ3RoIDEwOTcgb2JzZXJ2YXRpb25zLCBsZXNzIHRoYW4gdGhlIGZ1bGwgY29ycHVzICgyNjE3IG9icykgYmVjYXVzZSB3ZSBhZ2dyZWdhdGUgaW4gZWFjaCBpbnRlcnZpZXc6IApgY291bnQoeCwgLi4uLCB3dCA9IE5VTEwsIHNvcnQgPSBGQUxTRSlgIHdpdGggeCB0aGUgdGJsIGFuZCBgLi4uYCB0aGUgdmFyaWFibGVzIHRvIGdyb3VwIGJ5OyB3dCBpcyBmb3Igd2VpZ2h0aW5nIGJ5IGNvdW50aW5nIG51bWJlciBvZiByb3dzLiAKCmBgYHtyfQpncm91cF93b3JkcyA8LSB3b3Jkc19kZiAlPiUKICBjb3VudChHcm91cElELCB3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgdW5ncm91cCgpCgpoZWFkKGdyb3VwX3dvcmRzKQpgYGAKU28gaGVyZSB3ZSBoYXZlIGBjb3VudCh3b3Jkc19kZiwgSW50ZXJ2aWV3LCB3b3JkLCB3dCA9IE5VTEwsIHNvcnQgPSBUUlVFYC4gCgoKU3RlcCAyOiB0b3RhbCBvZiB3b3JkcyBwZXIgZ3JvdXAKYGBge3J9CnRvdGFsX3dvcmRzIDwtIGdyb3VwX3dvcmRzICU+JSAKICBncm91cF9ieShHcm91cElEKSAlPiUgCiAgc3VtbWFyaXplKHRvdGFsID0gc3VtKG4pKQoKdG90YWxfd29yZHMKYGBgCgpTdGVwIDM6IEFkZCB0aGUgdG90YWwgY291bnQgY29sdW1uIHRvIGVhY2ggb2YgdGhlIHdvcmRzIGFuZCB0aGVpciBpbmRpdmlkdWFsIGZyZXF1ZW5jeQoKYGBge3J9Cmdyb3VwX3dvcmRzIDwtIGxlZnRfam9pbihncm91cF93b3JkcywgdG90YWxfd29yZHMsIGJ5ID0gIkdyb3VwSUQiKQpoZWFkKGdyb3VwX3dvcmRzKQpgYGAKYExlZnRfam9pbihhLCBiLCBieSA9ICJ4MScpYCBqb2lucyBtYXRoaW5nIHJvd3MgZnJvbSBiIHRvIGEuIFRoZSAiYnkiIGlzIGluY2x1ZGVkIGhlcmUgZm9yIGNsYXJpdHksIGJ1dCBjb3VsZCBiZSBvbWl0dGVkIGJlY2F1c2UgaXQgaXMgdGhlIHNpbmdsZSBzaGFyZWQgdmFyaWFibGUgaW4gYm90aCB0YmxzLgoKTGV0J3MgbG9vayBhdCB0aGUgdGVybSBmcmVxdWVuY3kuIFRvIG1ha2UgdGhpcyB2aXN1YWxseSBpbnNwZWN0YWJsZSwgd2UgZmlsdGVyIG91dCAicXVlc3Rpb24iIGFuZCBmYWNldCBvbiBhIHNtYWxsZXIgbnVtYmVyIG9mIGdyb3Vwcy4gKEEgYmV0dGVyIHNvbHV0aW9uIHJlcXVpcmVzIGZpZGRsaW5nIHdpdGggdGhlIGdlb21faGlzdHJvZ3JhbS4pCgpgYGB7cn0KZ3JvdXBfd29yZHNfc21hbGwgPC0gZ3JvdXBfd29yZHMgJT4lIAogIGZpbHRlcihHcm91cElEIDwgIDE5MjM1ICYgd29yZCAhPSAicXVlc3Rpb24iKSAKCmdncGxvdChncm91cF93b3Jkc19zbWFsbCwgYWVzKG4vdG90YWwsIGZpbGwgPSBHcm91cElEKSkgKwogIGdlb21faGlzdG9ncmFtKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH5Hcm91cElELCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeSIpCmBgYAoKCgojIyMgdGZfaWRmIEZvciBzdHVkZW50cyByZXBvbnNlcyBvbmx5CgpUaGUgdGZfaWRmIHZhbHVlIGJlIGNhbGN1YXRlZCBiYXNlZCBvbiB0aGUgdGFibGUganVzdCBjcmVhdGVkIGxpa2UgdGhpczoKCmBgYHtyfQpncm91cF93b3JkcyA8LSBncm91cF93b3JkcyAlPiUKICBiaW5kX3RmX2lkZih3b3JkLCBHcm91cElELCBuKQpoZWFkKGdyb3VwX3dvcmRzKQpgYGAKV29yZHMgdGhhdCBhcmUgdmVyeSBjb21tb24gYWNyb3NzIGFsbCBncm91cHMgaGF2ZSBhIGxvdyBpZGYgdGVybSwgYW5kIHNvIHdpbGwgdGhlIHRmX2lkZi4gTGV0J3MgZmluZCB0aGUgZGlzdGluY3Qgd29yZHMgYnkgc29ydGluZyBvbiB0aGUgdGZfaWRmOgoKYGBge3J9Cmdyb3VwX3dvcmRzICU+JQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQpgYGAKCldlIGNhbiBwbG90IHRoaXMgcmVzdWx0IChhZ2Fpbiwgb25seSBvbiB0aGUgZmlyc3QgOCBncm91cHMgZm9yIHZpc2liaWxpdHkpCgpgYGB7cn0KZ3JvdXBfd29yZHMgJT4lCiAgZmlsdGVyKEdyb3VwSUQgPCAgMTkyMzUpICU+JSAgICAjIE9ubHkgYSBzdWJzZXQgb2YgZ3JvdXBzCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JQogIG11dGF0ZSh3b3JkID0gZmFjdG9yKHdvcmQsIGxldmVscyA9IHJldih1bmlxdWUod29yZCkpKSkgJT4lIAogIGdyb3VwX2J5KEdyb3VwSUQpICU+JSAKICB0b3BfbigxMCkgJT4lIAogIHVuZ3JvdXAgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCB0Zl9pZGYsIGZpbGwgPSBHcm91cElEKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gInRmLWlkZiIpICsKICBmYWNldF93cmFwKH5Hcm91cElELCBuY29sID0gMiwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoqKlRvZG86KiogdW5wYWNrIHRoZSBtdXRhdGUgbGluZSAKCgoKIyBUb3BpYyBtb2RlbHMKCkluIGdlbmVyYWwsIFRNcyBjbGFzc2lmeSBkb2N1bWVudHMgaW50byB0b3BpY3MgYW5kIGRldGVybWluZSB0aGUgb3ZlcmxhcCBvZiBhIGRvY3VtZW50IHdpdGggYSB0b3BpYy4gRWFjaCB0b3BpYyBpcyBtYWRlIHVwIG91dCBvZiBhIG51bWJlciBvZiB3b3Jkcy4gCgojIyBDYXN0aW5nIHRoZSB0aWR5IHRleHQgZGYgaW50byBhIGRvY3VtZW50LXRlcm0gbWF0cml4CgpNb3N0IHRleHQgbWluaW5nIHBhY2thZ2VzIHVzZSAgRFRNIGZvcm1hdCwgd2hpY2ggaXMgYSBtYXRyaXggd2hlcmU6CgoqIGVhY2ggcm93IHJlcHJlc2VudHMgb25lIGRvY3VtZW50IChzdWNoIGFzIGEgYm9vayBvciBhcnRpY2xlKSwKKiBlYWNoIGNvbHVtbiByZXByZXNlbnRzIG9uZSB0ZXJtLCBhbmQKKiBlYWNoIHZhbHVlICh0eXBpY2FsbHkpIGNvbnRhaW5zIHRoZSBudW1iZXIgb2YgYXBwZWFyYW5jZXMgb2YgdGhhdCB0ZXJtIGluIHRoYXQgZG9jdW1lbnQuCgpIZXJlJ3MgaG93IHRvIHR1cm4gYSB0aWR5IHRleHQgZGYgaW50byBhIERUTToKCmBgYHtyfQpsaWJyYXJ5KHRtKQpgYGAKRmlyc3Qgd2UgZmlsdGVyIG91dCB0aGUgb3Zlcmx5IGZyZXF1ZW50IHdvcmQgInF1ZXN0aW9uKHMpIiAgYW5kIHNvbWUgb3RoZXIgd29yZHMgd2hpY2ggYXJlIG5lY2Vzc2FyaWx5IGZyZXF1ZW50IGJlY2F1c2UgdGhleSByZWZlciB0byB0aGUgbWNxIGdlbnJlLiBBbmQgdGhlcmUncyB0aGF0IG51bWJlciB3aGljaCBhbHNvIG5lZWRzIHRvIGdvLgoKYGBge3J9CiMgcmVtb3ZlIHF1ZXN0aW9uIGZyb20gY29ycHVzCndvcmRzX2RmIDwtIHdvcmRzX2RmICAlPiUKICBmaWx0ZXIod29yZCAhPSAicXVlc3Rpb24iKSAlPiUKICBmaWx0ZXIod29yZCAhPSAicXVlc3Rpb25zIikgJT4lCiAgZmlsdGVyKHdvcmQgIT0gIm9wdGlvbiIpICU+JQogIGZpbHRlcih3b3JkICE9ICJvcHRpb25zIikgJT4lCiAgZmlsdGVyKHdvcmQgIT0gImFuc3dlciIpICU+JQogIGZpbHRlcih3b3JkICE9ICJhbnN3ZXJzIikgJT4lCiAgZmlsdGVyKHdvcmQgIT0gIm1jcSIpICU+JQogIGZpbHRlcih3b3JkICE9ICIzMjcwMjkiKQpgYGAKCgpgYGB7cn0Kc3R1ZGVudHNfZHRtIDwtIHdvcmRzX2RmICU+JQogICAgY291bnQoUXVlc3Rpb25JRCwgd29yZCkgJT4lCiAgICBjYXN0X2R0bShRdWVzdGlvbklELCB3b3JkLCBuKQoKc3R1ZGVudHNfZHRtCmBgYAoKIyMgVG9waWNzIGFjcm9zcyBxdWVzdGlvbnMKCldpdGggdGhpcyB3ZSBoYXZlIGEgZHRtIGZvciBlYWNoIG9mIHRoZSBxdWVzdGlvbnMuIEJhc2VkIG9uIHRoaXMgd2UgY2FuIG1pbmUgZm9yIHRvcGljczoKCmBgYHtyfQpsaWJyYXJ5KHRvcGljbW9kZWxzKQpgYGAKCmBgYHtyfQojIHNldCBhIHNlZWQgc28gdGhhdCB0aGUgb3V0cHV0IG9mIHRoZSBtb2RlbCBpcyBwcmVkaWN0YWJsZQpzdHVkZW50c19sZGEgPC0gTERBKHN0dWRlbnRzX2R0bSwgayA9IDUsIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSAxMjM0KSkKc3R1ZGVudHNfbGRhCmBgYApUaGlzIG9iamVjdCBjb250YWlucyBhIG1vZGVsIHdpdGggZml2ZSBncm91cHMuIFRoaXMgbW9kZWwgbmVlZHMgdG8gYmUgaW5zcGVjdGVkIHRvIGdhaW4gaW5zaWdodHMuIEZvciBpbnN0YW5jZSwgd2UgY2FuIGxvb2sgYXQgaG93IHRoZSB3b3JkcyByZWxhdGUgdG8gdGhlIHRvcGljcywgZXh0cmFjdGluZyB0aGUgYGJldGFgIHZhbHVlIGZyb20gdGhlIG1hdHJpeC4gV2UgdXNlIHRpZHkgdG8gdHVybiB0aGF0IGNvbHVtbiBpbnRvIGEgdGliYmxlIHRvcGljLXRlcm0tYmV0YS4gVGhlIGJldGFzIGNhbiBiZSBpbnRlcnByZXRlZCBhcyBwcm9iYWJpbGl0aWVzIG9yIHRoZSB0ZXJtIGJlbG9uZ2luZyB0byB0aGUgdG9waWMuIAoKYGBge3J9CnN0dWRlbnRzX3RvcGljcyA8LSB0aWR5KHN0dWRlbnRzX2xkYSwgbWF0cml4ID0gImJldGEiKQpzdHVkZW50c190b3BpY3MKYGBgCkxldCdzIGxvb2sgYXQgdGhlIHRvcC0xMCB0ZXJtczoKCmBgYHtyfQpzdHVkZW50c190b3BfdGVybXMgPC0gc3R1ZGVudHNfdG9waWNzICU+JQogIGdyb3VwX2J5KHRvcGljKSAlPiUKICB0b3BfbigxMCwgYmV0YSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2UodG9waWMsIC1iZXRhKQpgYGAKCkFuZCB2aXN1YWxpc2UgdGhlbToKCmBgYHtyfQpzdHVkZW50c190b3BfdGVybXMgJT4lCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyKHRlcm0sIGJldGEpKSAlPiUKICBnZ3Bsb3QoYWVzKHRlcm0sIGJldGEsIGZpbGwgPSBmYWN0b3IodG9waWMpKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCgoKVGhlIGFsdGVybmF0aXZlIHZpZXcgaXMgdG8gbG9vayBmb3IgdGVybXMgd2hpY2ggZGlmZmVyIG1vc3QgYmV0d2VlbiB0b3BpY3MuIAoKCmBgYHtyfQpiZXRhX3NwcmVhZCA8LSBzdHVkZW50c190b3BpY3MgJT4lCiAgbXV0YXRlKHRvcGljID0gcGFzdGUwKCJ0b3BpYyIsIHRvcGljKSkgJT4lCiAgc3ByZWFkKHRvcGljLCBiZXRhKSAlPiUKICBmaWx0ZXIodG9waWMxID4gLjAwMSB8IHRvcGljMiA+IC4wMDEpICU+JQogIG11dGF0ZShsb2dfcmF0aW8gPSBsb2cyKHRvcGljMiAvIHRvcGljMSkpCgpiZXRhX3NwcmVhZApgYGAKPiBUbyBkbzogUGxvdCBhIGdyYXBoIHdpdGggdGhlIGhpZ2hlc3QgKHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSkgZGlmZmVyZW5jZXMgaW4gbG9nIHJhdGlvLiAKCgojIFJlbGF0aW9uc2hpcCBiZXR3ZWVuIHdvcmRzIC0gTi1ncmFtcwoKPiBOT1QgTU9ESUZJRUQgWUVUIEZPUiBDT01NRU5UUyBBTkFMWVNJUwoKYGBge3J9CmJpZ3JhbXMgPC0gdGV4dF9kZiAlPiUKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgVGV4dCwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpCgpoZWFkKGJpZ3JhbXMpCmBgYAoKVGhlIHdheSB0byBjb250cm9sIHRoZSB0b2tlbiBzaXplIGlzIHRvIHNldCBwYXJhbWF0ZXJzIG9mIHRoZSB1bm5lc3RfdG9rZW4gZnVuY3Rpb24sIHNlZSAKYGhlbHAodW5uZXN0X3Rva2VuIilgLiBBIHNpbmdsZSB3b3JkIGlzIGFuIG5ncmFtIG9mIGxlbmd0aCAxLCBzbyB0d28gYWRqYWNlbnQgd29yZHMgY2FuIGJlIHVzZWQgYXMgdGhlIHRva2VuIHVuaXQgYnkgYHVubmVzdF90b2tlbnMobmdyYW0sIHR4dCwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpYC4gCgoKIyMjIFJlbW92aW5nIHN0b3Agd29yZHMgCgpNYW55IG9mIHRoZSBiaS1ncmFtcyB3aWxsIGJlIHN0b3Agd29yZCBwaHJhc2VzLCBvZiB3aGljaCB3ZSBuZWVkIHRvIGdldCByaWQgb2ZmLiBUaGUgcGF0dGVybiBpcyBzZXBhcmF0ZS9maWx0ZXIvY291bnQvdW5pdGU6IAoKU3RlcCAxOiBzZXBhcmF0ZSBpbnRvIHR3byBjb2x1bW5zCgoKYGBge3J9CmJpZ3JhbXNfc2VwYXJhdGVkIDwtIGJpZ3JhbXMgJT4lCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpCmBgYAoKU3RlcCAyOiBGaWx0ZXIgb3V0IHN0b3B3b3JkcyAKCmBgYHtyfQpiaWdyYW1zX2ZpbHRlcmVkIDwtIGJpZ3JhbXNfc2VwYXJhdGVkICU+JQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQogIGZpbHRlcighd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQpCmBgYAoKTXVjaCBsZXNzIHBhaXJzISAgTGV0J3MgY291bnQgdGhlbToKClN0ZXAgMzogY291bnQKCmBgYHtyfQpiaWdyYW1fY291bnRzIDwtIGJpZ3JhbXNfZmlsdGVyZWQgJT4lIAogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpCmBgYAoKQnV0IHN0aWxsIGxvdHMgb2Ygbm9uLW1lYW5pbmcgd29yZHMsIGFsbCB0aG9zZSBhaHMgYW5kIHVocywgYmVjYXVzZSB0aGlzIGlzIHRhbGsuIExldCdzIGZpbHRlciBzb21lIG1vcmUuIAoKYGBge3J9CmJpZ3JhbXNfZmlsdGVyZWQgPC0gYmlncmFtc19maWx0ZXJlZCAlPiUKICBmaWx0ZXIoIXdvcmQxICVpbiUgc3RvcF93b3JkczIkd29yZCkgJT4lCiAgZmlsdGVyKCF3b3JkMiAlaW4lIHN0b3Bfd29yZHMyJHdvcmQpCmBgYAoKU3RlcCA0OiBBbmQgbGV0J3MgcHJvZHVjZSBhIHVuaXRlZCB2ZXJzaW9uIGFnYWluOgoKCmBgYHtyfQpiaWdyYW1zX3VuaXRlZCA8LSBiaWdyYW1zX2ZpbHRlcmVkICU+JQogIHVuaXRlKGJpZ3JhbSwgd29yZDEsIHdvcmQyLCBzZXAgPSAiICIpCgpoZWFkKGJpZ3JhbXNfdW5pdGVkKQpgYGAKCiMjIyBBbmFseXNpcwoKQ291bnRpbmcgYmVjYXVzZSB3ZSBjYW46CiAKCmBgYHtyfQpiaWdyYW1zX3VuaXRlZCAlPiUKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKQpgYGAKCiBMZXRzIGxvb2sgYXQgYWxsIHBocmFzZXMgd2l0aCBhIHNwZWNpZmljIHdvcmQgaW4gcG9zaXRpb24gMgpgYGB7cn0KYmlncmFtc19maWx0ZXJlZCAlPiUKICBmaWx0ZXIod29yZDIgPT0gImFzc2lnbm1lbnQiKSAlPiUKICBjb3VudChJbnRlcnZpZXcsIHdvcmQxLCBzb3J0ID0gVFJVRSkKYGBgCiBJbXByb3ZlbWVudDogdXNlIHJlZ2V4IHRvIGRlYWwgd2l0aCBkaWZmZXJlbnQgZm9ybXMgb2YgdGhlIHN0ZW06IAogCmBgYHtyfQpiaWdyYW1zX2ZpbHRlcmVkICU+JQogIGZpbHRlcihncmVwbCgiYXNzaWduIiwgd29yZDIpKSAlPiUKICBjb3VudChJbnRlcnZpZXcsIHdvcmQxLCBzb3J0ID0gVFJVRSkKYGBgCgpgZ3JlcGwocGF0dGVybiwgeClgIGlzIHRoZSBsb2dpYyBmb3JtIG9mIGBncmVwYCwgcmV0dXJuaWduIGEgdmVjdG9yIG9mIG1hdGNoIG9yIG5vdCBmb3IgZWFjaCBlbGVtZW50IG9mIHguIAoKIyMjIFNlbnRpbWVudCBhbmFseXNpcyB3aXRoIHdvcmRzIGluIGNvbnRleHQKCkZvciBzZW50aW1lbnQgYW5hbHlzaXMgd2UgbmVlZCBpbiBwYXJ0aWN1bGFyIGludG8gbmVnYXRpb25zIiIKCmBgYHtyfQpiaWdyYW1zX3NlcGFyYXRlZCAlPiUKICBmaWx0ZXIod29yZDEgPT0gIm5vdCIpICU+JQogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpCmBgYAoKTGV0J3MgdXNlIHRoYXQgZm9yIHNvbWUgYW5hbHlzaXMgd2l0aCB0aGUgQUZJTk4gbGV4aWNvbiwgd2l0aCB1c2VzIGEgbnVtZXJpYyBzY29yZSB0byBleHByZXNzIHNlbnRpbWVudCBkaXJlY3Rpb246IAoKYGBge3J9CkFGSU5OIDwtIGdldF9zZW50aW1lbnRzKCJhZmlubiIpCmhlYWQoQUZJTk4pCmBgYAoKCmBgYHtyfQpub3Rfd29yZHMgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lCiAgZmlsdGVyKHdvcmQxID09ICJub3QiKSAlPiUKICBpbm5lcl9qb2luKEFGSU5OLCBieSA9IGMod29yZDIgPSAid29yZCIpKSAlPiUKICBjb3VudCh3b3JkMiwgc2NvcmUsIHNvcnQgPSBUUlVFKSAlPiUKICB1bmdyb3VwKCkKCm5vdF93b3JkcwpgYGAKCihUaGlzIG5lZWRzIHNvbWUgdW5wYWNraW5nKQpXZSBzZWUgaG93IGltcG9ydGFudCBpdCBpcyB0byB3YXRjaCBvdXQgZm9yIG5lZ2F0aW9ucyB3aGVuIGl0IGNvbWVzIHRvIHNlbnRpbWVudHMuIE90aGVyIG5lZ2F0aW9uIHdvcmRzIGRlc2VydmUgYXR0ZW50aW9uLCB0b286CgpgYGB7cn0KbmVnYXRpb25fd29yZHMgPC0gYygibm90IiwgIm5vIiwgIm5ldmVyIiwgIndpdGhvdXQiKQoKbmVnYXRlZF93b3JkcyA8LSBiaWdyYW1zX3NlcGFyYXRlZCAlPiUKICBmaWx0ZXIod29yZDEgJWluJSBuZWdhdGlvbl93b3JkcykgJT4lCiAgaW5uZXJfam9pbihBRklOTiwgYnkgPSBjKHdvcmQyID0gIndvcmQiKSkgJT4lCiAgY291bnQod29yZDEsIHdvcmQyLCBzY29yZSwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQoKbmVnYXRlZF93b3JkcwpgYGAKClRoaXMgb25seSBtYWtlcyBhIHNtYWxsIGRpZmZlcmVuY2UgaW4gdGhlIDUgaW50ZXJ2aWV3cywgYnV0IHdpdGggYSBsYXJnZXIgY29ycHVzIHdlIGFyZSBib3VuZCB0byBzZWUgbW9yZSBpbXBhY3QuIFdlIGNhbiBjb21wdXRlIGFuZCB2aXN1YWxpc2UgdGhlIGltcGFjdCBieSBjb21wdXRpbmcgYSB2YWx1ZSBgc2NvcmUgKiBuYDogCgpgYGB7cn0KbmVnYXRlZF93b3JkcyAlPiUKICBtdXRhdGUoY29udHJpYnV0aW9uID0gbiAqIHNjb3JlKSAlPiUKICBhcnJhbmdlKGRlc2MoYWJzKGNvbnRyaWJ1dGlvbikpKSAlPiUKICBoZWFkKDIwKSAlPiUKICBtdXRhdGUod29yZDIgPSByZW9yZGVyKHdvcmQyLCBjb250cmlidXRpb24pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQyLCBuICogc2NvcmUsIGZpbGwgPSBuICogc2NvcmUgPiAwKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICB4bGFiKCJXb3JkcyBwcmVjZWRlZCBieSBcIm5vdFwiIikgKwogIHlsYWIoIlNlbnRpbWVudCBzY29yZSAqIG51bWJlciBvZiBvY2N1cnJlbmNlcyIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpUaGlzIGFsc28gbWFrZXMgaXQgdmlzaWJsZSB0aGF0IGluIHRoZSBmaXJzdCA1IGludGVydmlld3MgdGhlcmUgd2VyZSBubyBuZWdhdHZlIHdvcmRzIG5lZ2F0ZWQsIHN1Y2ggYXMgIm5vIGRvdWJ0Ii4gCgojIyMgR3JhcGhpbmcgYmlncmFtIG5ldHdvcmtzIAoKYGBge3IgaW5pdGlhbGlzZSBpZ3JhcGh9CmxpYnJhcnkoaWdyYXBoKQpgYGAKClRoZSBkYXRhZnJhbWUgd2UgbmVlZCBmb3IgdGhpcyBpcyBgYmlncmFtX2NvdW50c2Agd2l0aCB3b3JkMSwgd29yZDIgYW5kIG4gYXMgdGhlICJ3ZWlnaHQiLiBMZXQncyB1c2Ugb25seSB0aGUgdGhvc2Ugd2l0aCBuIEdFRyAyOgoKCmBgYHtyfQpiaWdyYW1fZ3JhcGggPC0gYmlncmFtX2NvdW50cyAlPiUKICBmaWx0ZXIobiA+PSAyKSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKQoKYmlncmFtX2dyYXBoCmBgYApGb3IgcGxvdHRpZ25nIHdlIHVzZSBnZ3JhcGg6CgpgYGB7ciBnZ3JwcmFwaCBpbml0YWxpYXNlZH0KbGlicmFyeShnZ3JhcGgpCmBgYAoKYGBge3J9CnNldC5zZWVkKDIwMTcpCgpnZ3JhcGgoYmlncmFtX2dyYXBoLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkKYGBgCgpOb3QgYSBwYXJ0aWN1bGFybHkgY29ubmVjdGVkIGdyYXBoLCBidXQgdGhlIHByaW5jaXBsZSBpcyBjbGVhci4gCgojIyMgQ29ycmVsYXRpb25zIGFtb25nIHF1ZXN0aW9ucwoKYGBge3J9CmxpYnJhcnkod2lkeXIpCmBgYAoKYGBge3J9CndvcmRfcGFpcnMgPC0gd29yZHNfZGYgJT4lCiAgcGFpcndpc2VfY291bnQod29yZCwgUXVlc3Rpb24sIHNvcnQgPSBUUlVFKQoKd29yZF9wYWlycwpgYGAKVGhlc2UgYXJlIHBhaXIgY291bnRzIHBlciBxdWVzdGlvbi4gSGVyZSdzIHRoZSBzYW1lIGZvciBpbnRlcnZpZXdzOiAKCmBgYHtyfQp3b3JkX3BhaXJzIDwtIHdvcmRzX2RmICU+JQogIHBhaXJ3aXNlX2NvdW50KHdvcmQsIEludGVydmlldywgc29ydCA9IFRSVUUpCgp3b3JkX3BhaXJzCmBgYApOb3RlIHRoYXQgdGhlc2UgYmVjb21lIHJhdGhlciBiaWcgdGFibGVzIGJlY2F1c2Ugc28gbWFueSB0aGluZ3MgZ28gdG9nZWh0ZXIgb24gdGhhdCBzY2FsZS4gV2UgY2FuIGFsc28gbG9vayB0aGlzIHdheSBmb3Igc3BlY2lmaWMgY29tYmluYXRpb25zIG9mIHdvcmRzLiBMZXQncyBnbyBiYWNrIHRvIHBhaXJzIHdpdGhpbiBxdWVzdGlvbnMsIGFuZCBmdXJ0aGVyIHJlZHVjZSB0byB0aGUgZmlyc3QgaW50ZXJ2aWV3OgoKYGBge3J9CndvcmRfcGFpcnMgPC0gd29yZHNfZGYgJT4lCiAgZmlsdGVyKEludGVydmlldyA9PSAxKSAlPiUKICBwYWlyd2lzZV9jb3VudCh3b3JkLCBRdWVzdGlvbiwgc29ydCA9IFRSVUUpCgp3b3JkX3BhaXJzCmBgYAoKQWRuIGxldHMgbG9vayBmb3IgcGFpcnMgdGhhdCBoYXZlICJhc3NpZ25tZW50IiBpbiB0aGVtLiBOb3RlIHRoYXQgdGhlIGxldmVsIG9mIGNvdW50aW5nIGlzIHRoZSBRdWVzdGlvbi4gT25lIHByb2JsZW0gaXMgdGhhdCB3ZSBoYXZlIHF1ZXN0aW9ucyB3aXRoIGxhYmVscyBRPGk+IGFuZCBRPGk+LUYsIHdoaWNoIGluIHRoaXMgYW5hbHlzaXMgZ2V0IGNvdW50ZWQgc2VwYXJhdGVseS4gCgoKYGBge3J9CndvcmRfcGFpcnMgJT4lCiAgICBmaWx0ZXIoaXRlbTEgPT0gImFzc2lnbm1lbnQiKQpgYGAKCkxldCdzIGNhbGN1bGF0ZSBhIGNvcnJlbGF0aW9uIGJldHdlZW4gKGZyZXF1ZW50KSB3b3JkczoKCmBgYHtyfQp3b3JkX2NvcnMgPC0gd29yZHNfZGYgJT4lCiAgZ3JvdXBfYnkod29yZCkgJT4lCiAgZmlsdGVyKG4oKSA+PSAyMCkgJT4lCiAgcGFpcndpc2VfY29yKHdvcmQsIFF1ZXN0aW9uLCBzb3J0ID0gVFJVRSkKCndvcmRfY29ycwpgYGAKClBsb3RzIGhlbHAgdG8gZ2V0IGFuIG92ZXJ2aWV3OiAKCmBgYHtyfQp3b3JkX2NvcnMgJT4lCiAgZmlsdGVyKGl0ZW0xICVpbiUgYygibWFyayIsICJ0aW1lIiwgImlkZWEiLCAiYXNzaWdubWVudCIpKSAlPiUKICBncm91cF9ieShpdGVtMSkgJT4lCiAgdG9wX24oNikgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShpdGVtMiA9IHJlb3JkZXIoaXRlbTIsIGNvcnJlbGF0aW9uKSkgJT4lCiAgZ2dwbG90KGFlcyhpdGVtMiwgY29ycmVsYXRpb24pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBmYWNldF93cmFwKH4gaXRlbTEsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCkFuZCBiYXNlZCBvbiBjb3JyZWxhdGlvbnMgd2UgY2FuIGNyZWF0ZSBhIG5ldHdvcmsgZ3JhcGg6IAoKYGBge3J9CnNldC5zZWVkKDIwMTYpCgp3b3JkX2NvcnMgJT4lCiAgZmlsdGVyKGNvcnJlbGF0aW9uID4gLjE1KSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gY29ycmVsYXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9ub2RlX3BvaW50KGNvbG9yID0gImxpZ2h0Ymx1ZSIsIHNpemUgPSA1KSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSkgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCgoKIyBNaXNjYWxsZW5lb3VzCgoKCiMjIFN0ZW1taW5nCgpUaGlzIG5vdyBhbHNvIGlzIHRoZSBwb2ludCB3ZXJlIHdlIGFsc28gY291bGQgZG8gc29tZSBzdGVtbWluZy4gRm9yIHRoaXMgd2UgbmVlZCB0aGlzIGxpYnJhcnk6CmBgYHtyfQpsaWJyYXJ5KFNub3diYWxsQykKYGBgCldoaWNoIGdpdmVzIHVzIGEgZnVuY3Rpb24gJ3dvcmRzdGVtJzogCgpgYGB7ciBzdGVtbWluZ30Kd29yZHNfc3RlbW1lZCA8LSBtdXRhdGUod29yZHNfZGYsIHdvcmQgPSB3b3JkU3RlbSh3b3JkKSkKaGVhZCh3b3Jkc19zdGVtbWVkLCBuPTIwTCkKYGBgCkJ1dCB3ZSBkb24ndCB3YW50IHRvIHVzZSB0aGUgc3RlbW1lZCB3b3JkcyBqdXN0IG5vdyAgYmVjYXVzZSBpdCB3b3VsZCBpbnRlZmVyZSB3aXRoIHRoZSBzZW50aW1lbnQgYW5hbHlzaXMuIFN0ZW1taW5nIGlzIHVzZWZ1bCwgYnV0IG9ubHkgb25jZSB3ZSBubyBsb25nZXIgbmVlZCAicmVhbCIgd29yZHMsIHN1Y2ggYXMgZm9yIHRvcGljIG1vZGVsbGluZyBhbmQgdGhlIGxpa2VzLgoKCgoKCiMgQXBwZW5kaWNlcwoKIyMgTGVzc29ucyBsZWFybmVkCgpUbyBtYWtlIGFuYWx5c2lzIGZsZXhpYmxlLCB1c2UgYSBkaXN0aW5jdCB2YXJpYWJsZSBuYW1lIGZvciB0aGUgZGF0YSBzZXQgdGhlIGFuYWx5c2lzIGlzIGJhc2VkIG9uLCBhbmQgYmluZCBpdCBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBhbmFseXNpcyB0byB0aGUgc3BlY2lmaWMgZGF0YSBzZXQsIHdoaWNoIHlvdSBleHBlY3Qgd2lsbCBiZSB2YXJpZWQuIAoKCg==