Corpus
Twitter provides us with vast amounts of user-generated language data, which is a dream for anyone wanting to conduct textual analysis. The twitteR
library provides access to Twitter data. Twitter marks its use as the ‘official’ way to download its tweets. An attractive and ‘easy-to-use’ alternative to Twitter’s ‘official rules’ is based on the use of the rtweet
package. The following link seems to be a more updated package. This set of slides offers an easy-to-follow tutorial, showing the pipeline that you need.
Twitter’s link to create Twitter applications is https://developer.twitter.com/en/apps. You need to be logged in to Twitter to create a new app. This will provide you a set of 5 items related to the application called app
, consumerKey
, consumerSecret
, accessToken
and accessSecret
. Both accessToken
and accessSecret
need to be activated after receiving the consumerKey
and consumerSecret
. Five parameters need to be used in the final authentification function call, create_token()
.
token <- create_token(
app = app,
consumer_key = consumer_key,
consumer_secret = consumer_secret,
access_token = access_key,
access_secret = access_secret
)
Once the authentification is done, tweets of any user or hashtag can be retrieved and converted to a corpus. In this case, I have decided to make a corpus with the tweets of two mobile game accounts. As they are similar games, performing classification of tweets will be a challenging task. Only the last 1000 tweets of each account are retrieved.
Therefore, we have a binary classification problem, where the class is clashroyale
or clashofclans
. As we are working with text, the predictive features that we have are related to words.
library(rtweet)
# retrieve user tweets
n <- 1000
clashroyale_tweets <- get_timeline("clashroyale", n = n)
clashofclans_tweets <- get_timeline("clashofclans", n = n)
In the first 5 tweets of each dataset we can see that the tweets don’t only have words. There are also links and emotes for example. In the next section we will have to decide what we want to do with those words. Apart from the text, many other data is return by the previous function. In total, there are 90 columns, but we will only use a few of them. The most important one is the text
column. We will use some other features such as the date for visualization.
head(clashroyale_tweets, n = 5L)
head(clashofclans_tweets, n = 5L)
clashroyale_tweets$text[1:5]
[1] "Not tried a new deck out yet? ⚔️ \n\nThe 1v1 Showdown is the perfect Party Mode for this and is here all season long! https://t.co/Hgq6QIurmM"
[2] "Welcome to the year of the Tiger 🐯 https://t.co/BU8EXwAyNc"
[3] "Thank YOU for an amazing 2021 year! 🎊\n\nHere's to 2022 👇\nhttps://t.co/wQ8sqE0Q4j https://t.co/qUpS7kjrqm"
[4] "Happy New Year! 🥳\nMay 2022 be full of Victories and Crowns! 👑👑👑 https://t.co/VJdu458cRH"
[5] "☃️ ❄️ 🥶 https://t.co/huDJSbQNra"
clashofclans_tweets$text[1:5]
[1] "Caption this! \n\nThe face you and your Clan mates make when... 👇 https://t.co/mbNP1RIGVB"
[2] "New year, new Village layout? 🧐 \n\nThen check out @ClashChamps, and use the Advanced Search tool to find top layouts! Narrow down by Town Hall level, base type, sort by Most Downloaded, Most Recent, Highest Rated, and more!\n\nhttps://t.co/pykPfv0CWy"
[3] "Let's gooooooooo! https://t.co/RkzQs3z1sp https://t.co/1fD64uRb3s"
[4] "Predictions? 🧐🍿📺 https://t.co/RsNxCCAsw0"
[5] "The 2021 #ClashOfClans World Championships brought us SO MANY incredible matches...but there were also some surprising turn arounds and losses. \n\nWhich was the one World Champion match that didn't happen you wished we could've seen? \n\nComment below! https://t.co/KYMQ5FlD8Z"
We can use the tm
library to build a corpus for each class. Each tweet will be a document in this corpus. Then we can merge them to have a single corpus. Building a corpus is recommended because the tm
package offers many transformations for preprocessing text.
library(tm)
Loading required package: NLP
# combine both frames in a single, binary, annotated set
tweets <- rbind(clashroyale_tweets, clashofclans_tweets)
# interpreting each element of the annotated vector as a document
clashroyale_docs <- VectorSource(clashroyale_tweets$text)
clashofclans_docs <- VectorSource(clashofclans_tweets$text)
# convert to a corpus: supervised classification to be applied in future steps
clashroyale_corpus <- VCorpus(clashroyale_docs)
clashofclans_corpus <- VCorpus(clashofclans_docs)
# merge, concatenate both groups-corpuses
corpus <- c(clashroyale_corpus, clashofclans_corpus)
Visualization
Visualizing the data is important to understand our corpus. In this section there are various time series plots, donut plots and wordclouds.
Time Series Plot
We can use the rtweet
package get a time series plot with the frequencies of tweets. In these examples, I analyse the frequencies of both accounts by month, week and day. The tweet frequencies are similar, Clash Royale has more tweets.
ts_plot(dplyr::group_by(tweets, screen_name), "month") +
ggplot2::theme_minimal() +
ggplot2::theme(plot.title = ggplot2::element_text(face = "bold")) +
ggplot2::labs(
x = "Date", y = "Count",
title = "Frequency of Tweets from Clash Royale and Clash of Clans",
subtitle = "Tweet counts aggregated by month"
)
Attaching package: ‘ggplot2’
The following object is masked from ‘package:NLP’:
annotate
ts_plot(dplyr::group_by(tweets, screen_name), "week") +
ggplot2::theme_minimal() +
ggplot2::theme(plot.title = ggplot2::element_text(face = "bold")) +
ggplot2::labs(
x = "Date", y = "Count",
title = "Frequency of Tweets from Clash Royale and Clash of Clans",
subtitle = "Tweet counts aggregated by week"
)
ts_plot(dplyr::group_by(tweets, screen_name), "day") +
ggplot2::theme_minimal() +
ggplot2::theme(plot.title = ggplot2::element_text(face = "bold")) +
ggplot2::labs(
x = "Date", y = "Count",
title = "Frequency of Tweets from Clash Royale and Clash of Clans",
subtitle = "Tweet counts aggregated by day"
)
Initial Wordclouds
Before starting learning the exposed machine learning models, let’s build a wordcloud with the following package [3]. Its wordcloud()
command needs the list of words and their frequencies as parameters. As the words appear in columns in the document-term matrix, the colSums
command is used to calculate the word frequencies. In order to complete the needed calculations, note that the term-document matrix needs to be transformed (casted) to a matrix form with the as.matrix
cast-operator. This initial document-term matrix is very sparse, it contains 2000 documents and 7854 terms.
We can see that the generated wordclouds are not very informative. The reason for this is that the most common words are english stop words. These words are very common, but d’t have any meaning. That’s why we should remove them from our corpus.
corpus_dtm_init <- DocumentTermMatrix(corpus)
corpus_dtm_init
<<DocumentTermMatrix (documents: 2000, terms: 7846)>>
Non-/sparse entries: 26582/15665418
Sparsity : 100%
Maximal term length: 33
Weighting : term frequency (tf)
library(wordcloud)
Loading required package: RColorBrewer
word_freqs <- sort(colSums(as.matrix(corpus_dtm_init)[1:n, ]), decreasing = TRUE)
wordcloud(words = names(word_freqs), freq = word_freqs, max.words = 100, random.order = FALSE, colors = brewer.pal(8, "Dark2"))
word_freqs <- sort(colSums(as.matrix(corpus_dtm_init)[(n + 1):(n + n), ]), decreasing = TRUE)
wordcloud(words = names(word_freqs), freq = word_freqs, max.words = 100, random.order = FALSE, colors = brewer.pal(8, "Dark2"))
Better Wordclouds
To make a better wordcloud, we can pass the text directly. A corpus will be generated and stop words will be removed automatically. However, this time emotes are kept, and we can see that some of them are quite common. We can see that the following wordclouds are much more informative. We can already see some differences and similarities between the corpora.
wordcloud(clashroyale_tweets$text, max.words = 50, scale = c(3.5, 0.25), random.order = FALSE, colors = brewer.pal(8, "Dark2"))
Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation) :
transformation drops documents
Warning in tm_map.SimpleCorpus(corpus, function(x) tm::removeWords(x, tm::stopwords())) :
transformation drops documents
wordcloud(clashofclans_tweets$text, max.words = 50, scale = c(3.5, 0.25), random.order = FALSE, colors = brewer.pal(8, "Dark2"))
Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation) :
transformation drops documents
Warning in tm_map.SimpleCorpus(corpus, function(x) tm::removeWords(x, tm::stopwords())) :
transformation drops documents
Warning in wordcloud(clashofclans_tweets$text, max.words = 50, scale = c(3.5, :
complete could not be fit on page. It will not be plotted.
Hashtag Worldclouds
Finally, we can create another wordcloud that only contains the hashtags. We can see that hashtags are not very common, but they are different between the two corpora. We will have to decide if we want to keep or remove them in the next section.
clashroyale_tweets$hashtags <- as.character(clashroyale_tweets$hashtags)
clashroyale_tweets$hashtags <- gsub("c\\(", "", clashroyale_tweets$hashtags)
wordcloud(clashroyale_tweets$hashtags, min.freq = 1, scale = c(3.5, .5), max.words = 50, random.order = FALSE, rot.per = 0.35, colors = brewer.pal(8, "Dark2"))
Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation) :
transformation drops documents
Warning in tm_map.SimpleCorpus(corpus, function(x) tm::removeWords(x, tm::stopwords())) :
transformation drops documents
clashofclans_tweets$hashtags <- as.character(clashofclans_tweets$hashtags)
clashofclans_tweets$hashtags <- gsub("c\\(", "", clashofclans_tweets$hashtags)
wordcloud(clashofclans_tweets$hashtags, min.freq = 1, scale = c(3.5, .5), max.words = 50, random.order = FALSE, rot.per = 0.35, colors = brewer.pal(8, "Dark2"))
Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation) :
transformation drops documents
Warning in tm_map.SimpleCorpus(corpus, function(x) tm::removeWords(x, tm::stopwords())) :
transformation drops documents
Preprocessing
As we have said before, some preprocessing is needed so that we get better results when classifying the documents. First, we will apply some transformations such as removing stop words to the text. Then, we will remove sparse words and outlier documents from the corpus. Finally, we will display the final wordclouds so that we can compare them with the initial ones.
Apply Transformations
Transformations operators to the corpus are applied via tm_map
function, which applies (maps) a function to all elements of the corpus. The transformations will be applied to the whole corpus, that constains documents of both classes. Apart from the transformations that are available in the tm
package, some custom transformations are also applied with the function content_transformer
.
First, some elements are removed from the corpus: numbers, punctuation, urls, mentions, hashtags, newlines and emojis. Then, all the words are converted to lowercase. Next, the previously mentioned english stopwords are removed. After, multiple whitespace characters are collapsed to a single one. Finally, all the words are stemmed to reduce the number of words. We can print the first 5 tweets of each corpus to see the difference with the initial ones.
remove_urls <- function(text) {
gsub("http\\S*", "", text)
}
remove_mentions <- function(text) {
gsub("@\\S*", "", text)
}
remove_hashtags <- function(text) {
gsub("#\\S*", "", text)
}
remove_newlines <- function(text) {
gsub("\\\n", " ", text)
}
remove_emojis <- function(text) {
gsub("[^\x01-\x7F]", "", text)
}
# remove numbers
corpus_trans <- tm_map(corpus, removeNumbers)
# remove punctuation
corpus_trans <- tm_map(corpus_trans, removePunctuation)
# remove urls
corpus_trans <- tm_map(corpus_trans, content_transformer(remove_urls))
# remove mentions
corpus_trans <- tm_map(corpus_trans, content_transformer(remove_mentions))
# remove hastags
corpus_trans <- tm_map(corpus_trans, content_transformer(remove_hashtags))
# remove newlines
corpus_trans <- tm_map(corpus_trans, content_transformer(remove_newlines))
# remove emojis
corpus_trans <- tm_map(corpus_trans, content_transformer(remove_emojis))
# convert to lowercase
corpus_trans <- tm_map(corpus_trans, content_transformer(tolower))
# remove english stop words
corpus_trans <- tm_map(corpus_trans, removeWords, stopwords("english"))
# strip whitespace
corpus_trans <- tm_map(corpus_trans, stripWhitespace)
# to access Porter's word stemming algorithm
library(SnowballC)
corpus_trans <- tm_map(corpus_trans, stemDocument)
for (i in 1:5) {
print(corpus_trans[[i]]$content)
}
[1] "tri new deck yet v showdown perfect parti mode season long"
[1] "welcom year tiger"
[1] "thank amaz year here"
[1] "happi new year may full victori crown"
[1] ""
for (i in (n + 1):(n + 6)) {
print(corpus_trans[[i]]$content)
}
[1] "caption face clan mate make"
[1] "new year new villag layout check clashchamp use advanc search tool find top layout narrow town hall level base type sort download recent highest rate"
[1] "let gooooooooo"
[1] "predict"
[1] "clashofclan world championship brought us mani incred matchesbut also surpris turn around loss one world champion match didnt happen wish couldv seen comment"
[1] "first season challeng enjoy amaz perk reward complet multipl challeng unlock shadow queen skin month gold pass first hero skin shadow set"
Remove Sparse Terms
After corpus set transformation, a common approach in text mining is to create a document-term matrix from a corpus. This document-term matrix is the starting point to apply machine-learning modelization techniques such as classification and clustering. Different operations can be applied over this matrix. We can obtain the terms that occur at least 50 times. We can also consult the terms that associate with at least by a 0.3 correlation degree with the term “mainten”. We can see that the correlated words make sense: “short maintencance break soon”, “server upkeep”.
corpus_dtm <- DocumentTermMatrix(corpus_trans)
corpus_dtm
<<DocumentTermMatrix (documents: 2000, terms: 3073)>>
Non-/sparse entries: 17495/6128505
Sparsity : 100%
Maximal term length: 22
Weighting : term frequency (tf)
findFreqTerms(corpus_dtm, 50)
[1] "amp" "attack" "back" "battl"
[5] "best" "break" "can" "card"
[9] "challeng" "champion" "chang" "check"
[13] "chief" "clan" "clash" "clashesport"
[17] "clashworld" "come" "complet" "day"
[21] "esportsroyaleen" "final" "first" "fix"
[25] "game" "get" "happi" "hey"
[29] "king" "know" "let" "live"
[33] "look" "mainten" "make" "month"
[37] "new" "next" "now" "one"
[41] "play" "player" "reward" "royal"
[45] "season" "see" "server" "soon"
[49] "start" "super" "supercel" "take"
[53] "team" "thank" "time" "today"
[57] "troop" "tune" "unlock" "updat"
[61] "war" "well" "will" "win"
[65] "world" "year"
findAssocs(corpus_dtm, term = "mainten", corlimit = 0.3)
$mainten
break server upkeep well soon minut short hey
0.63 0.53 0.48 0.48 0.39 0.38 0.32 0.31
We have removed nearly 4000 words from the initiaal document-term matrix. However, it has still a huge degree of sparsity: a low amount of non-zero elements. Thus, one of the most important operations is to remove sparse terms, terms occurring in very few documents. The sparse
parameter in the removeSparseTerms
function refers to the maximum sparseness allowed: the smaller its proportion, fewer terms will be retained. A trial and error approach will finally return a proper number of terms. This matrix will be the starting point for building further machine learning models.
After trying multiple values, we decide to keep terms with a maximum sparseness of 0.99
. This seems to be very high, but it reduces the numbers of terms drastically. In fact, selecting lower values of sparseness the number of terms is too low.
corpus_dtm_95 <- removeSparseTerms(corpus_dtm, sparse = 0.95)
corpus_dtm_95
<<DocumentTermMatrix (documents: 2000, terms: 14)>>
Non-/sparse entries: 1860/26140
Sparsity : 93%
Maximal term length: 7
Weighting : term frequency (tf)
barplot(as.matrix(corpus_dtm_95),
xlab = "terms", ylab = "number of occurrences",
main = "Most frequent terms (sparseness=0.95)"
)
corpus_dtm_97 <- removeSparseTerms(corpus_dtm, sparse = 0.97)
corpus_dtm_97
<<DocumentTermMatrix (documents: 2000, terms: 42)>>
Non-/sparse entries: 3976/80024
Sparsity : 95%
Maximal term length: 11
Weighting : term frequency (tf)
barplot(as.matrix(corpus_dtm_97),
xlab = "terms", ylab = "number of occurrences",
main = "Most frequent terms (sparseness=0.97)"
)
corpus_dtm_99 <- removeSparseTerms(corpus_dtm, sparse = 0.99)
corpus_dtm_99
<<DocumentTermMatrix (documents: 2000, terms: 181)>>
Non-/sparse entries: 8723/353277
Sparsity : 98%
Maximal term length: 15
Weighting : term frequency (tf)
terms <- dim(corpus_dtm_99)[2]
barplot(as.matrix(corpus_dtm_99),
xlab = "terms", ylab = "number of occurrences",
main = "Most frequent terms (sparseness=0.99)"
)
Outlier Detection
Outlier detection can be used to detect and remove outlier documents from the corpus. We test the Isolation Forest method. I decided not to remove any document to simplify the next steps.
Isolation Forest constructs a tree per document. It tries to isolate the sample from the rest. As outliers are easy to isolate, their isolation score is high. We have to plot the outlierness and decide a threshold.
library(solitude)
Registered S3 method overwritten by 'data.table':
method from
print.data.table
# Empty tree structure
iso <- isolationForest$new()
# convert dtm to dataframe
corpus_df_99 <- as.data.frame(as.matrix(corpus_dtm_99))
# Learn the IsolationForest for our data
iso$fit(corpus_df_99)
INFO [18:05:20.026] dataset has duplicated rows
INFO [18:05:20.101] Building Isolation Forest ...
INFO [18:05:21.757] done
INFO [18:05:21.780] Computing depth of terminal nodes ...
INFO [18:05:22.867] done
INFO [18:05:22.977] Completed growing isolation forest
# predict for our data
p <- iso$predict(corpus_df_99)
# plot anomaly score
plot(density(p$anomaly_score), main = "Anomaly Score Density")
# Based on the plot, decide the cut-off point
which(p$anomaly_score > 0.62)
[1] 200 210 456 1006 1008 1104 1593 1827 1846 1962
Final Worldclouds
Finally, the wordclouds of the reduced document-term matrix are plotted. We can see the difference with the initial wordcloud. The terms of each wordcloud are significantly different
# calculate the frequency of words and sort in descending order.
word_freqs <- sort(colSums(as.matrix(corpus_dtm_99)[1:n, ]), decreasing = TRUE)
wordcloud(words = names(word_freqs), freq = word_freqs, max.words = 50, scale = c(3.5, 0.25), random.order = FALSE, colors = brewer.pal(8, "Dark2"))
word_freqs <- sort(colSums(as.matrix(corpus_dtm_99)[(n + 1):(n + n), ]), decreasing = TRUE)
wordcloud(words = names(word_freqs), freq = word_freqs, max.words = 50, scale = c(3.5, 0.25), random.order = FALSE, colors = brewer.pal(8, "Dark2"))
Clustering
Clustering Words
We try to find clusters of words with hierarchical clustering, a popular clustering techniques which builds a dendogram to iteratively group pairs of similar objects. To do so, a matrix with the sparse terms removed is needed. We select the 0.97 sparsity matrix so that we can visualize them. After the application of the matrix-casting operator, number of occurrences are scaled.
We need to calculate the distance between pairs of terms. The dist
operator performs this calculation between pairs of rows of the provided matrix. As terms appear in the columns of the document-term matrix (corpus_dtm_97
), it needs to be transposed by means of the t
operator. The clustering-dendogram is built with the hclust
operator. It needs as input the calculated distance matrix between pairs of terms and a criteria to decide which pair of clusters to be consecutively joined in the bottom-up dendogram. In this case, the “complete” criteria takes into account the maximum distance between any pair of terms of both clusters to be merged. Heigth in the dendogram denotes the distance between a merged pair of clusters.
dist_matrix <- dist(t(scale(as.matrix(corpus_dtm_97))))
term_clustering <- hclust(dist_matrix, method = "complete")
plot(term_clustering)
Clustering Documents
Another type of popular task is to construct clusters of similar documents based on the frequencies of word occurrences. Here we select a small subset of the initial corpus, 15 documents from each class. We then apply a similar method to the previous one and try to divide documents into two clusters.
dist_matrix <- dist(scale(as.matrix(corpus_dtm_99)[(n - 15):(n + 15), ]))
groups <- hclust(dist_matrix, method = "ward.D")
plot(groups, cex = 0.9, hang = -1)
rect.hclust(groups, k = 2)
Data Splitting
Before learning a classification model we have to define the subsets of samples (documents) to train and test our model. We first need create a Data Frame from the Document Term Matrix.
Create Data Frame
The 0.99 sparseness value document-term matrix is our starting point. This matrix has 181 features, which correspond to the mos frequent terms. We first need to append the class vector as the last column of the matrix. There are 1000 documents of each class, 2000 documents in total.
dim(corpus_dtm_99)
[1] 2000 181
type <- c(rep("clashroyale", n), rep("clashofclans", n)) # create the type vector
corpus_dtm_99 <- cbind(corpus_dtm_99, type) # append
dim(corpus_dtm_99) # consult the updated number of columns
[1] 2000 182
This new matrix is the starting point for supervised classification. However, we first need to convert it to a dataframe. The name of the last column is updated. All the values are converted to numeric and the last column is converted to factor.
corpus_df_99 <- as.data.frame(as.matrix(corpus_dtm_99))
colnames(corpus_df_99)[terms + 1] <- "type"
corpus_df_99$type <- as.factor(corpus_df_99$type)
corpus_df_99 <- as.data.frame(sapply(corpus_df_99, as.numeric))
corpus_df_99[is.na(corpus_df_99)] <- 0
corpus_df_99$type <- as.factor(corpus_df_99$type)
levels(corpus_df_99$type) <- c("clashofclans", "clashroyale")
Create Data Partition
The createDataPartition
produces a train-test partition of our corpus. This will be maintained during the whole pipeline of analysis. Test samples won’t be used for any modeling decision. We will only use them at the end to predict their class and create a confusion matrix. A list of randomly sampled numbers (in_train
) is used to partition the whole corpus. 75% of the samples are used for training and the remaining 25% is used for testing.
library(caret)
Loading required package: lattice
set.seed(107) # a random seed to enable reproducibility
in_train <- createDataPartition(y = corpus_df_99$type, p = .75, list = FALSE)
str(in_train)
int [1:1500, 1] 1 2 3 4 5 6 7 8 9 11 ...
- attr(*, "dimnames")=List of 2
..$ : NULL
..$ : chr "Resample1"
training <- corpus_df_99[in_train, ]
testing <- corpus_df_99[-in_train, ]
nrow(training)
[1] 1500
Similarly, createResample
can be used to make simple bootstrap samples. This creates resamples of the size of the corpus with repeated documents. createFolds
can be used to generate balanced cross-validation groupings from a set of data.
resamples <- createResample(y = corpus_df_99$type)
str(resamples)
List of 10
$ Resample01: int [1:2000] 1 1 3 3 4 4 4 5 5 5 ...
$ Resample02: int [1:2000] 1 1 2 2 2 4 4 4 6 7 ...
$ Resample03: int [1:2000] 1 1 1 1 2 2 5 5 6 6 ...
$ Resample04: int [1:2000] 1 1 3 3 4 5 5 10 14 16 ...
$ Resample05: int [1:2000] 1 5 5 6 6 7 7 9 10 10 ...
$ Resample06: int [1:2000] 1 1 3 3 3 3 4 4 5 6 ...
$ Resample07: int [1:2000] 3 3 4 4 5 7 11 11 12 13 ...
$ Resample08: int [1:2000] 2 3 5 5 6 7 8 9 10 12 ...
$ Resample09: int [1:2000] 2 2 4 4 5 6 6 7 7 8 ...
$ Resample10: int [1:2000] 1 1 2 2 2 3 4 5 5 9 ...
folds <- createFolds(y = corpus_df_99$type)
str(folds)
List of 10
$ Fold01: int [1:200] 5 7 10 17 26 32 34 40 46 50 ...
$ Fold02: int [1:200] 20 38 64 69 81 83 94 112 134 139 ...
$ Fold03: int [1:200] 6 18 29 30 35 42 55 63 67 74 ...
$ Fold04: int [1:200] 3 19 31 36 43 58 61 65 66 85 ...
$ Fold05: int [1:200] 9 37 49 52 62 68 72 75 87 88 ...
$ Fold06: int [1:200] 1 4 8 21 22 54 57 96 105 107 ...
$ Fold07: int [1:200] 2 14 41 48 59 76 84 98 103 117 ...
$ Fold08: int [1:200] 13 15 25 44 51 71 82 92 93 127 ...
$ Fold09: int [1:200] 11 12 27 33 39 60 95 99 126 202 ...
$ Fold10: int [1:200] 16 23 24 28 45 47 56 70 73 78 ...
Classification
The caret
[4, 5] package is the reference tool for building supervised classification and regression models in R. It covers all the steps of a classic pipeline: data preprocessing, model building, accuracy estimation, prediction of the type of new samples, and statistical comparison between the performance of different models. This cheatsheet of caret illustrates its main function in a single page: https://github.com/CABAH/learningRresources/blob/main/cheatsheets/caret.pdf.
Our objective is to learn a classifier that predicts the type of future documents based on terms occurrences. We have a two-class supervised classification problem.
We now can start training and testing different supervised classification models. The train
function implements the building process.
form
parameter is used with the expression type ~ .
to denote the variable to be predicted, followed by the set of predictors. A point indicates that the rest of variables are used as predictors. data
parameter is used for the training data.
method
parameter fixes the type of classification algorithm to be learned. caret
supports more than 150 supervised classification and regression algorithms. Taking into account the large dimensionality of classic NLP datasets, we have to use classifiers capable to deal with this. In this work we choose Linear Discriminant Analysis (LDA) and Boosted Logistic Regression (LR).
metric
parameter fixes the score to assess-validates the goodness of each model. A large set of metrics is offered and we test the following ones: Accuracy, Kappa, ROC, Sensitivity and Specificity.
trControl
parameter defines the method to estimate the error of the classifier. The trainControl
function allows the use of different performance estimation procedures such as k-fold cross-validation, bootstrapping, etc. We apply a 10-fold cross-validation, repeated 3 times. This is an adequate option because it creates 30 results that can later be used to compare algorithms statistically.
Linear Discriminant Analysis
LDA is used to find a linear combination of features that characterizes or separates two or more classes. The resulting combination can be used as a linear classifier, or for dimensionality reduction. This time we will use it as a classifier. We will see a similar unsupervised method called Principal Component Analysis (PCA) for dimensionality reduction in the Feature Extraction section.
Accuracy and Kappa are the default metrics used to evaluate algorithms on binary and multi-class classification datasets in caret. As we have to do binary classification, these metrics are adequate. Our classes are completely balanced, and that makes analysing the metrics easier.
Accuracy is the percentage of correctly classifies instances out of all instances. It is more useful on a binary classification than multi-class classification problems because it can be less clear exactly how the accuracy breaks down across those classes. This could be seen with a confusion matrix.
Kappa is similar to accuracy, but it is normalized at the baseline of random chance on our dataset. It is a more useful measure to use on problems that have an imbalance in the classes. For example, in a 70-30 split for classes 0 and 1 and you can achieve 70% accuracy by predicting all instances are for class 0. As our classes are completely balanced, 50% accuracy is obtained by predicting any of the classes for all instances.
The obtained accuracy is not very good, but this is expected because the problem is not an easy one. The kappa metric also reflects that our classifier is quite bad.
# fixing the performance estimation procedure
train_ctrl <- trainControl(method = "repeatedcv", repeats = 3)
lda_3x10cv <- train(type ~ ., data = training, method = "lda", trControl = train_ctrl)
lda_3x10cv
Linear Discriminant Analysis
1500 samples
181 predictor
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 1350, 1350, 1350, 1350, 1350, 1350, ...
Resampling results:
Accuracy Kappa
0.7924444 0.5848889
Another metric that is only suitable for binary classification problems is ROC. The area under the ROC curve represents a models ability to discriminate between positive and negative classes. An area of 1.0 represents a model that made all predicts perfectly. An area of 0.5 represents a model as good as random.
ROC can be broken down into sensitivity and specificity. A binary classification problem is really a trade-off between sensitivity and specificity. Sensitivity is the true positive rate also called the recall. It is the number instances from the positive (first) class that actually predicted correctly. Specificity is also called the true negative rate. Is the number of instances from the negative (second) class that were actually predicted correctly.
To use this metric we have to select it in the function parameters. Moreover, extra parameters must be added to the trainControl
function. In binary classification problems the twoClassSummary
option displays area under the ROC curve, sensitity and specificity metrics. To do so, activating the classProbs
option is also needed, which saves the class probabilities that the classifier assigns to each sample.
Looking at these numbers, we realise that the second class is predicted correctly more times than the first one. The first class is predicted correctly 67% of the times and the second one 90% of the times. This will also be evident if we calculate a confusion matrix when testing the model.
library(pROC)
Type 'citation("pROC")' for a citation.
Attaching package: ‘pROC’
The following objects are masked from ‘package:stats’:
cov, smooth, var
train_ctrl <- trainControl(method = "repeatedcv",repeats=3, classProbs=TRUE, summaryFunction=twoClassSummary)
lda_roc_3x10cv <- train(type ~ ., data = training, method = "lda", metric="ROC", trControl = train_ctrl)
lda_roc_3x10cv
Linear Discriminant Analysis
1500 samples
181 predictor
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 1350, 1350, 1350, 1350, 1350, 1350, ...
Resampling results:
ROC Sens Spec
0.8668474 0.6786667 0.9057778
Boosted Logistic Regression
Logistic Regression is used to model the probability of a certain class. It uses a linear combination of independent variables, and applies the logistic function at the end to obtain probabilities. If we define a cut-off probability, it can be used as a binary or multi-class classification model. Boosted LR is an additive logistic regression model. It uses and ensemble of similar LR models to make predictions.
While the linear LDA classifier does not have parameters, LR has the nIter
key parameter. This parameter indicates the number of iterations of the Logistic Regression model. By default, without changing the value of the parameter, caret
evaluates 3 models. The tuneLength
option of the train
function fixes the number of values of each parameter to be checked. For example, if the classifier has 2 parameters and the tuneLength
parameter is not changed, 3 x 3 = 9 models are evaluated.
train_ctrl <- trainControl(
method = "repeatedcv", repeats = 3
)
lr_3x10cv <- train(type ~ .,
data = training, method = "LogitBoost", trControl = train_ctrl
)
lr_3x10cv
Boosted Logistic Regression
1500 samples
181 predictor
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 1350, 1350, 1350, 1350, 1350, 1350, ...
Resampling results across tuning parameters:
nIter Accuracy Kappa
11 0.7051111 0.4102222
21 0.7548889 0.5097778
31 0.7704444 0.5408889
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was nIter = 31.
plot(lr_3x10cv)
If we increase the tuneLength
to 15
we can evaluate more models, and check if the accuracy increases. We can see that the accuracy improves up to some point and then it is nearly constant. Therefore, it is not worth to increase the value of nIter
train_ctrl <- trainControl(
method = "repeatedcv", repeats = 3
)
lr_tunel_3x10cv <- train(type ~ .,
data = training, method = "LogitBoost", trControl = train_ctrl, tuneLength = 15
)
lr_tunel_3x10cv
Boosted Logistic Regression
1500 samples
181 predictor
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 1350, 1350, 1350, 1350, 1350, 1350, ...
Resampling results across tuning parameters:
nIter Accuracy Kappa
11 0.7055556 0.4111111
21 0.7553333 0.5106667
31 0.7702222 0.5404444
41 0.7788889 0.5577778
51 0.7775556 0.5551111
61 0.7828889 0.5657778
71 0.7835556 0.5671111
81 0.7837778 0.5675556
91 0.7846667 0.5693333
101 0.7862222 0.5724444
111 0.7862222 0.5724444
121 0.7877778 0.5755556
131 0.7886667 0.5773333
141 0.7888889 0.5777778
151 0.7882222 0.5764444
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was nIter = 141.
plot(lr_tunel_3x10cv)
We can also try the ROC metric to have more information about the performance of our model. We get similar results to the LDA classifier, with a much higher Specificity than Sensitivity.
train_ctrl <- trainControl(method = "repeatedcv",repeats=3, classProbs=TRUE, summaryFunction=twoClassSummary)
lr_roc_3x10cv <- train(type ~ ., data=training, method="LogitBoost", trControl=train_ctrl, metric="ROC", tuneLength=15)
lr_roc_3x10cv
Boosted Logistic Regression
1500 samples
181 predictor
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 1350, 1350, 1350, 1350, 1350, 1350, ...
Resampling results across tuning parameters:
nIter ROC Sens Spec
11 0.7524148 0.4217778 0.9906667
21 0.8164059 0.5240000 0.9804444
31 0.8346933 0.5768889 0.9746667
41 0.8385096 0.6102222 0.9528889
51 0.8425156 0.6146667 0.9533333
61 0.8396770 0.6177778 0.9448889
71 0.8464533 0.6311111 0.9440000
81 0.8493867 0.6328889 0.9466667
91 0.8508533 0.6337778 0.9448889
101 0.8531674 0.6386667 0.9440000
111 0.8557422 0.6400000 0.9453333
121 0.8568889 0.6466667 0.9431111
131 0.8563644 0.6466667 0.9417778
141 0.8577393 0.6448889 0.9466667
151 0.8563704 0.6457778 0.9426667
ROC was used to select the optimal model using the largest value.
The final value used for the model was nIter = 141.
plot(lr_roc_3x10cv)
The tuneGrid
option offers the possibility to select among a set of values to be tuned-tested.
train_ctrl <- trainControl(
method = "repeatedcv", repeats = 3
)
tune_grid <- expand.grid(
nIter = seq(100, 120, 2)
)
lr_tuneg_3x10cv <- train(type ~ .,
data = training, method = "LogitBoost", trControl = train_ctrl, tuneGrid = tune_grid
)
lr_tuneg_3x10cv
Boosted Logistic Regression
1500 samples
181 predictor
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 1350, 1350, 1350, 1350, 1350, 1350, ...
Resampling results across tuning parameters:
nIter Accuracy Kappa
100 0.9099078 0.8102130
102 0.9114335 0.8140331
104 0.9112186 0.8128037
106 0.9074133 0.8050636
108 0.9062959 0.8035127
110 0.9089574 0.8082699
112 0.9093845 0.8090195
114 0.9090713 0.8091313
116 0.9087313 0.8075768
118 0.9083232 0.8067941
120 0.9075655 0.8061344
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was nIter = 102.
plot(lr_tuneg_3x10cv)
Subsampling
Our initial corpus is completely balanced, it has 1000 samples of each class. However, we can create an unbalanced corpus by removing some samples. For example, we can create a corpus that has 1000 samples of one class and 250 from the other class. If class-label distributions are unbalanced in our corpus, a resampling method will try to improve the recovery rate in the minority class.
This test will only be performed with the LR classifier. First, a normal classifier will be trained. Then multiple resampling methods will be tested and compared with the base classifier. ROC is an adequeate metric in this case because we can compare the sensitivity and specificity for each subsampling method.
We expect to have very high specificity but low sensitivity. Therefore, our aim is to increase sensistivity. Downsampling and upsampling improve the sensitivity a bit and the hybrid method gets worse results.
corpus_df_99_un = corpus_df_99[1:(n+n/4), ]
in_train_un <- createDataPartition(y = corpus_df_99_un$type, p = .75, list = FALSE)
str(in_train_un)
training_un <- corpus_df_99[in_train_un, ]
testing_un <- corpus_df_99[-in_train_un, ]
train_ctrl <- trainControl(method = "repeatedcv", repeats = 3, classProbs=TRUE, summaryFunction=twoClassSummary)
lda_un_3x10cv <- train(type ~ ., data = training_un, method = "LogitBoost", metric="ROC", trControl = train_ctrl)
lda_un_3x10cv
Boosted Logistic Regression
938 samples
181 predictors
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 844, 844, 844, 845, 844, 844, ...
Resampling results across tuning parameters:
nIter ROC Sens Spec
11 0.7196043 0.3411306 0.9835556
21 0.7734756 0.4455166 0.9728889
31 0.7980253 0.5037037 0.9644444
ROC was used to select the optimal model using the largest value.
The final value used for the model was nIter = 31.
Downsampling
Downsampling randomly subsets all the classes in the training set so that their class frequencies match the least prevalent class. For example, suppose that 80% of the training set samples are the first class and the remaining 20% are in the second class. Down-sampling would randomly sample the first class to be the same size as the second class (so that only 40% of the total training set is used to fit the model).
train_ctrl <- trainControl(method = "repeatedcv", repeats = 3, classProbs=TRUE, summaryFunction=twoClassSummary, sampling="down")
lda_down_3x10cv <- train(type ~ ., data = training_un, method = "LogitBoost", metric="ROC", trControl = train_ctrl)
lda_down_3x10cv
Boosted Logistic Regression
938 samples
181 predictors
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 844, 844, 844, 845, 844, 844, ...
Addtional sampling using down-sampling
Resampling results across tuning parameters:
nIter ROC Sens Spec
11 0.7018311 0.3384016 0.9648889
21 0.7510702 0.4434698 0.9480000
31 0.7826244 0.5300195 0.9293333
ROC was used to select the optimal model using the largest value.
The final value used for the model was nIter = 31.
Upsampling
Upsampling randomly samples the minority class to be the same size as the majority class.
train_ctrl <- trainControl(method = "repeatedcv", repeats = 3, classProbs=TRUE, summaryFunction=twoClassSummary, sampling="up")
lda_up_3x10cv <- train(type ~ ., data = training_un, method = "LogitBoost", metric="ROC", trControl = train_ctrl)
lda_up_3x10cv
Boosted Logistic Regression
938 samples
181 predictors
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 844, 844, 845, 844, 844, 845, ...
Addtional sampling using up-sampling
Resampling results across tuning parameters:
nIter ROC Sens Spec
11 0.7280273 0.3691033 0.9764444
21 0.7836797 0.4647173 0.9648889
31 0.7983190 0.5143275 0.9520000
ROC was used to select the optimal model using the largest value.
The final value used for the model was nIter = 31.
Hybrid
An hybrid method downsamples the majority class and synthesizes new data points in the minority class.
train_ctrl <- trainControl(method = "repeatedcv", repeats = 3, classProbs=TRUE, summaryFunction=twoClassSummary, sampling="smote")
lda_smote_3x10cv <- train(type ~ ., data = training_un, method = "LogitBoost", metric="ROC", trControl = train_ctrl)
lda_smote_3x10cv
Boosted Logistic Regression
938 samples
181 predictors
2 classes: 'clashofclans', 'clashroyale'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 844, 844, 844, 844, 845, 844, ...
Addtional sampling using SMOTE
Resampling results across tuning parameters:
nIter ROC Sens Spec
11 0.6861157 0.2978558 0.9764444
21 0.7571027 0.4308967 0.9648889
31 0.7837505 0.4765107 0.9631111
ROC was used to select the optimal model using the largest value.
The final value used for the model was nIter = 31.
Feature Selection
Most approaches for reducing the number of features can be placed into two main categories: wrappers and filters.
Wrapper methods evaluate multiple models using procedures that add and/or remove predictors to find the optimal combination that maximizes model performance. In essence, wrapper methods are search algorithms that treat the predictors as the inputs and utilize model performance as the output to be optimized.
Filter methods evaluate the relevance of the predictors outside of the predictive models and subsequently model only the predictors that pass some criterion. Each predictor is ecaluated individually to check if there is a plausible relationship between it and the observed classes. Only predictors with important relationships would then be included in a classification model.
The functions are applied to the entire training set and also to different resampled versions of the data set. From this, generalizable estimates of performance can be computed that properly take into account the feature selection step.
In our case we will test Univariate Filter and 2 wrapper methods: Recursive Feature Elimination and Simulated Annealing. We will apply these methods to both classifiers and we will compare the results at the end.
Univariate Filter
Predictors can be filtered by conducting some sort of sample test to see if the mean of the predictor is different between the classes. Predictors that have statistically significant differences between the classes are then used for modeling.
On average, less than 80 variables are selected and the accuracy of the classifiers is improved. Therefore, this method is a great option in this case.
sbf_ctrl <- sbfControl(functions = rfSBF, method = "repeatedcv", repeats = 3)
train_ctrl <- trainControl(method = "repeatedcv", repeats = 3, classProbs = TRUE)
lr_sbf_3x10cv <- sbf(type ~ ., data = training, method = "LogitBoost", trControl = train_ctrl, sbfControl = sbf_ctrl)
lr_sbf_3x10cv
Selection By Filter
Outer resampling method: Cross-Validated (10 fold, repeated 3 times)
Resampling performance:
Using the training set, 81 variables were selected:
action, art, attack, avail, back...
During resampling, the top 5 selected variables (out of a possible 106):
action (100%), attack (100%), best (100%), bit (100%), can (100%)
On average, 78.8 variables were selected (min = 74, max = 82)
lda_sbf_3x10cv <- sbf(type ~ ., data = training, method = "lda", trControl = train_ctrl, sbfControl = sbf_ctrl)
lda_sbf_3x10cv
Selection By Filter
Outer resampling method: Cross-Validated (10 fold, repeated 3 times)
Resampling performance:
Using the training set, 81 variables were selected:
action, art, attack, avail, back...
During resampling, the top 5 selected variables (out of a possible 104):
action (100%), attack (100%), back (100%), best (100%), bit (100%)
On average, 79.1 variables were selected (min = 74, max = 82)
Recursive Feature Elimination
First, the algorithm fits the model to all predictors. Each predictor is ranked using it’s importance to the model. At each iteration of feature selection, the top ranked predictors are retained, the model is refit and performance is assessed. The number of predictors with the best performance is determined and the top predictors are used to fit the final model. In this case 4, 8, 16 and 181 predictors are tested.
The accuracy of the classifiers is improved. Therefore, this method is also a great option in this case.
lr_rfe_3x10cv
Recursive feature selection
Outer resampling method: Cross-Validated (10 fold, repeated 3 times)
Resampling performance over subset size:
The top 5 variables (out of 181):
devourlick, chief, redditclash, clashworld, clashquest
lda_rfe_3x10cv
Recursive feature selection
Outer resampling method: Cross-Validated (10 fold, repeated 3 times)
Resampling performance over subset size:
The top 5 variables (out of 181):
devourlick, chief, redditclash, clashworld, clashquest
Simulated Annealing
Simulated annealing is a global search method that makes small perturbations to an initial candidate solution. If the performance value for the perturbed value is better than the previous solution, the new solution is accepted. If not, an acceptance probability is determined based on the difference between the two performance values and the current iteration of the search. In the context of feature selection, a solution is a binary vector that describes the current subset. The subset is perturbed by randomly changing a small number of members in the subset.
Using this method the accuracy of the models decreases a lot, so it is not a good option.
lr_safs_3x10cv
Simulated Annealing Feature Selection
1500 samples
181 predictors
2 classes: 'clashofclans', 'clashroyale'
Maximum search iterations: 10
Internal performance values: Accuracy, Kappa
Subset selection driven to maximize internal Accuracy
External performance values: Accuracy, Kappa
Best iteration chose by maximizing external Accuracy
External resampling method: Cross-Validated (10 fold, repeated 3 times)
During resampling:
* the top 5 selected variables (out of a possible 181):
short (46.7%), avail (40%), back (40%), final (40%), tomorrow (40%)
* on average, 40.4 variables were selected (min = 37, max = 46)
In the final search using the entire training set:
* 36 features selected at iteration 10 including:
anoth, art, attack, avail, back ...
* external performance at this iteration is
Accuracy Kappa
0.6236 0.2471
lda_safs_3x10cv
Simulated Annealing Feature Selection
1500 samples
181 predictors
2 classes: 'clashofclans', 'clashroyale'
Maximum search iterations: 10
Internal performance values: Accuracy, Kappa
Subset selection driven to maximize internal Accuracy
External performance values: Accuracy, Kappa
Best iteration chose by maximizing external Accuracy
External resampling method: Cross-Validated (10 fold, repeated 3 times)
During resampling:
* the top 5 selected variables (out of a possible 181):
around (46.7%), royal (43.3%), credit (40%), match (40%), teamquesogg (40%)
* on average, 40.9 variables were selected (min = 37, max = 46)
In the final search using the entire training set:
* 42 features selected at iteration 10 including:
best, bug, card, chanc, chang ...
* external performance at this iteration is
Accuracy Kappa
0.6451 0.2902
Testing
In order to predict the class value of unseen documents of the test partition caret uses the classifier which shows the best accuracy estimation of their parameters. Function predict implements this functionality. Consult its parameters. The type
parameter, by means of its probs
value, outputs the probability of test each sample belonging to each class. On the other hand, the raw
value outputs the class value with the largest probability. By means of the raw
option the confusion matrix can be calculated: this crosses, for each test sample, predicted with real class values.
All the previously learned classifiers are tested on the test partition. There are 10 different classifiers in total, the two main types with the variations of feature selection and extraction. As expected, the accuracy for the testing partition is a bit lower than the train partition. Specificity is higher than Sensitivity in all the cases, which means that our model is better at predicting samples of class 2: clashroyale. This can also be seen in the confusion matrices. The performance of each algorithm will be compared more in detail in the next section.
LDA
lda_pred <- predict(lda_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lda_pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 163 34
clashroyale 87 216
Accuracy : 0.758
95% CI : (0.718, 0.7949)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.516
Mcnemar's Test P-Value : 2.276e-06
Sensitivity : 0.6520
Specificity : 0.8640
Pos Pred Value : 0.8274
Neg Pred Value : 0.7129
Prevalence : 0.5000
Detection Rate : 0.3260
Detection Prevalence : 0.3940
Balanced Accuracy : 0.7580
'Positive' Class : clashofclans
LDA SBF
lda_sbf_pred <- predict(lda_sbf_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lda_sbf_pred$pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 170 25
clashroyale 80 225
Accuracy : 0.79
95% CI : (0.7516, 0.8249)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.58
Mcnemar's Test P-Value : 1.365e-07
Sensitivity : 0.6800
Specificity : 0.9000
Pos Pred Value : 0.8718
Neg Pred Value : 0.7377
Prevalence : 0.5000
Detection Rate : 0.3400
Detection Prevalence : 0.3900
Balanced Accuracy : 0.7900
'Positive' Class : clashofclans
LDA RFE
lda_rfe_pred <- predict(lda_rfe_3x10cv, newdata = testing)
confusionMatrix(data = lda_rfe_pred$pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 178 37
clashroyale 72 213
Accuracy : 0.782
95% CI : (0.7432, 0.8174)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.564
Mcnemar's Test P-Value : 0.001128
Sensitivity : 0.7120
Specificity : 0.8520
Pos Pred Value : 0.8279
Neg Pred Value : 0.7474
Prevalence : 0.5000
Detection Rate : 0.3560
Detection Prevalence : 0.4300
Balanced Accuracy : 0.7820
'Positive' Class : clashofclans
LDA SAFS
lda_safs_pred <- predict(lda_safs_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lda_safs_pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 115 40
clashroyale 135 210
Accuracy : 0.65
95% CI : (0.6064, 0.6918)
No Information Rate : 0.5
P-Value [Acc > NIR] : 9.513e-12
Kappa : 0.3
Mcnemar's Test P-Value : 1.197e-12
Sensitivity : 0.4600
Specificity : 0.8400
Pos Pred Value : 0.7419
Neg Pred Value : 0.6087
Prevalence : 0.5000
Detection Rate : 0.2300
Detection Prevalence : 0.3100
Balanced Accuracy : 0.6500
'Positive' Class : clashofclans
LDA PCA
lda_pca_pred <- predict(lda_pca_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lda_pca_pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 160 26
clashroyale 90 224
Accuracy : 0.768
95% CI : (0.7285, 0.8043)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.536
Mcnemar's Test P-Value : 4.933e-09
Sensitivity : 0.6400
Specificity : 0.8960
Pos Pred Value : 0.8602
Neg Pred Value : 0.7134
Prevalence : 0.5000
Detection Rate : 0.3200
Detection Prevalence : 0.3720
Balanced Accuracy : 0.7680
'Positive' Class : clashofclans
BLR
lr_pred <- predict(lr_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lr_pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 125 10
clashroyale 125 240
Accuracy : 0.73
95% CI : (0.6888, 0.7685)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.46
Mcnemar's Test P-Value : < 2.2e-16
Sensitivity : 0.5000
Specificity : 0.9600
Pos Pred Value : 0.9259
Neg Pred Value : 0.6575
Prevalence : 0.5000
Detection Rate : 0.2500
Detection Prevalence : 0.2700
Balanced Accuracy : 0.7300
'Positive' Class : clashofclans
BLR SBF
lr_sbf_pred <- predict(lr_sbf_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lr_sbf_pred$pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 169 27
clashroyale 81 223
Accuracy : 0.784
95% CI : (0.7453, 0.8193)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.568
Mcnemar's Test P-Value : 3.398e-07
Sensitivity : 0.6760
Specificity : 0.8920
Pos Pred Value : 0.8622
Neg Pred Value : 0.7336
Prevalence : 0.5000
Detection Rate : 0.3380
Detection Prevalence : 0.3920
Balanced Accuracy : 0.7840
'Positive' Class : clashofclans
BLR RFE
lr_rfe_pred <- predict(lr_rfe_3x10cv, newdata = testing)
confusionMatrix(data = lr_rfe_pred$pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 179 37
clashroyale 71 213
Accuracy : 0.784
95% CI : (0.7453, 0.8193)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.568
Mcnemar's Test P-Value : 0.001496
Sensitivity : 0.7160
Specificity : 0.8520
Pos Pred Value : 0.8287
Neg Pred Value : 0.7500
Prevalence : 0.5000
Detection Rate : 0.3580
Detection Prevalence : 0.4320
Balanced Accuracy : 0.7840
'Positive' Class : clashofclans
BLR SAFS
lr_safs_pred <- predict(lr_safs_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lr_safs_pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 79 10
clashroyale 171 240
Accuracy : 0.638
95% CI : (0.5942, 0.6802)
No Information Rate : 0.5
P-Value [Acc > NIR] : 3.52e-10
Kappa : 0.276
Mcnemar's Test P-Value : < 2.2e-16
Sensitivity : 0.3160
Specificity : 0.9600
Pos Pred Value : 0.8876
Neg Pred Value : 0.5839
Prevalence : 0.5000
Detection Rate : 0.1580
Detection Prevalence : 0.1780
Balanced Accuracy : 0.6380
'Positive' Class : clashofclans
BLR PCA
lr_pca_pred <- predict(lr_pca_3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = lr_pca_pred, testing$type)
Confusion Matrix and Statistics
Reference
Prediction clashofclans clashroyale
clashofclans 154 62
clashroyale 96 188
Accuracy : 0.684
95% CI : (0.6412, 0.7246)
No Information Rate : 0.5
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.368
Mcnemar's Test P-Value : 0.008656
Sensitivity : 0.616
Specificity : 0.752
Pos Pred Value : 0.713
Neg Pred Value : 0.662
Prevalence : 0.500
Detection Rate : 0.308
Detection Prevalence : 0.432
Balanced Accuracy : 0.684
'Positive' Class : clashofclans
Comparison
As a final step, we will compare the 10 models that we have trained. First, we will compare the results in a table. Then, we will create some plots to compare performance of the algorithms visually. Finally, we will perform a statistical significance test to know if there is a significant difference between pairs of classifiers.
Summary Tables
This is the easiest comparison that we can do, simply call the summary
function and pass it the resamples
result. It will create a table with one algorithm for each row and evaluation metrics for each column.
By looking at those values we can have an idea of which classifiers are the best ones. If we look at the base classifiers, LDA is better than LR. However, applying SBF or RFE feature selection improves the results of both classifiers and makes them similar. The other feature selection and extraction methods make the results of both classifiers worse.
resamps <- resamples(list(lr = lr_3x10cv, lr_sbf = lr_sbf_3x10cv, lr_rfe = lr_rfe_3x10cv, lr_safs = lr_safs_3x10cv, lr_pca = lr_pca_3x10cv, lda = lda_3x10cv, lda_sbf = lda_sbf_3x10cv, lda_rfe = lda_rfe_3x10cv, lda_safs = lda_safs_3x10cv, lda_pca = lda_pca_3x10cv))
summary(resamps)
Call:
summary.resamples(object = resamps)
Models: lr, lr_sbf, lr_rfe, lr_safs, lr_pca, lda, lda_sbf, lda_rfe, lda_safs, lda_pca
Number of resamples: 30
Accuracy
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
lr 0.7200000 0.7550000 0.7733333 0.7704444 0.7866667 0.8066667 0
lr_sbf 0.7466667 0.7816667 0.8066667 0.8082222 0.8400000 0.8666667 0
lr_rfe 0.7666667 0.7866667 0.8000000 0.8062222 0.8266667 0.8533333 0
lr_safs 0.5600000 0.5883333 0.6200000 0.6235556 0.6466667 0.7400000 0
lr_pca 0.6066667 0.6733333 0.6866667 0.6855556 0.7066667 0.7466667 0
lda 0.7200000 0.7733333 0.7900000 0.7924444 0.8133333 0.8466667 0
lda_sbf 0.7533333 0.7800000 0.8000000 0.8031111 0.8133333 0.8666667 0
lda_rfe 0.7533333 0.7733333 0.8100000 0.8097778 0.8533333 0.8733333 0
lda_safs 0.5533333 0.6166667 0.6566667 0.6451111 0.6716667 0.6933333 0
lda_pca 0.7133333 0.7683333 0.7833333 0.7831111 0.8050000 0.8400000 0
Kappa
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
lr 0.4400000 0.5100000 0.5466667 0.5408889 0.5733333 0.6133333 0
lr_sbf 0.4933333 0.5633333 0.6133333 0.6164444 0.6800000 0.7333333 0
lr_rfe 0.5333333 0.5733333 0.6000000 0.6124444 0.6533333 0.7066667 0
lr_safs 0.1200000 0.1766667 0.2400000 0.2471111 0.2933333 0.4800000 0
lr_pca 0.2133333 0.3466667 0.3733333 0.3711111 0.4133333 0.4933333 0
lda 0.4400000 0.5466667 0.5800000 0.5848889 0.6266667 0.6933333 0
lda_sbf 0.5066667 0.5600000 0.6000000 0.6062222 0.6266667 0.7333333 0
lda_rfe 0.5066667 0.5466667 0.6200000 0.6195556 0.7066667 0.7466667 0
lda_safs 0.1066667 0.2333333 0.3133333 0.2902222 0.3433333 0.3866667 0
lda_pca 0.4266667 0.5366667 0.5666667 0.5662222 0.6100000 0.6800000 0
Box and Whisker Plots
This is a useful way to look at the spread of the estimated accuracies for different methods and how they relate. Note that the boxes are ordered from highest to lowest mean accuracy. They are useful to look at the mean values (dots) and the boxes (middle 50% of results). We can extract the same conclusions we extracted by looking at the table easier by lookin at this plot.
scales <- list(x=list(relation="free"), y=list(relation="free"))
bwplot(resamps, scales=scales)
Density Plots
We can show the distribution of model accuracy as density plots. This is a useful way to evaluate the overlap in the estimated behavior of algorithms. They are also to look at the differences in the peaks as well as the variance of the distributions.
scales <- list(x=list(relation="free"), y=list(relation="free"))
densityplot(resamps, scales=scales, pch = "|")
Dot Plots
These are useful plots as the show both the mean estimated accuracy as well as the 95% confidence interval. They are useful to compare the means and the overlap of the spreads between algorithms. We can compare algorithms like we did with the boxplot.
scales <- list(x=list(relation="free"), y=list(relation="free"))
dotplot(resamps, scales=scales)
Scatterplot Matrix
This creates a scatterplot matrix of all results for an algorithm compared to the results for all other algorithms. These are useful to compare pairs of algorithms.
splom(resamps)
Pairwise xyPlots
We can zoom in on one pair-wise comparison of the accuracy for two algorithms with an xyplot. For example, we can compare the two main algorithms to see that LDA is better than LR.
xyplot(resamps, what = "BlandAltman", models = c("lr", "lda"))
Another useful comparison is to check the effect of feature selection and extraction. For the Logistic Regression algorithm, Univariate Filters and Recursive Feature Elimination improve the accuracy. However, Simulated Annealing and Principal Component Analysis get worse results.
xyplot(resamps, what = "BlandAltman", models = c("lr", "lr_sbf"))
xyplot(resamps, what = "BlandAltman", models = c("lr", "lr_rfe"))
xyplot(resamps, what = "BlandAltman", models = c("lr", "lr_safs"))
xyplot(resamps, what = "BlandAltman", models = c("lr", "lr_pca"))
Statistical Significance Tests
Note than in our case, due to the 3 repetitions of the 10-fold cross-validation process, there are 30 resampling results for each classifier. The same paired cross-validation subsets of samples were used for all classifiers. We have to use a paired t-test to calculate the significance of the differences between both classifiers.
Using the diff
function over the resamps
object calculates the differences between all pairs of classifiers. The output shows, for each metric (accuracy and kappa), the difference of the mean (positive or negative) between both classifiers. The p-value of the whole t-test is 0, which indicates that there is a significant difference between some classifiers. Therefore, we can discard the null hypothesis that says that there is no difference between classifiers.
The interpretation of the p-value is the key point. It is related with the risk of erroneously discarding the null-hypothesis of similarity between compared classifiers, when there is no real difference. Roughly speaking, it can also be interpreted as the degree of similarity between both classifiers. A p-value smaller than 0.05 alerts about statistically significant differences between both classifiers. That is, when the risk of erroneously discarding the hypothesis of similarity between both classifiers is low, we assume that there is a statistically significant difference between classifiers.
The lower diagonal of the table shows p-values for the null hypothesis. The upper diagonal of the table shows the estimated difference between the distributions. We can see that is come cases the p-value is bigger than 0.05 and therefore we can not discard the null hypothesis. In some other cases, the p-value is smaller than 0.05 so we can surely discard the null hypothesis.
We can see that all the ideas that we had before when comparing classifiers are confirmed with the statistical test. Some classifiers are significantly better than others. The base LDA is better than the base LR, applying SBF and RFE improves the results and applying SAFS and PCA makes results worse.
diffs <- diff(resamps)
summary(diffs)
Call:
summary.diff.resamples(object = diffs)
p-value adjustment: bonferroni
Upper diagonal: estimates of the difference
Lower diagonal: p-value for H0: difference = 0
Accuracy
lr lr_sbf lr_rfe lr_safs lr_pca lda lda_sbf
lr -0.037778 -0.035778 0.146889 0.084889 -0.022000 -0.032667
lr_sbf 0.0056665 0.002000 0.184667 0.122667 0.015778 0.005111
lr_rfe 0.0005784 1.0000000 0.182667 0.120667 0.013778 0.003111
lr_safs 6.967e-15 3.491e-16 < 2.2e-16 -0.062000 -0.168889 -0.179556
lr_pca 1.892e-09 1.372e-11 4.566e-13 0.0001458 -0.106889 -0.117556
lda 0.1057663 1.0000000 1.0000000 3.965e-15 3.230e-15 -0.010667
lda_sbf 0.0034098 1.0000000 1.0000000 6.978e-16 2.278e-15 1.0000000
lda_rfe 0.0088474 1.0000000 1.0000000 2.379e-15 9.486e-14 1.0000000 1.0000000
lda_safs 1.204e-13 2.051e-15 < 2.2e-16 1.0000000 0.0002075 6.769e-15 < 2.2e-16
lda_pca 1.0000000 0.5988760 0.3064779 6.841e-13 3.971e-12 1.0000000 0.1333848
lda_rfe lda_safs lda_pca
lr -0.039333 0.125333 -0.012667
lr_sbf -0.001556 0.163111 0.025111
lr_rfe -0.003556 0.161111 0.023111
lr_safs -0.186222 -0.021556 -0.159556
lr_pca -0.124222 0.040444 -0.097556
lda -0.017333 0.147333 0.009333
lda_sbf -0.006667 0.158000 0.020000
lda_rfe 0.164667 0.026667
lda_safs 3.805e-16 -0.138000
lda_pca 0.3630450 1.123e-12
Kappa
lr lr_sbf lr_rfe lr_safs lr_pca lda lda_sbf
lr -0.075556 -0.071556 0.293778 0.169778 -0.044000 -0.065333
lr_sbf 0.0056665 0.004000 0.369333 0.245333 0.031556 0.010222
lr_rfe 0.0005784 1.0000000 0.365333 0.241333 0.027556 0.006222
lr_safs 6.967e-15 3.491e-16 < 2.2e-16 -0.124000 -0.337778 -0.359111
lr_pca 1.892e-09 1.372e-11 4.566e-13 0.0001458 -0.213778 -0.235111
lda 0.1057663 1.0000000 1.0000000 3.965e-15 3.230e-15 -0.021333
lda_sbf 0.0034098 1.0000000 1.0000000 6.978e-16 2.278e-15 1.0000000
lda_rfe 0.0088474 1.0000000 1.0000000 2.379e-15 9.486e-14 1.0000000 1.0000000
lda_safs 1.204e-13 2.051e-15 < 2.2e-16 1.0000000 0.0002075 6.769e-15 < 2.2e-16
lda_pca 1.0000000 0.5988760 0.3064779 6.841e-13 3.971e-12 1.0000000 0.1333848
lda_rfe lda_safs lda_pca
lr -0.078667 0.250667 -0.025333
lr_sbf -0.003111 0.326222 0.050222
lr_rfe -0.007111 0.322222 0.046222
lr_safs -0.372444 -0.043111 -0.319111
lr_pca -0.248444 0.080889 -0.195111
lda -0.034667 0.294667 0.018667
lda_sbf -0.013333 0.316000 0.040000
lda_rfe 0.329333 0.053333
lda_safs 3.805e-16 -0.276000
lda_pca 0.3630450 1.123e-12
Bibliography
[1] Ingo Feinerer. tm: Text Mining Package, 2012. R package version 0.5-7.1.
[2] Ingo Feinerer, Kurt Hornik, and David Meyer. Text mining infrastructure in R. Journal of Statistical Software, 25(5):1-54, 3 2008.
[3] Ian Fellows. wordcloud: Word Clouds, 2014. R package version 2.5.
[4] M. Kuhn and K. Johnson. Applied Predictive Modeling. Springer, 2013.
[5] Max Kuhn. Contributions from Jed Wing, Steve Weston, Andre Williams, Chris Keefer, Allan Engelhardt, Tony Cooper, Zachary Mayer, and the R Core Team. caret: Classification and Regression Training, 2014. R package version 6.0-35.
LS0tDQp0aXRsZTogJ1ByZXByb2Nlc3NpbmcsIGNsdXN0ZXJpbmcgYW5kIGNsYXNzaWZpY2F0aW9uIG9mIHR3ZWV0cyBpbiBSJw0KYXV0aG9yOiAnSnVsZW4gRXR4YW5peicNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQotLS0NCg0KIyBDb3JwdXMNCg0KVHdpdHRlciBwcm92aWRlcyB1cyB3aXRoIHZhc3QgYW1vdW50cyBvZiB1c2VyLWdlbmVyYXRlZCBsYW5ndWFnZSBkYXRhLCB3aGljaCBpcyBhIGRyZWFtIGZvciBhbnlvbmUgd2FudGluZyB0byBjb25kdWN0IHRleHR1YWwgYW5hbHlzaXMuIFRoZSBgdHdpdHRlUmAgbGlicmFyeSBwcm92aWRlcyBhY2Nlc3MgdG8gVHdpdHRlciBkYXRhLiBUd2l0dGVyIG1hcmtzIGl0cyB1c2UgYXMgdGhlIOKAmG9mZmljaWFs4oCZIHdheSB0byBkb3dubG9hZCBpdHMgdHdlZXRzLiBBbiBhdHRyYWN0aXZlIGFuZCDigJhlYXN5LXRvLXVzZeKAmSBhbHRlcm5hdGl2ZSB0byBUd2l0dGVy4oCZcyDigJhvZmZpY2lhbCBydWxlc+KAmSBpcyBiYXNlZCBvbiB0aGUgdXNlIG9mIHRoZSBgcnR3ZWV0YCBwYWNrYWdlLiBUaGUgW2ZvbGxvd2luZyBsaW5rXShodHRwczovL2dpdGh1Yi5jb20vcm9wZW5zY2kvcnR3ZWV0KSBzZWVtcyB0byBiZSBhIG1vcmUgdXBkYXRlZCBwYWNrYWdlLiBUaGlzIFtzZXQgb2Ygc2xpZGVzXShodHRwczovL21rZWFybmV5LmdpdGh1Yi5pby9uaWNhcl90d29ya3Nob3AvKSBvZmZlcnMgYW4gZWFzeS10by1mb2xsb3cgdHV0b3JpYWwsIHNob3dpbmcgdGhlIHBpcGVsaW5lIHRoYXQgeW91IG5lZWQuDQoNClR3aXR0ZXLigJlzIGxpbmsgdG8gY3JlYXRlIFR3aXR0ZXIgYXBwbGljYXRpb25zIGlzIGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL2FwcHMuIFlvdSBuZWVkIHRvIGJlIGxvZ2dlZCBpbiB0byBUd2l0dGVyIHRvIGNyZWF0ZSBhIG5ldyBhcHAuIFRoaXMgd2lsbCBwcm92aWRlIHlvdSBhIHNldCBvZiA1IGl0ZW1zIHJlbGF0ZWQgdG8gdGhlIGFwcGxpY2F0aW9uIGNhbGxlZCBgYXBwYCwgYGNvbnN1bWVyS2V5YCwgYGNvbnN1bWVyU2VjcmV0YCwgYGFjY2Vzc1Rva2VuYCBhbmQgYGFjY2Vzc1NlY3JldGAuIEJvdGggYGFjY2Vzc1Rva2VuYCBhbmQgYGFjY2Vzc1NlY3JldGAgbmVlZCB0byBiZSBhY3RpdmF0ZWQgYWZ0ZXIgcmVjZWl2aW5nIHRoZSBgY29uc3VtZXJLZXlgIGFuZCBgY29uc3VtZXJTZWNyZXRgLiBGaXZlIHBhcmFtZXRlcnMgbmVlZCB0byBiZSB1c2VkIGluIHRoZSBmaW5hbCBhdXRoZW50aWZpY2F0aW9uIGZ1bmN0aW9uIGNhbGwsIGBjcmVhdGVfdG9rZW4oKWAuDQoNCmBgYA0KdG9rZW4gPC0gY3JlYXRlX3Rva2VuKA0KICAgIGFwcCA9IGFwcCwNCiAgICBjb25zdW1lcl9rZXkgPSBjb25zdW1lcl9rZXksDQogICAgY29uc3VtZXJfc2VjcmV0ID0gY29uc3VtZXJfc2VjcmV0LA0KICAgIGFjY2Vzc190b2tlbiA9IGFjY2Vzc19rZXksDQogICAgYWNjZXNzX3NlY3JldCA9IGFjY2Vzc19zZWNyZXQNCikNCmBgYA0KDQpPbmNlIHRoZSBhdXRoZW50aWZpY2F0aW9uIGlzIGRvbmUsIHR3ZWV0cyBvZiBhbnkgdXNlciBvciBoYXNodGFnIGNhbiBiZSByZXRyaWV2ZWQgYW5kIGNvbnZlcnRlZCB0byBhIGNvcnB1cy4gSW4gdGhpcyBjYXNlLCBJIGhhdmUgZGVjaWRlZCB0byBtYWtlIGEgY29ycHVzIHdpdGggdGhlIHR3ZWV0cyBvZiB0d28gbW9iaWxlIGdhbWUgYWNjb3VudHMuIEFzIHRoZXkgYXJlIHNpbWlsYXIgZ2FtZXMsIHBlcmZvcm1pbmcgY2xhc3NpZmljYXRpb24gb2YgdHdlZXRzIHdpbGwgYmUgYSBjaGFsbGVuZ2luZyB0YXNrLiBPbmx5IHRoZSBsYXN0IDEwMDAgdHdlZXRzIG9mIGVhY2ggYWNjb3VudCBhcmUgcmV0cmlldmVkLg0KDQpUaGVyZWZvcmUsIHdlIGhhdmUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbSwgd2hlcmUgdGhlIGNsYXNzIGlzIGBjbGFzaHJveWFsZWAgb3IgYGNsYXNob2ZjbGFuc2AuIEFzIHdlIGFyZSB3b3JraW5nIHdpdGggdGV4dCwgdGhlIHByZWRpY3RpdmUgZmVhdHVyZXMgdGhhdCB3ZSBoYXZlIGFyZSByZWxhdGVkIHRvIHdvcmRzLg0KDQpgYGB7cn0NCmxpYnJhcnkocnR3ZWV0KQ0KIyByZXRyaWV2ZSB1c2VyIHR3ZWV0cw0KbiA8LSAxMDAwDQpjbGFzaHJveWFsZV90d2VldHMgPC0gZ2V0X3RpbWVsaW5lKCJjbGFzaHJveWFsZSIsIG4gPSBuKQ0KY2xhc2hvZmNsYW5zX3R3ZWV0cyA8LSBnZXRfdGltZWxpbmUoImNsYXNob2ZjbGFucyIsIG4gPSBuKQ0KYGBgDQoNCkluIHRoZSBmaXJzdCA1IHR3ZWV0cyBvZiBlYWNoIGRhdGFzZXQgd2UgY2FuIHNlZSB0aGF0IHRoZSB0d2VldHMgZG9uJ3Qgb25seSBoYXZlIHdvcmRzLiBUaGVyZSBhcmUgYWxzbyBsaW5rcyBhbmQgZW1vdGVzIGZvciBleGFtcGxlLiBJbiB0aGUgbmV4dCBzZWN0aW9uIHdlIHdpbGwgaGF2ZSB0byBkZWNpZGUgd2hhdCB3ZSB3YW50IHRvIGRvIHdpdGggdGhvc2Ugd29yZHMuIEFwYXJ0IGZyb20gdGhlIHRleHQsIG1hbnkgb3RoZXIgZGF0YSBpcyByZXR1cm4gYnkgdGhlIHByZXZpb3VzIGZ1bmN0aW9uLiBJbiB0b3RhbCwgdGhlcmUgYXJlIDkwIGNvbHVtbnMsIGJ1dCB3ZSB3aWxsIG9ubHkgdXNlIGEgZmV3IG9mIHRoZW0uIFRoZSBtb3N0IGltcG9ydGFudCBvbmUgaXMgdGhlIGB0ZXh0YCBjb2x1bW4uIFdlIHdpbGwgdXNlIHNvbWUgb3RoZXIgZmVhdHVyZXMgc3VjaCBhcyB0aGUgZGF0ZSBmb3IgdmlzdWFsaXphdGlvbi4NCg0KYGBge3J9DQpoZWFkKGNsYXNocm95YWxlX3R3ZWV0cywgbiA9IDVMKQ0KaGVhZChjbGFzaG9mY2xhbnNfdHdlZXRzLCBuID0gNUwpDQpjbGFzaHJveWFsZV90d2VldHMkdGV4dFsxOjVdDQpjbGFzaG9mY2xhbnNfdHdlZXRzJHRleHRbMTo1XQ0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIGB0bWAgbGlicmFyeSB0byBidWlsZCBhIGNvcnB1cyBmb3IgZWFjaCBjbGFzcy4gRWFjaCB0d2VldCB3aWxsIGJlIGEgZG9jdW1lbnQgaW4gdGhpcyBjb3JwdXMuIFRoZW4gd2UgY2FuIG1lcmdlIHRoZW0gdG8gaGF2ZSBhIHNpbmdsZSBjb3JwdXMuIEJ1aWxkaW5nIGEgY29ycHVzIGlzIHJlY29tbWVuZGVkIGJlY2F1c2UgdGhlIGB0bWAgcGFja2FnZSBvZmZlcnMgbWFueSB0cmFuc2Zvcm1hdGlvbnMgZm9yIHByZXByb2Nlc3NpbmcgdGV4dC4NCg0KYGBge3J9DQpsaWJyYXJ5KHRtKQ0KIyBjb21iaW5lIGJvdGggZnJhbWVzIGluIGEgc2luZ2xlLCBiaW5hcnksIGFubm90YXRlZCBzZXQNCnR3ZWV0cyA8LSByYmluZChjbGFzaHJveWFsZV90d2VldHMsIGNsYXNob2ZjbGFuc190d2VldHMpDQojIGludGVycHJldGluZyBlYWNoIGVsZW1lbnQgb2YgdGhlIGFubm90YXRlZCB2ZWN0b3IgYXMgYSBkb2N1bWVudA0KY2xhc2hyb3lhbGVfZG9jcyA8LSBWZWN0b3JTb3VyY2UoY2xhc2hyb3lhbGVfdHdlZXRzJHRleHQpDQpjbGFzaG9mY2xhbnNfZG9jcyA8LSBWZWN0b3JTb3VyY2UoY2xhc2hvZmNsYW5zX3R3ZWV0cyR0ZXh0KQ0KIyBjb252ZXJ0IHRvIGEgY29ycHVzOiBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIHRvIGJlIGFwcGxpZWQgaW4gZnV0dXJlIHN0ZXBzDQpjbGFzaHJveWFsZV9jb3JwdXMgPC0gVkNvcnB1cyhjbGFzaHJveWFsZV9kb2NzKQ0KY2xhc2hvZmNsYW5zX2NvcnB1cyA8LSBWQ29ycHVzKGNsYXNob2ZjbGFuc19kb2NzKQ0KIyBtZXJnZSwgY29uY2F0ZW5hdGUgYm90aCBncm91cHMtY29ycHVzZXMNCmNvcnB1cyA8LSBjKGNsYXNocm95YWxlX2NvcnB1cywgY2xhc2hvZmNsYW5zX2NvcnB1cykNCmBgYA0KDQojIFZpc3VhbGl6YXRpb24NCg0KVmlzdWFsaXppbmcgdGhlIGRhdGEgaXMgaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgb3VyIGNvcnB1cy4gSW4gdGhpcyBzZWN0aW9uIHRoZXJlIGFyZSB2YXJpb3VzIHRpbWUgc2VyaWVzIHBsb3RzLCBkb251dCBwbG90cyBhbmQgd29yZGNsb3Vkcy4NCg0KIyMgVGltZSBTZXJpZXMgUGxvdA0KDQpXZSBjYW4gdXNlIHRoZSBgcnR3ZWV0YCBwYWNrYWdlIGdldCBhIHRpbWUgc2VyaWVzIHBsb3Qgd2l0aCB0aGUgZnJlcXVlbmNpZXMgb2YgdHdlZXRzLiBJbiB0aGVzZSBleGFtcGxlcywgSSBhbmFseXNlIHRoZSBmcmVxdWVuY2llcyBvZiBib3RoIGFjY291bnRzIGJ5IG1vbnRoLCB3ZWVrIGFuZCBkYXkuIFRoZSB0d2VldCBmcmVxdWVuY2llcyBhcmUgc2ltaWxhciwgQ2xhc2ggUm95YWxlIGhhcyBtb3JlIHR3ZWV0cy4NCg0KYGBge3J9DQp0c19wbG90KGRwbHlyOjpncm91cF9ieSh0d2VldHMsIHNjcmVlbl9uYW1lKSwgIm1vbnRoIikgKw0KICAgIGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSArDQogICAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkgKw0KICAgIGdncGxvdDI6OmxhYnMoDQogICAgICAgIHggPSAiRGF0ZSIsIHkgPSAiQ291bnQiLA0KICAgICAgICB0aXRsZSA9ICJGcmVxdWVuY3kgb2YgVHdlZXRzIGZyb20gQ2xhc2ggUm95YWxlIGFuZCBDbGFzaCBvZiBDbGFucyIsDQogICAgICAgIHN1YnRpdGxlID0gIlR3ZWV0IGNvdW50cyBhZ2dyZWdhdGVkIGJ5IG1vbnRoIg0KICAgICkNCmBgYA0KDQpgYGB7cn0NCnRzX3Bsb3QoZHBseXI6Omdyb3VwX2J5KHR3ZWV0cywgc2NyZWVuX25hbWUpLCAid2VlayIpICsNCiAgICBnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkgKw0KICAgIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpICsNCiAgICBnZ3Bsb3QyOjpsYWJzKA0KICAgICAgICB4ID0gIkRhdGUiLCB5ID0gIkNvdW50IiwNCiAgICAgICAgdGl0bGUgPSAiRnJlcXVlbmN5IG9mIFR3ZWV0cyBmcm9tIENsYXNoIFJveWFsZSBhbmQgQ2xhc2ggb2YgQ2xhbnMiLA0KICAgICAgICBzdWJ0aXRsZSA9ICJUd2VldCBjb3VudHMgYWdncmVnYXRlZCBieSB3ZWVrIg0KICAgICkNCmBgYA0KDQpgYGB7cn0NCnRzX3Bsb3QoZHBseXI6Omdyb3VwX2J5KHR3ZWV0cywgc2NyZWVuX25hbWUpLCAiZGF5IikgKw0KICAgIGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSArDQogICAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkgKw0KICAgIGdncGxvdDI6OmxhYnMoDQogICAgICAgIHggPSAiRGF0ZSIsIHkgPSAiQ291bnQiLA0KICAgICAgICB0aXRsZSA9ICJGcmVxdWVuY3kgb2YgVHdlZXRzIGZyb20gQ2xhc2ggUm95YWxlIGFuZCBDbGFzaCBvZiBDbGFucyIsDQogICAgICAgIHN1YnRpdGxlID0gIlR3ZWV0IGNvdW50cyBhZ2dyZWdhdGVkIGJ5IGRheSINCiAgICApDQpgYGANCg0KIyMgVHdlZXQgVHlwZXMgQ2hhcnQNCg0KQW5hbHlzaW5nIHRoZSByYXRpbyBvZiBxdW90ZXMsIHJlcGxpZXMsIHJldHdlZXRzIGFuZCBvcmdhbmljIHR3ZWV0cyBjYW4gdGVsbCB1cyB0aGUgdHlwZSBvZiB0d2VldHMgd2UgYXJlIGFuYWx5c2luZy4gV2UgY291bGQgY2hvb3NlIHRvIG9ubHkga2VlcCBvcmdhbmljIHR3ZWV0cyBmb3Igb3VyIGNvcnB1cy4gUmVtb3ZpbmcgcmV0d2VldHMgbWlnaHQgcmVkdWNlIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgZGF0YSBhbmQgdGhlcmVmb3JlLCBtYWtlIGl0IGVhc2llciB0byBjbGFzc2lmeS4gVGhpcyB0aW1lIHdlIHdpbGwga2VlcCBhbGwgdHdlZXQgdHlwZXMsIGJ1dCB3ZSB3aWxsIHN0aWxsIHZpc3VhbGl6ZSB0aGUgdHlwZXMgaW4gYSBkb251dCBjaGFydC4NCg0KQXMgYSBmaXJzdCBzdGVwIHdlIGhhdmUgdG8gZGl2aWRlIGVhY2ggYWNjb3VudCB0d2VldHMgaW50byB0aGUgcHJldmlvdXNseSBtZW50aW9uZWQgc3Vic2V0cy4NCg0KYGBge3J9DQp0d2VldF90eXBlcyA8LSBmdW5jdGlvbih0d2VldHMpIHsNCiAgICBvcmdhbmljIDwtIHR3ZWV0c1t0d2VldHMkaXNfcmV0d2VldCA9PSBGQUxTRSwgXQ0KICAgICMgUmVtb3ZlIHJlcGxpZXMNCiAgICBvcmdhbmljIDwtIHN1YnNldChvcmdhbmljLCBpcy5uYShvcmdhbmljJHJlcGx5X3RvX3N0YXR1c19pZCkpDQogICAgIyBSZW1vdmUgcXVvdGVzDQogICAgb3JnYW5pYyA8LSBvcmdhbmljW29yZ2FuaWMkaXNfcXVvdGUgPT0gRkFMU0UsIF0NCiAgICAjIEtlZXBpbmcgb25seSB0aGUgcmV0d2VldHMNCiAgICByZXR3ZWV0cyA8LSB0d2VldHNbdHdlZXRzJGlzX3JldHdlZXQgPT0gVFJVRSwgXQ0KICAgICMgS2VlcGluZyBvbmx5IHRoZSByZXBsaWVzDQogICAgcmVwbGllcyA8LSBzdWJzZXQodHdlZXRzLCAhaXMubmEodHdlZXRzJHJlcGx5X3RvX3N0YXR1c19pZCkpDQogICAgIyBLZWVwaW5nIG9ubHkgdGhlIHF1b3Rlcw0KICAgIHF1b3RlcyA8LSB0d2VldHNbdHdlZXRzJGlzX3F1b3RlID09IFRSVUUsIF0NCiAgICB0eXBlc19saXN0IDwtIGxpc3Qob3JnYW5pYywgcmV0d2VldHMsIHJlcGxpZXMsIHF1b3RlcykNCiAgICByZXR1cm4odHlwZXNfbGlzdCkNCn0NCmBgYA0KDQpgYGB7cn0NCiMgZ2V0IGNsYXNocm95YWxlIHR3ZWV0IHR5cGVzDQpjbGFzaHJveWFsZV90eXBlcyA8LSB0d2VldF90eXBlcyhjbGFzaHJveWFsZV90d2VldHMpDQpjbGFzaHJveWFsZV9vcmdhbmljIDwtIGNsYXNocm95YWxlX3R5cGVzW1sxXV0NCmNsYXNocm95YWxlX3JldHdlZXRzIDwtIGNsYXNocm95YWxlX3R5cGVzW1syXV0NCmNsYXNocm95YWxlX3JlcGxpZXMgPC0gY2xhc2hyb3lhbGVfdHlwZXNbWzNdXQ0KY2xhc2hyb3lhbGVfcXVvdGVzIDwtIGNsYXNocm95YWxlX3R5cGVzW1s0XV0NCg0KIyBnZXQgY2xhc2hvZmNsYW5zIHR3ZWV0IHR5cGVzDQpjbGFzaG9mY2xhbnNfdHlwZXMgPC0gdHdlZXRfdHlwZXMoY2xhc2hvZmNsYW5zX3R3ZWV0cykNCmNsYXNob2ZjbGFuc19vcmdhbmljIDwtIGNsYXNob2ZjbGFuc190eXBlc1tbMV1dDQpjbGFzaG9mY2xhbnNfcmV0d2VldHMgPC0gY2xhc2hvZmNsYW5zX3R5cGVzW1syXV0NCmNsYXNob2ZjbGFuc19yZXBsaWVzIDwtIGNsYXNob2ZjbGFuc190eXBlc1tbM11dDQpjbGFzaG9mY2xhbnNfcXVvdGVzIDwtIGNsYXNob2ZjbGFuc190eXBlc1tbNF1dDQpgYGANCg0KVGhlbiwgd2UgY3JlYXRlIGEgc2VwYXJhdGUgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSBudW1iZXIgb2Ygb3JnYW5pYyB0d2VldHMsIHJldHdlZXRzLCByZXBsaWVzIGFuZCBxdW90ZXMuIFdlIGhhdmUgdG8gcHJlcGFyZSB0aGUgZGF0YSBmcmFtZSBmb3IgYSBkb251dCBjaGFydC4gVGhpcyBpbmNsdWRlcyBhZGRpbmcgY29sdW1ucyB0aGF0IGNhbGN1bGF0ZSB0aGUgcmF0aW9zIGFuZCBwZXJjZW50YWdlcyBhbmQgc29tZSB2aXN1YWxpc2F0aW9uIHR3ZWFrcyBzdWNoIGFzIHNwZWNpZnlpbmcgdGhlIGxlZ2VuZCBhbmQgcm91bmRpbmcgdXAgeW91ciBkYXRhLg0KDQpgYGB7cn0NCnR5cGVfZGF0YSA8LSBmdW5jdGlvbihvcmdhbmljLCByZXR3ZWV0cywgcmVwbGllcywgcXVvdGVzKSB7DQogICAgIyBDcmVhdGluZyBhIGRhdGEgZnJhbWUNCiAgICBkYXRhIDwtIGRhdGEuZnJhbWUoDQogICAgICAgIGNhdGVnb3J5ID0gYygiT3JnYW5pYyIsICJSZXR3ZWV0cyIsICJSZXBsaWVzIiwgIlF1b3RlcyIpLA0KICAgICAgICBjb3VudCA9IGMoZGltKG9yZ2FuaWMpWzFdLCBkaW0ocmV0d2VldHMpWzFdLCBkaW0ocmVwbGllcylbMV0sIGRpbShxdW90ZXMpWzFdKQ0KICAgICkNCg0KICAgICMgQWRkaW5nIGNvbHVtbnMNCiAgICBkYXRhJGZyYWN0aW9uIDwtIGRhdGEkY291bnQgLyBzdW0oZGF0YSRjb3VudCkNCiAgICBkYXRhJHBlcmNlbnRhZ2UgPC0gZGF0YSRjb3VudCAvIHN1bShkYXRhJGNvdW50KSAqIDEwMA0KICAgIGRhdGEkeW1heCA8LSBjdW1zdW0oZGF0YSRmcmFjdGlvbikNCiAgICBkYXRhJHltaW4gPC0gYygwLCBoZWFkKGRhdGEkeW1heCwgbiA9IC0xKSkNCg0KICAgICMgUm91bmRpbmcgdGhlIGRhdGEgdG8gdHdvIGRlY2ltYWwgcG9pbnRzDQogICAgZGF0YVssIC0xXSA8LSByb3VuZChkYXRhWywgLTFdLCAyKQ0KICAgIHJldHVybihkYXRhKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KY2xhc2hyb3lhbGVfZGF0YSA8LSB0eXBlX2RhdGEoY2xhc2hyb3lhbGVfb3JnYW5pYywgY2xhc2hyb3lhbGVfcmV0d2VldHMsIGNsYXNocm95YWxlX3JlcGxpZXMsIGNsYXNocm95YWxlX3F1b3RlcykNCnR5cGUgPC0gcGFzdGUoY2xhc2hyb3lhbGVfZGF0YSRjYXRlZ29yeSwgY2xhc2hyb3lhbGVfZGF0YSRwZXJjZW50YWdlLCAiJSIpDQpnZ3Bsb3QoY2xhc2hyb3lhbGVfZGF0YSwgYWVzKHltYXggPSB5bWF4LCB5bWluID0geW1pbiwgeG1heCA9IDQsIHhtaW4gPSAzLCBmaWxsID0gdHlwZSkpICsNCiAgICBnZW9tX3JlY3QoKSArDQogICAgY29vcmRfcG9sYXIodGhldGEgPSAieSIpICsNCiAgICB4bGltKGMoMiwgNCkpICsNCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgICBsYWJzKHRpdGxlID0gIkNsYXNoIFJveWFsZSBUd2VldCBUeXBlcyIpDQpgYGANCg0KYGBge3J9DQpjbGFzaG9mY2xhbnNfZGF0YSA8LSB0eXBlX2RhdGEoY2xhc2hvZmNsYW5zX29yZ2FuaWMsIGNsYXNob2ZjbGFuc19yZXR3ZWV0cywgY2xhc2hvZmNsYW5zX3JlcGxpZXMsIGNsYXNob2ZjbGFuc19xdW90ZXMpDQp0eXBlIDwtIHBhc3RlKGNsYXNob2ZjbGFuc19kYXRhJGNhdGVnb3J5LCBjbGFzaG9mY2xhbnNfZGF0YSRwZXJjZW50YWdlLCAiJSIpDQpnZ3Bsb3QoY2xhc2hvZmNsYW5zX2RhdGEsIGFlcyh5bWF4ID0geW1heCwgeW1pbiA9IHltaW4sIHhtYXggPSA0LCB4bWluID0gMywgZmlsbCA9IHR5cGUpKSArDQogICAgZ2VvbV9yZWN0KCkgKw0KICAgIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArDQogICAgeGxpbShjKDIsIDQpKSArDQogICAgdGhlbWVfdm9pZCgpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArDQogICAgbGFicyh0aXRsZSA9ICJDbGFzaCBvZiBDbGFucyBUd2VldCBUeXBlcyIpDQpgYGANCg0KIyMgSW5pdGlhbCBXb3JkY2xvdWRzDQoNCkJlZm9yZSBzdGFydGluZyBsZWFybmluZyB0aGUgZXhwb3NlZCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgbGV04oCZcyBidWlsZCBhIHdvcmRjbG91ZCB3aXRoIHRoZSBmb2xsb3dpbmcgcGFja2FnZSBbM10uIEl0cyBgd29yZGNsb3VkKClgIGNvbW1hbmQgbmVlZHMgdGhlIGxpc3Qgb2Ygd29yZHMgYW5kIHRoZWlyIGZyZXF1ZW5jaWVzIGFzIHBhcmFtZXRlcnMuIEFzIHRoZSB3b3JkcyBhcHBlYXIgaW4gY29sdW1ucyBpbiB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXgsIHRoZSBgY29sU3Vtc2AgY29tbWFuZCBpcyB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgd29yZCBmcmVxdWVuY2llcy4gSW4gb3JkZXIgdG8gY29tcGxldGUgdGhlIG5lZWRlZCBjYWxjdWxhdGlvbnMsIG5vdGUgdGhhdCB0aGUgdGVybS1kb2N1bWVudCBtYXRyaXggbmVlZHMgdG8gYmUgdHJhbnNmb3JtZWQgKGNhc3RlZCkgdG8gYSBtYXRyaXggZm9ybSB3aXRoIHRoZSBgYXMubWF0cml4YCBjYXN0LW9wZXJhdG9yLiBUaGlzIGluaXRpYWwgZG9jdW1lbnQtdGVybSBtYXRyaXggaXMgdmVyeSBzcGFyc2UsIGl0IGNvbnRhaW5zIDIwMDAgZG9jdW1lbnRzIGFuZCA3ODU0IHRlcm1zLg0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlIGdlbmVyYXRlZCB3b3JkY2xvdWRzIGFyZSBub3QgdmVyeSBpbmZvcm1hdGl2ZS4gVGhlIHJlYXNvbiBmb3IgdGhpcyBpcyB0aGF0IHRoZSBtb3N0IGNvbW1vbiB3b3JkcyBhcmUgZW5nbGlzaCBzdG9wIHdvcmRzLiBUaGVzZSB3b3JkcyBhcmUgdmVyeSBjb21tb24sIGJ1dCBkJ3QgaGF2ZSBhbnkgbWVhbmluZy4gVGhhdCdzIHdoeSB3ZSBzaG91bGQgcmVtb3ZlIHRoZW0gZnJvbSBvdXIgY29ycHVzLg0KDQpgYGB7cn0NCmNvcnB1c19kdG1faW5pdCA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzKQ0KY29ycHVzX2R0bV9pbml0DQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHdvcmRjbG91ZCkNCndvcmRfZnJlcXMgPC0gc29ydChjb2xTdW1zKGFzLm1hdHJpeChjb3JwdXNfZHRtX2luaXQpWzE6biwgXSksIGRlY3JlYXNpbmcgPSBUUlVFKQ0Kd29yZGNsb3VkKHdvcmRzID0gbmFtZXMod29yZF9mcmVxcyksIGZyZXEgPSB3b3JkX2ZyZXFzLCBtYXgud29yZHMgPSAxMDAsIHJhbmRvbS5vcmRlciA9IEZBTFNFLCBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpKQ0KYGBgDQoNCmBgYHtyfQ0Kd29yZF9mcmVxcyA8LSBzb3J0KGNvbFN1bXMoYXMubWF0cml4KGNvcnB1c19kdG1faW5pdClbKG4gKyAxKToobiArIG4pLCBdKSwgZGVjcmVhc2luZyA9IFRSVUUpDQp3b3JkY2xvdWQod29yZHMgPSBuYW1lcyh3b3JkX2ZyZXFzKSwgZnJlcSA9IHdvcmRfZnJlcXMsIG1heC53b3JkcyA9IDEwMCwgcmFuZG9tLm9yZGVyID0gRkFMU0UsIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIikpDQpgYGANCg0KIyMgQmV0dGVyIFdvcmRjbG91ZHMNCg0KVG8gbWFrZSBhIGJldHRlciB3b3JkY2xvdWQsIHdlIGNhbiBwYXNzIHRoZSB0ZXh0IGRpcmVjdGx5LiBBIGNvcnB1cyB3aWxsIGJlIGdlbmVyYXRlZCBhbmQgc3RvcCB3b3JkcyB3aWxsIGJlIHJlbW92ZWQgYXV0b21hdGljYWxseS4gSG93ZXZlciwgdGhpcyB0aW1lIGVtb3RlcyBhcmUga2VwdCwgYW5kIHdlIGNhbiBzZWUgdGhhdCBzb21lIG9mIHRoZW0gYXJlIHF1aXRlIGNvbW1vbi4gV2UgY2FuIHNlZSB0aGF0IHRoZSBmb2xsb3dpbmcgd29yZGNsb3VkcyBhcmUgbXVjaCBtb3JlIGluZm9ybWF0aXZlLiBXZSBjYW4gYWxyZWFkeSBzZWUgc29tZSBkaWZmZXJlbmNlcyBhbmQgc2ltaWxhcml0aWVzIGJldHdlZW4gdGhlIGNvcnBvcmEuDQoNCmBgYHtyfQ0Kd29yZGNsb3VkKGNsYXNocm95YWxlX3R3ZWV0cyR0ZXh0LCBtYXgud29yZHMgPSA1MCwgc2NhbGUgPSBjKDMuNSwgMC4yNSksIHJhbmRvbS5vcmRlciA9IEZBTFNFLCBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpKQ0KYGBgDQoNCmBgYHtyfQ0Kd29yZGNsb3VkKGNsYXNob2ZjbGFuc190d2VldHMkdGV4dCwgbWF4LndvcmRzID0gNTAsIHNjYWxlID0gYygzLjUsIDAuMjUpLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkNCmBgYA0KDQojIyBIYXNodGFnIFdvcmxkY2xvdWRzDQoNCkZpbmFsbHksIHdlIGNhbiBjcmVhdGUgYW5vdGhlciB3b3JkY2xvdWQgdGhhdCBvbmx5IGNvbnRhaW5zIHRoZSBoYXNodGFncy4gV2UgY2FuIHNlZSB0aGF0IGhhc2h0YWdzIGFyZSBub3QgdmVyeSBjb21tb24sIGJ1dCB0aGV5IGFyZSBkaWZmZXJlbnQgYmV0d2VlbiB0aGUgdHdvIGNvcnBvcmEuIFdlIHdpbGwgaGF2ZSB0byBkZWNpZGUgaWYgd2Ugd2FudCB0byBrZWVwIG9yIHJlbW92ZSB0aGVtIGluIHRoZSBuZXh0IHNlY3Rpb24uDQoNCmBgYHtyfQ0KY2xhc2hyb3lhbGVfdHdlZXRzJGhhc2h0YWdzIDwtIGFzLmNoYXJhY3RlcihjbGFzaHJveWFsZV90d2VldHMkaGFzaHRhZ3MpDQpjbGFzaHJveWFsZV90d2VldHMkaGFzaHRhZ3MgPC0gZ3N1YigiY1xcKCIsICIiLCBjbGFzaHJveWFsZV90d2VldHMkaGFzaHRhZ3MpDQp3b3JkY2xvdWQoY2xhc2hyb3lhbGVfdHdlZXRzJGhhc2h0YWdzLCBtaW4uZnJlcSA9IDEsIHNjYWxlID0gYygzLjUsIC41KSwgbWF4LndvcmRzID0gNTAsIHJhbmRvbS5vcmRlciA9IEZBTFNFLCByb3QucGVyID0gMC4zNSwgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkNCmBgYA0KDQpgYGB7cn0NCmNsYXNob2ZjbGFuc190d2VldHMkaGFzaHRhZ3MgPC0gYXMuY2hhcmFjdGVyKGNsYXNob2ZjbGFuc190d2VldHMkaGFzaHRhZ3MpDQpjbGFzaG9mY2xhbnNfdHdlZXRzJGhhc2h0YWdzIDwtIGdzdWIoImNcXCgiLCAiIiwgY2xhc2hvZmNsYW5zX3R3ZWV0cyRoYXNodGFncykNCndvcmRjbG91ZChjbGFzaG9mY2xhbnNfdHdlZXRzJGhhc2h0YWdzLCBtaW4uZnJlcSA9IDEsIHNjYWxlID0gYygzLjUsIC41KSwgbWF4LndvcmRzID0gNTAsIHJhbmRvbS5vcmRlciA9IEZBTFNFLCByb3QucGVyID0gMC4zNSwgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkNCmBgYA0KDQojIFByZXByb2Nlc3NpbmcNCg0KQXMgd2UgaGF2ZSBzYWlkIGJlZm9yZSwgc29tZSBwcmVwcm9jZXNzaW5nIGlzIG5lZWRlZCBzbyB0aGF0IHdlIGdldCBiZXR0ZXIgcmVzdWx0cyB3aGVuIGNsYXNzaWZ5aW5nIHRoZSBkb2N1bWVudHMuIEZpcnN0LCB3ZSB3aWxsIGFwcGx5IHNvbWUgdHJhbnNmb3JtYXRpb25zIHN1Y2ggYXMgcmVtb3Zpbmcgc3RvcCB3b3JkcyB0byB0aGUgdGV4dC4gVGhlbiwgd2Ugd2lsbCByZW1vdmUgc3BhcnNlIHdvcmRzIGFuZCBvdXRsaWVyIGRvY3VtZW50cyBmcm9tIHRoZSBjb3JwdXMuIEZpbmFsbHksIHdlIHdpbGwgZGlzcGxheSB0aGUgZmluYWwgd29yZGNsb3VkcyBzbyB0aGF0IHdlIGNhbiBjb21wYXJlIHRoZW0gd2l0aCB0aGUgaW5pdGlhbCBvbmVzLg0KDQojIyBBcHBseSBUcmFuc2Zvcm1hdGlvbnMNCg0KVHJhbnNmb3JtYXRpb25zIG9wZXJhdG9ycyB0byB0aGUgY29ycHVzIGFyZSBhcHBsaWVkIHZpYSBgdG1fbWFwYCBmdW5jdGlvbiwgd2hpY2ggYXBwbGllcyAobWFwcykgYSBmdW5jdGlvbiB0byBhbGwgZWxlbWVudHMgb2YgdGhlIGNvcnB1cy4gVGhlIHRyYW5zZm9ybWF0aW9ucyB3aWxsIGJlIGFwcGxpZWQgdG8gdGhlIHdob2xlIGNvcnB1cywgdGhhdCBjb25zdGFpbnMgZG9jdW1lbnRzIG9mIGJvdGggY2xhc3Nlcy4gQXBhcnQgZnJvbSB0aGUgdHJhbnNmb3JtYXRpb25zIHRoYXQgYXJlIGF2YWlsYWJsZSBpbiB0aGUgYHRtYCBwYWNrYWdlLCBzb21lIGN1c3RvbSB0cmFuc2Zvcm1hdGlvbnMgYXJlIGFsc28gYXBwbGllZCB3aXRoIHRoZSBmdW5jdGlvbiBgY29udGVudF90cmFuc2Zvcm1lcmAuDQoNCkZpcnN0LCBzb21lIGVsZW1lbnRzIGFyZSByZW1vdmVkIGZyb20gdGhlIGNvcnB1czogbnVtYmVycywgcHVuY3R1YXRpb24sIHVybHMsIG1lbnRpb25zLCBoYXNodGFncywgbmV3bGluZXMgYW5kIGVtb2ppcy4gVGhlbiwgYWxsIHRoZSB3b3JkcyBhcmUgY29udmVydGVkIHRvIGxvd2VyY2FzZS4gTmV4dCwgdGhlIHByZXZpb3VzbHkgbWVudGlvbmVkIGVuZ2xpc2ggc3RvcHdvcmRzIGFyZSByZW1vdmVkLiBBZnRlciwgbXVsdGlwbGUgd2hpdGVzcGFjZSBjaGFyYWN0ZXJzIGFyZSBjb2xsYXBzZWQgdG8gYSBzaW5nbGUgb25lLiBGaW5hbGx5LCBhbGwgdGhlIHdvcmRzIGFyZSBzdGVtbWVkIHRvIHJlZHVjZSB0aGUgbnVtYmVyIG9mIHdvcmRzLiBXZSBjYW4gcHJpbnQgdGhlIGZpcnN0IDUgdHdlZXRzIG9mIGVhY2ggY29ycHVzIHRvIHNlZSB0aGUgZGlmZmVyZW5jZSB3aXRoIHRoZSBpbml0aWFsIG9uZXMuDQoNCmBgYHtyfQ0KcmVtb3ZlX3VybHMgPC0gZnVuY3Rpb24odGV4dCkgew0KICAgIGdzdWIoImh0dHBcXFMqIiwgIiIsIHRleHQpDQp9DQpyZW1vdmVfbWVudGlvbnMgPC0gZnVuY3Rpb24odGV4dCkgew0KICAgIGdzdWIoIkBcXFMqIiwgIiIsIHRleHQpDQp9DQpyZW1vdmVfaGFzaHRhZ3MgPC0gZnVuY3Rpb24odGV4dCkgew0KICAgIGdzdWIoIiNcXFMqIiwgIiIsIHRleHQpDQp9DQpyZW1vdmVfbmV3bGluZXMgPC0gZnVuY3Rpb24odGV4dCkgew0KICAgIGdzdWIoIlxcXG4iLCAiICIsIHRleHQpDQp9DQpyZW1vdmVfZW1vamlzIDwtIGZ1bmN0aW9uKHRleHQpIHsNCiAgICBnc3ViKCJbXlx4MDEtXHg3Rl0iLCAiIiwgdGV4dCkNCn0NCmBgYA0KDQpgYGB7cn0NCiMgcmVtb3ZlIG51bWJlcnMNCmNvcnB1c190cmFucyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVOdW1iZXJzKQ0KIyByZW1vdmUgcHVuY3R1YXRpb24NCmNvcnB1c190cmFucyA8LSB0bV9tYXAoY29ycHVzX3RyYW5zLCByZW1vdmVQdW5jdHVhdGlvbikNCiMgcmVtb3ZlIHVybHMNCmNvcnB1c190cmFucyA8LSB0bV9tYXAoY29ycHVzX3RyYW5zLCBjb250ZW50X3RyYW5zZm9ybWVyKHJlbW92ZV91cmxzKSkNCiMgcmVtb3ZlIG1lbnRpb25zDQpjb3JwdXNfdHJhbnMgPC0gdG1fbWFwKGNvcnB1c190cmFucywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfbWVudGlvbnMpKQ0KIyByZW1vdmUgaGFzdGFncw0KY29ycHVzX3RyYW5zIDwtIHRtX21hcChjb3JwdXNfdHJhbnMsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlX2hhc2h0YWdzKSkNCiMgcmVtb3ZlIG5ld2xpbmVzDQpjb3JwdXNfdHJhbnMgPC0gdG1fbWFwKGNvcnB1c190cmFucywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfbmV3bGluZXMpKQ0KIyByZW1vdmUgZW1vamlzDQpjb3JwdXNfdHJhbnMgPC0gdG1fbWFwKGNvcnB1c190cmFucywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfZW1vamlzKSkNCiMgY29udmVydCB0byBsb3dlcmNhc2UNCmNvcnB1c190cmFucyA8LSB0bV9tYXAoY29ycHVzX3RyYW5zLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0KIyByZW1vdmUgZW5nbGlzaCBzdG9wIHdvcmRzDQpjb3JwdXNfdHJhbnMgPC0gdG1fbWFwKGNvcnB1c190cmFucywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQ0KIyBzdHJpcCB3aGl0ZXNwYWNlDQpjb3JwdXNfdHJhbnMgPC0gdG1fbWFwKGNvcnB1c190cmFucywgc3RyaXBXaGl0ZXNwYWNlKQ0KIyB0byBhY2Nlc3MgUG9ydGVyJ3Mgd29yZCBzdGVtbWluZyBhbGdvcml0aG0NCmxpYnJhcnkoU25vd2JhbGxDKQ0KY29ycHVzX3RyYW5zIDwtIHRtX21hcChjb3JwdXNfdHJhbnMsIHN0ZW1Eb2N1bWVudCkNCmBgYA0KDQpgYGB7cn0NCmZvciAoaSBpbiAxOjUpIHsNCiAgICBwcmludChjb3JwdXNfdHJhbnNbW2ldXSRjb250ZW50KQ0KfQ0KZm9yIChpIGluIChuICsgMSk6KG4gKyA2KSkgew0KICAgIHByaW50KGNvcnB1c190cmFuc1tbaV1dJGNvbnRlbnQpDQp9DQpgYGANCg0KIyMgUmVtb3ZlIFNwYXJzZSBUZXJtcw0KDQpBZnRlciBjb3JwdXMgc2V0IHRyYW5zZm9ybWF0aW9uLCBhIGNvbW1vbiBhcHByb2FjaCBpbiB0ZXh0IG1pbmluZyBpcyB0byAqKmNyZWF0ZSBhIGRvY3VtZW50LXRlcm0gbWF0cml4KiogZnJvbSBhIGNvcnB1cy4gVGhpcyBkb2N1bWVudC10ZXJtIG1hdHJpeCBpcyB0aGUgc3RhcnRpbmcgcG9pbnQgdG8gYXBwbHkgbWFjaGluZS1sZWFybmluZyBtb2RlbGl6YXRpb24gdGVjaG5pcXVlcyBzdWNoIGFzIGNsYXNzaWZpY2F0aW9uIGFuZCBjbHVzdGVyaW5nLiBEaWZmZXJlbnQgb3BlcmF0aW9ucyBjYW4gYmUgYXBwbGllZCBvdmVyIHRoaXMgbWF0cml4LiBXZSBjYW4gb2J0YWluIHRoZSB0ZXJtcyB0aGF0IG9jY3VyIGF0IGxlYXN0IDUwIHRpbWVzLiBXZSBjYW4gYWxzbyBjb25zdWx0IHRoZSAqKnRlcm1zIHRoYXQgYXNzb2NpYXRlKiogd2l0aCBhdCBsZWFzdCBieSBhIDAuMyBjb3JyZWxhdGlvbiBkZWdyZWUgd2l0aCB0aGUgdGVybSAibWFpbnRlbiIuIFdlIGNhbiBzZWUgdGhhdCB0aGUgY29ycmVsYXRlZCB3b3JkcyBtYWtlIHNlbnNlOiAic2hvcnQgbWFpbnRlbmNhbmNlIGJyZWFrIHNvb24iLCAic2VydmVyIHVwa2VlcCIuDQoNCmBgYHtyfQ0KY29ycHVzX2R0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzX3RyYW5zKQ0KY29ycHVzX2R0bQ0KZmluZEZyZXFUZXJtcyhjb3JwdXNfZHRtLCA1MCkNCmZpbmRBc3NvY3MoY29ycHVzX2R0bSwgdGVybSA9ICJtYWludGVuIiwgY29ybGltaXQgPSAwLjMpDQpgYGANCg0KV2UgaGF2ZSByZW1vdmVkIG5lYXJseSA0MDAwIHdvcmRzIGZyb20gdGhlIGluaXRpYWFsIGRvY3VtZW50LXRlcm0gbWF0cml4LiBIb3dldmVyLCBpdCBoYXMgc3RpbGwgYSBodWdlIGRlZ3JlZSBvZiBzcGFyc2l0eTogYSBsb3cgYW1vdW50IG9mIG5vbi16ZXJvIGVsZW1lbnRzLiBUaHVzLCBvbmUgb2YgdGhlIG1vc3QgaW1wb3J0YW50IG9wZXJhdGlvbnMgaXMgdG8gcmVtb3ZlIHNwYXJzZSB0ZXJtcywgdGVybXMgb2NjdXJyaW5nIGluIHZlcnkgZmV3IGRvY3VtZW50cy4gVGhlIGBzcGFyc2VgIHBhcmFtZXRlciBpbiB0aGUgYHJlbW92ZVNwYXJzZVRlcm1zYCBmdW5jdGlvbiByZWZlcnMgdG8gdGhlIG1heGltdW0gc3BhcnNlbmVzcyBhbGxvd2VkOiB0aGUgc21hbGxlciBpdHMgcHJvcG9ydGlvbiwgZmV3ZXIgdGVybXMgd2lsbCBiZSByZXRhaW5lZC4gQSB0cmlhbCBhbmQgZXJyb3IgYXBwcm9hY2ggd2lsbCBmaW5hbGx5IHJldHVybiBhIHByb3BlciBudW1iZXIgb2YgdGVybXMuIFRoaXMgbWF0cml4IHdpbGwgYmUgdGhlIHN0YXJ0aW5nIHBvaW50IGZvciBidWlsZGluZyBmdXJ0aGVyIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLg0KDQpBZnRlciB0cnlpbmcgbXVsdGlwbGUgdmFsdWVzLCB3ZSBkZWNpZGUgdG8ga2VlcCB0ZXJtcyB3aXRoIGEgbWF4aW11bSBzcGFyc2VuZXNzIG9mIGAwLjk5YC4gVGhpcyBzZWVtcyB0byBiZSB2ZXJ5IGhpZ2gsIGJ1dCBpdCByZWR1Y2VzIHRoZSBudW1iZXJzIG9mIHRlcm1zIGRyYXN0aWNhbGx5LiBJbiBmYWN0LCBzZWxlY3RpbmcgbG93ZXIgdmFsdWVzIG9mIHNwYXJzZW5lc3MgdGhlIG51bWJlciBvZiB0ZXJtcyBpcyB0b28gbG93Lg0KDQpgYGB7cn0NCmNvcnB1c19kdG1fOTUgPC0gcmVtb3ZlU3BhcnNlVGVybXMoY29ycHVzX2R0bSwgc3BhcnNlID0gMC45NSkNCmNvcnB1c19kdG1fOTUNCmJhcnBsb3QoYXMubWF0cml4KGNvcnB1c19kdG1fOTUpLA0KICAgIHhsYWIgPSAidGVybXMiLCB5bGFiID0gIm51bWJlciBvZiBvY2N1cnJlbmNlcyIsDQogICAgbWFpbiA9ICJNb3N0IGZyZXF1ZW50IHRlcm1zIChzcGFyc2VuZXNzPTAuOTUpIg0KKQ0KY29ycHVzX2R0bV85NyA8LSByZW1vdmVTcGFyc2VUZXJtcyhjb3JwdXNfZHRtLCBzcGFyc2UgPSAwLjk3KQ0KY29ycHVzX2R0bV85Nw0KYmFycGxvdChhcy5tYXRyaXgoY29ycHVzX2R0bV85NyksDQogICAgeGxhYiA9ICJ0ZXJtcyIsIHlsYWIgPSAibnVtYmVyIG9mIG9jY3VycmVuY2VzIiwNCiAgICBtYWluID0gIk1vc3QgZnJlcXVlbnQgdGVybXMgKHNwYXJzZW5lc3M9MC45NykiDQopDQpjb3JwdXNfZHRtXzk5IDwtIHJlbW92ZVNwYXJzZVRlcm1zKGNvcnB1c19kdG0sIHNwYXJzZSA9IDAuOTkpDQpjb3JwdXNfZHRtXzk5DQp0ZXJtcyA8LSBkaW0oY29ycHVzX2R0bV85OSlbMl0NCmJhcnBsb3QoYXMubWF0cml4KGNvcnB1c19kdG1fOTkpLA0KICAgIHhsYWIgPSAidGVybXMiLCB5bGFiID0gIm51bWJlciBvZiBvY2N1cnJlbmNlcyIsDQogICAgbWFpbiA9ICJNb3N0IGZyZXF1ZW50IHRlcm1zIChzcGFyc2VuZXNzPTAuOTkpIg0KKQ0KYGBgDQoNCiMjIE91dGxpZXIgRGV0ZWN0aW9uDQoNCk91dGxpZXIgZGV0ZWN0aW9uIGNhbiBiZSB1c2VkIHRvIGRldGVjdCBhbmQgcmVtb3ZlIG91dGxpZXIgZG9jdW1lbnRzIGZyb20gdGhlIGNvcnB1cy4gV2UgdGVzdCB0aGUgSXNvbGF0aW9uIEZvcmVzdCBtZXRob2QuIEkgZGVjaWRlZCBub3QgdG8gcmVtb3ZlIGFueSBkb2N1bWVudCB0byBzaW1wbGlmeSB0aGUgbmV4dCBzdGVwcy4NCg0KSXNvbGF0aW9uIEZvcmVzdCBjb25zdHJ1Y3RzIGEgdHJlZSBwZXIgZG9jdW1lbnQuIEl0IHRyaWVzIHRvIGlzb2xhdGUgdGhlIHNhbXBsZSBmcm9tIHRoZSByZXN0LiBBcyBvdXRsaWVycyBhcmUgZWFzeSB0byBpc29sYXRlLCB0aGVpciBpc29sYXRpb24gc2NvcmUgaXMgaGlnaC4gV2UgaGF2ZSB0byBwbG90IHRoZSBvdXRsaWVybmVzcyBhbmQgZGVjaWRlIGEgdGhyZXNob2xkLg0KDQohW0lzb2xhdGlvbiBGb3Jlc3RdKC4uL2ltYWdlcy9pc29sYXRpb25fZm9yZXN0LnBuZykNCg0KYGBge3J9DQpsaWJyYXJ5KHNvbGl0dWRlKQ0KIyBFbXB0eSB0cmVlIHN0cnVjdHVyZQ0KaXNvIDwtIGlzb2xhdGlvbkZvcmVzdCRuZXcoKQ0KDQojIGNvbnZlcnQgZHRtIHRvIGRhdGFmcmFtZQ0KY29ycHVzX2RmXzk5IDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KGNvcnB1c19kdG1fOTkpKQ0KDQojIExlYXJuIHRoZSBJc29sYXRpb25Gb3Jlc3QgZm9yIG91ciBkYXRhDQppc28kZml0KGNvcnB1c19kZl85OSkNCg0KIyBwcmVkaWN0IGZvciBvdXIgZGF0YQ0KcCA8LSBpc28kcHJlZGljdChjb3JwdXNfZGZfOTkpDQoNCiMgcGxvdCBhbm9tYWx5IHNjb3JlDQpwbG90KGRlbnNpdHkocCRhbm9tYWx5X3Njb3JlKSwgbWFpbiA9ICJBbm9tYWx5IFNjb3JlIERlbnNpdHkiKQ0KDQojIEJhc2VkIG9uIHRoZSBwbG90LCBkZWNpZGUgdGhlIGN1dC1vZmYgcG9pbnQNCndoaWNoKHAkYW5vbWFseV9zY29yZSA+IDAuNjIpDQpgYGANCg0KIyMgRmluYWwgV29ybGRjbG91ZHMNCg0KRmluYWxseSwgdGhlIHdvcmRjbG91ZHMgb2YgdGhlIHJlZHVjZWQgZG9jdW1lbnQtdGVybSBtYXRyaXggYXJlIHBsb3R0ZWQuIFdlIGNhbiBzZWUgdGhlIGRpZmZlcmVuY2Ugd2l0aCB0aGUgaW5pdGlhbCB3b3JkY2xvdWQuIFRoZSB0ZXJtcyBvZiBlYWNoIHdvcmRjbG91ZCBhcmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQNCg0KYGBge3J9DQojIGNhbGN1bGF0ZSB0aGUgZnJlcXVlbmN5IG9mIHdvcmRzIGFuZCBzb3J0IGluIGRlc2NlbmRpbmcgb3JkZXIuDQp3b3JkX2ZyZXFzIDwtIHNvcnQoY29sU3Vtcyhhcy5tYXRyaXgoY29ycHVzX2R0bV85OSlbMTpuLCBdKSwgZGVjcmVhc2luZyA9IFRSVUUpDQp3b3JkY2xvdWQod29yZHMgPSBuYW1lcyh3b3JkX2ZyZXFzKSwgZnJlcSA9IHdvcmRfZnJlcXMsIG1heC53b3JkcyA9IDUwLCBzY2FsZSA9IGMoMy41LCAwLjI1KSwgcmFuZG9tLm9yZGVyID0gRkFMU0UsIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIikpDQpgYGANCg0KYGBge3J9DQp3b3JkX2ZyZXFzIDwtIHNvcnQoY29sU3Vtcyhhcy5tYXRyaXgoY29ycHVzX2R0bV85OSlbKG4gKyAxKToobiArIG4pLCBdKSwgZGVjcmVhc2luZyA9IFRSVUUpDQp3b3JkY2xvdWQod29yZHMgPSBuYW1lcyh3b3JkX2ZyZXFzKSwgZnJlcSA9IHdvcmRfZnJlcXMsIG1heC53b3JkcyA9IDUwLCBzY2FsZSA9IGMoMy41LCAwLjI1KSwgcmFuZG9tLm9yZGVyID0gRkFMU0UsIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIikpDQpgYGANCg0KIyBDbHVzdGVyaW5nDQoNCiMjIENsdXN0ZXJpbmcgV29yZHMNCg0KV2UgdHJ5IHRvIGZpbmQgY2x1c3RlcnMgb2Ygd29yZHMgd2l0aCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZywgYSBwb3B1bGFyIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcyB3aGljaCBidWlsZHMgYSBkZW5kb2dyYW0gdG8gaXRlcmF0aXZlbHkgZ3JvdXAgcGFpcnMgb2Ygc2ltaWxhciBvYmplY3RzLiBUbyBkbyBzbywgYSBtYXRyaXggd2l0aCB0aGUgc3BhcnNlIHRlcm1zIHJlbW92ZWQgaXMgbmVlZGVkLiBXZSBzZWxlY3QgdGhlIDAuOTcgc3BhcnNpdHkgbWF0cml4IHNvIHRoYXQgd2UgY2FuIHZpc3VhbGl6ZSB0aGVtLiBBZnRlciB0aGUgYXBwbGljYXRpb24gb2YgdGhlIG1hdHJpeC1jYXN0aW5nIG9wZXJhdG9yLCBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgYXJlIHNjYWxlZC4NCg0KV2UgbmVlZCB0byBjYWxjdWxhdGUgdGhlIGRpc3RhbmNlIGJldHdlZW4gcGFpcnMgb2YgdGVybXMuIFRoZSBgZGlzdGAgb3BlcmF0b3IgcGVyZm9ybXMgdGhpcyBjYWxjdWxhdGlvbiBiZXR3ZWVuIHBhaXJzIG9mIHJvd3Mgb2YgdGhlIHByb3ZpZGVkIG1hdHJpeC4gQXMgdGVybXMgYXBwZWFyIGluIHRoZSBjb2x1bW5zIG9mIHRoZSBkb2N1bWVudC10ZXJtIG1hdHJpeCAoYGNvcnB1c19kdG1fOTdgKSwgaXQgbmVlZHMgdG8gYmUgdHJhbnNwb3NlZCBieSBtZWFucyBvZiB0aGUgYHRgIG9wZXJhdG9yLiBUaGUgY2x1c3RlcmluZy1kZW5kb2dyYW0gaXMgYnVpbHQgd2l0aCB0aGUgYGhjbHVzdGAgb3BlcmF0b3IuIEl0IG5lZWRzIGFzIGlucHV0IHRoZSBjYWxjdWxhdGVkIGRpc3RhbmNlIG1hdHJpeCBiZXR3ZWVuIHBhaXJzIG9mIHRlcm1zIGFuZCBhIGNyaXRlcmlhIHRvIGRlY2lkZSB3aGljaCBwYWlyIG9mIGNsdXN0ZXJzIHRvIGJlIGNvbnNlY3V0aXZlbHkgam9pbmVkIGluIHRoZSBib3R0b20tdXAgZGVuZG9ncmFtLiBJbiB0aGlzIGNhc2UsIHRoZSDigJxjb21wbGV0ZeKAnSBjcml0ZXJpYSB0YWtlcyBpbnRvIGFjY291bnQgdGhlIG1heGltdW0gZGlzdGFuY2UgYmV0d2VlbiBhbnkgcGFpciBvZiB0ZXJtcyBvZiBib3RoIGNsdXN0ZXJzIHRvIGJlIG1lcmdlZC4gSGVpZ3RoIGluIHRoZSBkZW5kb2dyYW0gZGVub3RlcyB0aGUgKmRpc3RhbmNlKiBiZXR3ZWVuIGEgbWVyZ2VkIHBhaXIgb2YgY2x1c3RlcnMuDQoNCmBgYHtyfQ0KZGlzdF9tYXRyaXggPC0gZGlzdCh0KHNjYWxlKGFzLm1hdHJpeChjb3JwdXNfZHRtXzk3KSkpKQ0KdGVybV9jbHVzdGVyaW5nIDwtIGhjbHVzdChkaXN0X21hdHJpeCwgbWV0aG9kID0gImNvbXBsZXRlIikNCnBsb3QodGVybV9jbHVzdGVyaW5nKQ0KYGBgDQoNCiMjIENsdXN0ZXJpbmcgRG9jdW1lbnRzDQoNCkFub3RoZXIgdHlwZSBvZiBwb3B1bGFyIHRhc2sgaXMgdG8gY29uc3RydWN0IGNsdXN0ZXJzIG9mIHNpbWlsYXIgZG9jdW1lbnRzIGJhc2VkIG9uIHRoZSBmcmVxdWVuY2llcyBvZiB3b3JkIG9jY3VycmVuY2VzLiBIZXJlIHdlIHNlbGVjdCBhIHNtYWxsIHN1YnNldCBvZiB0aGUgaW5pdGlhbCBjb3JwdXMsIDE1IGRvY3VtZW50cyBmcm9tIGVhY2ggY2xhc3MuIFdlIHRoZW4gYXBwbHkgYSBzaW1pbGFyIG1ldGhvZCB0byB0aGUgcHJldmlvdXMgb25lIGFuZCB0cnkgdG8gZGl2aWRlIGRvY3VtZW50cyBpbnRvIHR3byBjbHVzdGVycy4NCg0KYGBge3J9DQpkaXN0X21hdHJpeCA8LSBkaXN0KHNjYWxlKGFzLm1hdHJpeChjb3JwdXNfZHRtXzk5KVsobiAtIDE1KToobiArIDE1KSwgXSkpDQpncm91cHMgPC0gaGNsdXN0KGRpc3RfbWF0cml4LCBtZXRob2QgPSAid2FyZC5EIikNCnBsb3QoZ3JvdXBzLCBjZXggPSAwLjksIGhhbmcgPSAtMSkNCnJlY3QuaGNsdXN0KGdyb3VwcywgayA9IDIpDQpgYGANCg0KIyBEYXRhIFNwbGl0dGluZw0KDQpCZWZvcmUgbGVhcm5pbmcgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3ZSBoYXZlIHRvIGRlZmluZSB0aGUgc3Vic2V0cyBvZiBzYW1wbGVzIChkb2N1bWVudHMpIHRvIHRyYWluIGFuZCB0ZXN0IG91ciBtb2RlbC4gV2UgZmlyc3QgbmVlZCBjcmVhdGUgYSBEYXRhIEZyYW1lIGZyb20gdGhlIERvY3VtZW50IFRlcm0gTWF0cml4Lg0KDQojIyBDcmVhdGUgRGF0YSBGcmFtZQ0KDQpUaGUgMC45OSBzcGFyc2VuZXNzIHZhbHVlIGRvY3VtZW50LXRlcm0gbWF0cml4IGlzIG91ciBzdGFydGluZyBwb2ludC4gVGhpcyBtYXRyaXggaGFzIDE4MSBmZWF0dXJlcywgd2hpY2ggY29ycmVzcG9uZCB0byB0aGUgbW9zIGZyZXF1ZW50IHRlcm1zLiBXZSBmaXJzdCBuZWVkIHRvIGFwcGVuZCB0aGUgY2xhc3MgdmVjdG9yIGFzIHRoZSBsYXN0IGNvbHVtbiBvZiB0aGUgbWF0cml4LiBUaGVyZSBhcmUgMTAwMCBkb2N1bWVudHMgb2YgZWFjaCBjbGFzcywgMjAwMCBkb2N1bWVudHMgaW4gdG90YWwuDQoNCmBgYHtyfQ0KZGltKGNvcnB1c19kdG1fOTkpDQp0eXBlIDwtIGMocmVwKCJjbGFzaHJveWFsZSIsIG4pLCByZXAoImNsYXNob2ZjbGFucyIsIG4pKSAjIGNyZWF0ZSB0aGUgdHlwZSB2ZWN0b3INCmNvcnB1c19kdG1fOTkgPC0gY2JpbmQoY29ycHVzX2R0bV85OSwgdHlwZSkgIyBhcHBlbmQNCmRpbShjb3JwdXNfZHRtXzk5KSAjIGNvbnN1bHQgdGhlIHVwZGF0ZWQgbnVtYmVyIG9mIGNvbHVtbnMNCmBgYA0KDQpUaGlzIG5ldyBtYXRyaXggaXMgdGhlIHN0YXJ0aW5nIHBvaW50IGZvciBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uLiBIb3dldmVyLCB3ZSBmaXJzdCBuZWVkIHRvIGNvbnZlcnQgaXQgdG8gYSBkYXRhZnJhbWUuIFRoZSBuYW1lIG9mIHRoZSBsYXN0IGNvbHVtbiBpcyB1cGRhdGVkLiBBbGwgdGhlIHZhbHVlcyBhcmUgY29udmVydGVkIHRvIG51bWVyaWMgYW5kIHRoZSBsYXN0IGNvbHVtbiBpcyBjb252ZXJ0ZWQgdG8gZmFjdG9yLg0KDQpgYGB7cn0NCmNvcnB1c19kZl85OSA8LSBhcy5kYXRhLmZyYW1lKGFzLm1hdHJpeChjb3JwdXNfZHRtXzk5KSkNCmNvbG5hbWVzKGNvcnB1c19kZl85OSlbdGVybXMgKyAxXSA8LSAidHlwZSINCmNvcnB1c19kZl85OSR0eXBlIDwtIGFzLmZhY3Rvcihjb3JwdXNfZGZfOTkkdHlwZSkNCmNvcnB1c19kZl85OSA8LSBhcy5kYXRhLmZyYW1lKHNhcHBseShjb3JwdXNfZGZfOTksIGFzLm51bWVyaWMpKQ0KY29ycHVzX2RmXzk5W2lzLm5hKGNvcnB1c19kZl85OSldIDwtIDANCmNvcnB1c19kZl85OSR0eXBlIDwtIGFzLmZhY3Rvcihjb3JwdXNfZGZfOTkkdHlwZSkNCmxldmVscyhjb3JwdXNfZGZfOTkkdHlwZSkgPC0gYygiY2xhc2hvZmNsYW5zIiwgImNsYXNocm95YWxlIikNCmBgYA0KDQojIyBDcmVhdGUgRGF0YSBQYXJ0aXRpb24NCg0KVGhlIGBjcmVhdGVEYXRhUGFydGl0aW9uYCBwcm9kdWNlcyBhIHRyYWluLXRlc3QgcGFydGl0aW9uIG9mIG91ciBjb3JwdXMuIFRoaXMgd2lsbCBiZSBtYWludGFpbmVkIGR1cmluZyB0aGUgd2hvbGUgcGlwZWxpbmUgb2YgYW5hbHlzaXMuIFRlc3Qgc2FtcGxlcyB3b24ndCBiZSB1c2VkIGZvciBhbnkgbW9kZWxpbmcgZGVjaXNpb24uIFdlIHdpbGwgb25seSB1c2UgdGhlbSBhdCB0aGUgZW5kIHRvIHByZWRpY3QgdGhlaXIgY2xhc3MgYW5kIGNyZWF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXguIEEgbGlzdCBvZiByYW5kb21seSBzYW1wbGVkIG51bWJlcnMgKGBpbl90cmFpbmApIGlzIHVzZWQgdG8gcGFydGl0aW9uIHRoZSB3aG9sZSBjb3JwdXMuIDc1JSBvZiB0aGUgc2FtcGxlcyBhcmUgdXNlZCBmb3IgdHJhaW5pbmcgYW5kIHRoZSByZW1haW5pbmcgMjUlIGlzIHVzZWQgZm9yIHRlc3RpbmcuDQoNCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDEwNykgIyBhIHJhbmRvbSBzZWVkIHRvIGVuYWJsZSByZXByb2R1Y2liaWxpdHkNCmluX3RyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGNvcnB1c19kZl85OSR0eXBlLCBwID0gLjc1LCBsaXN0ID0gRkFMU0UpDQpzdHIoaW5fdHJhaW4pDQp0cmFpbmluZyA8LSBjb3JwdXNfZGZfOTlbaW5fdHJhaW4sIF0NCnRlc3RpbmcgPC0gY29ycHVzX2RmXzk5Wy1pbl90cmFpbiwgXQ0KbnJvdyh0cmFpbmluZykNCmBgYA0KDQpTaW1pbGFybHksIGBjcmVhdGVSZXNhbXBsZWAgY2FuIGJlIHVzZWQgdG8gbWFrZSBzaW1wbGUgYm9vdHN0cmFwIHNhbXBsZXMuIFRoaXMgY3JlYXRlcyByZXNhbXBsZXMgb2YgdGhlIHNpemUgb2YgdGhlIGNvcnB1cyB3aXRoIHJlcGVhdGVkIGRvY3VtZW50cy4gYGNyZWF0ZUZvbGRzYCBjYW4gYmUgdXNlZCB0byBnZW5lcmF0ZSBiYWxhbmNlZCBjcm9zcy12YWxpZGF0aW9uIGdyb3VwaW5ncyBmcm9tIGEgc2V0IG9mIGRhdGEuDQoNCmBgYHtyfQ0KcmVzYW1wbGVzIDwtIGNyZWF0ZVJlc2FtcGxlKHkgPSBjb3JwdXNfZGZfOTkkdHlwZSkNCnN0cihyZXNhbXBsZXMpDQpgYGANCg0KYGBge3J9DQpmb2xkcyA8LSBjcmVhdGVGb2xkcyh5ID0gY29ycHVzX2RmXzk5JHR5cGUpDQpzdHIoZm9sZHMpDQpgYGANCg0KIyBDbGFzc2lmaWNhdGlvbg0KDQpUaGUgYGNhcmV0YCBbNCwgNV0gcGFja2FnZSBpcyB0aGUgcmVmZXJlbmNlIHRvb2wgZm9yIGJ1aWxkaW5nIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gbW9kZWxzIGluIFIuIEl0IGNvdmVycyBhbGwgdGhlIHN0ZXBzIG9mIGEgY2xhc3NpYyBwaXBlbGluZTogZGF0YSBwcmVwcm9jZXNzaW5nLCBtb2RlbCBidWlsZGluZywgYWNjdXJhY3kgZXN0aW1hdGlvbiwgcHJlZGljdGlvbiBvZiB0aGUgdHlwZSBvZiBuZXcgc2FtcGxlcywgYW5kIHN0YXRpc3RpY2FsIGNvbXBhcmlzb24gYmV0d2VlbiB0aGUgcGVyZm9ybWFuY2Ugb2YgZGlmZmVyZW50IG1vZGVscy4gVGhpcyBjaGVhdHNoZWV0IG9mIGNhcmV0IGlsbHVzdHJhdGVzIGl0cyBtYWluIGZ1bmN0aW9uIGluIGEgc2luZ2xlIHBhZ2U6IGh0dHBzOi8vZ2l0aHViLmNvbS9DQUJBSC9sZWFybmluZ1JyZXNvdXJjZXMvYmxvYi9tYWluL2NoZWF0c2hlZXRzL2NhcmV0LnBkZi4NCg0KT3VyIG9iamVjdGl2ZSBpcyB0byBsZWFybiBhIGNsYXNzaWZpZXIgdGhhdCBwcmVkaWN0cyB0aGUgdHlwZSBvZiBmdXR1cmUgZG9jdW1lbnRzIGJhc2VkIG9uIHRlcm1zIG9jY3VycmVuY2VzLiBXZSBoYXZlIGEgdHdvLWNsYXNzIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gcHJvYmxlbS4NCg0KV2Ugbm93IGNhbiBzdGFydCB0cmFpbmluZyBhbmQgdGVzdGluZyBkaWZmZXJlbnQgc3VwZXJ2aXNlZCBjbGFzc2lmaWNhdGlvbiBtb2RlbHMuIFRoZSBgdHJhaW5gIGZ1bmN0aW9uIGltcGxlbWVudHMgdGhlIGJ1aWxkaW5nIHByb2Nlc3MuDQoNCiogYGZvcm1gIHBhcmFtZXRlciBpcyB1c2VkIHdpdGggdGhlIGV4cHJlc3Npb24gYHR5cGUgfiAuYCB0byBkZW5vdGUgdGhlIHZhcmlhYmxlIHRvIGJlIHByZWRpY3RlZCwgZm9sbG93ZWQgYnkgdGhlIHNldCBvZiBwcmVkaWN0b3JzLiBBIHBvaW50IGluZGljYXRlcyB0aGF0IHRoZSByZXN0IG9mIHZhcmlhYmxlcyBhcmUgdXNlZCBhcyBwcmVkaWN0b3JzLiBgZGF0YWAgcGFyYW1ldGVyIGlzIHVzZWQgZm9yIHRoZSB0cmFpbmluZyBkYXRhLiANCg0KKiBgbWV0aG9kYCBwYXJhbWV0ZXIgZml4ZXMgdGhlIHR5cGUgb2YgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtIHRvIGJlIGxlYXJuZWQuIGBjYXJldGAgc3VwcG9ydHMgbW9yZSB0aGFuIDE1MCBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIGFsZ29yaXRobXMuIFRha2luZyBpbnRvIGFjY291bnQgdGhlIGxhcmdlIGRpbWVuc2lvbmFsaXR5IG9mIGNsYXNzaWMgTkxQIGRhdGFzZXRzLCB3ZSBoYXZlIHRvIHVzZSBjbGFzc2lmaWVycyBjYXBhYmxlIHRvIGRlYWwgd2l0aCB0aGlzLiBJbiB0aGlzIHdvcmsgd2UgY2hvb3NlIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgKExEQSkgYW5kIEJvb3N0ZWQgTG9naXN0aWMgUmVncmVzc2lvbiAoTFIpLg0KDQoqIGBtZXRyaWNgIHBhcmFtZXRlciBmaXhlcyB0aGUgc2NvcmUgdG8gYXNzZXNzLXZhbGlkYXRlcyB0aGUgZ29vZG5lc3Mgb2YgZWFjaCBtb2RlbC4gQSBsYXJnZSBzZXQgb2YgbWV0cmljcyBpcyBvZmZlcmVkIGFuZCB3ZSB0ZXN0IHRoZSBmb2xsb3dpbmcgb25lczogQWNjdXJhY3ksIEthcHBhLCBST0MsIFNlbnNpdGl2aXR5IGFuZCBTcGVjaWZpY2l0eS4NCg0KKiBgdHJDb250cm9sYCBwYXJhbWV0ZXIgZGVmaW5lcyB0aGUgbWV0aG9kIHRvIGVzdGltYXRlIHRoZSBlcnJvciBvZiB0aGUgY2xhc3NpZmllci4gVGhlIGB0cmFpbkNvbnRyb2xgIGZ1bmN0aW9uIGFsbG93cyB0aGUgdXNlIG9mIGRpZmZlcmVudCBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uIHByb2NlZHVyZXMgc3VjaCBhcyBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgYm9vdHN0cmFwcGluZywgZXRjLiBXZSBhcHBseSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgcmVwZWF0ZWQgMyB0aW1lcy4gVGhpcyBpcyBhbiBhZGVxdWF0ZSBvcHRpb24gYmVjYXVzZSBpdCBjcmVhdGVzIDMwIHJlc3VsdHMgdGhhdCBjYW4gbGF0ZXIgYmUgdXNlZCB0byBjb21wYXJlIGFsZ29yaXRobXMgc3RhdGlzdGljYWxseS4NCg0KIyMgTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcw0KDQohW0xpbmVhciBEaXNjaXJtaW5hbnQgQW5hbHlzaXNdKC4uL2ltYWdlcy9saW5lYXJfZGlzY3JpbWluYW50X2FuYWx5c2lzLnBuZykNCg0KTERBIGlzIHVzZWQgdG8gZmluZCBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiBmZWF0dXJlcyB0aGF0IGNoYXJhY3Rlcml6ZXMgb3Igc2VwYXJhdGVzIHR3byBvciBtb3JlIGNsYXNzZXMuIFRoZSByZXN1bHRpbmcgY29tYmluYXRpb24gY2FuIGJlIHVzZWQgYXMgYSBsaW5lYXIgY2xhc3NpZmllciwgb3IgZm9yIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbi4gVGhpcyB0aW1lIHdlIHdpbGwgdXNlIGl0IGFzIGEgY2xhc3NpZmllci4gV2Ugd2lsbCBzZWUgYSBzaW1pbGFyIHVuc3VwZXJ2aXNlZCBtZXRob2QgY2FsbGVkIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgZm9yIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBpbiB0aGUgRmVhdHVyZSBFeHRyYWN0aW9uIHNlY3Rpb24uDQoNCkFjY3VyYWN5IGFuZCBLYXBwYSBhcmUgdGhlIGRlZmF1bHQgbWV0cmljcyB1c2VkIHRvIGV2YWx1YXRlIGFsZ29yaXRobXMgb24gYmluYXJ5IGFuZCBtdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbiBkYXRhc2V0cyBpbiBjYXJldC4gQXMgd2UgaGF2ZSB0byBkbyBiaW5hcnkgY2xhc3NpZmljYXRpb24sIHRoZXNlIG1ldHJpY3MgYXJlIGFkZXF1YXRlLiBPdXIgY2xhc3NlcyBhcmUgY29tcGxldGVseSBiYWxhbmNlZCwgYW5kIHRoYXQgbWFrZXMgYW5hbHlzaW5nIHRoZSBtZXRyaWNzIGVhc2llci4NCg0KQWNjdXJhY3kgaXMgdGhlIHBlcmNlbnRhZ2Ugb2YgY29ycmVjdGx5IGNsYXNzaWZpZXMgaW5zdGFuY2VzIG91dCBvZiBhbGwgaW5zdGFuY2VzLiBJdCBpcyBtb3JlIHVzZWZ1bCBvbiBhIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiB0aGFuIG11bHRpLWNsYXNzIGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zIGJlY2F1c2UgaXQgY2FuIGJlIGxlc3MgY2xlYXIgZXhhY3RseSBob3cgdGhlIGFjY3VyYWN5IGJyZWFrcyBkb3duIGFjcm9zcyB0aG9zZSBjbGFzc2VzLiBUaGlzIGNvdWxkIGJlIHNlZW4gd2l0aCBhIGNvbmZ1c2lvbiBtYXRyaXguDQoNCkthcHBhIGlzIHNpbWlsYXIgdG8gYWNjdXJhY3ksIGJ1dCBpdCBpcyBub3JtYWxpemVkIGF0IHRoZSBiYXNlbGluZSBvZiByYW5kb20gY2hhbmNlIG9uIG91ciBkYXRhc2V0LiBJdCBpcyBhIG1vcmUgdXNlZnVsIG1lYXN1cmUgdG8gdXNlIG9uIHByb2JsZW1zIHRoYXQgaGF2ZSBhbiBpbWJhbGFuY2UgaW4gdGhlIGNsYXNzZXMuIEZvciBleGFtcGxlLCBpbiBhIDcwLTMwIHNwbGl0IGZvciBjbGFzc2VzIDAgYW5kIDEgYW5kIHlvdSBjYW4gYWNoaWV2ZSA3MCUgYWNjdXJhY3kgYnkgcHJlZGljdGluZyBhbGwgaW5zdGFuY2VzIGFyZSBmb3IgY2xhc3MgMC4gQXMgb3VyIGNsYXNzZXMgYXJlIGNvbXBsZXRlbHkgYmFsYW5jZWQsIDUwJSBhY2N1cmFjeSBpcyBvYnRhaW5lZCBieSBwcmVkaWN0aW5nIGFueSBvZiB0aGUgY2xhc3NlcyBmb3IgYWxsIGluc3RhbmNlcy4NCg0KVGhlIG9idGFpbmVkIGFjY3VyYWN5IGlzIG5vdCB2ZXJ5IGdvb2QsIGJ1dCB0aGlzIGlzIGV4cGVjdGVkIGJlY2F1c2UgdGhlIHByb2JsZW0gaXMgbm90IGFuIGVhc3kgb25lLiBUaGUga2FwcGEgbWV0cmljIGFsc28gcmVmbGVjdHMgdGhhdCBvdXIgY2xhc3NpZmllciBpcyBxdWl0ZSBiYWQuDQoNCmBgYHtyfQ0KIyBmaXhpbmcgdGhlIHBlcmZvcm1hbmNlIGVzdGltYXRpb24gcHJvY2VkdXJlDQp0cmFpbl9jdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzKQ0KbGRhXzN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAibGRhIiwgdHJDb250cm9sID0gdHJhaW5fY3RybCkNCmxkYV8zeDEwY3YNCmBgYA0KDQpBbm90aGVyIG1ldHJpYyB0aGF0IGlzIG9ubHkgc3VpdGFibGUgZm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyBpcyBST0MuIFRoZSBhcmVhIHVuZGVyIHRoZSBST0MgY3VydmUgcmVwcmVzZW50cyBhIG1vZGVscyBhYmlsaXR5IHRvIGRpc2NyaW1pbmF0ZSBiZXR3ZWVuIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBjbGFzc2VzLiBBbiBhcmVhIG9mIDEuMCByZXByZXNlbnRzIGEgbW9kZWwgdGhhdCBtYWRlIGFsbCBwcmVkaWN0cyBwZXJmZWN0bHkuIEFuIGFyZWEgb2YgMC41IHJlcHJlc2VudHMgYSBtb2RlbCBhcyBnb29kIGFzIHJhbmRvbS4NCg0KUk9DIGNhbiBiZSBicm9rZW4gZG93biBpbnRvIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eS4gQSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbSBpcyByZWFsbHkgYSB0cmFkZS1vZmYgYmV0d2VlbiBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHkuIFNlbnNpdGl2aXR5IGlzIHRoZSB0cnVlIHBvc2l0aXZlIHJhdGUgYWxzbyBjYWxsZWQgdGhlIHJlY2FsbC4gSXQgaXMgdGhlIG51bWJlciBpbnN0YW5jZXMgZnJvbSB0aGUgcG9zaXRpdmUgKGZpcnN0KSBjbGFzcyB0aGF0IGFjdHVhbGx5IHByZWRpY3RlZCBjb3JyZWN0bHkuIFNwZWNpZmljaXR5IGlzIGFsc28gY2FsbGVkIHRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUuIElzIHRoZSBudW1iZXIgb2YgaW5zdGFuY2VzIGZyb20gdGhlIG5lZ2F0aXZlIChzZWNvbmQpIGNsYXNzIHRoYXQgd2VyZSBhY3R1YWxseSBwcmVkaWN0ZWQgY29ycmVjdGx5Lg0KDQpUbyB1c2UgdGhpcyBtZXRyaWMgd2UgaGF2ZSB0byBzZWxlY3QgaXQgaW4gdGhlIGZ1bmN0aW9uIHBhcmFtZXRlcnMuIE1vcmVvdmVyLCBleHRyYSBwYXJhbWV0ZXJzIG11c3QgYmUgYWRkZWQgdG8gdGhlIGB0cmFpbkNvbnRyb2xgIGZ1bmN0aW9uLiBJbiBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbXMgdGhlIGB0d29DbGFzc1N1bW1hcnlgIG9wdGlvbiBkaXNwbGF5cyBhcmVhIHVuZGVyIHRoZSBST0MgY3VydmUsIHNlbnNpdGl0eSBhbmQgc3BlY2lmaWNpdHkgbWV0cmljcy4gVG8gZG8gc28sIGFjdGl2YXRpbmcgdGhlIGBjbGFzc1Byb2JzYCBvcHRpb24gaXMgYWxzbyBuZWVkZWQsIHdoaWNoIHNhdmVzIHRoZSBjbGFzcyBwcm9iYWJpbGl0aWVzIHRoYXQgdGhlIGNsYXNzaWZpZXIgYXNzaWducyB0byBlYWNoIHNhbXBsZS4NCg0KTG9va2luZyBhdCB0aGVzZSBudW1iZXJzLCB3ZSByZWFsaXNlIHRoYXQgdGhlIHNlY29uZCBjbGFzcyBpcyBwcmVkaWN0ZWQgY29ycmVjdGx5IG1vcmUgdGltZXMgdGhhbiB0aGUgZmlyc3Qgb25lLiBUaGUgZmlyc3QgY2xhc3MgaXMgcHJlZGljdGVkIGNvcnJlY3RseSA2NyUgb2YgdGhlIHRpbWVzIGFuZCB0aGUgc2Vjb25kIG9uZSA5MCUgb2YgdGhlIHRpbWVzLiBUaGlzIHdpbGwgYWxzbyBiZSBldmlkZW50IGlmIHdlIGNhbGN1bGF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXggd2hlbiB0ZXN0aW5nIHRoZSBtb2RlbC4NCg0KYGBge3J9DQpsaWJyYXJ5KHBST0MpDQp0cmFpbl9jdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzLCBjbGFzc1Byb2JzID0gVFJVRSwgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5KQ0KbGRhX3JvY18zeDEwY3YgPC0gdHJhaW4odHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gImxkYSIsIG1ldHJpYz0iUk9DIiwgdHJDb250cm9sID0gdHJhaW5fY3RybCkNCmxkYV9yb2NfM3gxMGN2DQpgYGANCg0KIyMgQm9vc3RlZCBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCiFbTG9naXN0aWMgUmVncmVzc2lvbl0oLi4vaW1hZ2VzL2xvZ2lzdGljX3JlZ3Jlc3Npb24ucG5nKQ0KDQpMb2dpc3RpYyBSZWdyZXNzaW9uIGlzIHVzZWQgdG8gbW9kZWwgdGhlIHByb2JhYmlsaXR5IG9mIGEgY2VydGFpbiBjbGFzcy4gSXQgdXNlcyBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiBpbmRlcGVuZGVudCB2YXJpYWJsZXMsIGFuZCBhcHBsaWVzIHRoZSBsb2dpc3RpYyBmdW5jdGlvbiBhdCB0aGUgZW5kIHRvIG9idGFpbiBwcm9iYWJpbGl0aWVzLiBJZiB3ZSBkZWZpbmUgYSBjdXQtb2ZmIHByb2JhYmlsaXR5LCBpdCBjYW4gYmUgdXNlZCBhcyBhIGJpbmFyeSBvciBtdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbiBtb2RlbC4gQm9vc3RlZCBMUiBpcyBhbiBhZGRpdGl2ZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLiBJdCB1c2VzIGFuZCBlbnNlbWJsZSBvZiBzaW1pbGFyIExSIG1vZGVscyB0byBtYWtlIHByZWRpY3Rpb25zLg0KDQpXaGlsZSB0aGUgbGluZWFyIExEQSBjbGFzc2lmaWVyIGRvZXMgbm90IGhhdmUgcGFyYW1ldGVycywgTFIgaGFzIHRoZSBgbkl0ZXJgIGtleSBwYXJhbWV0ZXIuIFRoaXMgcGFyYW1ldGVyIGluZGljYXRlcyB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbnMgb2YgdGhlIExvZ2lzdGljIFJlZ3Jlc3Npb24gbW9kZWwuIEJ5IGRlZmF1bHQsIHdpdGhvdXQgY2hhbmdpbmcgdGhlIHZhbHVlIG9mIHRoZSBwYXJhbWV0ZXIsIGBjYXJldGAgZXZhbHVhdGVzIDMgbW9kZWxzLiBUaGUgYHR1bmVMZW5ndGhgIG9wdGlvbiBvZiB0aGUgYHRyYWluYCBmdW5jdGlvbiBmaXhlcyB0aGUgbnVtYmVyIG9mIHZhbHVlcyBvZiBlYWNoIHBhcmFtZXRlciB0byBiZSBjaGVja2VkLiBGb3IgZXhhbXBsZSwgaWYgdGhlIGNsYXNzaWZpZXIgaGFzIDIgcGFyYW1ldGVycyBhbmQgdGhlIGB0dW5lTGVuZ3RoYCBwYXJhbWV0ZXIgaXMgbm90IGNoYW5nZWQsIDMgeCAzID0gOSBtb2RlbHMgYXJlIGV2YWx1YXRlZC4NCg0KYGBge3J9DQp0cmFpbl9jdHJsIDwtIHRyYWluQ29udHJvbCgNCiAgICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzDQopDQpscl8zeDEwY3YgPC0gdHJhaW4odHlwZSB+IC4sDQogICAgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAiTG9naXRCb29zdCIsIHRyQ29udHJvbCA9IHRyYWluX2N0cmwNCikNCmxyXzN4MTBjdg0KcGxvdChscl8zeDEwY3YpDQpgYGANCg0KSWYgd2UgaW5jcmVhc2UgdGhlIGB0dW5lTGVuZ3RoYCB0byBgMTVgIHdlIGNhbiBldmFsdWF0ZSBtb3JlIG1vZGVscywgYW5kIGNoZWNrIGlmIHRoZSBhY2N1cmFjeSBpbmNyZWFzZXMuIFdlIGNhbiBzZWUgdGhhdCB0aGUgYWNjdXJhY3kgaW1wcm92ZXMgdXAgdG8gc29tZSBwb2ludCBhbmQgdGhlbiBpdCBpcyBuZWFybHkgY29uc3RhbnQuIFRoZXJlZm9yZSwgaXQgaXMgbm90IHdvcnRoIHRvIGluY3JlYXNlIHRoZSB2YWx1ZSBvZiBgbkl0ZXJgIA0KDQpgYGB7cn0NCnRyYWluX2N0cmwgPC0gdHJhaW5Db250cm9sKA0KICAgIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMNCikNCmxyX3R1bmVsXzN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwNCiAgICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJMb2dpdEJvb3N0IiwgdHJDb250cm9sID0gdHJhaW5fY3RybCwgdHVuZUxlbmd0aCA9IDE1DQopDQpscl90dW5lbF8zeDEwY3YNCnBsb3QobHJfdHVuZWxfM3gxMGN2KQ0KYGBgDQoNCldlIGNhbiBhbHNvIHRyeSB0aGUgUk9DIG1ldHJpYyB0byBoYXZlIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHBlcmZvcm1hbmNlIG9mIG91ciBtb2RlbC4gV2UgZ2V0IHNpbWlsYXIgcmVzdWx0cyB0byB0aGUgTERBIGNsYXNzaWZpZXIsIHdpdGggYSBtdWNoIGhpZ2hlciBTcGVjaWZpY2l0eSB0aGFuIFNlbnNpdGl2aXR5Lg0KDQpgYGB7cn0NCnRyYWluX2N0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMsIGNsYXNzUHJvYnMgPSBUUlVFLCBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkpDQpscl9yb2NfM3gxMGN2IDwtIHRyYWluKHR5cGUgfiAuLCBkYXRhPXRyYWluaW5nLCBtZXRob2Q9IkxvZ2l0Qm9vc3QiLCB0ckNvbnRyb2w9dHJhaW5fY3RybCwgbWV0cmljPSJST0MiLCB0dW5lTGVuZ3RoPTE1KQ0KbHJfcm9jXzN4MTBjdg0KcGxvdChscl9yb2NfM3gxMGN2KQ0KYGBgDQoNClRoZSBgdHVuZUdyaWRgIG9wdGlvbiBvZmZlcnMgdGhlIHBvc3NpYmlsaXR5IHRvIHNlbGVjdCBhbW9uZyBhIHNldCBvZiB2YWx1ZXMgdG8gYmUgdHVuZWQtdGVzdGVkLg0KDQpgYGB7cn0NCnRyYWluX2N0cmwgPC0gdHJhaW5Db250cm9sKA0KICAgIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMNCikNCnR1bmVfZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgbkl0ZXIgPSBzZXEoMTAwLCAxMjAsIDIpDQopDQpscl90dW5lZ18zeDEwY3YgPC0gdHJhaW4odHlwZSB+IC4sDQogICAgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAiTG9naXRCb29zdCIsIHRyQ29udHJvbCA9IHRyYWluX2N0cmwsIHR1bmVHcmlkID0gdHVuZV9ncmlkDQopDQpscl90dW5lZ18zeDEwY3YNCnBsb3QobHJfdHVuZWdfM3gxMGN2KQ0KYGBgDQoNCiMgU3Vic2FtcGxpbmcNCg0KT3VyIGluaXRpYWwgY29ycHVzIGlzIGNvbXBsZXRlbHkgYmFsYW5jZWQsIGl0IGhhcyAxMDAwIHNhbXBsZXMgb2YgZWFjaCBjbGFzcy4gSG93ZXZlciwgd2UgY2FuIGNyZWF0ZSBhbiB1bmJhbGFuY2VkIGNvcnB1cyBieSByZW1vdmluZyBzb21lIHNhbXBsZXMuIEZvciBleGFtcGxlLCB3ZSBjYW4gY3JlYXRlIGEgY29ycHVzIHRoYXQgaGFzIDEwMDAgc2FtcGxlcyBvZiBvbmUgY2xhc3MgYW5kIDI1MCBmcm9tIHRoZSBvdGhlciBjbGFzcy4gSWYgY2xhc3MtbGFiZWwgZGlzdHJpYnV0aW9ucyBhcmUgdW5iYWxhbmNlZCBpbiBvdXIgY29ycHVzLCBhIHJlc2FtcGxpbmcgbWV0aG9kIHdpbGwgdHJ5IHRvIGltcHJvdmUgdGhlIHJlY292ZXJ5IHJhdGUgaW4gdGhlIG1pbm9yaXR5IGNsYXNzLg0KDQpUaGlzIHRlc3Qgd2lsbCBvbmx5IGJlIHBlcmZvcm1lZCB3aXRoIHRoZSBMUiBjbGFzc2lmaWVyLiBGaXJzdCwgYSBub3JtYWwgY2xhc3NpZmllciB3aWxsIGJlIHRyYWluZWQuIFRoZW4gbXVsdGlwbGUgcmVzYW1wbGluZyBtZXRob2RzIHdpbGwgYmUgdGVzdGVkIGFuZCBjb21wYXJlZCB3aXRoIHRoZSBiYXNlIGNsYXNzaWZpZXIuIFJPQyBpcyBhbiBhZGVxdWVhdGUgbWV0cmljIGluIHRoaXMgY2FzZSBiZWNhdXNlIHdlIGNhbiBjb21wYXJlIHRoZSBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHkgZm9yIGVhY2ggc3Vic2FtcGxpbmcgbWV0aG9kLg0KDQpXZSBleHBlY3QgdG8gaGF2ZSB2ZXJ5IGhpZ2ggc3BlY2lmaWNpdHkgYnV0IGxvdyBzZW5zaXRpdml0eS4gVGhlcmVmb3JlLCBvdXIgYWltIGlzIHRvIGluY3JlYXNlIHNlbnNpc3Rpdml0eS4gRG93bnNhbXBsaW5nIGFuZCB1cHNhbXBsaW5nIGltcHJvdmUgdGhlIHNlbnNpdGl2aXR5IGEgYml0IGFuZCB0aGUgaHlicmlkIG1ldGhvZCBnZXRzIHdvcnNlIHJlc3VsdHMuDQoNCmBgYHtyfQ0KY29ycHVzX2RmXzk5X3VuID0gY29ycHVzX2RmXzk5WzE6KG4rbi80KSwgXQ0KaW5fdHJhaW5fdW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gY29ycHVzX2RmXzk5X3VuJHR5cGUsIHAgPSAuNzUsIGxpc3QgPSBGQUxTRSkNCnN0cihpbl90cmFpbl91bikNCnRyYWluaW5nX3VuIDwtIGNvcnB1c19kZl85OVtpbl90cmFpbl91biwgXQ0KdGVzdGluZ191biA8LSBjb3JwdXNfZGZfOTlbLWluX3RyYWluX3VuLCBdDQpgYGANCg0KYGBge3J9DQp0cmFpbl9jdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzLCBjbGFzc1Byb2JzPVRSVUUsIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnkpDQpsZGFfdW5fM3gxMGN2IDwtIHRyYWluKHR5cGUgfiAuLCBkYXRhID0gdHJhaW5pbmdfdW4sIG1ldGhvZCA9ICJMb2dpdEJvb3N0IiwgbWV0cmljPSJST0MiLCB0ckNvbnRyb2wgPSB0cmFpbl9jdHJsKQ0KbGRhX3VuXzN4MTBjdg0KYGBgDQoNCiMjIERvd25zYW1wbGluZw0KDQpEb3duc2FtcGxpbmcgcmFuZG9tbHkgc3Vic2V0cyBhbGwgdGhlIGNsYXNzZXMgaW4gdGhlIHRyYWluaW5nIHNldCBzbyB0aGF0IHRoZWlyIGNsYXNzIGZyZXF1ZW5jaWVzIG1hdGNoIHRoZSBsZWFzdCBwcmV2YWxlbnQgY2xhc3MuIEZvciBleGFtcGxlLCBzdXBwb3NlIHRoYXQgODAlIG9mIHRoZSB0cmFpbmluZyBzZXQgc2FtcGxlcyBhcmUgdGhlIGZpcnN0IGNsYXNzIGFuZCB0aGUgcmVtYWluaW5nIDIwJSBhcmUgaW4gdGhlIHNlY29uZCBjbGFzcy4gRG93bi1zYW1wbGluZyB3b3VsZCByYW5kb21seSBzYW1wbGUgdGhlIGZpcnN0IGNsYXNzIHRvIGJlIHRoZSBzYW1lIHNpemUgYXMgdGhlIHNlY29uZCBjbGFzcyAoc28gdGhhdCBvbmx5IDQwJSBvZiB0aGUgdG90YWwgdHJhaW5pbmcgc2V0IGlzIHVzZWQgdG8gZml0IHRoZSBtb2RlbCkuDQoNCmBgYHtyfQ0KdHJhaW5fY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMywgY2xhc3NQcm9icz1UUlVFLCBzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5LCBzYW1wbGluZz0iZG93biIpDQpsZGFfZG93bl8zeDEwY3YgPC0gdHJhaW4odHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZ191biwgbWV0aG9kID0gIkxvZ2l0Qm9vc3QiLCBtZXRyaWM9IlJPQyIsIHRyQ29udHJvbCA9IHRyYWluX2N0cmwpDQpsZGFfZG93bl8zeDEwY3YNCmBgYA0KDQojIyBVcHNhbXBsaW5nDQoNClVwc2FtcGxpbmcgcmFuZG9tbHkgc2FtcGxlcyB0aGUgbWlub3JpdHkgY2xhc3MgdG8gYmUgdGhlIHNhbWUgc2l6ZSBhcyB0aGUgbWFqb3JpdHkgY2xhc3MuDQoNCmBgYHtyfQ0KdHJhaW5fY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMywgY2xhc3NQcm9icz1UUlVFLCBzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5LCBzYW1wbGluZz0idXAiKQ0KbGRhX3VwXzN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nX3VuLCBtZXRob2QgPSAiTG9naXRCb29zdCIsIG1ldHJpYz0iUk9DIiwgdHJDb250cm9sID0gdHJhaW5fY3RybCkNCmxkYV91cF8zeDEwY3YNCmBgYA0KDQojIyBIeWJyaWQNCg0KQW4gaHlicmlkIG1ldGhvZCBkb3duc2FtcGxlcyB0aGUgbWFqb3JpdHkgY2xhc3MgYW5kIHN5bnRoZXNpemVzIG5ldyBkYXRhIHBvaW50cyBpbiB0aGUgbWlub3JpdHkgY2xhc3MuDQoNCmBgYHtyfQ0KdHJhaW5fY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMywgY2xhc3NQcm9icz1UUlVFLCBzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5LCBzYW1wbGluZz0ic21vdGUiKQ0KbGRhX3Ntb3RlXzN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nX3VuLCBtZXRob2QgPSAiTG9naXRCb29zdCIsIG1ldHJpYz0iUk9DIiwgdHJDb250cm9sID0gdHJhaW5fY3RybCkNCmxkYV9zbW90ZV8zeDEwY3YNCmBgYA0KDQojIEZlYXR1cmUgU2VsZWN0aW9uDQoNCk1vc3QgYXBwcm9hY2hlcyBmb3IgcmVkdWNpbmcgdGhlIG51bWJlciBvZiBmZWF0dXJlcyBjYW4gYmUgcGxhY2VkIGludG8gdHdvIG1haW4gY2F0ZWdvcmllczogd3JhcHBlcnMgYW5kIGZpbHRlcnMuDQoNCldyYXBwZXIgbWV0aG9kcyBldmFsdWF0ZSBtdWx0aXBsZSBtb2RlbHMgdXNpbmcgcHJvY2VkdXJlcyB0aGF0IGFkZCBhbmQvb3IgcmVtb3ZlIHByZWRpY3RvcnMgdG8gZmluZCB0aGUgb3B0aW1hbCBjb21iaW5hdGlvbiB0aGF0IG1heGltaXplcyBtb2RlbCBwZXJmb3JtYW5jZS4gSW4gZXNzZW5jZSwgd3JhcHBlciBtZXRob2RzIGFyZSBzZWFyY2ggYWxnb3JpdGhtcyB0aGF0IHRyZWF0IHRoZSBwcmVkaWN0b3JzIGFzIHRoZSBpbnB1dHMgYW5kIHV0aWxpemUgbW9kZWwgcGVyZm9ybWFuY2UgYXMgdGhlIG91dHB1dCB0byBiZSBvcHRpbWl6ZWQuDQoNCkZpbHRlciBtZXRob2RzIGV2YWx1YXRlIHRoZSByZWxldmFuY2Ugb2YgdGhlIHByZWRpY3RvcnMgb3V0c2lkZSBvZiB0aGUgcHJlZGljdGl2ZSBtb2RlbHMgYW5kIHN1YnNlcXVlbnRseSBtb2RlbCBvbmx5IHRoZSBwcmVkaWN0b3JzIHRoYXQgcGFzcyBzb21lIGNyaXRlcmlvbi4gRWFjaCBwcmVkaWN0b3IgaXMgZWNhbHVhdGVkIGluZGl2aWR1YWxseSB0byBjaGVjayBpZiB0aGVyZSBpcyBhIHBsYXVzaWJsZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBpdCBhbmQgdGhlIG9ic2VydmVkIGNsYXNzZXMuIE9ubHkgcHJlZGljdG9ycyB3aXRoIGltcG9ydGFudCByZWxhdGlvbnNoaXBzIHdvdWxkIHRoZW4gYmUgaW5jbHVkZWQgaW4gYSBjbGFzc2lmaWNhdGlvbiBtb2RlbC4NCg0KVGhlIGZ1bmN0aW9ucyBhcmUgYXBwbGllZCB0byB0aGUgZW50aXJlIHRyYWluaW5nIHNldCBhbmQgYWxzbyB0byBkaWZmZXJlbnQgcmVzYW1wbGVkIHZlcnNpb25zIG9mIHRoZSBkYXRhIHNldC4gRnJvbSB0aGlzLCBnZW5lcmFsaXphYmxlIGVzdGltYXRlcyBvZiBwZXJmb3JtYW5jZSBjYW4gYmUgY29tcHV0ZWQgdGhhdCBwcm9wZXJseSB0YWtlIGludG8gYWNjb3VudCB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gc3RlcC4NCg0KSW4gb3VyIGNhc2Ugd2Ugd2lsbCB0ZXN0IFVuaXZhcmlhdGUgRmlsdGVyIGFuZCAyIHdyYXBwZXIgbWV0aG9kczogUmVjdXJzaXZlIEZlYXR1cmUgRWxpbWluYXRpb24gYW5kIFNpbXVsYXRlZCBBbm5lYWxpbmcuIFdlIHdpbGwgYXBwbHkgdGhlc2UgbWV0aG9kcyB0byBib3RoIGNsYXNzaWZpZXJzIGFuZCB3ZSB3aWxsIGNvbXBhcmUgdGhlIHJlc3VsdHMgYXQgdGhlIGVuZC4NCg0KIyMgVW5pdmFyaWF0ZSBGaWx0ZXINCg0KUHJlZGljdG9ycyBjYW4gYmUgZmlsdGVyZWQgYnkgY29uZHVjdGluZyBzb21lIHNvcnQgb2Ygc2FtcGxlIHRlc3QgdG8gc2VlIGlmIHRoZSBtZWFuIG9mIHRoZSBwcmVkaWN0b3IgaXMgZGlmZmVyZW50IGJldHdlZW4gdGhlIGNsYXNzZXMuIFByZWRpY3RvcnMgdGhhdCBoYXZlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgY2xhc3NlcyBhcmUgdGhlbiB1c2VkIGZvciBtb2RlbGluZy4NCg0KT24gYXZlcmFnZSwgbGVzcyB0aGFuIDgwIHZhcmlhYmxlcyBhcmUgc2VsZWN0ZWQgYW5kIHRoZSBhY2N1cmFjeSBvZiB0aGUgY2xhc3NpZmllcnMgaXMgaW1wcm92ZWQuIFRoZXJlZm9yZSwgdGhpcyBtZXRob2QgaXMgYSBncmVhdCBvcHRpb24gaW4gdGhpcyBjYXNlLg0KDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0Kc2JmX2N0cmwgPC0gc2JmQ29udHJvbChmdW5jdGlvbnMgPSByZlNCRiwgbWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMykNCnRyYWluX2N0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMsIGNsYXNzUHJvYnMgPSBUUlVFKQ0KbHJfc2JmXzN4MTBjdiA8LSBzYmYodHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gIkxvZ2l0Qm9vc3QiLCB0ckNvbnRyb2wgPSB0cmFpbl9jdHJsLCBzYmZDb250cm9sID0gc2JmX2N0cmwpDQpscl9zYmZfM3gxMGN2DQpsZGFfc2JmXzN4MTBjdiA8LSBzYmYodHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gImxkYSIsIHRyQ29udHJvbCA9IHRyYWluX2N0cmwsIHNiZkNvbnRyb2wgPSBzYmZfY3RybCkNCmxkYV9zYmZfM3gxMGN2DQpgYGANCg0KIyMgUmVjdXJzaXZlIEZlYXR1cmUgRWxpbWluYXRpb24NCg0KRmlyc3QsIHRoZSBhbGdvcml0aG0gZml0cyB0aGUgbW9kZWwgdG8gYWxsIHByZWRpY3RvcnMuIEVhY2ggcHJlZGljdG9yIGlzIHJhbmtlZCB1c2luZyBpdOKAmXMgaW1wb3J0YW5jZSB0byB0aGUgbW9kZWwuIEF0IGVhY2ggaXRlcmF0aW9uIG9mIGZlYXR1cmUgc2VsZWN0aW9uLCB0aGUgdG9wIHJhbmtlZCBwcmVkaWN0b3JzIGFyZSByZXRhaW5lZCwgdGhlIG1vZGVsIGlzIHJlZml0IGFuZCBwZXJmb3JtYW5jZSBpcyBhc3Nlc3NlZC4gVGhlIG51bWJlciBvZiBwcmVkaWN0b3JzIHdpdGggdGhlIGJlc3QgcGVyZm9ybWFuY2UgaXMgZGV0ZXJtaW5lZCBhbmQgdGhlIHRvcCBwcmVkaWN0b3JzIGFyZSB1c2VkIHRvIGZpdCB0aGUgZmluYWwgbW9kZWwuIEluIHRoaXMgY2FzZSA0LCA4LCAxNiBhbmQgMTgxIHByZWRpY3RvcnMgYXJlIHRlc3RlZC4NCg0KVGhlIGFjY3VyYWN5IG9mIHRoZSBjbGFzc2lmaWVycyBpcyBpbXByb3ZlZC4gVGhlcmVmb3JlLCB0aGlzIG1ldGhvZCBpcyBhbHNvIGEgZ3JlYXQgb3B0aW9uIGluIHRoaXMgY2FzZS4NCg0KYGBge3J9DQpyZmVfY3RybCA8LSByZmVDb250cm9sKGZ1bmN0aW9ucyA9IHJmRnVuY3MsIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMpDQp0cmFpbl9jdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzKQ0KbHJfcmZlXzN4MTBjdiA8LSByZmUodHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gIkxvZ2l0Qm9vc3QiLCB0ckNvbnRyb2wgPSB0cmFpbl9jdHJsLCByZmVDb250cm9sID0gcmZlX2N0cmwpDQpscl9yZmVfM3gxMGN2DQpsZGFfcmZlXzN4MTBjdiA8LSByZmUodHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gImxkYSIsIHRyQ29udHJvbCA9IHRyYWluX2N0cmwsIHJmZUNvbnRyb2wgPSByZmVfY3RybCkNCmxkYV9yZmVfM3gxMGN2DQpgYGANCg0KIyMgU2ltdWxhdGVkIEFubmVhbGluZw0KDQpTaW11bGF0ZWQgYW5uZWFsaW5nIGlzIGEgZ2xvYmFsIHNlYXJjaCBtZXRob2QgdGhhdCBtYWtlcyBzbWFsbCBwZXJ0dXJiYXRpb25zIHRvIGFuIGluaXRpYWwgY2FuZGlkYXRlIHNvbHV0aW9uLiBJZiB0aGUgcGVyZm9ybWFuY2UgdmFsdWUgZm9yIHRoZSBwZXJ0dXJiZWQgdmFsdWUgaXMgYmV0dGVyIHRoYW4gdGhlIHByZXZpb3VzIHNvbHV0aW9uLCB0aGUgbmV3IHNvbHV0aW9uIGlzIGFjY2VwdGVkLiBJZiBub3QsIGFuIGFjY2VwdGFuY2UgcHJvYmFiaWxpdHkgaXMgZGV0ZXJtaW5lZCBiYXNlZCBvbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gcGVyZm9ybWFuY2UgdmFsdWVzIGFuZCB0aGUgY3VycmVudCBpdGVyYXRpb24gb2YgdGhlIHNlYXJjaC4gSW4gdGhlIGNvbnRleHQgb2YgZmVhdHVyZSBzZWxlY3Rpb24sIGEgc29sdXRpb24gaXMgYSBiaW5hcnkgdmVjdG9yIHRoYXQgZGVzY3JpYmVzIHRoZSBjdXJyZW50IHN1YnNldC4gVGhlIHN1YnNldCBpcyBwZXJ0dXJiZWQgYnkgcmFuZG9tbHkgY2hhbmdpbmcgYSBzbWFsbCBudW1iZXIgb2YgbWVtYmVycyBpbiB0aGUgc3Vic2V0Lg0KDQpVc2luZyB0aGlzIG1ldGhvZCB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVscyBkZWNyZWFzZXMgYSBsb3QsIHNvIGl0IGlzIG5vdCBhIGdvb2Qgb3B0aW9uLg0KDQpgYGB7cn0NCnNhZnNfY3RybCA8LSBzYWZzQ29udHJvbChmdW5jdGlvbnMgPSBjYXJldFNBLCBtZXRob2QgPSAicmVwZWF0ZWRjdiIsIHJlcGVhdHMgPSAzKQ0KdHJhaW5fY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMykNCmxyX3NhZnNfM3gxMGN2IDwtIHNhZnMoeCA9IHRyYWluaW5nWywgLW5jb2wodHJhaW5pbmcpXSwgeSA9IHRyYWluaW5nJHR5cGUsIG1ldGhvZCA9ICJMb2dpdEJvb3N0IiwgdHJDb250cm9sID0gdHJhaW5fY3RybCwgc2Fmc0NvbnRyb2wgPSBzYWZzX2N0cmwpDQpscl9zYWZzXzN4MTBjdg0KbGRhX3NhZnNfM3gxMGN2IDwtIHNhZnMoeCA9IHRyYWluaW5nWywgLW5jb2wodHJhaW5pbmcpXSwgeSA9IHRyYWluaW5nJHR5cGUsIG1ldGhvZCA9ICJsZGEiLCB0ckNvbnRyb2wgPSB0cmFpbl9jdHJsLCBzYWZzQ29udHJvbCA9IHNhZnNfY3RybCkNCmxkYV9zYWZzXzN4MTBjdg0KYGBgDQoNCiMgRmVhdHVyZSBFeHRyYWN0aW9uDQoNClVubGlrZSBmZWF0dXJlIHNlbGVjdGlvbiwgc2V0IG9mIG5ldyBmZWF0dXJlcyBpcyBjb25zdHJ1Y3RlZCBmcm9tIG9yaWdpbmFsIG9uZXMsIHdoaWNoIGFyZSBjb21tb25seSBsaW5lYXIgY29tYmluYXRpb25zIG9mIG9yaWdpbmFsIG9uZXMuIFRoZXJlIGFyZSBtdWx0aXBsZSBtZXRob2RzIHRvIGRvIGZlYXR1cmUgZXh0cmFjdGlvbiBzdWNoIGFzIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgYW5kIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgKExEQSkuIFVubGlrZSBQQ0EsIExEQSBpcyBhIHN1cGVydmlzZWQgbWV0aG9kIHRoYXQgY2FuIGFsc28gYmUgdXNlZCBmb3IgY2xhc3NpZmljYXRpb24uIFRoaXMgdGltZSB3ZSB3aWxsIG9ubHkgYXBwbHkgUENBIHRvIGJvdGggY2xhc3NpZmllcnMsIGJlY2F1c2Ugb25lIG9mIG91ciBjbGFzc2lmaWVycyBpcyBMREEuDQoNCiMjIFN1bW1hcnkgVGFibGUNCg0KSXQgaXMgZWFzeSB0byBsZWFybiBhIFBDQSBpbiBSIHdpdGggdGhlIGBwcmNvbXBgIGZ1bmN0aW9uLiBGaXJzdCwgd2Ugd2lsbCBwcmludCB0aGUgc3VtbWFyeSBvZiB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMuIFdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgMTgxIHByaW5jaXBhbCBjb21wb25lbnRzLiBUaGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgYXJlIG5vdCB2ZXJ5IGdvb2QsIHRoZWlyIHByb3BvcnRpb24gb2YgdmFyaWFuY2UgaXMgZ2VuZXJhbGx5IHZlcnkgbG93LiBXZSB3b3VsZCBoYXZlIHRvIHNlbGVjdCBtYW55IHByaW5jaXBhbCBjb21wb25lbnRzIHRvIGdldCBhIGhpZ2ggcHJvcG9ydGlvbiBvZiB2YXJpYW5jZS4NCg0KYGBge3J9DQpwY2FfcmVzIDwtIHByY29tcChzY2FsZSh0cmFpbmluZ1ssIC1uY29sKHRyYWluaW5nKV0pKQ0Kc3VtbWFyeShwY2FfcmVzKQ0KYGBgDQoNCiMjIFZhcmlhbmNlIFBsb3RzDQoNCldlIGNhbiB2aXN1YWxpemUgdGhlIHByZXZpb3VzIHZhbHVlcyBpbiBkaWZmZXJlbnQgcGxvdHMgdG8gZ2V0IGFiZXR0ZXIgaWRlYSBvZiB0aGUgdmFyaWFuY2Ugb2YgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzLg0KDQpgYGB7cn0NCnBjYV9yZXNfdmFyIDwtIHBjYV9yZXMkc2RldiBeIDINCnBjYV9yZXNfcHZhciA8LSBwY2FfcmVzX3Zhci9zdW0ocGNhX3Jlc192YXIpDQoNCnBsb3QocGNhX3Jlc19wdmFyLHhsYWI9IlByaW5jaXBhbCBjb21wb25lbnQiLCB5bGFiPSJQcm9wb3J0aW9uIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCIsIHlsaW09YygwLDEpLCB0eXBlPSdiJykNCnBsb3QoY3Vtc3VtKHBjYV9yZXNfcHZhcikseGxhYj0iUHJpbmNpcGFsIGNvbXBvbmVudCIsIHlsYWI9IkN1bXVsYXRpdmUgUHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQiLCB5bGltPWMoMCwxKSwgdHlwZT0nYicpDQpzY3JlZXBsb3QocGNhX3Jlcyx0eXBlPSJsIikNCmBgYA0KDQojIyBNYWluIENvbXBvbmVudHMgUGxvdA0KDQpXZSB2aXN1YWxpemUgaW4gYSAyLUQgZ3JhcGggdHdvIGZpcnN0IGNvbXBvbmVudHMsIHRob3NlIHRoYXQgc2F2ZSBsYXJnZXIgdmFyaWFiaWxpdHkgb2Ygb3JpZ2luYWwgZGF0YS4gVGhlIGFpbSBpcyB0byBmaW5kIGFuIGludHVpdGl2ZSBzZXBhcmF0aW9uIG9mIHByb2JsZW0gY2xhc3Nlcy4gQXMgZXhwZWN0ZWQsIHRoZXJlIGlzIG5vIGNsZWFyIHNlcGFyYXRpb24gYmV0d2VlbiB0aGUgY2xhc3Nlcy4gVGhlIHZhcmlhbmNlIG9mIHRoZSBwcmluY2lwYWwgY29tcG9uZW50cyBpcyB0b28gbG93IHRvIGRlY2lkZSB0aGUgdHdvIGNsYXNzZXMuDQoNCmBgYHtyfQ0KcGxvdChtYWluPSJQcmluY2lwYWwgQ29tcG9uZW50cyIsIHBjYV9yZXMkeFssMToyXSwgY29sID0gdHJhaW5pbmckdHlwZSkNCmBgYA0KDQojIyBDbGFzc2lmaWNhdGlvbg0KDQpGaW5hbGx5LCB3ZSBjYW4gdXNlIGBjYXJldGAgdG8gdGVzdCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHR3byBtb2RlbHMgaWYgd2UgYXBwbHkgUENBIGFzIGEgcHJlcHJvY2Vzc2luZyBvcHRpb24uIFRoZSBgcHJlUHJvY2Vzc2AgcGFyYW1ldGVyIGRlZmluZXMgdGhlIHByZXByb2Nlc3Npbmcgc3RlcHMgdG8gYmUgYXBwbGllZC4gVGhleSBhcmUgcG9wdWxhciB3aXRoIGNsYXNzaWMgbnVtZXJpYyB2YXJpYWJsZXMsIHN1Y2ggYXMgaW1wdXRhdGlvbiBvZiBtaXNzaW5nIHZhbHVlcywgY2VudGVyaW5nIGFuZCBzY2FsaW5nLCBldGMuIEFzIE5MUCBkYXRhc2V0cyBoYXZlIHRoZWlyIG93biBwcmVwcm9jZXNzaW5nIHRvb2xzLCB0aGV5IGhhdmUgbm90IGJlZW4gYXBwbGllZCB1bnRpbCBub3cuIEhvd2V2ZXIsIGNhcmV0IG9mZmVycyBgcGNhYCBhcyBhIHByZXBvY2Vzc2luZyBvcHRpb24uIFR3byBtb3JlIHByZXByb2Nlc3NpbmcgZnVuY3Rpb25zIGFyZSBhcHBsaWVkOiBgY2VudGVyYCBhbmQgYHNjYWxlYC4NCg0KQXMgd2UgZXhwZWN0ZWQsIGFwcGx5aW5nIFBDQSBkb2VzIG5vdCBpbXByb3ZlIHRoZSByZXN1bHRzIG9mIHRoZSBjbGFzc2lmaWVycy4gSW4gZmFjdCwgdGhlIHJlc3VsdHMgYXJlIHdvcnNlIGZvciBib3RoIGNsYXNzaWZpZXJzLg0KDQpgYGB7cn0NCiMgZml4aW5nIHRoZSBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uIHByb2NlZHVyZQ0KdHJhaW5fY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMykNCmxyX3BjYV8zeDEwY3YgPC0gdHJhaW4odHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gIkxvZ2l0Qm9vc3QiLCBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiwgInBjYSIpLCB0ckNvbnRyb2wgPSB0cmFpbl9jdHJsKQ0KbHJfcGNhXzN4MTBjdg0KbGRhX3BjYV8zeDEwY3YgPC0gdHJhaW4odHlwZSB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gImxkYSIsIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiLCAicGNhIiksIHRyQ29udHJvbCA9IHRyYWluX2N0cmwpDQpsZGFfcGNhXzN4MTBjdg0KYGBgDQoNCiMgVGVzdGluZw0KDQpJbiBvcmRlciB0byBwcmVkaWN0IHRoZSBjbGFzcyB2YWx1ZSBvZiB1bnNlZW4gZG9jdW1lbnRzIG9mIHRoZSB0ZXN0IHBhcnRpdGlvbiBjYXJldCB1c2VzIHRoZSBjbGFzc2lmaWVyIHdoaWNoIHNob3dzIHRoZSBiZXN0IGFjY3VyYWN5IGVzdGltYXRpb24gb2YgdGhlaXIgcGFyYW1ldGVycy4gRnVuY3Rpb24gcHJlZGljdCBpbXBsZW1lbnRzIHRoaXMgZnVuY3Rpb25hbGl0eS4gQ29uc3VsdCBpdHMgcGFyYW1ldGVycy4gVGhlIGB0eXBlYCBwYXJhbWV0ZXIsIGJ5IG1lYW5zIG9mIGl0cyBgcHJvYnNgIHZhbHVlLCBvdXRwdXRzIHRoZSBwcm9iYWJpbGl0eSBvZiB0ZXN0IGVhY2ggc2FtcGxlIGJlbG9uZ2luZyB0byBlYWNoIGNsYXNzLiBPbiB0aGUgb3RoZXIgaGFuZCwgdGhlIGByYXdgIHZhbHVlIG91dHB1dHMgdGhlIGNsYXNzIHZhbHVlIHdpdGggdGhlIGxhcmdlc3QgcHJvYmFiaWxpdHkuIEJ5IG1lYW5zIG9mIHRoZSBgcmF3YCBvcHRpb24gdGhlIGNvbmZ1c2lvbiBtYXRyaXggY2FuIGJlIGNhbGN1bGF0ZWQ6IHRoaXMgY3Jvc3NlcywgZm9yIGVhY2ggdGVzdCBzYW1wbGUsIHByZWRpY3RlZCB3aXRoIHJlYWwgY2xhc3MgdmFsdWVzLg0KDQpBbGwgdGhlIHByZXZpb3VzbHkgbGVhcm5lZCBjbGFzc2lmaWVycyBhcmUgdGVzdGVkIG9uIHRoZSB0ZXN0IHBhcnRpdGlvbi4gVGhlcmUgYXJlIDEwIGRpZmZlcmVudCBjbGFzc2lmaWVycyBpbiB0b3RhbCwgdGhlIHR3byBtYWluIHR5cGVzIHdpdGggdGhlIHZhcmlhdGlvbnMgb2YgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGV4dHJhY3Rpb24uIEFzIGV4cGVjdGVkLCB0aGUgYWNjdXJhY3kgZm9yIHRoZSB0ZXN0aW5nIHBhcnRpdGlvbiBpcyBhIGJpdCBsb3dlciB0aGFuIHRoZSB0cmFpbiBwYXJ0aXRpb24uIFNwZWNpZmljaXR5IGlzIGhpZ2hlciB0aGFuIFNlbnNpdGl2aXR5IGluIGFsbCB0aGUgY2FzZXMsIHdoaWNoIG1lYW5zIHRoYXQgb3VyIG1vZGVsIGlzIGJldHRlciBhdCBwcmVkaWN0aW5nIHNhbXBsZXMgb2YgY2xhc3MgMjogY2xhc2hyb3lhbGUuIFRoaXMgY2FuIGFsc28gYmUgc2VlbiBpbiB0aGUgY29uZnVzaW9uIG1hdHJpY2VzLiBUaGUgcGVyZm9ybWFuY2Ugb2YgZWFjaCBhbGdvcml0aG0gd2lsbCBiZSBjb21wYXJlZCBtb3JlIGluIGRldGFpbCBpbiB0aGUgbmV4dCBzZWN0aW9uLg0KDQojIyBMREENCg0KYGBge3J9DQpsZGFfcHJlZCA8LSBwcmVkaWN0KGxkYV8zeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInJhdyIpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IGxkYV9wcmVkLCB0ZXN0aW5nJHR5cGUpDQpgYGANCiMjIExEQSBTQkYNCg0KYGBge3J9DQpsZGFfc2JmX3ByZWQgPC0gcHJlZGljdChsZGFfc2JmXzN4MTBjdiwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicmF3IikNCmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gbGRhX3NiZl9wcmVkJHByZWQsIHRlc3RpbmckdHlwZSkNCmBgYA0KDQojIyBMREEgUkZFDQoNCmBgYHtyfQ0KbGRhX3JmZV9wcmVkIDwtIHByZWRpY3QobGRhX3JmZV8zeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nKQ0KY29uZnVzaW9uTWF0cml4KGRhdGEgPSBsZGFfcmZlX3ByZWQkcHJlZCwgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIExEQSBTQUZTDQoNCmBgYHtyfQ0KbGRhX3NhZnNfcHJlZCA8LSBwcmVkaWN0KGxkYV9zYWZzXzN4MTBjdiwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicmF3IikNCmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gbGRhX3NhZnNfcHJlZCwgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIExEQSBQQ0ENCg0KYGBge3J9DQpsZGFfcGNhX3ByZWQgPC0gcHJlZGljdChsZGFfcGNhXzN4MTBjdiwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicmF3IikNCmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gbGRhX3BjYV9wcmVkLCB0ZXN0aW5nJHR5cGUpDQpgYGANCg0KIyMgQkxSDQoNCmBgYHtyfQ0KbHJfcHJlZCA8LSBwcmVkaWN0KGxyXzN4MTBjdiwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicmF3IikNCmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gbHJfcHJlZCwgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIEJMUiBTQkYNCg0KYGBge3J9DQpscl9zYmZfcHJlZCA8LSBwcmVkaWN0KGxyX3NiZl8zeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInJhdyIpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IGxyX3NiZl9wcmVkJHByZWQsIHRlc3RpbmckdHlwZSkNCmBgYA0KDQojIyBCTFIgUkZFDQoNCmBgYHtyfQ0KbHJfcmZlX3ByZWQgPC0gcHJlZGljdChscl9yZmVfM3gxMGN2LCBuZXdkYXRhID0gdGVzdGluZykNCmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gbHJfcmZlX3ByZWQkcHJlZCwgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIEJMUiBTQUZTDQoNCmBgYHtyfQ0KbHJfc2Fmc19wcmVkIDwtIHByZWRpY3QobHJfc2Fmc18zeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInJhdyIpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IGxyX3NhZnNfcHJlZCwgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIEJMUiBQQ0ENCg0KYGBge3J9DQpscl9wY2FfcHJlZCA8LSBwcmVkaWN0KGxyX3BjYV8zeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInJhdyIpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IGxyX3BjYV9wcmVkLCB0ZXN0aW5nJHR5cGUpDQpgYGANCg0KIyBDb21wYXJpc29uDQoNCkFzIGEgZmluYWwgc3RlcCwgd2Ugd2lsbCBjb21wYXJlIHRoZSAxMCBtb2RlbHMgdGhhdCB3ZSBoYXZlIHRyYWluZWQuIEZpcnN0LCB3ZSB3aWxsIGNvbXBhcmUgdGhlIHJlc3VsdHMgaW4gYSB0YWJsZS4gVGhlbiwgd2Ugd2lsbCBjcmVhdGUgc29tZSBwbG90cyB0byBjb21wYXJlIHBlcmZvcm1hbmNlIG9mIHRoZSBhbGdvcml0aG1zIHZpc3VhbGx5LiBGaW5hbGx5LCB3ZSB3aWxsIHBlcmZvcm0gYSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UgdGVzdCB0byBrbm93IGlmIHRoZXJlIGlzIGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIHBhaXJzIG9mIGNsYXNzaWZpZXJzLiANCg0KIyMgU3VtbWFyeSBUYWJsZXMNCg0KVGhpcyBpcyB0aGUgZWFzaWVzdCBjb21wYXJpc29uIHRoYXQgd2UgY2FuIGRvLCBzaW1wbHkgY2FsbCB0aGUgYHN1bW1hcnlgIGZ1bmN0aW9uIGFuZCBwYXNzIGl0IHRoZSBgcmVzYW1wbGVzYCByZXN1bHQuIEl0IHdpbGwgY3JlYXRlIGEgdGFibGUgd2l0aCBvbmUgYWxnb3JpdGhtIGZvciBlYWNoIHJvdyBhbmQgZXZhbHVhdGlvbiBtZXRyaWNzIGZvciBlYWNoIGNvbHVtbi4gDQoNCkJ5IGxvb2tpbmcgYXQgdGhvc2UgdmFsdWVzIHdlIGNhbiBoYXZlIGFuIGlkZWEgb2Ygd2hpY2ggY2xhc3NpZmllcnMgYXJlIHRoZSBiZXN0IG9uZXMuIElmIHdlIGxvb2sgYXQgdGhlIGJhc2UgY2xhc3NpZmllcnMsIExEQSBpcyBiZXR0ZXIgdGhhbiBMUi4gSG93ZXZlciwgYXBwbHlpbmcgU0JGIG9yIFJGRSBmZWF0dXJlIHNlbGVjdGlvbiBpbXByb3ZlcyB0aGUgcmVzdWx0cyBvZiBib3RoIGNsYXNzaWZpZXJzIGFuZCBtYWtlcyB0aGVtIHNpbWlsYXIuIFRoZSBvdGhlciBmZWF0dXJlIHNlbGVjdGlvbiBhbmQgZXh0cmFjdGlvbiBtZXRob2RzIG1ha2UgdGhlIHJlc3VsdHMgb2YgYm90aCBjbGFzc2lmaWVycyB3b3JzZS4NCg0KYGBge3J9DQpyZXNhbXBzIDwtIHJlc2FtcGxlcyhsaXN0KGxyID0gbHJfM3gxMGN2LCBscl9zYmYgPSBscl9zYmZfM3gxMGN2LCBscl9yZmUgPSBscl9yZmVfM3gxMGN2LCBscl9zYWZzID0gbHJfc2Fmc18zeDEwY3YsIGxyX3BjYSA9IGxyX3BjYV8zeDEwY3YsIGxkYSA9IGxkYV8zeDEwY3YsIGxkYV9zYmYgPSBsZGFfc2JmXzN4MTBjdiwgbGRhX3JmZSA9IGxkYV9yZmVfM3gxMGN2LCBsZGFfc2FmcyA9IGxkYV9zYWZzXzN4MTBjdiwgbGRhX3BjYSA9IGxkYV9wY2FfM3gxMGN2KSkNCnN1bW1hcnkocmVzYW1wcykNCmBgYA0KDQojIyBCb3ggYW5kIFdoaXNrZXIgUGxvdHMNCg0KVGhpcyBpcyBhIHVzZWZ1bCB3YXkgdG8gbG9vayBhdCB0aGUgc3ByZWFkIG9mIHRoZSBlc3RpbWF0ZWQgYWNjdXJhY2llcyBmb3IgZGlmZmVyZW50IG1ldGhvZHMgYW5kIGhvdyB0aGV5IHJlbGF0ZS4gTm90ZSB0aGF0IHRoZSBib3hlcyBhcmUgb3JkZXJlZCBmcm9tIGhpZ2hlc3QgdG8gbG93ZXN0IG1lYW4gYWNjdXJhY3kuIFRoZXkgYXJlIHVzZWZ1bCB0byBsb29rIGF0IHRoZSBtZWFuIHZhbHVlcyAoZG90cykgYW5kIHRoZSBib3hlcyAobWlkZGxlIDUwJSBvZiByZXN1bHRzKS4gV2UgY2FuIGV4dHJhY3QgdGhlIHNhbWUgY29uY2x1c2lvbnMgd2UgZXh0cmFjdGVkIGJ5IGxvb2tpbmcgYXQgdGhlIHRhYmxlIGVhc2llciBieSBsb29raW4gYXQgdGhpcyBwbG90Lg0KDQpgYGB7cn0NCnNjYWxlcyA8LSBsaXN0KHg9bGlzdChyZWxhdGlvbj0iZnJlZSIpLCB5PWxpc3QocmVsYXRpb249ImZyZWUiKSkNCmJ3cGxvdChyZXNhbXBzLCBzY2FsZXM9c2NhbGVzKQ0KYGBgDQoNCiMjIERlbnNpdHkgUGxvdHMNCg0KV2UgY2FuIHNob3cgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtb2RlbCBhY2N1cmFjeSBhcyBkZW5zaXR5IHBsb3RzLiBUaGlzIGlzIGEgdXNlZnVsIHdheSB0byBldmFsdWF0ZSB0aGUgb3ZlcmxhcCBpbiB0aGUgZXN0aW1hdGVkIGJlaGF2aW9yIG9mIGFsZ29yaXRobXMuIFRoZXkgYXJlIGFsc28gdG8gbG9vayBhdCB0aGUgZGlmZmVyZW5jZXMgaW4gdGhlIHBlYWtzIGFzIHdlbGwgYXMgdGhlIHZhcmlhbmNlIG9mIHRoZSBkaXN0cmlidXRpb25zLg0KDQpgYGB7cn0NCnNjYWxlcyA8LSBsaXN0KHg9bGlzdChyZWxhdGlvbj0iZnJlZSIpLCB5PWxpc3QocmVsYXRpb249ImZyZWUiKSkNCmRlbnNpdHlwbG90KHJlc2FtcHMsIHNjYWxlcz1zY2FsZXMsIHBjaCA9ICJ8IikNCmBgYA0KDQojIyBEb3QgUGxvdHMNCg0KVGhlc2UgYXJlIHVzZWZ1bCBwbG90cyBhcyB0aGUgc2hvdyBib3RoIHRoZSBtZWFuIGVzdGltYXRlZCBhY2N1cmFjeSBhcyB3ZWxsIGFzIHRoZSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbC4gVGhleSBhcmUgdXNlZnVsIHRvIGNvbXBhcmUgdGhlIG1lYW5zIGFuZCB0aGUgb3ZlcmxhcCBvZiB0aGUgc3ByZWFkcyBiZXR3ZWVuIGFsZ29yaXRobXMuIFdlIGNhbiBjb21wYXJlIGFsZ29yaXRobXMgbGlrZSB3ZSBkaWQgd2l0aCB0aGUgYm94cGxvdC4NCg0KYGBge3J9DQpzY2FsZXMgPC0gbGlzdCh4PWxpc3QocmVsYXRpb249ImZyZWUiKSwgeT1saXN0KHJlbGF0aW9uPSJmcmVlIikpDQpkb3RwbG90KHJlc2FtcHMsIHNjYWxlcz1zY2FsZXMpDQpgYGANCg0KIyMgU2NhdHRlcnBsb3QgTWF0cml4DQoNClRoaXMgY3JlYXRlcyBhIHNjYXR0ZXJwbG90IG1hdHJpeCBvZiBhbGwgcmVzdWx0cyBmb3IgYW4gYWxnb3JpdGhtIGNvbXBhcmVkIHRvIHRoZSByZXN1bHRzIGZvciBhbGwgb3RoZXIgYWxnb3JpdGhtcy4gVGhlc2UgYXJlIHVzZWZ1bCB0byBjb21wYXJlIHBhaXJzIG9mIGFsZ29yaXRobXMuDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCnNwbG9tKHJlc2FtcHMpDQpgYGANCg0KIyMgUGFpcndpc2UgeHlQbG90cw0KDQpXZSBjYW4gem9vbSBpbiBvbiBvbmUgcGFpci13aXNlIGNvbXBhcmlzb24gb2YgdGhlIGFjY3VyYWN5IGZvciB0d28gYWxnb3JpdGhtcyB3aXRoIGFuIHh5cGxvdC4gRm9yIGV4YW1wbGUsIHdlIGNhbiBjb21wYXJlIHRoZSB0d28gbWFpbiBhbGdvcml0aG1zIHRvIHNlZSB0aGF0IExEQSBpcyBiZXR0ZXIgdGhhbiBMUi4NCg0KYGBge3J9DQp4eXBsb3QocmVzYW1wcywgd2hhdCA9ICJCbGFuZEFsdG1hbiIsIG1vZGVscyA9IGMoImxyIiwgImxkYSIpKQ0KYGBgDQoNCkFub3RoZXIgdXNlZnVsIGNvbXBhcmlzb24gaXMgdG8gY2hlY2sgdGhlIGVmZmVjdCBvZiBmZWF0dXJlIHNlbGVjdGlvbiBhbmQgZXh0cmFjdGlvbi4gRm9yIHRoZSBMb2dpc3RpYyBSZWdyZXNzaW9uIGFsZ29yaXRobSwgVW5pdmFyaWF0ZSBGaWx0ZXJzIGFuZCBSZWN1cnNpdmUgRmVhdHVyZSBFbGltaW5hdGlvbiBpbXByb3ZlIHRoZSBhY2N1cmFjeS4gSG93ZXZlciwgU2ltdWxhdGVkIEFubmVhbGluZyBhbmQgUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyBnZXQgd29yc2UgcmVzdWx0cy4NCg0KYGBge3J9DQp4eXBsb3QocmVzYW1wcywgd2hhdCA9ICJCbGFuZEFsdG1hbiIsIG1vZGVscyA9IGMoImxyIiwgImxyX3NiZiIpKQ0KYGBgDQoNCmBgYHtyfQ0KeHlwbG90KHJlc2FtcHMsIHdoYXQgPSAiQmxhbmRBbHRtYW4iLCBtb2RlbHMgPSBjKCJsciIsICJscl9yZmUiKSkNCmBgYA0KDQpgYGB7cn0NCnh5cGxvdChyZXNhbXBzLCB3aGF0ID0gIkJsYW5kQWx0bWFuIiwgbW9kZWxzID0gYygibHIiLCAibHJfc2FmcyIpKQ0KYGBgDQoNCmBgYHtyfQ0KeHlwbG90KHJlc2FtcHMsIHdoYXQgPSAiQmxhbmRBbHRtYW4iLCBtb2RlbHMgPSBjKCJsciIsICJscl9wY2EiKSkNCmBgYA0KDQojIyBTdGF0aXN0aWNhbCBTaWduaWZpY2FuY2UgVGVzdHMNCg0KTm90ZSB0aGFuIGluIG91ciBjYXNlLCBkdWUgdG8gdGhlIDMgcmVwZXRpdGlvbnMgb2YgdGhlIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBwcm9jZXNzLCB0aGVyZSBhcmUgMzAgcmVzYW1wbGluZyByZXN1bHRzIGZvciBlYWNoIGNsYXNzaWZpZXIuIFRoZSBzYW1lIHBhaXJlZCBjcm9zcy12YWxpZGF0aW9uIHN1YnNldHMgb2Ygc2FtcGxlcyB3ZXJlIHVzZWQgZm9yIGFsbCBjbGFzc2lmaWVycy4gV2UgaGF2ZSB0byB1c2UgYSBwYWlyZWQgdC10ZXN0IHRvIGNhbGN1bGF0ZSB0aGUgc2lnbmlmaWNhbmNlIG9mIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGJvdGggY2xhc3NpZmllcnMuDQoNClVzaW5nIHRoZSBgZGlmZmAgZnVuY3Rpb24gb3ZlciB0aGUgYHJlc2FtcHNgIG9iamVjdCBjYWxjdWxhdGVzIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGFsbCBwYWlycyBvZiBjbGFzc2lmaWVycy4gVGhlIG91dHB1dCBzaG93cywgZm9yIGVhY2ggbWV0cmljIChhY2N1cmFjeSBhbmQga2FwcGEpLCB0aGUgZGlmZmVyZW5jZSBvZiB0aGUgbWVhbiAocG9zaXRpdmUgb3IgbmVnYXRpdmUpIGJldHdlZW4gYm90aCBjbGFzc2lmaWVycy4gVGhlIHAtdmFsdWUgb2YgdGhlIHdob2xlIHQtdGVzdCBpcyAwLCB3aGljaCBpbmRpY2F0ZXMgdGhhdCB0aGVyZSBpcyBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiBzb21lIGNsYXNzaWZpZXJzLiBUaGVyZWZvcmUsIHdlIGNhbiBkaXNjYXJkIHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCBzYXlzIHRoYXQgdGhlcmUgaXMgbm8gZGlmZmVyZW5jZSBiZXR3ZWVuIGNsYXNzaWZpZXJzLg0KDQpUaGUgaW50ZXJwcmV0YXRpb24gb2YgdGhlIHAtdmFsdWUgaXMgdGhlIGtleSBwb2ludC4gSXQgaXMgcmVsYXRlZCB3aXRoIHRoZSByaXNrIG9mIGVycm9uZW91c2x5IGRpc2NhcmRpbmcgdGhlIG51bGwtaHlwb3RoZXNpcyBvZiBzaW1pbGFyaXR5IGJldHdlZW4gY29tcGFyZWQgY2xhc3NpZmllcnMsIHdoZW4gdGhlcmUgaXMgbm8gcmVhbCBkaWZmZXJlbmNlLiBSb3VnaGx5IHNwZWFraW5nLCBpdCBjYW4gYWxzbyBiZSBpbnRlcnByZXRlZCBhcyB0aGUgZGVncmVlIG9mIHNpbWlsYXJpdHkgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzLiBBIHAtdmFsdWUgc21hbGxlciB0aGFuIDAuMDUgYWxlcnRzIGFib3V0IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzLiBUaGF0IGlzLCB3aGVuIHRoZSByaXNrIG9mIGVycm9uZW91c2x5IGRpc2NhcmRpbmcgdGhlIGh5cG90aGVzaXMgb2Ygc2ltaWxhcml0eSBiZXR3ZWVuIGJvdGggY2xhc3NpZmllcnMgaXMgbG93LCB3ZSBhc3N1bWUgdGhhdCB0aGVyZSBpcyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIGNsYXNzaWZpZXJzLg0KDQpUaGUgbG93ZXIgZGlhZ29uYWwgb2YgdGhlIHRhYmxlIHNob3dzIHAtdmFsdWVzIGZvciB0aGUgbnVsbCBoeXBvdGhlc2lzLiBUaGUgdXBwZXIgZGlhZ29uYWwgb2YgdGhlIHRhYmxlIHNob3dzIHRoZSBlc3RpbWF0ZWQgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBkaXN0cmlidXRpb25zLiBXZSBjYW4gc2VlIHRoYXQgaXMgY29tZSBjYXNlcyB0aGUgcC12YWx1ZSBpcyBiaWdnZXIgdGhhbiAwLjA1IGFuZCB0aGVyZWZvcmUgd2UgY2FuIG5vdCBkaXNjYXJkIHRoZSBudWxsIGh5cG90aGVzaXMuIEluIHNvbWUgb3RoZXIgY2FzZXMsIHRoZSBwLXZhbHVlIGlzIHNtYWxsZXIgdGhhbiAwLjA1IHNvIHdlIGNhbiBzdXJlbHkgZGlzY2FyZCB0aGUgbnVsbCBoeXBvdGhlc2lzLg0KDQpXZSBjYW4gc2VlIHRoYXQgYWxsIHRoZSBpZGVhcyB0aGF0IHdlIGhhZCBiZWZvcmUgd2hlbiBjb21wYXJpbmcgY2xhc3NpZmllcnMgYXJlIGNvbmZpcm1lZCB3aXRoIHRoZSBzdGF0aXN0aWNhbCB0ZXN0LiBTb21lIGNsYXNzaWZpZXJzIGFyZSBzaWduaWZpY2FudGx5IGJldHRlciB0aGFuIG90aGVycy4gVGhlIGJhc2UgTERBIGlzIGJldHRlciB0aGFuIHRoZSBiYXNlIExSLCBhcHBseWluZyBTQkYgYW5kIFJGRSBpbXByb3ZlcyB0aGUgcmVzdWx0cyBhbmQgYXBwbHlpbmcgU0FGUyBhbmQgUENBIG1ha2VzIHJlc3VsdHMgd29yc2UuDQoNCmBgYHtyfQ0KZGlmZnMgPC0gZGlmZihyZXNhbXBzKQ0Kc3VtbWFyeShkaWZmcykNCmBgYA0KDQojIEJpYmxpb2dyYXBoeQ0KDQpbMV0gSW5nbyBGZWluZXJlci4gdG06IFRleHQgTWluaW5nIFBhY2thZ2UsIDIwMTIuIFIgcGFja2FnZSB2ZXJzaW9uIDAuNS03LjEuDQoNClsyXSBJbmdvIEZlaW5lcmVyLCBLdXJ0IEhvcm5paywgYW5kIERhdmlkIE1leWVyLiBUZXh0IG1pbmluZyBpbmZyYXN0cnVjdHVyZSBpbiBSLiBKb3VybmFsIG9mIFN0YXRpc3RpY2FsIFNvZnR3YXJlLCAyNSg1KToxLTU0LCAzIDIwMDguDQoNClszXSBJYW4gRmVsbG93cy4gd29yZGNsb3VkOiBXb3JkIENsb3VkcywgMjAxNC4gUiBwYWNrYWdlIHZlcnNpb24gMi41Lg0KDQpbNF0gTS4gS3VobiBhbmQgSy4gSm9obnNvbi4gQXBwbGllZCBQcmVkaWN0aXZlIE1vZGVsaW5nLiBTcHJpbmdlciwgMjAxMy4NCg0KWzVdIE1heCBLdWhuLiBDb250cmlidXRpb25zIGZyb20gSmVkIFdpbmcsIFN0ZXZlIFdlc3RvbiwgQW5kcmUgV2lsbGlhbXMsIENocmlzIEtlZWZlciwgQWxsYW4gRW5nZWxoYXJkdCwgVG9ueSBDb29wZXIsIFphY2hhcnkgTWF5ZXIsIGFuZCB0aGUgUiBDb3JlIFRlYW0uIGNhcmV0OiBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmFpbmluZywgMjAxNC4gUiBwYWNrYWdlIHZlcnNpb24gNi4wLTM1Lg0K