Scalability
Scatter plots work well for hundreds of observations
Over-plotting becomes an issue once the number of observations gets
into tens of thousands.
For some output formats storage also becomes an issue as the number
of points plotted increases.
Some simulated data:
n <- 50000
## n50K <- data.frame(x = rnorm(n), y = rnorm(n))
x <- rnorm(n)
y <- x + 0.4 * x ^ 2 + rnorm(n)
y <- y / sd(y)
n50K <- data.frame(x = x, y = y)
n10K <- n50K[1 : 10000, ]
n1K <- n50K[1 : 1000, ]
p1 <- ggplot(n1K, aes(x, y)) +
geom_point() +
coord_equal() +
ggtitle(sprintf("%d Points", nrow(n1K)))
p2 <- ggplot(n10K, aes(x, y)) +
geom_point() +
coord_equal() +
ggtitle(sprintf("%d Points", nrow(n10K)))
library(patchwork)
p1 | p2
Some Simple Options
Simple options to address over-plotting:
sampling
reducing the point size
alpha blending
With no adjustments:
p2 + xlim(c(-5, 5)) + ylim(c(-3, 10))
Reducing the point size helps when the number of points is in the low
tens of thousands:
ggplot(n10K, aes(x, y)) +
geom_point(size = 0.1) +
coord_equal() +
xlim(c(-5, 5)) +
ylim(c(-3, 10)) +
ggtitle(sprintf("%d Points", nrow(n10K)))
Alpha blending can also be effective, on its own or in combination
with point size adjustment:
ggplot(n50K, aes(x, y)) +
geom_point(alpha = 0.01, size = 0.5) +
coord_equal() +
xlim(c(-5, 5)) +
ylim(c(-3, 10)) +
ggtitle(sprintf("%d Points", nrow(n50K)))
Experimentation is usually needed to identify a good point size and
alpha level.
Both alpha blending and point size reduction inhibit the use of color
for encoding a grouping variable.
The best choices may vary from one output format to another.
Density Estimation Methods
Some methods based on density estimation or binning:
Density Contours
A 2D density estimate can be displayed in terms of its
contours , or level curves .
p <- ggplot(n50K, aes(x, y)) +
coord_equal() +
ggtitle(sprintf("%d Points", nrow(n50K)))
pp <- geom_point(alpha = 0.01, size = 0.5)
dd <- geom_density_2d(color = "red")
p + dd
These are the contours of the estimated density surface:
d <- MASS::kde2d(n50K$x, n50K$y, n = 50)
v <- expand.grid(x = d$x, y = d$y)
v$z <- as.numeric(d$z)
lattice::wireframe(z ~ x + y,
data = v,
screen = list(z = 70,
x = -60))
2D density estimate contours can be superimposed on a set of points
or placed beneath a set of points:
p1 <- p + list(pp, dd)
p2 <- p + list(dd, pp)
p1 | p2
Density Levels Encoded with Point Size
Density levels can also be encoded in point size in a grid of
points:
p + stat_density_2d(
aes(size = after_stat(density)),
geom = "point",
n = 30,
contour = FALSE) +
scale_size(range = c(0, 6))
This scales well computationally
It does not easily support encoding a grouping with color or
shape.
It introduces some distracting visual artifacts that are related to
some optical illusions seen
earlier .
This effect can be reduced somewhat by jittering:
jit_amt <- 0.03
p + stat_density_2d(
aes(size = after_stat(density)),
geom = "point",
position = position_jitter(jit_amt,
jit_amt),
n = 30, contour = FALSE) +
scale_size(range = c(0, 6))
Another option is to display the density using a tile map:
p + stat_density_2d(
aes(fill = after_stat(density)),
geom="tile", n = 30, contour = FALSE)
The rectangular grid creates distracting visual artifacts.
An alternative is to use a hexagonal grid.
Hexagonal Binning
Hexagonal binning divides the plane into hexagonal bins and displays
the number of points in each bin:
p + geom_hex()
This also scales very well to larger data sets.
The default color scheme seems less than ideal.
An alternative fill color choice:
p + geom_hex() +
scale_fill_gradient(low = "gray",
high = "black")
Again it is possible to use a scaled point representation:
p + stat_bin_hex(
geom = "point",
aes(size = after_stat(density)),
fill = NA)
This representation still produces some visual distractions, but less
than a rectangular grid.
Again, jittering can help:
jit_amt <- 0.05
p + stat_bin_hex(
aes(size = after_stat(density)),
geom = "point",
position = position_jitter(jit_amt,
jit_amt),
fill = NA)
Other Density Plots
The hdrcde package computes and plots density contours
containing specified proportions of the data.
The hdr.boxplot.2d function plots these contours and
shows the points not in the outermost contour:
library(hdrcde)
with(n50K,
hdr.boxplot.2d(x, y,
prob = c(0.1, 0.5, 0.75, 0.9)))
Some Enhancements
Encoding Additional Variables
Scatter plots can encode information about other variables using
symbol color
symbol size
symbol shape
An example using the mpg data set:
p <- ggplot(mpg, aes(cty, hwy,
color = factor(cyl)))
p + geom_point(aes(size = drv))
## Warning: Using size for a discrete variable is not advised.
Some encodings work better than others:
Size is not a good fit for a discrete variable
Even though cyl is numeric, it is best encoded as
categorical.
Size and color interfere with each other as the color of smaller
objects is harder to perceive.
Similarly, size and shape interfere with each other.
Using shape instead of size for
drv:
p + geom_point(aes(shape = drv))
Increasing the size makes shapes and the colors easier to
distinguish:
p + geom_point(aes(shape = drv), size = 3)
Marginal Plots
The ggMargin function in the ggExtra
package attaches marginal histograms to (some) plots produced by
ggplot:
library(ggExtra)
p <- ggplot(n50K, aes(x, y)) +
geom_point(alpha = 0.01, size = 0.5)
ggMarginal(p,
type = "histogram",
bins = 50,
fill = "lightgrey")
The default type is "density" for a marginal density
plot:
ggMarginal(p, fill = "lightgrey")
For data sets of more modest size rug plots along the axes can be
useful:
ggplot(faithful,
aes(eruptions, waiting)) +
geom_point() +
geom_rug(alpha = 0.2)
Adding a Smooth Curve
When the variables on the \(y\) axis
is a response variable a useful enhancement is to add a smooth curve to
a plot.
The default method is a form of local averaging, and includes a
representation of uncertainty:
p1 <- ggplot(faithful, aes(eruptions, waiting)) +
geom_point() +
geom_smooth() +
ggtitle("Old Faithful Eruptions")
p2 <- ggplot(n50K, aes(x, y)) +
pp +
geom_smooth() +
ggtitle(sprintf("%d Points", nrow(n50K)))
p1 | p2
Conditioning
When the \(y\) variable is a
response it can also be useful to explore the conditional distribution
of \(y\) given different values of the
\(x\) variable.
One way to do this is to focus on the data for narrow ranges of the
conditioning variable.
Two approaches for creating groups to focus on:
Rounding to an integer or using
cut_width(x, width = 1, center = 0)
produces these bins:
p <- ggplot(n50K, aes(x, y)) +
geom_point(alpha = 0.05, size = 0.5)
p + geom_vline(xintercept =
seq(-3.5, 4.5, by = 1),
linetype = 2,
color = "red") +
scale_x_continuous(breaks = seq(-3, 4),
labels = as.character(seq(-3, 4)))
Data within each group can then be shown using box plots (you need to
use the group aesthetic to plot on a continuous axis):
n50K_trm <- filter(n50K, x > -2.5, x < 2.5)
mutate(n50K_trm, xrnd = round(x)) |>
ggplot(aes(x, y)) +
geom_boxplot(aes(group = xrnd))
## Warning: Orientation is not uniquely specified when both the x and y aesthetics are
## continuous. Picking default orientation 'x'.
Using cut_width and violin plots:
mutate(n50K_trm,
xcut = cut_width(x,
width = 1,
center = 0)) |>
group_by(xcut) |>
mutate(xmed = median(x)) |>
ungroup() |>
ggplot(aes(x, y)) +
geom_violin(aes(group = xmed),
draw_quantiles = 0.5)
## Warning: The `draw_quantiles` argument of `geom_violin()` is deprecated as of ggplot2
## 4.0.0.
## ℹ Please use the `quantiles.linetype` argument instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Another option for visualizing the conditional distributions is
faceted density plots:
mutate(n50K_trm, xrnd = round(x)) |>
ggplot(aes(x)) +
geom_density(aes(y)) +
facet_wrap(~ xrnd, ncol = 1)
Density ridges work as well:
library(ggridges)
mutate(n50K_trm, xrnd = round(x)) |>
ggplot(aes(y, xrnd, group = xrnd)) +
geom_density_ridges(quantile_lines = TRUE,
quantiles = 2)
Using a larger set of narrower bins centered on multiples of 1/2:
mutate(n50K_trm, xrnd = round(2 * x) / 2) |>
ggplot(aes(y, xrnd, group = xrnd)) +
geom_density_ridges(quantile_lines = TRUE,
quantiles = 2)
Sometimes it is useful to use a small number of narrow bins:
rdata <- data.frame(xmin = (-2 : 2) - 0.1,
xmax = (-2 : 2) + 0.1,
ymin = -Inf,
ymax = Inf)
p1 <- p + geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
data = rdata,
inherit.aes = FALSE,
fill = "red",
alpha = 0.2)
p2 <- mutate(n50K_trm, xrnd = round(x)) |>
filter(abs(x - xrnd) < 0.1) |>
ggplot(aes(y, xrnd, group = xrnd)) +
geom_density_ridges(quantile_lines = TRUE, quantiles = 2)
p1 | p2
For examining conditional distributions, bins need to be:
For smaller data sets it can also be useful to allow bins to
overlap.
The lattice functions shingle and
equal.count create shingles that can be used in
lattice-style faceting.
Example: Diamond Prices
The diamonds data set contains prices and other
attributes for 53,940 diamonds.
The cut variable indicates the quality of a
diamond’s cut.
You might expect ‘better’ cuts to cost more, but that is not true on
average:
mm <- group_by(diamonds, cut) |>
summarize(med_price = median(price),
avg_price = mean(price),
n = length(price)) |>
pivot_longer(2 : 3,
names_to = "which",
values_to = "price")
ggplot(mm, aes(x = price,
y = cut,
color = which)) +
geom_point(, size = 3)
The proportion of diamonds at each cut level is also
perhaps surprising:
ggplot(diamonds) +
geom_bar(aes(x = cut),
fill = "deepskyblue3")
And the price distributions within each cut level differ
in shape:
ggplot(diamonds, aes(x = price, y = cut)) +
geom_violin() +
geom_point(aes(color = which), data = mm)
Another important factor is the size, measured in
carat.
ggplot(diamonds, aes(x = carat, y = cut)) +
geom_violin()
Histograms with a narrow bin width may help understand the unusual
shapes.
ggplot(diamonds) +
geom_histogram(aes(carat),
binwidth = 0.01)
Try faceting on cut:
ggplot(diamonds) +
geom_histogram(aes(carat),
binwidth = 0.01) +
facet_wrap(~ cut, ncol = 1)
To focus on the shapes of the conditional distributions of carat
given cut use y = after_stat(density):
ggplot(diamonds) +
geom_histogram(
aes(x = carat,
y = after_stat(density)),
binwidth = 0.01) +
facet_wrap(~ cut, ncol = 1)
Alternatively, you can facet with scales = "free_y":
ggplot(diamonds) +
geom_histogram(aes(carat),
binwidth = 0.01) +
facet_wrap(~ cut,
scales = "free_y",
ncol = 1)
Larger diamonds have a higher price:
ggplot(diamonds, aes(x = carat, y = price)) +
geom_point()
There is a lot of over-plotting, so a good opportunity to try some of
the techniques we have learned.
Some explorations:
Reduced point size:
p <- ggplot(diamonds,
aes(x = carat, y = price))
p + geom_point(size = 0.1)
Reduced point size and alpha level:
p + geom_point(size = 0.1, alpha = 0.1)
Try log scale for price:
p + geom_point(size = 0.1, alpha = 0.1) +
scale_y_log10()
Log scale for both:
p + geom_point(size = 0.1, alpha = 0.1) +
scale_x_log10() +
scale_y_log10()
Add a smooth:
p + geom_point(size = 0.1, alpha = 0.1) +
geom_smooth() +
scale_x_log10() +
scale_y_log10()
Separate smooths for each cut:
p + geom_point(size = 0.1, alpha = 0.1) +
geom_smooth(aes(color = cut)) +
scale_x_log10() +
scale_y_log10()
Exploring a sample:
d500 <- sample_n(diamonds, 500)
Distribution of cut in the sample:
ggplot(d500, aes(x = cut)) +
geom_bar()
ggplot(d500, aes(x = carat, y = price)) +
geom_point()
You can also take a stratified sample stratified on cut
using group_by, sample_frac, and
ungroup.
Explorations using facets:
p0 <- ggplot(diamonds, aes(x = carat, y = price))
dNoCut <- mutate(diamonds, cut = NULL)
p1 <- p0 + geom_point(alpha = 0.01, color = "grey", data = dNoCut)
p <- p1 + geom_point(color = "red", size = 1, alpha = 0.05)
## p + facet_wrap(~ cut)
p + facet_wrap(~ cut) + scale_x_log10() + scale_y_log10()
Facets with a sample:
p500 <- p1 +
geom_point(data = d500,
color = "red",
size = 1)
## p500
## p500 + facet_wrap(~ cut)
p500 +
facet_wrap(~ cut) +
scale_x_log10() +
scale_y_log10()
Muted data with smooths:
p1 +
geom_smooth(aes(color = cut),
se = FALSE) +
scale_x_log10() +
scale_y_log10()
Conditioning, carat values near 1, 1.5, or 2:
drnd <- mutate(diamonds,
crnd = round(2 * carat) / 2)
## Look at a bar chart of count(drnd, crnd)
## Drop higher carat values from density ridges
## map cut to color or fill (need to use group = interaction(crnd, cut))
## try log scale for price
filter(drnd,
crnd <= 2, crnd >= 1,
carat >= crnd, carat <= crnd + 0.1) |>
ggplot(aes(x = price, y = cut)) +
geom_density_ridges(quantile_lines = TRUE,
quantiles = 2) +
facet_wrap(~crnd, ncol = 1)
Exercises
A plot of arrival delay against departure delay for the NYC
flight data shows a lot of over-plotting, even for a 10% sample.
library(dplyr)
library(ggplot2)
library(nycflights13)
fl <- filter(flights, dep_delay < 120) |>
sample_frac(0.1)
p <- ggplot(fl, aes(x = dep_delay, arr_delay)) +
geom_point()
p
## Warning: Removed 101 rows containing missing values or values outside the scale range
## (`geom_point()`).
This masks the fact that three quarters of the flights in this sample
have departure delays of less than 10 minutes. Superimposing density
contours is one way to address this issue.
Which of the following adds four red density contours to the
plot?
p + geom_density_2d(color = "red")
p + geom_density_2d(bins = 4, color = "red")
p + geom_hex(bins = 5)
p + geom_density_2d(bins = 5)
The diamonds data set is large enough for a scatter
plot of price against carat to suffer from a
significant amount of over-plotting. With p created as
library(ggplot2)
p <- ggplot(diamonds, aes(x = carat, y = price))
which of the following is the best choice to address the
over-plotting issue?
p + geom_point()
p + geom_point(size = 0.5)
p + geom_point(size = 0.1, alpha = 0.1)
p + geom_point(position = "jitter")
Consider the code
library(dplyr)
library(ggplot2)
filter(diamonds, carat < 3) |>
mutate(crnd = round(carat)) |>
filter(---) |>
ggplot(aes(x = price, y = crnd, group = crnd)) +
geom_density_ridges()
Which replacement for --- produces a plot showing the
conditional density of price given carat for
carat values near one and the conditional density of
price given carat for carat
values near two?
abs(carat - crnd) < 0.1
carat < 0.1
abs(crnd) < 1
carat + crnd < 2
LS0tCnRpdGxlOiAiU2NhdHRlciBQbG90IFNjYWxhYmlsaXR5IGFuZCBFbmhhbmNlbWVudHMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0ic3RhdDQ1ODAuY3NzIiB0eXBlPSJ0ZXh0L2NzcyIgLz4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4gLnJlbWFyay1jb2RlIHsgZm9udC1zaXplOiA4NSU7IH0gPC9zdHlsZT4KPCEtLSB0aXRsZSBiYXNlZCBvbiBXaWxrZSdzIGNoYXB0ZXIgLS0+CgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnNvdXJjZShoZXJlOjpoZXJlKCJzZXR1cC5SIikpCmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZSA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGggPSA2LCBmaWcuYWxpZ24gPSAiY2VudGVyIikKCnNldC5zZWVkKDEyMzQ1KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkocGF0Y2h3b3JrKQpzb3VyY2UoaGVyZTo6aGVyZSgiZGF0YXNldHMuUiIpKQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpICsKICAgICAgICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgICAgICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICJncmV5MzAiLCBmaWxsID0gTkEpKSkKYGBgCgojIyBTY2FsYWJpbGl0eQoKU2NhdHRlciBwbG90cyB3b3JrIHdlbGwgZm9yIGh1bmRyZWRzIG9mIG9ic2VydmF0aW9ucwoKT3Zlci1wbG90dGluZyBiZWNvbWVzIGFuIGlzc3VlIG9uY2UgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgZ2V0cwppbnRvIHRlbnMgb2YgdGhvdXNhbmRzLgoKRm9yIHNvbWUgb3V0cHV0IGZvcm1hdHMgc3RvcmFnZSBhbHNvIGJlY29tZXMgYW4gaXNzdWUgYXMgdGhlIG51bWJlciBvZgpwb2ludHMgcGxvdHRlZCBpbmNyZWFzZXMuCgpTb21lIHNpbXVsYXRlZCBkYXRhOgoKYGBge3Igc2ltdWxhdGVkLWRhdGEtcGxvdHMsIGV2YWwgPSBGQUxTRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cm4gPC0gNTAwMDAKIyMgbjUwSyA8LSBkYXRhLmZyYW1lKHggPSBybm9ybShuKSwgeSA9IHJub3JtKG4pKQp4IDwtIHJub3JtKG4pCnkgPC0geCArIDAuNCAqIHggXiAyICsgcm5vcm0obikKeSA8LSB5IC8gc2QoeSkKbjUwSyA8LSBkYXRhLmZyYW1lKHggPSB4LCB5ID0geSkKCm4xMEsgPC0gbjUwS1sxIDogMTAwMDAsIF0KbjFLIDwtIG41MEtbMSA6IDEwMDAsIF0KcDEgPC0gZ2dwbG90KG4xSywgYWVzKHgsIHkpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgY29vcmRfZXF1YWwoKSArCiAgICBnZ3RpdGxlKHNwcmludGYoIiVkIFBvaW50cyIsIG5yb3cobjFLKSkpCnAyIDwtIGdncGxvdChuMTBLLCBhZXMoeCwgeSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBjb29yZF9lcXVhbCgpICsKICAgIGdndGl0bGUoc3ByaW50ZigiJWQgUG9pbnRzIiwgbnJvdyhuMTBLKSkpCmxpYnJhcnkocGF0Y2h3b3JrKQpwMSB8IHAyCmBgYAoKYGBge3Igc2ltdWxhdGVkLWRhdGEtcGxvdHMsIGZpZy53aWR0aCA9IDgsIGVjaG8gPSBGQUxTRX0KYGBgCgoKIyMgU29tZSBTaW1wbGUgT3B0aW9ucwoKU2ltcGxlIG9wdGlvbnMgdG8gYWRkcmVzcyBvdmVyLXBsb3R0aW5nOgoKKiBzYW1wbGluZwoKKiByZWR1Y2luZyB0aGUgcG9pbnQgc2l6ZQoKKiBhbHBoYSBibGVuZGluZwoKV2l0aCBubyBhZGp1c3RtZW50czoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcDIgKyB4bGltKGMoLTUsIDUpKSArIHlsaW0oYygtMywgMTApKQpgYGAKClJlZHVjaW5nIHRoZSBwb2ludCBzaXplIGhlbHBzIHdoZW4gdGhlIG51bWJlciBvZiBwb2ludHMgaXMgaW4gdGhlIGxvdwp0ZW5zIG9mIHRob3VzYW5kczoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KG4xMEssIGFlcyh4LCB5KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMC4xKSArCiAgICBjb29yZF9lcXVhbCgpICsKICAgIHhsaW0oYygtNSwgNSkpICsKICAgIHlsaW0oYygtMywgMTApKSArCiAgICBnZ3RpdGxlKHNwcmludGYoIiVkIFBvaW50cyIsIG5yb3cobjEwSykpKQpgYGAKCkFscGhhIGJsZW5kaW5nIGNhbiBhbHNvIGJlIGVmZmVjdGl2ZSwgb24gaXRzIG93biBvciBpbiBjb21iaW5hdGlvbgp3aXRoIHBvaW50IHNpemUgYWRqdXN0bWVudDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KG41MEssIGFlcyh4LCB5KSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMDEsIHNpemUgPSAwLjUpICsKICAgIGNvb3JkX2VxdWFsKCkgKwogICAgeGxpbShjKC01LCA1KSkgKwogICAgeWxpbShjKC0zLCAxMCkpICsKICAgIGdndGl0bGUoc3ByaW50ZigiJWQgUG9pbnRzIiwgbnJvdyhuNTBLKSkpCmBgYAoKRXhwZXJpbWVudGF0aW9uIGlzIHVzdWFsbHkgbmVlZGVkIHRvIGlkZW50aWZ5IGEgZ29vZCBwb2ludCBzaXplIGFuZAphbHBoYSBsZXZlbC4KCkJvdGggYWxwaGEgYmxlbmRpbmcgYW5kIHBvaW50IHNpemUgcmVkdWN0aW9uIGluaGliaXQgdGhlIHVzZSBvZiBjb2xvcgpmb3IgZW5jb2RpbmcgYSBncm91cGluZyB2YXJpYWJsZS4KClRoZSBiZXN0IGNob2ljZXMgbWF5IHZhcnkgZnJvbSBvbmUgb3V0cHV0IGZvcm1hdCB0byBhbm90aGVyLgoKCiMjIERlbnNpdHkgRXN0aW1hdGlvbiBNZXRob2RzCgpTb21lIG1ldGhvZHMgYmFzZWQgb24gZGVuc2l0eSBlc3RpbWF0aW9uIG9yIGJpbm5pbmc6CgoqIERpc3BsYXlpbmcgY29udG91cnMgb2YgYSAyRCBkZW5zaXR5IGVzdGltYXRlLgoKKiBFbmNvZGluZyBkZW5zaXR5IGVzdGltYXRlcyBpbiBwb2ludCBzaXplLgoKKiBIZXhhZ29uYWwgYmlubmluZy4KCgojIyMgRGVuc2l0eSBDb250b3VycwoKQSAyRCBkZW5zaXR5IGVzdGltYXRlIGNhbiBiZSBkaXNwbGF5ZWQgaW4gdGVybXMgb2YgaXRzIF9jb250b3Vyc18sIG9yCl9sZXZlbCBjdXJ2ZXNfLgoKYGBge3IsIGZpZy5oZWlnaHQgPSA0Ljc1LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCA8LSBnZ3Bsb3QobjUwSywgYWVzKHgsIHkpKSArCiAgICBjb29yZF9lcXVhbCgpICsKICAgIGdndGl0bGUoc3ByaW50ZigiJWQgUG9pbnRzIiwgbnJvdyhuNTBLKSkpCnBwIDwtIGdlb21fcG9pbnQoYWxwaGEgPSAwLjAxLCBzaXplID0gMC41KQpkZCA8LSBnZW9tX2RlbnNpdHlfMmQoY29sb3IgPSAicmVkIikKcCArIGRkCmBgYAoKVGhlc2UgYXJlIHRoZSBjb250b3VycyBvZiB0aGUgZXN0aW1hdGVkIGRlbnNpdHkgc3VyZmFjZToKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNC43NSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmQgPC0gTUFTUzo6a2RlMmQobjUwSyR4LCBuNTBLJHksIG4gPSA1MCkKdiA8LSBleHBhbmQuZ3JpZCh4ID0gZCR4LCB5ID0gZCR5KQp2JHogPC0gYXMubnVtZXJpYyhkJHopCmxhdHRpY2U6OndpcmVmcmFtZSh6IH4geCArIHksCiAgICAgICAgICAgICAgICAgICBkYXRhID0gdiwKICAgICAgICAgICAgICAgICAgIHNjcmVlbiA9IGxpc3QoeiA9IDcwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gLTYwKSkKYGBgCgoyRCBkZW5zaXR5IGVzdGltYXRlIGNvbnRvdXJzIGNhbiBiZSBzdXBlcmltcG9zZWQgb24gYSBzZXQgb2YgcG9pbnRzIG9yCnBsYWNlZCBiZW5lYXRoIGEgc2V0IG9mIHBvaW50czoKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNiwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAxIDwtIHAgKyBsaXN0KHBwLCBkZCkKcDIgPC0gcCArIGxpc3QoZGQsIHBwKQpwMSB8IHAyCmBgYAoKCiMjIyBEZW5zaXR5IExldmVscyBFbmNvZGVkIHdpdGggUG9pbnQgU2l6ZQoKRGVuc2l0eSBsZXZlbHMgY2FuIGFsc28gYmUgZW5jb2RlZCBpbiBwb2ludCBzaXplIGluIGEgZ3JpZCBvZiBwb2ludHM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAgKyBzdGF0X2RlbnNpdHlfMmQoCiAgICAgICAgYWVzKHNpemUgPSBhZnRlcl9zdGF0KGRlbnNpdHkpKSwKICAgICAgICBnZW9tID0gInBvaW50IiwKICAgICAgICBuID0gMzAsCiAgICAgICAgY29udG91ciA9IEZBTFNFKSArCiAgICBzY2FsZV9zaXplKHJhbmdlID0gYygwLCA2KSkKYGBgCgpUaGlzIHNjYWxlcyB3ZWxsIGNvbXB1dGF0aW9uYWxseQoKSXQgZG9lcyBub3QgZWFzaWx5IHN1cHBvcnQgZW5jb2RpbmcgYSBncm91cGluZyB3aXRoIGNvbG9yIG9yIHNoYXBlLgoKSXQgaW50cm9kdWNlcyBzb21lIGRpc3RyYWN0aW5nIHZpc3VhbCBhcnRpZmFjdHMgdGhhdCBhcmUgcmVsYXRlZCB0bwpzb21lIG9wdGljYWwgaWxsdXNpb25zIFtzZWVuCmVhcmxpZXJdKHBlcmNlcC5odG1sI3NvbWUtb3B0aWNhbC1pbGx1c2lvbnMpLgoKVGhpcyBlZmZlY3QgY2FuIGJlIHJlZHVjZWQgc29tZXdoYXQgYnkgaml0dGVyaW5nOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifSAgICAgCmppdF9hbXQgPC0gMC4wMwpwICsgc3RhdF9kZW5zaXR5XzJkKAogICAgICAgIGFlcyhzaXplID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSksCiAgICAgICAgZ2VvbSA9ICJwb2ludCIsCiAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIoaml0X2FtdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqaXRfYW10KSwKICAgICAgICBuID0gMzAsIGNvbnRvdXIgPSBGQUxTRSkgKwogICAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMCwgNikpCmBgYAoKQW5vdGhlciBvcHRpb24gaXMgdG8gZGlzcGxheSB0aGUgZGVuc2l0eSB1c2luZyBhIHRpbGUgbWFwOgoKYGBge3J9CnAgKyBzdGF0X2RlbnNpdHlfMmQoCiAgICAgICAgYWVzKGZpbGwgPSBhZnRlcl9zdGF0KGRlbnNpdHkpKSwKICAgICAgICBnZW9tPSJ0aWxlIiwgbiA9IDMwLCBjb250b3VyID0gRkFMU0UpCmBgYAoKVGhlIHJlY3Rhbmd1bGFyIGdyaWQgY3JlYXRlcyBkaXN0cmFjdGluZyB2aXN1YWwgYXJ0aWZhY3RzLgoKQW4gYWx0ZXJuYXRpdmUgaXMgdG8gdXNlIGEgaGV4YWdvbmFsIGdyaWQuCgoKIyMjIEhleGFnb25hbCBCaW5uaW5nCgpIZXhhZ29uYWwgYmlubmluZyBkaXZpZGVzIHRoZSBwbGFuZSBpbnRvIGhleGFnb25hbCBiaW5zIGFuZCBkaXNwbGF5cwp0aGUgbnVtYmVyIG9mIHBvaW50cyBpbiBlYWNoIGJpbjoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCArIGdlb21faGV4KCkKYGBgCgpUaGlzIGFsc28gc2NhbGVzIHZlcnkgd2VsbCB0byBsYXJnZXIgZGF0YSBzZXRzLgoKVGhlIGRlZmF1bHQgY29sb3Igc2NoZW1lIHNlZW1zIGxlc3MgdGhhbiBpZGVhbC4KCkFuIGFsdGVybmF0aXZlIGZpbGwgY29sb3IgY2hvaWNlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwICsgZ2VvbV9oZXgoKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJncmF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJibGFjayIpCmBgYAoKQWdhaW4gaXQgaXMgcG9zc2libGUgdG8gdXNlIGEgc2NhbGVkIHBvaW50IHJlcHJlc2VudGF0aW9uOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwICsgc3RhdF9iaW5faGV4KAogICAgICAgIGdlb20gPSAicG9pbnQiLAogICAgICAgIGFlcyhzaXplID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSksCiAgICAgICAgZmlsbCA9IE5BKQpgYGAKClRoaXMgcmVwcmVzZW50YXRpb24gc3RpbGwgcHJvZHVjZXMgc29tZSB2aXN1YWwgZGlzdHJhY3Rpb25zLCBidXQgbGVzcwp0aGFuIGEgcmVjdGFuZ3VsYXIgZ3JpZC4KCkFnYWluLCBqaXR0ZXJpbmcgY2FuIGhlbHA6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmppdF9hbXQgPC0gMC4wNQpwICsgc3RhdF9iaW5faGV4KAogICAgICAgIGFlcyhzaXplID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSksCiAgICAgICAgZ2VvbSA9ICJwb2ludCIsCiAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIoaml0X2FtdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqaXRfYW10KSwKICAgICAgICBmaWxsID0gTkEpCmBgYAoKCiMjIyBPdGhlciBEZW5zaXR5IFBsb3RzCgpUaGUgYGhkcmNkZWAgcGFja2FnZSBjb21wdXRlcyBhbmQgcGxvdHMgZGVuc2l0eSBjb250b3VycyBjb250YWluaW5nCnNwZWNpZmllZCBwcm9wb3J0aW9ucyBvZiB0aGUgZGF0YS4KClRoZSBgaGRyLmJveHBsb3QuMmRgIGZ1bmN0aW9uIHBsb3RzIHRoZXNlIGNvbnRvdXJzIGFuZCBzaG93cyB0aGUKcG9pbnRzIG5vdCBpbiB0aGUgb3V0ZXJtb3N0IGNvbnRvdXI6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoaGRyY2RlKQp3aXRoKG41MEssCiAgICAgaGRyLmJveHBsb3QuMmQoeCwgeSwKICAgICAgICAgICAgICAgICAgICBwcm9iID0gYygwLjEsIDAuNSwgMC43NSwgMC45KSkpCmBgYAo8IS0tCkl0IHNob3VsZCBiZSBwb3NzaWJsZSB0byBzZWxlY3QgdGhlIGNvbnRvdXIgbGV2ZWxzIHVzZWQgaW4gYGdncGxvdGAgaW4gYQpzaW1pbGFyIHdheS4KLS0+CgoKIyMgU29tZSBFbmhhbmNlbWVudHMKCgojIyMgRW5jb2RpbmcgQWRkaXRpb25hbCBWYXJpYWJsZXMKClNjYXR0ZXIgcGxvdHMgY2FuIGVuY29kZSBpbmZvcm1hdGlvbiBhYm91dCBvdGhlciB2YXJpYWJsZXMgdXNpbmcKCiogc3ltYm9sIGNvbG9yCgoqIHN5bWJvbCBzaXplCgoqIHN5bWJvbCBzaGFwZQoKQW4gZXhhbXBsZSB1c2luZyB0aGUgYG1wZ2AgZGF0YSBzZXQ6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAgPC0gZ2dwbG90KG1wZywgYWVzKGN0eSwgaHd5LAogICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGZhY3RvcihjeWwpKSkKcCArIGdlb21fcG9pbnQoYWVzKHNpemUgPSBkcnYpKQpgYGAKClNvbWUgZW5jb2RpbmdzIHdvcmsgYmV0dGVyIHRoYW4gb3RoZXJzOgoKKiBTaXplIGlzIG5vdCBhIGdvb2QgZml0IGZvciBhIGRpc2NyZXRlIHZhcmlhYmxlCgoqIEV2ZW4gdGhvdWdoIGBjeWxgIGlzIG51bWVyaWMsIGl0IGlzIGJlc3QgZW5jb2RlZCBhcyBjYXRlZ29yaWNhbC4KCiogU2l6ZSBhbmQgY29sb3IgaW50ZXJmZXJlIHdpdGggZWFjaCBvdGhlciBhcyB0aGUgY29sb3Igb2Ygc21hbGxlcgogIG9iamVjdHMgaXMgaGFyZGVyIHRvIHBlcmNlaXZlLgoKKiBTaW1pbGFybHksIHNpemUgYW5kIHNoYXBlIGludGVyZmVyZSB3aXRoIGVhY2ggb3RoZXIuCgpVc2luZyBgc2hhcGVgIGluc3RlYWQgb2YgYHNpemVgIGZvciBgZHJ2YDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gZHJ2KSkKYGBgCgpJbmNyZWFzaW5nIHRoZSBzaXplIG1ha2VzIHNoYXBlcyBhbmQgdGhlIGNvbG9ycyBlYXNpZXIgdG8gZGlzdGluZ3Vpc2g6CgpgYGB7cn0KcCArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gZHJ2KSwgc2l6ZSA9IDMpCmBgYAoKCiMjIyBNYXJnaW5hbCBQbG90cwoKVGhlIGBnZ01hcmdpbmAgZnVuY3Rpb24gaW4gdGhlIGBnZ0V4dHJhYCBwYWNrYWdlIGF0dGFjaGVzIG1hcmdpbmFsCmhpc3RvZ3JhbXMgdG8gKHNvbWUpIHBsb3RzIHByb2R1Y2VkIGJ5IGBnZ3Bsb3RgOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KGdnRXh0cmEpCnAgPC0gZ2dwbG90KG41MEssIGFlcyh4LCB5KSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMDEsIHNpemUgPSAwLjUpCmdnTWFyZ2luYWwocCwKICAgICAgICAgICB0eXBlID0gImhpc3RvZ3JhbSIsCiAgICAgICAgICAgYmlucyA9IDUwLAogICAgICAgICAgIGZpbGwgPSAibGlnaHRncmV5IikKYGBgCgpUaGUgZGVmYXVsdCB0eXBlIGlzIGAiZGVuc2l0eSJgIGZvciBhIG1hcmdpbmFsIGRlbnNpdHkgcGxvdDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dNYXJnaW5hbChwLCBmaWxsID0gImxpZ2h0Z3JleSIpCmBgYAoKRm9yIGRhdGEgc2V0cyBvZiBtb3JlIG1vZGVzdCBzaXplIHJ1ZyBwbG90cyBhbG9uZyB0aGUgYXhlcyBjYW4gYmUKdXNlZnVsOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoZmFpdGhmdWwsCiAgICAgICBhZXMoZXJ1cHRpb25zLCB3YWl0aW5nKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fcnVnKGFscGhhID0gMC4yKQpgYGAKCgojIyMgQWRkaW5nIGEgU21vb3RoIEN1cnZlCgpXaGVuIHRoZSB2YXJpYWJsZXMgb24gdGhlICR5JCBheGlzIGlzIGEgcmVzcG9uc2UgdmFyaWFibGUgYSB1c2VmdWwKZW5oYW5jZW1lbnQgaXMgdG8gYWRkIGEgc21vb3RoIGN1cnZlIHRvIGEgcGxvdC4KClRoZSBkZWZhdWx0IG1ldGhvZCBpcyBhIGZvcm0gb2YgbG9jYWwgYXZlcmFnaW5nLCBhbmQgaW5jbHVkZXMgYQpyZXByZXNlbnRhdGlvbiBvZiB1bmNlcnRhaW50eToKCmBgYHtyLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNC4yNSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAxIDwtIGdncGxvdChmYWl0aGZ1bCwgYWVzKGVydXB0aW9ucywgd2FpdGluZykpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdndGl0bGUoIk9sZCBGYWl0aGZ1bCBFcnVwdGlvbnMiKQpwMiA8LSBnZ3Bsb3QobjUwSywgYWVzKHgsIHkpKSArCiAgICBwcCArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdndGl0bGUoc3ByaW50ZigiJWQgUG9pbnRzIiwgbnJvdyhuNTBLKSkpCnAxIHwgcDIKYGBgCgoKIyMjIEF4aXMgVHJhbnNmb3JtYXRpb24KClZhcmlhYmxlIHRyYW5zZm9ybWF0aW9ucyBhcmUgb2Z0ZW4gaGVscGZ1bC4KClZhcmlhYmxlIHRyYW5zZm9ybWF0aW9ucyBjYW4gYmUgYXBwbGllZCBieQoKKiBwbG90dGluZyB0aGUgdHJhbnNmb3JtZWQgZGF0YQoKKiBwbG90dGluZyBvbiB0cmFuc2Zvcm1lZCBheGVzCgpBeGlzIHRyYW5zZm9ybWF0aW9ucyBgZ2dwbG90YCBzdXBwb3J0czoKCiogYGxvZzEwYCB3aXRoIGBzY2FsZV94X2xvZzEwYCwgc2NhbGVfeV9sb2cxMAoKKiBzcXVhcmUgcm9vdCB3aXRoIGBzY2FsZV94X3NxcnRgLCBgc2NhbGVfeV9zcXJ0YAoKKiByZXZlcnNlZCBheGVzIHdpdGggYHNjYWxlX3hfcmV2ZXJzZWAsIGBzY2FsZV95X3JldmVyc2VgCgpDaGFuZ2luZyBzY2FsZXMgY2FuIG1ha2UgcGxvdCBzaGFwZXMgc2ltcGxlciwgYnV0IG5vbi1saW5lYXIgYXhlcyBhcmUKaGFyZGVyIHRvIGludGVycHJldC4KCmBgYHtyLCBmaWcud2lkdGggPSA5LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeShnYXBtaW5kZXIpCmdhcCA8LSBmaWx0ZXIoZ2FwbWluZGVyLCB5ZWFyID09IDIwMDcpCnAgPC0gZ2dwbG90KGdhcCwKICAgICAgICAgICAgYWVzKHggPSBnZHBQZXJjYXAsCiAgICAgICAgICAgICAgICB5ID0gbGlmZUV4cCwKICAgICAgICAgICAgICAgIGNvbG9yID0gY29udGluZW50LAogICAgICAgICAgICAgICAgc2l6ZSA9IHBvcCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemUgPSA4KQpwMSA8LSBwICsKICAgIGd1aWRlcyhjb2xvciA9ICJub25lIiwKICAgICAgICAgICBzaXplID0gIm5vbmUiKQpwMiA8LSBwICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBndWlkZXMoc2l6ZSA9ICJub25lIikKcDEgKyBwMgpgYGAKCgojIyBDb25kaXRpb25pbmcKCldoZW4gdGhlICR5JCB2YXJpYWJsZSBpcyBhIHJlc3BvbnNlIGl0IGNhbiBhbHNvIGJlIHVzZWZ1bCB0byBleHBsb3JlCnRoZSBjb25kaXRpb25hbCBkaXN0cmlidXRpb24gb2YgJHkkIGdpdmVuIGRpZmZlcmVudCB2YWx1ZXMgb2YgdGhlICR4JAp2YXJpYWJsZS4KCk9uZSB3YXkgdG8gZG8gdGhpcyBpcyB0byBmb2N1cyBvbiB0aGUgZGF0YSBmb3IgbmFycm93IHJhbmdlcyBvZiB0aGUKY29uZGl0aW9uaW5nIHZhcmlhYmxlLgoKVHdvIGFwcHJvYWNoZXMgZm9yIGNyZWF0aW5nIGdyb3VwcyB0byBmb2N1cyBvbjoKCiogVXNlIHRoZSBgYmFzZWAgZnVuY3Rpb24gYGN1dGAsIG9yIG9uZSBvZiB0aGUgYGdnbG90MmAgZnVuY3Rpb25zCgogICAgKiBgY3V0X2ludGVydmFsYAogICAgKiBgY3V0X251bWJlcmAKICAgICogYGN1dF93aWR0aGAKCiogVXNlIHRoZSBgcm91bmRgIGZ1bmN0aW9uLgoKUm91bmRpbmcgdG8gYW4gaW50ZWdlciBvciB1c2luZwoKYGBge3IsIGV2YWwgPSBGQUxTRX0KY3V0X3dpZHRoKHgsIHdpZHRoID0gMSwgY2VudGVyID0gMCkKYGBgCgpwcm9kdWNlcyB0aGVzZSBiaW5zOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwIDwtIGdncGxvdChuNTBLLCBhZXMoeCwgeSkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjA1LCBzaXplID0gMC41KQpwICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0KICAgICAgICAgICAgICAgICAgIHNlcSgtMy41LCA0LjUsIGJ5ID0gMSksCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgY29sb3IgPSAicmVkIikgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgtMywgNCksCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYXMuY2hhcmFjdGVyKHNlcSgtMywgNCkpKQpgYGAKCkRhdGEgd2l0aGluIGVhY2ggZ3JvdXAgY2FuIHRoZW4gYmUgc2hvd24gdXNpbmcgYm94IHBsb3RzICh5b3UgbmVlZCB0bwp1c2UgdGhlIGBncm91cGAgYWVzdGhldGljIHRvIHBsb3Qgb24gYSBjb250aW51b3VzIGF4aXMpOgoKYGBge3J9Cm41MEtfdHJtIDwtIGZpbHRlcihuNTBLLCB4ID4gLTIuNSwgeCA8IDIuNSkKbXV0YXRlKG41MEtfdHJtLCB4cm5kID0gcm91bmQoeCkpIHw+CiAgICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKGdyb3VwID0geHJuZCkpCmBgYAoKVXNpbmcgYGN1dF93aWR0aGAgYW5kIHZpb2xpbiBwbG90czoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbXV0YXRlKG41MEtfdHJtLAogICAgICAgeGN1dCA9IGN1dF93aWR0aCh4LAogICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IDApKSB8PgogICAgZ3JvdXBfYnkoeGN1dCkgfD4KICAgIG11dGF0ZSh4bWVkID0gbWVkaWFuKHgpKSB8PgogICAgdW5ncm91cCgpIHw+CiAgICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgICBnZW9tX3Zpb2xpbihhZXMoZ3JvdXAgPSB4bWVkKSwKICAgICAgICAgICAgICAgIGRyYXdfcXVhbnRpbGVzID0gMC41KQpgYGAKCkFub3RoZXIgb3B0aW9uIGZvciB2aXN1YWxpemluZyB0aGUgY29uZGl0aW9uYWwgZGlzdHJpYnV0aW9ucyBpcwpmYWNldGVkIGRlbnNpdHkgcGxvdHM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cm11dGF0ZShuNTBLX3RybSwgeHJuZCA9IHJvdW5kKHgpKSB8PgogICAgZ2dwbG90KGFlcyh4KSkgKwogICAgZ2VvbV9kZW5zaXR5KGFlcyh5KSkgKwogICAgZmFjZXRfd3JhcCh+IHhybmQsIG5jb2wgPSAxKQpgYGAKCkRlbnNpdHkgcmlkZ2VzIHdvcmsgYXMgd2VsbDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KI3wgd2FybmluZzogZmFsc2UKbGlicmFyeShnZ3JpZGdlcykKbXV0YXRlKG41MEtfdHJtLCB4cm5kID0gcm91bmQoeCkpIHw+CiAgICBnZ3Bsb3QoYWVzKHksIHhybmQsIGdyb3VwID0geHJuZCkpICsKICAgIGdlb21fZGVuc2l0eV9yaWRnZXMocXVhbnRpbGVfbGluZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICBxdWFudGlsZXMgPSAyKQpgYGAKClVzaW5nIGEgbGFyZ2VyIHNldCBvZiBuYXJyb3dlciBiaW5zIGNlbnRlcmVkIG9uIG11bHRpcGxlcyBvZiAxLzI6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cm11dGF0ZShuNTBLX3RybSwgeHJuZCA9IHJvdW5kKDIgKiB4KSAvIDIpIHw+CiAgICBnZ3Bsb3QoYWVzKHksIHhybmQsIGdyb3VwID0geHJuZCkpICsKICAgIGdlb21fZGVuc2l0eV9yaWRnZXMocXVhbnRpbGVfbGluZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICBxdWFudGlsZXMgPSAyKQpgYGAKClNvbWV0aW1lcyBpdCBpcyB1c2VmdWwgdG8gdXNlIGEgc21hbGwgbnVtYmVyIG9mIG5hcnJvdyBiaW5zOgoKYGBge3IsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA0LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcmRhdGEgPC0gZGF0YS5mcmFtZSh4bWluID0gKC0yIDogMikgLSAwLjEsCiAgICAgICAgICAgICAgICAgICAgeG1heCA9ICgtMiA6IDIpICsgMC4xLAogICAgICAgICAgICAgICAgICAgIHltaW4gPSAtSW5mLAogICAgICAgICAgICAgICAgICAgIHltYXggPSBJbmYpCnAxIDwtIHAgKyBnZW9tX3JlY3QoYWVzKHhtaW4gPSB4bWluLCB4bWF4ID0geG1heCwgeW1pbiA9IHltaW4sIHltYXggPSB5bWF4KSwKICAgICAgICAgICAgICAgICAgICBkYXRhID0gcmRhdGEsCiAgICAgICAgICAgICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICBmaWxsID0gInJlZCIsCiAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjIpCgpwMiA8LSBtdXRhdGUobjUwS190cm0sIHhybmQgPSByb3VuZCh4KSkgfD4KICAgIGZpbHRlcihhYnMoeCAtIHhybmQpIDwgMC4xKSB8PgogICAgZ2dwbG90KGFlcyh5LCB4cm5kLCBncm91cCA9IHhybmQpKSArCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHF1YW50aWxlX2xpbmVzID0gVFJVRSwgcXVhbnRpbGVzID0gMikKcDEgfCBwMgpgYGAKCkZvciBleGFtaW5pbmcgY29uZGl0aW9uYWwgZGlzdHJpYnV0aW9ucywgYmlucyBuZWVkIHRvIGJlOgoKKiBuYXJyb3cgZW5vdWdoIHRvIGZvY3VzIG9uIGEgcGFydGljdWxhciAkeCQgdmFsdWU7CgoqIHdpZGUgZW5vdWdoIHRvIGluY2x1ZGUgZW5vdWdoIG9ic2VydmF0aW9ucyBmb3IgYSB1c2VmdWwgdmlzdWFsaXphdGlvbi4KCkZvciBzbWFsbGVyIGRhdGEgc2V0cyBpdCBjYW4gYWxzbyBiZSB1c2VmdWwgdG8gYWxsb3cgYmlucyB0byBvdmVybGFwLgoKKiBgZ2dwbG90YCBkb2VzIG5vdCBwcm92aWRlIHN1cHBvcnQgZm9yIHRoaXMuCgoqIGBsYXR0aWNlYCBkb2VzIHN1cHBvcnQgdGhpcyB3aXRoIF9zaGluZ2xlc18uCgpUaGUgbGF0dGljZSBmdW5jdGlvbnMgYHNoaW5nbGVgIGFuZCBgZXF1YWwuY291bnRgIGNyZWF0ZSBzaGluZ2xlcyB0aGF0CmNhbiBiZSB1c2VkIGluIGBsYXR0aWNlYC1zdHlsZSBmYWNldGluZy4KCgojIyBFeGFtcGxlOiBEaWFtb25kIFByaWNlcwoKVGhlIGBkaWFtb25kc2AgZGF0YSBzZXQgY29udGFpbnMgcHJpY2VzIGFuZCBvdGhlciBhdHRyaWJ1dGVzIGZvcgpgciBzY2FsZXM6OmNvbW1hKG5yb3coZGlhbW9uZHMpKWAgZGlhbW9uZHMuCgpUaGUgYGN1dGAgdmFyaWFibGUgaW5kaWNhdGVzIHRoZSBfcXVhbGl0eV8gb2YgYSBkaWFtb25kJ3MgY3V0LgoKWW91IG1pZ2h0IGV4cGVjdCAnYmV0dGVyJyBjdXRzIHRvIGNvc3QgbW9yZSwgYnV0IHRoYXQgaXMgbm90IHRydWUgb24KYXZlcmFnZToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbW0gPC0gZ3JvdXBfYnkoZGlhbW9uZHMsIGN1dCkgfD4KICAgIHN1bW1hcml6ZShtZWRfcHJpY2UgPSBtZWRpYW4ocHJpY2UpLAogICAgICAgICAgICAgIGF2Z19wcmljZSA9IG1lYW4ocHJpY2UpLAogICAgICAgICAgICAgIG4gPSBsZW5ndGgocHJpY2UpKSB8PgogICAgcGl2b3RfbG9uZ2VyKDIgOiAzLAogICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIndoaWNoIiwKICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAicHJpY2UiKQpnZ3Bsb3QobW0sIGFlcyh4ID0gcHJpY2UsCiAgICAgICAgICAgICAgIHkgPSBjdXQsCiAgICAgICAgICAgICAgIGNvbG9yID0gd2hpY2gpKSArCiAgICBnZW9tX3BvaW50KCwgc2l6ZSA9IDMpCmBgYAoKVGhlIHByb3BvcnRpb24gb2YgZGlhbW9uZHMgYXQgZWFjaCBgY3V0YCBsZXZlbCBpcyBhbHNvIHBlcmhhcHMgc3VycHJpc2luZzoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGRpYW1vbmRzKSArCiAgICBnZW9tX2JhcihhZXMoeCA9IGN1dCksCiAgICAgICAgICAgICBmaWxsID0gImRlZXBza3libHVlMyIpCmBgYAoKQW5kIHRoZSBwcmljZSBkaXN0cmlidXRpb25zIHdpdGhpbiBlYWNoIGBjdXRgIGxldmVsIGRpZmZlciBpbiBzaGFwZToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IHByaWNlLCB5ID0gY3V0KSkgKwogICAgZ2VvbV92aW9saW4oKSArCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHdoaWNoKSwgZGF0YSA9IG1tKQpgYGAKCkFub3RoZXIgaW1wb3J0YW50IGZhY3RvciBpcyB0aGUgc2l6ZSwgbWVhc3VyZWQgaW4gYGNhcmF0YC4KCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGNhcmF0LCB5ID0gY3V0KSkgKwogICAgZ2VvbV92aW9saW4oKQpgYGAKCkhpc3RvZ3JhbXMgd2l0aCBhIG5hcnJvdyBiaW4gd2lkdGggbWF5IGhlbHAgdW5kZXJzdGFuZCB0aGUgdW51c3VhbCBzaGFwZXMuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChkaWFtb25kcykgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKGNhcmF0KSwKICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4wMSkKYGBgCgpUcnkgZmFjZXRpbmcgb24gYGN1dGA6CgpgYGB7ciwgZmlnLmhlaWdodCA9IDYuNSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChkaWFtb25kcykgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKGNhcmF0KSwKICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4wMSkgKwogICAgZmFjZXRfd3JhcCh+IGN1dCwgbmNvbCA9IDEpCmBgYAoKVG8gZm9jdXMgb24gdGhlIHNoYXBlcyBvZiB0aGUgY29uZGl0aW9uYWwgZGlzdHJpYnV0aW9ucyBvZiBjYXJhdCBnaXZlbgpjdXQgdXNlIGB5ID0gYWZ0ZXJfc3RhdChkZW5zaXR5KWA6CgpgYGB7ciwgZmlnLmhlaWdodCA9IDYuNSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChkaWFtb25kcykgKwogICAgZ2VvbV9oaXN0b2dyYW0oCiAgICAgICAgYWVzKHggPSBjYXJhdCwKICAgICAgICAgICAgeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSkpLAogICAgICAgIGJpbndpZHRoID0gMC4wMSkgKwogICAgZmFjZXRfd3JhcCh+IGN1dCwgbmNvbCA9IDEpCmBgYAoKQWx0ZXJuYXRpdmVseSwgeW91IGNhbiBmYWNldCB3aXRoIGBzY2FsZXMgPSAiZnJlZV95ImA6CgpgYGB7ciwgZmlnLmhlaWdodCA9IDYuNSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChkaWFtb25kcykgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKGNhcmF0KSwKICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4wMSkgKwogICAgZmFjZXRfd3JhcCh+IGN1dCwKICAgICAgICAgICAgICAgc2NhbGVzID0gImZyZWVfeSIsCiAgICAgICAgICAgICAgIG5jb2wgPSAxKQpgYGAKCkxhcmdlciBkaWFtb25kcyBoYXZlIGEgaGlnaGVyIHByaWNlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsKICAgIGdlb21fcG9pbnQoKQpgYGAKClRoZXJlIGlzIGEgbG90IG9mIG92ZXItcGxvdHRpbmcsIHNvIGEgZ29vZCBvcHBvcnR1bml0eSB0byB0cnkgc29tZSBvZiB0aGUKdGVjaG5pcXVlcyB3ZSBoYXZlIGxlYXJuZWQuCgpTb21lIGV4cGxvcmF0aW9uczoKClJlZHVjZWQgcG9pbnQgc2l6ZToKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwIDwtIGdncGxvdChkaWFtb25kcywKICAgICAgICAgICAgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkKcCArIGdlb21fcG9pbnQoc2l6ZSA9IDAuMSkKYGBgCgpSZWR1Y2VkIHBvaW50IHNpemUgYW5kIGFscGhhIGxldmVsOgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAgKyBnZW9tX3BvaW50KHNpemUgPSAwLjEsIGFscGhhID0gMC4xKQpgYGAKClRyeSBsb2cgc2NhbGUgZm9yIHByaWNlOgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAgKyBnZW9tX3BvaW50KHNpemUgPSAwLjEsIGFscGhhID0gMC4xKSArCiAgICBzY2FsZV95X2xvZzEwKCkKYGBgCgpMb2cgc2NhbGUgZm9yIGJvdGg6CmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCArIGdlb21fcG9pbnQoc2l6ZSA9IDAuMSwgYWxwaGEgPSAwLjEpICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkKYGBgCgpBZGQgYSBzbW9vdGg6CmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCArIGdlb21fcG9pbnQoc2l6ZSA9IDAuMSwgYWxwaGEgPSAwLjEpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgc2NhbGVfeF9sb2cxMCgpICsKICAgIHNjYWxlX3lfbG9nMTAoKQpgYGAKClNlcGFyYXRlIHNtb290aHMgZm9yIGVhY2ggY3V0OgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAgKyBnZW9tX3BvaW50KHNpemUgPSAwLjEsIGFscGhhID0gMC4xKSArCiAgICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSBjdXQpKSArCiAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgc2NhbGVfeV9sb2cxMCgpCmBgYAoKRXhwbG9yaW5nIGEgc2FtcGxlOgoKYGBge3J9CmQ1MDAgPC0gc2FtcGxlX24oZGlhbW9uZHMsIDUwMCkKYGBgCkRpc3RyaWJ1dGlvbiBvZiBgY3V0YCBpbiB0aGUgc2FtcGxlOgoKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGQ1MDAsIGFlcyh4ID0gY3V0KSkgKwogICAgZ2VvbV9iYXIoKQpgYGAKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGQ1MDAsIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsKICAgIGdlb21fcG9pbnQoKQpgYGAKCllvdSBjYW4gYWxzbyB0YWtlIGEgX3N0cmF0aWZpZWQgc2FtcGxlXyBzdHJhdGlmaWVkIG9uIGN1dCB1c2luZwpgZ3JvdXBfYnlgLCBgc2FtcGxlX2ZyYWNgLCBhbmQgYHVuZ3JvdXBgLgoKRXhwbG9yYXRpb25zIHVzaW5nIGZhY2V0czoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcDAgPC0gZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKQpkTm9DdXQgPC0gbXV0YXRlKGRpYW1vbmRzLCBjdXQgPSBOVUxMKQpwMSA8LSBwMCArIGdlb21fcG9pbnQoYWxwaGEgPSAwLjAxLCBjb2xvciA9ICJncmV5IiwgZGF0YSA9IGROb0N1dCkKcCA8LSBwMSArIGdlb21fcG9pbnQoY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEsIGFscGhhID0gMC4wNSkKIyMgcCArIGZhY2V0X3dyYXAofiBjdXQpCnAgKyBmYWNldF93cmFwKH4gY3V0KSArIHNjYWxlX3hfbG9nMTAoKSArIHNjYWxlX3lfbG9nMTAoKQpgYGAKCkZhY2V0cyB3aXRoIGEgc2FtcGxlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwNTAwIDwtIHAxICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGQ1MDAsCiAgICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsCiAgICAgICAgICAgICAgIHNpemUgPSAxKQojIyBwNTAwCiMjIHA1MDAgKyBmYWNldF93cmFwKH4gY3V0KQpwNTAwICsKICAgIGZhY2V0X3dyYXAofiBjdXQpICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkKYGBgCgpNdXRlZCBkYXRhIHdpdGggc21vb3RoczoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcDEgKwogICAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gY3V0KSwKICAgICAgICAgICAgICAgIHNlID0gRkFMU0UpICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkKYGBgCgpDb25kaXRpb25pbmcsIGBjYXJhdGAgdmFsdWVzIG5lYXIgMSwgMS41LCBvciAyOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpkcm5kIDwtIG11dGF0ZShkaWFtb25kcywKICAgICAgICAgICAgICAgY3JuZCA9IHJvdW5kKDIgKiBjYXJhdCkgLyAyKQojIyBMb29rIGF0IGEgYmFyIGNoYXJ0IG9mIGNvdW50KGRybmQsIGNybmQpCiMjIERyb3AgaGlnaGVyIGNhcmF0IHZhbHVlcyBmcm9tIGRlbnNpdHkgcmlkZ2VzCiMjIG1hcCBjdXQgdG8gY29sb3Igb3IgZmlsbCAobmVlZCB0byB1c2UgZ3JvdXAgPSBpbnRlcmFjdGlvbihjcm5kLCBjdXQpKQojIyB0cnkgbG9nIHNjYWxlIGZvciBwcmljZQpmaWx0ZXIoZHJuZCwKICAgICAgIGNybmQgPD0gMiwgY3JuZCA+PSAxLAogICAgICAgY2FyYXQgPj0gY3JuZCwgY2FyYXQgPD0gY3JuZCArIDAuMSkgfD4KICAgIGdncGxvdChhZXMoeCA9IHByaWNlLCB5ID0gY3V0KSkgKwogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhxdWFudGlsZV9saW5lcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlcyA9IDIpICsKICAgIGZhY2V0X3dyYXAofmNybmQsIG5jb2wgPSAxKQpgYGAKCjwhLS0KRm9jdXMgb24gY2FyYXQgdmFsdWVzIG5lYXIgb25lOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZmlsdGVyKGRpYW1vbmRzLCBjYXJhdCA+PSAwLjk1LCBjYXJhdCA8IDEuMDUpIHw+CiAgICBnZ3Bsb3QoYWVzKHggPSBjdXQsIHkgPSBwcmljZSkpICsKICAgIGdlb21fdmlvbGluKHNjYWxlID0gImNvdW50IiwgZHJhd19xdWFudGlsZXMgPSAwLjUpCiMjIFRyeSBkZW5zaXR5IHJpZGdlcwpgYGAKLS0+CgoKIyMgUmVhZGluZwoKQ2hhcHRlciBbX1Zpc3VhbGl6aW5nIGFzc29jaWF0aW9ucyBhbW9uZyB0d28gb3IgbW9yZSBxdWFudGl0YXRpdmUKICB2YXJpYWJsZXNfXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovdmlzdWFsaXppbmctYXNzb2NpYXRpb25zLmh0bWwpCiAgaW4gW19GdW5kYW1lbnRhbHMgb2YgRGF0YQogIFZpc3VhbGl6YXRpb25fXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovKS4KCgojIyBFeGVyY2lzZXMKCjEuIEEgcGxvdCBvZiBhcnJpdmFsIGRlbGF5IGFnYWluc3QgZGVwYXJ0dXJlIGRlbGF5IGZvciB0aGUgTllDIGZsaWdodAogICBkYXRhIHNob3dzIGEgbG90IG9mIG92ZXItcGxvdHRpbmcsIGV2ZW4gZm9yIGEgMTAlIHNhbXBsZS4KCiAgICBgYGB7cn0KICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KGdncGxvdDIpCiAgICBsaWJyYXJ5KG55Y2ZsaWdodHMxMykKICAgIGZsIDwtIGZpbHRlcihmbGlnaHRzLCBkZXBfZGVsYXkgPCAxMjApIHw+CiAgICAgICAgc2FtcGxlX2ZyYWMoMC4xKQogICAgcCA8LSBnZ3Bsb3QoZmwsIGFlcyh4ID0gZGVwX2RlbGF5LCBhcnJfZGVsYXkpKSArCiAgICAgICAgZ2VvbV9wb2ludCgpCiAgICBwCiAgICBgYGAKCiAgICBUaGlzIG1hc2tzIHRoZSBmYWN0IHRoYXQgdGhyZWUgcXVhcnRlcnMgb2YgdGhlIGZsaWdodHMgaW4gdGhpcwogICAgc2FtcGxlIGhhdmUgZGVwYXJ0dXJlIGRlbGF5cyBvZiBsZXNzIHRoYW4gMTAKICAgIG1pbnV0ZXMuIFN1cGVyaW1wb3NpbmcgZGVuc2l0eSBjb250b3VycyBpcyBvbmUgd2F5IHRvIGFkZHJlc3MgdGhpcwogICAgaXNzdWUuCgogICAgV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBhZGRzIGZvdXIgcmVkIGRlbnNpdHkgY29udG91cnMgdG8gdGhlIHBsb3Q/CgogICAgYS4gYHAgKyBnZW9tX2RlbnNpdHlfMmQoY29sb3IgPSAicmVkIilgCiAgICBiLiBgcCArIGdlb21fZGVuc2l0eV8yZChiaW5zID0gNCwgY29sb3IgPSAicmVkIilgCiAgICBjLiBgcCArIGdlb21faGV4KGJpbnMgPSA1KWAKICAgIGQuIGBwICsgZ2VvbV9kZW5zaXR5XzJkKGJpbnMgPSA1KWAKCjIuIFRoZSBgZGlhbW9uZHNgIGRhdGEgc2V0IGlzIGxhcmdlIGVub3VnaCBmb3IgYSBzY2F0dGVyIHBsb3Qgb2YKICAgIGBwcmljZWAgYWdhaW5zdCBgY2FyYXRgIHRvIHN1ZmZlciBmcm9tIGEgc2lnbmlmaWNhbnQgYW1vdW50IG9mCiAgICBvdmVyLXBsb3R0aW5nLiAgV2l0aCBgcGAgY3JlYXRlZCBhcwoKICAgIGBgYHtyfQogICAgbGlicmFyeShnZ3Bsb3QyKQogICAgcCA8LSBnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpCiAgICBgYGAKCiAgICB3aGljaCBvZiB0aGUgZm9sbG93aW5nIGlzIHRoZSBiZXN0IGNob2ljZSB0byBhZGRyZXNzIHRoZQogICAgb3Zlci1wbG90dGluZyBpc3N1ZT8KCiAgICBhLiBgcCArIGdlb21fcG9pbnQoKWAKICAgIGIuIGBwICsgZ2VvbV9wb2ludChzaXplID0gMC41KWAKICAgIGMuIGBwICsgZ2VvbV9wb2ludChzaXplID0gMC4xLCBhbHBoYSA9IDAuMSlgCiAgICBkLiBgcCArIGdlb21fcG9pbnQocG9zaXRpb24gPSAiaml0dGVyIilgCgoKMy4gIENvbnNpZGVyIHRoZSBjb2RlCjwhLS0gIyMgbm9saW50IHN0YXJ0IC0tPgogICAgYGBge3IsIGV2YWwgPSBGQUxTRX0KICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KGdncGxvdDIpCiAgICBmaWx0ZXIoZGlhbW9uZHMsIGNhcmF0IDwgMykgfD4KICAgICAgICBtdXRhdGUoY3JuZCA9IHJvdW5kKGNhcmF0KSkgfD4KICAgICAgICBmaWx0ZXIoLS0tKSB8PgogICAgICAgIGdncGxvdChhZXMoeCA9IHByaWNlLCB5ID0gY3JuZCwgZ3JvdXAgPSBjcm5kKSkgKwogICAgICAgIGdlb21fZGVuc2l0eV9yaWRnZXMoKQogICAgYGBgCjwhLS0jIyBub2xpbnQgZW5kIC0tPgoKICAgIFdoaWNoIHJlcGxhY2VtZW50IGZvciBgLS0tYCBwcm9kdWNlcyBhIHBsb3Qgc2hvd2luZyB0aGUKICAgIGNvbmRpdGlvbmFsIGRlbnNpdHkgb2YgYHByaWNlYCBnaXZlbiBgY2FyYXRgIGZvciBgY2FyYXRgIHZhbHVlcwogICAgbmVhciBvbmUgYW5kIHRoZSBjb25kaXRpb25hbCBkZW5zaXR5IG9mIGBwcmljZWAgZ2l2ZW4gYGNhcmF0YCBmb3IKICAgIGBjYXJhdGAgdmFsdWVzIG5lYXIgdHdvPwoKICAgIGEuIGBhYnMoY2FyYXQgLSBjcm5kKSA8IDAuMWAKICAgIGIuIGBjYXJhdCA8IDAuMWAKICAgIGMuIGBhYnMoY3JuZCkgPCAxYAogICAgZC4gYGNhcmF0ICsgY3JuZCA8IDJgCg==