Wie auch einige andere hier behandelte Verfahren hat die Sentimentanalyse ihre Wurzeln in der Computerlinguistik und Informatik, wird aber seit einigen Jahren auch zunehmend in den Sozialwissenschaften angewandt, um ganz unterschiedliche Texte automatisch zu klassifizieren, etwa Parlamentsdebatten, Freitextantworten in Befragungen, oder Social Media-Diskurse. Ziel der Sentimentanalyse ist die Bestimmung der Polarität eines Textes, womit gemeint ist, ob die darin zum Ausdruck gebrachten Emotionen eher positiv oder negativ sind. Dies geschieht häufig durch Wortlisten und über das Auszählen von Begriffen, die zuvor den Kategorien positiv oder negativ zugeordnet wurden. In vielen Verfahren wird das Resultat anachließend skaliert, oder es werden andere Schritte angewandt, um fehlerhafte Klassifizierungen zu vermeiden. Diese treten vor allem dann auf, wenn Negation oder Ironie verwendet werden, aber auch, wenn der Gegenstand der positiven oder negativen Ausdrücke wechselt oder unklar ist. Unsprünglich wurde die Sentimentanalyse auf Produktbewertungen auf E-Commerence-Plattformen wie Amazon.com getestet, wo diese Probleme eine relativ geringe Rolle spielen. Bei Pressetexten oder Diskursen in den sozialen Medien hingegen, ist oft schwerer zu bewerten, auf was sich eine Sentimentbewertung bezieht, oder welches Sentimenniveau etwa als ‘normal’ betrachtet werden sollte. So kommen beispielsweise in Pressetexten generell wenig Emotionen zum Ausdruck, und die negativen Begriffe überwiegen häufig, ohne dass dies notwendigerweise auf einen schlechten Zustand der Welt zurückzuführen wäre. Schließlich sollte man sich vor Augen führen, dass die Sentimentanalyse ein heuristisches Verfahren ist, dass immer auch fehlerhafte Einzelklassifikationen produziert, was aber idealerweise nicht zu stark ins Gewicht fällt, wenn man etwas Veränderungen im Sentimentverlauf über die Zeit untersucht.
Was die technische Umsetzung angeht, so gehören dieser Abschnitt und das nächste zu spezialisierten Lexika insofern zusammen, als dass es sich bei beiden Ansätzen um ganz ähnliche Verfahren handelt. In beiden Fällen wird ein Lexikon (‘sentiment/topic dictionary’) verwendet, um eine Reihe von Einzelbegriffen in einer Kategorie zusammenzufassen.
Wir verwenden in diesem Kapitel sechs unterschiedliche Sentimentlexika, davon vier in englischer Sprache und zwei für Deutsch:
Diese Lexika sind lediglich Listen von Wörtern, welche wie oben beschrieben den Kategorien postiv oder negativ zugeordnet sind. Zum Teil existiert auch noch eine dritte Kategorie neutral, desweiteren können Begriffe auch mehreren Kategorien zugeordnet sein, oder neben einer Zuordnung der Polarität auch noch eine Sentimentstärke zugeschrieben bekommen. Die hier vorgestellte Technik ist vergleichsweise primitiv, weil sie lediglich Wörter auszählt, allerdings lassen sich die Verfahren leicht noch verfeinern (vgl. bspw. diesen Beitrag von Christian Rauh zur Validierung politischer Sentiment-Lexika). Auch Verfahren die gewichten oder andere Kniffe für die Verringerung der Fehlerrate einsetzen, funktionieren so — die Sentimentanalye ist effektiv, aber auch alles andere als Hexenwerk.
Diese Lexika wenden wir folgend auf fünf Datensätze an: das bereits bekannte Sherlock Holmes—Korpus, einen Datensatz aus Tweets von Donald Trump und Hillary Clinten, einen Kommentar-Korpus aus der Diskussionsplattform Reddit, ein Korpus Schweizer Tageszeitugen mit Artikeln zur Finanzkrise, die zwischen 2007 und 2012 verfasst wurden, und schließlich noch einen Debattenkorpus des 18. Deutschen Bundestags (2013 bis 2017). Auf die Zusammenstellung der Korpora gehen wir später noch ein.
Installation und Laden der benötigten R-Bibliotheken, Laden des Korpus
Zunächst werden wieder die notwendigen Bibliotheken geladen. Neu ist die Bibliothek scales die bei der Normalisierung von Sentiment—Scores zum Einsatz kommt. Dann wird in einem zweiten Schritt das Sherlock-Korpus geladen, welches wir ja bereits zuvor nebst Metadaten im RData-Format gespeichert haben.
if(!require("quanteda")) {install.packages("quanteda"); library("quanteda")}
if(!require("readtext")) {install.packages("readtext"); library("readtext")}
if(!require("tidyverse")) {install.packages("tidyverse"); library("tidyverse")}
if(!require("scales")) {install.packages("scales"); library("scales")}
theme_set(theme_minimal())
load("daten/sherlock/sherlock.korpus.RData")
Erstellung eines Lexikons in quanteda
Wir beginnen zunächst mit einer Sentimentanalyse der Sherlock Holmes-Erzählungen, um bei einem bereits aus Kapitel 1 und 2 vertrauten Korpus zu bleiben. In einem ersten Schritt erstellen wir ein sehr einfaches Ad hoc-Lexikon aus nur sechs Begriffen, um die Struktur eines Lexikons in quanteda zu illustrieren. Dies geschieht mit dem quanteda-Befehl dictionary. Dictionary() akzeptiert eine Reihe von Standardformaten (dazu später noch mehr), aber auch Vektoren, welche die Begriffe enthalten, die eine abstrakte Kategorie operationalisieren. Beliebig viele Kategorien können so definiert und dann mit tausenden von Begriffen ‘befüllt’ werden. Auch Kategorien mit mehreren hierarchischen Ebenen sind möglich — dazu im nächsten Kapitel noch etwas mehr.
test.lexikon <- dictionary(list(posititive.begriffe = c("glück", "freude", "licht"), negative.begriffe = c("trauer", "wut", "dunkelheit")))
test.lexikon
Dictionary object with 2 key entries.
- [posititive.begriffe]:
- glück, freude, licht
- [negative.begriffe]:
- trauer, wut, dunkelheit
Erste Sentiment-Analyse mit dem Sherlock Holmes-Korpus
Mit diesem Lexikon können wir mit unserem englischsprachigen Korpus wenig konkretes anfangen, daher wechseln wir besser zu einem echten Sentimentlexikon. In einem zweiten Schritt lesen wir mit dem Befehl scan das Bing Liu Sentiment Lexikon in R ein. Dieses Lexikon umfasst über 6.700 englischsprachige Begriffe die in zwei einfachenn Textdateien abgelegt sind, die jeweils ein Wort je Zeile enthalten. Wir überspringen mit dem Argument skip die ersten 35 Zeilen, da diese Metainformationen über das Lexikon enthalten. Das Argument quiet verhindet die Ausgabe einer Statusmeldung.
positive.woerter.bl <- scan("lexika/bingliu-positive-words.txt", what = "char", sep = "\n", skip = 35, quiet = T)
negative.woerter.bl <- scan("lexika/bingliu-negative-words.txt", what = "char", sep = "\n", skip = 35, quiet = T)
Nun erstellen wir das Lexikon mithilfe der gerade eingelesenen Textvektoren. Dies erfolgt wieder mit der Funktion dictionary(), diesmal mit den gerade eingelesenen Vektoren als Argument.
sentiment.lexikon <- dictionary(list(positive = positive.woerter.bl, negative = negative.woerter.bl))
str(sentiment.lexikon)
Formal class 'dictionary2' [package "quanteda"] with 2 slots
..@ .Data:List of 2
.. ..$ :List of 1
.. .. ..$ : chr [1:2006] "a+" "abound" "abounds" "abundance" ...
.. ..$ :List of 1
.. .. ..$ : chr [1:4783] "2-faced" "2-faces" "abnormal" "abolish" ...
..@ meta :List of 3
.. ..$ system:List of 5
.. .. ..$ package-version:Classes 'package_version', 'numeric_version' hidden list of 1
.. .. .. ..$ : int [1:3] 2 1 2
.. .. ..$ r-version :Classes 'R_system_version', 'package_version', 'numeric_version' hidden list of 1
.. .. .. ..$ : int [1:3] 4 0 2
.. .. ..$ system : Named chr [1:3] "Darwin" "x86_64" "cp"
.. .. .. ..- attr(*, "names")= chr [1:3] "sysname" "machine" "user"
.. .. ..$ directory : chr "/Users/cp/Documents/GitHub/inhaltsanalyse-mit-r.de"
.. .. ..$ created : Date[1:1], format: "2021-01-23"
.. ..$ object:List of 2
.. .. ..$ valuetype: chr "glob"
.. .. ..$ separator: chr " "
.. ..$ user : list()
Wie man sieht, sind nun mehrere tausend Begriffe den beiden Kategorien des Lexikons zugeordnet worden. Jetzt können wir eine DFM berechnen, welche das erstellte Lexikon auf das Korpus anwendet.
meine.dfm.sentiment <- dfm(korpus, dictionary = sentiment.lexikon)
meine.dfm.sentiment
Document-feature matrix of: 12 documents, 2 features (0.0% sparse) and 1 docvar.
features
docs positive negative
A Scandal in Bohemia 245 201
The Red-headed League 272 216
A Case of Identity 198 194
The Boscombe Valley Mystery 194 293
The Five Orange Pips 148 213
The Man with the Twisted Lip 191 297
[ reached max_ndoc ... 6 more documents ]
Was ist geschehen? Alle tatsächlich vorkommenden Nennungen der rund 6.700 im Bing Liu-Lexikon enthaltenen Begriffe in den zwölf Sherlock Holmes—Romanen sind jeweils durch die ihnen zugeordnete Kategorie ersetzt worden. Sämtliche Begriffe, die nicht im Lexikon vorkommen, fallen dabei einfach weg. Dadurch bleibt eine Tabelle zurück, die nur noch zwei Spalten enthält — die Summe aller positiven und negativen Begriffe pro Roman. Wir werden darauf später noch im Detail zu sprechen kommen, aber vielleicht haben Sie schon bemerkt, dass mittels dictionary die Spalten einer DFM zusammengefasst werden (also die Wörter), während das Argument group der Funktion dfm() die Zeilen zusammenfasst (also die Texte). Diese dimensionale Reduzierung gehört zu den nützlichsten Eigenschaften von quanteda.
Das folgende Plot zeigt die Sentiment-Verteilung in den zwölf Sherlock Holmes-Erzählungen.
sentiment <- convert(meine.dfm.sentiment, "data.frame") %>%
gather(positive, negative, key = "Polarität", value = "Wörter") %>%
mutate(doc_id = as_factor(doc_id)) %>%
rename(Roman = doc_id)
ggplot(sentiment, aes(Roman, Wörter, colour = Polarität, group = Polarität)) +
geom_line(size = 1) +
scale_colour_brewer(palette = "Set1") +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
ggtitle("Sentiment-Scores in zwölf Sherlock Holmes-Romanen")
Gewichtung von Sentiment-Scores
Erinnert man sich an die im vorherigen Kapitel adressierten Probleme absoluter Wortfrequenzen, so möchte man vielleicht lieber relative Frequenzen berechnen. Das bedeutet beim Einsatz von Lexika in der Regel nicht nur, dass man das Auftreten der Lexikon-Begriffe relativ zur Gesamtwortfrequenz misst, sondern deren Anteil relativ zu einander (also das Verhältnis positiver und negativer Begriffe). Dies hat den Vorteil, dass man die große Zahl aller Begriffe, die weder positiv noch negativ sind, unberücksichtigt lassen kann, was durchaus Sinn ergibt, wenn man sich eben nur für das Sentiment interessiert.
Das folgenden Beispiel verdeutlicht dieses Vorgehen.
meine.dfm.sentiment.prop <- dfm_weight(meine.dfm.sentiment, scheme = "prop")
meine.dfm.sentiment.prop
Document-feature matrix of: 12 documents, 2 features (0.0% sparse) and 1 docvar.
features
docs positive negative
A Scandal in Bohemia 0.5493274 0.4506726
The Red-headed League 0.5573770 0.4426230
A Case of Identity 0.5051020 0.4948980
The Boscombe Valley Mystery 0.3983573 0.6016427
The Five Orange Pips 0.4099723 0.5900277
The Man with the Twisted Lip 0.3913934 0.6086066
[ reached max_ndoc ... 6 more documents ]
Auch diese DFM lässt sich natürlich leicht plotten.
sentiment <- convert(meine.dfm.sentiment.prop, "data.frame") %>%
gather(positive, negative, key = "Polarität", value = "Sentiment") %>%
mutate(doc_id = as_factor(doc_id)) %>%
rename(Roman = doc_id)
ggplot(sentiment, aes(Roman, Sentiment, colour = Polarität, group = Polarität)) +
geom_line(size = 1) +
scale_colour_brewer(palette = "Set1") +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
ggtitle("Sentiment-Scores in zwölf Sherlock Holmes-Romanen (relativ)")
Verrechnung und Skalierung positiver und negativer Sentiment-Anteile
Die Darstellung von Sentimentanteilen innerhalb der zwölf Erzählungen lässt sich noch verbessern, indem wir darauf verzichten, beide Polaritäten darszustellen. Da bei dieser Anwendungen die negative Polarität schlicht die Invertierung des positiven Sentiments ergibt, reicht dies aus. Zudem skalieren wir die Werte mittels rescale neu, so dass sie zwischen –1 und +1 liegen.
sentiment <- convert(meine.dfm.sentiment.prop, "data.frame") %>%
rename(Roman = doc_id, Sentiment = positive) %>%
select(Roman, Sentiment) %>%
mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
mutate(Roman = as_factor(Roman))
ggplot(sentiment, aes(Roman, Sentiment, group = 1)) +
geom_line(size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
ggtitle("Verrechnete Sentiment-Scores in zwölf Sherlock Holmes-Romanen")
Wir halten fest: Die Romane zu Beginn des Sherlock Holmes-Zylus sind etwas positiver, während es in der Mitte düsterer zugeht. Zum Schluss hebt sich die Stimmung aber - jedenfalls im Vergleich - wieder. Wenn man diese Darstellung mit dem ersten Plot kontrastiert wird klar, warum verrechnete Frequenzen oftmals einen guten Ansatz darstellen. Andererseits darf man aber auch nicht der Annahme auf den Leim gehen, das Sentiment in ‘The Adventure of the Speckled Band’ sei ausschließlich negativ, also ‘zu 0% positiv’, denn dies ist ein Artefakt unserer proportionalen Skalierung. Es ist lediglich anteilig negativer als in den anderen elf Erzählungen.
Vergleich unterschiedlicher Sentiment-Lexika
Welche Unterschiede gibt es zwischen verschiedenen Lexika? Da unterschiedliche Lexika auch unterschiedliche Begriffe enthalten, ist diese Frage durchaus bedeutsam. Um sie zu beantworten, berechnen wir anhand der Tweets von Donald Trump drei unterschiedliche DFMs, jeweils mit einem anderen Lexikon. Wir lesen zunächst einmal alle drei Lexika ein.
sentiment.lexikon.bingliu <- dictionary(list(positive = scan("lexika/bingliu-positive-words.txt", what = "char", sep = "\n", skip = 35, quiet = T), negative = scan("lexika/bingliu-negative-words.txt", what = "char", sep = "\n", skip = 35, quiet = T)))
sentiment.lexikon.nrc <- dictionary(list(positive = scan("lexika/nrc.pos.txt", what = "char", sep = "\n", quiet = T), negative = scan("lexika/nrc.neg.txt", what = "char", sep = "\n", quiet = T)))
afinn <- read.csv("lexika/AFINN-111.txt", header = F, sep = "\t", stringsAsFactors = F)
sentiment.lexikon.afinn <- dictionary(list(positive = afinn$V1[afinn$V2>0], negative = afinn$V1[afinn$V2<0]))
Während der Import für das Bing Liu-Lexikon und das NRC Emotions Lexicon sehr einfach abläuft, hat das AFINN-Dictionary ein spezielles Format, bei dem eine Zahl zwischen -5 und +5 die Polarität von sehr negativ bis sehr positiv beschreibt. Wir nutzen hier diesen besonderen Vorteil nicht aus, sondern behandeln alle Begriffe als ‘einfach’ negativ oder positv (<0 oder >0).
Wieder wird das Lexikon angewendet, nach Monat und Jahr gruppiert, und anschließend proportional gewichtet – jeweils für jedes der drei Lexika.
meine.dfm.trump.bingliu <- dfm_weight(dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon.bingliu), scheme = "prop")
meine.dfm.trump.nrc <- dfm_weight(dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon.nrc), scheme = "prop")
meine.dfm.trump.afinn <- dfm_weight(dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon.afinn), scheme = "prop")
Schließlich werden die drei DFMs in Data Frames umgewandelt und eine Variable hinzugefügt, welche das jeweilige Lexikon identifiziert.
sentiment.trump.bingliu <- convert(meine.dfm.trump.bingliu, "data.frame") %>% mutate(Lexikon = "Bing Liu")
sentiment.trump.nrc <- convert(meine.dfm.trump.nrc, "data.frame") %>% mutate(Lexikon = "NRC")
sentiment.trump.afinn <- convert(meine.dfm.trump.afinn, "data.frame") %>% mutate(Lexikon = "AFINN")
Zuletzt wird ein gemeinsamer Data Frame zusammengesetzt und etwas umgeformt. Das resultierenden Plot zeigt die verrechneten Sentiment-Scores von Donald Trump für alle drei Lexika.
sentiment.trump.kombi <- bind_rows(sentiment.trump.bingliu, sentiment.trump.nrc, sentiment.trump.afinn) %>%
gather(positive, negative, key = "Polarität", value = "Sentiment") %>%
filter(Polarität == "positive") %>%
mutate(Datum = as.Date(paste("01", doc_id, sep = "."), "%d.%m.%Y")) %>%
filter(Datum >= "2015-04-01" & Datum <= "2017-03-01") %>%
select(Datum, Lexikon, Sentiment) %>%
mutate(Sentiment = rescale(Sentiment, to = c(-1,1)))
ggplot(sentiment.trump.kombi, aes(Datum, Sentiment, colour = Lexikon, group = Lexikon)) +
geom_line(size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") +
scale_colour_brewer(palette = "Dark2") +
scale_x_date(date_breaks = "2 months", date_labels = "%b %Y") +
ggtitle("Verrechnete Sentiment-Scores für Donald Trump mit drei Lexika") +
xlab("Monat") + theme(axis.text.x = element_text(angle = 45, hjust = 1))
Wie wir sehen, stimmt die Tendenz der drei Lexika zwar klar überein, jedoch ergeben sich durchaus markanten Unterschiede. So sind AFINN und Bing Liu gegenüber NRC etwas positiver. Zum Teil unterscheidet sich auch die Intensität der Ausschläge in beide Polaritätsrichtungen. Ein Grund für die Variation ist die Länge der Wortlisten, da umfangreichere Listen eine besser Abdeckung der tatsächlich verwendeten Begriffe erreichen. Grundsätzlich unterscheiden sich die drei Lexika aber nicht signifikant und stimmen bspw. in ihrer Messung der Sentiment–Schwankung zwischen September und November 2016 klar überein.
Sentiment in zwei Subreddits mit dem Lexicoder Sentiment Dictionary
Nun wenden wir uns einem Beispiel aus einer anderen Social Media Plattform – dem Diskussionsforum Reddit – zu. Es handelt sich um Beiträge aus zwei unterschiedlichen Bereichen der Plattform (sog. Subreddits). Wir laden zunächst den Datensatz welcher wie auch die Twitter-Daten schon als Quanteda-Korpus vorliegt. Die Metadaten ähneln teilweise denen von Twitter (“post_date”), weisen aber auch Spezifika auf (die Variabel “structure” liefert Informationen zur Schachtelung der Diskussion).
load("daten/reddit/reddit.RData")
as.data.frame(reddit.stats)
Das Korpus enthält etwa 20.000 Kommentare, die in zwei unterschiedlichen Subreddits veröffentlicht wurden, den Foren ‘science’ und ‘syriancivilwar’. Wir berechnen Sentiment-Scores für diese Nachrichten mit Hilfe des Lexicoder Sentiment Dictionary (LSD2015). Im Gegensatz zum Vorgehen in den vorausgehenden Beispielen verwenden wir die logarithmische Durchschnittsgewichtung und kürzen das Ergebnis, um so einen Polaritätswert pro Kommentar und nicht pro Wort zu bestimmen (beachten Sie, dass es hierfür unterschiedliche Strategien gibt).
reddit.dfm <- dfm(reddit.corpus, dictionary = data_dictionary_LSD2015) %>%
dfm_remove(c("neg_positive", "neg_negative"))
reddit.sentiment <- dfm_weight(reddit.dfm, scheme = "logave") %>%
convert("data.frame") %>%
mutate(positive = trunc(positive), negative = trunc(negative)) %>%
mutate(neutral = positive == negative) %>%
left_join(reddit.stats, by = c("doc_id" = "Text"))
sentiment <- ""
sentiment[reddit.sentiment$positive==1] <- "positive"
sentiment[reddit.sentiment$negative==1] <- "negative"
sentiment[reddit.sentiment$neutral==T] <- "neutral"
reddit.sentiment.share <- reddit.sentiment %>%
select(doc_id, structure, comm_date, subreddit, user, comment_score) %>%
data.frame(Sentiment = sentiment)
reddit.sentiment.share
Nun plotten wir den relativen Anteil der Polarität je Kommentar innerhalb der zwei Subreddits.
reddit.sentiment.share <- data.frame(prop.table(table(reddit.sentiment.share$Sentiment, reddit.sentiment.share$subreddit), 2))
colnames(reddit.sentiment.share) <- c("Sentiment", "Subreddit", "Share")
ggplot(reddit.sentiment.share, aes(Subreddit, Share, colour = Sentiment, fill = Sentiment)) +
geom_bar(stat = "identity", position = position_dodge()) +
scale_colour_brewer(palette = "Set1") +
scale_fill_brewer(palette = "Pastel1") +
ggtitle("Sentiment-Verteilung in zwei Subreddits") +
xlab("") + ylab("Sentiment-Anteil (%)")
Die nachstehenden Beispiele (Zufallssample) zeigen Kommentare und deren jeweilige vorhergesagte Polarität auf Grundlage des Lexikons. Es lassen sich zahlreiche Beispiele für fehlerhaft klassifizierte Kommentare entdecken, etwa ironische oder unverständliche Beiträge, oder solche, die sich schlicht einer klaren Einordnung in das Raster positiv/negativ entziehen. Allerdings scheint die Tendenz insofern korrekt, als das etwa Kommentare mit Kraftausdrücken als negativ und solche mit Glückwünschen als positiv klassifiziert werden (auch hier gibt es falsch positive Treffer).
data.frame(reddit.sentiment, sentiment, comment = texts(reddit.corpus)) %>%
filter(sentiment == "positive") %>%
sample_n(10) %>%
select(comment, sentiment, subreddit, title)
data.frame(reddit.sentiment, sentiment, comment = texts(reddit.corpus)) %>%
filter(sentiment == "negative") %>%
sample_n(10) %>%
select(comment, sentiment, subreddit, title)
data.frame(reddit.sentiment, sentiment, comment = texts(reddit.corpus)) %>%
filter(sentiment == "neutral") %>%
sample_n(10) %>%
select(comment, sentiment, subreddit, title)
Sentiment in Schweizer Tageszeitungen in der Berichterstattung zur Finanzkrise
Wir wechseln nun die Perspektive und untersuchen deutschsprachige Texte. Dazu lesen wir das Schweizer Korpus zur Finanzkrise ein. Dieses emthält rund 21,000 Artikel, die zwischen 2007 und 2012 in einer von fünf Schweizer Tageszeigungen veröffentlich wurden und den Begriff ‘Finanzkrise’ in Text oder Titel enthalten. Neben Sprache und Textsorte gibt einen weiteren Unterschied zu den Twitter-Daten: Die Aggregationsebene ist hier nicht mehr ein Monat, wie zuvor, sondern schlicht ein Artikel. Das leuchtet ein, allerdings ist dieser Ansatz hier auch deshalb ergiebig, weil Zeitungsartikel nun einmal deutlich länger sind als Tweets.
Zusätzlich zum Finanzkrise-Korpus laden wir auch gleich das deutschsprachige SentiWS-Lexikon. Im Gegensatz zu dem Bing Liu-Lexikon handelt es sich hier um eine RData-Datei, nicht um eine Textdatei, was aber praktisch keinerlei Unterschied macht.
load("lexika/sentiWS.RData")
load("daten/cosmas/finanzkrise/finanzkrise.korpus.RData")
sentiment.lexikon.sentiws <- dictionary(list(positive = positive.woerter.senti, negative = negative.woerter.senti))
head(korpus.finanzkrise.stats, 100)
Sentiment-Anteile nach Quelle (hier: Zeitung)
Im nächste Schritt berechnen wir eine DFM, die nun nicht nach Monat und Jahr gruppiert ist, sondern nach dem Feld quelle, d.h. der jeweiligen Zeitung. Dieser Schritt dauert deshalb etwas länger als zuvor, weil wir es mit einem Korpus von 21,000 Dokumenten und rund 4 Mio. Tokens zu tun haben – deutlich größer, als das Sherlock Holmes-Korpus (126T Wörter) und Twitter Korpus (459T Wörter). Heraus kommt eine Tabelle, die sich sogar vollständig ausgeben lässt, ohne den head–Befehl zu verwenden.
meine.dfm.finanzkrise <- dfm(korpus.finanzkrise, groups = "quelle", dictionary = sentiment.lexikon.sentiws)
meine.dfm.finanzkrise.prop <- dfm_weight(meine.dfm.finanzkrise, scheme = "prop")
meine.dfm.finanzkrise.prop
Document-feature matrix of: 5 documents, 2 features (0.0% sparse) and 1 docvar.
features
docs positive negative
Basler Zeitung 0.3255584 0.6744416
Berner Zeitung 0.3592919 0.6407081
Der Bund 0.3015228 0.6984772
Neue Luzerner Zeitung 0.3489669 0.6510331
Neue Zürcher Zeitung 0.3327953 0.6672047
Auch diese sehr einfache Tabelle können wir plotten, auch wenn das in diesem konkreten Fall vielleicht nicht unbedingt notwendig ist.
sentiment.finanzkrise <- convert(meine.dfm.finanzkrise.prop, "data.frame") %>%
gather(positive, negative, key = "Polarität", value = "Sentiment")
ggplot(sentiment.finanzkrise, aes(doc_id, Sentiment, colour = Polarität, fill = Polarität)) +
geom_bar(stat="identity") +
scale_colour_brewer(palette = "Set1") +
scale_fill_brewer(palette = "Pastel1") +
ggtitle("Sentiment-Scores in Beiträgen zur Finanzkrise aus fünf Schweizer Tageszeitungen") +
xlab("") + ylab("Sentiment-Anteil (%)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Wie wir sehen ist die Berichterstattung zur Finanzkrise überwiegende negativ, was angesichts des Themas natürlich kaum überrascht. Es lassen sich aber auch keinerlei nennenswerte Unterschiede zwischen den Zeitungen erkennen, sondern die Übereinstimmung ist ausgesprochen klar.
Verrechnete Sentiment-Anteile nach Quelle und Jahr
Wie stellt sich das Sentiment in den fünf Zeitungen im Vergleich über die Zeit dar? Wir berechnen noch einmal eine DFM unter Verwendung des SentiWS–Lexikons, gruppieren aber dieses Mal sowohl nach Quelle als auch nach Jahr.
meine.dfm.finanzkrise <- dfm(korpus.finanzkrise, groups = c("quelle", "jahr"), dictionary = sentiment.lexikon.sentiws)
meine.dfm.finanzkrise.prop <- dfm_weight(meine.dfm.finanzkrise, scheme = "prop")
head(meine.dfm.finanzkrise.prop)
Document-feature matrix of: 6 documents, 2 features (0.0% sparse) and 2 docvars.
features
docs positive negative
Basler Zeitung.2007 0.3601036 0.6398964
Berner Zeitung.2007 0.2903226 0.7096774
Der Bund.2007 0.3322476 0.6677524
Neue Luzerner Zeitung.2007 0.2552083 0.7447917
Neue Zürcher Zeitung.2007 0.2971549 0.7028451
Basler Zeitung.2008 0.3115323 0.6884677
Die resultierende DFM formen wir etwas um und plotten dann wieder eine Grafik, welche einerseits die zeitliche Entwicklung darstellt, und andererseits, wie auch schon im vorherigen Beispiel, die Unterschiede stark hervorhebt – einerseits zwischen den fünf Zeitungen und andererseits innerhalb des fünfjährigen Messzeitraums. So kann man erkennen, das ‘Der Bund’ durchweg etwas negativer ist, als es die anderen Zeitungen sind, dass die Berner Zeitung in 2010 positiver ist als die andere vier Blätter, und dass die NZZ tendenziell über die Zeit etwas positiver wird, aber im Vergleich doch negativ bleibt. Die Berner Zeitung schwankt im Vergleich relativ stark.
sentiment.finanzkrise.zeit <- convert(meine.dfm.finanzkrise.prop, "data.frame")
finanzkrise.quellen <- data.frame(str_split(sentiment.finanzkrise.zeit$doc_id, "\\.", simplify = T))
colnames(finanzkrise.quellen) <- c("Zeitung", "Jahr")
sentiment.finanzkrise.zeit <- bind_cols(sentiment.finanzkrise.zeit, finanzkrise.quellen) %>%
rename(Sentiment = positive) %>%
mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
select(Sentiment, Zeitung, Jahr)
ggplot(sentiment.finanzkrise.zeit , aes(Jahr, Sentiment)) +
geom_bar(stat = "identity", aes(fill = Zeitung), position = "dodge") +
scale_fill_brewer(palette = "Accent") +
ggtitle("Verrechnete Sentiment-Scores in Beiträgen zur Finanzkrise")
Sentiment in Debatten des Deutschen Bundestags nach Partei, Sitzung und Sprecher
Wir schließen mit einem weitere deutschsprachigen Beispiel, dem Debattenkorpus des Deutschen Bundestags in der 18. Legislaturperiode. Wieder ist das Korpus dazu bereits als RData-Datei abgelegt und wir überspringen and dieser Stelle den Hintergrund dazu, wie genau das Korpus zusammengestellt wurde. Der Umfang ist mit 206.000 Wortmeldungen in 243 Sitzungen und rund 15 Mio. Tokens noch einmal erheblich größer, als der vorausgegangener Korpora.
load("daten/bundestag/bundestag.korpus.RData")
load("lexika/Rauh_SentDictionaryGerman.RData")
sentiment.lexikon.rauh <- dictionary(list(positive = str_trim(sent.dictionary$feature[sent.dictionary$sentiment>0]), negative = str_trim(sent.dictionary$feature[sent.dictionary$sentiment<0])))
Nachdem Korpus und Lexikon-Rohdaten geladen wurden, erstellen wir aus der umfangreichen Sentiment-Wortliste von Christian Rauh ein quanteda-Lexikon (dabei muss ähnlich wie beim AFINN-Diktionary noch etwas umgeformt werden). Anschließend berechnen wir dann eine DFM, welche Sentiment-Wörter nach Parteien auszählt.
meine.dfm.bundestag.partei <- dfm(korpus.bundestag, groups = "party", dictionary = sentiment.lexikon.rauh)
meine.dfm.bundestag.partei
Document-feature matrix of: 5 documents, 2 features (0.0% sparse) and 3 docvars.
features
docs positive negative
CDU 301212 184566
CSU 103447 62955
DIE GRÜNEN 151134 132210
DIE LINKE 119610 115444
SPD 275064 173376
Auch hier plotten wir wieder das Resultat. Die Normalisierung, welche wir im vorausgegangenen Beispiel durchgeführt haben, lassen wir an dieser Stelle weg, um auch einen Eindruck von den Redenanteilen der Parteien zu erhalten.
sentiment.bundestag.partei <- convert(meine.dfm.bundestag.partei, "data.frame") %>%
gather(positive, negative, key = "Polarität", value = "Sentiment")
ggplot(sentiment.bundestag.partei, aes(doc_id, Sentiment, colour = Polarität, fill = Polarität)) +
geom_bar(stat="identity") + scale_colour_brewer(palette = "Set1") +
scale_fill_brewer(palette = "Pastel1") +
scale_y_continuous(labels = comma) +
ggtitle("Sentiment-Scores im Deutschen Bundestag nach Partei") +
xlab("") + ylab("Wörter") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
An diesem Ergebnis überrascht vielleicht etwas, dass das Sentiment der Regierungsparteien nicht merklich positiver ausfällt, als das der Opposition. Zwar hat Linke anteilig das negativste Sentiment und die CSU das positivste, aber die Unterschiede sind nicht sehr groß.
Nun wiederholen wir die Erstellung der DFM, gruppieren aber diesmal nicht nach Partei, sondern nach Sitzung. Das Ziel dieses Ansatzes besteht darin, Sitzungen zu identifizieren, in denen besonders negative Themen (was hier mehrere Bedeutungen haben kann) zu finden.
meine.dfm.bundestag.sitzung <- dfm(korpus.bundestag, groups = "sitzung", dictionary = sentiment.lexikon.rauh)
meine.dfm.bundestag.sitzung.prop <- dfm_weight(meine.dfm.bundestag.sitzung, scheme = "prop")
head(meine.dfm.bundestag.sitzung.prop)
Document-feature matrix of: 6 documents, 2 features (0.0% sparse) and 5 docvars.
features
docs positive negative
1 0.6353383 0.3646617
2 0.5944617 0.4055383
3 0.5908111 0.4091889
4 0.7329193 0.2670807
5 0.6277672 0.3722328
6 0.5840116 0.4159884
Nachdem wir das anteilige Sentiment für alle 243 Sitzungen der Legislaturperiode 2013-2017 bestimmt haben, skalieren wir die Daten wieder und plotten das Resultat in einer (sehr dichten) Zeitreihe. Für das Plot filtern wir dabei die Daten so, dass nur das Jahr 2015 (Sitzungen 1-60) dargestellt wird.
sentiment.bundestag.sitzung <- convert(meine.dfm.bundestag.sitzung.prop, "data.frame") %>%
rename(Sitzung = doc_id, Sentiment = positive) %>%
select(Sitzung, Sentiment) %>%
mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
mutate(Sitzung = as_factor(Sitzung)) %>%
slice(1:60)
ggplot(sentiment.bundestag.sitzung, aes(Sitzung, Sentiment, group = 1)) +
geom_line() +
geom_point() +
geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5, size = 8)) +
ggtitle("Verrechnete Sentiment-Scores in Sitzungen der Deutschen Bundestags")
Ähnlich wie bei Trump und Clinton lässt sich ein klarer Bezug zwischen Sentiment–Scores und historischen Ereignissen herstellen, wenn man den Blick auf solche Sitzungen mit besonders hohen/niedrigen Sentiment-Scores lenkt. Folgend wählen wir solche Sitzungen aus und reichern die Informationen zur Sitzung mit den enthaltenen Tagesordnungspunkten (TOPs) an. Das Sentiment ist allerdings in der nachstehenden Tabelle für die gesamte Sitzung, nicht nur für den jeweiligen TOP kalkuliert.
load("daten/bundestag/tagesordnungspunkte.RData")
tagesordnungspunkte <- select(tagesordnungspunkte, sitzung, held_on, name, category)
bundestag.possitzung <- head(arrange(sentiment.bundestag.sitzung, desc(sentiment.bundestag.sitzung$Sentiment)), 3)
bundestag.possitzung$Sitzung <- as.numeric(bundestag.possitzung$Sitzung)
bundestag.negsitzung <- head(arrange(sentiment.bundestag.sitzung, sentiment.bundestag.sitzung$Sentiment), 3)
bundestag.negsitzung$Sitzung <- as.numeric(bundestag.negsitzung$Sitzung)
# Sitzungen mit positivem Sentiment
left_join(bundestag.possitzung, tagesordnungspunkte, by = c("Sitzung" = "sitzung"))
# Sitzungen mit negativem Sentiment
left_join(bundestag.negsitzung, tagesordnungspunkte, by = c("Sitzung" = "sitzung"))
Beim positiven Sentiment sticht die Sitzung vom 17. Dezember 2013 heraus, in der Angela Merkel vereidigt wurde – dem Anlass und den nahenden Weihnachtsferien entsprechend ist die Stimmung gut. Negativ fallen Sitzungen zum kontroversen aussenpolitischen Themen ebenso wie zum CETA-Abkommen oder zur Bekämpfung der Kinderarmut auf. Die beiden TOPs, welche die Liste anführen (‘Regierungserklärung Humanitäre Hilfe im Irak’ sowie ‘Stabilitätshilfe zugunsten Griechenlands’) liefern bei augenscheinlicher Bertrachtung ein gutes Maß der besonders kontroversen Themen zurück, bei denen die Regierung mit Kritik der Opposition oder aus den eignenen Reihen umgehen musste.
Hierbei berücksichtigt unsere Herangehensweise indes nicht, ob ein Thema negative Äußerungen nach sich zieht, weil es sich um einen negativen Sachverhalt (etwa eine Naturkatastrophe) handelt, der aber nicht automatisch kontrovers sein muss, oder ob Kritik an der Regierung geübt wird.
Etwas zu individuellen Unterschieden erfahren wir, wenn wir die Sprecher mit besonders positivem und negativem Sentiment nach Parteizugehörigkeit betrachten. Wieder berechnen wir hierzu eine gruppierte DFM.
meine.dfm.bundestag.sprecher <- dfm(korpus.bundestag, groups = "speaker_cleaned", dictionary = sentiment.lexikon.rauh)
meine.dfm.bundestag.sprecher.prop <- dfm_weight(meine.dfm.bundestag.sprecher, scheme = "prop")
head(meine.dfm.bundestag.sprecher.prop)
Document-feature matrix of: 6 documents, 2 features (16.7% sparse) and 19 docvars.
features
docs positive negative
(Alexander Dobrindt 0.5000000 0.5000000
Abg. Dr. Johannes Fechner 0 0
Achim Post 0.6164818 0.3835182
Agnieszka Brugger 0.5289470 0.4710530
Albert Rupprecht 0.6961326 0.3038674
Albert Stegemann 0.6164223 0.3835777
Nun ziehen wir noch die Parteizugehörigkeit aus den Metadaten hinzu, skalieren das Sentiment-Ergebnis, und sortieren schließelich die 637 Abgeordneten einmal absteigend und einmal aufsteigend (s.u.).
sentiment.bundestag.sprecher <- convert(meine.dfm.bundestag.sprecher, "data.frame") %>%
left_join(unique(select(korpus.bundestag.stats, speaker_cleaned, party)), by = c("doc_id" = "speaker_cleaned")) %>%
rename(Sprecher = doc_id, Partei = party) %>%
filter(positive != 0, negative != 0) %>%
gather(positive, negative, key = "Polarität", value = "Sentiment") %>%
filter(Polarität == "positive") %>%
mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
select(Sentiment, Sprecher, Partei)
# Sprecher mit positivem Sentiment
arrange(sentiment.bundestag.sprecher, desc(Sentiment))
# Sprecher mit negativem Sentiment
arrange(sentiment.bundestag.sprecher, Sentiment)
Der damalige Bundestagspräsident Norbert Lammert führt die Positiv–Liste an, gefolgt von den Vizepräsidentinnen Claudia Roth, Ulla Schmidt, Edelgard Bulmahn und Petra Pau, dazwischen die Bundeskanzlerin Angela Merkel und der damalige Vizekanzler Sigmar Gabriel. Die hohen Werte für die Vertreter der wichtigsten parlamentarischen Ämter sind wenig überraschend. Bei den Negativwerten fällt auf, dass die Regierungsparteien CDU/CSU/SPD überraschend stark vertretend sind, auch wenn die Liste von Oppositionspolitikerinnen der Grünen bzw. Linken angeführt wird.
Eine wirklich adäquate Analyse müsste unter anderem hier noch Sprecher mit sehr geringen Redeanteilen ausschließen, da deren Sentiment-Score durch einige wenige Begriffe in die eine oder andere Richtung ausschlagen kann, auch wenn dies intuitiv keierlei Sinn ergibt. Und wie eingangs beschrieben muss man den Kontext einer Äußerung und die Zugehörigkeit zur Regierung bzw. Opposition heranziehen, was etwa durch die Untersuchung der TOPs grundsätzlich möglich wird.
Abschließend sollte festgehalten werden, dass Sentimentanalyse mittels Quanteda über aus Auszählen von Begriffen abläuft, allerdings auch leichte Variationen dieses Ansatzes existieren. so will man etwa für Tweets aber auch für einzelnen Sätzen in einem Debattenkorpus ein Sentiment festlegen, welches nicht allein aus der Addition von positiven/negativen Wörtern ergibt. Darauf wird später noch eingegangen, allerdings erreichen auch die bereits beschriebenen Verfahren insgesamt recht gute Ergebnisse, solange die Korpusgröße und die Genauigkeit des Lexikons ausreichend sind.
LS0tCnRpdGxlOiAiQXV0b21hdGlzaWVydGUgSW5oYWx0c2FuYWx5c2UgbWl0IFIiCmF1dGhvcjogIkNvcm5lbGl1cyBQdXNjaG1hbm4iCnN1YnRpdGxlOiBTZW50aW1lbnRhbmFseXNlCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjwhLS0tClRvZG9zCiogUmVkZGl0OiBkaXN0aW5jdCB3b3JkcyBmb3IgcG9zaXRpdmUvbmVnYXRpdmUvbmV1dHJhbAoqIHRhcHBseShyZWRkaXQuc2VudGltZW50LnNoYXJlJGNvbW1lbnRfc2NvcmUsIHJlZGRpdC5zZW50aW1lbnQuc2hhcmUkU2VudGltZW50LCBtZWFuKQoqIC4uLgotLT4KCldpZSBhdWNoIGVpbmlnZSBhbmRlcmUgaGllciBiZWhhbmRlbHRlIFZlcmZhaHJlbiBoYXQgZGllIFtTZW50aW1lbnRhbmFseXNlXShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9TZW50aW1lbnRhbmFseXNlKSBpaHJlIFd1cnplbG4gaW4gZGVyIENvbXB1dGVybGluZ3Vpc3RpayB1bmQgSW5mb3JtYXRpaywgd2lyZCBhYmVyIHNlaXQgZWluaWdlbiBKYWhyZW4gYXVjaCB6dW5laG1lbmQgaW4gZGVuIFNvemlhbHdpc3NlbnNjaGFmdGVuIGFuZ2V3YW5kdCwgdW0gZ2FueiB1bnRlcnNjaGllZGxpY2hlIFRleHRlIGF1dG9tYXRpc2NoIHp1IGtsYXNzaWZpemllcmVuLCBldHdhIFBhcmxhbWVudHNkZWJhdHRlbiwgRnJlaXRleHRhbnR3b3J0ZW4gaW4gQmVmcmFndW5nZW4sIG9kZXIgU29jaWFsIE1lZGlhLURpc2t1cnNlLiBaaWVsIGRlciBTZW50aW1lbnRhbmFseXNlIGlzdCBkaWUgQmVzdGltbXVuZyBkZXIgUG9sYXJpdMOkdCBlaW5lcyBUZXh0ZXMsIHdvbWl0IGdlbWVpbnQgaXN0LCBvYiBkaWUgZGFyaW4genVtIEF1c2RydWNrIGdlYnJhY2h0ZW4gRW1vdGlvbmVuIGVoZXIgcG9zaXRpdiBvZGVyIG5lZ2F0aXYgc2luZC4gRGllcyBnZXNjaGllaHQgaMOkdWZpZyBkdXJjaCBXb3J0bGlzdGVuIHVuZCDDvGJlciBkYXMgQXVzesOkaGxlbiB2b24gQmVncmlmZmVuLCBkaWUgenV2b3IgZGVuIEthdGVnb3JpZW4gKnBvc2l0aXYqIG9kZXIgKm5lZ2F0aXYqIHp1Z2VvcmRuZXQgd3VyZGVuLiBJbiB2aWVsZW4gVmVyZmFocmVuIHdpcmQgZGFzIFJlc3VsdGF0IGFuYWNobGllw59lbmQgc2thbGllcnQsIG9kZXIgZXMgd2VyZGVuIGFuZGVyZSBTY2hyaXR0ZSBhbmdld2FuZHQsIHVtIGZlaGxlcmhhZnRlIEtsYXNzaWZpemllcnVuZ2VuIHp1IHZlcm1laWRlbi4gRGllc2UgdHJldGVuIHZvciBhbGxlbSBkYW5uIGF1Ziwgd2VubiBOZWdhdGlvbiBvZGVyIElyb25pZSB2ZXJ3ZW5kZXQgd2VyZGVuLCBhYmVyIGF1Y2gsIHdlbm4gZGVyIEdlZ2Vuc3RhbmQgZGVyIHBvc2l0aXZlbiBvZGVyIG5lZ2F0aXZlbiBBdXNkcsO8Y2tlIHdlY2hzZWx0IG9kZXIgdW5rbGFyIGlzdC4gVW5zcHLDvG5nbGljaCB3dXJkZSBkaWUgU2VudGltZW50YW5hbHlzZSBhdWYgUHJvZHVrdGJld2VydHVuZ2VuIGF1ZiBFLUNvbW1lcmVuY2UtUGxhdHRmb3JtZW4gd2llIEFtYXpvbi5jb20gZ2V0ZXN0ZXQsIHdvIGRpZXNlIFByb2JsZW1lIGVpbmUgcmVsYXRpdiBnZXJpbmdlIFJvbGxlIHNwaWVsZW4uIEJlaSBQcmVzc2V0ZXh0ZW4gb2RlciBEaXNrdXJzZW4gaW4gZGVuIHNvemlhbGVuIE1lZGllbiBoaW5nZWdlbiwgaXN0IG9mdCBzY2h3ZXJlciB6dSBiZXdlcnRlbiwgYXVmIHdhcyBzaWNoIGVpbmUgU2VudGltZW50YmV3ZXJ0dW5nIGJlemllaHQsIG9kZXIgd2VsY2hlcyBTZW50aW1lbm5pdmVhdSBldHdhIGFscyDigJhub3JtYWzigJkgYmV0cmFjaHRldCB3ZXJkZW4gc29sbHRlLiBTbyBrb21tZW4gYmVpc3BpZWxzd2Vpc2UgaW4gUHJlc3NldGV4dGVuIGdlbmVyZWxsIHdlbmlnIEVtb3Rpb25lbiB6dW0gQXVzZHJ1Y2ssIHVuZCBkaWUgbmVnYXRpdmVuIEJlZ3JpZmZlIMO8YmVyd2llZ2VuIGjDpHVmaWcsIG9obmUgZGFzcyBkaWVzIG5vdHdlbmRpZ2Vyd2Vpc2UgYXVmIGVpbmVuIHNjaGxlY2h0ZW4gWnVzdGFuZCBkZXIgV2VsdCB6dXLDvGNrenVmw7xocmVuIHfDpHJlLiBTY2hsaWXDn2xpY2ggc29sbHRlIG1hbiBzaWNoIHZvciBBdWdlbiBmw7xocmVuLCBkYXNzIGRpZSBTZW50aW1lbnRhbmFseXNlIGVpbiBoZXVyaXN0aXNjaGVzIFZlcmZhaHJlbiBpc3QsIGRhc3MgaW1tZXIgYXVjaCBmZWhsZXJoYWZ0ZSBFaW56ZWxrbGFzc2lmaWthdGlvbmVuIHByb2R1emllcnQsIHdhcyBhYmVyIGlkZWFsZXJ3ZWlzZSBuaWNodCB6dSBzdGFyayBpbnMgR2V3aWNodCBmw6RsbHQsIHdlbm4gbWFuIGV0d2FzIFZlcsOkbmRlcnVuZ2VuIGltIFNlbnRpbWVudHZlcmxhdWYgw7xiZXIgZGllIFplaXQgdW50ZXJzdWNodC4KCldhcyBkaWUgdGVjaG5pc2NoZSBVbXNldHp1bmcgYW5nZWh0LCBzbyBnZWjDtnJlbiBkaWVzZXIgQWJzY2huaXR0IHVuZCBkYXMgbsOkY2hzdGUgenUgc3BlemlhbGlzaWVydGVuIExleGlrYSBpbnNvZmVybiB6dXNhbW1lbiwgYWxzIGRhc3MgZXMgc2ljaCBiZWkgYmVpZGVuIEFuc8OkdHplbiB1bSBnYW56IMOkaG5saWNoZSBWZXJmYWhyZW4gaGFuZGVsdC4gSW4gYmVpZGVuIEbDpGxsZW4gd2lyZCBlaW4gTGV4aWtvbiAoJ3NlbnRpbWVudC90b3BpYyBkaWN0aW9uYXJ5JykgdmVyd2VuZGV0LCB1bSBlaW5lIFJlaWhlIHZvbiBFaW56ZWxiZWdyaWZmZW4gaW4gZWluZXIgS2F0ZWdvcmllIHp1c2FtbWVuenVmYXNzZW4uIAoKV2lyIHZlcndlbmRlbiBpbiBkaWVzZW0gS2FwaXRlbCBzZWNocyB1bnRlcnNjaGllZGxpY2hlIFNlbnRpbWVudGxleGlrYSwgZGF2b24gdmllciBpbiBlbmdsaXNjaGVyIFNwcmFjaGUgdW5kIHp3ZWkgZsO8ciBEZXV0c2NoOgoKKiBbQmluZyBMaXUgU2VudGltZW50IExleGljb25dKGh0dHBzOi8vd3d3LmNzLnVpYy5lZHUvfmxpdWIvRkJTL3NlbnRpbWVudC1hbmFseXNpcy5odG1sI2xleGljb24pCiogW05SQyBFbW90aW9uIExleGljb25dKGh0dHA6Ly9zYWlmbW9oYW1tYWQuY29tL1dlYlBhZ2VzL05SQy1FbW90aW9uLUxleGljb24uaHRtKQoqIFtBRklOTl0oaHR0cDovL3d3dzIuaW1tLmR0dS5kay9wdWJkYi92aWV3cy9wdWJsaWNhdGlvbl9kZXRhaWxzLnBocD9pZD02MDEwKQoqIFtMZXhpY29kZXIgU2VudGltZW50IERpY3Rpb25hcnldKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL2RhdGFfZGljdGlvbmFyeV9MU0QyMDE1Lmh0bWwpCiogW1NlbnRpV1NdKGh0dHA6Ly93b3J0c2NoYXR6LnVuaS1sZWlwemlnLmRlL2RlL2Rvd25sb2FkI3NlbnRpV1NEb3dubG9hZCkKKiBbQ2hyaXN0aWFuIFJhdWgncyBTZW50aW1lbnQgRGljdGlvbmFyeV0oaHR0cHM6Ly9kYXRhdmVyc2UuaGFydmFyZC5lZHUvZGF0YXNldC54aHRtbD9wZXJzaXN0ZW50SWQ9ZG9pOjEwLjc5MTAvRFZOL0JLQlhXRCkKCkRpZXNlIExleGlrYSBzaW5kIGxlZGlnbGljaCBMaXN0ZW4gdm9uIFfDtnJ0ZXJuLCB3ZWxjaGUgd2llIG9iZW4gYmVzY2hyaWViZW4gZGVuIEthdGVnb3JpZW4gKnBvc3Rpdiogb2RlciAqbmVnYXRpdiogenVnZW9yZG5ldCBzaW5kLiBadW0gVGVpbCBleGlzdGllcnQgYXVjaCBub2NoIGVpbmUgZHJpdHRlIEthdGVnb3JpZSAqbmV1dHJhbCosIGRlc3dlaXRlcmVuIGvDtm5uZW4gQmVncmlmZmUgYXVjaCBtZWhyZXJlbiBLYXRlZ29yaWVuIHp1Z2VvcmRuZXQgc2Vpbiwgb2RlciBuZWJlbiBlaW5lciBadW9yZG51bmcgZGVyIFBvbGFyaXTDpHQgYXVjaCBub2NoIGVpbmUgU2VudGltZW50c3TDpHJrZSB6dWdlc2NocmllYmVuIGJla29tbWVuLiBEaWUgaGllciB2b3JnZXN0ZWxsdGUgVGVjaG5payBpc3QgdmVyZ2xlaWNoc3dlaXNlIHByaW1pdGl2LCB3ZWlsIHNpZSBsZWRpZ2xpY2ggV8O2cnRlciBhdXN6w6RobHQsIGFsbGVyZGluZ3MgbGFzc2VuIHNpY2ggZGllIFZlcmZhaHJlbiBsZWljaHQgbm9jaCB2ZXJmZWluZXJuICh2Z2wuIGJzcHcuIFtkaWVzZW4gQmVpdHJhZyB2b24gQ2hyaXN0aWFuIFJhdWhdKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9kb2kvZnVsbC8xMC4xMDgwLzE5MzMxNjgxLjIwMTguMTQ4NTYwOCkgenVyIFZhbGlkaWVydW5nIHBvbGl0aXNjaGVyIFNlbnRpbWVudC1MZXhpa2EpLiBBdWNoIFZlcmZhaHJlbiBkaWUgZ2V3aWNodGVuIG9kZXIgYW5kZXJlIEtuaWZmZSBmw7xyIGRpZSBWZXJyaW5nZXJ1bmcgZGVyIEZlaGxlcnJhdGUgZWluc2V0emVuLCBmdW5rdGlvbmllcmVuIHNvIOKAlCBkaWUgU2VudGltZW50YW5hbHllIGlzdCBlZmZla3RpdiwgYWJlciBhdWNoIGFsbGVzIGFuZGVyZSBhbHMgSGV4ZW53ZXJrLgoKRGllc2UgTGV4aWthIHdlbmRlbiB3aXIgZm9sZ2VuZCBhdWYgZsO8bmYgRGF0ZW5zw6R0emUgYW46IGRhcyBiZXJlaXRzIGJla2FubnRlIFNoZXJsb2NrIEhvbG1lc+KAlEtvcnB1cywgZWluZW4gRGF0ZW5zYXR6IGF1cyBUd2VldHMgdm9uIERvbmFsZCBUcnVtcCB1bmQgSGlsbGFyeSBDbGludGVuLCBlaW5lbiBLb21tZW50YXItS29ycHVzIGF1cyBkZXIgRGlza3Vzc2lvbnNwbGF0dGZvcm0gUmVkZGl0LCBlaW4gS29ycHVzIFNjaHdlaXplciBUYWdlc3plaXR1Z2VuIG1pdCBBcnRpa2VsbiB6dXIgRmluYW56a3Jpc2UsIGRpZSB6d2lzY2hlbiAyMDA3IHVuZCAyMDEyIHZlcmZhc3N0IHd1cmRlbiwgdW5kIHNjaGxpZcOfbGljaCBub2NoIGVpbmVuIERlYmF0dGVua29ycHVzIGRlcyAxOC4gRGV1dHNjaGVuIEJ1bmRlc3RhZ3MgKDIwMTMgYmlzIDIwMTcpLiBBdWYgZGllIFp1c2FtbWVuc3RlbGx1bmcgZGVyIEtvcnBvcmEgZ2VoZW4gd2lyIHNww6R0ZXIgbm9jaCBlaW4uCgojIyMgSW5zdGFsbGF0aW9uIHVuZCBMYWRlbiBkZXIgYmVuw7Z0aWd0ZW4gUi1CaWJsaW90aGVrZW4sIExhZGVuIGRlcyBLb3JwdXMKClp1bsOkY2hzdCB3ZXJkZW4gd2llZGVyIGRpZSBub3R3ZW5kaWdlbiBCaWJsaW90aGVrZW4gZ2VsYWRlbi4gTmV1IGlzdCBkaWUgQmlibGlvdGhlayBbc2NhbGVzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPXNjYWxlcykgZGllIGJlaSBkZXIgTm9ybWFsaXNpZXJ1bmcgdm9uIFNlbnRpbWVudOKAlFNjb3JlcyB6dW0gRWluc2F0eiBrb21tdC4gRGFubiB3aXJkIGluIGVpbmVtIHp3ZWl0ZW4gU2Nocml0dCBkYXMgU2hlcmxvY2stS29ycHVzIGdlbGFkZW4sIHdlbGNoZXMgd2lyIGphIGJlcmVpdHMgenV2b3IgbmVic3QgTWV0YWRhdGVuIGltIFJEYXRhLUZvcm1hdCBnZXNwZWljaGVydCBoYWJlbi4KCmBgYHtyIEluc3RhbGxhdGlvbiB1bmQgTGFkZW4gZGVyIGJlbsO2dGlndGVuIFItQmlibGlvdGhla2VuLCBtZXNzYWdlID0gRkFMU0V9CmlmKCFyZXF1aXJlKCJxdWFudGVkYSIpKSB7aW5zdGFsbC5wYWNrYWdlcygicXVhbnRlZGEiKTsgbGlicmFyeSgicXVhbnRlZGEiKX0KaWYoIXJlcXVpcmUoInJlYWR0ZXh0IikpIHtpbnN0YWxsLnBhY2thZ2VzKCJyZWFkdGV4dCIpOyBsaWJyYXJ5KCJyZWFkdGV4dCIpfQppZighcmVxdWlyZSgidGlkeXZlcnNlIikpIHtpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKTsgbGlicmFyeSgidGlkeXZlcnNlIil9CmlmKCFyZXF1aXJlKCJzY2FsZXMiKSkge2luc3RhbGwucGFja2FnZXMoInNjYWxlcyIpOyBsaWJyYXJ5KCJzY2FsZXMiKX0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgpgYGB7ciBMYWRlbiBkZXMgU2hlcmxvY2sgSG9sbWVzLUtvcnB1c30KbG9hZCgiZGF0ZW4vc2hlcmxvY2svc2hlcmxvY2sua29ycHVzLlJEYXRhIikKYGBgCgojIyMgRXJzdGVsbHVuZyBlaW5lcyBMZXhpa29ucyBpbiBxdWFudGVkYQoKV2lyIGJlZ2lubmVuIHp1bsOkY2hzdCBtaXQgZWluZXIgU2VudGltZW50YW5hbHlzZSBkZXIgU2hlcmxvY2sgSG9sbWVzLUVyesOkaGx1bmdlbiwgdW0gYmVpIGVpbmVtIGJlcmVpdHMgYXVzIEthcGl0ZWwgMSB1bmQgMiB2ZXJ0cmF1dGVuIEtvcnB1cyB6dSBibGVpYmVuLiBJbiBlaW5lbSBlcnN0ZW4gU2Nocml0dCBlcnN0ZWxsZW4gd2lyIGVpbiBzZWhyIGVpbmZhY2hlcyBBZCBob2MtTGV4aWtvbiBhdXMgbnVyIHNlY2hzIEJlZ3JpZmZlbiwgdW0gZGllIFN0cnVrdHVyIGVpbmVzIExleGlrb25zIGluIHF1YW50ZWRhIHp1IGlsbHVzdHJpZXJlbi4gRGllcyBnZXNjaGllaHQgbWl0IGRlbSBxdWFudGVkYS1CZWZlaGwgW2RpY3Rpb25hcnldKGh0dHA6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS9kaWN0aW9uYXJ5Lmh0bWwpLiBEaWN0aW9uYXJ5KCkgYWt6ZXB0aWVydCBlaW5lIFJlaWhlIHZvbiBTdGFuZGFyZGZvcm1hdGVuIChkYXp1IHNww6R0ZXIgbm9jaCBtZWhyKSwgYWJlciBhdWNoIFZla3RvcmVuLCB3ZWxjaGUgZGllIEJlZ3JpZmZlIGVudGhhbHRlbiwgZGllIGVpbmUgYWJzdHJha3RlIEthdGVnb3JpZSBvcGVyYXRpb25hbGlzaWVyZW4uIEJlbGllYmlnIHZpZWxlIEthdGVnb3JpZW4ga8O2bm5lbiBzbyBkZWZpbmllcnQgdW5kIGRhbm4gbWl0IHRhdXNlbmRlbiB2b24gQmVncmlmZmVuICdiZWbDvGxsdCcgd2VyZGVuLiBBdWNoIEthdGVnb3JpZW4gbWl0IG1laHJlcmVuIGhpZXJhcmNoaXNjaGVuIEViZW5lbiBzaW5kIG3DtmdsaWNoIOKAlCBkYXp1IGltIG7DpGNoc3RlbiBLYXBpdGVsIG5vY2ggZXR3YXMgbWVoci4KCmBgYHtyIEVyc3RlbGx1bmcgZWluZXMgVGV4dGxleGlrb25zfQp0ZXN0LmxleGlrb24gPC0gZGljdGlvbmFyeShsaXN0KHBvc2l0aXRpdmUuYmVncmlmZmUgPSBjKCJnbMO8Y2siLCAiZnJldWRlIiwgImxpY2h0IiksIG5lZ2F0aXZlLmJlZ3JpZmZlID0gYygidHJhdWVyIiwgInd1dCIsICJkdW5rZWxoZWl0IikpKQp0ZXN0LmxleGlrb24KYGBgCgojIyMgRXJzdGUgU2VudGltZW50LUFuYWx5c2UgbWl0IGRlbSBTaGVybG9jayBIb2xtZXMtS29ycHVzCgpNaXQgZGllc2VtIExleGlrb24ga8O2bm5lbiB3aXIgbWl0IHVuc2VyZW0gZW5nbGlzY2hzcHJhY2hpZ2VuIEtvcnB1cyB3ZW5pZyBrb25rcmV0ZXMgYW5mYW5nZW4sIGRhaGVyIHdlY2hzZWxuIHdpciBiZXNzZXIgenUgZWluZW0gZWNodGVuIFNlbnRpbWVudGxleGlrb24uIEluIGVpbmVtIHp3ZWl0ZW4gU2Nocml0dCBsZXNlbiB3aXIgbWl0IGRlbSBCZWZlaGwgW3NjYW5dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9iYXNlL3ZlcnNpb25zLzMuNS4xL3RvcGljcy9zY2FuKSBkYXMgW0JpbmcgTGl1IFNlbnRpbWVudCBMZXhpa29uXShodHRwczovL3d3dy5jcy51aWMuZWR1L35saXViL0ZCUy9zZW50aW1lbnQtYW5hbHlzaXMuaHRtbCNsZXhpY29uKSBpbiBSIGVpbi4gRGllc2VzIExleGlrb24gdW1mYXNzdCDDvGJlciA2LjcwMCBlbmdsaXNjaHNwcmFjaGlnZSBCZWdyaWZmZSBkaWUgaW4gendlaSBlaW5mYWNoZW5uIFRleHRkYXRlaWVuIGFiZ2VsZWd0IHNpbmQsIGRpZSBqZXdlaWxzIGVpbiBXb3J0IGplIFplaWxlIGVudGhhbHRlbi4gV2lyIMO8YmVyc3ByaW5nZW4gbWl0IGRlbSBBcmd1bWVudCAqc2tpcCogZGllIGVyc3RlbiAzNSBaZWlsZW4sIGRhIGRpZXNlIE1ldGFpbmZvcm1hdGlvbmVuIMO8YmVyIGRhcyBMZXhpa29uIGVudGhhbHRlbi4gRGFzIEFyZ3VtZW50ICpxdWlldCogdmVyaGluZGV0IGRpZSBBdXNnYWJlIGVpbmVyIFN0YXR1c21lbGR1bmcuIAoKYGBge3IgRWlubGVzZW4gZGVzIEJpbmcgTGl1LUxleGlrb25zfQpwb3NpdGl2ZS53b2VydGVyLmJsIDwtIHNjYW4oImxleGlrYS9iaW5nbGl1LXBvc2l0aXZlLXdvcmRzLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHNraXAgPSAzNSwgcXVpZXQgPSBUKQpuZWdhdGl2ZS53b2VydGVyLmJsIDwtIHNjYW4oImxleGlrYS9iaW5nbGl1LW5lZ2F0aXZlLXdvcmRzLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHNraXAgPSAzNSwgcXVpZXQgPSBUKQpgYGAKCk51biBlcnN0ZWxsZW4gd2lyIGRhcyBMZXhpa29uIG1pdGhpbGZlIGRlciBnZXJhZGUgZWluZ2VsZXNlbmVuIFRleHR2ZWt0b3Jlbi4gRGllcyBlcmZvbGd0IHdpZWRlciBtaXQgZGVyIEZ1bmt0aW9uIGRpY3Rpb25hcnkoKSwgZGllc21hbCBtaXQgZGVuIGdlcmFkZSBlaW5nZWxlc2VuZW4gVmVrdG9yZW4gYWxzIEFyZ3VtZW50LgoKYGBge3IgR2VuZXJpZXJ1bmcgZWluZXMgTGV4aWtvbi1PYmpla3RzIGR1cmNoIGRpZSBlaW5nZWxlc2VuZW4gVGV4dHZla3RvcmVufQpzZW50aW1lbnQubGV4aWtvbiA8LSBkaWN0aW9uYXJ5KGxpc3QocG9zaXRpdmUgPSBwb3NpdGl2ZS53b2VydGVyLmJsLCBuZWdhdGl2ZSA9IG5lZ2F0aXZlLndvZXJ0ZXIuYmwpKQpzdHIoc2VudGltZW50LmxleGlrb24pCmBgYAoKV2llIG1hbiBzaWVodCwgc2luZCBudW4gbWVocmVyZSB0YXVzZW5kIEJlZ3JpZmZlIGRlbiBiZWlkZW4gS2F0ZWdvcmllbiBkZXMgTGV4aWtvbnMgenVnZW9yZG5ldCB3b3JkZW4uIEpldHp0IGvDtm5uZW4gd2lyIGVpbmUgREZNIGJlcmVjaG5lbiwgd2VsY2hlIGRhcyBlcnN0ZWxsdGUgTGV4aWtvbiBhdWYgZGFzIEtvcnB1cyBhbndlbmRldC4KCmBgYHtyIEVyc3RlbGx1bmcgZWluZXIgREZNIHVudGVyIEFud2VuZHVuZyBkZXMgTGV4aWtvbnN9Cm1laW5lLmRmbS5zZW50aW1lbnQgPC0gZGZtKGtvcnB1cywgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uKQptZWluZS5kZm0uc2VudGltZW50CmBgYAoKV2FzIGlzdCBnZXNjaGVoZW4/ICpBbGxlKiB0YXRzw6RjaGxpY2ggdm9ya29tbWVuZGVuIE5lbm51bmdlbiBkZXIgcnVuZCA2LjcwMCBpbSBCaW5nIExpdS1MZXhpa29uIGVudGhhbHRlbmVuIEJlZ3JpZmZlIGluIGRlbiB6d8O2bGYgU2hlcmxvY2sgSG9sbWVz4oCUUm9tYW5lbiBzaW5kIGpld2VpbHMgZHVyY2ggZGllIGlobmVuIHp1Z2VvcmRuZXRlIEthdGVnb3JpZSBlcnNldHp0IHdvcmRlbi4gU8OkbXRsaWNoZSBCZWdyaWZmZSwgZGllIG5pY2h0IGltIExleGlrb24gdm9ya29tbWVuLCBmYWxsZW4gZGFiZWkgZWluZmFjaCB3ZWcuIERhZHVyY2ggYmxlaWJ0IGVpbmUgVGFiZWxsZSB6dXLDvGNrLCBkaWUgbnVyIG5vY2ggendlaSBTcGFsdGVuIGVudGjDpGx0IOKAlCBkaWUgU3VtbWUgYWxsZXIgKnBvc2l0aXZlbiogdW5kICpuZWdhdGl2ZW4qIEJlZ3JpZmZlIHBybyBSb21hbi4gV2lyIHdlcmRlbiBkYXJhdWYgc3DDpHRlciBub2NoIGltIERldGFpbCB6dSBzcHJlY2hlbiBrb21tZW4sIGFiZXIgdmllbGxlaWNodCBoYWJlbiBTaWUgc2Nob24gYmVtZXJrdCwgZGFzcyBtaXR0ZWxzICpkaWN0aW9uYXJ5KiBkaWUgU3BhbHRlbiBlaW5lciBERk0genVzYW1tZW5nZWZhc3N0IHdlcmRlbiAoYWxzbyBkaWUgV8O2cnRlciksIHfDpGhyZW5kIGRhcyBBcmd1bWVudCAqZ3JvdXAqIGRlciBGdW5rdGlvbiBkZm0oKSBkaWUgWmVpbGVuIHp1c2FtbWVuZmFzc3QgKGFsc28gZGllIFRleHRlKS4gRGllc2UgZGltZW5zaW9uYWxlIFJlZHV6aWVydW5nIGdlaMO2cnQgenUgZGVuIG7DvHR6bGljaHN0ZW4gRWlnZW5zY2hhZnRlbiB2b24gcXVhbnRlZGEuICAKCkRhcyBmb2xnZW5kZSBQbG90IHplaWd0IGRpZSBTZW50aW1lbnQtVmVydGVpbHVuZyBpbiBkZW4genfDtmxmIFNoZXJsb2NrIEhvbG1lcy1FcnrDpGhsdW5nZW4uIAoKYGBge3IgU2VudGltZW50LVNjb3JlcyBpbSBTaGVybG9jayBIb2xtZXMtS29ycHVzIHBsb3R0ZW59CnNlbnRpbWVudCA8LSBjb252ZXJ0KG1laW5lLmRmbS5zZW50aW1lbnQsICJkYXRhLmZyYW1lIikgJT4lCiAgZ2F0aGVyKHBvc2l0aXZlLCBuZWdhdGl2ZSwga2V5ID0gIlBvbGFyaXTDpHQiLCB2YWx1ZSA9ICJXw7ZydGVyIikgJT4lIAogIG11dGF0ZShkb2NfaWQgPSBhc19mYWN0b3IoZG9jX2lkKSkgJT4lIAogIHJlbmFtZShSb21hbiA9IGRvY19pZCkKZ2dwbG90KHNlbnRpbWVudCwgYWVzKFJvbWFuLCBXw7ZydGVyLCBjb2xvdXIgPSBQb2xhcml0w6R0LCBncm91cCA9IFBvbGFyaXTDpHQpKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIlNlbnRpbWVudC1TY29yZXMgaW4genfDtmxmIFNoZXJsb2NrIEhvbG1lcy1Sb21hbmVuIikKYGBgCgojIyMgR2V3aWNodHVuZyB2b24gU2VudGltZW50LVNjb3JlcwoKRXJpbm5lcnQgbWFuIHNpY2ggYW4gZGllIGltIHZvcmhlcmlnZW4gS2FwaXRlbCBhZHJlc3NpZXJ0ZW4gUHJvYmxlbWUgYWJzb2x1dGVyIFdvcnRmcmVxdWVuemVuLCBzbyBtw7ZjaHRlIG1hbiB2aWVsbGVpY2h0IGxpZWJlciByZWxhdGl2ZSBGcmVxdWVuemVuIGJlcmVjaG5lbi4gRGFzIGJlZGV1dGV0IGJlaW0gRWluc2F0eiB2b24gTGV4aWthIGluIGRlciBSZWdlbCBuaWNodCBudXIsIGRhc3MgbWFuIGRhcyBBdWZ0cmV0ZW4gZGVyIExleGlrb24tQmVncmlmZmUgcmVsYXRpdiB6dXIgR2VzYW10d29ydGZyZXF1ZW56IG1pc3N0LCBzb25kZXJuIGRlcmVuIEFudGVpbCByZWxhdGl2IHp1IGVpbmFuZGVyIChhbHNvIGRhcyBWZXJow6RsdG5pcyBwb3NpdGl2ZXIgdW5kIG5lZ2F0aXZlciBCZWdyaWZmZSkuIERpZXMgaGF0IGRlbiBWb3J0ZWlsLCBkYXNzIG1hbiBkaWUgZ3Jvw59lIFphaGwgYWxsZXIgQmVncmlmZmUsIGRpZSB3ZWRlciBwb3NpdGl2IG5vY2ggbmVnYXRpdiBzaW5kLCB1bmJlcsO8Y2tzaWNodGlndCBsYXNzZW4ga2Fubiwgd2FzIGR1cmNoYXVzIFNpbm4gZXJnaWJ0LCB3ZW5uIG1hbiBzaWNoIGViZW4gbnVyIGbDvHIgZGFzIFNlbnRpbWVudCBpbnRlcmVzc2llcnQuCgpEYXMgZm9sZ2VuZGVuIEJlaXNwaWVsIHZlcmRldXRsaWNodCBkaWVzZXMgVm9yZ2VoZW4uIAoKYGBge3IgREZNIG1pdCBTZW50aW1lbnQtU2NvcmVzIGdld2ljaHRlbn0KbWVpbmUuZGZtLnNlbnRpbWVudC5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLnNlbnRpbWVudCwgc2NoZW1lID0gInByb3AiKQptZWluZS5kZm0uc2VudGltZW50LnByb3AKYGBgCgpBdWNoIGRpZXNlIERGTSBsw6Rzc3Qgc2ljaCBuYXTDvHJsaWNoIGxlaWNodCBwbG90dGVuLiAKCmBgYHtyIFJlbGF0aXZlIFNlbnRpbWVudC1TY29yZXMgcGxvdHRlbn0Kc2VudGltZW50IDwtIGNvbnZlcnQobWVpbmUuZGZtLnNlbnRpbWVudC5wcm9wLCAiZGF0YS5mcmFtZSIpICU+JQogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIG11dGF0ZShkb2NfaWQgPSBhc19mYWN0b3IoZG9jX2lkKSkgJT4lIAogIHJlbmFtZShSb21hbiA9IGRvY19pZCkKZ2dwbG90KHNlbnRpbWVudCwgYWVzKFJvbWFuLCBTZW50aW1lbnQsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGdyb3VwID0gUG9sYXJpdMOkdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0ID0gMSkpICsgCiAgZ2d0aXRsZSgiU2VudGltZW50LVNjb3JlcyBpbiB6d8O2bGYgU2hlcmxvY2sgSG9sbWVzLVJvbWFuZW4gKHJlbGF0aXYpIikKYGBgCgoKIyMjIFZlcnJlY2hudW5nIHVuZCBTa2FsaWVydW5nIHBvc2l0aXZlciB1bmQgbmVnYXRpdmVyIFNlbnRpbWVudC1BbnRlaWxlCgpEaWUgRGFyc3RlbGx1bmcgdm9uIFNlbnRpbWVudGFudGVpbGVuIGlubmVyaGFsYiBkZXIgenfDtmxmIEVyesOkaGx1bmdlbiBsw6Rzc3Qgc2ljaCBub2NoIHZlcmJlc3Nlcm4sIGluZGVtIHdpciBkYXJhdWYgdmVyemljaHRlbiwgYmVpZGUgUG9sYXJpdMOkdGVuIGRhcnN6dXN0ZWxsZW4uIERhIGJlaSBkaWVzZXIgQW53ZW5kdW5nZW4gZGllIG5lZ2F0aXZlIFBvbGFyaXTDpHQgc2NobGljaHQgZGllIEludmVydGllcnVuZyBkZXMgcG9zaXRpdmVuIFNlbnRpbWVudHMgZXJnaWJ0LCByZWljaHQgZGllcyBhdXMuIFp1ZGVtIHNrYWxpZXJlbiB3aXIgZGllIFdlcnRlIG1pdHRlbHMgW3Jlc2NhbGVdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9zY2FsZXMvdmVyc2lvbnMvMC40LjEvdG9waWNzL3Jlc2NhbGUpIG5ldSwgc28gZGFzcyBzaWUgendpc2NoZW4g4oCTMSB1bmQgKzEgbGllZ2VuLgoKYGBge3IgVmVycmVjaG5ldGUgU2VudGltZW50LVNjb3JlcyBwbG90dGVufQpzZW50aW1lbnQgPC0gY29udmVydChtZWluZS5kZm0uc2VudGltZW50LnByb3AsICJkYXRhLmZyYW1lIikgJT4lCiAgcmVuYW1lKFJvbWFuID0gZG9jX2lkLCBTZW50aW1lbnQgPSBwb3NpdGl2ZSkgJT4lCiAgc2VsZWN0KFJvbWFuLCBTZW50aW1lbnQpICU+JQogIG11dGF0ZShTZW50aW1lbnQgPSByZXNjYWxlKFNlbnRpbWVudCwgdG8gPSBjKC0xLDEpKSkgJT4lCiAgbXV0YXRlKFJvbWFuID0gYXNfZmFjdG9yKFJvbWFuKSkKZ2dwbG90KHNlbnRpbWVudCwgYWVzKFJvbWFuLCBTZW50aW1lbnQsIGdyb3VwID0gMSkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImxpZ2h0Z3JheSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIlZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgaW4genfDtmxmIFNoZXJsb2NrIEhvbG1lcy1Sb21hbmVuIikKYGBgCgpXaXIgaGFsdGVuIGZlc3Q6IERpZSBSb21hbmUgenUgQmVnaW5uIGRlcyBTaGVybG9jayBIb2xtZXMtWnlsdXMgc2luZCBldHdhcyBwb3NpdGl2ZXIsIHfDpGhyZW5kIGVzIGluIGRlciBNaXR0ZSBkw7xzdGVyZXIgenVnZWh0LiBadW0gU2NobHVzcyBoZWJ0IHNpY2ggZGllIFN0aW1tdW5nIGFiZXIgLSBqZWRlbmZhbGxzIGltIFZlcmdsZWljaCAtIHdpZWRlci4gV2VubiBtYW4gZGllc2UgRGFyc3RlbGx1bmcgbWl0IGRlbSBlcnN0ZW4gUGxvdCBrb250cmFzdGllcnQgd2lyZCBrbGFyLCB3YXJ1bSB2ZXJyZWNobmV0ZSBGcmVxdWVuemVuIG9mdG1hbHMgZWluZW4gZ3V0ZW4gQW5zYXR6IGRhcnN0ZWxsZW4uIEFuZGVyZXJzZWl0cyBkYXJmIG1hbiBhYmVyIGF1Y2ggbmljaHQgZGVyIEFubmFobWUgYXVmIGRlbiBMZWltIGdlaGVuLCBkYXMgU2VudGltZW50IGluICdUaGUgQWR2ZW50dXJlIG9mIHRoZSBTcGVja2xlZCBCYW5kJyBzZWkgYXVzc2NobGllw59saWNoIG5lZ2F0aXYsIGFsc28gJ3p1IDAlIHBvc2l0aXYnLCBkZW5uIGRpZXMgaXN0IGVpbiBBcnRlZmFrdCB1bnNlcmVyIHByb3BvcnRpb25hbGVuIFNrYWxpZXJ1bmcuIEVzIGlzdCBsZWRpZ2xpY2ggKmFudGVpbGlnIG5lZ2F0aXZlciogYWxzIGluIGRlbiBhbmRlcmVuIGVsZiBFcnrDpGhsdW5nZW4uIAoKIyMjIFNlbnRpbWVudGFuYWx5c2UgbWl0IFR3aXR0ZXItRGF0ZW4gdm9uIERvbmFsZCBUcnVtcCB1bmQgSGlsbGFyeSBDbGludG9uCgpXZW5kZW4gd2lyIHVucyBqZXR6dCBlaW5lbSBldHdhcyBha3R1ZWxsZXJlbiBCZWlzcGllbCB6dSwgbsOkbWxpY2ggZGVyIEFuYWx5c2UgZGVzIFNlbnRpbWVudHMgaW4gZGVuIFR3ZWV0cyB2b24gRG9uYWxkIFRydW1wIHVuZCBIaWxsYXJ5IENsaW50b24gdm9yLCB3w6RocmVuZCwgdW5kIG5hY2ggZGVtIFVTLVByw6RzaWRlbnRzY2hhZnRzd2FobGthbXBmIHZvbiAyMDE2LgoKWnVuw6RjaHN0IGxhZGVuIHdpZSBkaWUgVHJ1bXAtIHVuZCBDbGludG9uLVR3aXR0ZXItRGF0ZW5zw6R0emUgKGJlcmVpdHMgaW4gZWluZW0gS29ycHVzLU9iamVrdCB6dXNhbW1lbmdlZmFzc3QgdW5kIGFscyBSRGF0YS1GaWxlIGdlc3BlaWNoZXJ0KS4gRGllc2UgRGF0ZW4gd3VyZGVuIGF1cyB2ZXJzY2hpZWRlbmVuIE9ubGluZeKAk0FyY2hpdmVuIHVuZCBkdXJjaCBkaWUgVHdpdHRlciBBUEkgenVzYW1tZW5nZXN0ZWxsdC4gSWNoIGdlaGUgaGllciBuaWNodCBnZW5hdWVyIGF1ZiBkaWUgRXJzdGVsbHVuZyBkZXMgS29ycHVzIGVpbiwgZGllcyB3aXJkIGFiZXIgc3DDpHRlciBub2NoIGVybMOkdXRlcnQuCgpgYGB7ciBUd2l0dGVyLUtvcnB1cyBsYWRlbn0KbG9hZCgiZGF0ZW4vdHdpdHRlci90cnVtcGNsaW50b24ua29ycHVzLlJEYXRhIikKa29ycHVzLnN0YXRzLm1vbmF0IDwtIHVuZ3JvdXAoa29ycHVzLnN0YXRzLm1vbmF0KQprb3JwdXMuc3RhdHMubW9uYXQKYGBgCgpEaWUgVGFiZWxsZSB6ZWlndCBkaWUgYmVyZWl0cyBtb25hdHN3ZWlzZSBhZ2dyZWdpZXJ0ZW4gVHdlZXQtWmFobGVuIChvZGVyIGdlbmF1ZXIsIGRpZSBBbnphaGwgZGVyIFR5cGVzL1Rva2Vucy9Tw6R0emUpLiBIaWVyIGVpbmlnZSBrb25rcmV0ZSBCZWlzcGllbGUgZsO8ciBUd2VldHMgZGVyIGJlaWRlbiBLYW5kaWRhdGVuICh3aWVkZXIga2FubiBtYW4gbWl0IGRlbSBQZmVpbOKAk0ljb24gbmFjaCByZWNodHMvbGlua3Mgc2Nyb2xsZW4pOgoKYGBge3IgQmVpc3BpZWx0d2VldHMgYW5zZWhlbn0KdHJ1bXBjbGludG9uLnNhbXBsZSA8LSBjb3JwdXNfc2FtcGxlKGtvcnB1cywgc2l6ZSA9IDIwKQpiaW5kX2NvbHModGV4dCA9IHRleHRzKHRydW1wY2xpbnRvbi5zYW1wbGUpLCBkb2N2YXJzKHRydW1wY2xpbnRvbi5zYW1wbGUpKQpgYGAKClp1bsOkY2hzdCBwbG90dGVuIHdpciBkaWUgV8O2cnRlciAob2RlciBnZW5hdWVyIGRlciBUb2tlbnMpIHBybyBNb25hdCBmw7xyIEhpbGxhcnkgQ2xpbnRvbiB1bmQgRG9uYWxkIFRydW1wIGltIFplaXRyYXVtIHZvbiBBcHJpbCAyMDE1IGJpcyBBcHJpbCAyMDE3LCB1bSB1bnMgZWluZW4gRWluZHJ1Y2sgaWhyZXIgQWt0aXZpdMOkdCB6dSB2ZXJzY2hhZmZlbi4gRGllcyBlbnRzcHJpY2h0IHJlbGF0aXYgZ3V0IGRlciBBbnphaGwgZGVyIFR3ZWV0cywgZGEgZGllIFZhcmlhdGlvbiBiZWkgZGVyIEzDpG5nZSBuaWNodCBhbGx6dSBzdGFyayBhdXNmw6RsbHQuIAoKQWNodHVuZzogSGllciB6ZWlndCBkaWUgRmFyYmUgZGVuIEthbmRpZGF0ZW4gYW4sIG5pY2h0IGRhcyBTZW50aW1lbnQuCgpgYGB7ciBUd2l0dGVyLUFrdGl2aXTDpHQgw7xiZXIgZGllIFplaXQgcGxvdHRlbn0KZ2dwbG90KGtvcnB1cy5zdGF0cy5tb25hdCwgYWVzKGRhdGUucHJpbnQsIFRva2VucywgZ3JvdXAgPSBLYW5kaWRhdCwgY29sID0gS2FuZGlkYXQpKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjIgbW9udGhzIiwgZGF0ZV9sYWJlbHMgPSAiJWIgJVkiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgeGxhYigiTW9uYXQiKSArIAogIGdndGl0bGUoIlRydW1wIHZzLiBDbGludG9uOiBUb2tlbnMgcHJvIE1vbmF0ICgyMDE1LTIwMTcpIikKYGBgCgpFcyBsw6Rzc3Qgc2ljaCBiZW9iYWNodGVuLCBkYXNzIEhpbGxhcnkgQ2xpbnRvbiBpbiBkZW4gc2VjaHMgTW9uYXRlbiB2b3IgZGVyIFdhaGwgaW0gTm92ZW1iZXIgMjAxNiBkZXV0bGljaCBha3RpdmVyIHd1cmRlLCB3w6RocmVuZCBEb25hbGQgVHJ1bXAgaW0gZGlyZWt0ZW4gVmVyZ2xlaWNoIHp1IHNlaW5lciBiZXJlaXRzIHNlaXQgMjAwOSBhbmhhbHRlbmRlbiBQcsOkc2VueiBiZWkgVHdpdHRlciBlaGVyIGV0d2FzIHdlbmlnZXIgQmVpdHLDpGdlIHZlcmZhc3N0ZS4gCgpXaXIgZXJzdGVsbGVuIG51biB6dW7DpGNoc3QgZWluZSBERk0gZsO8ciBqZWRlbiBkZXIgS2FuZGlkYXRlbiB1bmQgd2VuZGVuIGVybmV1dCBkYXMgQmluZyBMaXUgU2VudGltZW50LUxleGlrb24gYW4uIFdpciBiZWdpbm5lbiBtaXQgRG9uYWxkIFRydW1wIHVuZCBmaWx0ZXJuIHp1bsOkY2hzdCBkYXMgS29ycHVzIG5hY2ggc2VpbmVuIFR3ZWV0cywgZGllIHdpciBhbnNjaGxpZcOfZW5kIG1pdCBIaWxmZSBkZXMgQXJndW1lbnRzICpncm91cHMqIG5hY2ggTW9uYXQgdW5kIEphaHIgYWdncmVnaWVyZW4gKHNvbnN0IGVyaMOkbHQgbWFuIGRhcyBTZW50aW1lbnQtRXJnZWJuaXMgZsO8ciAqamVkZW4gZWluemVsbmVuIFR3ZWV0Kiwgd2FzIGJlaSBydW5kIDIwLjAwMCBUd2VldHMgbmljaHQgdW5iZWRpbmd0IGludGVycHJldGllcmJhciBpc3QpLiBBbnNjaGxpZcOfZW5kIGVyc3RlbGxlbiB3aXIgbWl0dGVscyBbY29udmVydF0oaHR0cHM6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS9jb252ZXJ0Lmh0bWwpIGVpbmVuIGRhdGEgZnJhbWUsIHdlbGNoZW4gd2lyIG5vY2ggZXR3YXMgYmVhcmJlaXRlbiwgdW0gaWhuIGJlc3NlciBwbG90dGVuIHp1IGvDtm5uZW4uIAoKYGBge3IgU2VudGltZW50LVNjb3JlcyBmw7xyIERvbmFsZCBUcnVtcCBwbG90dGVufQprb3JwdXMudHJ1bXAgPC0gY29ycHVzX3N1YnNldChrb3JwdXMsIEthbmRpZGF0ID09ICJUcnVtcCIpCm1laW5lLmRmbS50cnVtcCA8LSBkZm0oa29ycHVzLnRydW1wLCBncm91cHMgPSBjKCJtb25hdCIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbikKc2VudGltZW50LnRydW1wIDwtIGNvbnZlcnQobWVpbmUuZGZtLnRydW1wLCAiZGF0YS5mcmFtZSIpICU+JQogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiV8O2cnRlciIpICU+JSAKICBtdXRhdGUoRGF0dW0gPSBhcy5EYXRlKHBhc3RlKCIwMSIsIGRvY19pZCwgc2VwID0gIi4iKSwgIiVkLiVtLiVZIikpICU+JSAKICBmaWx0ZXIoRGF0dW0gPj0gIjIwMTUtMDQtMDEiICYgRGF0dW0gPD0gIjIwMTctMDQtMDEiKQpnZ3Bsb3Qoc2VudGltZW50LnRydW1wLCBhZXMoRGF0dW0sIFfDtnJ0ZXIsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGdyb3VwID0gUG9sYXJpdMOkdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiBtb250aHMiLCBkYXRlX2xhYmVscyA9ICIlYiAlWSIpICsgCiAgZ2d0aXRsZSgiU2VudGltZW50LVNjb3JlcyBmw7xyIERvbmFsZCBUcnVtcCIpICsgeGxhYigiTW9uYXQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKRGVuIGV4YWt0IGdsZWljaCBQcm96ZXNzIHdpZWRlcmhvbGVuIHdpciBudW4gZsO8ciBIaWxsYXJ5IENsaW50b24uIFdpciBmaWx0ZXJuIGlocmUgVHdlZXRzIGF1cyBkZW0gR2VzYW10a29ycHVzIGhlcmF1cywgR3J1cHBpZXJlbiBlaW5lIERGTSBuYWNoIE1vbmF0IHVuZCBKYWhyIHVudGVyIEFud2VuZHVuZyBkZXMgTGV4aWtvbnMsIHVuZCBwbG90dGVuIGRhbm4gZGFzIEVyZ2VibmlzLiAKCmBgYHtyIFNlbnRpbWVudC1TY29yZXMgZsO8ciBIaWxsYXJ5IENsaW50b24gcGxvdHRlbn0Ka29ycHVzLmNsaW50b24gPC0gY29ycHVzX3N1YnNldChrb3JwdXMsIEthbmRpZGF0ID09ICJDbGludG9uIikKbWVpbmUuZGZtLmNsaW50b24gPC0gZGZtKGtvcnB1cy5jbGludG9uLCBncm91cHMgPSBjKCJtb25hdCIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbikKc2VudGltZW50LmNsaW50b24gPC0gY29udmVydChtZWluZS5kZm0uY2xpbnRvbiwgImRhdGEuZnJhbWUiKSAlPiUKICBnYXRoZXIocG9zaXRpdmUsIG5lZ2F0aXZlLCBrZXkgPSAiUG9sYXJpdMOkdCIsIHZhbHVlID0gIlfDtnJ0ZXIiKSAlPiUgCiAgbXV0YXRlKERhdHVtID0gYXMuRGF0ZShwYXN0ZSgiMDEiLCBkb2NfaWQsIHNlcCA9ICIuIiksICIlZC4lbS4lWSIpKSAlPiUgCiAgZmlsdGVyKERhdHVtID49ICIyMDE1LTA0LTAxIiAmIERhdHVtIDw9ICIyMDE3LTA0LTAxIikKZ2dwbG90KHNlbnRpbWVudC5jbGludG9uLCBhZXMoRGF0dW0sIFfDtnJ0ZXIsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGdyb3VwID0gUG9sYXJpdMOkdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiBtb250aHMiLCBkYXRlX2xhYmVscyA9ICIlYiAlWSIpICsgCiAgZ2d0aXRsZSgiU2VudGltZW50LVNjb3JlcyBmw7xyIEhpbGxhcnkgQ2xpbnRvbiIpICsgeGxhYigiTW9uYXQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKSW0gZGlyZWt0ZW4gVmVyZ2xlaWNoIHR3aXR0ZXJuIGJlaWRlIEthbmRpZGF0ZW4gZWhlciBwb3NpdGl2IGFscyBuZWdhdGl2LCB3YXMgdmllbGxlaWNodCDDvGJlcnJhc2NodC4gQWxsZXJkaW5ncyBpc3QgZGVyIEFic3RhbmQgendpc2NoZW4gcG9zaXRpdmVtIHVuZCBuZWdhdGl2ZW0gU2VudGltZW50IGJlaSBIaWxsYXJ5IENsaW50b24gaW5zZ2VzYW10IGhvY2gsIHVuZCBpbiBkZXIgaGVpw59lbiBQaGFzZSBkZXMgV2FobGthbXBmZXMgc29nYXIgYmVzb25kZXJzIGF1c2dlcHLDpGd0LiBCZWkgRG9uYWxkIFRydW1wIGhpbmdlZ2VuIMO8YmVyd2llZ3QgaW0gSnVsaSAyMDE2IGRhcyBuZWdhdGl2ZSBTZW50aW1lbnQgdW5kIGltIEZlYnJ1YXIgMjAxNyBsaWVnZW4gcG9zaXRpdmUgdW5kIG5lZ2F0aXZlIEJlZ3JpZmZlIHVuZ2Vmw6RociBnbGVpY2ggYXVmLiBCZWkgVHJ1bXAgZmFsbGVuIHN0YXJrZSBTY2h3YW5rdW5nZW4gYXVmLCBkaWUgYmVpIENsaW50b24gZmVobGVuLgoKV2llIHNpZWh0IGRpZSBUd2l0dGVy4oCTQWt0aXZpdMOkdCBiZWlkZXIgS2FuZGlkYXRlbiBpbSBkaXJla3RlbiBWZXJnbGVpY2ggYXVzPyBXaXIgw7xiZXJzcHJpbmdlbiBkZW4gendlaXRlbiBTY2hyaXR0IGF1cyBkZXIgb2JpZ2VuIEFuYWx5c2UgdW5kIGdlaGVuIGdsZWljaCBkYXp1IMO8YmVyLCBudXIgbm9jaCBkZW4gdmVycmVjaG5ldGVuIHJlbGF0aXZlbiBBbnRlaWwgZGVzIHBvc2l0aXZlbiBTZW50aW1lbnRzIGbDvHIgYmVpZGUgUG9sdGlrZXIgenUgcGxvdHRlbi4gCgpgYGB7ciBWZXJyZWNobmV0ZSBTZW50aW1lbnQtU2NvcmVzIGbDvHIgYmVpZGUgS2FuZGlkYXRlbiBwbG90dGVufQpzZW50aW1lbnQudHJ1bXAucHJvcCA8LSBkZm1fd2VpZ2h0KG1laW5lLmRmbS50cnVtcCwgc2NoZW1lID0gInByb3AiKSAlPiUgCiAgY29udmVydCgiZGF0YS5mcmFtZSIpICU+JQogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIG11dGF0ZShEYXR1bSA9IGFzLkRhdGUocGFzdGUoIjAxIiwgZG9jX2lkLCBzZXAgPSAiLiIpLCAiJWQuJW0uJVkiKSkgJT4lIAogIGZpbHRlcihEYXR1bSA+PSAiMjAxNS0wNC0wMSIgJiBEYXR1bSA8PSAiMjAxNi0xMS0wMSIpICU+JSAKICBtdXRhdGUoS2FuZGlkYXQgPSAiVHJ1bXAiKQpzZW50aW1lbnQuY2xpbnRvbi5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLmNsaW50b24sIHNjaGVtZSA9ICJwcm9wIikgJT4lIAogIGNvbnZlcnQoImRhdGEuZnJhbWUiKSAlPiUKICBnYXRoZXIocG9zaXRpdmUsIG5lZ2F0aXZlLCBrZXkgPSAiUG9sYXJpdMOkdCIsIHZhbHVlID0gIlNlbnRpbWVudCIpICU+JSAKICBtdXRhdGUoRGF0dW0gPSBhcy5EYXRlKHBhc3RlKCIwMSIsIGRvY19pZCwgc2VwID0gIi4iKSwgIiVkLiVtLiVZIikpICU+JSAKICBmaWx0ZXIoRGF0dW0gPj0gIjIwMTUtMDQtMDEiICYgRGF0dW0gPD0gIjIwMTYtMTEtMDEiKSAlPiUgCiAgbXV0YXRlKEthbmRpZGF0ID0gIkNsaW50b24iKQpzZW50aW1lbnQudHJ1bXBjbGludG9uIDwtIGJpbmRfcm93cyhzZW50aW1lbnQudHJ1bXAucHJvcCwgc2VudGltZW50LmNsaW50b24ucHJvcCkgJT4lIAogIGZpbHRlcihQb2xhcml0w6R0ID09ICJwb3NpdGl2ZSIpICU+JSAKICBzZWxlY3QoRGF0dW0sIEthbmRpZGF0LCBTZW50aW1lbnQpICU+JSAKICBtdXRhdGUoU2VudGltZW50ID0gcmVzY2FsZShTZW50aW1lbnQsIHRvID0gYygtMSwxKSkpICU+JQogIG11dGF0ZShLYW5kaWRhdCA9IGFzX2ZhY3RvcihLYW5kaWRhdCkpCmdncGxvdChzZW50aW1lbnQudHJ1bXBjbGludG9uLCBhZXMoRGF0dW0sIFNlbnRpbWVudCwgY29sb3VyID0gS2FuZGlkYXQsIGdyb3VwID0gS2FuZGlkYXQpKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJsaWdodGdyYXkiKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViICVZIikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIlZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgZsO8ciBiZWlkZSBLYW5kaWRhdGVuIikgKyAKICB4bGFiKCJNb25hdCIpCmBgYAoKRGVyIFplaXRyYXVtIGlzdCBoaWVyIGV0d2FzIGFuZGVycyBnZXfDpGhsdCBhbHMgenV2b3IgKDEuIEFwcmlsIDIwMTUgYmlzIDMwLiBOb3ZlbWJlciAyMDE2KSwgd2VpbCBkaWUgc2VociBnZXJpbmdlIEFuemFobCB2b24gVHdlZXRzIGR1cmNoIEhpbGxhcnkgQ2xpbnRvbiBuYWNoIGRlciBXYWhsIGRlbiBWZXJnbGVpY2ggZXJzY2h3ZXJ0IHVuZCB6dSBWZXJ6ZXJydW5nZW4gZsO8aHJ0LiBEaWUgTm9ybWFsaXNpZXJ1bmcgemVpZ3QgZGllIEjDtmhlbiB1bmQgVGllZmVuIHJlbGF0aXYgenVtIFNlbnRpbWVudHZlcmxhdWYgaW0gR2VzYW10emVpdHJ1bSwgdW5kIHZlcnJlY2huZXQgZGFiZWkgd2llIGF1Y2ggc2Nob24genV2b3IgZGllIHBvc2l0aXZlbiB1bmQgbmVnYXRpdmVuIEJlZ3JpZmZlLiBJbSBKdWxpIDIwMTYgdHdpdHRlcnQgVHJ1bXAgc3RhcmsgbmVnYXRpdiwgd2FzIGFiZXIgbmljaHQgaGVpw590LCBkYXNzIGRlciBBbnRlaWwgbmVnYXRpdmVyIEJlZ3JpZmZlIGdydW5kc8OkdHpsaWNoIHZpZWwgaMO2aGVyIGxhZywgYWxzIGRlciBwb3NpdGl2ZXIgQmVncmlmZmUuIEVzIGxhc3NlbiBzaWNoIGludGVyZXNzYW50ZSBCZXrDvGdlIHp1bSBLb250ZXh0IGRlciBVUy1XYWhsZW4gaGVyc3RlbGxlbiwgZC5oLiB1bnRlciBhbmRlcmVtIGRpZSBOb21pbmllcnVuZyB2b24gRG9uYWxkIFRydW1wIHVuZCBIaWxsYXJ5IENsaW50b24gYWxzIEthbmRpZGF0ZW4gZGVyIFJlcHVibGlrYW5lciBiencuIERlbW9rcmF0ZW4sIHNvd2llIGRpZSBFbnRow7xsbHVuZyBbZ2VoYWNrdGVyIEVtYWlscyBkZXMgRE5DXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS8yMDE2X0RlbW9jcmF0aWNfTmF0aW9uYWxfQ29tbWl0dGVlX2VtYWlsX2xlYWspLCBkaWUgYXVjaCBzY2hvbiAyMDE2IGRlbiBWb3J3dXJmIGVpbmVzIGdlemllbHRlbiBNYW5pcHVsYXRpb25zdmVyc3VjaHMgZHVyY2ggUnVzc2xhbmQgbGF1dCB3ZXJkZW4gbGllw58uIFphaGxyZWljaGUgVHdlZXRzIHZvbiBUcnVtcCBrcml0aXNpZXJlbiBkaWUgTWVkaWVuIGbDvHIgZGllc2UgaW4gZGVuIEF1Z2VuIGRlcyBLYW5kaWRhdGVuIGZhbHNjaGUgVW50ZXJzdGVsbHVuZy4gRGVyIEdydW5kIGbDvHIgVHJ1bXBzIGRlbm5vY2ggenVtIFRlaWwgaG9oZW4gU2VudGltZW504oCTV2VydGUgbGllZ3QgaW5kZXMgaW4gZGVyIFZlcndlbmR1bmcgdmllbGVyIHBvc2l0aXZlciBBZGpla3RpdmUgdW5kIFN1cGVybGF0aXZlICgnZ3JlYXQnLCAnYmVzdCcpLCBkaWUgaW4gU2VudGltZW504oCTTGV4aWthIG5hdMO8cmxpY2ggdm9ya29tbWVuLgoKRGVyIENvZGUgZsO8ciBkaWVzZSBBbmFseXNlIGzDpHNzdCBzaWNoIHNpY2ggaW5zb2Zlcm4gbm9jaCBldHdhcyB2ZXJlaW5mYWNoZW4sIGFscyBkYXNzIGVpbmUgUmVpaGUgdm9uIEhhbmRncmlmZmVuIG51ciBmw7xyIGRpZSBFcnN0ZWxsdW5nIGRlcyBQbG90cywgbmljaHQgYWJlciBmw7xyIGRpZSBFcnN0ZWxsdW5nIGRlciBnZXdpY2h0ZXRlbiBERk0gdW5kIGRlciBBbndlbmR1bmcgZGVzIExleGlrb25zIG5vdHdlbmRpZyBzaW5kLgoKIyMjIFZlcmdsZWljaCB1bnRlcnNjaGllZGxpY2hlciBTZW50aW1lbnQtTGV4aWthCgpXZWxjaGUgVW50ZXJzY2hpZWRlIGdpYnQgZXMgendpc2NoZW4gdmVyc2NoaWVkZW5lbiBMZXhpa2E/IERhIHVudGVyc2NoaWVkbGljaGUgTGV4aWthIGF1Y2ggdW50ZXJzY2hpZWRsaWNoZSBCZWdyaWZmZSBlbnRoYWx0ZW4sIGlzdCBkaWVzZSBGcmFnZSBkdXJjaGF1cyBiZWRldXRzYW0uIFVtIHNpZSB6dSBiZWFudHdvcnRlbiwgYmVyZWNobmVuIHdpciBhbmhhbmQgZGVyIFR3ZWV0cyB2b24gRG9uYWxkIFRydW1wIGRyZWkgdW50ZXJzY2hpZWRsaWNoZSBERk1zLCBqZXdlaWxzIG1pdCBlaW5lbSBhbmRlcmVuIExleGlrb24uIFdpciBsZXNlbiB6dW7DpGNoc3QgZWlubWFsIGFsbGUgZHJlaSBMZXhpa2EgZWluLiAKCmBgYHtyIFNlbnRpbWVudC1MZXhpa2EgbGFkZW4gdW5kIGFubGVnZW59CnNlbnRpbWVudC5sZXhpa29uLmJpbmdsaXUgPC0gZGljdGlvbmFyeShsaXN0KHBvc2l0aXZlID0gc2NhbigibGV4aWthL2JpbmdsaXUtcG9zaXRpdmUtd29yZHMudHh0Iiwgd2hhdCA9ICJjaGFyIiwgc2VwID0gIlxuIiwgc2tpcCA9IDM1LCBxdWlldCA9IFQpLCBuZWdhdGl2ZSA9IHNjYW4oImxleGlrYS9iaW5nbGl1LW5lZ2F0aXZlLXdvcmRzLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHNraXAgPSAzNSwgcXVpZXQgPSBUKSkpCnNlbnRpbWVudC5sZXhpa29uLm5yYyA8LSBkaWN0aW9uYXJ5KGxpc3QocG9zaXRpdmUgPSBzY2FuKCJsZXhpa2EvbnJjLnBvcy50eHQiLCB3aGF0ID0gImNoYXIiLCBzZXAgPSAiXG4iLCBxdWlldCA9IFQpLCBuZWdhdGl2ZSA9IHNjYW4oImxleGlrYS9ucmMubmVnLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHF1aWV0ID0gVCkpKQphZmlubiA8LSByZWFkLmNzdigibGV4aWthL0FGSU5OLTExMS50eHQiLCBoZWFkZXIgPSBGLCBzZXAgPSAiXHQiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKc2VudGltZW50LmxleGlrb24uYWZpbm4gPC0gZGljdGlvbmFyeShsaXN0KHBvc2l0aXZlID0gYWZpbm4kVjFbYWZpbm4kVjI+MF0sIG5lZ2F0aXZlID0gYWZpbm4kVjFbYWZpbm4kVjI8MF0pKQpgYGAKClfDpGhyZW5kIGRlciBJbXBvcnQgZsO8ciBkYXMgQmluZyBMaXUtTGV4aWtvbiB1bmQgZGFzIE5SQyBFbW90aW9ucyBMZXhpY29uIHNlaHIgZWluZmFjaCBhYmzDpHVmdCwgaGF0IGRhcyBBRklOTi1EaWN0aW9uYXJ5IGVpbiBzcGV6aWVsbGVzIEZvcm1hdCwgYmVpIGRlbSBlaW5lIFphaGwgendpc2NoZW4gLTUgdW5kICs1IGRpZSBQb2xhcml0w6R0IHZvbiBzZWhyIG5lZ2F0aXYgYmlzIHNlaHIgcG9zaXRpdiBiZXNjaHJlaWJ0LiBXaXIgbnV0emVuIGhpZXIgZGllc2VuIGJlc29uZGVyZW4gVm9ydGVpbCBuaWNodCBhdXMsIHNvbmRlcm4gYmVoYW5kZWxuIGFsbGUgQmVncmlmZmUgYWxzICdlaW5mYWNoJyBuZWdhdGl2IG9kZXIgcG9zaXR2ICg8MCBvZGVyID4wKS4KCldpZWRlciB3aXJkIGRhcyBMZXhpa29uIGFuZ2V3ZW5kZXQsIG5hY2ggTW9uYXQgdW5kIEphaHIgZ3J1cHBpZXJ0LCB1bmQgYW5zY2hsaWXDn2VuZCBwcm9wb3J0aW9uYWwgZ2V3aWNodGV0IC0tIGpld2VpbHMgZsO8ciBqZWRlcyBkZXIgZHJlaSBMZXhpa2EuIAoKYGBge3IgU2VudGltZW50LURGTXMgZ2V3aWNodGVufQptZWluZS5kZm0udHJ1bXAuYmluZ2xpdSA8LSBkZm1fd2VpZ2h0KGRmbShrb3JwdXMudHJ1bXAsIGdyb3VwcyA9IGMoIm1vbmF0IiwgImphaHIiKSwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLmJpbmdsaXUpLCBzY2hlbWUgPSAicHJvcCIpCm1laW5lLmRmbS50cnVtcC5ucmMgPC0gZGZtX3dlaWdodChkZm0oa29ycHVzLnRydW1wLCBncm91cHMgPSBjKCJtb25hdCIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbi5ucmMpLCBzY2hlbWUgPSAicHJvcCIpCm1laW5lLmRmbS50cnVtcC5hZmlubiA8LSBkZm1fd2VpZ2h0KGRmbShrb3JwdXMudHJ1bXAsIGdyb3VwcyA9IGMoIm1vbmF0IiwgImphaHIiKSwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLmFmaW5uKSwgc2NoZW1lID0gInByb3AiKQpgYGAKClNjaGxpZcOfbGljaCB3ZXJkZW4gZGllIGRyZWkgREZNcyBpbiBEYXRhIEZyYW1lcyB1bWdld2FuZGVsdCB1bmQgZWluZSBWYXJpYWJsZSBoaW56dWdlZsO8Z3QsIHdlbGNoZSBkYXMgamV3ZWlsaWdlIExleGlrb24gaWRlbnRpZml6aWVydC4KCmBgYHtyIFNlbnRpbWVudC1ERk1zIHp1IERhdGEgRnJhbWVzIGtvbnZlcnRpZXJlbn0Kc2VudGltZW50LnRydW1wLmJpbmdsaXUgPC0gY29udmVydChtZWluZS5kZm0udHJ1bXAuYmluZ2xpdSwgImRhdGEuZnJhbWUiKSAlPiUgbXV0YXRlKExleGlrb24gPSAiQmluZyBMaXUiKQpzZW50aW1lbnQudHJ1bXAubnJjIDwtIGNvbnZlcnQobWVpbmUuZGZtLnRydW1wLm5yYywgImRhdGEuZnJhbWUiKSAlPiUgbXV0YXRlKExleGlrb24gPSAiTlJDIikKc2VudGltZW50LnRydW1wLmFmaW5uIDwtIGNvbnZlcnQobWVpbmUuZGZtLnRydW1wLmFmaW5uLCAiZGF0YS5mcmFtZSIpICU+JSBtdXRhdGUoTGV4aWtvbiA9ICJBRklOTiIpCmBgYAoKWnVsZXR6dCB3aXJkIGVpbiBnZW1laW5zYW1lciBEYXRhIEZyYW1lIHp1c2FtbWVuZ2VzZXR6dCB1bmQgZXR3YXMgdW1nZWZvcm10LiBEYXMgcmVzdWx0aWVyZW5kZW4gUGxvdCB6ZWlndCBkaWUgdmVycmVjaG5ldGVuIFNlbnRpbWVudC1TY29yZXMgdm9uIERvbmFsZCBUcnVtcCBmw7xyIGFsbGUgZHJlaSBMZXhpa2EuIAoKYGBge3IgVmVycmVjaG5ldGUgU2VudGltZW50LVNjb3JlcyBmw7xyIGRyZWkgTGV4aWthIHBsb3R0ZW59CnNlbnRpbWVudC50cnVtcC5rb21iaSA8LSBiaW5kX3Jvd3Moc2VudGltZW50LnRydW1wLmJpbmdsaXUsIHNlbnRpbWVudC50cnVtcC5ucmMsIHNlbnRpbWVudC50cnVtcC5hZmlubikgJT4lIAogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIGZpbHRlcihQb2xhcml0w6R0ID09ICJwb3NpdGl2ZSIpICU+JSAKICBtdXRhdGUoRGF0dW0gPSBhcy5EYXRlKHBhc3RlKCIwMSIsIGRvY19pZCwgc2VwID0gIi4iKSwgIiVkLiVtLiVZIikpICU+JSAKICBmaWx0ZXIoRGF0dW0gPj0gIjIwMTUtMDQtMDEiICYgRGF0dW0gPD0gIjIwMTctMDMtMDEiKSAlPiUgCiAgc2VsZWN0KERhdHVtLCBMZXhpa29uLCBTZW50aW1lbnQpICU+JSAKICBtdXRhdGUoU2VudGltZW50ID0gcmVzY2FsZShTZW50aW1lbnQsIHRvID0gYygtMSwxKSkpCmdncGxvdChzZW50aW1lbnQudHJ1bXAua29tYmksIGFlcyhEYXR1bSwgU2VudGltZW50LCBjb2xvdXIgPSBMZXhpa29uLCBncm91cCA9IExleGlrb24pKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJsaWdodGdyYXkiKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsgCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjIgbW9udGhzIiwgZGF0ZV9sYWJlbHMgPSAiJWIgJVkiKSArIAogIGdndGl0bGUoIlZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgZsO8ciBEb25hbGQgVHJ1bXAgbWl0IGRyZWkgTGV4aWthIikgKyAKICB4bGFiKCJNb25hdCIpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgpXaWUgd2lyIHNlaGVuLCBzdGltbXQgZGllIFRlbmRlbnogZGVyIGRyZWkgTGV4aWthIHp3YXIga2xhciDDvGJlcmVpbiwgamVkb2NoIGVyZ2ViZW4gc2ljaCBkdXJjaGF1cyBtYXJrYW50ZW4gVW50ZXJzY2hpZWRlLiBTbyBzaW5kIEFGSU5OIHVuZCBCaW5nIExpdSBnZWdlbsO8YmVyIE5SQyBldHdhcyBwb3NpdGl2ZXIuIFp1bSBUZWlsIHVudGVyc2NoZWlkZXQgc2ljaCBhdWNoIGRpZSBJbnRlbnNpdMOkdCBkZXIgQXVzc2NobMOkZ2UgaW4gYmVpZGUgUG9sYXJpdMOkdHNyaWNodHVuZ2VuLiBFaW4gR3J1bmQgZsO8ciBkaWUgVmFyaWF0aW9uIGlzdCBkaWUgTMOkbmdlIGRlciBXb3J0bGlzdGVuLCBkYSB1bWZhbmdyZWljaGVyZSBMaXN0ZW4gZWluZSBiZXNzZXIgQWJkZWNrdW5nIGRlciB0YXRzw6RjaGxpY2ggdmVyd2VuZGV0ZW4gQmVncmlmZmUgZXJyZWljaGVuLiBHcnVuZHPDpHR6bGljaCB1bnRlcnNjaGVpZGVuIHNpY2ggZGllIGRyZWkgTGV4aWthIGFiZXIgbmljaHQgc2lnbmlmaWthbnQgdW5kIHN0aW1tZW4gYnNwdy4gaW4gaWhyZXIgTWVzc3VuZyBkZXIgU2VudGltZW504oCTU2Nod2Fua3VuZyB6d2lzY2hlbiBTZXB0ZW1iZXIgdW5kIE5vdmVtYmVyIDIwMTYga2xhciDDvGJlcmVpbi4KCiMjIyBTZW50aW1lbnQgaW4gendlaSBTdWJyZWRkaXRzIG1pdCBkZW0gTGV4aWNvZGVyIFNlbnRpbWVudCBEaWN0aW9uYXJ5CgpOdW4gd2VuZGVuIHdpciB1bnMgZWluZW0gQmVpc3BpZWwgYXVzIGVpbmVyIGFuZGVyZW4gU29jaWFsIE1lZGlhIFBsYXR0Zm9ybSAtLSBkZW0gRGlza3Vzc2lvbnNmb3J1bSBSZWRkaXQgLS0genUuIEVzIGhhbmRlbHQgc2ljaCB1bSBCZWl0csOkZ2UgYXVzIHp3ZWkgdW50ZXJzY2hpZWRsaWNoZW4gQmVyZWljaGVuIGRlciBQbGF0dGZvcm0gKHNvZy4gU3VicmVkZGl0cykuIFdpciBsYWRlbiB6dW7DpGNoc3QgZGVuIERhdGVuc2F0eiB3ZWxjaGVyIHdpZSBhdWNoIGRpZSBUd2l0dGVyLURhdGVuIHNjaG9uIGFscyBRdWFudGVkYS1Lb3JwdXMgdm9ybGllZ3QuIERpZSBNZXRhZGF0ZW4gw6RobmVsbiB0ZWlsd2Vpc2UgZGVuZW4gdm9uIFR3aXR0ZXIgKCJwb3N0X2RhdGUiKSwgd2Vpc2VuIGFiZXIgYXVjaCBTcGV6aWZpa2EgYXVmIChkaWUgVmFyaWFiZWwgInN0cnVjdHVyZSIgbGllZmVydCBJbmZvcm1hdGlvbmVuIHp1ciBTY2hhY2h0ZWx1bmcgZGVyIERpc2t1c3Npb24pLiAgCgpgYGB7ciBSZWRkaXQtS29ycHVzIGxhZGVufQpsb2FkKCJkYXRlbi9yZWRkaXQvcmVkZGl0LlJEYXRhIikKYXMuZGF0YS5mcmFtZShyZWRkaXQuc3RhdHMpCmBgYAoKRGFzIEtvcnB1cyBlbnRow6RsdCBldHdhIDIwLjAwMCBLb21tZW50YXJlLCBkaWUgaW4gendlaSB1bnRlcnNjaGllZGxpY2hlbiBTdWJyZWRkaXRzIHZlcsO2ZmZlbnRsaWNodCB3dXJkZW4sIGRlbiBGb3JlbiAnc2NpZW5jZScgdW5kICdzeXJpYW5jaXZpbHdhcicuIFdpciBiZXJlY2huZW4gU2VudGltZW50LVNjb3JlcyBmw7xyIGRpZXNlIE5hY2hyaWNodGVuIG1pdCBIaWxmZSBkZXMgTGV4aWNvZGVyIFNlbnRpbWVudCBEaWN0aW9uYXJ5IChMU0QyMDE1KS4gSW0gR2VnZW5zYXR6IHp1bSBWb3JnZWhlbiBpbiBkZW4gdm9yYXVzZ2VoZW5kZW4gQmVpc3BpZWxlbiB2ZXJ3ZW5kZW4gd2lyIGRpZSBsb2dhcml0aG1pc2NoZSBEdXJjaHNjaG5pdHRzZ2V3aWNodHVuZyB1bmQga8O8cnplbiBkYXMgRXJnZWJuaXMsIHVtIHNvIGVpbmVuIFBvbGFyaXTDpHRzd2VydCBwcm8gS29tbWVudGFyIHVuZCBuaWNodCBwcm8gV29ydCB6dSBiZXN0aW1tZW4gKGJlYWNodGVuIFNpZSwgZGFzcyBlcyBoaWVyZsO8ciB1bnRlcnNjaGllZGxpY2hlIFN0cmF0ZWdpZW4gZ2lidCkuCgpgYGB7ciBERk0gYmVyZWNoZW4gdW5kIFBvbGFyaXTDpHQgYW5oYW5kIGRlcyBMU0QtTGV4aWtvbnMgYmVzdGltbWVufQpyZWRkaXQuZGZtIDwtIGRmbShyZWRkaXQuY29ycHVzLCBkaWN0aW9uYXJ5ID0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpICU+JSAKICBkZm1fcmVtb3ZlKGMoIm5lZ19wb3NpdGl2ZSIsICJuZWdfbmVnYXRpdmUiKSkKcmVkZGl0LnNlbnRpbWVudCA8LSBkZm1fd2VpZ2h0KHJlZGRpdC5kZm0sIHNjaGVtZSA9ICJsb2dhdmUiKSAlPiUgCiAgY29udmVydCgiZGF0YS5mcmFtZSIpICU+JQogIG11dGF0ZShwb3NpdGl2ZSA9IHRydW5jKHBvc2l0aXZlKSwgbmVnYXRpdmUgPSB0cnVuYyhuZWdhdGl2ZSkpICU+JSAKICBtdXRhdGUobmV1dHJhbCA9IHBvc2l0aXZlID09IG5lZ2F0aXZlKSAlPiUgCiAgbGVmdF9qb2luKHJlZGRpdC5zdGF0cywgYnkgPSBjKCJkb2NfaWQiID0gIlRleHQiKSkKc2VudGltZW50IDwtICIiCnNlbnRpbWVudFtyZWRkaXQuc2VudGltZW50JHBvc2l0aXZlPT0xXSA8LSAicG9zaXRpdmUiCnNlbnRpbWVudFtyZWRkaXQuc2VudGltZW50JG5lZ2F0aXZlPT0xXSA8LSAibmVnYXRpdmUiCnNlbnRpbWVudFtyZWRkaXQuc2VudGltZW50JG5ldXRyYWw9PVRdIDwtICJuZXV0cmFsIgpyZWRkaXQuc2VudGltZW50LnNoYXJlIDwtIHJlZGRpdC5zZW50aW1lbnQgJT4lIAogIHNlbGVjdChkb2NfaWQsIHN0cnVjdHVyZSwgY29tbV9kYXRlLCBzdWJyZWRkaXQsIHVzZXIsIGNvbW1lbnRfc2NvcmUpICU+JSAKICBkYXRhLmZyYW1lKFNlbnRpbWVudCA9IHNlbnRpbWVudCkKcmVkZGl0LnNlbnRpbWVudC5zaGFyZQpgYGAKCk51biBwbG90dGVuIHdpciBkZW4gcmVsYXRpdmVuIEFudGVpbCBkZXIgUG9sYXJpdMOkdCBqZSBLb21tZW50YXIgaW5uZXJoYWxiIGRlciB6d2VpIFN1YnJlZGRpdHMuCgpgYGB7ciBTZW50aW1lbnQtQW50ZWlsZSBpbSBSZWRkaXQtS29ycHVzIGJlcmVjaG5lbiB1bmQgZGFzIEVyZ2VibmlzIHBsb3R0ZW59CnJlZGRpdC5zZW50aW1lbnQuc2hhcmUgPC0gZGF0YS5mcmFtZShwcm9wLnRhYmxlKHRhYmxlKHJlZGRpdC5zZW50aW1lbnQuc2hhcmUkU2VudGltZW50LCByZWRkaXQuc2VudGltZW50LnNoYXJlJHN1YnJlZGRpdCksIDIpKQpjb2xuYW1lcyhyZWRkaXQuc2VudGltZW50LnNoYXJlKSA8LSBjKCJTZW50aW1lbnQiLCAiU3VicmVkZGl0IiwgIlNoYXJlIikKZ2dwbG90KHJlZGRpdC5zZW50aW1lbnQuc2hhcmUsIGFlcyhTdWJyZWRkaXQsIFNoYXJlLCBjb2xvdXIgPSBTZW50aW1lbnQsIGZpbGwgPSBTZW50aW1lbnQpKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpICsKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwxIikgKyAKICBnZ3RpdGxlKCJTZW50aW1lbnQtVmVydGVpbHVuZyBpbiB6d2VpIFN1YnJlZGRpdHMiKSArIAogIHhsYWIoIiIpICsgeWxhYigiU2VudGltZW50LUFudGVpbCAoJSkiKQpgYGAKCkRpZSBuYWNoc3RlaGVuZGVuIEJlaXNwaWVsZSAoWnVmYWxsc3NhbXBsZSkgemVpZ2VuIEtvbW1lbnRhcmUgdW5kIGRlcmVuIGpld2VpbGlnZSB2b3JoZXJnZXNhZ3RlIFBvbGFyaXTDpHQgYXVmIEdydW5kbGFnZSBkZXMgTGV4aWtvbnMuIEVzIGxhc3NlbiBzaWNoIHphaGxyZWljaGUgQmVpc3BpZWxlIGbDvHIgZmVobGVyaGFmdCBrbGFzc2lmaXppZXJ0ZSBLb21tZW50YXJlIGVudGRlY2tlbiwgZXR3YSBpcm9uaXNjaGUgb2RlciB1bnZlcnN0w6RuZGxpY2hlIEJlaXRyw6RnZSwgb2RlciBzb2xjaGUsIGRpZSBzaWNoIHNjaGxpY2h0IGVpbmVyIGtsYXJlbiBFaW5vcmRudW5nIGluIGRhcyBSYXN0ZXIgcG9zaXRpdi9uZWdhdGl2IGVudHppZWhlbi4gQWxsZXJkaW5ncyBzY2hlaW50IGRpZSBUZW5kZW56IGluc29mZXJuIGtvcnJla3QsIGFscyBkYXMgZXR3YSBLb21tZW50YXJlIG1pdCBLcmFmdGF1c2Ryw7xja2VuIGFscyBuZWdhdGl2IHVuZCBzb2xjaGUgbWl0IEdsw7xja3fDvG5zY2hlbiBhbHMgcG9zaXRpdiBrbGFzc2lmaXppZXJ0IHdlcmRlbiAoYXVjaCBoaWVyIGdpYnQgZXMgZmFsc2NoIHBvc2l0aXZlIFRyZWZmZXIpLiAgCgpgYGB7ciBCZWlzcGllbGUgdm9uIFJlZGRpdC1Lb21tZW50YXJlbiB1bnRlcnNjaGllZGxpY2hlciBQb2xhcml0w6R0fQpkYXRhLmZyYW1lKHJlZGRpdC5zZW50aW1lbnQsIHNlbnRpbWVudCwgY29tbWVudCA9IHRleHRzKHJlZGRpdC5jb3JwdXMpKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKSAlPiUgCiAgc2FtcGxlX24oMTApICU+JSAKICBzZWxlY3QoY29tbWVudCwgc2VudGltZW50LCBzdWJyZWRkaXQsIHRpdGxlKQpkYXRhLmZyYW1lKHJlZGRpdC5zZW50aW1lbnQsIHNlbnRpbWVudCwgY29tbWVudCA9IHRleHRzKHJlZGRpdC5jb3JwdXMpKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAibmVnYXRpdmUiKSAlPiUgCiAgc2FtcGxlX24oMTApICU+JSAKICBzZWxlY3QoY29tbWVudCwgc2VudGltZW50LCBzdWJyZWRkaXQsIHRpdGxlKQpkYXRhLmZyYW1lKHJlZGRpdC5zZW50aW1lbnQsIHNlbnRpbWVudCwgY29tbWVudCA9IHRleHRzKHJlZGRpdC5jb3JwdXMpKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAibmV1dHJhbCIpICU+JSAKICBzYW1wbGVfbigxMCkgJT4lIAogIHNlbGVjdChjb21tZW50LCBzZW50aW1lbnQsIHN1YnJlZGRpdCwgdGl0bGUpCmBgYAoKCiMjIyBTZW50aW1lbnQgaW4gU2Nod2VpemVyIFRhZ2VzemVpdHVuZ2VuIGluIGRlciBCZXJpY2h0ZXJzdGF0dHVuZyB6dXIgRmluYW56a3Jpc2UKCldpciB3ZWNoc2VsbiBudW4gZGllIFBlcnNwZWt0aXZlIHVuZCB1bnRlcnN1Y2hlbiBkZXV0c2Noc3ByYWNoaWdlIFRleHRlLiBEYXp1IGxlc2VuIHdpciBkYXMgU2Nod2VpemVyIEtvcnB1cyB6dXIgRmluYW56a3Jpc2UgZWluLiBEaWVzZXMgZW10aMOkbHQgcnVuZCAyMSwwMDAgQXJ0aWtlbCwgZGllIHp3aXNjaGVuIDIwMDcgdW5kIDIwMTIgaW4gZWluZXIgdm9uIGbDvG5mIFNjaHdlaXplciBUYWdlc3plaWd1bmdlbiB2ZXLDtmZmZW50bGljaCB3dXJkZW4gdW5kIGRlbiBCZWdyaWZmICdGaW5hbnprcmlzZScgaW4gVGV4dCBvZGVyIFRpdGVsIGVudGhhbHRlbi4gTmViZW4gU3ByYWNoZSB1bmQgVGV4dHNvcnRlIGdpYnQgZWluZW4gd2VpdGVyZW4gVW50ZXJzY2hpZWQgenUgZGVuIFR3aXR0ZXItRGF0ZW46IERpZSBBZ2dyZWdhdGlvbnNlYmVuZSBpc3QgaGllciBuaWNodCBtZWhyIGVpbiBNb25hdCwgd2llIHp1dm9yLCBzb25kZXJuIHNjaGxpY2h0IGVpbiBBcnRpa2VsLiBEYXMgbGV1Y2h0ZXQgZWluLCBhbGxlcmRpbmdzIGlzdCBkaWVzZXIgQW5zYXR6IGhpZXIgYXVjaCBkZXNoYWxiIGVyZ2llYmlnLCB3ZWlsIFplaXR1bmdzYXJ0aWtlbCBudW4gZWlubWFsIGRldXRsaWNoIGzDpG5nZXIgc2luZCBhbHMgVHdlZXRzLgoKWnVzw6R0emxpY2ggenVtIEZpbmFuemtyaXNlLUtvcnB1cyBsYWRlbiB3aXIgYXVjaCBnbGVpY2ggZGFzIGRldXRzY2hzcHJhY2hpZ2UgU2VudGlXUy1MZXhpa29uLiBJbSBHZWdlbnNhdHogenUgZGVtIEJpbmcgTGl1LUxleGlrb24gaGFuZGVsdCBlcyBzaWNoIGhpZXIgdW0gZWluZSBSRGF0YS1EYXRlaSwgbmljaHQgdW0gZWluZSBUZXh0ZGF0ZWksIHdhcyBhYmVyIHByYWt0aXNjaCBrZWluZXJsZWkgVW50ZXJzY2hpZWQgbWFjaHQuCgpgYGB7ciBTZW50aVdTLUxleGlrb24gdW5kIEZpbmFuemtyaXNlLUtvcnB1cyBsYWRlbn0KbG9hZCgibGV4aWthL3NlbnRpV1MuUkRhdGEiKQpsb2FkKCJkYXRlbi9jb3NtYXMvZmluYW56a3Jpc2UvZmluYW56a3Jpc2Uua29ycHVzLlJEYXRhIikKc2VudGltZW50LmxleGlrb24uc2VudGl3cyA8LSBkaWN0aW9uYXJ5KGxpc3QocG9zaXRpdmUgPSBwb3NpdGl2ZS53b2VydGVyLnNlbnRpLCBuZWdhdGl2ZSA9IG5lZ2F0aXZlLndvZXJ0ZXIuc2VudGkpKQpoZWFkKGtvcnB1cy5maW5hbnprcmlzZS5zdGF0cywgMTAwKQpgYGAKCgojIyMjIFNlbnRpbWVudC1BbnRlaWxlIG5hY2ggUXVlbGxlIChoaWVyOiBaZWl0dW5nKQoKSW0gbsOkY2hzdGUgU2Nocml0dCBiZXJlY2huZW4gd2lyIGVpbmUgREZNLCBkaWUgbnVuIG5pY2h0IG5hY2ggTW9uYXQgdW5kIEphaHIgZ3J1cHBpZXJ0IGlzdCwgc29uZGVybiBuYWNoIGRlbSBGZWxkICpxdWVsbGUqLCBkLmguIGRlciBqZXdlaWxpZ2VuIFplaXR1bmcuIERpZXNlciBTY2hyaXR0IGRhdWVydCBkZXNoYWxiIGV0d2FzIGzDpG5nZXIgYWxzIHp1dm9yLCB3ZWlsIHdpciBlcyBtaXQgZWluZW0gS29ycHVzIHZvbiAyMSwwMDAgRG9rdW1lbnRlbiB1bmQgcnVuZCA0IE1pby4gVG9rZW5zIHp1IHR1biBoYWJlbiAtLSBkZXV0bGljaCBncsO2w59lciwgYWxzIGRhcyBTaGVybG9jayBIb2xtZXMtS29ycHVzICgxMjZUIFfDtnJ0ZXIpIHVuZCBUd2l0dGVyIEtvcnB1cyAoNDU5VCBXw7ZydGVyKS4gSGVyYXVzIGtvbW10IGVpbmUgVGFiZWxsZSwgZGllIHNpY2ggc29nYXIgdm9sbHN0w6RuZGlnIGF1c2dlYmVuIGzDpHNzdCwgb2huZSBkZW4gW2hlYWRdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy91dGlscy92ZXJzaW9ucy8zLjUuMS90b3BpY3MvaGVhZCnigJNCZWZlaGwgenUgdmVyd2VuZGVuLiAKCmBgYHtyIERGTSBmw7xyIGRhcyBGaW5hbnprcmlzZS1Lb3JwdXMgYmVyZWNobmVuIHVuZCBnZXdpY2h0ZW59Cm1laW5lLmRmbS5maW5hbnprcmlzZSA8LSBkZm0oa29ycHVzLmZpbmFuemtyaXNlLCBncm91cHMgPSAicXVlbGxlIiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnNlbnRpd3MpCm1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLmZpbmFuemtyaXNlLCBzY2hlbWUgPSAicHJvcCIpCm1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wCmBgYAoKQXVjaCBkaWVzZSBzZWhyIGVpbmZhY2hlIFRhYmVsbGUga8O2bm5lbiB3aXIgcGxvdHRlbiwgYXVjaCB3ZW5uIGRhcyBpbiBkaWVzZW0ga29ua3JldGVuIEZhbGwgdmllbGxlaWNodCBuaWNodCB1bmJlZGluZ3Qgbm90d2VuZGlnIGlzdC4gCgpgYGB7ciBUcmVlbWFwIHp1IFNlbnRpbWVudC1BbnRlaWxlbiBuYWNoIE1lZGl1bSBpbSBGaW5hbnprcmlzZS1Lb3JwdXN9CnNlbnRpbWVudC5maW5hbnprcmlzZSA8LSBjb252ZXJ0KG1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wLCAiZGF0YS5mcmFtZSIpICU+JSAKICBnYXRoZXIocG9zaXRpdmUsIG5lZ2F0aXZlLCBrZXkgPSAiUG9sYXJpdMOkdCIsIHZhbHVlID0gIlNlbnRpbWVudCIpCmdncGxvdChzZW50aW1lbnQuZmluYW56a3Jpc2UsIGFlcyhkb2NfaWQsIFNlbnRpbWVudCwgY29sb3VyID0gUG9sYXJpdMOkdCwgZmlsbCA9IFBvbGFyaXTDpHQpKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwxIikgKyAKICBnZ3RpdGxlKCJTZW50aW1lbnQtU2NvcmVzIGluIEJlaXRyw6RnZW4genVyIEZpbmFuemtyaXNlIGF1cyBmw7xuZiBTY2h3ZWl6ZXIgVGFnZXN6ZWl0dW5nZW4iKSArIAogIHhsYWIoIiIpICsgeWxhYigiU2VudGltZW50LUFudGVpbCAoJSkiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKV2llIHdpciBzZWhlbiBpc3QgZGllIEJlcmljaHRlcnN0YXR0dW5nIHp1ciBGaW5hbnprcmlzZSDDvGJlcndpZWdlbmRlIG5lZ2F0aXYsIHdhcyBhbmdlc2ljaHRzIGRlcyBUaGVtYXMgbmF0w7xybGljaCBrYXVtIMO8YmVycmFzY2h0LiBFcyBsYXNzZW4gc2ljaCBhYmVyIGF1Y2gga2VpbmVybGVpIG5lbm5lbnN3ZXJ0ZSBVbnRlcnNjaGllZGUgendpc2NoZW4gZGVuIFplaXR1bmdlbiBlcmtlbm5lbiwgc29uZGVybiBkaWUgw5xiZXJlaW5zdGltbXVuZyBpc3QgYXVzZ2VzcHJvY2hlbiBrbGFyLgoKIyMjIFZlcnJlY2huZXRlIFNlbnRpbWVudC1BbnRlaWxlIG5hY2ggUXVlbGxlIHVuZCBKYWhyCgpXaWUgc3RlbGx0IHNpY2ggZGFzIFNlbnRpbWVudCBpbiBkZW4gZsO8bmYgWmVpdHVuZ2VuIGltIFZlcmdsZWljaCDDvGJlciBkaWUgWmVpdCBkYXI/IFdpciBiZXJlY2huZW4gbm9jaCBlaW5tYWwgZWluZSBERk0gdW50ZXIgVmVyd2VuZHVuZyBkZXMgU2VudGlXU+KAk0xleGlrb25zLCBncnVwcGllcmVuIGFiZXIgZGllc2VzIE1hbCBzb3dvaGwgbmFjaCBRdWVsbGUgYWxzIGF1Y2ggbmFjaCBKYWhyLiAKCmBgYHtyIERGTSBtaXQgU2VudGltZW50LUVudHdpY2tsdW5nIG5hY2ggTWVkaXVtIHVuZCBaZWl0IGJlcmVjaG5lbn0KbWVpbmUuZGZtLmZpbmFuemtyaXNlIDwtIGRmbShrb3JwdXMuZmluYW56a3Jpc2UsIGdyb3VwcyA9IGMoInF1ZWxsZSIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbi5zZW50aXdzKQptZWluZS5kZm0uZmluYW56a3Jpc2UucHJvcCA8LSBkZm1fd2VpZ2h0KG1laW5lLmRmbS5maW5hbnprcmlzZSwgc2NoZW1lID0gInByb3AiKQpoZWFkKG1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wKQpgYGAKCkRpZSByZXN1bHRpZXJlbmRlIERGTSBmb3JtZW4gd2lyIGV0d2FzIHVtIHVuZCBwbG90dGVuIGRhbm4gd2llZGVyIGVpbmUgR3JhZmlrLCB3ZWxjaGUgZWluZXJzZWl0cyBkaWUgemVpdGxpY2hlIEVudHdpY2tsdW5nIGRhcnN0ZWxsdCwgdW5kIGFuZGVyZXJzZWl0cywgd2llIGF1Y2ggc2Nob24gaW0gdm9yaGVyaWdlbiBCZWlzcGllbCwgZGllIFVudGVyc2NoaWVkZSBzdGFyayBoZXJ2b3JoZWJ0IC0tIGVpbmVyc2VpdHMgendpc2NoZW4gZGVuIGbDvG5mIFplaXR1bmdlbiB1bmQgYW5kZXJlcnNlaXRzIGlubmVyaGFsYiBkZXMgZsO8bmZqw6RocmlnZW4gTWVzc3plaXRyYXVtcy4gU28ga2FubiBtYW4gZXJrZW5uZW4sIGRhcyAnRGVyIEJ1bmQnIGR1cmNod2VnIGV0d2FzIG5lZ2F0aXZlciBpc3QsIGFscyBlcyBkaWUgYW5kZXJlbiBaZWl0dW5nZW4gc2luZCwgZGFzcyBkaWUgQmVybmVyIFplaXR1bmcgaW4gMjAxMCBwb3NpdGl2ZXIgaXN0IGFscyBkaWUgYW5kZXJlIHZpZXIgQmzDpHR0ZXIsIHVuZCBkYXNzIGRpZSBOWlogdGVuZGVuemllbGwgw7xiZXIgZGllIFplaXQgZXR3YXMgcG9zaXRpdmVyIHdpcmQsIGFiZXIgaW0gVmVyZ2xlaWNoIGRvY2ggbmVnYXRpdiBibGVpYnQuIERpZSBCZXJuZXIgWmVpdHVuZyBzY2h3YW5rdCBpbSBWZXJnbGVpY2ggcmVsYXRpdiBzdGFyay4gCgpgYGB7ciBTZW50aW1lbnQtRW50d2lja2x1bmcgbmFjaCBNZWRpdW0gdW5kIFplaXQgcGxvdHRlbn0Kc2VudGltZW50LmZpbmFuemtyaXNlLnplaXQgPC0gY29udmVydChtZWluZS5kZm0uZmluYW56a3Jpc2UucHJvcCwgImRhdGEuZnJhbWUiKSAKZmluYW56a3Jpc2UucXVlbGxlbiA8LSBkYXRhLmZyYW1lKHN0cl9zcGxpdChzZW50aW1lbnQuZmluYW56a3Jpc2UuemVpdCRkb2NfaWQsICJcXC4iLCBzaW1wbGlmeSA9IFQpKQpjb2xuYW1lcyhmaW5hbnprcmlzZS5xdWVsbGVuKSA8LSBjKCJaZWl0dW5nIiwgIkphaHIiKQpzZW50aW1lbnQuZmluYW56a3Jpc2UuemVpdCA8LSBiaW5kX2NvbHMoc2VudGltZW50LmZpbmFuemtyaXNlLnplaXQsIGZpbmFuemtyaXNlLnF1ZWxsZW4pICU+JQogIHJlbmFtZShTZW50aW1lbnQgPSBwb3NpdGl2ZSkgJT4lCiAgbXV0YXRlKFNlbnRpbWVudCA9IHJlc2NhbGUoU2VudGltZW50LCB0byA9IGMoLTEsMSkpKSAlPiUKICBzZWxlY3QoU2VudGltZW50LCBaZWl0dW5nLCBKYWhyKQpnZ3Bsb3Qoc2VudGltZW50LmZpbmFuemtyaXNlLnplaXQgLCBhZXMoSmFociwgU2VudGltZW50KSkgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWVzKGZpbGwgPSBaZWl0dW5nKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQWNjZW50IikgKyAKICBnZ3RpdGxlKCJWZXJyZWNobmV0ZSBTZW50aW1lbnQtU2NvcmVzIGluIEJlaXRyw6RnZW4genVyIEZpbmFuemtyaXNlIikgCmBgYAoKCiMjIyBTZW50aW1lbnQgaW4gRGViYXR0ZW4gZGVzIERldXRzY2hlbiBCdW5kZXN0YWdzIG5hY2ggUGFydGVpLCBTaXR6dW5nIHVuZCBTcHJlY2hlcgoKV2lyIHNjaGxpZcOfZW4gbWl0IGVpbmVtIHdlaXRlcmUgZGV1dHNjaHNwcmFjaGlnZW4gQmVpc3BpZWwsIGRlbSBEZWJhdHRlbmtvcnB1cyBkZXMgRGV1dHNjaGVuIEJ1bmRlc3RhZ3MgaW4gZGVyIDE4LiBMZWdpc2xhdHVycGVyaW9kZS4gV2llZGVyIGlzdCBkYXMgS29ycHVzIGRhenUgYmVyZWl0cyBhbHMgUkRhdGEtRGF0ZWkgYWJnZWxlZ3QgdW5kIHdpciDDvGJlcnNwcmluZ2VuIGFuZCBkaWVzZXIgU3RlbGxlIGRlbiBIaW50ZXJncnVuZCBkYXp1LCB3aWUgZ2VuYXUgZGFzIEtvcnB1cyB6dXNhbW1lbmdlc3RlbGx0IHd1cmRlLiBEZXIgVW1mYW5nIGlzdCBtaXQgMjA2LjAwMCBXb3J0bWVsZHVuZ2VuIGluIDI0MyBTaXR6dW5nZW4gdW5kIHJ1bmQgMTUgTWlvLiBUb2tlbnMgbm9jaCBlaW5tYWwgZXJoZWJsaWNoIGdyw7bDn2VyLCBhbHMgZGVyIHZvcmF1c2dlZ2FuZ2VuZXIgS29ycG9yYS4gCgpgYGB7ciBCdW5kZXN0YWdzLUtvcnB1cyB1bmQgU2VudGltZW50LUxleGlrb24gbGFkZW59CmxvYWQoImRhdGVuL2J1bmRlc3RhZy9idW5kZXN0YWcua29ycHVzLlJEYXRhIikKbG9hZCgibGV4aWthL1JhdWhfU2VudERpY3Rpb25hcnlHZXJtYW4uUkRhdGEiKQpzZW50aW1lbnQubGV4aWtvbi5yYXVoIDwtIGRpY3Rpb25hcnkobGlzdChwb3NpdGl2ZSA9IHN0cl90cmltKHNlbnQuZGljdGlvbmFyeSRmZWF0dXJlW3NlbnQuZGljdGlvbmFyeSRzZW50aW1lbnQ+MF0pLCBuZWdhdGl2ZSA9IHN0cl90cmltKHNlbnQuZGljdGlvbmFyeSRmZWF0dXJlW3NlbnQuZGljdGlvbmFyeSRzZW50aW1lbnQ8MF0pKSkKYGBgCgpOYWNoZGVtIEtvcnB1cyB1bmQgTGV4aWtvbi1Sb2hkYXRlbiBnZWxhZGVuIHd1cmRlbiwgZXJzdGVsbGVuIHdpciBhdXMgZGVyIHVtZmFuZ3JlaWNoZW4gU2VudGltZW50LVdvcnRsaXN0ZSB2b24gQ2hyaXN0aWFuIFJhdWggZWluIHF1YW50ZWRhLUxleGlrb24gKGRhYmVpIG11c3Mgw6RobmxpY2ggd2llIGJlaW0gQUZJTk4tRGlrdGlvbmFyeSBub2NoIGV0d2FzIHVtZ2Vmb3JtdCB3ZXJkZW4pLiBBbnNjaGxpZcOfZW5kIGJlcmVjaG5lbiB3aXIgZGFubiBlaW5lIERGTSwgd2VsY2hlIFNlbnRpbWVudC1Xw7ZydGVyIG5hY2ggUGFydGVpZW4gYXVzesOkaGx0LiAgCgpgYGB7ciBERk0gbmFjaCBQYXJ0ZWkgZsO8ciBkYXMgQnVuZGVzdGFncy1Lb3JwdXMgYmVyZWNobmVufQptZWluZS5kZm0uYnVuZGVzdGFnLnBhcnRlaSA8LSBkZm0oa29ycHVzLmJ1bmRlc3RhZywgZ3JvdXBzID0gInBhcnR5IiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnJhdWgpCm1laW5lLmRmbS5idW5kZXN0YWcucGFydGVpCmBgYAoKQXVjaCBoaWVyIHBsb3R0ZW4gd2lyIHdpZWRlciBkYXMgUmVzdWx0YXQuIERpZSBOb3JtYWxpc2llcnVuZywgd2VsY2hlIHdpciBpbSB2b3JhdXNnZWdhbmdlbmVuIEJlaXNwaWVsIGR1cmNoZ2Vmw7xocnQgaGFiZW4sIGxhc3NlbiB3aXIgYW4gZGllc2VyIFN0ZWxsZSB3ZWcsIHVtIGF1Y2ggZWluZW4gRWluZHJ1Y2sgdm9uIGRlbiBSZWRlbmFudGVpbGVuIGRlciBQYXJ0ZWllbiB6dSBlcmhhbHRlbi4gCgpgYGB7ciBERk0gaW4gZWluZW4gRGF0YSBGcmFtZSB1bXdhbmRlbG4gdW5kIGFic29sdXRlIFNlbnRpbWVudC1WZXJ0ZWlsdW5nIHBsb3R0ZW59CnNlbnRpbWVudC5idW5kZXN0YWcucGFydGVpIDwtIGNvbnZlcnQobWVpbmUuZGZtLmJ1bmRlc3RhZy5wYXJ0ZWksICJkYXRhLmZyYW1lIikgJT4lIAogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikKZ2dwbG90KHNlbnRpbWVudC5idW5kZXN0YWcucGFydGVpLCBhZXMoZG9jX2lkLCBTZW50aW1lbnQsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGZpbGwgPSBQb2xhcml0w6R0KSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgc2NhbGVfY29sb3VyX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIGdndGl0bGUoIlNlbnRpbWVudC1TY29yZXMgaW0gRGV1dHNjaGVuIEJ1bmRlc3RhZyBuYWNoIFBhcnRlaSIpICsgCiAgeGxhYigiIikgKyB5bGFiKCJXw7ZydGVyIikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKCkFuIGRpZXNlbSBFcmdlYm5pcyDDvGJlcnJhc2NodCB2aWVsbGVpY2h0IGV0d2FzLCBkYXNzIGRhcyBTZW50aW1lbnQgZGVyIFJlZ2llcnVuZ3NwYXJ0ZWllbiBuaWNodCBtZXJrbGljaCBwb3NpdGl2ZXIgYXVzZsOkbGx0LCBhbHMgZGFzIGRlciBPcHBvc2l0aW9uLiBad2FyIGhhdCBMaW5rZSBhbnRlaWxpZyBkYXMgbmVnYXRpdnN0ZSBTZW50aW1lbnQgdW5kIGRpZSBDU1UgZGFzIHBvc2l0aXZzdGUsIGFiZXIgZGllIFVudGVyc2NoaWVkZSBzaW5kIG5pY2h0IHNlaHIgZ3Jvw58uIAoKTnVuIHdpZWRlcmhvbGVuIHdpciBkaWUgRXJzdGVsbHVuZyBkZXIgREZNLCBncnVwcGllcmVuIGFiZXIgZGllc21hbCBuaWNodCBuYWNoIFBhcnRlaSwgc29uZGVybiBuYWNoIFNpdHp1bmcuIERhcyBaaWVsIGRpZXNlcyBBbnNhdHplcyBiZXN0ZWh0IGRhcmluLCBTaXR6dW5nZW4genUgaWRlbnRpZml6aWVyZW4sIGluIGRlbmVuIGJlc29uZGVycyBuZWdhdGl2ZSBUaGVtZW4gKHdhcyBoaWVyIG1laHJlcmUgQmVkZXV0dW5nZW4gaGFiZW4ga2FubikgenUgZmluZGVuLiAKCmBgYHtyIERGTSBuYWNoIFNpdHp1bmcgZ3J1cHBpZXJlbiB1bmQgYmVyZWNobmVufQptZWluZS5kZm0uYnVuZGVzdGFnLnNpdHp1bmcgPC0gZGZtKGtvcnB1cy5idW5kZXN0YWcsIGdyb3VwcyA9ICJzaXR6dW5nIiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnJhdWgpCm1laW5lLmRmbS5idW5kZXN0YWcuc2l0enVuZy5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLmJ1bmRlc3RhZy5zaXR6dW5nLCBzY2hlbWUgPSAicHJvcCIpCmhlYWQobWVpbmUuZGZtLmJ1bmRlc3RhZy5zaXR6dW5nLnByb3ApCmBgYAoKTmFjaGRlbSB3aXIgZGFzIGFudGVpbGlnZSBTZW50aW1lbnQgZsO8ciBhbGxlIDI0MyBTaXR6dW5nZW4gZGVyIExlZ2lzbGF0dXJwZXJpb2RlIDIwMTMtMjAxNyBiZXN0aW1tdCBoYWJlbiwgc2thbGllcmVuIHdpciBkaWUgRGF0ZW4gd2llZGVyIHVuZCBwbG90dGVuIGRhcyBSZXN1bHRhdCBpbiBlaW5lciAoc2VociBkaWNodGVuKSBaZWl0cmVpaGUuIEbDvHIgZGFzIFBsb3QgZmlsdGVybiB3aXIgZGFiZWkgZGllIERhdGVuIHNvLCBkYXNzIG51ciBkYXMgSmFociAyMDE1IChTaXR6dW5nZW4gMS02MCkgZGFyZ2VzdGVsbHQgd2lyZC4KCmBgYHtyIFZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgbmFjaCBTaXR6dW5nc3ZlcmxhdWYgcGxvdHRlbn0Kc2VudGltZW50LmJ1bmRlc3RhZy5zaXR6dW5nIDwtIGNvbnZlcnQobWVpbmUuZGZtLmJ1bmRlc3RhZy5zaXR6dW5nLnByb3AsICJkYXRhLmZyYW1lIikgJT4lIAogIHJlbmFtZShTaXR6dW5nID0gZG9jX2lkLCBTZW50aW1lbnQgPSBwb3NpdGl2ZSkgJT4lCiAgc2VsZWN0KFNpdHp1bmcsIFNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKFNlbnRpbWVudCA9IHJlc2NhbGUoU2VudGltZW50LCB0byA9IGMoLTEsMSkpKSAlPiUgCiAgbXV0YXRlKFNpdHp1bmcgPSBhc19mYWN0b3IoU2l0enVuZykpICU+JSAKICBzbGljZSgxOjYwKQpnZ3Bsb3Qoc2VudGltZW50LmJ1bmRlc3RhZy5zaXR6dW5nLCBhZXMoU2l0enVuZywgU2VudGltZW50LCBncm91cCA9IDEpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAibGlnaHRncmF5IikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41LCBzaXplID0gOCkpICsgCiAgZ2d0aXRsZSgiVmVycmVjaG5ldGUgU2VudGltZW50LVNjb3JlcyBpbiBTaXR6dW5nZW4gZGVyIERldXRzY2hlbiBCdW5kZXN0YWdzIikKYGBgCgrDhGhubGljaCB3aWUgYmVpIFRydW1wIHVuZCBDbGludG9uIGzDpHNzdCBzaWNoIGVpbiBrbGFyZXIgQmV6dWcgendpc2NoZW4gU2VudGltZW504oCTU2NvcmVzIHVuZCBoaXN0b3Jpc2NoZW4gRXJlaWduaXNzZW4gaGVyc3RlbGxlbiwgd2VubiBtYW4gZGVuIEJsaWNrIGF1ZiBzb2xjaGUgU2l0enVuZ2VuIG1pdCBiZXNvbmRlcnMgaG9oZW4vbmllZHJpZ2VuIFNlbnRpbWVudC1TY29yZXMgbGVua3QuIEZvbGdlbmQgd8OkaGxlbiB3aXIgc29sY2hlIFNpdHp1bmdlbiBhdXMgdW5kIHJlaWNoZXJuIGRpZSBJbmZvcm1hdGlvbmVuIHp1ciBTaXR6dW5nIG1pdCBkZW4gZW50aGFsdGVuZW4gVGFnZXNvcmRudW5nc3B1bmt0ZW4gKFRPUHMpIGFuLiBEYXMgU2VudGltZW50IGlzdCBhbGxlcmRpbmdzIGluIGRlciBuYWNoc3RlaGVuZGVuIFRhYmVsbGUgZsO8ciBkaWUgZ2VzYW10ZSBTaXR6dW5nLCBuaWNodCBudXIgZsO8ciBkZW4gamV3ZWlsaWdlbiBUT1Aga2Fsa3VsaWVydC4gCgpgYGB7ciBQb2xhcml0w6R0IGRlciBTaXR6dW5nZW4gbWl0IGRlbiBUYWdlc29yZG51bmdzcHVua3RlbiBpbiBCZXppZWh1bmcgc2V0emVufQpsb2FkKCJkYXRlbi9idW5kZXN0YWcvdGFnZXNvcmRudW5nc3B1bmt0ZS5SRGF0YSIpCnRhZ2Vzb3JkbnVuZ3NwdW5rdGUgPC0gc2VsZWN0KHRhZ2Vzb3JkbnVuZ3NwdW5rdGUsIHNpdHp1bmcsIGhlbGRfb24sIG5hbWUsIGNhdGVnb3J5KQpidW5kZXN0YWcucG9zc2l0enVuZyA8LSBoZWFkKGFycmFuZ2Uoc2VudGltZW50LmJ1bmRlc3RhZy5zaXR6dW5nLCBkZXNjKHNlbnRpbWVudC5idW5kZXN0YWcuc2l0enVuZyRTZW50aW1lbnQpKSwgMykKYnVuZGVzdGFnLnBvc3NpdHp1bmckU2l0enVuZyA8LSBhcy5udW1lcmljKGJ1bmRlc3RhZy5wb3NzaXR6dW5nJFNpdHp1bmcpCmJ1bmRlc3RhZy5uZWdzaXR6dW5nIDwtIGhlYWQoYXJyYW5nZShzZW50aW1lbnQuYnVuZGVzdGFnLnNpdHp1bmcsIHNlbnRpbWVudC5idW5kZXN0YWcuc2l0enVuZyRTZW50aW1lbnQpLCAzKQpidW5kZXN0YWcubmVnc2l0enVuZyRTaXR6dW5nIDwtIGFzLm51bWVyaWMoYnVuZGVzdGFnLm5lZ3NpdHp1bmckU2l0enVuZykKIyBTaXR6dW5nZW4gbWl0IHBvc2l0aXZlbSBTZW50aW1lbnQKbGVmdF9qb2luKGJ1bmRlc3RhZy5wb3NzaXR6dW5nLCB0YWdlc29yZG51bmdzcHVua3RlLCBieSA9IGMoIlNpdHp1bmciID0gInNpdHp1bmciKSkKIyBTaXR6dW5nZW4gbWl0IG5lZ2F0aXZlbSBTZW50aW1lbnQKbGVmdF9qb2luKGJ1bmRlc3RhZy5uZWdzaXR6dW5nLCB0YWdlc29yZG51bmdzcHVua3RlLCBieSA9IGMoIlNpdHp1bmciID0gInNpdHp1bmciKSkKYGBgCgpCZWltIHBvc2l0aXZlbiBTZW50aW1lbnQgc3RpY2h0IGRpZSBTaXR6dW5nIHZvbSAxNy4gRGV6ZW1iZXIgMjAxMyBoZXJhdXMsIGluIGRlciBBbmdlbGEgTWVya2VsIHZlcmVpZGlndCB3dXJkZSAtLSBkZW0gQW5sYXNzIHVuZCBkZW4gbmFoZW5kZW4gV2VpaG5hY2h0c2ZlcmllbiBlbnRzcHJlY2hlbmQgaXN0IGRpZSBTdGltbXVuZyBndXQuIE5lZ2F0aXYgZmFsbGVuIFNpdHp1bmdlbiB6dW0ga29udHJvdmVyc2VuIGF1c3NlbnBvbGl0aXNjaGVuIFRoZW1lbiBlYmVuc28gd2llIHp1bSBDRVRBLUFia29tbWVuIG9kZXIgenVyIEJla8OkbXBmdW5nIGRlciBLaW5kZXJhcm11dCBhdWYuIERpZSBiZWlkZW4gVE9Qcywgd2VsY2hlIGRpZSBMaXN0ZSBhbmbDvGhyZW4gKCdSZWdpZXJ1bmdzZXJrbMOkcnVuZyBIdW1hbml0w6RyZSBIaWxmZSBpbSBJcmFrJyBzb3dpZSAnU3RhYmlsaXTDpHRzaGlsZmUgenVndW5zdGVuIEdyaWVjaGVubGFuZHMnKSBsaWVmZXJuIGJlaSBhdWdlbnNjaGVpbmxpY2hlciBCZXJ0cmFjaHR1bmcgZWluIGd1dGVzIE1hw58gZGVyIGJlc29uZGVycyBrb250cm92ZXJzZW4gVGhlbWVuIHp1csO8Y2ssIGJlaSBkZW5lbiBkaWUgUmVnaWVydW5nIG1pdCBLcml0aWsgZGVyIE9wcG9zaXRpb24gb2RlciBhdXMgZGVuIGVpZ25lbmVuIFJlaWhlbiB1bWdlaGVuIG11c3N0ZS4KCkhpZXJiZWkgYmVyw7xja3NpY2h0aWd0IHVuc2VyZSBIZXJhbmdlaGVuc3dlaXNlIGluZGVzIG5pY2h0LCBvYiBlaW4gVGhlbWEgbmVnYXRpdmUgw4R1w59lcnVuZ2VuIG5hY2ggc2ljaCB6aWVodCwgd2VpbCBlcyBzaWNoIHVtIGVpbmVuIG5lZ2F0aXZlbiBTYWNodmVyaGFsdCAoZXR3YSBlaW5lIE5hdHVya2F0YXN0cm9waGUpIGhhbmRlbHQsIGRlciBhYmVyIG5pY2h0IGF1dG9tYXRpc2NoIGtvbnRyb3ZlcnMgc2VpbiBtdXNzLCBvZGVyIG9iIEtyaXRpayBhbiBkZXIgUmVnaWVydW5nIGdlw7xidCB3aXJkLgoKRXR3YXMgenUgaW5kaXZpZHVlbGxlbiBVbnRlcnNjaGllZGVuIGVyZmFocmVuIHdpciwgd2VubiB3aXIgZGllIFNwcmVjaGVyIG1pdCBiZXNvbmRlcnMgcG9zaXRpdmVtIHVuZCBuZWdhdGl2ZW0gU2VudGltZW50IG5hY2ggUGFydGVpenVnZWjDtnJpZ2tlaXQgYmV0cmFjaHRlbi4gV2llZGVyIGJlcmVjaG5lbiB3aXIgaGllcnp1IGVpbmUgZ3J1cHBpZXJ0ZSBERk0uIAoKYGBge3IgREZNIG5hY2ggU3ByZWNoZXIgZ3J1cHBpZXJlbiB1bmQgYmVyZWNobmVufQptZWluZS5kZm0uYnVuZGVzdGFnLnNwcmVjaGVyIDwtIGRmbShrb3JwdXMuYnVuZGVzdGFnLCBncm91cHMgPSAic3BlYWtlcl9jbGVhbmVkIiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnJhdWgpCm1laW5lLmRmbS5idW5kZXN0YWcuc3ByZWNoZXIucHJvcCA8LSBkZm1fd2VpZ2h0KG1laW5lLmRmbS5idW5kZXN0YWcuc3ByZWNoZXIsIHNjaGVtZSA9ICJwcm9wIikKaGVhZChtZWluZS5kZm0uYnVuZGVzdGFnLnNwcmVjaGVyLnByb3ApCmBgYAoKTnVuIHppZWhlbiB3aXIgbm9jaCBkaWUgUGFydGVpenVnZWjDtnJpZ2tlaXQgYXVzIGRlbiBNZXRhZGF0ZW4gaGluenUsIHNrYWxpZXJlbiBkYXMgU2VudGltZW50LUVyZ2VibmlzLCB1bmQgc29ydGllcmVuIHNjaGxpZcOfZWxpY2ggZGllIDYzNyBBYmdlb3JkbmV0ZW4gZWlubWFsIGFic3RlaWdlbmQgdW5kIGVpbm1hbCBhdWZzdGVpZ2VuZCAocy51LikuIAoKYGBge3IgUG9sYXJpdMOkdCBqZSBTcHJlY2huZXIgYmVzdGltbWVuIHVuZCBzb3J0aWVyZW59CnNlbnRpbWVudC5idW5kZXN0YWcuc3ByZWNoZXIgPC0gY29udmVydChtZWluZS5kZm0uYnVuZGVzdGFnLnNwcmVjaGVyLCAiZGF0YS5mcmFtZSIpICU+JSAKICBsZWZ0X2pvaW4odW5pcXVlKHNlbGVjdChrb3JwdXMuYnVuZGVzdGFnLnN0YXRzLCBzcGVha2VyX2NsZWFuZWQsIHBhcnR5KSksIGJ5ID0gYygiZG9jX2lkIiA9ICJzcGVha2VyX2NsZWFuZWQiKSkgJT4lIAogIHJlbmFtZShTcHJlY2hlciA9IGRvY19pZCwgUGFydGVpID0gcGFydHkpICU+JSAKICBmaWx0ZXIocG9zaXRpdmUgIT0gMCwgbmVnYXRpdmUgIT0gMCkgJT4lIAogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIGZpbHRlcihQb2xhcml0w6R0ID09ICJwb3NpdGl2ZSIpICU+JSAKICBtdXRhdGUoU2VudGltZW50ID0gcmVzY2FsZShTZW50aW1lbnQsIHRvID0gYygtMSwxKSkpICU+JSAKICBzZWxlY3QoU2VudGltZW50LCBTcHJlY2hlciwgUGFydGVpKQojIFNwcmVjaGVyIG1pdCBwb3NpdGl2ZW0gU2VudGltZW50CmFycmFuZ2Uoc2VudGltZW50LmJ1bmRlc3RhZy5zcHJlY2hlciwgZGVzYyhTZW50aW1lbnQpKQojIFNwcmVjaGVyIG1pdCBuZWdhdGl2ZW0gU2VudGltZW50CmFycmFuZ2Uoc2VudGltZW50LmJ1bmRlc3RhZy5zcHJlY2hlciwgU2VudGltZW50KQpgYGAKCkRlciBkYW1hbGlnZSBCdW5kZXN0YWdzcHLDpHNpZGVudCBOb3JiZXJ0IExhbW1lcnQgZsO8aHJ0IGRpZSBQb3NpdGl24oCTTGlzdGUgYW4sIGdlZm9sZ3Qgdm9uIGRlbiBWaXplcHLDpHNpZGVudGlubmVuIENsYXVkaWEgUm90aCwgVWxsYSBTY2htaWR0LCBFZGVsZ2FyZCBCdWxtYWhuIHVuZCBQZXRyYSBQYXUsIGRhendpc2NoZW4gZGllIEJ1bmRlc2thbnpsZXJpbiBBbmdlbGEgTWVya2VsIHVuZCBkZXIgZGFtYWxpZ2UgVml6ZWthbnpsZXIgU2lnbWFyIEdhYnJpZWwuIERpZSBob2hlbiBXZXJ0ZSBmw7xyIGRpZSBWZXJ0cmV0ZXIgZGVyIHdpY2h0aWdzdGVuIHBhcmxhbWVudGFyaXNjaGVuIMOEbXRlciBzaW5kIHdlbmlnIMO8YmVycmFzY2hlbmQuIEJlaSBkZW4gTmVnYXRpdndlcnRlbiBmw6RsbHQgYXVmLCBkYXNzIGRpZSBSZWdpZXJ1bmdzcGFydGVpZW4gQ0RVL0NTVS9TUEQgw7xiZXJyYXNjaGVuZCBzdGFyayB2ZXJ0cmV0ZW5kIHNpbmQsIGF1Y2ggd2VubiBkaWUgTGlzdGUgdm9uIE9wcG9zaXRpb25zcG9saXRpa2VyaW5uZW4gZGVyIEdyw7xuZW4gYnp3LiBMaW5rZW4gYW5nZWbDvGhydCB3aXJkLiAKCkVpbmUgd2lya2xpY2ggYWTDpHF1YXRlIEFuYWx5c2UgbcO8c3N0ZSB1bnRlciBhbmRlcmVtIGhpZXIgbm9jaCBTcHJlY2hlciBtaXQgc2VociBnZXJpbmdlbiBSZWRlYW50ZWlsZW4gYXVzc2NobGllw59lbiwgZGEgZGVyZW4gU2VudGltZW50LVNjb3JlIGR1cmNoIGVpbmlnZSB3ZW5pZ2UgQmVncmlmZmUgaW4gZGllIGVpbmUgb2RlciBhbmRlcmUgUmljaHR1bmcgYXVzc2NobGFnZW4ga2FubiwgYXVjaCB3ZW5uIGRpZXMgaW50dWl0aXYga2VpZXJsZWkgU2lubiBlcmdpYnQuIFVuZCB3aWUgZWluZ2FuZ3MgYmVzY2hyaWViZW4gbXVzcyBtYW4gZGVuIEtvbnRleHQgZWluZXIgw4R1w59lcnVuZyB1bmQgZGllIFp1Z2Vow7ZyaWdrZWl0IHp1ciBSZWdpZXJ1bmcgYnp3LiBPcHBvc2l0aW9uIGhlcmFuemllaGVuLCB3YXMgZXR3YSBkdXJjaCBkaWUgVW50ZXJzdWNodW5nIGRlciBUT1BzIGdydW5kc8OkdHpsaWNoIG3DtmdsaWNoIHdpcmQuIAoKQWJzY2hsaWXDn2VuZCBzb2xsdGUgZmVzdGdlaGFsdGVuIHdlcmRlbiwgZGFzcyBTZW50aW1lbnRhbmFseXNlIG1pdHRlbHMgUXVhbnRlZGEgw7xiZXIgYXVzIEF1c3rDpGhsZW4gdm9uIEJlZ3JpZmZlbiBhYmzDpHVmdCwgYWxsZXJkaW5ncyBhdWNoIGxlaWNodGUgVmFyaWF0aW9uZW4gZGllc2VzIEFuc2F0emVzIGV4aXN0aWVyZW4uIHNvIHdpbGwgbWFuIGV0d2EgZsO8ciBUd2VldHMgYWJlciBhdWNoIGbDvHIgZWluemVsbmVuIFPDpHR6ZW4gaW4gZWluZW0gRGViYXR0ZW5rb3JwdXMgZWluIFNlbnRpbWVudCBmZXN0bGVnZW4sIHdlbGNoZXMgbmljaHQgYWxsZWluIGF1cyBkZXIgQWRkaXRpb24gdm9uIHBvc2l0aXZlbi9uZWdhdGl2ZW4gV8O2cnRlcm4gZXJnaWJ0LiBEYXJhdWYgd2lyZCBzcMOkdGVyIG5vY2ggZWluZ2VnYW5nZW4sIGFsbGVyZGluZ3MgZXJyZWljaGVuIGF1Y2ggZGllIGJlcmVpdHMgYmVzY2hyaWViZW5lbiBWZXJmYWhyZW4gaW5zZ2VzYW10IHJlY2h0IGd1dGUgRXJnZWJuaXNzZSwgc29sYW5nZSBkaWUgS29ycHVzZ3LDtsOfZSB1bmQgZGllIEdlbmF1aWdrZWl0IGRlcyBMZXhpa29ucyBhdXNyZWljaGVuZCBzaW5kLiAKCgo=