Description

Swiftkey is a Natural Language processing project as the Final Capstone project in the Coursera Data Science Specialization Course

Introduction and Summary

This is a concise Markdown document that is describing the step by step process of handling this NLP project . This is the first milestone report that is showing the progress for the complete project.

The motivation for this project is to:

  1. Demonstrate that you’ve downloaded the data and have successfully loaded it in.
  2. Create a basic report of summary statistics about the data sets.
  3. Report any interesting findings that you amassed so far.
  4. Get feedback on your plans for creating a prediction algorithm and Shiny app.

Tasks to accomplish mostly concentrates on the Milestone goals and the Task-2 for the week no. 2

  • Tokenization - identifying appropriate tokens such as words, punctuation, and numbers. Writing a function that takes a file as input and returns a tokenized version of it.
  • Profanity filtering - removing profanity and other words you do not want to predict.
  • Does the link lead to an HTML page describing the exploratory analysis of the training data set?
  • Has the data scientist done basic summaries of the three files? Word counts, line counts and basic data tables?
  • Has the data scientist made basic plots, such as histograms to illustrate features of the data?
  • Was the report written in a brief, concise style, in a way that a non-data scientist manager could appreciate?

Loading the Data and Data Description

Course dataset

This is the training data to get you started that will be the basis for most of the capstone. You must download the data from the link below and not from external websites to start.

path1<-NULL
path1 <- "C:/Public/nlp"

for(i in list.files(path = path1, pattern = "\\.zip$")) {
  unzip(i, overwrite = TRUE)
}

Getting the Filenames and also getting connections to read the Text files

path1 <- "C:/Public/nlp"
foldernames <- c("DE", "US", "FI", "RU")
filenames_n<-character(0)
for (j in list.dirs(path = path1)) {
  if (length(unlist(strsplit(basename(j),"_")[[1]])) == 2) {
    for (k in list.files(path = j)) {
      if (grepl(".txt",k, perl = TRUE)) {
        filenames_n <- c(filenames_n, unlist(strsplit(k, "\\.txt"))[1]) 
      }
  }
  }
}

Getting the Data that is of Concern

  • Loading the data in. This dataset is fairly large. We emphasize that you don’t necessarily need to load the entire dataset in to build your algorithms (see point 2 below). At least initially, you might want to use a smaller subset of the data.
  • Sampling. To reiterate, to build models you don’t need to load in and use all of the data.
library(stringi)
library(strip)
countern<<-0
lines<-NULL
filesize <- c()
for (j in list.dirs(path = path1)) {
  #print(unlist(strsplit(basename(j),"_")[[1]])[1])
  if ((length(unlist(strsplit(basename(j),"_")[[1]])) == 2) & (unlist(strsplit(basename(j),"_")[[1]])[1] == "en") ) {
     for (k in list.files(path = j)) {
       # Grep based on Categories and then read
       if (grepl(".txt", k, perl = TRUE)) {
         countern<<-countern+1
         pathtemp <- paste(j,k, sep = "/")
         filesize[countern]<-file.size(pathtemp)
         nam <- unlist(strip(strsplit(pathtemp, "/")))[6]
         assign(nam, NULL)
         conntemp <- file(pathtemp, open = "r")
         lines <- readLines(conntemp)
         assign(nam , lines)
         close(conntemp) 
       }
   }
  }
}

Data Cleaning and representation of the data

Analyzing the corpus here and Cleaning using the “tm” package

  • Checking for Puntuations , numbers , periods, hyphens etc and removing them
  • Converting the entire document to lower case
  • Removing stopwords (extremely common words such as “and”, “or”, “not”, “in”, “is” etc)
    • This is an interesting effect based on the fact that N-grams might be affected due to this
    • Upto to us if we want to take this out or not, I am deciding to take these out
  • Removing numbers
  • Filtering out unwanted terms and weird characters
  • Removing extra whitespace
# How to see a line when in Vcorpus format
lapply(c(1:3), function(x) {
 strwrap(corpusblogs[[x]]) 
})
[[1]]
[1] "In the years thereafter, most of the Oil fields and platforms were named after pagan “godsâ€\u009d."

[[2]]
[1] "We love you Mr. Brown."

[[3]]
[1] "Chad has been awesome with the kids and holding down the fort while I work later than usual! The kids have been busy together playing Skylander on the XBox" 
[2] "together, after Kyan cashed in his $$$ from his piggy bank. He wanted that game so bad and used his gift card from his birthday he has been saving and the"  
[3] "money to get it (he never taps into that thing either, that is how we know he wanted it so bad). We made him count all of his money to make sure that he had"
[4] "enough! It was very cute to watch his reaction when he realized he did! He also does a very good job of letting Lola feel like she is playing too, by"       
[5] "letting her switch out the characters! She loves it almost as much as him."                                                                                  
# To Lower case
corpusblogs_proc1 <- tm_map(corpusblogs, content_transformer(tolower)) 
corpususnews_proc1 <- tm_map(corpususnews, content_transformer(tolower))
corpustwitter_proc1 <- tm_map(corpustwitter, content_transformer(tolower))
#Replacing the Full stops and commas and pinctuations 
corpusblogs_proc1 <- tm_map(corpusblogs_proc1, removePunctuation) 
corpususnews_proc1 <- tm_map(corpususnews_proc1, content_transformer(tolower))
corpustwitter_proc1 <- tm_map(corpustwitter_proc1, content_transformer(tolower))
#Removing Whitespace
corpusblogs_proc1 <- tm_map(corpusblogs_proc1, stripWhitespace) 
corpususnews_proc1 <- tm_map(corpususnews_proc1, stripWhitespace)
corpustwitter_proc1 <- tm_map(corpustwitter_proc1, stripWhitespace)
#Removing Numbers
corpusblogs_proc1 <- tm_map(corpusblogs_proc1, removeNumbers) 
corpususnews_proc1 <- tm_map(corpususnews_proc1, removeNumbers)
corpustwitter_proc1 <- tm_map(corpustwitter_proc1, removeNumbers)
#Removing weird characters and ASCII
toSpace <- content_transformer(function (x , pattern) gsub(pattern, " ", x))
corpusblogs_proc1 <- tm_map(corpusblogs_proc1, toSpace, "â€") 
corpususnews_proc1 <- tm_map(corpususnews_proc1, toSpace, "â€")
corpustwitter_proc1 <- tm_map(corpustwitter_proc1, toSpace, "â€")
toNormal <- content_transformer(function (x) iconv(x, "latin1", "ASCII", sub=""))
corpusblogs_proc1 <- tm_map(corpusblogs_proc1, toNormal) 
corpususnews_proc1 <- tm_map(corpususnews_proc1, toNormal)
corpustwitter_proc1 <- tm_map(corpustwitter_proc1, toNormal)
#Removing english Stop words is a Choice
corpusblogs_proc1 <- tm_map(corpusblogs_proc1, removeWords, stopwords("english")) 
corpususnews_proc1 <- tm_map(corpususnews_proc1, removeWords, stopwords("english"))
corpustwitter_proc1 <- tm_map(corpustwitter_proc1, removeWords, stopwords("english"))
#strwrap(corpusblogs_proc1[[1]])
#iconv(strwrap(corpusblogs_proc1[[1]]), "latin1", "ASCII", sub = "")  

Build a Data table for the text present (Obvious as they ridiculously optimized to handle big data!!)

library(data.table)
library(dplyr)
library(tidytext)
library(ggplot2)
corpusblogs_proc1.dt <- NULL
corpususnews_proc1.dt<-NULL
corpustwitter_proc1.dt<-NULL
# Blogs
corpusblogs_proc1.dt <- data.table(text=sapply(corpusblogs_proc1, identity), stringsAsFactors = F) 
corpusblogs_proc1.dt.tidy<-corpusblogs_proc1.dt %>% unnest_tokens(word,text)
corpusblogs_proc1.dt.tidy %>% count(word, sort=TRUE) %>%
filter(n>1500) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
  geom_col()+
  xlab("Words")+
  coord_flip()+
  ggtitle("Word Count - BLOGS")

#usnews
corpususnews_proc1.dt<-data.table(text=sapply(corpususnews_proc1, identity), stringsAsFactors = F)
corpususnews_proc1.dt.tidy<-corpususnews_proc1.dt %>% unnest_tokens(word, text)
corpususnews_proc1.dt.tidy %>% count(word, sort=TRUE) %>%
  filter(n>75) %>%
  mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
  geom_col()+
  xlab("Words")+
  coord_flip()+
  ggtitle("Word Count - US NEWS")

#Twitter
corpustwitter_proc1.dt<-data.table(text=sapply(corpustwitter_proc1, identity), stringsAsFactors = F)
corpustwitter_proc1.dt.tidy<-corpustwitter_proc1.dt %>% unnest_tokens(word, text)
corpustwitter_proc1.dt.tidy %>% count(word, sort=TRUE) %>%
  filter(n>2000) %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n)) + 
  geom_col()+
  xlab("Words")+
  coord_flip()+
  ggtitle("Word Count - Twitter")

Tokenizing based on Adjacent words

  • Consecutive sequence of words called “n-grams”
  • Bigram
  • There seems a lot of count of Bigrams such as “dont”, “wont” .. somewords that ends on a t , we would need to remove those.
library(dplyr)
library(tidytext)
library(tidyr)
l = list(corpusblogs_proc1.dt, corpususnews_proc1.dt,corpustwitter_proc1.dt)
comb.dt<-rbindlist(l)
comb.dt.bigram <-  comb.dt %>% unnest_tokens(bigram, text, token="ngrams", n=2)
#Removing the words that end with t and stop words
comb.dt.bigram_separated<-comb.dt.bigram %>% separate(bigram, c("word1","word2"), sep=" ")
comb.dt.bigram_filtered <- comb.dt.bigram_separated %>%
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word)
comb.dt.bigram_filtered <- comb.dt.bigram_filtered %>%
  filter(!word2 %in% "t")
comb.dt.bigram_united <- comb.dt.bigram_filtered %>%
  unite(bigram, word1, word2, sep=" ")
comb.dt.bigram_united %>% count(bigram, sort=TRUE) %>%
  filter(n>100) %>%
  mutate(bigram = reorder(bigram, n)) %>%
  ggplot(aes(bigram, n)) +
  geom_col()+
  xlab("bigram")+
  coord_flip() +
  ggtitle("Bigram Count - Combined Dataset")

NA

Trigram Frequency Determination

  • For this case , We decide to keep all the stop words etc .. and see how it goes
  • Word Cloud and the frequency distribution for all the trigrams
library(wordcloud)
comb.dt.trigram <- comb.dt %>% unnest_tokens(trigram, text, token="ngrams", n=3)
set.seed(1234)
comb.dt.final <- comb.dt.trigram %>% count(trigram , sort=TRUE)
wordcloud(words = comb.dt.final$trigram, freq = comb.dt.final$n, max.words = 100, colors = brewer.pal(6,"Dark2")) 

# plot of frequencies 
comb.dt.final %>%
  filter(n>50) %>%
ggplot(aes(trigram, n)) +
  geom_col() +
  xlab("trigram") +
  coord_flip() +
  ggtitle("Trigram Count - Combined Dataset")

Plotting Top 10 Unigram, Bigram & Trigram for Blogs, news and twitter dataset in one Plot

  • For this case I did not do a lot of filtering , as i wanted to check the raw top 10
par(mfrow=c(3,3))
data.blogs = corpusblogs_proc1.dt.tidy %>% count(word, sort=TRUE) 
data.news = corpususnews_proc1.dt.tidy %>% count(word, sort=TRUE)
data.twitter = corpustwitter_proc1.dt.tidy %>% count(word, sort=TRUE)
ggblog1 <- ggplot(data = head(data.blogs,10)) + geom_bar(aes(x=word, y=n), stat="identity") + coord_flip() + xlab("Unigram Blog Words")
ggnews1 <- ggplot(data = head(data.news,10)) + geom_bar(aes(x=word, y=n), stat="identity") + coord_flip() + xlab("Unigram news Words")
ggtwitter1 <- ggplot(data = head(data.twitter,10)) + geom_bar(aes(x=word, y=n), stat="identity") + coord_flip() + xlab("Unigram twitter Words")
list1 <- list(ggblog1,ggnews1,ggtwitter1)
data.blogs.bigram <-  corpusblogs_proc1.dt %>% unnest_tokens(bigram, text, token="ngrams", n=2) %>% count(bigram, sort=TRUE)
data.news.bigram <-  corpususnews_proc1.dt %>% unnest_tokens(bigram, text, token="ngrams", n=2) %>% count(bigram, sort=TRUE)
data.twitter.bigram <-  corpustwitter_proc1.dt %>% unnest_tokens(bigram, text, token="ngrams", n=2) %>% count(bigram, sort=TRUE)
ggblog2 <- ggplot(data = head(data.blogs.bigram,10)) + geom_bar(aes(x=bigram, y=n), stat="identity") + coord_flip() + xlab("Bigram Blog Words")
ggnews2 <- ggplot(data = head(data.news.bigram,10)) + geom_bar(aes(x=bigram, y=n), stat="identity") + coord_flip() + xlab("Bigram news Words")
ggtwitter2 <- ggplot(data = head(data.twitter.bigram,10)) + geom_bar(aes(x=bigram, y=n), stat="identity") + coord_flip() + xlab("Bigram twitter Words")
list2 <- list(ggblog2,ggnews2,ggtwitter2)
data.blogs.trigram <-  corpusblogs_proc1.dt %>% unnest_tokens(trigram, text, token="ngrams", n=3) %>% count(trigram, sort=TRUE)
data.news.trigram <-  corpususnews_proc1.dt %>% unnest_tokens(trigram, text, token="ngrams", n=3) %>% count(trigram, sort=TRUE)
data.twitter.trigram <-  corpustwitter_proc1.dt %>% unnest_tokens(trigram, text, token="ngrams", n=3) %>% count(trigram, sort=TRUE)
ggblog3 <- ggplot(data = head(data.blogs.trigram,10)) + geom_bar(aes(x=trigram, y=n), stat="identity") + coord_flip() + xlab("Trigram Blog Words")
ggnews3 <- ggplot(data = head(data.news.trigram,10)) + geom_bar(aes(x=trigram, y=n), stat="identity") + coord_flip() + xlab("Trigram news Words")
ggtwitter3 <- ggplot(data = head(data.twitter.trigram,10)) + geom_bar(aes(x=trigram, y=n), stat="identity") + coord_flip() + xlab("Trigram twitter Words")
list3 <- list(ggblog3,ggnews3,ggtwitter3)
library(grid)
library(gridExtra)
grid.arrange(grobs = c(list1,list2,list3),ncol = 3, as.table = FALSE)

How many unique words do you need in a frequency sorted dictionary to cover 50% of all word instances in the language? 90%?

comb.dt.freqdict<-comb.dt %>% unnest_tokens(word, text) %>% count(word, sort=TRUE)
sumofwords <- sum(comb.dt.freqdict$n)
print(paste("The total number of words present here", sumofwords))
[1] "The total number of words present here 2029277"
sumcounter<<-0
counter<<-0
valtemp<-lapply(comb.dt.freqdict$n, function(x){
  if (sumcounter<=0.5*sumofwords) {
   sumcounter<<-sumcounter+x
   counter<<-counter+1
  }
})
print(paste("The number of unique words required to cover 50% of all words in case of a sample size of 3% = ", counter))
[1] "The number of unique words required to cover 50% of all words in case of a sample size of 3% =  1130"
sumcounter<<-0
counter<<-0
valtemp<-lapply(comb.dt.freqdict$n, function(x){
  if (sumcounter<=0.9*sumofwords) {
   sumcounter<<-sumcounter+x
   counter<<-counter+1
  }
})
print(paste("The number of unique words required to cover 90% of all words in case of a sample size of 3% = ", counter))
[1] "The number of unique words required to cover 90% of all words in case of a sample size of 3% =  15052"

How do you evaluate how many of the words come from foreign languages?

  • Right now I did not deal with foreign language words as it seems after the Frequency distributions of the english words , these words are have very low presence , and if present they need to compared based on a hashmap or dictionary data base of some sort

Can you think of a way to increase the coverage – identifying words that may not be in the corpora or using a smaller number of words in the dictionary to cover the same number of phrases?

  • This is an interesting question that involves prediction based on smaller sample size, we can use higher order n-grams with higher frequency and remove the lower frequency ones to predict which words are more probable to appear in a larger Population size .

Summary of Findings

  • Zipf’s Law - The Frequency that a word appears is inversely proportional to the rank of the word
  • Initially I thought of using the tdm function to form a tem document matrix from the corpus data, but it seems that is not possible to process due to size limitations and hence I decided to go with data tables instead , which as expected are good with large datasets
  • I also see even after a lot of filtering , some words like won’t , don’t, can’t , need to filtered out of the 2- or 3- n grams as they will produce misleading frequency distributions
  • Sampling only 3% of the data seems to be low, so I will be increasing my sample size to 10% in the coming runs of the project

Future Feedback

  • I plan to use some sort of a algorithm , I see there is a discussion about KBO(Katz backoff) , We basically need to predict the next word in a sequence of words .
    • There could be a Bayesian approch to the prediction problem with putting probablities on each of the words in a n-gram to predict n+1 word
    • There should be other similar approaces to KBO , which I plan to study and implement as required
  • I would also try to change my sample size and see how that changes my Frequency distributions
  • I also believe there is a lot of research/reading papers/tutorials & videos that needs to be done between task-2 and task-3 to reach a more through understanding of a better approach
LS0tDQp0aXRsZTogIlN3aWZ0IEtleSBDb3Vyc2VyYSBOTFAgUHJvamVjdCAtIE1pbGVzdG9uZSByZXBvcnQiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KRGF0ZTogQXVndXN0IDUsIDIwMTcNCi0tLQ0KDQojIERlc2NyaXB0aW9uIA0KDQojIyMgU3dpZnRrZXkgaXMgYSBOYXR1cmFsIExhbmd1YWdlIHByb2Nlc3NpbmcgcHJvamVjdCBhcyB0aGUgRmluYWwgQ2Fwc3RvbmUgcHJvamVjdCBpbiB0aGUgQ291cnNlcmEgRGF0YSBTY2llbmNlIFNwZWNpYWxpemF0aW9uIENvdXJzZQ0KDQojIyBJbnRyb2R1Y3Rpb24gYW5kIFN1bW1hcnkNCg0KVGhpcyBpcyBhIGNvbmNpc2UgTWFya2Rvd24gZG9jdW1lbnQgdGhhdCBpcyBkZXNjcmliaW5nIHRoZSBzdGVwIGJ5IHN0ZXAgcHJvY2VzcyBvZiBoYW5kbGluZyB0aGlzIE5MUCBwcm9qZWN0IC4gVGhpcyBpcyB0aGUgZmlyc3QgbWlsZXN0b25lIHJlcG9ydCB0aGF0IGlzIHNob3dpbmcgdGhlIHByb2dyZXNzIGZvciB0aGUgY29tcGxldGUgcHJvamVjdC4NCg0KKipUaGUgbW90aXZhdGlvbiBmb3IgdGhpcyBwcm9qZWN0IGlzIHRvOioqIA0KDQogIDEuIERlbW9uc3RyYXRlIHRoYXQgeW91J3ZlIGRvd25sb2FkZWQgdGhlIGRhdGEgYW5kIGhhdmUgc3VjY2Vzc2Z1bGx5IGxvYWRlZCBpdCBpbi4NCiAgMi4gQ3JlYXRlIGEgYmFzaWMgcmVwb3J0IG9mIHN1bW1hcnkgc3RhdGlzdGljcyBhYm91dCB0aGUgZGF0YSBzZXRzLg0KICAzLiBSZXBvcnQgYW55IGludGVyZXN0aW5nIGZpbmRpbmdzIHRoYXQgeW91IGFtYXNzZWQgc28gZmFyLg0KICA0LiBHZXQgZmVlZGJhY2sgb24geW91ciBwbGFucyBmb3IgY3JlYXRpbmcgYSBwcmVkaWN0aW9uIGFsZ29yaXRobSBhbmQgU2hpbnkgYXBwLg0KDQojIyNUYXNrcyB0byBhY2NvbXBsaXNoIG1vc3RseSBjb25jZW50cmF0ZXMgb24gdGhlIE1pbGVzdG9uZSBnb2FscyBhbmQgdGhlIFRhc2stMiBmb3IgdGhlIHdlZWsgbm8uIDINCg0KICAtIFRva2VuaXphdGlvbiAtIGlkZW50aWZ5aW5nIGFwcHJvcHJpYXRlIHRva2VucyBzdWNoIGFzIHdvcmRzLCBwdW5jdHVhdGlvbiwgYW5kIG51bWJlcnMuIFdyaXRpbmcgYSBmdW5jdGlvbiB0aGF0IHRha2VzIGEgZmlsZSBhcyBpbnB1dCBhbmQgcmV0dXJucyBhIHRva2VuaXplZCB2ZXJzaW9uIG9mIGl0Lg0KICAtIFByb2Zhbml0eSBmaWx0ZXJpbmcgLSByZW1vdmluZyBwcm9mYW5pdHkgYW5kIG90aGVyIHdvcmRzIHlvdSBkbyBub3Qgd2FudCB0byBwcmVkaWN0Lg0KICAtIERvZXMgdGhlIGxpbmsgbGVhZCB0byBhbiBIVE1MIHBhZ2UgZGVzY3JpYmluZyB0aGUgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb2YgdGhlIHRyYWluaW5nIGRhdGEgc2V0Pw0KICAtIEhhcyB0aGUgZGF0YSBzY2llbnRpc3QgZG9uZSBiYXNpYyBzdW1tYXJpZXMgb2YgdGhlIHRocmVlIGZpbGVzPyBXb3JkIGNvdW50cywgbGluZSBjb3VudHMgYW5kIGJhc2ljIGRhdGEgdGFibGVzPw0KICAtIEhhcyB0aGUgZGF0YSBzY2llbnRpc3QgbWFkZSBiYXNpYyBwbG90cywgc3VjaCBhcyBoaXN0b2dyYW1zIHRvIGlsbHVzdHJhdGUgZmVhdHVyZXMgb2YgdGhlIGRhdGE/DQogIC0gV2FzIHRoZSByZXBvcnQgd3JpdHRlbiBpbiBhIGJyaWVmLCBjb25jaXNlIHN0eWxlLCBpbiBhIHdheSB0aGF0IGEgbm9uLWRhdGEgc2NpZW50aXN0IG1hbmFnZXIgY291bGQgYXBwcmVjaWF0ZT8NCg0KIyBMb2FkaW5nIHRoZSBEYXRhIGFuZCBEYXRhIERlc2NyaXB0aW9uDQoNCioqQ291cnNlIGRhdGFzZXQqKg0KDQpUaGlzIGlzIHRoZSB0cmFpbmluZyBkYXRhIHRvIGdldCB5b3Ugc3RhcnRlZCB0aGF0IHdpbGwgYmUgdGhlIGJhc2lzIGZvciBtb3N0IG9mIHRoZSBjYXBzdG9uZS4gWW91IG11c3QgZG93bmxvYWQgdGhlIGRhdGEgZnJvbSB0aGUgbGluayBiZWxvdyBhbmQgbm90IGZyb20gZXh0ZXJuYWwgd2Vic2l0ZXMgdG8gc3RhcnQuDQoNCiAgLSBodHRwczovL2QzOTZxdXN6YTQwb3JjLmNsb3VkZnJvbnQubmV0L2Rzc2NhcHN0b25lL2RhdGFzZXQvQ291cnNlcmEtU3dpZnRLZXkuemlwDQoNCmBgYHtyIExvYWRpbmcgdGhlIERhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9RkFMU0UsIGVjaG89VFJVRX0NCnBhdGgxPC1OVUxMDQpwYXRoMSA8LSAiQzovUHVibGljL25scCINCg0KZm9yKGkgaW4gbGlzdC5maWxlcyhwYXRoID0gcGF0aDEsIHBhdHRlcm4gPSAiXFwuemlwJCIpKSB7DQogIHVuemlwKGksIG92ZXJ3cml0ZSA9IFRSVUUpDQp9DQpgYGANCg0KIyMgR2V0dGluZyB0aGUgRmlsZW5hbWVzIGFuZCBhbHNvIGdldHRpbmcgY29ubmVjdGlvbnMgdG8gcmVhZCB0aGUgVGV4dCBmaWxlcw0KDQpgYGB7ciBjaGVja2luZyB0aGUgZmlsZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRSwgZWNobz1UUlVFfQ0KcGF0aDEgPC0gIkM6L1B1YmxpYy9ubHAiDQpmb2xkZXJuYW1lcyA8LSBjKCJERSIsICJVUyIsICJGSSIsICJSVSIpDQpmaWxlbmFtZXNfbjwtY2hhcmFjdGVyKDApDQpmb3IgKGogaW4gbGlzdC5kaXJzKHBhdGggPSBwYXRoMSkpIHsNCiAgaWYgKGxlbmd0aCh1bmxpc3Qoc3Ryc3BsaXQoYmFzZW5hbWUoaiksIl8iKVtbMV1dKSkgPT0gMikgew0KICAgIGZvciAoayBpbiBsaXN0LmZpbGVzKHBhdGggPSBqKSkgew0KICAgICAgaWYgKGdyZXBsKCIudHh0IixrLCBwZXJsID0gVFJVRSkpIHsNCiAgICAgICAgZmlsZW5hbWVzX24gPC0gYyhmaWxlbmFtZXNfbiwgdW5saXN0KHN0cnNwbGl0KGssICJcXC50eHQiKSlbMV0pIA0KICAgICAgfQ0KICB9DQogIH0NCn0NCmBgYA0KDQojIyBHZXR0aW5nIHRoZSBEYXRhIHRoYXQgaXMgb2YgQ29uY2Vybg0KDQogIC0gTG9hZGluZyB0aGUgZGF0YSBpbi4gVGhpcyBkYXRhc2V0IGlzIGZhaXJseSBsYXJnZS4gV2UgZW1waGFzaXplIHRoYXQgeW91IGRvbid0IG5lY2Vzc2FyaWx5IG5lZWQgdG8gbG9hZCB0aGUgZW50aXJlIGRhdGFzZXQgaW4gdG8gYnVpbGQgeW91ciBhbGdvcml0aG1zIChzZWUgcG9pbnQgMiBiZWxvdykuIEF0IGxlYXN0IGluaXRpYWxseSwgeW91IG1pZ2h0IHdhbnQgdG8gdXNlIGEgc21hbGxlciBzdWJzZXQgb2YgdGhlIGRhdGEuDQogIC0gU2FtcGxpbmcuIFRvIHJlaXRlcmF0ZSwgdG8gYnVpbGQgbW9kZWxzIHlvdSBkb24ndCBuZWVkIHRvIGxvYWQgaW4gYW5kIHVzZSBhbGwgb2YgdGhlIGRhdGEuIA0KIA0KDQpgYGB7ciBjaGVja2luZyB0aGUgZmlyc3QgZmV3IGxpbmVzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPVRSVUUsIGVjaG89VFJVRX0NCmxpYnJhcnkoc3RyaW5naSkNCmxpYnJhcnkoc3RyaXApDQoNCmNvdW50ZXJuPDwtMA0KbGluZXM8LU5VTEwNCmZpbGVzaXplIDwtIGMoKQ0KDQpmb3IgKGogaW4gbGlzdC5kaXJzKHBhdGggPSBwYXRoMSkpIHsNCiAgI3ByaW50KHVubGlzdChzdHJzcGxpdChiYXNlbmFtZShqKSwiXyIpW1sxXV0pWzFdKQ0KICBpZiAoKGxlbmd0aCh1bmxpc3Qoc3Ryc3BsaXQoYmFzZW5hbWUoaiksIl8iKVtbMV1dKSkgPT0gMikgJiAodW5saXN0KHN0cnNwbGl0KGJhc2VuYW1lKGopLCJfIilbWzFdXSlbMV0gPT0gImVuIikgKSB7DQogICAgIGZvciAoayBpbiBsaXN0LmZpbGVzKHBhdGggPSBqKSkgew0KICAgICAgICMgR3JlcCBiYXNlZCBvbiBDYXRlZ29yaWVzIGFuZCB0aGVuIHJlYWQNCiAgICAgICBpZiAoZ3JlcGwoIi50eHQiLCBrLCBwZXJsID0gVFJVRSkpIHsNCiAgICAgICAgIGNvdW50ZXJuPDwtY291bnRlcm4rMQ0KICAgICAgICAgcGF0aHRlbXAgPC0gcGFzdGUoaixrLCBzZXAgPSAiLyIpDQogICAgICAgICBmaWxlc2l6ZVtjb3VudGVybl08LWZpbGUuc2l6ZShwYXRodGVtcCkNCiAgICAgICAgIG5hbSA8LSB1bmxpc3Qoc3RyaXAoc3Ryc3BsaXQocGF0aHRlbXAsICIvIikpKVs2XQ0KICAgICAgICAgYXNzaWduKG5hbSwgTlVMTCkNCiAgICAgICAgIGNvbm50ZW1wIDwtIGZpbGUocGF0aHRlbXAsIG9wZW4gPSAiciIpDQogICAgICAgICBsaW5lcyA8LSByZWFkTGluZXMoY29ubnRlbXApDQogICAgICAgICBhc3NpZ24obmFtICwgbGluZXMpDQogICAgICAgICBjbG9zZShjb25udGVtcCkgDQogICAgICAgfQ0KICAgfQ0KICB9DQp9DQoNCmBgYA0KDQojIERhdGEgQ2xlYW5pbmcgYW5kIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhDQoNCiAgLSBUYWtpbmcgYSBTbWFsbCBTYW1wbGUgYXMgdGhlIGRhdGEgaXMgcXVpdGUgaHVnZSB0byBwcm9jZXNzIGFuZCBjbGVhbiBpbiBvbmUgZ28NCiAgLSBTdWJzYW1wbGluZyB0aGUgZW50aXJlIHRleHQgYXMgdGhlIHdob2xlIGlzIHZlcnkgbGFyZ2UgdG8gcHJvY2VzcyBoZXJlDQogIC0gTWFraW5nIGEgY29ycHVzIGhlcmUgZm9yIGFsbCB0aHJlZSBzbWFsbGVyIHN1YiBzYW1wbGVzIHVzaW5nIHRoZSAidG0iIHBhY2thZ2UNCiAgLSBPbmUgdGhpbmcgdGhvdWdoIHRoZSBzaXplIG9mIHRoZSBjb3JwdXMgZm9yIGV2ZW4gdGhlIHNhbXBsZXMgYXJlIHF1aXRlIGxhcmdlLCB0aGlzIG1ha2VzIGEgY29uY2x1c2lvbiB0aGF0IHdlIG1pZ2h0IGhhdmUgdG8gdGhpbmsgb2YgYSBkaWZmZXJlbnQgYXBwcm9hY2ggdG8gc3RvcmUgdGhlc2UgYXMgYSB3aG9sZQ0KDQpgYGB7ciBGaW5kIHRoZSBGcmVxdWVuY3kgb2Ygd29yZHMgZm9yIGFsbCB0aGUgdGhyZWUgZmlsZXMsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGRhdGEudGFibGUpDQoNCmVuX1VTLmJsb2dzLnR4dC5kdDwtYXMuZGF0YS50YWJsZShlbl9VUy5ibG9ncy50eHQpDQplbl9VUy5uZXdzLnR4dC5kdDwtYXMuZGF0YS50YWJsZShlbl9VUy5uZXdzLnR4dCkNCmVuX1VTLnR3aXR0ZXIudHh0LmR0PC1hcy5kYXRhLnRhYmxlKGVuX1VTLnR3aXR0ZXIudHh0KQ0KDQpEZXNjLmR0IDwtIGRhdGEudGFibGUoRmlsZSA9IGMoIm5fVVMuYmxvZ3MudHh0IiwgImVuX1VTLm5ld3MudHh0IiwgImVuX1VTLnR3aXR0ZXIudHh0IiksIHNpemUgPSBmaWxlc2l6ZS8xMF42LCBsaW5lY291bnQgPSBjKGVuX1VTLmJsb2dzLnR4dC5kdFssLk5dLCBlbl9VUy5uZXdzLnR4dC5kdFssLk5dLCBlbl9VUy50d2l0dGVyLnR4dC5kdFssIC5OXSkpDQoNCkRlc2MuZHQNCg0Kc2FtcGxlX3NpemUgPC0gMC4wMyAjb25seSAzJSBmb3Igbm93DQoNCmJsb2dzX3N1Yj1OVUxMDQp1c25ld3Nfc3ViPU5VTEwNCnVzdHdpdHRlcl9zdWI9TlVMTA0KYmxvZ3Nfc3ViIDwtIGVuX1VTLmJsb2dzLnR4dFsxOmFzLmludGVnZXIoc2FtcGxlX3NpemUqbGVuZ3RoKGVuX1VTLmJsb2dzLnR4dCkpXQ0KdXNuZXdzX3N1YiA8LSBlbl9VUy5uZXdzLnR4dFsxOmFzLmludGVnZXIoc2FtcGxlX3NpemUqbGVuZ3RoKGVuX1VTLm5ld3MudHh0KSldDQp1c3R3aXR0ZXJfc3ViIDwtIGVuX1VTLm5ld3MudHh0WzE6YXMuaW50ZWdlcihzYW1wbGVfc2l6ZSpsZW5ndGgoZW5fVVMudHdpdHRlci50eHQpKV0NCg0KIyBVc2luZyB0aGUgdG0gcGFja2FnZSB0byBjcmVhdGUgYSBjb3JwdXMNCg0KbGlicmFyeSh0bSkNCmNyZWF0ZUNvcnB1cyA8LSBmdW5jdGlvbihsaXN0dmFsKSB7DQogIHZzIDwtIFZlY3RvclNvdXJjZShsaXN0dmFsKQ0KICBDb3JwdXModnMsIHJlYWRlckNvbnRyb2wgPSBsaXN0KHJlYWRQbGFpbixsYW5ndWFnZT0iZW4iLCBsb2FkPVRSVUUpKQ0KfQ0KDQpjb3JwdXNibG9ncz1OVUxMDQpjb3JwdXN1c25ld3M9TlVMTA0KY29ycHVzdHdpdHRlcj1OVUxMDQpjb3JwdXNibG9ncyA8LSBjcmVhdGVDb3JwdXMoYmxvZ3Nfc3ViKQ0KY29ycHVzdXNuZXdzIDwtIGNyZWF0ZUNvcnB1cyh1c25ld3Nfc3ViKQ0KY29ycHVzdHdpdHRlciA8LSBjcmVhdGVDb3JwdXModXN0d2l0dGVyX3N1YikNCg0KYGBgDQoNCiMjIEFuYWx5emluZyB0aGUgY29ycHVzIGhlcmUgYW5kIENsZWFuaW5nIHVzaW5nIHRoZSAidG0iIHBhY2thZ2UNCg0KICAtIENoZWNraW5nIGZvciBQdW50dWF0aW9ucyAsIG51bWJlcnMgLCBwZXJpb2RzLCBoeXBoZW5zICBldGMgYW5kIHJlbW92aW5nIHRoZW0NCiAgLSBDb252ZXJ0aW5nIHRoZSBlbnRpcmUgZG9jdW1lbnQgdG8gbG93ZXIgY2FzZQ0KICAtIFJlbW92aW5nIHN0b3B3b3JkcyAoZXh0cmVtZWx5IGNvbW1vbiB3b3JkcyBzdWNoIGFzICJhbmQiLCAib3IiLCAibm90IiwgImluIiwgImlzIiBldGMpDQogICAgKyBUaGlzIGlzIGFuIGludGVyZXN0aW5nIGVmZmVjdCBiYXNlZCBvbiB0aGUgZmFjdCB0aGF0IE4tZ3JhbXMgbWlnaHQgYmUgYWZmZWN0ZWQgZHVlIHRvIHRoaXMNCiAgICArIFVwdG8gdG8gdXMgaWYgd2Ugd2FudCB0byB0YWtlIHRoaXMgb3V0IG9yIG5vdCwgSSBhbSBkZWNpZGluZyB0byB0YWtlIHRoZXNlIG91dA0KICAtIFJlbW92aW5nIG51bWJlcnMNCiAgLSBGaWx0ZXJpbmcgb3V0IHVud2FudGVkIHRlcm1zIGFuZCB3ZWlyZCBjaGFyYWN0ZXJzDQogIC0gUmVtb3ZpbmcgZXh0cmEgd2hpdGVzcGFjZQ0KDQoNCmBgYHtyIEFuYWx5c2luZyB0aGUgY29ycHVzLCBlY2hvPVRSVUUsIGV2YWw9VFJVRSx3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KIyBIb3cgdG8gc2VlIGEgbGluZSB3aGVuIGluIFZjb3JwdXMgZm9ybWF0DQpsYXBwbHkoYygxOjMpLCBmdW5jdGlvbih4KSB7DQogc3Ryd3JhcChjb3JwdXNibG9nc1tbeF1dKSANCn0pDQoNCiMgVG8gTG93ZXIgY2FzZQ0KY29ycHVzYmxvZ3NfcHJvYzEgPC0gdG1fbWFwKGNvcnB1c2Jsb2dzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKSANCmNvcnB1c3VzbmV3c19wcm9jMSA8LSB0bV9tYXAoY29ycHVzdXNuZXdzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0KY29ycHVzdHdpdHRlcl9wcm9jMSA8LSB0bV9tYXAoY29ycHVzdHdpdHRlciwgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkNCg0KI1JlcGxhY2luZyB0aGUgRnVsbCBzdG9wcyBhbmQgY29tbWFzIGFuZCBwaW5jdHVhdGlvbnMgDQpjb3JwdXNibG9nc19wcm9jMSA8LSB0bV9tYXAoY29ycHVzYmxvZ3NfcHJvYzEsIHJlbW92ZVB1bmN0dWF0aW9uKSANCmNvcnB1c3VzbmV3c19wcm9jMSA8LSB0bV9tYXAoY29ycHVzdXNuZXdzX3Byb2MxLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0KY29ycHVzdHdpdHRlcl9wcm9jMSA8LSB0bV9tYXAoY29ycHVzdHdpdHRlcl9wcm9jMSwgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkNCg0KI1JlbW92aW5nIFdoaXRlc3BhY2UNCmNvcnB1c2Jsb2dzX3Byb2MxIDwtIHRtX21hcChjb3JwdXNibG9nc19wcm9jMSwgc3RyaXBXaGl0ZXNwYWNlKSANCmNvcnB1c3VzbmV3c19wcm9jMSA8LSB0bV9tYXAoY29ycHVzdXNuZXdzX3Byb2MxLCBzdHJpcFdoaXRlc3BhY2UpDQpjb3JwdXN0d2l0dGVyX3Byb2MxIDwtIHRtX21hcChjb3JwdXN0d2l0dGVyX3Byb2MxLCBzdHJpcFdoaXRlc3BhY2UpDQoNCiNSZW1vdmluZyBOdW1iZXJzDQpjb3JwdXNibG9nc19wcm9jMSA8LSB0bV9tYXAoY29ycHVzYmxvZ3NfcHJvYzEsIHJlbW92ZU51bWJlcnMpIA0KY29ycHVzdXNuZXdzX3Byb2MxIDwtIHRtX21hcChjb3JwdXN1c25ld3NfcHJvYzEsIHJlbW92ZU51bWJlcnMpDQpjb3JwdXN0d2l0dGVyX3Byb2MxIDwtIHRtX21hcChjb3JwdXN0d2l0dGVyX3Byb2MxLCByZW1vdmVOdW1iZXJzKQ0KDQojUmVtb3Zpbmcgd2VpcmQgY2hhcmFjdGVycyBhbmQgQVNDSUkNCnRvU3BhY2UgPC0gY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbiAoeCAsIHBhdHRlcm4pIGdzdWIocGF0dGVybiwgIiAiLCB4KSkNCmNvcnB1c2Jsb2dzX3Byb2MxIDwtIHRtX21hcChjb3JwdXNibG9nc19wcm9jMSwgdG9TcGFjZSwgIuI/Pz8iKSANCmNvcnB1c3VzbmV3c19wcm9jMSA8LSB0bV9tYXAoY29ycHVzdXNuZXdzX3Byb2MxLCB0b1NwYWNlLCAi4j8/PyIpDQpjb3JwdXN0d2l0dGVyX3Byb2MxIDwtIHRtX21hcChjb3JwdXN0d2l0dGVyX3Byb2MxLCB0b1NwYWNlLCAi4j8/PyIpDQoNCnRvTm9ybWFsIDwtIGNvbnRlbnRfdHJhbnNmb3JtZXIoZnVuY3Rpb24gKHgpIGljb252KHgsICJsYXRpbjEiLCAiQVNDSUkiLCBzdWI9IiIpKQ0KY29ycHVzYmxvZ3NfcHJvYzEgPC0gdG1fbWFwKGNvcnB1c2Jsb2dzX3Byb2MxLCB0b05vcm1hbCkgDQpjb3JwdXN1c25ld3NfcHJvYzEgPC0gdG1fbWFwKGNvcnB1c3VzbmV3c19wcm9jMSwgdG9Ob3JtYWwpDQpjb3JwdXN0d2l0dGVyX3Byb2MxIDwtIHRtX21hcChjb3JwdXN0d2l0dGVyX3Byb2MxLCB0b05vcm1hbCkNCg0KDQojUmVtb3ZpbmcgZW5nbGlzaCBTdG9wIHdvcmRzIGlzIGEgQ2hvaWNlDQpjb3JwdXNibG9nc19wcm9jMSA8LSB0bV9tYXAoY29ycHVzYmxvZ3NfcHJvYzEsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkgDQpjb3JwdXN1c25ld3NfcHJvYzEgPC0gdG1fbWFwKGNvcnB1c3VzbmV3c19wcm9jMSwgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQ0KY29ycHVzdHdpdHRlcl9wcm9jMSA8LSB0bV9tYXAoY29ycHVzdHdpdHRlcl9wcm9jMSwgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQ0KDQoNCiNzdHJ3cmFwKGNvcnB1c2Jsb2dzX3Byb2MxW1sxXV0pDQojaWNvbnYoc3Ryd3JhcChjb3JwdXNibG9nc19wcm9jMVtbMV1dKSwgImxhdGluMSIsICJBU0NJSSIsIHN1YiA9ICIiKSAgDQoNCmBgYA0KDQoNCiMgQnVpbGQgYSBEYXRhIHRhYmxlIGZvciB0aGUgdGV4dCBwcmVzZW50IChPYnZpb3VzIGFzIHRoZXkgcmlkaWN1bG91c2x5IG9wdGltaXplZCB0byBoYW5kbGUgYmlnIGRhdGEhISkgDQogIC0gQmxvZ3MgZGF0YSBjb25zaWRlcmVkIGFuZCBwbG90dGVkIGhlcmUNCiAgLSBGaXJzdCBDb252ZXJ0IHRoZSBDb3JwdXMgaW50byBhIERhdGEtdGFibGUgZm9yIGJldHRlciBoYW5kbGluZw0KICAtIEdldHRpbmcgdGhlIEZyZXF1ZW5jeSBvZiB3b3JkcyBhbmQgcGxvdHRpbmcNCg0KYGBge3IgRmluZCB0aGUgRnJlcXVlbmN5IERpc3RyaWJ1dGlvbnMgb2YgdGhlIFdvcmRzLCBtZXNzYWdlPUZBTFNFLCBldmFsPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5dGV4dCkNCmxpYnJhcnkoZ2dwbG90MikNCg0KY29ycHVzYmxvZ3NfcHJvYzEuZHQgPC0gTlVMTA0KY29ycHVzdXNuZXdzX3Byb2MxLmR0PC1OVUxMDQpjb3JwdXN0d2l0dGVyX3Byb2MxLmR0PC1OVUxMDQoNCiMgQmxvZ3MNCmNvcnB1c2Jsb2dzX3Byb2MxLmR0IDwtIGRhdGEudGFibGUodGV4dD1zYXBwbHkoY29ycHVzYmxvZ3NfcHJvYzEsIGlkZW50aXR5KSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpIA0KY29ycHVzYmxvZ3NfcHJvYzEuZHQudGlkeTwtY29ycHVzYmxvZ3NfcHJvYzEuZHQgJT4lIHVubmVzdF90b2tlbnMod29yZCx0ZXh0KQ0KY29ycHVzYmxvZ3NfcHJvYzEuZHQudGlkeSAlPiUgY291bnQod29yZCwgc29ydD1UUlVFKSAlPiUNCmZpbHRlcihuPjE1MDApICU+JQ0KbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUNCmdncGxvdChhZXMod29yZCwgbikpICsNCiAgZ2VvbV9jb2woKSsNCiAgeGxhYigiV29yZHMiKSsNCiAgY29vcmRfZmxpcCgpKw0KICBnZ3RpdGxlKCJXb3JkIENvdW50IC0gQkxPR1MiKQ0KDQoNCg0KI3VzbmV3cw0KY29ycHVzdXNuZXdzX3Byb2MxLmR0PC1kYXRhLnRhYmxlKHRleHQ9c2FwcGx5KGNvcnB1c3VzbmV3c19wcm9jMSwgaWRlbnRpdHkpLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmNvcnB1c3VzbmV3c19wcm9jMS5kdC50aWR5PC1jb3JwdXN1c25ld3NfcHJvYzEuZHQgJT4lIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkNCmNvcnB1c3VzbmV3c19wcm9jMS5kdC50aWR5ICU+JSBjb3VudCh3b3JkLCBzb3J0PVRSVUUpICU+JQ0KICBmaWx0ZXIobj43NSkgJT4lDQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQpnZ3Bsb3QoYWVzKHdvcmQsIG4pKSArDQogIGdlb21fY29sKCkrDQogIHhsYWIoIldvcmRzIikrDQogIGNvb3JkX2ZsaXAoKSsNCiAgZ2d0aXRsZSgiV29yZCBDb3VudCAtIFVTIE5FV1MiKQ0KDQoNCiNUd2l0dGVyDQpjb3JwdXN0d2l0dGVyX3Byb2MxLmR0PC1kYXRhLnRhYmxlKHRleHQ9c2FwcGx5KGNvcnB1c3R3aXR0ZXJfcHJvYzEsIGlkZW50aXR5KSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpjb3JwdXN0d2l0dGVyX3Byb2MxLmR0LnRpZHk8LWNvcnB1c3R3aXR0ZXJfcHJvYzEuZHQgJT4lIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkNCmNvcnB1c3R3aXR0ZXJfcHJvYzEuZHQudGlkeSAlPiUgY291bnQod29yZCwgc29ydD1UUlVFKSAlPiUNCiAgZmlsdGVyKG4+MjAwMCkgJT4lDQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQogIGdncGxvdChhZXMod29yZCwgbikpICsgDQogIGdlb21fY29sKCkrDQogIHhsYWIoIldvcmRzIikrDQogIGNvb3JkX2ZsaXAoKSsNCiAgZ2d0aXRsZSgiV29yZCBDb3VudCAtIFR3aXR0ZXIiKQ0KDQpgYGANCg0KIyMgVG9rZW5pemluZyBiYXNlZCBvbiBBZGphY2VudCB3b3Jkcw0KICAtIENvbnNlY3V0aXZlIHNlcXVlbmNlIG9mIHdvcmRzIGNhbGxlZCAibi1ncmFtcyINCiAgLSBCaWdyYW0NCiAgLSBUaGVyZSBzZWVtcyBhIGxvdCBvZiBjb3VudCBvZiBCaWdyYW1zIHN1Y2ggYXMgImRvbnQiLCAid29udCIgLi4gc29tZXdvcmRzIHRoYXQgZW5kcyBvbiBhIHQgLCB3ZSB3b3VsZCBuZWVkIHRvIHJlbW92ZSB0aG9zZS4NCiAgDQoNCmBgYHtyIFVzaW5nIHRoZSBTYW1lIHN0cnVjdHVyZSBhcyBhYm92ZSBidXQgd2l0aCBuLWdyYW1zLCBldmFsPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KbGlicmFyeSh0aWR5cikNCmwgPSBsaXN0KGNvcnB1c2Jsb2dzX3Byb2MxLmR0LCBjb3JwdXN1c25ld3NfcHJvYzEuZHQsY29ycHVzdHdpdHRlcl9wcm9jMS5kdCkNCmNvbWIuZHQ8LXJiaW5kbGlzdChsKQ0KDQpjb21iLmR0LmJpZ3JhbSA8LSAgY29tYi5kdCAlPiUgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuPSJuZ3JhbXMiLCBuPTIpDQoNCiNSZW1vdmluZyB0aGUgd29yZHMgdGhhdCBlbmQgd2l0aCB0IGFuZCBzdG9wIHdvcmRzDQoNCmNvbWIuZHQuYmlncmFtX3NlcGFyYXRlZDwtY29tYi5kdC5iaWdyYW0gJT4lIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCJ3b3JkMiIpLCBzZXA9IiAiKQ0KDQpjb21iLmR0LmJpZ3JhbV9maWx0ZXJlZCA8LSBjb21iLmR0LmJpZ3JhbV9zZXBhcmF0ZWQgJT4lDQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQ0KICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKQ0KDQpjb21iLmR0LmJpZ3JhbV9maWx0ZXJlZCA8LSBjb21iLmR0LmJpZ3JhbV9maWx0ZXJlZCAlPiUNCiAgZmlsdGVyKCF3b3JkMiAlaW4lICJ0IikNCg0KY29tYi5kdC5iaWdyYW1fdW5pdGVkIDwtIGNvbWIuZHQuYmlncmFtX2ZpbHRlcmVkICU+JQ0KICB1bml0ZShiaWdyYW0sIHdvcmQxLCB3b3JkMiwgc2VwPSIgIikNCg0KY29tYi5kdC5iaWdyYW1fdW5pdGVkICU+JSBjb3VudChiaWdyYW0sIHNvcnQ9VFJVRSkgJT4lDQogIGZpbHRlcihuPjEwMCkgJT4lDQogIG11dGF0ZShiaWdyYW0gPSByZW9yZGVyKGJpZ3JhbSwgbikpICU+JQ0KICBnZ3Bsb3QoYWVzKGJpZ3JhbSwgbikpICsNCiAgZ2VvbV9jb2woKSsNCiAgeGxhYigiYmlncmFtIikrDQogIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoIkJpZ3JhbSBDb3VudCAtIENvbWJpbmVkIERhdGFzZXQiKQ0KICANCmBgYA0KDQojIyBUcmlncmFtIEZyZXF1ZW5jeSBEZXRlcm1pbmF0aW9uIA0KICAtIEZvciB0aGlzIGNhc2UgLCBXZSBkZWNpZGUgdG8ga2VlcCBhbGwgdGhlIHN0b3Agd29yZHMgZXRjIC4uIGFuZCBzZWUgaG93IGl0IGdvZXMgDQogIC0gV29yZCBDbG91ZCBhbmQgdGhlIGZyZXF1ZW5jeSBkaXN0cmlidXRpb24gZm9yIGFsbCB0aGUgdHJpZ3JhbXMNCg0KDQpgYGB7ciBTYW1lIGNvZGUgYXMgQmlncmFtLCBldmFsPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHdvcmRjbG91ZCkNCmNvbWIuZHQudHJpZ3JhbSA8LSBjb21iLmR0ICU+JSB1bm5lc3RfdG9rZW5zKHRyaWdyYW0sIHRleHQsIHRva2VuPSJuZ3JhbXMiLCBuPTMpDQpzZXQuc2VlZCgxMjM0KQ0KY29tYi5kdC5maW5hbCA8LSBjb21iLmR0LnRyaWdyYW0gJT4lIGNvdW50KHRyaWdyYW0gLCBzb3J0PVRSVUUpDQp3b3JkY2xvdWQod29yZHMgPSBjb21iLmR0LmZpbmFsJHRyaWdyYW0sIGZyZXEgPSBjb21iLmR0LmZpbmFsJG4sIG1heC53b3JkcyA9IDEwMCwgY29sb3JzID0gYnJld2VyLnBhbCg2LCJEYXJrMiIpKSANCg0KIyBwbG90IG9mIGZyZXF1ZW5jaWVzIA0KY29tYi5kdC5maW5hbCAlPiUNCiAgZmlsdGVyKG4+NTApICU+JQ0KZ2dwbG90KGFlcyh0cmlncmFtLCBuKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgeGxhYigidHJpZ3JhbSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgZ2d0aXRsZSgiVHJpZ3JhbSBDb3VudCAtIENvbWJpbmVkIERhdGFzZXQiKQ0KDQpgYGANCg0KIyMgUGxvdHRpbmcgVG9wIDEwIFVuaWdyYW0sIEJpZ3JhbSAmIFRyaWdyYW0gZm9yIEJsb2dzLCBuZXdzIGFuZCB0d2l0dGVyIGRhdGFzZXQgaW4gb25lIFBsb3QNCiAgLSBGb3IgdGhpcyBjYXNlIEkgZGlkIG5vdCBkbyBhIGxvdCBvZiBmaWx0ZXJpbmcgLCBhcyBpIHdhbnRlZCB0byBjaGVjayB0aGUgcmF3IHRvcCAxMCANCg0KYGBge3IgQ29tYmluZWQtUGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbD1UUlVFfQ0KcGFyKG1mcm93PWMoMywzKSkNCmRhdGEuYmxvZ3MgPSBjb3JwdXNibG9nc19wcm9jMS5kdC50aWR5ICU+JSBjb3VudCh3b3JkLCBzb3J0PVRSVUUpIA0KZGF0YS5uZXdzID0gY29ycHVzdXNuZXdzX3Byb2MxLmR0LnRpZHkgJT4lIGNvdW50KHdvcmQsIHNvcnQ9VFJVRSkNCmRhdGEudHdpdHRlciA9IGNvcnB1c3R3aXR0ZXJfcHJvYzEuZHQudGlkeSAlPiUgY291bnQod29yZCwgc29ydD1UUlVFKQ0KDQoNCmdnYmxvZzEgPC0gZ2dwbG90KGRhdGEgPSBoZWFkKGRhdGEuYmxvZ3MsMTApKSArIGdlb21fYmFyKGFlcyh4PXdvcmQsIHk9biksIHN0YXQ9ImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyB4bGFiKCJVbmlncmFtIEJsb2cgV29yZHMiKQ0KZ2duZXdzMSA8LSBnZ3Bsb3QoZGF0YSA9IGhlYWQoZGF0YS5uZXdzLDEwKSkgKyBnZW9tX2JhcihhZXMoeD13b3JkLCB5PW4pLCBzdGF0PSJpZGVudGl0eSIpICsgY29vcmRfZmxpcCgpICsgeGxhYigiVW5pZ3JhbSBuZXdzIFdvcmRzIikNCmdndHdpdHRlcjEgPC0gZ2dwbG90KGRhdGEgPSBoZWFkKGRhdGEudHdpdHRlciwxMCkpICsgZ2VvbV9iYXIoYWVzKHg9d29yZCwgeT1uKSwgc3RhdD0iaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArIHhsYWIoIlVuaWdyYW0gdHdpdHRlciBXb3JkcyIpDQoNCmxpc3QxIDwtIGxpc3QoZ2dibG9nMSxnZ25ld3MxLGdndHdpdHRlcjEpDQoNCmRhdGEuYmxvZ3MuYmlncmFtIDwtICBjb3JwdXNibG9nc19wcm9jMS5kdCAlPiUgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuPSJuZ3JhbXMiLCBuPTIpICU+JSBjb3VudChiaWdyYW0sIHNvcnQ9VFJVRSkNCmRhdGEubmV3cy5iaWdyYW0gPC0gIGNvcnB1c3VzbmV3c19wcm9jMS5kdCAlPiUgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuPSJuZ3JhbXMiLCBuPTIpICU+JSBjb3VudChiaWdyYW0sIHNvcnQ9VFJVRSkNCmRhdGEudHdpdHRlci5iaWdyYW0gPC0gIGNvcnB1c3R3aXR0ZXJfcHJvYzEuZHQgJT4lIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbj0ibmdyYW1zIiwgbj0yKSAlPiUgY291bnQoYmlncmFtLCBzb3J0PVRSVUUpDQoNCmdnYmxvZzIgPC0gZ2dwbG90KGRhdGEgPSBoZWFkKGRhdGEuYmxvZ3MuYmlncmFtLDEwKSkgKyBnZW9tX2JhcihhZXMoeD1iaWdyYW0sIHk9biksIHN0YXQ9ImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyB4bGFiKCJCaWdyYW0gQmxvZyBXb3JkcyIpDQpnZ25ld3MyIDwtIGdncGxvdChkYXRhID0gaGVhZChkYXRhLm5ld3MuYmlncmFtLDEwKSkgKyBnZW9tX2JhcihhZXMoeD1iaWdyYW0sIHk9biksIHN0YXQ9ImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyB4bGFiKCJCaWdyYW0gbmV3cyBXb3JkcyIpDQpnZ3R3aXR0ZXIyIDwtIGdncGxvdChkYXRhID0gaGVhZChkYXRhLnR3aXR0ZXIuYmlncmFtLDEwKSkgKyBnZW9tX2JhcihhZXMoeD1iaWdyYW0sIHk9biksIHN0YXQ9ImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyB4bGFiKCJCaWdyYW0gdHdpdHRlciBXb3JkcyIpDQoNCmxpc3QyIDwtIGxpc3QoZ2dibG9nMixnZ25ld3MyLGdndHdpdHRlcjIpDQoNCmRhdGEuYmxvZ3MudHJpZ3JhbSA8LSAgY29ycHVzYmxvZ3NfcHJvYzEuZHQgJT4lIHVubmVzdF90b2tlbnModHJpZ3JhbSwgdGV4dCwgdG9rZW49Im5ncmFtcyIsIG49MykgJT4lIGNvdW50KHRyaWdyYW0sIHNvcnQ9VFJVRSkNCmRhdGEubmV3cy50cmlncmFtIDwtICBjb3JwdXN1c25ld3NfcHJvYzEuZHQgJT4lIHVubmVzdF90b2tlbnModHJpZ3JhbSwgdGV4dCwgdG9rZW49Im5ncmFtcyIsIG49MykgJT4lIGNvdW50KHRyaWdyYW0sIHNvcnQ9VFJVRSkNCmRhdGEudHdpdHRlci50cmlncmFtIDwtICBjb3JwdXN0d2l0dGVyX3Byb2MxLmR0ICU+JSB1bm5lc3RfdG9rZW5zKHRyaWdyYW0sIHRleHQsIHRva2VuPSJuZ3JhbXMiLCBuPTMpICU+JSBjb3VudCh0cmlncmFtLCBzb3J0PVRSVUUpDQoNCmdnYmxvZzMgPC0gZ2dwbG90KGRhdGEgPSBoZWFkKGRhdGEuYmxvZ3MudHJpZ3JhbSwxMCkpICsgZ2VvbV9iYXIoYWVzKHg9dHJpZ3JhbSwgeT1uKSwgc3RhdD0iaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArIHhsYWIoIlRyaWdyYW0gQmxvZyBXb3JkcyIpDQpnZ25ld3MzIDwtIGdncGxvdChkYXRhID0gaGVhZChkYXRhLm5ld3MudHJpZ3JhbSwxMCkpICsgZ2VvbV9iYXIoYWVzKHg9dHJpZ3JhbSwgeT1uKSwgc3RhdD0iaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArIHhsYWIoIlRyaWdyYW0gbmV3cyBXb3JkcyIpDQpnZ3R3aXR0ZXIzIDwtIGdncGxvdChkYXRhID0gaGVhZChkYXRhLnR3aXR0ZXIudHJpZ3JhbSwxMCkpICsgZ2VvbV9iYXIoYWVzKHg9dHJpZ3JhbSwgeT1uKSwgc3RhdD0iaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArIHhsYWIoIlRyaWdyYW0gdHdpdHRlciBXb3JkcyIpDQoNCmxpc3QzIDwtIGxpc3QoZ2dibG9nMyxnZ25ld3MzLGdndHdpdHRlcjMpDQoNCmxpYnJhcnkoZ3JpZCkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KZ3JpZC5hcnJhbmdlKGdyb2JzID0gYyhsaXN0MSxsaXN0MixsaXN0MyksbmNvbCA9IDMsIGFzLnRhYmxlID0gRkFMU0UpDQoNCmBgYA0KDQoNCg0KIyBIb3cgbWFueSB1bmlxdWUgd29yZHMgZG8geW91IG5lZWQgaW4gYSBmcmVxdWVuY3kgc29ydGVkIGRpY3Rpb25hcnkgdG8gY292ZXIgNTAlIG9mIGFsbCB3b3JkIGluc3RhbmNlcyBpbiB0aGUgbGFuZ3VhZ2U/IDkwJT8NCiAgLSBGb3IgVGhlIGN1cnJlbnQgc2FtcGxlIHNpemUgb2YgMyUgLCB3ZSB3aWxsIGNhbGN1bGF0ZSB0aGUgZnJlcXVlbmN5IGRpY3Rpb25hcnkNCiAgLSBCZWxvdyBXZSBzZWUgdGhhdCBhcyBwZXJjZW50YWdlIHRvIGNvdmVyIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgdW5pcXVlIHdvcmRzIGluY3JlYXNlIGRyYW1hdGljYWxseSAsIHNob3dpbmcgdGhhdCByYW5rIG9mIHRoZSB3b3JkIGlzIGludmVyc2VseSBwcm9wb3J0aW9uYWwgdG8gdGhlIGZyZXF1ZW5jeQ0KDQpgYGB7ciB1bmlxdWUgd29yZHMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsZXZhbD1UUlVFfQ0KY29tYi5kdC5mcmVxZGljdDwtY29tYi5kdCAlPiUgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgY291bnQod29yZCwgc29ydD1UUlVFKQ0Kc3Vtb2Z3b3JkcyA8LSBzdW0oY29tYi5kdC5mcmVxZGljdCRuKQ0KDQpwcmludChwYXN0ZSgiVGhlIHRvdGFsIG51bWJlciBvZiB3b3JkcyBwcmVzZW50IGhlcmUiLCBzdW1vZndvcmRzKSkNCnN1bWNvdW50ZXI8PC0wDQpjb3VudGVyPDwtMA0KdmFsdGVtcDwtbGFwcGx5KGNvbWIuZHQuZnJlcWRpY3QkbiwgZnVuY3Rpb24oeCl7DQogIGlmIChzdW1jb3VudGVyPD0wLjUqc3Vtb2Z3b3Jkcykgew0KICAgc3VtY291bnRlcjw8LXN1bWNvdW50ZXIreA0KICAgY291bnRlcjw8LWNvdW50ZXIrMQ0KICB9DQp9KQ0KDQpwcmludChwYXN0ZSgiVGhlIG51bWJlciBvZiB1bmlxdWUgd29yZHMgcmVxdWlyZWQgdG8gY292ZXIgNTAlIG9mIGFsbCB3b3JkcyBpbiBjYXNlIG9mIGEgc2FtcGxlIHNpemUgb2YgMyUgPSAiLCBjb3VudGVyKSkNCg0Kc3VtY291bnRlcjw8LTANCmNvdW50ZXI8PC0wDQp2YWx0ZW1wPC1sYXBwbHkoY29tYi5kdC5mcmVxZGljdCRuLCBmdW5jdGlvbih4KXsNCiAgaWYgKHN1bWNvdW50ZXI8PTAuOSpzdW1vZndvcmRzKSB7DQogICBzdW1jb3VudGVyPDwtc3VtY291bnRlcit4DQogICBjb3VudGVyPDwtY291bnRlcisxDQogIH0NCn0pDQoNCnByaW50KHBhc3RlKCJUaGUgbnVtYmVyIG9mIHVuaXF1ZSB3b3JkcyByZXF1aXJlZCB0byBjb3ZlciA5MCUgb2YgYWxsIHdvcmRzIGluIGNhc2Ugb2YgYSBzYW1wbGUgc2l6ZSBvZiAzJSA9ICIsIGNvdW50ZXIpKQ0KDQpgYGANCg0KIyMgSG93IGRvIHlvdSBldmFsdWF0ZSBob3cgbWFueSBvZiB0aGUgd29yZHMgY29tZSBmcm9tIGZvcmVpZ24gbGFuZ3VhZ2VzPw0KIC0gUmlnaHQgbm93IEkgZGlkIG5vdCBkZWFsIHdpdGggZm9yZWlnbiBsYW5ndWFnZSB3b3JkcyBhcyBpdCBzZWVtcyBhZnRlciB0aGUgRnJlcXVlbmN5IGRpc3RyaWJ1dGlvbnMgb2YgdGhlIGVuZ2xpc2ggd29yZHMgLCB0aGVzZSB3b3JkcyBhcmUgaGF2ZSB2ZXJ5IGxvdyBwcmVzZW5jZSAsIGFuZCBpZiBwcmVzZW50IHRoZXkgbmVlZCB0byBjb21wYXJlZCBiYXNlZCBvbiBhIGhhc2htYXAgb3IgZGljdGlvbmFyeSBkYXRhIGJhc2Ugb2Ygc29tZSBzb3J0IA0KIA0KIyMgQ2FuIHlvdSB0aGluayBvZiBhIHdheSB0byBpbmNyZWFzZSB0aGUgY292ZXJhZ2UgLS0gaWRlbnRpZnlpbmcgd29yZHMgdGhhdCBtYXkgbm90IGJlIGluIHRoZSBjb3Jwb3JhIG9yIHVzaW5nIGEgc21hbGxlciBudW1iZXIgb2Ygd29yZHMgaW4gdGhlIGRpY3Rpb25hcnkgdG8gY292ZXIgdGhlIHNhbWUgbnVtYmVyIG9mIHBocmFzZXM/IA0KICAtIFRoaXMgaXMgYW4gaW50ZXJlc3RpbmcgcXVlc3Rpb24gdGhhdCBpbnZvbHZlcyBwcmVkaWN0aW9uIGJhc2VkIG9uIHNtYWxsZXIgc2FtcGxlIHNpemUsIHdlIGNhbiB1c2UgaGlnaGVyIG9yZGVyIG4tZ3JhbXMgd2l0aCBoaWdoZXIgZnJlcXVlbmN5IGFuZCByZW1vdmUgdGhlIGxvd2VyIGZyZXF1ZW5jeSBvbmVzIHRvIHByZWRpY3Qgd2hpY2ggd29yZHMgYXJlIG1vcmUgcHJvYmFibGUgdG8gYXBwZWFyIGluIGEgbGFyZ2VyIFBvcHVsYXRpb24gc2l6ZSAuDQoNCiMjIFN1bW1hcnkgb2YgRmluZGluZ3MNCg0KICAtICoqWmlwZidzIExhdyoqIC0gVGhlIEZyZXF1ZW5jeSB0aGF0IGEgd29yZCBhcHBlYXJzIGlzIGludmVyc2VseSBwcm9wb3J0aW9uYWwgdG8gdGhlIHJhbmsgb2YgdGhlIHdvcmQNCiAgLSBJbml0aWFsbHkgSSB0aG91Z2h0IG9mIHVzaW5nIHRoZSB0ZG0gZnVuY3Rpb24gdG8gZm9ybSBhIHRlbSBkb2N1bWVudCBtYXRyaXggZnJvbSB0aGUgY29ycHVzIGRhdGEsIGJ1dCBpdCBzZWVtcyB0aGF0IGlzIG5vdCBwb3NzaWJsZSB0byBwcm9jZXNzIGR1ZSB0byBzaXplIGxpbWl0YXRpb25zIGFuZCBoZW5jZSBJIGRlY2lkZWQgdG8gZ28gd2l0aCBkYXRhIHRhYmxlcyBpbnN0ZWFkICwgd2hpY2ggYXMgZXhwZWN0ZWQgYXJlIGdvb2Qgd2l0aCBsYXJnZSBkYXRhc2V0cw0KICAtIEkgYWxzbyBzZWUgZXZlbiBhZnRlciBhIGxvdCBvZiBmaWx0ZXJpbmcgLCBzb21lIHdvcmRzIGxpa2UgKndvbid0ICwgZG9uJ3QsIGNhbid0KiAsIG5lZWQgdG8gZmlsdGVyZWQgb3V0IG9mIHRoZSAyLSBvciAzLSBuIGdyYW1zIGFzIHRoZXkgd2lsbCBwcm9kdWNlIG1pc2xlYWRpbmcgZnJlcXVlbmN5IGRpc3RyaWJ1dGlvbnMNCiAgLSBTYW1wbGluZyBvbmx5IDMlIG9mIHRoZSBkYXRhIHNlZW1zIHRvIGJlIGxvdywgc28gSSB3aWxsIGJlIGluY3JlYXNpbmcgbXkgc2FtcGxlIHNpemUgdG8gMTAlIGluIHRoZSBjb21pbmcgcnVucyBvZiB0aGUgcHJvamVjdA0KICANCiMjIEZ1dHVyZSBGZWVkYmFjaw0KDQogIC0gSSBwbGFuIHRvIHVzZSBzb21lIHNvcnQgb2YgYSBhbGdvcml0aG0gLCBJIHNlZSB0aGVyZSBpcyBhIGRpc2N1c3Npb24gYWJvdXQgS0JPKEthdHogYmFja29mZikgLCBXZSBiYXNpY2FsbHkgbmVlZCB0byBwcmVkaWN0IHRoZSBuZXh0IHdvcmQgaW4gYSBzZXF1ZW5jZSBvZiB3b3JkcyAuIA0KICAgICsgVGhlcmUgY291bGQgYmUgYSBCYXllc2lhbiBhcHByb2NoIHRvIHRoZSBwcmVkaWN0aW9uIHByb2JsZW0gd2l0aCBwdXR0aW5nIHByb2JhYmxpdGllcyBvbiBlYWNoIG9mIHRoZSB3b3JkcyBpbiBhIG4tZ3JhbSB0byBwcmVkaWN0IG4rMSB3b3JkIA0KICAgICsgVGhlcmUgc2hvdWxkIGJlIG90aGVyIHNpbWlsYXIgYXBwcm9hY2VzIHRvIEtCTyAsIHdoaWNoIEkgcGxhbiB0byBzdHVkeSBhbmQgaW1wbGVtZW50IGFzIHJlcXVpcmVkIA0KICAtIEkgd291bGQgYWxzbyB0cnkgdG8gY2hhbmdlIG15IHNhbXBsZSBzaXplIGFuZCBzZWUgaG93IHRoYXQgY2hhbmdlcyBteSBGcmVxdWVuY3kgZGlzdHJpYnV0aW9ucw0KICAtIEkgYWxzbyBiZWxpZXZlIHRoZXJlIGlzIGEgbG90IG9mIHJlc2VhcmNoL3JlYWRpbmcgcGFwZXJzL3R1dG9yaWFscyAmIHZpZGVvcyB0aGF0IG5lZWRzIHRvIGJlIGRvbmUgYmV0d2VlbiB0YXNrLTIgYW5kIHRhc2stMyB0byByZWFjaCBhIG1vcmUgdGhyb3VnaCB1bmRlcnN0YW5kaW5nIG9mIGEgYmV0dGVyIGFwcHJvYWNoDQoNCmBgYHtyIEZpbmQgdGhlIGxlbmd0aCBvZiB0aGUgYmlnZ2VzdCBsaW5lLCBtZXNzYWdlPUZBTFNFLCBldmFsPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9DQpsaWJyYXJ5KHN0cmluZ2kpDQoNCg0Kc3RvcmV2YWwgPC0gYygpDQpsaW5lczwtTlVMTA0KDQpmb3IgKGogaW4gbGlzdC5kaXJzKHBhdGggPSBwYXRoMSkpIHsNCiAgaWYgKGxlbmd0aCh1bmxpc3Qoc3Ryc3BsaXQoYmFzZW5hbWUoaiksIl8iKVtbMV1dKSkgPT0gMikgew0KICAgIGZvciAoayBpbiBsaXN0LmZpbGVzKHBhdGggPSBqKSkgew0KICAgICAgIyBHcmVwIGJhc2VkIG9uIENhdGVnb3JpZXMgYW5kIHRoZW4gcmVhZA0KICAgICAgaWYgKGdyZXBsKCIudHh0IiwgaywgcGVybCA9IFRSVUUpKSB7DQogICAgICAgIHBhdGh0ZW1wIDwtIHBhc3RlKGosaywgc2VwID0gIi8iKQ0KICAgICAgICBpZiAoZ3JlcGwoImVuX1VTLnR3aXR0ZXIiLCBwYXRodGVtcCwgcGVybCA9IFRSVUUpKSB7DQogICAgICAgICAgcHJpbnQocGF0aHRlbXApDQogICAgICAgICAgY29ubnRlbXAxIDwtIGZpbGUocGF0aHRlbXAsIG9wZW4gPSAiciIpDQogICAgICAgICAgbGluZXNuZXcgPC0gcmVhZExpbmVzKGNvbm50ZW1wMSkNCiAgICAgICAgICB2YWwxPC1ncmVwbCgiLmxvdmUuIiwgbGluZXNuZXcsIHBlcmwgPSBUUlVFLCBpZ25vcmUuY2FzZSA9IEZBTFNFKQ0KICAgICAgICAgIHZhbDI8LWdyZXBsKCIuaGF0ZS4iLCBsaW5lc25ldywgcGVybCA9IFRSVUUsIGlnbm9yZS5jYXNlID0gRkFMU0UpDQogICAgICAgICAgY2xvc2UoY29ubnRlbXAxKSANCiAgICAgICAgfQ0KICAgICAgfQ0KICB9DQogIH0NCn0NCg0Kc3VtKHZhbDEpL3N1bSh2YWwyKQ0KDQojVHdlZXQgZm9yICJiaW9zdGF0cyIgbWF0Y2gNCmxpbmVzbmV3W2dyZXBsKCIuYmlvc3RhdHMuIiwgbGluZXNuZXcsIGlnbm9yZS5jYXNlID0gRkFMU0UsIHBlcmwgPSBUUlVFKV0NCg0KI1R3ZWV0cyBvZiB0aGUgZXhhY3QgY2hhcmFjdGVycw0KdGVzdHN0cmluZyA8LSAiQSBjb21wdXRlciBvbmNlIGJlYXQgbWUgYXQgY2hlc3MsIGJ1dCBpdCB3YXMgbm8gbWF0Y2ggZm9yIG1lIGF0IGtpY2tib3hpbmciDQpsaW5lc25ld1tncmVwbCh0ZXN0c3RyaW5nLCBsaW5lc25ldywgcGVybCA9IFRSVUUsIGlnbm9yZS5jYXNlID0gRkFMU0UpXQ0KYGBgDQoNCg0KIyBTb21lIEltcG9ydGFudCBMaW5rcyBmb3IgSGVscCANCg0KICArIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzE4MTAxMDQ3L2xpc3Qtb2Ytd29yZC1mcmVxdWVuY2llcy11c2luZy1yDQogICsgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjE2NDE1MjIvaG93LXRvLXJlbW92ZS1zcGVjaWZpYy1zcGVjaWFsLWNoYXJhY3RlcnMtaW4tcg0KICArIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzMwNDM1MDU0L2hvdy10by1zaG93LWNvcnB1cy10ZXh0LWluLXItdG0tcGFja2FnZQ0KICArIGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS90ZXh0LW1pbmluZy1hbmQtd29yZC1jbG91ZC1mdW5kYW1lbnRhbHMtaW4tci01LXNpbXBsZS1zdGVwcy15b3Utc2hvdWxkLWtub3cNCiAgKyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xMTUyNTQwOC9yLXJlZ3VsYXItZXhwcmVzc2lvbnMtdW5leHBlY3RlZC1iZWhhdmlvci1vZi1kaWdpdA0KICArICoqQ29udmVydGluZyBDb3JwdXMgdG8gRGF0YSBmcmFtZSoqIDogaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjQ3MDM5MjAvci10bS1wYWNrYWdlLXZjb3JwdXMtZXJyb3ItaW4tY29udmVydGluZy1jb3JwdXMtdG8tZGF0YS1mcmFtZSANCiAgKyAqKnRpZHl0ZXN0IHBhY2thZ2UqKjogaHR0cDovL3RpZHl0ZXh0bWluaW5nLmNvbS90aWR5dGV4dC5odG1sDQogICsgKipTdHJpbmcgYW5kIHRleHQgbWFuaXB1bGF0aW9uKio6IGh0dHBzOi8vd3d3My5uZC5lZHUvfnN0ZXZlL2NvbXB1dGluZ193aXRoX2RhdGEvMTlfc3RyaW5nc19hbmRfdGV4dC9zdHJpbmdzX2FuZF90ZXh0Lmh0bWwjLzI0DQogICsgKipVc2Ugb2YgV29yZCBDbG91ZCoqIDogaHR0cHM6Ly9yc3R1ZGlvLXB1YnMtc3RhdGljLnMzLmFtYXpvbmF3cy5jb20vMjY1NzEzX2NiZWY5MTBhZWU3NjQyZGM4YjYyOTk2ZTM4ZDI4MjVkLmh0bWwNCiAgKyAqKlRlcm0gZG9jdW1lbnQgbWF0cml4KiogOiBodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvcWRhcC92ZXJzaW9ucy8yLjAuMC90b3BpY3MvdGRtDQogICsgKipCaW5kaW5nIG11bHRpcGxlIERhdGEtdGFibGVzKiogDQogICAgLSBodHRwczovL3JkcnIuaW8vcmZvcmdlL2RhdGEudGFibGUvbWFuL3JiaW5kbGlzdC5odG1sIA0KICAgIC0gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjIzMjY5OS9ob3ctdG8tZG8tYS1kYXRhLXRhYmxlLW1lcmdlLW9wZXJhdGlvbg0KICAgIC0gaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2RhdGEudGFibGUvdmVyc2lvbnMvMS4xMC40L3RvcGljcy9tZXJnZQ0KICAgIC0gaHR0cHM6Ly9yZHJyLmlvL3Jmb3JnZS9kYXRhLnRhYmxlL21hbi9tZXJnZS5odG1sDQo=