1 Aufgaben

In dieser Übung wollen wir uns mit kontinuierlichen Daten beschäftigen. Hierzu betrachten wir den Datensatz airquality, welcher verschiedene Messungen bezüglich Luftqualität zwischen Mai und September im Jahre 1973 umfasst.
Für mehr Information siehe ?airquality.

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.4.0      ✔ purrr   0.3.5 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.1      ✔ stringr 1.4.1 
✔ readr   2.1.3      ✔ forcats 0.5.2 ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
airquality <- as_tibble(airquality)
# Faktoren aus Monat und Tag machen:
airquality <- airquality %>%
  mutate_at(c("Month", "Day"), factor)
# Alternativ:
# airquality %>%
#   mutate(Month = factor(Month), Day = factor(Day))
airquality

1.1 Übungsaufgaben

  1. Schaffen Sie sich zunächst eine (statistische) Übersicht über die Daten. Wie viele Beobachtungen und Variablen wurden erfasst?
  2. Versichern Sie sich zunächst, dass die Daten einigermaßen kontinuierlich innerhalb des Zeitraums erfasst wurden.
  3. Veranschaulichen Sie die Verteilungen der kontinuierlichen Variablen von airquality. Welche Aspekte fallen ins Auge und könnten interessant sein?
  4. Finden Sie Zusammenhänge zwischen den verschiedenen Variablen.
    1. Visualisieren Sie für alle sinnvollen Variablen die paarweisen Beziehungen. Sind diese alle linear?
    2. Berechnen Sie für alle sinnvollen Variablen eine Korrelationsmatrix. Bietet sich Pearsons Korrelationskoeffizient an?
    3. Bestimmen Sie durch Visualisierung, wo Zusammenhänge vorliegen.
  5. Erstellen Sie für relevante (kausale) Zusammenhänge Regressionsmodelle.

1.2 Lösungen

1.2.1 Schaffen Sie sich zunächst eine (statistische) Übersicht über die Daten. Wie viele Beobachtungen und Variablen wurden erfasst?

Anhang des tibbles sehen wir direkt die Anzahl der Beobachtungen und Variablen. Alternativ auch oben rechts unter Environment oder mit dim()

airquality
dim(airquality)
[1] 153   6
summary(airquality)
     Ozone           Solar.R           Wind             Temp       Month       Day     
 Min.   :  1.00   Min.   :  7.0   Min.   : 1.700   Min.   :56.00   5:31   1      :  5  
 1st Qu.: 18.00   1st Qu.:115.8   1st Qu.: 7.400   1st Qu.:72.00   6:30   2      :  5  
 Median : 31.50   Median :205.0   Median : 9.700   Median :79.00   7:31   3      :  5  
 Mean   : 42.13   Mean   :185.9   Mean   : 9.958   Mean   :77.88   8:31   4      :  5  
 3rd Qu.: 63.25   3rd Qu.:258.8   3rd Qu.:11.500   3rd Qu.:85.00   9:30   5      :  5  
 Max.   :168.00   Max.   :334.0   Max.   :20.700   Max.   :97.00          6      :  5  
 NA's   :37       NA's   :7                                               (Other):123  

1.2.2 Versichern Sie sich zunächst, dass die Daten einigermaßen kontinuierlich innerhalb des Zeitraums erfasst wurden.

Hier gibt es mehrere Möglichkeiten. Naiv könnten wir die Tabelle durchschauen und jeden einzelnen Tag und Monat nachschlagen, ob dieser vorhanden ist. Hier kann aber leicht das Gesamtbild aus den Augen verloren gehen, daher wollen wir das kurz veranschaulichen:

ggplot(airquality) +
  geom_bar(aes(x = Day))

ggplot(airquality) +
  geom_bar(aes(x = Month))

Wenn wir Balkendiagramme für Tag und Monat separat zeichnen, deutet sich an, dass diese scheinbar kontinuierlich erfasst wurden.
Seien Sie hierbei vorsichtig, denn es wäre prinzipiell auch möglich, mehrere Messungen an demselben Tag durchzuführen, um das Ergebnis zu verändern.

Für eine deutlichere Einsicht können wir uns das gemeinsame Balkendiagramm anzeigen:

ggplot(airquality) +
  geom_bar(aes(x = Day, fill = Month))

ggplot(airquality) +
  geom_bar(aes(x = Month, fill = Day)) +
  facet_wrap(~ Day)

Scheinbar liegen die kleinen Unterschiede lediglich an dem 31. Tag im Monat. Die Daten wurden somit kontinuierlich erfasst.

1.2.3 Veranschaulichen Sie die Verteilungen der kontinuierlichen Variablen von airquality. Welche Aspekte fallen ins Auge und könnten interessant sein?

Wir könnten uns hier jede einzelne Variable im Detail anschauen, aber verbleiben beispielhaft bei der Variablen Ozone.

Schauen wir uns zunächst die allgemeine Verteilung anhand von Boxplot und Histogramm an:

ggplot(airquality) +
  geom_boxplot(aes(y = Ozone), fill = "steelblue")

ggplot(airquality, aes(x = Ozone)) +
  geom_histogram(fill = "steelblue", bins = 15) +
  geom_freqpoly(bins = 15)

Viele Werte scheinen sich zwischen ~10 und ~30 aufzuhalten. Das erste Quartil wird durch 18 und das dritte Quartial durch 63 markiert.

Aus der Auskunft (?airquality) sehen wir, dass es sich hierbei um durchschnittliche Teilchen pro Milliarde zwischen 13 und 15 Uhr, gemessen auf Roosevelt Island, handelt. Scheinbar liegen auch einige fehlende Werte und Ausreißer vor, welche wir zunächst betrachten wollen.

Ausreißer können wir erhalten, in dem wir (in diesem Fall) nach hohen Werte von Ozone filtern:

airquality %>%
  filter(Ozone > 125)

Wir sehen, dass am 1.07.1973 und am 25.08.1973 Ausreißer bei den Ozonwerten vorlagen. Um die Ausreißer der Boxplots direkt zu erhalten (ohne selbstständiges Filtern), können wir aus rstatix die Funktion identify_outliers() verwenden:

library(rstatix)

Attache Paket: ‘rstatix’

Das folgende Objekt ist maskiert ‘package:stats’:

    filter
airquality %>%
  identify_outliers(Ozone)

Wir sehen, dass dieselben zwei Werte in der Spalte is.outlier als Ausreißer (TRUE) markiert wurden, jedoch nicht als extreme Ausreißer (FALSE). Ausreißer weichen um einen IQR von mehr als 1.5 von dem 3. Quartil ab, extreme Ausreißer ab dem dreifachen IQR.

Um die fehlenden Einträge zu betrachten, können wir nach is.na() filtern:

airquality %>%
  filter(is.na(Ozone))

Scheinbar wurden an 2/3 der Tage des Junis keine Daten bezüglich der Ozonwerte erhoben. Wohlmöglich gab es Störungen der Messgeräte?

Nun stellt sich die Frage, wieso so viele Messwerte fehlen. Das lässt sich ohne weitere Information des Datenerhebers nicht einfach klären, allerdings können wir nach Mustern suchen:

airquality %>%
  group_by(Month) %>%
  summarise(n = sum(is.na(Ozone)))

Schauen wir, ob es an zusammenhängenden Zeitpunkten Probleme bei der Datenerhebung gab. Hierzu fügen wir zunächst Monat und Tag zu einem Objekt für (Kalender-)Daten zusammen:

airquality <- airquality %>%
  mutate(date = as.Date(paste("1973", Month, Day, sep = "-")))
airquality
ggplot(airquality) + 
  geom_line(aes(x = date, y = Ozone), color = "tomato1")

In den meisten Fällen scheinen die fehlenden Einträge nicht mehrere aufeinanderfolgende Tage zu beschreiben. Im Juni scheinen gänzlich am Anfang und Ende des Monats die Einträge zu fehlen.

Es könnte auch interessant sein, den Verlauf der Ozonwerte an den verschiedenen Monatstagen zu vergleichen oder zwischen den Monat (oder beides zugleich):

geom_histogram(aes(x = Ozone, fill = Day)) +

facet_wrap(~ Day)

ggplot(airquality) +
  geom_boxplot(aes(Day, Ozone, fill = Day))

ggplot(airquality) +
  geom_histogram(aes(x = Ozone, fill = Day)) +
  facet_wrap(~ Day)

ggplot(airquality) +
  geom_boxplot(aes(Month, Ozone, fill = Month))

ggplot(airquality, aes(x = Ozone, fill = Month)) +
  geom_histogram(aes(y = after_stat(density))) +
  geom_density(alpha = 0.2) +
  facet_wrap(~ Month) +
    theme(legend.position = NULL)

Bei der Betrachtung der Monatstage sehen wir tendenziell am Anfang und Ende des Monats mehr Variabilität (wohlmöglich wegen der fehlenden Werte im Juni). In der Mitte des Monats scheinen die Werte weniger auseinanderzugehen, auch wenn es Ausreißer gibt.

Bei der Betrachtung der Monate sehen wir, dass es durchschnittlich vermutlich mehr Ozon im Juli und August gab, wobei hier auch die Varianz höher war. Besonders im September gab es viele Ausreißer. In dem Histogramm vom Juni sehen wir insbesondere die fehlenden Werte. Auch hier werden die Muster der Ozonwerte etwas deutlicher, dass im Mai und September (und ggf. Juni) tendenziell (gehäuft) geringere Werte vorliegen.

Der Vorteil an rstatix liegt auch darin, dass sich verschiedene Funktionen auf Gruppen berechnen lassen. So können wir auch separat für jeden Tag/Monat die Ausreißer von Ozone bestimmen:

Wir hatten auch bereits gelernt, wie wir für Gruppen/Kategorien statistische Zusammenfassungen machen können, um die Sachverhalte auch durch Zahlenwerte betrachten zu können. Hierbei muss auf die NAs geachtet werden:

Meist ist es hilfreich, bei interessanten Aspekten zusätzlich zu einer Grafik auch markante statistische Maßzahlen zu benennen. Beispielsweise sind die Standardabweichungen und Mediane/Mittelwerte von Juli und August hoch (hohe Varianz). Somit kann auch die entsprechende Maßzahl angegeben werden.

Betrachten wir letztlich noch, ob die Variable Ozone normalverteilt ist:

airquality %>%
  shapiro_test(Ozone)
  • Da der p-Wert wesentlich kleiner ist als übliche Signifikanzniveaus ⍺, lehnen wir die Nullhypothese ab, d. h. wir gehen davon aus, dass die Daten nicht normalverteilt sind.
  • Es stellt sich dennoch die Frage, ob die Daten der einzelnen Monate normalverteilt sind:
airquality %>%
  group_by(Month) %>%
  shapiro_test(Ozone)
  • Scheinbar gilt p < ⍺ für Mai und September => keine Normalverteilung
  • Für Juni, Juli und August sind die Daten (vermutlich) normalverteilt, da der p-Wert kleiner als ⍺ ist.
  • Wenn wir die anderen Variablen in die Funktion shapiro_test() eingeben würden, könnten wir direkt alle gemeinsam auf Normalverteilung testen und müssten dies nicht einzeln machen.

Entsprechende Überlegungen könnten auch für die anderen Variablen in Betracht gezogen werden.

1.2.4 Finden Sie Zusammenhänge zwischen den verschiedenen Variablen.

1.2.4.1 Visualisieren Sie für alle sinnvollen Variablen die paarweisen Beziehungen. Sind diese alle linear?

Eine Herangehensweise ist, für jedes Paar an Variablen ein neues Streudiagramm zu zeichnen:

ggplot(airquality, aes(x = Ozone, y = Temp)) +
    geom_point(aes(color = Month)) +
    geom_smooth(color = "red")
  • Hier sehen wir beispielsweise einen scheinbar quadratischen Zusammenhang (Parabel)
  • Das ist jedoch aufwendig, wenn kein spezifisches Interesse an jedem Variablenpaar besteht

Eine Standardfunktion in R, die einen Überblick über einen gesamten Dataframe liefert, ist die plot() Funktion:

plot(airquality)
  • Die Grafik ist jedoch nicht sehr ordentlich
  • Weitere Alternativen sind pairs() oder ggpairs() aus dem Paket GGally:
pairs(airquality, panel = panel.smooth) # panel = panel.smooth zeichnet die Kurven ein
library(GGally)
airquality %>%
    select(-c(Day, date)) %>%
ggpairs(progress = FALSE)
  • Beachten Sie, dass für jede Grafik die NAs entfernt wurden.
  • Diese Grafik ist in vielerlei Hinsicht hilfreich:
    • Zunächst sehen wir auf der Diagonalen die Verteilung der Variablen
    • Oberhalb der Diagonalen sehen wir die Korrelationskoeffizienten für die Variablenpaare oder Boxplots nach Gruppen arrangiert
    • Unterhalb der Diagonalen sehen wir die Streudiagramme für die Variablenpaare oder Histogramme nach der Gruppe arrangiert
    • Die Grafiken sind im Gegensatz zu den obigen Grafiken nicht “doppelt” (x- und y-Achse)
    • Die Grafik lässt sich noch weiter verfeinern
    • Durch die Vielzahl an Grafiken und dem hohen Informationsgehalt ist die Grafik gut geeignet, um sich eine Übersicht zu schaffen
    • Die Grafik mag in dieser Ausführlichkeit jedoch nicht unbedingt die erste Wahl sein, wenn Daten/Ergebnisse vorgestellt werden sollen (es sei denn, es gibt Platzmangel …)

Fokussieren wir uns nur auf die kontinuierlichen Daten und erweitern die Grafik etwas:

# In columns lassen sich ebenfalls die Variablen auswählen:
airquality %>%
    ggpairs(columns = 1:4, aes(color = Month, alpha = 0.01), progress = FALSE)
  • Durch Aesthetics in ggpairs() können wir ebenfalls alle Grafiken gruppieren (hier nach dem Monat)
  • Zusätzlich erhalten wir für jede Gruppe separat den Korrelationskoeffizienten
  • Weitere Information finden Sie hier.

1.2.4.2 Berechnen Sie für alle sinnvollen Variablen eine Korrelationsmatrix. Bietet sich Pearsons Korrelationskoeffizient an?

Wir hatten zuvor die Korrelationskoeffizienten schon gesehen, wollen das an dieser Stelle noch einmal gesamteinheitlich machen:

airquality %>%
    select(-c(Day, Month, date)) %>% # Faktoren/Gruppen nicht beachten
    cor_mat()
  • Temp und Ozone sind stark positiv korreliert
  • Wind und Ozone sowie Temp und Wind sind moderat negativ korreliert

1.2.4.3 Bestimmen Sie durch Visualisierung, wo Zusammenhänge vorliegen.

airquality %>%
    select(-c(Day, Month, date)) %>% # Faktoren/Gruppen nicht beachten
    cor_mat() %>%
    cor_reorder() %>%
    cor_plot(type = "upper", label = TRUE,
                     palette = ggpubr::get_palette(c("darkred", "cornsilk", "darkcyan"), 200)
                     )

Die Zusammenhänge wurden oben bereits erläutert. Zusätzlich haben Wind und Solar.R keinen Zusammenhang und die restlichen Paare nur schwache Zusammenhänge.

1.2.5 Erstellen Sie für relevante (kausale) Zusammenhänge Regressionsmodelle.

Inbesondere Wind und Ozone sowie Temp und Ozone könnten interessante Zusammenhänge sein:

  • Wind kann das das Ozongas wegtransportieren, sodass die Anzahl der Ozonmoleküle in der Luft sinkt

  • Sonneneinstrahlung und hohe Temperaturen begünstigen die Reaktion von Stickstoff und Sauerstoff zu Ozon

ggplot(airquality, aes(Wind, Ozone)) +
    geom_point() +
    geom_smooth()
  • Die Trendkurve zeigt, dass der Ozongehalt in der Luft mit steigendem Wind sinkt
  • Der Zusammenhang könnte quadratisch sein, wohlmöglich aber auch linear

Erstellen wir das zugehörige Modell:

# Linearer Zusammenhang:
lin_modell <- lm(Ozone ~ Wind, data = airquality)
summary(lin_modell)
# Quadratischer Zusammenhang:
quad_modell <- lm(Ozone ~ Wind * I(Wind ** 2), data = airquality)
summary(quad_modell)
  • Da der R-Quadrat-Wert von dem quadratischen Zusammenhang deutlich besser ist, scheint dieser zu bevorzugen

Dasselbe können wir nun auch für Temp und Ozone machen:

ggplot(airquality, aes(Temp, Ozone)) +
    geom_point() +
    geom_smooth()
  • Es liegt ein steigender Trend vor
  • Auch hier ist nicht ganz eindeutig, ob dieser linear ist

Erstellen wir wieder Regressionsmodelle:

# Linearer Zusammenhang:
lin_modell <- lm(Ozone ~ Temp, data = airquality)
summary(lin_modell)
# Quadratischer Zusammenhang:
quad_modell <- lm(Ozone ~ Temp * I(Temp ** 2), data = airquality)
summary(quad_modell)
  • Das Modell mit dem quadratischen Koeffizienten ist nicht deutlich besser als das Modell mit dem linearen Koeffizienten
  • Dennoch sehen wir auch hier, dass sich ein großer Teil der Varianz durch die Temperatur erklären kann
  • Anschaulich bedeutet dies, dass wir einigermaßen gut vorhersagen können, welcher Ozongehalt vorliegt, wenn wir lediglich die Temperatur oder die Windgeschwindigkeit kennen

1.2.5.1 Zusätzliches/Ausschau

  • Angenommen, Sie sagen, es liegen 80 Fahrenheit (~26,6 Grad Celsius) vor, dann könnte ich Ihnen einigermaßen akurat sagen, wie hoch der Ozongehalt ungefähr sein muss (in Roosevelt Island):
predict(quad_modell, list(Temp = 80))

Vergleichen wir diesen Wert (42) mit den tatsächlich gemessenen Werten:

# Werte nahe 80 Fahrenheit:
nahe_80f <- airquality %>%
    filter(between(Temp, 75, 85) & !is.na(Ozone)) %>%
    select(Ozone, Temp)
nahe_80f %>%
    summarise(mittelwert = mean(Ozone))
ggplot(nahe_80f, aes(Temp, Ozone)) +
    geom_jitter() +
    geom_smooth(se = FALSE) +
    geom_hline(yintercept = 42, color = "red")
  • Wir sehen, dass die Punkte im Mittelwert nahe bei unserer Vorhersage 42 liegen
  • Die individuellen Punkte liegen allerdings gestreut um die Linie

Letztendlich können wir auch ein etwas komplexeres Modell erstellen, um den Wert von Ozone zu berechnen:

modell <- lm(Ozone ~ Wind + I(Wind ** 2) + I(Temp ** 2), data = airquality)
summary(modell)
  • Das Modell wird durch jeden weiteren Parameter besser in seiner Vorhersage
    • Das kann jedoch dazu führen, dass die Vorhersage nur noch für den eigenen Datensatz relevant wird (Overfitting)
    • Testen wir einfach alle möglichen Kombinationen und der R-Quadrat-Wert wird sehr hoch sein:
modell <- lm(Ozone ~ Wind * I(Wind ** 2) * Temp * Solar.R * Month, data = airquality)
summary(modell)
  • Manchmal ergibt es Sinn, einfach ein paar Zusammenhänge zu betrachten und Vermutungen anzustellen, welche Zusammenhänge vorliegen
  • Innerhalb des Modells sehen wir an dem t-Test von jedem Koeffiziente, welcher Koeffizient einen signifikanten Einfluss hat

Um ein Modell visuell darzustellen, kann das Paket GGally mit den Funktionen ggnostic() und ggcoef_model() helfen:

Um sich die Koeffizienten schnell anzuschauen (anstelle der Ansicht in der R-Konsole), kann ggcoef_model() verwendet werden. Hierzu wird zunächst broom.helpers benötigt (install.packages("broom.helpers"):

library(broom.helpers)
modell <- lm(Ozone ~ Temp + Wind + Solar.R + Month, data = airquality)
summary(modell)
ggcoef_model(modell)
  • Beta zeigt hierbei den standardisierten Koeffizienten und dessen Unsicherheit an

  • Temp und Wind haben in dem Modell einen signifikanten Einfluss (kleiner p-Wert).

  • Solar.R ist ebenfalls noch signifikant.

  • Month hingegen scheint nur im September einen signifikanten Einfluss auf Ozone zu haben und weist hohe Schwankungen auf.

Um weitere Aspekte des Modells zu betrachten und um seine Güte zu beurteilen, kann danach ggnostic() verwendet werden:

ggnostic(modell, aes(color = Month), progress = FALSE)
  • Die verschiedenen Bereiche auf der y-Achse stellen das Modell auf unterschiedliche Arten dar (Residueen, Diagonale der Hat-Matrix, Schätzung der SD von Residueen, wenn die Beobachtung aus dem Modell genommen wurde, Cooks-Distanz)

  • Der wichtigste Aspekt liegt darin, dass Ausreißer anhand der Trennlinien erkennbar werden

  • Auf der x-Achse sind die Variablen des Modells

Filtern wir die zwei Ausreißer von Ozone, um ggf. das Modell zu verbessern:

# Entfernen von hohen Ozonwerten
airquality_gefiltert <- airquality %>%
    filter(Ozone < 125)
modell <- lm(Ozone ~ Temp + Wind + Month, data = airquality_gefiltert)
summary(modell)
ggnostic(modell, aes(color = Month), progress = FALSE)
  • Das Modell scheint (ein wenig) besser geworden zu sein
LS0tCnRpdGxlOiAiw5xidW5nc3pldHRlbCA4IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKLS0tCgojIEF1ZmdhYmVuCgpJbiBkaWVzZXIgw5xidW5nIHdvbGxlbiB3aXIgdW5zIG1pdCBrb250aW51aWVybGljaGVuIERhdGVuIGJlc2Now6RmdGlnZW4uCkhpZXJ6dSBiZXRyYWNodGVuIHdpciBkZW4gRGF0ZW5zYXR6IGBhaXJxdWFsaXR5YCwgd2VsY2hlciB2ZXJzY2hpZWRlbmUKTWVzc3VuZ2VuIGJlesO8Z2xpY2ggTHVmdHF1YWxpdMOkdCB6d2lzY2hlbiBNYWkgdW5kIFNlcHRlbWJlciBpbSBKYWhyZQoxOTczIHVtZmFzc3QuXApGw7xyIG1laHIgSW5mb3JtYXRpb24gc2llaGUgYD9haXJxdWFsaXR5YC4KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYWlycXVhbGl0eSA8LSBhc190aWJibGUoYWlycXVhbGl0eSkKIyBGYWt0b3JlbiBhdXMgTW9uYXQgdW5kIFRhZyBtYWNoZW46CmFpcnF1YWxpdHkgPC0gYWlycXVhbGl0eSAlPiUKICBtdXRhdGVfYXQoYygiTW9udGgiLCAiRGF5IiksIGZhY3RvcikKIyBBbHRlcm5hdGl2OgojIGFpcnF1YWxpdHkgJT4lCiMgICBtdXRhdGUoTW9udGggPSBmYWN0b3IoTW9udGgpLCBEYXkgPSBmYWN0b3IoRGF5KSkKYWlycXVhbGl0eQpgYGAKCiMjIMOcYnVuZ3NhdWZnYWJlbgoKMS4gIFNjaGFmZmVuIFNpZSBzaWNoIHp1bsOkY2hzdCBlaW5lIChzdGF0aXN0aXNjaGUpIMOcYmVyc2ljaHQgw7xiZXIgZGllCiAgICBEYXRlbi4gV2llIHZpZWxlIEJlb2JhY2h0dW5nZW4gdW5kIFZhcmlhYmxlbiB3dXJkZW4gZXJmYXNzdD8KMi4gIFZlcnNpY2hlcm4gU2llIHNpY2ggenVuw6RjaHN0LCBkYXNzIGRpZSBEYXRlbiBlaW5pZ2VybWHDn2VuCiAgICBrb250aW51aWVybGljaCBpbm5lcmhhbGIgZGVzIFplaXRyYXVtcyBlcmZhc3N0IHd1cmRlbi4KMy4gIFZlcmFuc2NoYXVsaWNoZW4gU2llIGRpZSBWZXJ0ZWlsdW5nZW4gZGVyIGtvbnRpbnVpZXJsaWNoZW4gVmFyaWFibGVuCiAgICB2b24gYGFpcnF1YWxpdHlgLiBXZWxjaGUgQXNwZWt0ZSBmYWxsZW4gaW5zIEF1Z2UgdW5kIGvDtm5udGVuCiAgICBpbnRlcmVzc2FudCBzZWluPwo0LiAgRmluZGVuIFNpZSBadXNhbW1lbmjDpG5nZSB6d2lzY2hlbiBkZW4gdmVyc2NoaWVkZW5lbiBWYXJpYWJsZW4uCiAgICAxLiAgVmlzdWFsaXNpZXJlbiBTaWUgZsO8ciBhbGxlIHNpbm52b2xsZW4gVmFyaWFibGVuIGRpZSBwYWFyd2Vpc2VuCiAgICAgICAgQmV6aWVodW5nZW4uIFNpbmQgZGllc2UgYWxsZSBsaW5lYXI/CiAgICAyLiAgQmVyZWNobmVuIFNpZSBmw7xyIGFsbGUgc2lubnZvbGxlbiBWYXJpYWJsZW4gZWluZQogICAgICAgIEtvcnJlbGF0aW9uc21hdHJpeC4gQmlldGV0IHNpY2ggUGVhcnNvbnMgS29ycmVsYXRpb25za29lZmZpemllbnQKICAgICAgICBhbj8KICAgIDMuICBCZXN0aW1tZW4gU2llIGR1cmNoIFZpc3VhbGlzaWVydW5nLCB3byBadXNhbW1lbmjDpG5nZSB2b3JsaWVnZW4uCjUuICBFcnN0ZWxsZW4gU2llIGbDvHIgcmVsZXZhbnRlIChrYXVzYWxlKSBadXNhbW1lbmjDpG5nZQogICAgUmVncmVzc2lvbnNtb2RlbGxlLgoKIyMgTMO2c3VuZ2VuCgojIyMgU2NoYWZmZW4gU2llIHNpY2ggenVuw6RjaHN0IGVpbmUgKHN0YXRpc3Rpc2NoZSkgw5xiZXJzaWNodCDDvGJlciBkaWUgRGF0ZW4uIFdpZSB2aWVsZSBCZW9iYWNodHVuZ2VuIHVuZCBWYXJpYWJsZW4gd3VyZGVuIGVyZmFzc3Q/CgpBbmhhbmcgZGVzIGB0aWJibGVzYCBzZWhlbiB3aXIgZGlyZWt0IGRpZSBBbnphaGwgZGVyIEJlb2JhY2h0dW5nZW4gdW5kClZhcmlhYmxlbi4gQWx0ZXJuYXRpdiBhdWNoIG9iZW4gcmVjaHRzIHVudGVyIGBFbnZpcm9ubWVudGAgb2RlciBtaXQKYGRpbSgpYAoKYGBge3J9CmFpcnF1YWxpdHkKZGltKGFpcnF1YWxpdHkpCnN1bW1hcnkoYWlycXVhbGl0eSkKYGBgCgojIyMgVmVyc2ljaGVybiBTaWUgc2ljaCB6dW7DpGNoc3QsIGRhc3MgZGllIERhdGVuIGVpbmlnZXJtYcOfZW4ga29udGludWllcmxpY2ggaW5uZXJoYWxiIGRlcyBaZWl0cmF1bXMgZXJmYXNzdCB3dXJkZW4uCgpIaWVyIGdpYnQgZXMgbWVocmVyZSBNw7ZnbGljaGtlaXRlbi4gTmFpdiBrw7ZubnRlbiB3aXIgZGllIFRhYmVsbGUKZHVyY2hzY2hhdWVuIHVuZCBqZWRlbiBlaW56ZWxuZW4gVGFnIHVuZCBNb25hdCBuYWNoc2NobGFnZW4sIG9iIGRpZXNlcgp2b3JoYW5kZW4gaXN0LiBIaWVyIGthbm4gYWJlciBsZWljaHQgZGFzIEdlc2FtdGJpbGQgYXVzIGRlbiBBdWdlbgp2ZXJsb3JlbiBnZWhlbiwgZGFoZXIgd29sbGVuIHdpciBkYXMga3VyeiB2ZXJhbnNjaGF1bGljaGVuOgoKYGBge3J9CmdncGxvdChhaXJxdWFsaXR5KSArCiAgZ2VvbV9iYXIoYWVzKHggPSBEYXkpKQpnZ3Bsb3QoYWlycXVhbGl0eSkgKwogIGdlb21fYmFyKGFlcyh4ID0gTW9udGgpKQpgYGAKCldlbm4gd2lyIEJhbGtlbmRpYWdyYW1tZSBmw7xyIFRhZyB1bmQgTW9uYXQgc2VwYXJhdCB6ZWljaG5lbiwgZGV1dGV0IHNpY2gKYW4sIGRhc3MgZGllc2Ugc2NoZWluYmFyIGtvbnRpbnVpZXJsaWNoIGVyZmFzc3Qgd3VyZGVuLlwKU2VpZW4gU2llIGhpZXJiZWkgdm9yc2ljaHRpZywgZGVubiBlcyB3w6RyZSBwcmluemlwaWVsbCBhdWNoIG3DtmdsaWNoLAptZWhyZXJlIE1lc3N1bmdlbiBhbiBkZW1zZWxiZW4gVGFnIGR1cmNoenVmw7xocmVuLCB1bSBkYXMgRXJnZWJuaXMgenUKdmVyw6RuZGVybi4KCkbDvHIgZWluZSBkZXV0bGljaGVyZSBFaW5zaWNodCBrw7ZubmVuIHdpciB1bnMgZGFzIGdlbWVpbnNhbWUKQmFsa2VuZGlhZ3JhbW0gYW56ZWlnZW46CgpgYGB7cn0KZ2dwbG90KGFpcnF1YWxpdHkpICsKICBnZW9tX2JhcihhZXMoeCA9IERheSwgZmlsbCA9IE1vbnRoKSkKZ2dwbG90KGFpcnF1YWxpdHkpICsKICBnZW9tX2JhcihhZXMoeCA9IE1vbnRoLCBmaWxsID0gRGF5KSkgKwogIGZhY2V0X3dyYXAofiBEYXkpCmBgYAoKU2NoZWluYmFyIGxpZWdlbiBkaWUga2xlaW5lbiBVbnRlcnNjaGllZGUgbGVkaWdsaWNoIGFuIGRlbSAzMS4gVGFnIGltCk1vbmF0LiBEaWUgRGF0ZW4gd3VyZGVuIHNvbWl0IGtvbnRpbnVpZXJsaWNoIGVyZmFzc3QuCgojIyMgVmVyYW5zY2hhdWxpY2hlbiBTaWUgZGllIFZlcnRlaWx1bmdlbiBkZXIga29udGludWllcmxpY2hlbiBWYXJpYWJsZW4gdm9uIGBhaXJxdWFsaXR5YC4gV2VsY2hlIEFzcGVrdGUgZmFsbGVuIGlucyBBdWdlIHVuZCBrw7ZubnRlbiBpbnRlcmVzc2FudCBzZWluPwoKV2lyIGvDtm5udGVuIHVucyBoaWVyIGplZGUgZWluemVsbmUgVmFyaWFibGUgaW0gRGV0YWlsIGFuc2NoYXVlbiwgYWJlcgp2ZXJibGVpYmVuIGJlaXNwaWVsaGFmdCBiZWkgZGVyIFZhcmlhYmxlbiBgT3pvbmVgLgoKU2NoYXVlbiB3aXIgdW5zIHp1bsOkY2hzdCBkaWUgYWxsZ2VtZWluZSBWZXJ0ZWlsdW5nIGFuaGFuZCB2b24gQm94cGxvdAp1bmQgSGlzdG9ncmFtbSBhbjoKCmBgYHtyfQpnZ3Bsb3QoYWlycXVhbGl0eSkgKwogIGdlb21fYm94cGxvdChhZXMoeSA9IE96b25lKSwgZmlsbCA9ICJzdGVlbGJsdWUiKQpnZ3Bsb3QoYWlycXVhbGl0eSwgYWVzKHggPSBPem9uZSkpICsKICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gInN0ZWVsYmx1ZSIsIGJpbnMgPSAxNSkgKwogIGdlb21fZnJlcXBvbHkoYmlucyA9IDE1KQpgYGAKClZpZWxlIFdlcnRlIHNjaGVpbmVuIHNpY2ggendpc2NoZW4gXH4xMCB1bmQgXH4zMCBhdWZ6dWhhbHRlbi4gRGFzIGVyc3RlClF1YXJ0aWwgd2lyZCBkdXJjaCAxOCB1bmQgZGFzIGRyaXR0ZSBRdWFydGlhbCBkdXJjaCA2MyBtYXJraWVydC4KCkF1cyBkZXIgQXVza3VuZnQgKGA/YWlycXVhbGl0eWApIHNlaGVuIHdpciwgZGFzcyBlcyBzaWNoIGhpZXJiZWkgdW0KZHVyY2hzY2huaXR0bGljaGUgVGVpbGNoZW4gcHJvIE1pbGxpYXJkZSB6d2lzY2hlbiAxMyB1bmQgMTUgVWhyLApnZW1lc3NlbiBhdWYgUm9vc2V2ZWx0IElzbGFuZCwgaGFuZGVsdC4gU2NoZWluYmFyIGxpZWdlbiBhdWNoIGVpbmlnZQpmZWhsZW5kZSBXZXJ0ZSB1bmQgQXVzcmVpw59lciB2b3IsIHdlbGNoZSB3aXIgenVuw6RjaHN0IGJldHJhY2h0ZW4gd29sbGVuLgoKQXVzcmVpw59lciBrw7ZubmVuIHdpciBlcmhhbHRlbiwgaW4gZGVtIHdpciAoaW4gZGllc2VtIEZhbGwpIG5hY2ggaG9oZW4KV2VydGUgdm9uIGBPem9uZWAgZmlsdGVybjoKCmBgYHtyfQphaXJxdWFsaXR5ICU+JQogIGZpbHRlcihPem9uZSA+IDEyNSkKYGBgCgpXaXIgc2VoZW4sIGRhc3MgYW0gMS4wNy4xOTczIHVuZCBhbSAyNS4wOC4xOTczIEF1c3JlacOfZXIgYmVpIGRlbgpPem9ud2VydGVuIHZvcmxhZ2VuLiBVbSBkaWUgQXVzcmVpw59lciBkZXIgQm94cGxvdHMgZGlyZWt0IHp1IGVyaGFsdGVuCihvaG5lIHNlbGJzdHN0w6RuZGlnZXMgRmlsdGVybiksIGvDtm5uZW4gd2lyIGF1cyBgcnN0YXRpeGAgZGllIEZ1bmt0aW9uCmBpZGVudGlmeV9vdXRsaWVycygpYCB2ZXJ3ZW5kZW46CgpgYGB7cn0KbGlicmFyeShyc3RhdGl4KQphaXJxdWFsaXR5ICU+JQogIGlkZW50aWZ5X291dGxpZXJzKE96b25lKQpgYGAKCldpciBzZWhlbiwgZGFzcyBkaWVzZWxiZW4gendlaSBXZXJ0ZSBpbiBkZXIgU3BhbHRlIGBpcy5vdXRsaWVyYCBhbHMKQXVzcmVpw59lciAoYFRSVUVgKSBtYXJraWVydCB3dXJkZW4sIGplZG9jaCBuaWNodCBhbHMgZXh0cmVtZSBBdXNyZWnDn2VyCihgRkFMU0VgKS4gQXVzcmVpw59lciB3ZWljaGVuIHVtIGVpbmVuIElRUiB2b24gbWVociBhbHMgMS41IHZvbiBkZW0gMy4KUXVhcnRpbCBhYiwgZXh0cmVtZSBBdXNyZWnDn2VyIGFiIGRlbSBkcmVpZmFjaGVuIElRUi4KClVtIGRpZSBmZWhsZW5kZW4gRWludHLDpGdlIHp1IGJldHJhY2h0ZW4sIGvDtm5uZW4gd2lyIG5hY2ggYGlzLm5hKClgCmZpbHRlcm46CgpgYGB7cn0KYWlycXVhbGl0eSAlPiUKICBmaWx0ZXIoaXMubmEoT3pvbmUpKQpgYGAKClNjaGVpbmJhciB3dXJkZW4gYW4gMi8zIGRlciBUYWdlIGRlcyBKdW5pcyBrZWluZSBEYXRlbiBiZXrDvGdsaWNoIGRlcgpPem9ud2VydGUgZXJob2Jlbi4gV29obG3DtmdsaWNoIGdhYiBlcyBTdMO2cnVuZ2VuIGRlciBNZXNzZ2Vyw6R0ZT8KCk51biBzdGVsbHQgc2ljaCBkaWUgRnJhZ2UsIHdpZXNvIHNvIHZpZWxlIE1lc3N3ZXJ0ZSBmZWhsZW4uIERhcyBsw6Rzc3QKc2ljaCBvaG5lIHdlaXRlcmUgSW5mb3JtYXRpb24gZGVzIERhdGVuZXJoZWJlcnMgbmljaHQgZWluZmFjaCBrbMOkcmVuLAphbGxlcmRpbmdzIGvDtm5uZW4gd2lyIG5hY2ggTXVzdGVybiBzdWNoZW46CgpgYGB7cn0KYWlycXVhbGl0eSAlPiUKICBncm91cF9ieShNb250aCkgJT4lCiAgc3VtbWFyaXNlKG4gPSBzdW0oaXMubmEoT3pvbmUpKSkKYGBgCgpTY2hhdWVuIHdpciwgb2IgZXMgYW4genVzYW1tZW5ow6RuZ2VuZGVuIFplaXRwdW5rdGVuIFByb2JsZW1lIGJlaSBkZXIKRGF0ZW5lcmhlYnVuZyBnYWIuIEhpZXJ6dSBmw7xnZW4gd2lyIHp1bsOkY2hzdCBNb25hdCB1bmQgVGFnIHp1IGVpbmVtCk9iamVrdCBmw7xyIChLYWxlbmRlci0pRGF0ZW4genVzYW1tZW46CgpgYGB7cn0KYWlycXVhbGl0eSA8LSBhaXJxdWFsaXR5ICU+JQogIG11dGF0ZShkYXRlID0gYXMuRGF0ZShwYXN0ZSgiMTk3MyIsIE1vbnRoLCBEYXksIHNlcCA9ICItIikpKQphaXJxdWFsaXR5CmdncGxvdChhaXJxdWFsaXR5KSArIAogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBPem9uZSksIGNvbG9yID0gInRvbWF0bzEiKQpgYGAKCkluIGRlbiBtZWlzdGVuIEbDpGxsZW4gc2NoZWluZW4gZGllIGZlaGxlbmRlbiBFaW50csOkZ2UgbmljaHQgbWVocmVyZQphdWZlaW5hbmRlcmZvbGdlbmRlIFRhZ2UgenUgYmVzY2hyZWliZW4uIEltIEp1bmkgc2NoZWluZW4gZ8OkbnpsaWNoIGFtCkFuZmFuZyB1bmQgRW5kZSBkZXMgTW9uYXRzIGRpZSBFaW50csOkZ2UgenUgZmVobGVuLgoKRXMga8O2bm50ZSBhdWNoIGludGVyZXNzYW50IHNlaW4sIGRlbiBWZXJsYXVmIGRlciBPem9ud2VydGUgYW4gZGVuCnZlcnNjaGllZGVuZW4gTW9uYXRzdGFnZW4genUgdmVyZ2xlaWNoZW4gb2RlciB6d2lzY2hlbiBkZW4gTW9uYXQgKG9kZXIKYmVpZGVzIHp1Z2xlaWNoKToKCmdlb21faGlzdG9ncmFtKGFlcyh4ID0gT3pvbmUsIGZpbGwgPSBEYXkpKSArCgpmYWNldF93cmFwKFx+IERheSkKCmBgYHtyfQpnZ3Bsb3QoYWlycXVhbGl0eSkgKwogIGdlb21fYm94cGxvdChhZXMoRGF5LCBPem9uZSwgZmlsbCA9IERheSkpCmdncGxvdChhaXJxdWFsaXR5KSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBPem9uZSwgZmlsbCA9IERheSkpICsKICBmYWNldF93cmFwKH4gRGF5KQpnZ3Bsb3QoYWlycXVhbGl0eSkgKwogIGdlb21fYm94cGxvdChhZXMoTW9udGgsIE96b25lLCBmaWxsID0gTW9udGgpKQpnZ3Bsb3QoYWlycXVhbGl0eSwgYWVzKHggPSBPem9uZSwgZmlsbCA9IE1vbnRoKSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSkpICsgIyBpbiDDpGx0ZXJlbiBWZXJzaW9uZW4geSA9IC4uZGVuc2l0eS4uCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC4yKSArCiAgZmFjZXRfd3JhcCh+IE1vbnRoKSArCgl0aGVtZShsZWdlbmQucG9zaXRpb24gPSBOVUxMKQpgYGAKCkJlaSBkZXIgQmV0cmFjaHR1bmcgZGVyIE1vbmF0c3RhZ2Ugc2VoZW4gd2lyIHRlbmRlbnppZWxsIGFtIEFuZmFuZyB1bmQKRW5kZSBkZXMgTW9uYXRzIG1laHIgVmFyaWFiaWxpdMOkdCAod29obG3DtmdsaWNoIHdlZ2VuIGRlciBmZWhsZW5kZW4gV2VydGUKaW0gSnVuaSkuIEluIGRlciBNaXR0ZSBkZXMgTW9uYXRzIHNjaGVpbmVuIGRpZSBXZXJ0ZSB3ZW5pZ2VyCmF1c2VpbmFuZGVyenVnZWhlbiwgYXVjaCB3ZW5uIGVzIEF1c3JlacOfZXIgZ2lidC4KCkJlaSBkZXIgQmV0cmFjaHR1bmcgZGVyIE1vbmF0ZSBzZWhlbiB3aXIsIGRhc3MgZXMgZHVyY2hzY2huaXR0bGljaAp2ZXJtdXRsaWNoIG1laHIgT3pvbiBpbSBKdWxpIHVuZCBBdWd1c3QgZ2FiLCB3b2JlaSBoaWVyIGF1Y2ggZGllIFZhcmlhbnoKaMO2aGVyIHdhci4gQmVzb25kZXJzIGltIFNlcHRlbWJlciBnYWIgZXMgdmllbGUgQXVzcmVpw59lci4gSW4gZGVtCkhpc3RvZ3JhbW0gdm9tIEp1bmkgc2VoZW4gd2lyIGluc2Jlc29uZGVyZSBkaWUgZmVobGVuZGVuIFdlcnRlLiBBdWNoCmhpZXIgd2VyZGVuIGRpZSBNdXN0ZXIgZGVyIE96b253ZXJ0ZSBldHdhcyBkZXV0bGljaGVyLCBkYXNzIGltIE1haSB1bmQKU2VwdGVtYmVyICh1bmQgZ2dmLiBKdW5pKSB0ZW5kZW56aWVsbCAoZ2Vow6R1ZnQpIGdlcmluZ2VyZSBXZXJ0ZQp2b3JsaWVnZW4uCgpEZXIgVm9ydGVpbCBhbiBgcnN0YXRpeGAgbGllZ3QgYXVjaCBkYXJpbiwgZGFzcyBzaWNoIHZlcnNjaGllZGVuZQpGdW5rdGlvbmVuIGF1ZiBHcnVwcGVuIGJlcmVjaG5lbiBsYXNzZW4uIFNvIGvDtm5uZW4gd2lyIGF1Y2ggc2VwYXJhdCBmw7xyCmplZGVuIFRhZy9Nb25hdCBkaWUgQXVzcmVpw59lciB2b24gYE96b25lYCBiZXN0aW1tZW46CgpgYGB7cn0KIyBEZXIgMzAuMDUuIGlzdCBpbm5lcmhhbGIgZGVzIE1haXMgZWluIGV4dHJlbWVyIEF1c3JlacOfZXIKYWlycXVhbGl0eSAlPiUKCWdyb3VwX2J5KE1vbnRoKSAlPiUKICBpZGVudGlmeV9vdXRsaWVycyhPem9uZSkKYGBgCgpXaXIgaGF0dGVuIGF1Y2ggYmVyZWl0cyBnZWxlcm50LCB3aWUgd2lyIGbDvHIgR3J1cHBlbi9LYXRlZ29yaWVuCnN0YXRpc3Rpc2NoZSBadXNhbW1lbmZhc3N1bmdlbiBtYWNoZW4ga8O2bm5lbiwgdW0gZGllIFNhY2h2ZXJoYWx0ZSBhdWNoCmR1cmNoIFphaGxlbndlcnRlIGJldHJhY2h0ZW4genUga8O2bm5lbi4gSGllcmJlaSBtdXNzIGF1ZiBkaWUgYE5Bc2AKZ2VhY2h0ZXQgd2VyZGVuOgoKYGBge3J9CmFpcnF1YWxpdHkgJT4lCglncm91cF9ieShNb250aCkgJT4lCglzdW1tYXJpc2UobiA9IG4oKSwKCQkJCQkJbWl0dGVsd2VydCA9IG1lYW4oT3pvbmUsIG5hLnJtID0gVFJVRSksCgkJCQkJCW1lZGlhbiA9IG1lZGlhbihPem9uZSwgbmEucm0gPSBUUlVFKSwKCQkJCQkJbWluID0gbWluKE96b25lLCBuYS5ybSA9IFRSVUUpLAoJCQkJCQltYXggPSBtYXgoT3pvbmUsIG5hLnJtID0gVFJVRSksCgkJCQkJCXN0YW5kYXJkYWJ3ID0gc2QoT3pvbmUsIG5hLnJtID0gVFJVRSkpCmBgYAoKTWVpc3QgaXN0IGVzIGhpbGZyZWljaCwgYmVpIGludGVyZXNzYW50ZW4gQXNwZWt0ZW4genVzw6R0emxpY2ggenUgZWluZXIKR3JhZmlrIGF1Y2ggbWFya2FudGUgc3RhdGlzdGlzY2hlIE1hw596YWhsZW4genUgYmVuZW5uZW4uIEJlaXNwaWVsc3dlaXNlCnNpbmQgZGllIFN0YW5kYXJkYWJ3ZWljaHVuZ2VuIHVuZCBNZWRpYW5lL01pdHRlbHdlcnRlIHZvbiBKdWxpIHVuZApBdWd1c3QgaG9jaCAoaG9oZSBWYXJpYW56KS4gU29taXQga2FubiBhdWNoIGRpZSBlbnRzcHJlY2hlbmRlIE1hw596YWhsCmFuZ2VnZWJlbiB3ZXJkZW4uCgpCZXRyYWNodGVuIHdpciBsZXR6dGxpY2ggbm9jaCwgb2IgZGllIFZhcmlhYmxlIGBPem9uZWAgbm9ybWFsdmVydGVpbHQKaXN0OgoKYGBge3J9CmFpcnF1YWxpdHkgJT4lCiAgc2hhcGlyb190ZXN0KE96b25lKQpgYGAKCi0gICBEYSBkZXIgcC1XZXJ0IHdlc2VudGxpY2gga2xlaW5lciBpc3QgYWxzIMO8YmxpY2hlIFNpZ25pZmlrYW56bml2ZWF1cwogICAg4o26LCBsZWhuZW4gd2lyIGRpZSBOdWxsaHlwb3RoZXNlIGFiLCBkLiBoLiB3aXIgZ2VoZW4gZGF2b24gYXVzLCBkYXNzCiAgICBkaWUgRGF0ZW4gbmljaHQgbm9ybWFsdmVydGVpbHQgc2luZC4KLSAgIEVzIHN0ZWxsdCBzaWNoIGRlbm5vY2ggZGllIEZyYWdlLCBvYiBkaWUgRGF0ZW4gZGVyIGVpbnplbG5lbiBNb25hdGUKICAgIG5vcm1hbHZlcnRlaWx0IHNpbmQ6CgpgYGB7cn0KYWlycXVhbGl0eSAlPiUKICBncm91cF9ieShNb250aCkgJT4lCiAgc2hhcGlyb190ZXN0KE96b25lKQpgYGAKCi0gICBTY2hlaW5iYXIgZ2lsdCBwIFw8IOKNuiBmw7xyIE1haSB1bmQgU2VwdGVtYmVyID1cPiBrZWluZQogICAgTm9ybWFsdmVydGVpbHVuZwotICAgRsO8ciBKdW5pLCBKdWxpIHVuZCBBdWd1c3Qgc2luZCBkaWUgRGF0ZW4gKHZlcm11dGxpY2gpCiAgICBub3JtYWx2ZXJ0ZWlsdCwgZGEgZGVyIHAtV2VydCBrbGVpbmVyIGFscyDijbogaXN0LgotICAgV2VubiB3aXIgZGllIGFuZGVyZW4gVmFyaWFibGVuIGluIGRpZSBGdW5rdGlvbiBgc2hhcGlyb190ZXN0KClgCiAgICBlaW5nZWJlbiB3w7xyZGVuLCBrw7ZubnRlbiB3aXIgZGlyZWt0IGFsbGUgZ2VtZWluc2FtIGF1ZgogICAgTm9ybWFsdmVydGVpbHVuZyB0ZXN0ZW4gdW5kIG3DvHNzdGVuIGRpZXMgbmljaHQgZWluemVsbiBtYWNoZW4uCgpFbnRzcHJlY2hlbmRlIMOcYmVybGVndW5nZW4ga8O2bm50ZW4gYXVjaCBmw7xyIGRpZSBhbmRlcmVuIFZhcmlhYmxlbiBpbgpCZXRyYWNodCBnZXpvZ2VuIHdlcmRlbi4KCiMjIyBGaW5kZW4gU2llIFp1c2FtbWVuaMOkbmdlIHp3aXNjaGVuIGRlbiB2ZXJzY2hpZWRlbmVuIFZhcmlhYmxlbi4KCiMjIyMgVmlzdWFsaXNpZXJlbiBTaWUgZsO8ciBhbGxlIHNpbm52b2xsZW4gVmFyaWFibGVuIGRpZSBwYWFyd2Vpc2VuIEJlemllaHVuZ2VuLiBTaW5kIGRpZXNlIGFsbGUgbGluZWFyPwoKRWluZSBIZXJhbmdlaGVuc3dlaXNlIGlzdCwgZsO8ciBqZWRlcyBQYWFyIGFuIFZhcmlhYmxlbiBlaW4gbmV1ZXMKU3RyZXVkaWFncmFtbSB6dSB6ZWljaG5lbjoKCmBgYHtyfQpnZ3Bsb3QoYWlycXVhbGl0eSwgYWVzKHggPSBPem9uZSwgeSA9IFRlbXApKSArCglnZW9tX3BvaW50KGFlcyhjb2xvciA9IE1vbnRoKSkgKwoJZ2VvbV9zbW9vdGgoY29sb3IgPSAicmVkIikKYGBgCgotICAgSGllciBzZWhlbiB3aXIgYmVpc3BpZWxzd2Vpc2UgZWluZW4gc2NoZWluYmFyIHF1YWRyYXRpc2NoZW4KICAgIFp1c2FtbWVuaGFuZyAoUGFyYWJlbCkKLSAgIERhcyBpc3QgamVkb2NoIGF1ZndlbmRpZywgd2VubiBrZWluIHNwZXppZmlzY2hlcyBJbnRlcmVzc2UgYW4gamVkZW0KICAgIFZhcmlhYmxlbnBhYXIgYmVzdGVodAoKRWluZSBTdGFuZGFyZGZ1bmt0aW9uIGluIFIsIGRpZSBlaW5lbiDDnGJlcmJsaWNrIMO8YmVyIGVpbmVuIGdlc2FtdGVuCkRhdGFmcmFtZSBsaWVmZXJ0LCBpc3QgZGllIGBwbG90KClgIEZ1bmt0aW9uOgoKYGBge3J9CnBsb3QoYWlycXVhbGl0eSkKYGBgCgotICAgRGllIEdyYWZpayBpc3QgamVkb2NoIG5pY2h0IHNlaHIgb3JkZW50bGljaAotICAgV2VpdGVyZSBBbHRlcm5hdGl2ZW4gc2luZCBgcGFpcnMoKWAgb2RlciBgZ2dwYWlycygpYCBhdXMgZGVtIFBha2V0CiAgICBgR0dhbGx5YDoKCmBgYHtyfQpwYWlycyhhaXJxdWFsaXR5LCBwYW5lbCA9IHBhbmVsLnNtb290aCkgIyBwYW5lbCA9IHBhbmVsLnNtb290aCB6ZWljaG5ldCBkaWUgS3VydmVuIGVpbgpgYGAKCmBgYHtyfQpsaWJyYXJ5KEdHYWxseSkKYWlycXVhbGl0eSAlPiUKCXNlbGVjdCgtYyhEYXksIGRhdGUpKSAlPiUKZ2dwYWlycyhwcm9ncmVzcyA9IEZBTFNFKQpgYGAKCi0gICBCZWFjaHRlbiBTaWUsIGRhc3MgZsO8ciBqZWRlIEdyYWZpayBkaWUgYE5Bc2AgZW50ZmVybnQgd3VyZGVuLgotICAgRGllc2UgR3JhZmlrIGlzdCBpbiB2aWVsZXJsZWkgSGluc2ljaHQgaGlsZnJlaWNoOgogICAgLSAgIFp1bsOkY2hzdCBzZWhlbiB3aXIgYXVmIGRlciBEaWFnb25hbGVuIGRpZSBWZXJ0ZWlsdW5nIGRlcgogICAgICAgIFZhcmlhYmxlbgogICAgLSAgIE9iZXJoYWxiIGRlciBEaWFnb25hbGVuIHNlaGVuIHdpciBkaWUgS29ycmVsYXRpb25za29lZmZpemllbnRlbgogICAgICAgIGbDvHIgZGllIFZhcmlhYmxlbnBhYXJlIG9kZXIgQm94cGxvdHMgbmFjaCBHcnVwcGVuIGFycmFuZ2llcnQKICAgIC0gICBVbnRlcmhhbGIgZGVyIERpYWdvbmFsZW4gc2VoZW4gd2lyIGRpZSBTdHJldWRpYWdyYW1tZSBmw7xyIGRpZQogICAgICAgIFZhcmlhYmxlbnBhYXJlIG9kZXIgSGlzdG9ncmFtbWUgbmFjaCBkZXIgR3J1cHBlIGFycmFuZ2llcnQKICAgIC0gICBEaWUgR3JhZmlrZW4gc2luZCBpbSBHZWdlbnNhdHogenUgZGVuIG9iaWdlbiBHcmFmaWtlbiBuaWNodAogICAgICAgICJkb3BwZWx0IiAoeC0gdW5kIHktQWNoc2UpCiAgICAtICAgRGllIEdyYWZpayBsw6Rzc3Qgc2ljaCBub2NoIHdlaXRlciB2ZXJmZWluZXJuCiAgICAtICAgRHVyY2ggZGllIFZpZWx6YWhsIGFuIEdyYWZpa2VuIHVuZCBkZW0gaG9oZW4gSW5mb3JtYXRpb25zZ2VoYWx0CiAgICAgICAgaXN0IGRpZSBHcmFmaWsgZ3V0IGdlZWlnbmV0LCB1bSBzaWNoIGVpbmUgw5xiZXJzaWNodCB6dSBzY2hhZmZlbgogICAgLSAgIERpZSBHcmFmaWsgbWFnIGluIGRpZXNlciBBdXNmw7xocmxpY2hrZWl0IGplZG9jaCBuaWNodCB1bmJlZGluZ3QKICAgICAgICBkaWUgZXJzdGUgV2FobCBzZWluLCB3ZW5uIERhdGVuL0VyZ2Vibmlzc2Ugdm9yZ2VzdGVsbHQgd2VyZGVuCiAgICAgICAgc29sbGVuIChlcyBzZWkgZGVubiwgZXMgZ2lidCBQbGF0em1hbmdlbCAuLi4pCgpGb2t1c3NpZXJlbiB3aXIgdW5zIG51ciBhdWYgZGllIGtvbnRpbnVpZXJsaWNoZW4gRGF0ZW4gdW5kIGVyd2VpdGVybiBkaWUKR3JhZmlrIGV0d2FzOgoKYGBge3J9CiMgSW4gY29sdW1ucyBsYXNzZW4gc2ljaCBlYmVuZmFsbHMgZGllIFZhcmlhYmxlbiBhdXN3w6RobGVuOgphaXJxdWFsaXR5ICU+JQoJZ2dwYWlycyhjb2x1bW5zID0gMTo0LCBhZXMoY29sb3IgPSBNb250aCwgYWxwaGEgPSAwLjAxKSwgcHJvZ3Jlc3MgPSBGQUxTRSkKYGBgCgotICAgRHVyY2ggQWVzdGhldGljcyBpbiBgZ2dwYWlycygpYCBrw7ZubmVuIHdpciBlYmVuZmFsbHMgYWxsZSBHcmFmaWtlbgogICAgZ3J1cHBpZXJlbiAoaGllciBuYWNoIGRlbSBNb25hdCkKLSAgIFp1c8OkdHpsaWNoIGVyaGFsdGVuIHdpciBmw7xyIGplZGUgR3J1cHBlIHNlcGFyYXQgZGVuCiAgICBLb3JyZWxhdGlvbnNrb2VmZml6aWVudGVuCi0gICBXZWl0ZXJlIEluZm9ybWF0aW9uIGZpbmRlbiBTaWUKICAgIFtoaWVyXShodHRwczovL2dnb2JpLmdpdGh1Yi5pby9nZ2FsbHkvcmVmZXJlbmNlL2dncGFpcnMuaHRtbCkuCgojIyMjIEJlcmVjaG5lbiBTaWUgZsO8ciBhbGxlIHNpbm52b2xsZW4gVmFyaWFibGVuIGVpbmUgS29ycmVsYXRpb25zbWF0cml4LiBCaWV0ZXQgc2ljaCBQZWFyc29ucyBLb3JyZWxhdGlvbnNrb2VmZml6aWVudCBhbj8KCldpciBoYXR0ZW4genV2b3IgZGllIEtvcnJlbGF0aW9uc2tvZWZmaXppZW50ZW4gc2Nob24gZ2VzZWhlbiwgd29sbGVuIGRhcwphbiBkaWVzZXIgU3RlbGxlIG5vY2ggZWlubWFsIGdlc2FtdGVpbmhlaXRsaWNoIG1hY2hlbjoKCmBgYHtyfQphaXJxdWFsaXR5ICU+JQoJc2VsZWN0KC1jKERheSwgTW9udGgsIGRhdGUpKSAlPiUgIyBGYWt0b3Jlbi9HcnVwcGVuIG5pY2h0IGJlYWNodGVuCgljb3JfbWF0KCkKYGBgCgotICAgYFRlbXBgIHVuZCBgT3pvbmVgIHNpbmQgc3RhcmsgcG9zaXRpdiBrb3JyZWxpZXJ0Ci0gICBgV2luZGAgdW5kIGBPem9uZWAgc293aWUgYFRlbXBgIHVuZCBgV2luZGAgc2luZCBtb2RlcmF0IG5lZ2F0aXYKICAgIGtvcnJlbGllcnQKCiMjIyMgQmVzdGltbWVuIFNpZSBkdXJjaCBWaXN1YWxpc2llcnVuZywgd28gWnVzYW1tZW5ow6RuZ2Ugdm9ybGllZ2VuLgoKYGBge3J9CmFpcnF1YWxpdHkgJT4lCglzZWxlY3QoLWMoRGF5LCBNb250aCwgZGF0ZSkpICU+JSAjIEZha3RvcmVuL0dydXBwZW4gbmljaHQgYmVhY2h0ZW4KCWNvcl9tYXQoKSAlPiUKCWNvcl9yZW9yZGVyKCkgJT4lCgljb3JfcGxvdCh0eXBlID0gInVwcGVyIiwgbGFiZWwgPSBUUlVFLAoJCQkJCSBwYWxldHRlID0gZ2dwdWJyOjpnZXRfcGFsZXR0ZShjKCJkYXJrcmVkIiwgImNvcm5zaWxrIiwgImRhcmtjeWFuIiksIDIwMCkKCQkJCQkgKQpgYGAKCkRpZSBadXNhbW1lbmjDpG5nZSB3dXJkZW4gb2JlbiBiZXJlaXRzIGVybMOkdXRlcnQuIFp1c8OkdHpsaWNoIGhhYmVuIGBXaW5kYAp1bmQgYFNvbGFyLlJgIGtlaW5lbiBadXNhbW1lbmhhbmcgdW5kIGRpZSByZXN0bGljaGVuIFBhYXJlIG51ciBzY2h3YWNoZQpadXNhbW1lbmjDpG5nZS4KCiMjIyBFcnN0ZWxsZW4gU2llIGbDvHIgcmVsZXZhbnRlIChrYXVzYWxlKSBadXNhbW1lbmjDpG5nZSBSZWdyZXNzaW9uc21vZGVsbGUuCgpJbmJlc29uZGVyZSBgV2luZGAgdW5kIGBPem9uZWAgc293aWUgYFRlbXBgIHVuZCBgT3pvbmVgIGvDtm5udGVuCmludGVyZXNzYW50ZSBadXNhbW1lbmjDpG5nZSBzZWluOgoKLSAgIFdpbmQga2FubiBkYXMgZGFzIE96b25nYXMgd2VndHJhbnNwb3J0aWVyZW4sIHNvZGFzcyBkaWUgQW56YWhsIGRlcgogICAgT3pvbm1vbGVrw7xsZSBpbiBkZXIgTHVmdCBzaW5rdAoKLSAgIFNvbm5lbmVpbnN0cmFobHVuZyB1bmQgaG9oZSBUZW1wZXJhdHVyZW4gYmVnw7xuc3RpZ2VuIGRpZSBSZWFrdGlvbgogICAgdm9uIFN0aWNrc3RvZmYgdW5kIFNhdWVyc3RvZmYgenUgT3pvbgoKYGBge3J9CmdncGxvdChhaXJxdWFsaXR5LCBhZXMoV2luZCwgT3pvbmUpKSArCglnZW9tX3BvaW50KCkgKwoJZ2VvbV9zbW9vdGgoKQpgYGAKCi0gICBEaWUgVHJlbmRrdXJ2ZSB6ZWlndCwgZGFzcyBkZXIgT3pvbmdlaGFsdCBpbiBkZXIgTHVmdCBtaXQgc3RlaWdlbmRlbQogICAgV2luZCBzaW5rdAotICAgRGVyIFp1c2FtbWVuaGFuZyBrw7ZubnRlIHF1YWRyYXRpc2NoIHNlaW4sIHdvaGxtw7ZnbGljaCBhYmVyIGF1Y2gKICAgIGxpbmVhcgoKRXJzdGVsbGVuIHdpciBkYXMgenVnZWjDtnJpZ2UgTW9kZWxsOgoKYGBge3J9CiMgTGluZWFyZXIgWnVzYW1tZW5oYW5nOgpsaW5fbW9kZWxsIDwtIGxtKE96b25lIH4gV2luZCwgZGF0YSA9IGFpcnF1YWxpdHkpCnN1bW1hcnkobGluX21vZGVsbCkKIyBRdWFkcmF0aXNjaGVyIFp1c2FtbWVuaGFuZzoKcXVhZF9tb2RlbGwgPC0gbG0oT3pvbmUgfiBXaW5kICogSShXaW5kICoqIDIpLCBkYXRhID0gYWlycXVhbGl0eSkKc3VtbWFyeShxdWFkX21vZGVsbCkKYGBgCgotICAgRGEgZGVyIFItUXVhZHJhdC1XZXJ0IHZvbiBkZW0gcXVhZHJhdGlzY2hlbiBadXNhbW1lbmhhbmcgZGV1dGxpY2gKICAgIGJlc3NlciBpc3QsIHNjaGVpbnQgZGllc2VyIHp1IGJldm9yenVnZW4KCkRhc3NlbGJlIGvDtm5uZW4gd2lyIG51biBhdWNoIGbDvHIgYFRlbXBgIHVuZCBgT3pvbmVgIG1hY2hlbjoKCmBgYHtyfQpnZ3Bsb3QoYWlycXVhbGl0eSwgYWVzKFRlbXAsIE96b25lKSkgKwoJZ2VvbV9wb2ludCgpICsKCWdlb21fc21vb3RoKCkKYGBgCgotICAgRXMgbGllZ3QgZWluIHN0ZWlnZW5kZXIgVHJlbmQgdm9yCi0gICBBdWNoIGhpZXIgaXN0IG5pY2h0IGdhbnogZWluZGV1dGlnLCBvYiBkaWVzZXIgbGluZWFyIGlzdAoKRXJzdGVsbGVuIHdpciB3aWVkZXIgUmVncmVzc2lvbnNtb2RlbGxlOgoKYGBge3J9CiMgTGluZWFyZXIgWnVzYW1tZW5oYW5nOgpsaW5fbW9kZWxsIDwtIGxtKE96b25lIH4gVGVtcCwgZGF0YSA9IGFpcnF1YWxpdHkpCnN1bW1hcnkobGluX21vZGVsbCkKIyBRdWFkcmF0aXNjaGVyIFp1c2FtbWVuaGFuZzoKcXVhZF9tb2RlbGwgPC0gbG0oT3pvbmUgfiBUZW1wICogSShUZW1wICoqIDIpLCBkYXRhID0gYWlycXVhbGl0eSkKc3VtbWFyeShxdWFkX21vZGVsbCkKYGBgCgotICAgRGFzIE1vZGVsbCBtaXQgZGVtIHF1YWRyYXRpc2NoZW4gS29lZmZpemllbnRlbiBpc3QgbmljaHQgZGV1dGxpY2gKICAgIGJlc3NlciBhbHMgZGFzIE1vZGVsbCBtaXQgZGVtIGxpbmVhcmVuIEtvZWZmaXppZW50ZW4KLSAgIERlbm5vY2ggc2VoZW4gd2lyIGF1Y2ggaGllciwgZGFzcyBzaWNoIGVpbiBncm/Dn2VyIFRlaWwgZGVyIFZhcmlhbnoKICAgIGR1cmNoIGRpZSBUZW1wZXJhdHVyIGVya2zDpHJlbiBrYW5uCi0gICBBbnNjaGF1bGljaCBiZWRldXRldCBkaWVzLCBkYXNzIHdpciBlaW5pZ2VybWHDn2VuIGd1dCB2b3JoZXJzYWdlbgogICAga8O2bm5lbiwgd2VsY2hlciBPem9uZ2VoYWx0IHZvcmxpZWd0LCB3ZW5uIHdpciBsZWRpZ2xpY2ggZGllCiAgICBUZW1wZXJhdHVyIG9kZXIgZGllIFdpbmRnZXNjaHdpbmRpZ2tlaXQga2VubmVuCgojIyMjIFp1c8OkdHpsaWNoZXMvQXVzc2NoYXUKCi0gICBBbmdlbm9tbWVuLCBTaWUgc2FnZW4sIGVzIGxpZWdlbiA4MCBGYWhyZW5oZWl0IChcfjI2LDYgR3JhZCBDZWxzaXVzKQogICAgdm9yLCBkYW5uIGvDtm5udGUgaWNoIElobmVuIGVpbmlnZXJtYcOfZW4gYWt1cmF0IHNhZ2VuLCB3aWUgaG9jaCBkZXIKICAgIE96b25nZWhhbHQgdW5nZWbDpGhyIHNlaW4gbXVzcyAoaW4gUm9vc2V2ZWx0IElzbGFuZCk6CgpgYGB7cn0KcHJlZGljdChxdWFkX21vZGVsbCwgbGlzdChUZW1wID0gODApKQpgYGAKClZlcmdsZWljaGVuIHdpciBkaWVzZW4gV2VydCAoNDIpIG1pdCBkZW4gdGF0c8OkY2hsaWNoIGdlbWVzc2VuZW4gV2VydGVuOgoKYGBge3J9CiMgV2VydGUgbmFoZSA4MCBGYWhyZW5oZWl0OgpuYWhlXzgwZiA8LSBhaXJxdWFsaXR5ICU+JQoJZmlsdGVyKGJldHdlZW4oVGVtcCwgNzUsIDg1KSAmICFpcy5uYShPem9uZSkpICU+JQoJc2VsZWN0KE96b25lLCBUZW1wKQpuYWhlXzgwZiAlPiUKCXN1bW1hcmlzZShtaXR0ZWx3ZXJ0ID0gbWVhbihPem9uZSkpCmdncGxvdChuYWhlXzgwZiwgYWVzKFRlbXAsIE96b25lKSkgKwoJZ2VvbV9qaXR0ZXIoKSArCglnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCglnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA0MiwgY29sb3IgPSAicmVkIikKYGBgCgotICAgV2lyIHNlaGVuLCBkYXNzIGRpZSBQdW5rdGUgaW0gTWl0dGVsd2VydCBuYWhlIGJlaSB1bnNlcmVyIFZvcmhlcnNhZ2UKICAgIDQyIGxpZWdlbgotICAgRGllIGluZGl2aWR1ZWxsZW4gUHVua3RlIGxpZWdlbiBhbGxlcmRpbmdzIGdlc3RyZXV0IHVtIGRpZSBMaW5pZQoKTGV0enRlbmRsaWNoIGvDtm5uZW4gd2lyIGF1Y2ggZWluIGV0d2FzIGtvbXBsZXhlcmVzIE1vZGVsbCBlcnN0ZWxsZW4sIHVtCmRlbiBXZXJ0IHZvbiBgT3pvbmVgIHp1IGJlcmVjaG5lbjoKCmBgYHtyfQptb2RlbGwgPC0gbG0oT3pvbmUgfiBXaW5kICsgSShXaW5kICoqIDIpICsgSShUZW1wICoqIDIpLCBkYXRhID0gYWlycXVhbGl0eSkKc3VtbWFyeShtb2RlbGwpCmBgYAoKLSAgIERhcyBNb2RlbGwgd2lyZCBkdXJjaCBqZWRlbiB3ZWl0ZXJlbiBQYXJhbWV0ZXIgYmVzc2VyIGluIHNlaW5lcgogICAgVm9yaGVyc2FnZQogICAgLSAgIERhcyBrYW5uIGplZG9jaCBkYXp1IGbDvGhyZW4sIGRhc3MgZGllIFZvcmhlcnNhZ2UgbnVyIG5vY2ggZsO8cgogICAgICAgIGRlbiBlaWdlbmVuIERhdGVuc2F0eiByZWxldmFudCB3aXJkIChPdmVyZml0dGluZykKICAgIC0gICBUZXN0ZW4gd2lyIGVpbmZhY2ggYWxsZSBtw7ZnbGljaGVuIEtvbWJpbmF0aW9uZW4gdW5kIGRlcgogICAgICAgIFItUXVhZHJhdC1XZXJ0IHdpcmQgc2VociBob2NoIHNlaW46CgpgYGB7cn0KbW9kZWxsIDwtIGxtKE96b25lIH4gV2luZCAqIEkoV2luZCAqKiAyKSAqIFRlbXAgKiBTb2xhci5SICogTW9udGgsIGRhdGEgPSBhaXJxdWFsaXR5KQpzdW1tYXJ5KG1vZGVsbCkKYGBgCgotICAgTWFuY2htYWwgZXJnaWJ0IGVzIFNpbm4sIGVpbmZhY2ggZWluIHBhYXIgWnVzYW1tZW5ow6RuZ2UgenUKICAgIGJldHJhY2h0ZW4gdW5kIFZlcm11dHVuZ2VuIGFuenVzdGVsbGVuLCB3ZWxjaGUgWnVzYW1tZW5ow6RuZ2UKICAgIHZvcmxpZWdlbgotICAgSW5uZXJoYWxiIGRlcyBNb2RlbGxzIHNlaGVuIHdpciBhbiBkZW0gdC1UZXN0IHZvbiBqZWRlbQogICAgS29lZmZpemllbnRlLCB3ZWxjaGVyIEtvZWZmaXppZW50IGVpbmVuIHNpZ25pZmlrYW50ZW4gRWluZmx1c3MgaGF0CgpVbSBlaW4gTW9kZWxsIHZpc3VlbGwgZGFyenVzdGVsbGVuLCBrYW5uIGRhcyBQYWtldCBgR0dhbGx5YCBtaXQgZGVuCkZ1bmt0aW9uZW4gYGdnbm9zdGljKClgIHVuZCBgZ2djb2VmX21vZGVsKClgIGhlbGZlbjoKClVtIHNpY2ggZGllIEtvZWZmaXppZW50ZW4gc2NobmVsbCBhbnp1c2NoYXVlbiAoYW5zdGVsbGUgZGVyIEFuc2ljaHQgaW4KZGVyIFItS29uc29sZSksIGthbm4gYGdnY29lZl9tb2RlbCgpYCB2ZXJ3ZW5kZXQgd2VyZGVuLiBIaWVyenUgd2lyZAp6dW7DpGNoc3QgYGJyb29tLmhlbHBlcnNgIGJlbsO2dGlndCAoYGluc3RhbGwucGFja2FnZXMoImJyb29tLmhlbHBlcnMiYCk6CgpgYGB7cn0KbGlicmFyeShicm9vbS5oZWxwZXJzKQptb2RlbGwgPC0gbG0oT3pvbmUgfiBUZW1wICsgV2luZCArIFNvbGFyLlIgKyBNb250aCwgZGF0YSA9IGFpcnF1YWxpdHkpCnN1bW1hcnkobW9kZWxsKQpnZ2NvZWZfbW9kZWwobW9kZWxsKQpgYGAKCi0gICBgQmV0YWAgemVpZ3QgaGllcmJlaSBkZW4gc3RhbmRhcmRpc2llcnRlbiBLb2VmZml6aWVudGVuIHVuZCBkZXNzZW4KICAgIFVuc2ljaGVyaGVpdCBhbgoKLSAgIGBUZW1wYCB1bmQgYFdpbmRgIGhhYmVuIGluIGRlbSBNb2RlbGwgZWluZW4gc2lnbmlmaWthbnRlbiBFaW5mbHVzcwogICAgKGtsZWluZXIgcC1XZXJ0KS4KCi0gICBgU29sYXIuUmAgaXN0IGViZW5mYWxscyBub2NoIHNpZ25pZmlrYW50LgoKLSAgIGBNb250aGAgaGluZ2VnZW4gc2NoZWludCBudXIgaW0gU2VwdGVtYmVyIGVpbmVuIHNpZ25pZmlrYW50ZW4KICAgIEVpbmZsdXNzIGF1ZiBgT3pvbmVgIHp1IGhhYmVuIHVuZCB3ZWlzdCBob2hlIFNjaHdhbmt1bmdlbiBhdWYuCgpVbSB3ZWl0ZXJlIEFzcGVrdGUgZGVzIE1vZGVsbHMgenUgYmV0cmFjaHRlbiB1bmQgdW0gc2VpbmUgR8O8dGUgenUKYmV1cnRlaWxlbiwga2FubiBkYW5hY2ggYGdnbm9zdGljKClgIHZlcndlbmRldCB3ZXJkZW46CgpgYGB7cn0KZ2dub3N0aWMobW9kZWxsLCBhZXMoY29sb3IgPSBNb250aCksIHByb2dyZXNzID0gRkFMU0UpCmBgYAoKLSAgIERpZSB2ZXJzY2hpZWRlbmVuIEJlcmVpY2hlIGF1ZiBkZXIgeS1BY2hzZSBzdGVsbGVuIGRhcyBNb2RlbGwgYXVmCiAgICB1bnRlcnNjaGllZGxpY2hlIEFydGVuIGRhciAoUmVzaWR1ZWVuLCBEaWFnb25hbGUgZGVyIEhhdC1NYXRyaXgsCiAgICBTY2jDpHR6dW5nIGRlciBTRCB2b24gUmVzaWR1ZWVuLCB3ZW5uIGRpZSBCZW9iYWNodHVuZyBhdXMgZGVtIE1vZGVsbAogICAgZ2Vub21tZW4gd3VyZGUsIENvb2tzLURpc3RhbnopCgotICAgRGVyIHdpY2h0aWdzdGUgQXNwZWt0IGxpZWd0IGRhcmluLCBkYXNzIEF1c3JlacOfZXIgYW5oYW5kIGRlcgogICAgVHJlbm5saW5pZW4gZXJrZW5uYmFyIHdlcmRlbgoKLSAgIEF1ZiBkZXIgeC1BY2hzZSBzaW5kIGRpZSBWYXJpYWJsZW4gZGVzIE1vZGVsbHMKCkZpbHRlcm4gd2lyIGRpZSB6d2VpIEF1c3JlacOfZXIgdm9uIGBPem9uZWAsIHVtIGdnZi4gZGFzIE1vZGVsbCB6dQp2ZXJiZXNzZXJuOgoKYGBge3J9CiMgRW50ZmVybmVuIHZvbiBob2hlbiBPem9ud2VydGVuCmFpcnF1YWxpdHlfZ2VmaWx0ZXJ0IDwtIGFpcnF1YWxpdHkgJT4lCglmaWx0ZXIoT3pvbmUgPCAxMjUpCm1vZGVsbCA8LSBsbShPem9uZSB+IFRlbXAgKyBXaW5kICsgTW9udGgsIGRhdGEgPSBhaXJxdWFsaXR5X2dlZmlsdGVydCkKc3VtbWFyeShtb2RlbGwpCmdnbm9zdGljKG1vZGVsbCwgYWVzKGNvbG9yID0gTW9udGgpLCBwcm9ncmVzcyA9IEZBTFNFKQpgYGAKCi0gICBEYXMgTW9kZWxsIHNjaGVpbnQgKGVpbiB3ZW5pZykgYmVzc2VyIGdld29yZGVuIHp1IHNlaW4K